boo/lib/inputdev/HIDDeviceIOKit.cpp

384 lines
14 KiB
C++
Raw Normal View History

2015-04-19 20:16:50 +00:00
#include "IHIDDevice.hpp"
#include <IOKit/hid/IOHIDLib.h>
2017-05-07 21:24:00 +00:00
#include <IOKit/hid/IOHIDDevicePlugin.h>
2015-04-24 00:24:15 +00:00
#include <IOKit/usb/IOUSBLib.h>
2017-05-07 21:24:00 +00:00
#include "IOKitPointer.hpp"
#include <thread>
namespace boo
{
2015-09-02 19:09:13 +00:00
class HIDDeviceIOKit : public IHIDDevice
{
2015-09-02 19:09:13 +00:00
DeviceToken& m_token;
2017-09-15 17:20:52 +00:00
std::shared_ptr<DeviceBase> m_devImp;
2015-04-24 00:24:15 +00:00
2017-05-07 21:24:00 +00:00
IUnknownPointer<IOUSBInterfaceInterface> m_usbIntf;
2015-04-24 00:24:15 +00:00
uint8_t m_usbIntfInPipe = 0;
uint8_t m_usbIntfOutPipe = 0;
CFPointer<IOHIDDeviceRef> m_hidIntf;
2015-04-24 00:24:15 +00:00
bool m_runningTransferLoop = false;
bool m_isBt = false;
2017-11-13 06:13:32 +00:00
std::string_view m_devPath;
std::mutex m_initMutex;
std::condition_variable m_initCond;
std::thread m_thread;
2015-05-10 07:02:18 +00:00
bool _sendUSBInterruptTransfer(const uint8_t* data, size_t length)
2015-04-24 00:24:15 +00:00
{
if (m_usbIntf)
{
2017-05-07 21:24:00 +00:00
IOReturn res = m_usbIntf->WritePipe(m_usbIntf.storage(), m_usbIntfOutPipe, (void*)data, length);
2015-04-24 00:24:15 +00:00
return res == kIOReturnSuccess;
}
return false;
}
2015-05-10 07:02:18 +00:00
size_t _receiveUSBInterruptTransfer(uint8_t* data, size_t length)
2015-04-24 00:24:15 +00:00
{
if (m_usbIntf)
{
UInt32 readSize = length;
2017-05-07 21:24:00 +00:00
IOReturn res = m_usbIntf->ReadPipe(m_usbIntf.storage(), m_usbIntfInPipe, data, &readSize);
if (res != kIOReturnSuccess)
return 0;
return readSize;
}
return 0;
}
2017-09-15 17:20:52 +00:00
std::vector<uint8_t> _getReportDescriptor()
{
if (m_hidIntf)
{
if (CFTypeRef desc = IOHIDDeviceGetProperty(m_hidIntf.get(), CFSTR(kIOHIDReportDescriptorKey)))
{
CFIndex len = CFDataGetLength(CFDataRef(desc));
std::vector<uint8_t> ret(len, '\0');
CFDataGetBytes(CFDataRef(desc), CFRangeMake(0, len), &ret[0]);
return ret;
}
}
return {};
}
2017-05-07 21:24:00 +00:00
bool _sendHIDReport(const uint8_t* data, size_t length, HIDReportType tp, uint32_t message)
{
/* HACK: A bug in IOBluetoothGamepadHIDDriver prevents raw output report transmission
* USB driver appears to work correctly */
if (m_hidIntf && !m_isBt)
2017-05-07 21:24:00 +00:00
{
IOReturn res = IOHIDDeviceSetReport(m_hidIntf.get(), IOHIDReportType(tp), message, data, length);
2017-05-07 21:24:00 +00:00
return res == kIOReturnSuccess;
}
return false;
}
size_t _receiveHIDReport(uint8_t* data, size_t length, HIDReportType tp, uint32_t message)
{
if (m_hidIntf)
{
CFIndex readSize = length;
IOReturn res = IOHIDDeviceGetReport(m_hidIntf.get(), IOHIDReportType(tp), message, data, &readSize);
2015-04-24 00:24:15 +00:00
if (res != kIOReturnSuccess)
return 0;
return readSize;
}
return 0;
}
2017-09-15 17:20:52 +00:00
static void _threadProcUSBLL(std::shared_ptr<HIDDeviceIOKit> device)
2015-04-24 00:24:15 +00:00
{
char thrName[128];
2017-11-13 06:13:32 +00:00
snprintf(thrName, 128, "%s Transfer Thread", device->m_token.getProductName().data());
2015-04-24 00:24:15 +00:00
pthread_setname_np(thrName);
2015-05-03 06:40:20 +00:00
char errStr[256];
2015-04-24 00:24:15 +00:00
std::unique_lock<std::mutex> lk(device->m_initMutex);
2015-04-24 00:24:15 +00:00
/* Get the HID element's parent (USB interrupt transfer-interface) */
2017-05-07 21:24:00 +00:00
IOObjectPointer<io_iterator_t> devIter;
2017-11-13 06:13:32 +00:00
IOObjectPointer<io_registry_entry_t> devEntry = IORegistryEntryFromPath(kIOMasterPortDefault,
device->m_devPath.data());
2015-04-24 00:24:15 +00:00
IORegistryEntryGetChildIterator(devEntry, kIOServicePlane, &devIter);
2017-05-07 21:24:00 +00:00
IOObjectPointer<io_object_t> interfaceEntry;
while (IOObjectPointer<io_service_t> obj = IOIteratorNext(devIter))
2015-04-24 00:24:15 +00:00
{
if (IOObjectConformsTo(obj, kIOUSBInterfaceClassName))
interfaceEntry = obj;
else
IOObjectRelease(obj);
}
if (!interfaceEntry)
{
2015-05-03 06:40:20 +00:00
snprintf(errStr, 256, "Unable to find interface for %s@%s\n",
2017-11-13 06:13:32 +00:00
device->m_token.getProductName().data(),
device->m_devPath.data());
2017-09-15 17:20:52 +00:00
device->m_devImp->deviceError(errStr);
2015-04-24 00:24:15 +00:00
lk.unlock();
device->m_initCond.notify_one();
return;
}
2015-04-24 00:24:15 +00:00
/* IOKit Plugin COM interface (WTF Apple???) */
2017-05-07 21:24:00 +00:00
IOCFPluginPointer iodev;
2015-04-24 00:24:15 +00:00
SInt32 score;
IOReturn err;
err = IOCreatePlugInInterfaceForService(interfaceEntry,
kIOUSBInterfaceUserClientTypeID,
kIOCFPlugInInterfaceID,
&iodev,
&score);
if (err)
{
2015-05-03 06:40:20 +00:00
snprintf(errStr, 256, "Unable to open %s@%s\n",
2017-11-13 06:13:32 +00:00
device->m_token.getProductName().data(), device->m_devPath.data());
2017-09-15 17:20:52 +00:00
device->m_devImp->deviceError(errStr);
2015-04-24 00:24:15 +00:00
lk.unlock();
device->m_initCond.notify_one();
return;
}
2015-04-24 00:24:15 +00:00
/* USB interface function-pointer table */
2017-05-07 21:24:00 +00:00
IUnknownPointer<IOUSBInterfaceInterface> intf;
err = iodev.As(&intf, kIOUSBInterfaceInterfaceID);
2015-04-24 00:24:15 +00:00
if (err)
{
2015-05-03 06:40:20 +00:00
snprintf(errStr, 256, "Unable to open %s@%s\n",
2017-11-13 06:13:32 +00:00
device->m_token.getProductName().data(), device->m_devPath.data());
2017-09-15 17:20:52 +00:00
device->m_devImp->deviceError(errStr);
2015-04-24 00:24:15 +00:00
lk.unlock();
device->m_initCond.notify_one();
return;
}
2015-04-24 00:24:15 +00:00
/* Obtain exclusive lock on device */
device->m_usbIntf = intf;
2017-05-07 21:24:00 +00:00
err = intf->USBInterfaceOpen(intf.storage());
2015-04-24 00:24:15 +00:00
if (err != kIOReturnSuccess)
{
if (err == kIOReturnExclusiveAccess)
2015-05-03 06:40:20 +00:00
{
snprintf(errStr, 256, "Unable to open %s@%s: someone else using it\n",
2017-11-13 06:13:32 +00:00
device->m_token.getProductName().data(), device->m_devPath.data());
2017-09-15 17:20:52 +00:00
device->m_devImp->deviceError(errStr);
2015-05-03 06:40:20 +00:00
}
2015-04-24 00:24:15 +00:00
else
2015-05-03 06:40:20 +00:00
{
snprintf(errStr, 256, "Unable to open %s@%s\n",
2017-11-13 06:13:32 +00:00
device->m_token.getProductName().data(), device->m_devPath.data());
2017-09-15 17:20:52 +00:00
device->m_devImp->deviceError(errStr);
2015-05-03 06:40:20 +00:00
}
2015-04-24 00:24:15 +00:00
lk.unlock();
device->m_initCond.notify_one();
return;
}
2015-04-24 00:24:15 +00:00
/* Determine pipe indices for interrupt I/O */
UInt8 numEndpoints = 0;
2017-05-07 21:24:00 +00:00
err = intf->GetNumEndpoints(intf.storage(), &numEndpoints);
2015-04-24 00:24:15 +00:00
for (int i=1 ; i<numEndpoints+1 ; ++i)
{
UInt8 dir, num, tType, interval;
UInt16 mPacketSz;
2017-05-07 21:24:00 +00:00
err = intf->GetPipeProperties(intf.storage(), i, &dir, &num, &tType, &mPacketSz, &interval);
2015-04-24 00:24:15 +00:00
if (tType == kUSBInterrupt)
{
if (dir == kUSBIn)
device->m_usbIntfInPipe = num;
else if (dir == kUSBOut)
device->m_usbIntfOutPipe = num;
}
}
2015-04-24 00:24:15 +00:00
/* Return control to main thread */
device->m_runningTransferLoop = true;
lk.unlock();
device->m_initCond.notify_one();
2015-04-24 00:24:15 +00:00
/* Start transfer loop */
2017-09-15 17:20:52 +00:00
device->m_devImp->initialCycle();
2015-04-24 00:24:15 +00:00
while (device->m_runningTransferLoop)
2017-09-15 17:20:52 +00:00
device->m_devImp->transferCycle();
device->m_devImp->finalCycle();
2015-04-26 08:25:44 +00:00
2015-04-24 00:24:15 +00:00
/* Cleanup */
2017-05-07 21:24:00 +00:00
intf->USBInterfaceClose(intf.storage());
device->m_usbIntf = nullptr;
2015-04-24 00:24:15 +00:00
}
2017-09-15 17:20:52 +00:00
static void _threadProcBTLL(std::shared_ptr<HIDDeviceIOKit> device)
2015-05-01 01:11:25 +00:00
{
std::unique_lock<std::mutex> lk(device->m_initMutex);
2015-05-01 01:11:25 +00:00
/* Return control to main thread */
device->m_runningTransferLoop = true;
lk.unlock();
device->m_initCond.notify_one();
2015-05-01 01:11:25 +00:00
/* Start transfer loop */
2017-09-15 17:20:52 +00:00
device->m_devImp->initialCycle();
2015-05-01 01:11:25 +00:00
while (device->m_runningTransferLoop)
2017-09-15 17:20:52 +00:00
device->m_devImp->transferCycle();
device->m_devImp->finalCycle();
}
2017-09-15 17:20:52 +00:00
static void _hidRemoveCb(void * _Nullable context,
IOReturn result,
void * _Nullable sender)
{
reinterpret_cast<HIDDeviceIOKit*>(context)->m_runningTransferLoop = false;
2015-05-01 01:11:25 +00:00
}
2017-05-07 21:24:00 +00:00
static void _hidReportCb(void * _Nullable context,
IOReturn,
void * _Nullable,
IOHIDReportType type,
uint32_t reportID,
uint8_t * report,
CFIndex reportLength)
{
reinterpret_cast<DeviceBase*>(context)->receivedHIDReport(report, reportLength, HIDReportType(type), reportID);
}
2017-09-15 17:20:52 +00:00
static void _threadProcHID(std::shared_ptr<HIDDeviceIOKit> device)
{
2017-05-07 21:24:00 +00:00
char thrName[128];
2017-11-13 06:13:32 +00:00
snprintf(thrName, 128, "%s Transfer Thread", device->m_token.getProductName().data());
2017-05-07 21:24:00 +00:00
pthread_setname_np(thrName);
char errStr[256];
2015-05-01 01:11:25 +00:00
std::unique_lock<std::mutex> lk(device->m_initMutex);
2017-05-07 21:24:00 +00:00
/* Get the HID element's object (HID device interface) */
IOObjectPointer<io_service_t> interfaceEntry =
2017-11-13 06:13:32 +00:00
IORegistryEntryFromPath(kIOMasterPortDefault, device->m_devPath.data());
2017-05-07 21:24:00 +00:00
if (!IOObjectConformsTo(interfaceEntry.get(), "IOHIDDevice"))
{
snprintf(errStr, 256, "Unable to find interface for %s@%s\n",
2017-11-13 06:13:32 +00:00
device->m_token.getProductName().data(),
device->m_devPath.data());
2017-09-15 17:20:52 +00:00
device->m_devImp->deviceError(errStr);
2017-05-07 21:24:00 +00:00
lk.unlock();
device->m_initCond.notify_one();
return;
}
device->m_hidIntf = IOHIDDeviceCreate(nullptr, interfaceEntry.get());
if (!device->m_hidIntf)
2017-05-07 21:24:00 +00:00
{
snprintf(errStr, 256, "Unable to open %s@%s\n",
2017-11-13 06:13:32 +00:00
device->m_token.getProductName().data(), device->m_devPath.data());
2017-09-15 17:20:52 +00:00
device->m_devImp->deviceError(errStr);
2017-05-07 21:24:00 +00:00
lk.unlock();
device->m_initCond.notify_one();
return;
}
/* Open device */
IOReturn err = IOHIDDeviceOpen(device->m_hidIntf.get(), kIOHIDOptionsTypeNone);
2017-05-07 21:24:00 +00:00
if (err != kIOReturnSuccess)
{
if (err == kIOReturnExclusiveAccess)
{
snprintf(errStr, 256, "Unable to open %s@%s: someone else using it\n",
2017-11-13 06:13:32 +00:00
device->m_token.getProductName().data(), device->m_devPath.data());
2017-09-15 17:20:52 +00:00
device->m_devImp->deviceError(errStr);
2017-05-07 21:24:00 +00:00
}
else
{
snprintf(errStr, 256, "Unable to open %s@%s\n",
2017-11-13 06:13:32 +00:00
device->m_token.getProductName().data(), device->m_devPath.data());
2017-09-15 17:20:52 +00:00
device->m_devImp->deviceError(errStr);
2017-05-07 21:24:00 +00:00
}
lk.unlock();
device->m_initCond.notify_one();
return;
}
2017-09-15 17:20:52 +00:00
/* Register removal callback */
IOHIDDeviceRegisterRemovalCallback(device->m_hidIntf.get(), _hidRemoveCb, device.get());
/* Make note if device uses bluetooth driver */
if (CFTypeRef transport = IOHIDDeviceGetProperty(device->m_hidIntf.get(), CFSTR(kIOHIDTransportKey)))
device->m_isBt = CFStringCompare(CFStringRef(transport), CFSTR(kIOHIDTransportBluetoothValue), 0) == kCFCompareEqualTo;
2017-05-07 21:24:00 +00:00
/* Register input buffer */
std::unique_ptr<uint8_t[]> buffer;
2017-09-15 17:20:52 +00:00
int bufSize = 0;
if (CFTypeRef maxSize = IOHIDDeviceGetProperty(device->m_hidIntf.get(), CFSTR(kIOHIDMaxInputReportSizeKey)))
CFNumberGetValue(CFNumberRef(maxSize), kCFNumberIntType, &bufSize);
if (bufSize)
2017-05-07 21:24:00 +00:00
{
buffer = std::unique_ptr<uint8_t[]>(new uint8_t[bufSize]);
IOHIDDeviceRegisterInputReportCallback(device->m_hidIntf.get(), buffer.get(), bufSize,
2017-09-15 17:20:52 +00:00
_hidReportCb, device->m_devImp.get());
IOHIDDeviceScheduleWithRunLoop(device->m_hidIntf.get(), CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
2017-05-07 21:24:00 +00:00
}
2015-05-01 01:11:25 +00:00
/* Return control to main thread */
device->m_runningTransferLoop = true;
lk.unlock();
device->m_initCond.notify_one();
2015-05-01 01:11:25 +00:00
/* Start transfer loop */
2017-09-15 17:20:52 +00:00
device->m_devImp->initialCycle();
2015-05-01 01:11:25 +00:00
while (device->m_runningTransferLoop)
2017-05-07 21:24:00 +00:00
{
CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.010, true);
2017-09-15 17:20:52 +00:00
if (device->m_runningTransferLoop)
device->m_devImp->transferCycle();
2017-05-07 21:24:00 +00:00
}
2017-09-15 17:20:52 +00:00
device->m_devImp->finalCycle();
2017-05-07 21:24:00 +00:00
/* Cleanup */
IOHIDDeviceClose(device->m_hidIntf.get(), kIOHIDOptionsTypeNone);
device->m_hidIntf.reset();
}
2015-04-22 21:48:23 +00:00
void _deviceDisconnected()
{
2015-04-24 00:24:15 +00:00
m_runningTransferLoop = false;
}
public:
2017-09-15 17:20:52 +00:00
HIDDeviceIOKit(DeviceToken& token, const std::shared_ptr<DeviceBase>& devImp)
: m_token(token),
2015-04-24 00:24:15 +00:00
m_devImp(devImp),
m_devPath(token.getDevicePath())
{
}
void _startThread()
{
std::unique_lock<std::mutex> lk(m_initMutex);
DeviceType dType = m_token.getDeviceType();
2017-05-07 21:24:00 +00:00
if (dType == DeviceType::USB)
2017-09-15 17:20:52 +00:00
m_thread = std::thread(_threadProcUSBLL, std::static_pointer_cast<HIDDeviceIOKit>(shared_from_this()));
2017-05-07 21:24:00 +00:00
else if (dType == DeviceType::Bluetooth)
2017-09-15 17:20:52 +00:00
m_thread = std::thread(_threadProcBTLL, std::static_pointer_cast<HIDDeviceIOKit>(shared_from_this()));
2017-05-07 21:24:00 +00:00
else if (dType == DeviceType::HID)
2017-09-15 17:20:52 +00:00
m_thread = std::thread(_threadProcHID, std::static_pointer_cast<HIDDeviceIOKit>(shared_from_this()));
2015-04-24 00:24:15 +00:00
else
2015-09-02 19:09:13 +00:00
{
fprintf(stderr, "invalid token supplied to device constructor\n");
return;
}
m_initCond.wait(lk);
}
2015-09-02 19:09:13 +00:00
~HIDDeviceIOKit()
{
2015-04-24 00:24:15 +00:00
m_runningTransferLoop = false;
if (m_thread.joinable())
2017-09-15 17:20:52 +00:00
m_thread.detach();
}
};
2017-09-15 17:20:52 +00:00
std::shared_ptr<IHIDDevice> IHIDDeviceNew(DeviceToken& token, const std::shared_ptr<DeviceBase>& devImp)
{
2017-09-15 17:20:52 +00:00
return std::make_shared<HIDDeviceIOKit>(token, devImp);
}
}