Working Dualshock output reports on macOS; fix runaway loop

This commit is contained in:
Jack Andersen 2017-05-11 09:12:44 -10:00
parent 7823aecc57
commit 6e1c71aefd
6 changed files with 101 additions and 134 deletions

View File

@ -19,6 +19,7 @@ struct DualshockLED
struct DualshockRumble
{
uint8_t padding;
uint8_t rightDuration;
bool rightOn;
uint8_t leftDuration;
@ -38,7 +39,7 @@ union DualshockOutReport
DualshockLED led[4];
DualshockLED reserved;
};
uint8_t buf[36];
uint8_t buf[49];
};
enum class EDualshockPadButtons
@ -130,7 +131,6 @@ class DualshockPad final : public DeviceBase
uint8_t m_rumbleIntensity[2];
EDualshockLED m_led;
DualshockOutReport m_report;
uint8_t m_btAddress[6];
void deviceDisconnected();
void initialCycle();
void transferCycle();
@ -157,9 +157,9 @@ public:
}
}
void stopRumble(EDualshockMotor motor)
void stopRumble(int motor)
{
m_rumbleRequest &= ~motor;
m_rumbleRequest &= ~EDualshockMotor(motor);
}
EDualshockLED getLED()
@ -180,7 +180,7 @@ public:
void setRawLED(int led)
{
m_report.leds = led;
sendHIDReport(m_report.buf, sizeof(m_report), HIDReportType::Output, 0x0201);
sendHIDReport(m_report.buf, sizeof(m_report), HIDReportType::Output, 0x01);
}
size_t getInputBufferSize() const { return 49; }

View File

@ -53,6 +53,15 @@ public:
}
operator bool() const { return storage != nullptr; }
void reset()
{
if (storage)
{
CFRelease(storage);
storage = nullptr;
}
}
private:
CFTypeRef storage;

View File

