//
//  QSIUSBComms.m
//  QSICamera
//
//  Copyright (c) 2011, 2012, Joe Shimkus
//   All rights reserved.
//
//  Redistribution and use in source and binary forms, with or without
//  modification, are permitted provided that the following conditions are
//  met:
//  	• 	Redistributions of source code must retain the above copyright
//      	notice, this list of conditions and the following disclaimer.
//  	• 	Redistributions in binary form must reproduce the above copyright
//        notice, this list of conditions and the following disclaimer in the
//        documentation and/or other materials provided with the distribution.
//  	• 	The name of Joe Shimkus may not be used to endorse or promote
//        products derived from this software without specific prior written
//        permission.
//
//  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
//  IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
//  TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
//  PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
//  OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
//  EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
//  PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
//  PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
//  LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
//  NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
//  SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//

#import "QSIUSBCommsProtected.h"

#import "QSI500USBComms.h"
#import "QSI600USBComms.h"
#import "QSICamera.h"

#import <IOKit/IOCFPlugIn.h>
#import <IOKit/IOKitLib.h>
#import <IOKit/usb/IOUSBLib.h>

/***********************************************************************************************
 ************************************************************************************************
 * QSIUSBComms Private Defines
 ************************************************************************************************
 ***********************************************************************************************/

// The max read buffer size comes from QSI's comments regarding the max read buffer size
// supported by the utilized FTDI chip.
//
// Don't change this.
#define QSIUSBCommsMaxReadBufferSize  65536

/***********************************************************************************************
************************************************************************************************
* QSIUSBComms Private Methods
************************************************************************************************
***********************************************************************************************/

//
// QSIUSBComms Private Class Methods
//
@interface QSIUSBComms (QSIUSBCommsPrivateClassMethods)
@end // @interface QSIUSBComms (QSIUSBCommsPrivateClassMethods)

@implementation QSIUSBComms (QSIUSBCommsPrivateClassMethods)
@end // @implementation QSIUSBComms (QSIUSBCommsPrivateClassMethods)

//
// QSIUSBComms Private Instance Methods
//
@interface QSIUSBComms (QSIUSBCommsPrivateInstanceMethods)
- (QSIStatus) clearPipeStall : (uint8_t) aPipeIndex;
- (QSIStatus) connectInterface;
- (QSIStatus) disconnectInterface;
- (QSIStatus) getDeviceDescriptor : (IOUSBDeviceDescriptor *) aDeviceDescriptor;
- (QSIStatus) purgeReceiveBuffer;
- (QSIStatus) purgeTransmitBuffer;
- (QSIStatus) setFlowControl : (uint16_t) aFlowControlParameter;
@end // @interface QSIUSBComms (QSIUSBCommsPrivateInstanceMethods)

@implementation QSIUSBComms (QSIUSBCommsPrivateInstanceMethods)
- (QSIStatus) clearPipeStall : (uint8_t) aPipeIndex
{
  QSIStatus status    = QSISuccess;
  IOReturn  ioStatus;

  IOUSBInterfaceInterface190 *  *usbInterface = [self usbInterfaceInterface];

  ioStatus = (*usbInterface)->ClearPipeStallBothEnds(usbInterface, aPipeIndex);
  if (ioStatus != kIOReturnSuccess)
  {
    status = QSIFailure;
  }

  return (status);
} // end -clearPipeStall:

