//
// USBDevice.m
// iOUSB
//
// Created by Brandon on 2018-05-28.
// Copyright © 2018 XIO. All rights reserved.
//
#include "USBDevice.hxx"
#include <memory>
struct USBNotification
{
USBDeviceManager* _this;
IONotificationPortRef notificationPort;
io_iterator_t deviceAddedIterator;
io_iterator_t deviceRemovedIterator;
io_object_t notification;
CFRunLoopRef runLoop;
void(*onDeviceAdded)(USBDevice *device);
void(*onDeviceDisconnected)(USBDevice *device);
};
struct DeviceMessage
{
USBDevice* _this;
USBNotification* notification;
};
#pragma mark - Utilities
#define CFSAFERELEASE(ref) if (ref) CFRelease(ref)
void DeviceAdded(void *userInfo, io_iterator_t iterator);
void DeviceDisconnected(void *userInfo, io_iterator_t iterator);
void DeviceMessageReceived(void *userInfo, io_service_t service, natural_t messageType, void *messageArgument);
/// Converts IOKit errors to human readable strings.
std::string usb_human_error_string(IOReturn errorCode)
{
// #define err_get_system(err) (((err)>>26)&0x3f)
// #define err_get_sub(err) (((err)>>14)&0xfff)
// #define err_get_code(err) ((err)&0x3fff)
switch (errorCode) {
case kIOUSBUnknownPipeErr:
return string_format("Pipe Ref Not Recognized (%08x)", errorCode);
case kIOUSBTooManyPipesErr:
return string_format("Too Many Pipes (%08x)", errorCode);
case kIOUSBNoAsyncPortErr:
return string_format("No Async Port (%08x)", errorCode);
case kIOUSBNotEnoughPipesErr:
return string_format("Not Enough Pipes in Interface (%08x)", errorCode);
case kIOUSBNotEnoughPowerErr:
return string_format("Not Enough Power for Selected Configuration (%08x)", errorCode);
case kIOUSBEndpointNotFound:
return string_format("Endpoint Not Found (%08x)", errorCode);
case kIOUSBConfigNotFound:
return string_format("Configuration Not Found (%08x)", errorCode);
case kIOUSBTransactionTimeout:
return string_format("Transaction Timed Out (%08x)", errorCode);
case kIOUSBTransactionReturned:
return string_format("Transaction has been returned to the caller (%08x)", errorCode);
case kIOUSBPipeStalled:
return string_format("Pipe has stalled, Error needs to be cleared (%08x)", errorCode);
case kIOUSBInterfaceNotFound:
return string_format("Interface Ref Not Recognized (%08x)", errorCode);
case kIOUSBLowLatencyBufferNotPreviouslyAllocated:
return string_format("Attempted to use user land low latency isoc calls w/out calling PrepareBuffer (on the data buffer) first (%08x)", errorCode);
case kIOUSBLowLatencyFrameListNotPreviouslyAllocated:
return string_format("Attempted to use user land low latency isoc calls w/out calling PrepareBuffer (on the frame list) first (%08x)", errorCode);
case kIOUSBHighSpeedSplitError:
return string_format("Error to hub on high speed bus trying to do split transaction (%08x)", errorCode);
case kIOUSBSyncRequestOnWLThread:
return string_format("A synchronous USB request was made on the workloop thread (from a callback?). Only async requests are permitted in that case (%08x)", errorCode);
case kIOUSBDeviceTransferredToCompanion:
return string_format("The device has been tranferred to another controller for enumeration (%08x)", errorCode);
case kIOUSBClearPipeStallNotRecursive:
return string_format("ClearPipeStall should not be called recursively (%08x)", errorCode);
case kIOUSBDevicePortWasNotSuspended:
return string_format("Port was not suspended (%08x)", errorCode);
case kIOUSBEndpointCountExceeded:
return string_format("The endpoint was not created because the controller cannot support more endpoints (%08x)", errorCode);
case kIOUSBDeviceCountExceeded:
return string_format("The device cannot be enumerated because the controller cannot support more devices (%08x)", errorCode);
case kIOUSBStreamsNotSupported:
return string_format("The request cannot be completed because the XHCI controller does not support streams (%08x)", errorCode);
case kIOUSBInvalidSSEndpoint:
return string_format("An endpoint found in a SuperSpeed device is invalid (usually because there is no Endpoint Companion Descriptor) (%08x)", errorCode);
case kIOUSBTooManyTransactionsPending:
return string_format("The transaction cannot be submitted because it would exceed the allowed number of pending transactions (%08x)", errorCode);
default:
return string_format("Error Code (%08x)\n-- System: (%02X), SubSystem: (%02X), Code: (%02X) ", errorCode, err_get_system(errorCode), err_get_sub(errorCode), err_get_code(errorCode));
}
}
std::string human_error_string(IOReturn errorCode)
{
switch (errorCode) {
case kIOReturnSuccess:
return string_format("Success (%08x)", errorCode);
case kIOReturnError:
return string_format("General Error (%08x)", errorCode);
case kIOReturnNoMemory:
return string_format("Cannot Allocate Memory (%08x)", errorCode);
case kIOReturnNoResources:
return string_format("Resource Shortage (%08x)", errorCode);
case kIOReturnIPCError:
return string_format("IPC Error (%08x)", errorCode);
case kIOReturnNoDevice:
return string_format("No Such Device (%08x)", errorCode);
case kIOReturnNotPrivileged:
return string_format("Insufficient Privileges (%08x)", errorCode);
case kIOReturnBadArgument:
return string_format("Invalid Argument (%08x)", errorCode);
case kIOReturnLockedRead:
return string_format("Device Read Locked (%08x)", errorCode);
case kIOReturnLockedWrite:
return string_format("Device Write Locked (%08x)", errorCode);
case kIOReturnExclusiveAccess:
return string_format("Exclusive Access and Device already opened (%08x)", errorCode);
case kIOReturnBadMessageID:
return string_format("Sent/Received Messages had different MSG_ID (%08x)", errorCode);
case kIOReturnUnsupported:
return string_format("Unsupported Function (%08x)", errorCode);
case kIOReturnVMError:
return string_format("Misc. VM Failure (%08x)", errorCode);
case kIOReturnInternalError:
return string_format("Internal Error (%08x)", errorCode);
case kIOReturnIOError:
return string_format("General I/O Error (%08x)", errorCode);
case kIOReturnCannotLock:
return string_format("Can't Acquire Lock (%08x)", errorCode);
case kIOReturnNotOpen:
return string_format("Device Not Open (%08x)", errorCode);
case kIOReturnNotReadable:
return string_format("Read Not Supported (%08x)", errorCode);
case kIOReturnNotWritable:
return string_format("Write Not Supported (%08x)", errorCode);
case kIOReturnNotAligned:
return string_format("Alignment Error (%08x)", errorCode);
case kIOReturnBadMedia:
return string_format("Media Error (%08x)", errorCode);
case kIOReturnStillOpen:
return string_format("Device(s) Still Open (%08x)", errorCode);
case kIOReturnRLDError:
return string_format("RLD Failure (%08x)", errorCode);
case kIOReturnDMAError:
return string_format("DMA Failure (%08x)", errorCode);
case kIOReturnBusy:
return string_format("Device Busy (%08x)", errorCode);
case kIOReturnTimeout:
return string_format("I/O Timeout (%08x)", errorCode);
case kIOReturnOffline:
return string_format("Device Offline (%08x)", errorCode);
case kIOReturnNotReady:
return string_format("Not Ready (%08x)", errorCode);
case kIOReturnNotAttached:
return string_format("Device Not Attached (%08x)", errorCode);
case kIOReturnNoChannels:
return string_format("No DMA Channels Left (%08x)", errorCode);
case kIOReturnNoSpace:
return string_format("No Space For Data (%08x)", errorCode);
case kIOReturnPortExists:
return string_format("Port Already Exists (%08x)", errorCode);
case kIOReturnCannotWire:
return string_format("Can't Write Down (%08x)", errorCode);
case kIOReturnNoInterrupt:
return string_format("No Interrupt Attached (%08x)", errorCode);
case kIOReturnNoFrames:
return string_format("No DMA Frames Enqueued (%08x)", errorCode);
case kIOReturnMessageTooLarge:
return string_format("Oversized MSG Received On Interrupt Port (%08x)", errorCode);
case kIOReturnNotPermitted:
return string_format("Not Permitted (%08x)", errorCode);
case kIOReturnNoPower:
return string_format("No Power To Device (%08x)", errorCode);
case kIOReturnNoMedia:
return string_format("Media Not Present (%08x)", errorCode);
case kIOReturnUnformattedMedia:
return string_format("Media Not Formatted (%08x)", errorCode);
case kIOReturnUnsupportedMode:
return string_format("No Such Mode (%08x)", errorCode);
case kIOReturnUnderrun:
return string_format("Buffer Underflow (%08x)", errorCode);
case kIOReturnOverrun:
return string_format("Buffer Overflow (%08x)", errorCode);
case kIOReturnDeviceError:
return string_format("The Device Is Not Working Properly! (%08x)", errorCode);
case kIOReturnNoCompletion:
return string_format("A Completion Routine Is Required (%08x)", errorCode);
case kIOReturnAborted:
return string_format("Operation Aborted (%08x)", errorCode);
case kIOReturnNoBandwidth:
return string_format("Bus Bandwidth Would Be Exceeded (%08x)", errorCode);
case kIOReturnNotResponding:
return string_format("Device Not Responding (%08x)", errorCode);
case kIOReturnIsoTooOld:
return string_format("ISOChronous I/O request for distance past! (%08x)", errorCode);
case kIOReturnIsoTooNew:
return string_format("ISOChronous I/O request for distant future! (%08x)", errorCode);
case kIOReturnNotFound:
return string_format("Data Not Found (%08x)", errorCode);
case kIOReturnInvalid:
return string_format("Should Never Be Seen(%08x)", errorCode);
default:
/// See here for more: https://developer.apple.com/library/content/qa/qa1075/_index.html
return usb_human_error_string(errorCode);
}
}
#pragma mark - Device Private
USBDevice::USBDevice(io_object_t device) : deviceInterface(nullptr), interface(nullptr), device(device), controlPipeRef(0x00), readPipeRef(0x01), writePipeRef(0x02)
{
IOObjectRetain(device);
}
USBDevice::~USBDevice()
{
Close();
IOObjectRelease(device);
}
void** USBDevice::GetInterface(io_object_t device, CFUUIDRef type, CFUUIDRef uuid)
{
/// Create a plugin interface for the service.
IOCFPlugInInterface **plugInInterface = nullptr;
SInt32 score = 0;
kern_return_t kr = IOCreatePlugInInterfaceForService(device, type, kIOCFPlugInInterfaceID, &plugInInterface, &score);
if ((kIOReturnSuccess != kr) || !plugInInterface)
{
std::cerr<<human_error_string(kr)<<"\n";
return nullptr;
}
/// Get an interface from the plugin.. and release the plugin
void **interface = nullptr;
HRESULT res = (*plugInInterface)->QueryInterface(plugInInterface, CFUUIDGetUUIDBytes(uuid), (LPVOID*) &interface);
(*plugInInterface)->Release(plugInInterface);
if (res || !interface)
{
return nullptr;
}
return interface;
}
int USBDevice::GetProperty(std::string propertyName)
{
CFStringRef name = CFStringCreateWithCString(kCFAllocatorDefault, propertyName.c_str(), kCFStringEncodingUTF8);
CFNumberRef number = (CFNumberRef)IORegistryEntryCreateCFProperty(device, name, kCFAllocatorDefault, 0);
if (number)
{
int value = 0;
CFNumberGetValue(number, kCFNumberSInt32Type, &value);
CFSAFERELEASE(number);
CFSAFERELEASE(name);
return value;
}
CFSAFERELEASE(name);
return -1;
}
std::string USBDevice::GetStringDescriptor(std::uint8_t index)
{
std::uint8_t requestBuffer[256];
IOUSBDevRequest request = {
.bmRequestType = USBmakebmRequestType(kUSBIn, kUSBStandard, kUSBDevice),
.bRequest = kUSBRqGetDescriptor,
.wValue = static_cast<std::uint16_t>((kUSBStringDesc << 8) | index),
.wIndex = 0x409, //English
.wLength = sizeof(requestBuffer),
.pData = requestBuffer
};
kern_return_t kr = (*deviceInterface)->DeviceRequest(deviceInterface, &request);
if (kr != KERN_SUCCESS)
{
std::cerr<<human_error_string(kr)<<"\n";
return "";
}
CFStringRef descriptor = CFStringCreateWithBytes(kCFAllocatorDefault, &requestBuffer[2], requestBuffer[0] - 2, kCFStringEncodingUTF16LE, false);
CFIndex length = CFStringGetLength(descriptor);
CFIndex maxSize = CFStringGetMaximumSizeForEncoding(length, kCFStringEncodingUTF8) + 1;
std::string result(maxSize, '\0');
CFStringGetCString(descriptor, &result[0], maxSize, kCFStringEncodingUTF8);
CFSAFERELEASE(descriptor);
return result;
}
void USBDevice::GetPipeRefs()
{
controlPipeRef = 0x00;
readPipeRef = 0x01;
writePipeRef = 0x02;
if (interface)
{
UInt8 interfaceNumEndpoints = 0;
IOReturn err = (*interface)->GetNumEndpoints(interface, &interfaceNumEndpoints);
if (err)
{
std::cerr<<human_error_string(err)<<"\n";
return;
}
for (UInt8 ref = 1; ref <= interfaceNumEndpoints; ++ref)
{
UInt8 direction;
UInt8 number;
UInt8 transferType;
UInt16 maxPacketSize;
UInt8 interval;
IOReturn kr = (*interface)->GetPipeProperties(interface, ref, &direction, &number, &transferType, &maxPacketSize, &interval);
if (kr == kIOReturnSuccess)
{
if (transferType == kUSBControl)
{
controlPipeRef = 0;
}
else if (transferType == kUSBBulk)
{
if (direction == kUSBIn)
{
readPipeRef = ref;
}
else if (direction == kUSBOut)
{
writePipeRef = ref;
}
}
}
}
}
}
std::string USBDevice::GetName()
{
io_string_t name = {0};
kern_return_t kr = IORegistryEntryGetName(device, name);
if (kr != KERN_SUCCESS)
{
std::cerr<<human_error_string(kr)<<"\n";
}
return std::string(name);
}
std::string USBDevice::GetClass()
{
io_string_t name = {0};
kern_return_t kr = IOObjectGetClass(device, name);
if (kr != KERN_SUCCESS)
{
std::cerr<<human_error_string(kr)<<"\n";
}
return std::string(name);
}
std::string USBDevice::GetPath()
{
io_string_t path = {0};
kern_return_t kr = IORegistryEntryGetPath(device, kIOServicePlane, path);
if (kr != KERN_SUCCESS)
{
std::cerr<<human_error_string(kr)<<"\n";
}
return std::string(path);
}
std::int32_t USBDevice::GetVendorID()
{
return GetProperty(kUSBVendorID);
}
std::int32_t USBDevice::GetProductID()
{
return GetProperty(kUSBProductID);
}
std::string USBDevice::GetSerialNumber()
{
if (deviceInterface)
{
std::uint8_t index = 0;
IOReturn kr = (*deviceInterface)->USBGetSerialNumberStringIndex(deviceInterface, &index);
if (kr != kIOReturnSuccess)
{
std::cerr<<human_error_string(kr)<<"\n";
return "";
}
return GetStringDescriptor(index);
}
return "";
}
std::string USBDevice::GetManufacturer()
{
if (deviceInterface)
{
std::uint8_t index = 0;
IOReturn kr = (*deviceInterface)->USBGetManufacturerStringIndex(deviceInterface, &index);
if (kr != kIOReturnSuccess)
{
std::cerr<<human_error_string(kr)<<"\n";
return "";
}
return GetStringDescriptor(index);
}
return "";
}
std::uint32_t USBDevice::GetLocationID()
{
UInt32 locationID = -1;
IOUSBDeviceInterface **deviceInterface = reinterpret_cast<IOUSBDeviceInterface **>(GetInterface(device, kIOUSBDeviceUserClientTypeID, kIOUSBDeviceInterfaceID));
if (deviceInterface)
{
// Get the location ID from the USB Interface
(*deviceInterface)->GetLocationID(deviceInterface, &locationID);
// Cleanup
(*deviceInterface)->Release(deviceInterface);
deviceInterface = nil;
}
return locationID;
}
std::string USBDevice::PrintInterface()
{
UInt8 interfaceNumEndpoints = 0;
IOReturn err = (*interface)->GetNumEndpoints(interface, &interfaceNumEndpoints);
if (err)
{
std::cerr<<human_error_string(err)<<"\n";
return string_format("Unable to get number of endpoints (%08x)", err);
}
std::string output = string_format("Interface has %d endpoints\n", interfaceNumEndpoints);
/// USUALLY..
/// Index 0 = DeviceControl/ControlRequest.
/// Index 1 = DeviceRead
/// Index 2 = DeviceWrite
for (UInt8 pipeRef = 1; pipeRef <= interfaceNumEndpoints; ++pipeRef)
{
UInt8 direction;
UInt8 number;
UInt8 transferType;
UInt16 maxPacketSize;
UInt8 interval;
IOReturn kr = (*interface)->GetPipeProperties(interface, pipeRef, &direction, &number, &transferType, &maxPacketSize, &interval);
if (kr != kIOReturnSuccess)
{
output += string_format("Unable to get properties of pipe %d (%08x)\n", pipeRef, kr);
}
else
{
std::string directions[] = {"Out", "In", "None", "Any"};
std::string transferTypes[] = {"Control", "ISOC", "Bulk", "Interrupt", "Any"};
output += string_format("Pipe: %d, Direction: %s, TransferType: %s, MaxPacketSize: %d\n", pipeRef, directions[direction].c_str(), transferTypes[transferType].c_str(), maxPacketSize);
}
}
return output;
}
std::uint16_t USBDevice::GetMaxPipePacketSize(std::uint8_t pipeRef)
{
UInt8 direction;
UInt8 number;
UInt8 transferType;
UInt16 maxPacketSize;
UInt8 interval;
IOReturn kr = (*interface)->GetPipeProperties(interface, pipeRef, &direction, &number, &transferType, &maxPacketSize, &interval);
if (kr != kIOReturnSuccess)
{
std::cerr<<human_error_string(kr)<<"\n";
return 0;
}
return maxPacketSize;
}
void USBDevice::ClearPipe(std::uint8_t pipeRef, std::uint32_t timeout)
{
uint16_t maxSize = GetMaxPipePacketSize(pipeRef);
std::unique_ptr<char[]> readBuffer(new char[maxSize]);
while(true)
{
std::uint32_t readSize = maxSize;
bzero(readBuffer.get(), maxSize * sizeof(char));
IOReturn kr = (*interface)->ReadPipeTO(interface, pipeRef, readBuffer.get(), (UInt32 *)&readSize, timeout, timeout);
if (kr != kIOReturnSuccess)
{
std::cerr<<human_error_string(kr)<<"\n";
break;
}
if (readSize == 0)
{
break;
}
}
IOReturn kr = (*interface)->ClearPipeStallBothEnds(interface, pipeRef);
if (kr)
{
std::cerr<<human_error_string(kr)<<"\n";
return;
}
}
bool USBDevice::Open()
{
// Open the USB device for communication.
deviceInterface = reinterpret_cast<IOUSBDeviceInterface300 **>(GetInterface(device, kIOUSBDeviceUserClientTypeID, kIOUSBDeviceInterfaceID300));
if (deviceInterface)
{
kern_return_t kr = (*deviceInterface)->USBDeviceOpen(deviceInterface);
if (kr != kIOReturnSuccess)
{
(*deviceInterface)->Release(deviceInterface);
deviceInterface = nullptr;
std::cerr<<human_error_string(kr)<<"\n";
return false;
}
//Get the configuration..
IOUSBConfigurationDescriptorPtr config;
kr = (*deviceInterface)->GetConfigurationDescriptorPtr(deviceInterface, 0, &config);
if (kr != kIOReturnSuccess)
{
(*deviceInterface)->USBDeviceClose(deviceInterface);
(*deviceInterface)->Release(deviceInterface);
deviceInterface = nullptr;
std::cerr<<human_error_string(kr)<<"\n";
return false;
}
//Set the configuration..
(*deviceInterface)->SetConfiguration(deviceInterface, config->bConfigurationValue);
//Find the USB interface..
IOUSBFindInterfaceRequest interfaceRequest;
interfaceRequest.bInterfaceClass = kIOUSBFindInterfaceDontCare;
interfaceRequest.bInterfaceSubClass = kIOUSBFindInterfaceDontCare;
interfaceRequest.bInterfaceProtocol = kIOUSBFindInterfaceDontCare;
interfaceRequest.bAlternateSetting = kIOUSBFindInterfaceDontCare;
//Get an interface iterator..
io_iterator_t iterator;
kr = (*deviceInterface)->CreateInterfaceIterator(deviceInterface, &interfaceRequest, &iterator);
if (kr != kIOReturnSuccess)
{
(*deviceInterface)->USBDeviceClose(deviceInterface);
(*deviceInterface)->Release(deviceInterface);
deviceInterface = nullptr;
std::cerr<<human_error_string(kr)<<"\n";
return false;
}
//Get the device object
io_object_t usbDevice = IOIteratorNext(iterator);
if (!usbDevice)
{
IOObjectRelease(iterator);
(*deviceInterface)->USBDeviceClose(deviceInterface);
(*deviceInterface)->Release(deviceInterface);
deviceInterface = nullptr;
return false;
}
//Get a USB Interface
interface = reinterpret_cast<IOUSBInterfaceInterface300 **>(GetInterface(usbDevice, kIOUSBInterfaceUserClientTypeID, kIOUSBInterfaceInterfaceID300));
if (!interface)
{
IOObjectRelease(usbDevice);
IOObjectRelease(iterator);
(*deviceInterface)->USBDeviceClose(deviceInterface);
(*deviceInterface)->Release(deviceInterface);
deviceInterface = nullptr;
return false;
}
kr = (*interface)->USBInterfaceOpen(interface);
if (kr != kIOReturnSuccess)
{
(*interface)->Release(interface);
interface = nullptr;
IOObjectRelease(usbDevice);
IOObjectRelease(iterator);
(*deviceInterface)->USBDeviceClose(deviceInterface);
(*deviceInterface)->Release(deviceInterface);
deviceInterface = nullptr;
return false;
}
//Load the pipe references
GetPipeRefs();
//Create Async Notifications
//TODO: Release this event..
CFRunLoopSourceRef asyncEventSource;
kr = (*interface)->CreateInterfaceAsyncEventSource(interface, &asyncEventSource);
CFRunLoopAddSource(CFRunLoopGetCurrent(), asyncEventSource, kCFRunLoopDefaultMode);
IOObjectRelease(usbDevice);
IOObjectRelease(iterator);
return true;
}
return false;
}
void USBDevice::Close()
{
if (interface)
{
(*interface)->USBInterfaceClose(interface);
(*interface)->Release(interface);
interface = nullptr;
}
if (deviceInterface)
{
(*deviceInterface)->USBDeviceClose(deviceInterface);
(*deviceInterface)->Release(deviceInterface);
deviceInterface = nullptr;
}
}
kern_return_t USBDevice::GetPipeStatus(std::uint8_t pipeRef)
{
if (interface)
{
return (*interface)->GetPipeStatus(interface, pipeRef);
}
return kIOReturnNotOpen;
}
NumVersion USBDevice::GetDriverVersion()
{
if (interface)
{
NumVersion ioUSBLibVersion;
NumVersion usbFamilyVersion;
(*interface)->GetIOUSBLibVersion(interface, &ioUSBLibVersion, &usbFamilyVersion);
return ioUSBLibVersion;
}
return NumVersion{0};
}
std::uint8_t USBDevice::GetControlPipeRef()
{
return controlPipeRef;
}
std::uint8_t USBDevice::GetReadPipeRef()
{
return readPipeRef;
}
std::uint8_t USBDevice::GetWritePipeRef()
{
return writePipeRef;
}
void onReadAsync(void *refcon, IOReturn result, void *arg0)
{
}
void onWriteAsync(void *refcon, IOReturn result, void *arg0)
{
}
int USBDevice::Read(std::uint8_t* buffer, std::size_t bufferSize)
{
if (interface)
{
UInt8 pipeRef = readPipeRef;
UInt32 size = static_cast<UInt32>(bufferSize);
kern_return_t res = (*interface)->ReadPipe(interface, pipeRef, buffer, &size);
return res != kIOReturnSuccess ? -1 : static_cast<int>(size);
}
return -1;
}
int USBDevice::ReadAsync(std::uint8_t* buffer, std::size_t bufferSize)
{
if (interface)
{
UInt8 pipeRef = readPipeRef;
UInt32 size = static_cast<UInt32>(bufferSize);
kern_return_t res = (*interface)->ReadPipeAsync(interface, pipeRef, buffer, size, onReadAsync, this);
return res != kIOReturnSuccess ? -1 : static_cast<int>(size);
}
return -1;
}
int USBDevice::Write(uint8_t* buffer, size_t bufferSize)
{
UInt8 pipeRef = writePipeRef;
int packetSize = GetMaxPipePacketSize(pipeRef);
size_t bytesWritten = 0;
int bytesRemaining = static_cast<int>(bufferSize);
auto internal_write = [&](std::uint8_t pipeRef, std::uint8_t* buffer, std::size_t bufferSize) {
kern_return_t res = (*interface)->WritePipe(interface, pipeRef, buffer, static_cast<int>(bufferSize));
return res != kIOReturnSuccess ? -1 : static_cast<int>(bufferSize);
};
while (bytesRemaining > 0)
{
size_t bytesToWrite = std::min(bytesRemaining, packetSize);
auto retVal = internal_write(pipeRef, &buffer[bytesWritten], bytesToWrite);
if (retVal < 0)
{
return retVal;
}
else if (retVal < static_cast<int>(bytesToWrite))
{
return static_cast<int>(bytesWritten) + retVal;
}
bytesWritten += retVal;
bytesRemaining -= retVal;
}
return static_cast<int>(bytesWritten);
}
int USBDevice::WriteAsync(std::uint8_t* buffer, std::size_t bufferSize)
{
UInt8 pipeRef = writePipeRef;
int packetSize = GetMaxPipePacketSize(pipeRef);
size_t bytesWritten = 0;
int bytesRemaining = static_cast<int>(bufferSize);
auto internal_write = [&](std::uint8_t pipeRef, std::uint8_t* buffer, std::size_t bufferSize) {
kern_return_t res = (*interface)->WritePipeAsync(interface, pipeRef, buffer, static_cast<int>(bufferSize), onWriteAsync, this);
return res != kIOReturnSuccess ? -1 : static_cast<int>(bufferSize);
};
while (bytesRemaining > 0)
{
size_t bytesToWrite = std::min(bytesRemaining, packetSize);
auto retVal = internal_write(pipeRef, &buffer[bytesWritten], bytesToWrite);
if (retVal < 0)
{
return retVal;
}
else if (retVal < static_cast<int>(bytesToWrite))
{
return static_cast<int>(bytesWritten) + retVal;
}
bytesWritten += retVal;
bytesRemaining -= retVal;
}
return static_cast<int>(bytesWritten);
}
int USBDevice::SendDeviceRequest(int requesttype, int request, int value, int index, char *bytes, int size, int timeout)
{
if (!deviceInterface)
{
return -1;
}
//If there's no timeout.. just send a regular request..
if (timeout <= 0)
{
IOUSBDevRequest req;
req.bmRequestType = requesttype;
req.bRequest = request;
req.wValue = value;
req.wIndex = index;
req.wLength = size;
req.pData = bytes;
kern_return_t result = (*deviceInterface)->DeviceRequest(deviceInterface, &req);
return result != kIOReturnSuccess ? -1 : req.wLenDone; //Bytes Transferred..
}
//Send Timeout request..
IOUSBDevRequestTO req;
req.bmRequestType = requesttype;
req.bRequest = request;
req.wValue = value;
req.wIndex = index;
req.wLength = size;
req.pData = bytes;
req.completionTimeout = timeout;
kern_return_t result = (*deviceInterface)->DeviceRequestTO(deviceInterface, &req);
return result != kIOReturnSuccess ? -1 : req.wLenDone; //Bytes Transferred..
}
int USBDevice::SendControlRequest(int requesttype, int request, int value, int index, char *bytes, int size, int timeout)
{
if (!interface)
{
return -1;
}
//If there's no timeout.. just send a regular request..
if (timeout <= 0)
{
IOUSBDevRequest req;
req.bmRequestType = requesttype;
req.bRequest = request;
req.wValue = value;
req.wIndex = index;
req.wLength = size;
req.pData = bytes;
kern_return_t result = (*interface)->ControlRequest(interface, controlPipeRef, &req);
return result != kIOReturnSuccess ? -1 : req.wLenDone; //Bytes Transferred..
}
//Send Timeout request..
IOUSBDevRequestTO req;
req.bmRequestType = requesttype;
req.bRequest = request;
req.wValue = value;
req.wIndex = index;
req.wLength = size;
req.pData = bytes;
req.completionTimeout = timeout;
kern_return_t result = (*interface)->ControlRequestTO(interface, controlPipeRef, &req);
return result != kIOReturnSuccess ? -1 : req.wLenDone; //Bytes Transferred..
}
int USBDevice::SendRawRequest(IOUSBDevRequest *request)
{
if (!deviceInterface)
{
return -1;
}
kern_return_t result = (*deviceInterface)->DeviceRequest(deviceInterface, request);
return result != kIOReturnSuccess ? -1 : request->wLenDone; //Bytes Transferred..
}
int USBDevice::SendRawControlRequest(IOUSBDevRequest *request, std::uint8_t pipeRef)
{
if (!interface)
{
return -1;
}
kern_return_t result = (*interface)->ControlRequest(interface, pipeRef, request);
return result != kIOReturnSuccess ? -1 : request->wLenDone; //Bytes Transferred..
}
#pragma mark - USB Public
USBDeviceManager::USBDeviceManager() : notificationRegistry(nullptr)
{
}
USBDeviceManager::~USBDeviceManager()
{
if (this->notificationRegistry)
{
USBNotification *notification = static_cast<USBNotification *>(this->notificationRegistry);
delete notification;
}
}
std::vector<std::unique_ptr<USBDevice>> USBDeviceManager::GetDevicesMatching(std::string service, int vendorId, int productId)
{
io_iterator_t iter = 0;
/// Find Matching Services
CFMutableDictionaryRef matchingDict = IOServiceMatching(service.c_str());
if (matchingDict == nil)
{
return std::vector<std::unique_ptr<USBDevice>> {};
}
/// Added VendorID and ProductID to query
CFNumberRef numberRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &vendorId);
CFDictionarySetValue(matchingDict, CFSTR(kUSBVendorID), numberRef);
CFRelease(numberRef);
numberRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &productId);
CFDictionarySetValue(matchingDict, CFSTR(kUSBProductID), numberRef);
CFRelease(numberRef);
numberRef = nullptr;
/// Find devices matching the above criteria
kern_return_t kr = IOServiceGetMatchingServices(kIOMasterPortDefault, matchingDict, &iter);
if (kr != KERN_SUCCESS)
{
std::cerr<<human_error_string(kr)<<"\n";
return std::vector<std::unique_ptr<USBDevice>> {};
}
/// Iterate over all devices
io_service_t device = 0;
std::vector<std::unique_ptr<USBDevice>> devices;
while ((device = IOIteratorNext(iter)))
{
devices.push_back(std::unique_ptr<USBDevice>(new USBDevice(device)));
}
IOObjectRelease(iter);
return devices;
}
void USBDeviceManager::RegisterForDeviceNotifications(std::string service, int vendorId, int productId, void(*onDeviceAdded)(USBDevice *device), void(*onDeviceDisconnected)(USBDevice *device))
{
/// Find Matching Services
CFMutableDictionaryRef matchingDict = IOServiceMatching(service.c_str());
if (matchingDict == nil)
{
return;
}
/// Added VendorID and ProductID to query
CFNumberRef numberRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &vendorId);
CFDictionarySetValue(matchingDict, CFSTR(kUSBVendorID), numberRef);
CFRelease(numberRef);
numberRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &productId);
CFDictionarySetValue(matchingDict, CFSTR(kUSBProductID), numberRef);
CFRelease(numberRef);
numberRef = nullptr;
// Setup notifications for when the device is available
USBNotification *notificationInfo = new USBNotification();
notificationInfo->_this = this;
notificationInfo->onDeviceAdded = onDeviceAdded;
notificationInfo->onDeviceDisconnected = onDeviceDisconnected;
notificationInfo->notificationPort = IONotificationPortCreate(kIOMasterPortDefault);
notificationInfo->runLoop = CFRunLoopGetCurrent();
notificationInfo->deviceAddedIterator = 0;
notificationInfo->deviceRemovedIterator = 0;
this->notificationRegistry = notificationInfo;
CFRunLoopSourceRef runLoopSource = IONotificationPortGetRunLoopSource(notificationInfo->notificationPort);
CFRunLoopAddSource(notificationInfo->runLoop, runLoopSource, kCFRunLoopDefaultMode);
// Now set up a notification to be called when a device is matched and detached by I/O Kit.
kern_return_t rc;
// rc = IOServiceAddMatchingNotification(notificationInfo->notificationPort,
// kIOTerminatedNotification,
// matchingDict,
// DeviceDisconnected,
// notificationInfo, //userInfo
// ¬ificationInfo->deviceRemovedIterator);
//
// if (rc != kIOReturnSuccess)
// {
// IONotificationPortDestroy(notificationInfo->notificationPort);
// delete notificationInfo;
// std::cerr<<human_error_string(rc)<<"\n";
// return;
// }
// Now set up a notification to be called when a device is matched and attached by I/O Kit.
rc = IOServiceAddMatchingNotification(notificationInfo->notificationPort,
kIOMatchedNotification,
matchingDict,
DeviceAdded,
notificationInfo, //userInfo
¬ificationInfo->deviceAddedIterator);
if (rc != kIOReturnSuccess)
{
IONotificationPortDestroy(notificationInfo->notificationPort);
delete notificationInfo;
std::cerr<<human_error_string(rc)<<"\n";
return;
}
// DeviceDisconnected(notificationInfo, notificationInfo->deviceRemovedIterator);
DeviceAdded(notificationInfo, notificationInfo->deviceAddedIterator);
}
// When a device has been plugged into the phone's serial port, this function will get called
void DeviceAdded(void *userInfo, io_iterator_t iterator)
{
kern_return_t kr;
io_service_t usbDevice;
USBNotification *notificationInfo = static_cast<USBNotification *>(userInfo);
// Iterate over all devices..
while ((usbDevice = IOIteratorNext(iterator))) {
DeviceMessage* notificationMessage = new DeviceMessage();
notificationMessage->_this = new USBDevice(usbDevice);
notificationMessage->notification = notificationInfo;
if (notificationInfo->onDeviceAdded)
{
notificationInfo->onDeviceAdded(notificationMessage->_this);
}
// Register for Device Notifications.. IE: Disconnected Notification..
kr = IOServiceAddInterestNotification(notificationInfo->notificationPort,
usbDevice,
kIOGeneralInterest,
DeviceMessageReceived,
notificationMessage, //userInfo
¬ificationInfo->notification);
// Cleanup
kr = IOObjectRelease(usbDevice);
}
}
// When a device has been unplugged from the phone's serial port, this function will get called.
void DeviceDisconnected(void *userInfo, io_iterator_t iterator)
{
USBNotification *notificationInfo = static_cast<USBNotification *>(userInfo);
if (notificationInfo->onDeviceDisconnected)
{
notificationInfo->onDeviceDisconnected(nullptr);
}
// Cleanup
IOObjectRelease(notificationInfo->notification);
delete notificationInfo;
}
// When a device message has been received..
void DeviceMessageReceived(void* userInfo, io_service_t service, uint32_t messageType, void* messageArgument)
{
DeviceMessage *message = static_cast<DeviceMessage *>(userInfo);
// If it's the disconnect message, we need to cleanup..
if (messageType == kIOMessageServiceIsTerminated)
{
if (message->notification && message->notification->onDeviceDisconnected)
{
message->notification->onDeviceDisconnected(message->_this);
}
// Cleanup
// IOObjectRelease(message->notification->notificationPort);
delete message;
}
}