@ -17,14 +17,15 @@ static inline uint16_t bswap16(uint16_t val) {return __builtin_byteswap(val);}
namespace boo
{
static const uint8_t defaultReport[35] = {
0x01, 0xff, 0x00, 0xff, 0x00,
0xff, 0x80, 0x00, 0x00, 0x00,
0xff, 0x27, 0x10, 0x00, 0x32,
0xff, 0x27, 0x10, 0x00, 0x32,
0xff, 0x27, 0x10, 0x00, 0x32,
0xff, 0x27, 0x10, 0x00, 0x32,
0x00, 0x00, 0x00, 0x00, 0x00
static const uint8_t defaultReport[49] = {
0x01,
0x01, 0xff, 0x00, 0xff, 0x00,
0xff, 0x80, 0x00, 0x00, 0x00,
0xff, 0x27, 0x10, 0x00, 0x32,
0xff, 0x27, 0x10, 0x00, 0x32,
0xff, 0x27, 0x10, 0x00, 0x32,
0xff, 0x27, 0x10, 0x00, 0x32,
0x00, 0x00, 0x00, 0x00, 0x00
};
DualshockPad::DualshockPad(DeviceToken* token)
@ -33,7 +34,7 @@ DualshockPad::DualshockPad(DeviceToken* token)
m_rumbleRequest(EDualshockMotor::None),
m_rumbleState(EDualshockMotor::None)
{
memcpy(m_report.buf, defaultReport, 35);
memcpy(m_report.buf, defaultReport, 49);
}
DualshockPad::~DualshockPad()
@ -49,8 +50,9 @@ void DualshockPad::deviceDisconnected()
void DualshockPad::initialCycle()
{
uint8_t setupCommand[4] = {0x42, 0x0c, 0x00, 0x00}; //Tells controller to start sending changes on in pipe
if (!sendHIDReport(setupCommand, sizeof(setupCommand), HIDReportType::Feature, 0xF4))
#if 0
uint8_t setupCommand[5] = {0xF4, 0x42, 0x0c, 0x00, 0x00}; //Tells controller to start sending changes on in pipe
if (!sendHIDReport(setupCommand, 5, HIDReportType::Feature, 0xF4))
{
deviceError("Unable to send complete packet! Request size %x\n", sizeof(setupCommand));
return;
@ -59,6 +61,7 @@ void DualshockPad::initialCycle()
receiveHIDReport(btAddr, sizeof(btAddr), HIDReportType::Feature, 0xF5);
for (int i = 0; i < 6; i++)
m_btAddress[5 - i] = btAddr[i + 2]; // Copy into buffer reversed, so it is LSB first
#endif
}
void DualshockPad::transferCycle()
@ -67,7 +70,11 @@ void DualshockPad::transferCycle()
void DualshockPad::finalCycle()
{
m_report.rumble.leftDuration = 0;
m_report.rumble.leftForce = 0;
m_report.rumble.rightDuration = 0;
m_report.rumble.rightOn = false;
sendHIDReport(m_report.buf, sizeof(m_report), HIDReportType::Output, 0x01);
}
void DualshockPad::receivedHIDReport(const uint8_t* data, size_t length, HIDReportType tp, uint32_t message)
@ -80,6 +87,14 @@ void DualshockPad::receivedHIDReport(const uint8_t* data, size_t length, HIDRepo
state.m_accelerometer[i] = bswap16(state.m_accelerometer[i]);
state.m_gyrometerZ = bswap16(state.m_gyrometerZ);
const double zeroG = 511.5; // 1.65/3.3*1023 (1,65V);
float accXval = -((double)state.m_accelerometer[0] - zeroG);
float accYval = -((double)state.m_accelerometer[1] - zeroG);
float accZval = -((double)state.m_accelerometer[2] - zeroG);
state.accPitch = (atan2(accYval, accZval) + M_PI) * RAD_TO_DEG;
state.accYaw = (atan2(accXval, accZval) + M_PI) * RAD_TO_DEG;
state.gyroZ = (state.m_gyrometerZ / 1023.f);
if (m_callback)
m_callback->controllerUpdate(*this, state);
@ -98,8 +113,8 @@ void DualshockPad::receivedHIDReport(const uint8_t* data, size_t length, HIDRepo
if ((m_rumbleRequest & EDualshockMotor::Right) != EDualshockMotor::None)
{
m_report.rumble.rightDuration = m_rumbleDuration[0];
m_report.rumble.rightOn = true;
m_report.rumble.rightDuration = m_rumbleDuration[1];
m_report.rumble.rightOn = m_rumbleIntensity[1] > 0;
}
else
{
@ -116,13 +131,6 @@ void DualshockPad::receivedHIDReport(const uint8_t* data, size_t length, HIDRepo
if (state.m_reserved5[7] & 0x01)
m_rumbleRequest &= ~EDualshockMotor::Left;
m_rumbleState = m_rumbleRequest;
const double zeroG = 511.5; // 1.65/3.3*1023 (1,65V);
float accXval = -((double)state.m_accelerometer[0] - zeroG);
float accYval = -((double)state.m_accelerometer[1] - zeroG);
float accZval = -((double)state.m_accelerometer[2] - zeroG);
state.accPitch = (atan2(accYval, accZval) + M_PI) * RAD_TO_DEG;
state.accYaw = (atan2(accXval, accZval) + M_PI) * RAD_TO_DEG;
state.gyroZ = (state.m_gyrometerZ / 1023.f);
}
}

View File

@ -20,8 +20,9 @@ class HIDDeviceIOKit : public IHIDDevice
IUnknownPointer<IOUSBInterfaceInterface> m_usbIntf;
uint8_t m_usbIntfInPipe = 0;
uint8_t m_usbIntfOutPipe = 0;
IUnknownPointer<IOHIDDeviceDeviceInterface> m_hidIntf;
CFPointer<IOHIDDeviceRef> m_hidIntf;
bool m_runningTransferLoop = false;
bool m_isBt = false;
const std::string& m_devPath;
std::mutex m_initMutex;
@ -53,10 +54,11 @@ class HIDDeviceIOKit : public IHIDDevice
bool _sendHIDReport(const uint8_t* data, size_t length, HIDReportType tp, uint32_t message)
{
if (m_hidIntf)
/* HACK: A bug in IOBluetoothGamepadHIDDriver prevents raw output report transmission
* USB driver appears to work correctly */
if (m_hidIntf && !m_isBt)
{
IOReturn res = m_hidIntf->setReport(m_hidIntf.storage(), IOHIDReportType(tp), message, data, length,
1000, nullptr, nullptr, 0);
IOReturn res = IOHIDDeviceSetReport(m_hidIntf.get(), IOHIDReportType(tp), message, data, length);
return res == kIOReturnSuccess;
}
return false;
@ -67,8 +69,7 @@ class HIDDeviceIOKit : public IHIDDevice
if (m_hidIntf)
{
CFIndex readSize = length;
IOReturn res = m_hidIntf->getReport(m_hidIntf.storage(), IOHIDReportType(tp), message, data, &readSize,
1000, nullptr, nullptr, 0);
IOReturn res = IOHIDDeviceGetReport(m_hidIntf.get(), IOHIDReportType(tp), message, data, &readSize);
if (res != kIOReturnSuccess)
return 0;
return readSize;
@ -231,7 +232,8 @@ class HIDDeviceIOKit : public IHIDDevice
std::unique_lock<std::mutex> lk(device->m_initMutex);
/* Get the HID element's object (HID device interface) */
IOObjectPointer<io_service_t> interfaceEntry = IORegistryEntryFromPath(kIOMasterPortDefault, device->m_devPath.c_str());
IOObjectPointer<io_service_t> interfaceEntry =
IORegistryEntryFromPath(kIOMasterPortDefault, device->m_devPath.c_str());
if (!IOObjectConformsTo(interfaceEntry.get(), "IOHIDDevice"))
{
snprintf(errStr, 256, "Unable to find interface for %s@%s\n",
@ -243,29 +245,8 @@ class HIDDeviceIOKit : public IHIDDevice
return;
}
/* IOKit Plugin COM interface (WTF Apple???) */
IOCFPluginPointer iodev;
SInt32 score;
IOReturn err;
err = IOCreatePlugInInterfaceForService(interfaceEntry.get(),
kIOHIDDeviceTypeID,
kIOCFPlugInInterfaceID,
&iodev,
&score);
if (err)
{
snprintf(errStr, 256, "Unable to open %s@%s\n",
device->m_token.getProductName().c_str(), device->m_devPath.c_str());
device->m_devImp.deviceError(errStr);
lk.unlock();
device->m_initCond.notify_one();
return;
}
/* HID interface function-pointer table */
IUnknownPointer<IOHIDDeviceDeviceInterface> intf;
err = iodev.As(&intf, kIOHIDDeviceDeviceInterfaceID);
if (err)
device->m_hidIntf = IOHIDDeviceCreate(nullptr, interfaceEntry.get());
if (!device->m_hidIntf)
{
snprintf(errStr, 256, "Unable to open %s@%s\n",
device->m_token.getProductName().c_str(), device->m_devPath.c_str());
@ -276,8 +257,7 @@ class HIDDeviceIOKit : public IHIDDevice
}
/* Open device */
device->m_hidIntf = intf;
err = intf->open(intf.storage(), kIOHIDOptionsTypeNone);
IOReturn err = IOHIDDeviceOpen(device->m_hidIntf.get(), kIOHIDOptionsTypeNone);
if (err != kIOReturnSuccess)
{
if (err == kIOReturnExclusiveAccess)
@ -297,18 +277,18 @@ class HIDDeviceIOKit : public IHIDDevice
return;
}
/* 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;
/* Register input buffer */
std::unique_ptr<uint8_t[]> buffer;
if (size_t bufSize = device->m_devImp.getInputBufferSize())
{
buffer = std::unique_ptr<uint8_t[]>(new uint8_t[bufSize]);
CFTypeRef source;
device->m_hidIntf->getAsyncEventSource(device->m_hidIntf.storage(), &source);
device->m_hidIntf->setInputReportCallback(device->m_hidIntf.storage(), buffer.get(),
bufSize, _hidReportCb, &device->m_devImp, 0);
CFRunLoopRef rl = CFRunLoopGetCurrent();
CFRunLoopAddSource(rl, CFRunLoopSourceRef(source), kCFRunLoopDefaultMode);
CFRunLoopWakeUp(rl);
IOHIDDeviceRegisterInputReportCallback(device->m_hidIntf.get(), buffer.get(), bufSize,
_hidReportCb, &device->m_devImp);
IOHIDDeviceScheduleWithRunLoop(device->m_hidIntf.get(), CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
}
/* Return control to main thread */
@ -320,14 +300,14 @@ class HIDDeviceIOKit : public IHIDDevice
device->m_devImp.initialCycle();
while (device->m_runningTransferLoop)
{
CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, false);
CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.010, true);
device->m_devImp.transferCycle();
}
device->m_devImp.finalCycle();
/* Cleanup */
intf->close(intf.storage(), kIOHIDOptionsTypeNone);
device->m_hidIntf = nullptr;
IOHIDDeviceClose(device->m_hidIntf.get(), kIOHIDOptionsTypeNone);
device->m_hidIntf.reset();
}
void _deviceDisconnected()

View File

@ -204,13 +204,20 @@ class HIDDeviceUdev final : public IHIDDevice
device->m_devImp.initialCycle();
while (device->m_runningTransferLoop)
{
while (true)
fd_set readset;
FD_ZERO(&readset);
FD_SET(fd, &readset);
struct timeval timeout = {0, 10000};
if (select(fd + 1, &readset, nullptr, nullptr, &timeout) > 0)
{
ssize_t sz = read(fd, readBuf.get(), readSz);
if (sz < 0)
break;
device->m_devImp.receivedHIDReport(readBuf.get(), sz,
HIDReportType::Input, readBuf[0]);
while (true)
{
ssize_t sz = read(fd, readBuf.get(), readSz);
if (sz < 0)
break;
device->m_devImp.receivedHIDReport(readBuf.get(), sz,
HIDReportType::Input, readBuf[0]);
}
}
device->m_devImp.transferCycle();
}
@ -245,23 +252,6 @@ class HIDDeviceUdev final : public IHIDDevice
return false;
return true;
}
/*
usbdevfs_ctrltransfer xfer =
{
USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE,
0x09, // HID_SET_REPORT
message,
0,
(uint16_t)length,
30,
(void*)data
};
int ret = ioctl(m_devFd, USBDEVFS_CONTROL, &xfer);
if (ret != (int)length)
return false;
return true;
*/
}
return false;
}
@ -278,20 +268,6 @@ class HIDDeviceUdev final : public IHIDDevice
return 0;
return length;
}
/*
usbdevfs_ctrltransfer xfer =
{
USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE,
0x01, // HID_GET_REPORT
message,
0,
(uint16_t)length,
0,
(void*)data
};
return ioctl(m_devFd, USBDEVFS_CONTROL, &xfer);
*/
}
return 0;
}