- (QSIStatus) connectInterface
{
  QSIStatus status  = QSISuccess;

  // If we don't have the interface, we need to get it.
  if (_interfaceInterface == NULL)
  {
    IOReturn                  ioStatus  = kIOReturnSuccess;
    io_iterator_t             iterator;
    IOUSBFindInterfaceRequest findRequest;

    findRequest.bInterfaceClass     = kIOUSBFindInterfaceDontCare;
    findRequest.bInterfaceSubClass  = kIOUSBFindInterfaceDontCare;
    findRequest.bInterfaceProtocol  = kIOUSBFindInterfaceDontCare;
    findRequest.bAlternateSetting   = kIOUSBFindInterfaceDontCare;

    IOUSBDeviceInterface182 * *usbDevice  = [self usbDeviceInterface];

    ioStatus = (*usbDevice)->CreateInterfaceIterator(usbDevice, &findRequest, &iterator);
    if (ioStatus != kIOReturnSuccess)
    {
      status = QSIOpenFailed;
    }

    if (status == QSISuccess)
    {
      io_service_t  service;

      _interfaceIndex = 0;

      while ((service = IOIteratorNext(iterator)) != IO_OBJECT_NULL)
      {
        // Create an intermediate plug-in.
        IOCFPlugInInterface * *plugIn = NULL;
        SInt32                score;

        ioStatus = IOCreatePlugInInterfaceForService(service,
                                                     kIOUSBInterfaceUserClientTypeID,
                                                     kIOCFPlugInInterfaceID,
                                                     &plugIn,
                                                     &score);

        // No longer need the service object.
        IOObjectRelease(service);

        if (ioStatus != kIOReturnSuccess)
        {
          break;
        }

        // Get the interface interface.
        HRESULT result;

        IOUSBInterfaceInterface190 *  *interfaceInterface = NULL;

        result = (*plugIn)->QueryInterface(plugIn,
                                           CFUUIDGetUUIDBytes(kIOUSBInterfaceInterfaceID190),
                                           (LPVOID *) &interfaceInterface);

        // Don't need the plug-in anymore.
        (*plugIn)->Release(plugIn);

        // Look for the bulk endpoints.
        uint8_t numberOfEndPoints;

        ioStatus = (*interfaceInterface)->GetNumEndpoints(interfaceInterface, &numberOfEndPoints);
        if (ioStatus != kIOReturnSuccess)
        {
          (*interfaceInterface)->Release(interfaceInterface);

          status = QSIOpenFailed;
          break;
        }

        // Need to open the interface to be able to get pipe properties.
        ioStatus = (*interfaceInterface)->USBInterfaceOpen(interfaceInterface);
        if (ioStatus != kIOReturnSuccess)
        {
          (*interfaceInterface)->Release(interfaceInterface);

          status = QSIOpenFailed;
          break;
        }

        uint8_t endpointIndex;
        for (endpointIndex = 0; endpointIndex < numberOfEndPoints; endpointIndex ++)
        {
          uint8_t   direction;
          uint8_t   number;
          uint8_t   transferType;
          uint16_t  maxPacketSize;
          uint8_t   interval;

          ioStatus = (*interfaceInterface)->GetPipeProperties(interfaceInterface,
                                                              endpointIndex + 1,
                                                              &direction,
                                                              &number,
                                                              &transferType,
                                                              &maxPacketSize,
                                                              &interval);
          if (ioStatus != kIOReturnSuccess)
          {
            status = QSIOpenFailed;
            break;
          }

          if (transferType == kUSBBulk)
          {
            if (direction == kUSBIn)
            {
              _bulkInPipeReference  = endpointIndex + 1;
              _bulkInMaxPacketSize  = maxPacketSize;
            }
            else
            {
              _bulkOutPipeReference = endpointIndex + 1;
            }

            if ((_bulkInPipeReference != 0) && (_bulkOutPipeReference != 0))
            {
              break;
            }
          }
        }

        if (status != QSISuccess)
        {
          (*interfaceInterface)->USBInterfaceClose(interfaceInterface);
          (*interfaceInterface)->Release(interfaceInterface);

          break;
        }

        if ((_bulkInPipeReference != 0) && (_bulkOutPipeReference != 0))
        {
          _interfaceInterface = interfaceInterface;

          break;
        }

        // Not an interface with both bulk in and out endpoints.
        _bulkInPipeReference  = 0;
        _bulkOutPipeReference = 0;

        (*interfaceInterface)->USBInterfaceClose(interfaceInterface);
        (*interfaceInterface)->Release(interfaceInterface);

        _interfaceIndex ++;
      }

      IOObjectRelease(iterator);
    }

    // Did we find an interface with the requisite bulk endpoints?
    if ((status == QSISuccess) && (_interfaceInterface == NULL))
    {
      _interfaceIndex = -1;

      status = QSIOpenFailed;
    }
  }
  else
  {
    // We have the interface; we need to open it.
    IOReturn  ioStatus;

    ioStatus = (*_interfaceInterface)->USBInterfaceOpen(_interfaceInterface);
    if (ioStatus != kIOReturnSuccess)
    {
      status = QSIOpenFailed;
    }
  }

  // Set the flow control.
  if (status == QSISuccess)
  {
    status = [self setFlowControl:QSIUSBDeviceSetFlowControlRtsCts];

    if (status != QSISuccess)
    {
      QSIStatus status2 = [self disconnectInterface];

      if (status2 != QSISuccess)
      {
        status = status2;
      }
    }
  }

  return (status);
} // end -connectInterface

- (QSIStatus) disconnectInterface
{
  QSIStatus status    = QSISuccess;

  IOReturn  ioStatus  = kIOReturnSuccess;

  // Disconnect the interface.
  IOUSBInterfaceInterface190 *  *usbInterface = [self usbInterfaceInterface];

  ioStatus = (*usbInterface)->USBInterfaceClose(usbInterface);
  if (ioStatus != kIOReturnSuccess)
  {
    status = QSICloseFailed;
  }

  return (status);
} // end -disconnectInterface

- (QSIStatus) getDeviceDescriptor : (IOUSBDeviceDescriptor *) aDeviceDescriptor
{
  QSIStatus status  = QSISuccess;

  status = [self sendControlRequest:kUSBRqGetDescriptor
                             ofType:USBmakebmRequestType(kUSBIn, kUSBStandard, kUSBDevice)
                          withValue:(kUSBDeviceDesc << 8) | _interfaceIndex
                              index:0
                            timeout:0
                            andData:aDeviceDescriptor
                           ofLength:sizeof(*aDeviceDescriptor)];

  return (status);
} // end -getDeviceDescriptor:

- (QSIStatus) purgeReceiveBuffer
{
  QSIStatus status  = QSISuccess;

  status = [self sendControlRequest:QSIUSBDeviceRequestReset
                             ofType:USBmakebmRequestType(kUSBOut, kUSBVendor, kUSBDevice)
                          withValue:QSIUSBDeviceResetPurgeReceiveBuffer
                              index:_interfaceIndex
                         andTimeout:[self writeTimeout]];

  _readBufferOffset     = 0;
  _readBufferRemaining  = 0;

  return (status);
} // end -purgeReceiveBuffer

- (QSIStatus) purgeTransmitBuffer
{
  QSIStatus status  = QSISuccess;

  status = [self sendControlRequest:QSIUSBDeviceRequestReset
                             ofType:USBmakebmRequestType(kUSBOut, kUSBVendor, kUSBDevice)
                          withValue:QSIUSBDeviceResetPurgeTransmitBuffer
                              index:_interfaceIndex
                         andTimeout:[self writeTimeout]];

  return (status);
} // end -purgeTransmitBuffer

- (QSIStatus) setFlowControl : (uint16_t) aFlowControlParameter
{
  QSIStatus status  = QSISuccess;

  status = [self sendControlRequest:QSIUSBDeviceRequestSetFlowControl
                             ofType:USBmakebmRequestType(kUSBOut, kUSBVendor, kUSBDevice)
                          withValue:0
                              index:aFlowControlParameter | _interfaceIndex
                         andTimeout:[self writeTimeout]];

  return (status);
} // end setFlowControl:
@end // @implementation QSIUSBComms (QSIUSBCommsPrivateInstanceMethods)


/***********************************************************************************************
************************************************************************************************
* QSIUSBComms Infrastructure Methods
************************************************************************************************
***********************************************************************************************/

//
// QSIUSBComms Infrastructure Class Methods
//
@implementation QSIUSBComms (QSIUSBCommsInfrastructureClassMethods)
@end // @implementation QSIUSBComms (QSIUSBCommsInfrastructureClassMethods)

//
// QSIUSBComms Infrastructure Instance Methods
//
@implementation QSIUSBComms (QSIUSBCommsInfrastructureInstanceMethods)
- (void) dealloc
{
  [_readBuffer release];

  if (_interfaceInterface != NULL)
  {
    (*_interfaceInterface)->Release(_interfaceInterface);
  }

  [super dealloc];
} // end -dealloc
@end // @implementation QSIUSBComms (QSIUSBCommsInfrastructureInstanceMethods)