View File

@ -174,6 +174,7 @@ class HIDDeviceWinUSB final : public IHIDDevice
std::unique_lock<std::mutex> lk(device->m_initMutex);
/* POSIX.. who needs it?? -MS */
m_overlapped.hEvent = CreateEvent(nullptr, TRUE, FALSE, nullptr);
device->m_hidHandle = CreateFileA(device->m_devPath.c_str(),
GENERIC_WRITE | GENERIC_READ,
FILE_SHARE_WRITE | FILE_SHARE_READ,
@ -230,6 +231,7 @@ class HIDDeviceWinUSB final : public IHIDDevice
device->m_devImp.finalCycle();
/* Cleanup */
CloseHandle(m_overlapped.hEvent);
CloseHandle(device->m_hidHandle);
device->m_hidHandle = nullptr;
}
@ -353,45 +355,37 @@ public:
void ReadCycle(uint8_t* inBuffer, size_t inBufferSz)
{
while (true)
ResetEvent(m_overlapped.hEvent);
ZeroMemory(inBuffer, inBufferSz);
DWORD BytesRead = 0;
BOOL Result = ReadFile(m_hidHandle, inBuffer, DWORD(inBufferSz), &BytesRead, &m_overlapped);
if (!Result)
{
ZeroMemory(inBuffer, inBufferSz);
DWORD BytesRead = 0;
BOOL Result = ReadFile(m_hidHandle, inBuffer, DWORD(inBufferSz), &BytesRead, &m_overlapped);
if (!Result)
DWORD Error = GetLastError();
if (Error != ERROR_IO_PENDING)
{
DWORD Error = GetLastError();
if (Error != ERROR_IO_PENDING)
fprintf(stderr, "Read Failed: %08X\n", Error);
return;
}
else
{
if (!GetOverlappedResult(m_hidHandle, &m_overlapped, &BytesRead, FALSE))
{
Error = GetLastError();
if (Error == ERROR_IO_INCOMPLETE)
return;
fprintf(stderr, "Read Failed: %08X\n", Error);
return;
}
else
{
if (!GetOverlappedResult(m_hidHandle, &m_overlapped, &BytesRead, TRUE))
{
Error = GetLastError();
if (Error == ERROR_IO_INCOMPLETE)
return;
fprintf(stderr, "Read Failed: %08X\n", Error);
return;
}
if (m_overlapped.Internal == STATUS_PENDING)
{
//std::cout << "Read Interrupted" << std::endl;
if(!CancelIo(m_hidHandle))
{
Error = GetLastError();
fprintf(stderr, "Cancel IO Failed: %08X\n", Error);
}
return;
}
if (WaitForSingleObject(m_overlapped.hEvent, 10) != WAIT_OBJECT_0)
{
return;
}
}
m_devImp.receivedHIDReport(inBuffer, BytesRead, HIDReportType::Input, inBuffer[0]);
}
m_devImp.receivedHIDReport(inBuffer, BytesRead, HIDReportType::Input, inBuffer[0]);
}
};