/***********************************************************************************************
************************************************************************************************
* QSIUSBComms Overridden Methods
************************************************************************************************
***********************************************************************************************/

//
// QSIUSBComms Overridden Class Methods
//
@implementation QSIUSBComms (QSIUSBCommsOverriddenClassMethods)
+ (NSArray *) findAllCameras
{
  QSIDebugLog(@"looking for USB cameras");

  NSMutableArray  *cameras  = [NSMutableArray array];

  // Get the 500-series cameras.
  if (cameras != nil)
  {
    NSArray *cameras500 = [QSI500USBComms findAllCameras];

    if (cameras500 != nil)
    {
      [cameras addObjectsFromArray:cameras500];
    }
    else
    {
      cameras = nil;
    }
  }

  // Get the 600-series cameras.
  if (cameras != nil)
  {
    NSArray *cameras600 = [QSI600USBComms findAllCameras];

    if (cameras600 != nil)
    {
      [cameras addObjectsFromArray:cameras600];
    }
    else
    {
      cameras = nil;
    }
  }

  return (cameras);
} // end +findAllCameras
@end // @implementation QSIUSBComms (QSIUSBCommsOverriddenClassMethods)

//
// QSIUSBComms Overridden Instance Methods
//
@implementation QSIUSBComms (QSIUSBCommsOverriddenInstanceMethods)
- (QSIStatus) connect
{
  QSIStatus status  = QSISuccess;

  if (! [self isConnected])
  {
    // Connect the device.
    status = [self connectDevice];

    // Connect the interface.
    if (status == QSISuccess)
    {
      status = [self connectInterface];
      if (status != QSISuccess)
      {
        QSIStatus status2 = [self disconnectDevice];

        if (status2 != QSISuccess)
        {
          status = status2;
        }
      }
    }

    bool  performDisconnect = (status == QSISuccess);

    // Get the buffer for reads.
    if (status == QSISuccess)
    {
      _readBufferOffset     = 0;
      _readBufferRemaining  = 0;
      
      // Make the read buffer size, if possible, a whole number of the max bulk in packet size less than
      // or equal to the maximum buffer size.
      uint32_t  bufferSize  = (QSIUSBCommsMaxReadBufferSize / _bulkInMaxPacketSize) * _bulkInMaxPacketSize;
      
      if (bufferSize == 0)
      {
        // The bulk in max packet size must be greater than the constant max read buffer size we specified (based on QSI's code).
        // It must be safe to use a buffer equal to the bulk in max packet size.
        bufferSize = _bulkInMaxPacketSize;
      }

      _readBuffer = [[NSMutableData alloc] initWithLength:bufferSize];
      if (_readBuffer == nil)
      {
        status = QSIAllocationFailed;
      }
    }

    // Sync up with the bus.
    if (status == QSISuccess)
    {
      status = [self clearPipeStall:_bulkInPipeReference];

      if (status == QSISuccess)
      {
        status = [self clearPipeStall:_bulkOutPipeReference];
      }

      if (status == QSISuccess)
      {
        status = [self purge];
      }
    }

    if ((status != QSISuccess) && (performDisconnect))
    {
      QSIStatus status2;

      status2 = [self disconnect];
      if (status2 != QSISuccess)
      {
        status = status2;
      }
    }
  }

  [self setConnected:(status == QSISuccess)];

  return (status);
} // end -connect

- (id) initWithDeviceInterface : (IUnknownVTbl * *) aDeviceInterface
              shortReadTimeout : (uint16_t) aShortReadTimeout
             shortWriteTimeout : (uint16_t) aShortWriteTimeout
           standardReadTimeout : (uint16_t) aStandardReadTimeout
          standardWriteTimeout : (uint16_t) aStandardWriteTimeout
           extendedReadTimeout : (uint16_t) anExtendedReadTimeout
       andExtendedWriteTimeout : (uint16_t) anExtendedWriteTimeout
{
  self = [super initWithDeviceInterface:aDeviceInterface
                       shortReadTimeout:aShortReadTimeout
                      shortWriteTimeout:aShortWriteTimeout
                    standardReadTimeout:aStandardReadTimeout
                   standardWriteTimeout:aStandardWriteTimeout
                    extendedReadTimeout:anExtendedReadTimeout
                andExtendedWriteTimeout:anExtendedWriteTimeout];
  if (self != nil)
  {
    _bulkInMaxPacketSize  = 0;
    _bulkInPipeReference  = 0;
    _bulkOutPipeReference = 0;
    _interfaceIndex       = -1;
    _interfaceInterface   = NULL;
    _readBufferOffset     = 0;
    _readBufferRemaining  = 0;
    _readBuffer           = nil;
  }

  return (self);
} // end -initWithStandardReadTimeout:standardWriteTimeout:extendedReadTimeout:andExtendedWriteTimeout:

- (QSIStatus) performSetOfReadTimeout : (uint16_t) aReadTimeout
                      andWriteTimeout : (uint16_t) aWriteTimeout
{
  QSIStatus status  = QSISuccess;

  // Nothing to do here.
  // The timeouts are not set on the I/O channel but specified with the I/O request.

  return (status);
} // end -performSetOfReadTimeout:andWriteTimeout:

- (QSIStatus) purge
{
  QSIStatus status  = QSISuccess;

  status = [self purgeReceiveBuffer];
  if (status == QSISuccess)
  {
    status = [self purgeTransmitBuffer];
  }

  return (status);
} // end -purge

- (QSIStatus) readToBuffer : (void *) aBuffer
             numberOfBytes : (uint32_t *) aNumberOfBytes
{
  QSIStatus status  = QSISuccess;

  IOUSBInterfaceInterface190 *  *usbInterface = [self usbInterfaceInterface];

  // Using uint32_t for bytesRead results in a compiler warning of incompatible type
  // when calling ReadPipeTO() on 32-bit x86.
  UInt32  bytesRead = _readBufferRemaining;

  // If there aren't any remaining bytes from a previous read, cycle until we have a response
  // that isn't just the chip status.
  if (_readBufferRemaining == 0)
  {
    _readBufferOffset = 0;

    NSDate  *timeout  = [NSDate dateWithTimeIntervalSinceNow:(((NSTimeInterval) [self readTimeout]) / 1000)]; // msec => sec.

    if (timeout == nil)
    {
      status = QSIAllocationFailed;
    }

    if (status == QSISuccess)
    {
      IOReturn  ioStatus;

      while (yes)
      {
        // Since the read buffer's size is capped at 64K (QSIUSBCommsMaxReadBufferSize) we can safely
        // cast the results which avoids a compilation warning for 64-bit.
        bytesRead = (UInt32) [_readBuffer length];

        ioStatus = (*usbInterface)->ReadPipeTO(usbInterface,
                                               _bulkInPipeReference,
                                               [_readBuffer mutableBytes],
                                               &bytesRead,
                                               [self readTimeout],
                                               [self readTimeout]);

        // Check for an error or not enough read.
        if ((ioStatus != kIOReturnSuccess) || (bytesRead < 2))
        {
          if (ioStatus != kIOReturnSuccess)
          {
            QSILog(@"read from pipe failed; IOReturn = 0x%x", ioStatus);
          }
          else
          {
            QSILog(@"read returned insufficient bytes (%d)", bytesRead);
          }

          status = QSIReadFailed;
          break;
        }

        // If we've read more than chip status bytes, break out of the loop and move on to
        // processing the response.
        if (bytesRead > 2)
        {
          break;
        }

        // If we hit our timeout, stop.
        NSTimeInterval timeInterval = [timeout timeIntervalSinceNow];
        if (timeInterval <= 0)
        {
          QSILog(@"read timed out");

          status = QSIReadFailed;
          break;
        }

        // Sleep a bit; 10 us.
        usleep(10);
      }
    }
  }

  // Process the actual response.
  if (status == QSISuccess)
  {
    IOReturn  ioStatus  = kIOReturnSuccess;

    uint32_t  callerBytesTransfered = 0;

    do
    {
      uint32_t  bytesToCopy;

      _readBufferRemaining = bytesRead;

      do
      {
        if ((_readBufferOffset % _bulkInMaxPacketSize) == 0)
        {
          _readBufferOffset     += 2;
          _readBufferRemaining  -= 2;
        }

        bytesToCopy = _bulkInMaxPacketSize - (_readBufferOffset % _bulkInMaxPacketSize);
        if (bytesToCopy > _readBufferRemaining)
        {
          bytesToCopy = _readBufferRemaining;
        }
        if (bytesToCopy > ((*aNumberOfBytes) - callerBytesTransfered))
        {
          bytesToCopy = (*aNumberOfBytes) - callerBytesTransfered;
        }

        memcpy(aBuffer + callerBytesTransfered,
               ((uint8_t *) [_readBuffer mutableBytes]) + _readBufferOffset,
               bytesToCopy);

        _readBufferOffset     += bytesToCopy;
        _readBufferRemaining  -= bytesToCopy;

        callerBytesTransfered += bytesToCopy;
      } while ((callerBytesTransfered < (*aNumberOfBytes)) && (_readBufferRemaining > 0));

      if (callerBytesTransfered >= (*aNumberOfBytes))
      {
        break;
      }

      // Since the read buffer's size is capped at 64K (QSIUSBCommsMaxReadBufferSize) we can safely
      // cast the results which avoids a compilation warning for 64-bit.
      bytesRead = (UInt32) [_readBuffer length];

      ioStatus = (*usbInterface)->ReadPipeTO(usbInterface,
                                             _bulkInPipeReference,
                                             [_readBuffer mutableBytes],
                                             &bytesRead,
                                             [self readTimeout],
                                             [self readTimeout]);

      _readBufferOffset = 0;
    } while (ioStatus == kIOReturnSuccess);

    if (callerBytesTransfered < (*aNumberOfBytes))
    {
      QSILog(@"insufficient bytes read, expected: %d, read: %d; IOReturn = 0x%x",
             *aNumberOfBytes,
             callerBytesTransfered,
             ioStatus);

      status = QSIReadFailed;
    }
  }

  if (status == QSIReadFailed)
  {
    _readBufferRemaining  = 0;

    QSIStatus status2 = [self clearPipeStall:_bulkInPipeReference];

    if (status2 != QSISuccess)
    {
      status = status2;
    }
  }

  return (status);
} // end -readToBuffer:numberOfBytes:

- (QSIStatus) writeFromBuffer : (void *) aBuffer
                numberOfBytes : (uint32_t *) aNumberOfBytes
{
  QSIStatus status  = QSISuccess;

  IOUSBInterfaceInterface190 *  *usbInterface = [self usbInterfaceInterface];

  IOReturn  ioStatus;

  ioStatus = (*usbInterface)->WritePipeTO(usbInterface,
                                          _bulkOutPipeReference,
                                          aBuffer,
                                          *aNumberOfBytes,
                                          [self writeTimeout],
                                          [self writeTimeout]);
  if (ioStatus != kIOReturnSuccess)
  {
    QSILog(@"write to pipe failed; IOReturn = 0x%x", ioStatus);

    status = QSIWriteFailed;
  }

  if (status != QSISuccess)
  {
    QSIStatus status2 = [self clearPipeStall:_bulkOutPipeReference];

    if (status2 != QSISuccess)
    {
      status = status2;
    }
  }

  return (status);
} // end -writeFromBuffer:numberOfBytes:
@end // @implementation QSIUSBComms (QSIUSBCommsOverriddenInstanceMethods)

/***********************************************************************************************
************************************************************************************************
* QSIUSBComms Public Methods
************************************************************************************************
***********************************************************************************************/

//
// QSIUSBComms Public Class Methods
//
@implementation QSIUSBComms (QSIUSBCommsPublicClassMethods)
@end // @implementation QSIUSBComms (QSIUSBCommsPublicClassMethods)

//
// QSIUSBComms Public Instance Methods
//
@implementation QSIUSBComms (QSIUSBCommsPublicInstanceMethods)
@end // @implementation QSIUSBComms (QSIUSBCommsPublicInstanceMethods)

/***********************************************************************************************
************************************************************************************************
* QSIUSBComms Protected Methods
************************************************************************************************
***********************************************************************************************/

//
// QSIUSBComms Protected Class Methods
//
@implementation QSIUSBComms (QSIUSBCommsProtectedClassMethods)
+ (NSArray *) findAllCamerasWithVendorId : (int32_t) aVendorId
                            andProductId : (int32_t) aProductId
{
  QSIDebugLog(@"looking for vendor: 0x%x and product: 0x%x", aVendorId, aProductId);

  NSMutableArray  *cameras = [[[NSMutableArray alloc] init] autorelease];

  if (cameras != nil)
  {
    IOReturn  ioStatus  = kIOReturnSuccess;

    CFMutableDictionaryRef  matchingDictionary;

    // Create a matching dictionary for USB devices.
    matchingDictionary = IOServiceMatching(kIOUSBDeviceClassName);
    if (matchingDictionary == NULL)
    {
      ioStatus = kIOReturnError;
    }

    if (ioStatus == kIOReturnSuccess)
    {
      CFNumberRef numberReference;

      // Create a CFNumber for the vendor ID and add it to the matching dictionary.
      numberReference = CFNumberCreate(kCFAllocatorDefault,
                                       kCFNumberSInt32Type,
                                       &aVendorId);

      CFDictionarySetValue(matchingDictionary,
                           CFSTR(kUSBVendorID),
                           numberReference);

      CFRelease(numberReference);

      // Create a CFNumber for the product ID and add it to the matching dictionary.
      numberReference = CFNumberCreate(kCFAllocatorDefault,
                                       kCFNumberSInt32Type,
                                       &aProductId);

      CFDictionarySetValue(matchingDictionary,
                           CFSTR(kUSBProductID),
                           numberReference);

      CFRelease(numberReference);
    }

    // Find the cameras.
    io_iterator_t iterator;

    if (ioStatus == kIOReturnSuccess)
    {
      ioStatus = IOServiceGetMatchingServices(kIOMasterPortDefault,
                                              matchingDictionary,
                                              &iterator);

      // IOServiceGetMatchingServices() always performs a reference count decrement on the dictionary.
      // Since we never retained it, it's gone.
      matchingDictionary = NULL;
    }

    if ((ioStatus == kIOReturnSuccess) && (iterator != 0))
    {
      // Iterate over the found devices and create appropriate cameras.
      io_object_t currentDevice;

      while ((currentDevice = IOIteratorNext(iterator)) != 0)
      {
        // Create an intermediate plug-in.
        IOCFPlugInInterface * *plugIn = NULL;
        SInt32                score;

        ioStatus = IOCreatePlugInInterfaceForService(currentDevice,
                                                     kIOUSBDeviceUserClientTypeID,
                                                     kIOCFPlugInInterfaceID,
                                                     &plugIn,
                                                     &score);

        // No longer need the device object.
        IOObjectRelease(currentDevice);

        if (ioStatus != kIOReturnSuccess)
        {
          break;
        }

        // Get the device interface.
        HRESULT result;

        IOUSBDeviceInterface182 * *deviceInterface  = NULL;

        result = (*plugIn)->QueryInterface(plugIn,
                                           CFUUIDGetUUIDBytes(kIOUSBDeviceInterfaceID182),
                                           (LPVOID *) &deviceInterface);

        // Don't need the plug-in anymore.
        (*plugIn)->Release(plugIn);

        // We now have the device interface.
        // Create ourselves a camera from it.
        QSICamera   *camera;

        camera = [self cameraUsingDeviceInterface:(IUnknownVTbl * *) deviceInterface];

        if (camera != nil)
        {
          [cameras addObject:camera];
        }

        // We don't care about the device interface any more.
        (*deviceInterface)->Release(deviceInterface);
      }

      IOObjectRelease(iterator);
    }
  }

  return (cameras);
} // end +findAllCamerasWithVendorId:andProductId:
@end // @implementation QSIUSBComms (QSIUSBCommsProtectedClassMethods)

//
// QSIUSBComms Protected Instance Methods
//
@implementation QSIUSBComms (QSIUSBCommsProtectedInstanceMethods)
- (QSIStatus) connectDevice
{
  QSIStatus status    = QSISuccess;
  IOReturn  ioStatus  = kIOReturnSuccess;

  // Connect the device.
  IOUSBDeviceInterface182 * *usbDevice = [self usbDeviceInterface];

  ioStatus = (*usbDevice)->USBDeviceOpen(usbDevice);
  if (ioStatus != kIOReturnSuccess)
  {
    status = QSIOpenFailed;
  }

  //   // Establish the correct configuration.
  //   if (status == QSISuccess)
  //   {
  //     ioStatus = (*usbDevice)->SetConfiguration(usbDevice, 1);
  //     if (ioStatus != kIOReturnSuccess)
  //     {
  //      status = QSIOpenFailed;
  //     }
  //   }

  return (status);
} // end -connectDevice

- (QSIStatus) disconnectDevice
{
  QSIStatus status    = QSISuccess;

  IOReturn  ioStatus  = kIOReturnSuccess;

  IOUSBDeviceInterface182 * *usbDevice = [self usbDeviceInterface];

  ioStatus = (*usbDevice)->USBDeviceClose(usbDevice);
  if (ioStatus != kIOReturnSuccess)
  {
    status = QSICloseFailed;
  }

  return (status);
} // end -disconnectDevice

- (IOUSBDeviceInterface182 * *) usbDeviceInterface
{
  return ((IOUSBDeviceInterface182 * *) [self deviceInterface]);
} // end -usbDeviceInterface

- (IOUSBInterfaceInterface190 * *) usbInterfaceInterface
{
  return (_interfaceInterface);
} // end -usbInterfaceInterface

- (QSIStatus) sendControlRequest : (uint8_t)  aRequest
                          ofType : (uint8_t)  aType
                       withValue : (uint16_t) aValue
                           index : (uint16_t) anIndex
                      andTimeout : (uint16_t) aTimeout
{
  return ([self sendControlRequest:aRequest
                            ofType:aType
                         withValue:aValue
                             index:anIndex
                           timeout:aTimeout
                           andData:NULL
                          ofLength:0]);
} // end -sendControlRequest:ofType:withValue:index:andTimeout:

- (QSIStatus) sendControlRequest : (uint8_t)  aRequest
                          ofType : (uint8_t)  aType
                       withValue : (uint16_t) aValue
                           index : (uint16_t) anIndex
                         timeout : (uint16_t) aTimeout
                         andData : (void *)   aBuffer
                        ofLength : (uint16_t) aNumberOfBytes
{
  QSIStatus status  = QSISuccess;

  IOUSBDevRequestTO request;

  request.bmRequestType     = aType;
  request.bRequest          = aRequest;
  request.wValue            = aValue;
  request.wIndex            = anIndex;
  request.wLength           = aNumberOfBytes;
  request.pData             = aBuffer;
  request.wLenDone          = 0;
  request.noDataTimeout     = aTimeout;
  request.completionTimeout = aTimeout;

  IOUSBDeviceInterface182 * *usbDevice  = [self usbDeviceInterface];

  IOReturn  ioStatus;

  ioStatus = (*usbDevice)->DeviceRequestTO(usbDevice, &request);
  if (ioStatus != kIOReturnSuccess)
  {
    QSILog(@"control request failed; IOReturn = 0x%x", ioStatus);

    status = QSIControlFailed;
  }

  return (status);
} // end -sendControlRequest:ofType:withValue:index:timeout:andData:ofLength:
@end // @implementation QSIUSBComms (QSIUSBCommsProtectedInstanceMethods)

/***********************************************************************************************
************************************************************************************************
* The QSIUSBComms
************************************************************************************************
***********************************************************************************************/

@implementation QSIUSBComms
//
// The QSIComms protocol
//
- (QSIStatus) disconnect
{
  QSIStatus status  = QSISuccess;

  if ([self isConnected])
  {
    // Disconnect the interface.
    status = [self disconnectInterface];

    // Disconnect the device.
    if (status == QSISuccess)
    {
      status = [self disconnectDevice];

      if (status != QSISuccess)
      {
        // Reconnect the interface.
        status = [self connectInterface];

        if (status == QSISuccess)
        {
          status = QSICloseFailed;
        }
      }
    }
  }

  [self setConnected:(status != QSISuccess)];

  return (status);
} // end -disconnect
@end // @implementation QSIUSBComms
