initial udev implementation for linux-hosted hid devices

This commit is contained in:
Jack Andersen 2015-04-28 21:56:16 -10:00
parent cf0eaad388
commit 8442c492d4
7 changed files with 364 additions and 13 deletions

View File

@ -1,5 +1,3 @@
!contains(CONFIG,c++11):CONFIG += C++11
HEADERS += \
$$PWD/include/boo.hpp \
$$PWD/include/IGraphicsContext.hpp \
@ -18,8 +16,8 @@ HEADERS += \
$$PWD/include/inputdev/CDeviceToken.hpp \
$$PWD/include/inputdev/CDeviceBase.hpp \
$$PWD/include/inputdev/DeviceClasses.hpp \
$$PWD/src/inputdev/IHIDDevice.hpp \
$$PWD/src/inputdev/IHIDListener.hpp
$$PWD/include/inputdev/IHIDListener.hpp \
$$PWD/src/inputdev/IHIDDevice.hpp
unix:!macx:HEADERS += \
$$PWD/include/x11/CGLXContext.hpp
@ -41,12 +39,12 @@ SOURCES += \
$$PWD/src/inputdev/CDualshockPad.cpp \
$$PWD/src/inputdev/CGenericPad.cpp \
$$PWD/src/inputdev/CDeviceBase.cpp \
$$PWD/src/inputdev/DeviceClasses.cpp
$$PWD/src/inputdev/DeviceClasses.cpp \
$$PWD/src/inputdev/CHIDListenerUdev.cpp \
$$PWD/src/inputdev/CHIDDeviceUdev.cpp
unix:!macx:SOURCES += \
$$PWD/src/x11/CGLXContext.cpp \
$$PWD/src/inputdev/CHIDDeviceUdev.cpp \
$$PWD/src/inputdev/CHIDListenerUdev.cpp
$$PWD/src/x11/CGLXContext.cpp
macx:SOURCES += \
$$PWD/src/mac/CCGLContext.cpp \
@ -63,3 +61,5 @@ win32:SOURCES += \
$$PWD/src/inputdev/CHIDListenerWin32.cpp
INCLUDEPATH += $$PWD/include
unix:!macx:LIBS += -ludev

View File

@ -1,8 +1,9 @@
CONFIG -= Qt
CONFIG += app c++11
#QMAKE_CXXFLAGS -= -std=c++0x
QMAKE_CXXFLAGS += -std=c++11
unix:!macx:CONFIG += link_pkgconfig
unix:!macx:PKGCONFIG += x11
#unix:!macx:CONFIG += link_pkgconfig
#unix:!macx:PKGCONFIG += x11
include(libBoo.pri)
include(test/test.pri)

View File

@ -1 +1,182 @@
#include "IHIDDevice.hpp"
#include "inputdev/CDeviceToken.hpp"
#include "inputdev/CDeviceBase.hpp"
#include <thread>
#include <mutex>
#include <condition_variable>
#include <libudev.h>
#include <stropts.h>
#include <linux/usb/ch9.h>
#include <linux/usbdevice_fs.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <string.h>
udev* BooGetUdev();
#define MAX_REPORT_SIZE 65536
/* Reference: http://tali.admingilde.org/linux-docbook/usb/ch07s06.html
*/
class CHIDDeviceUdev final : public IHIDDevice
{
CDeviceToken& m_token;
CDeviceBase& m_devImp;
int m_devFd = 0;
unsigned m_usbIntfInPipe = 0;
unsigned m_usbIntfOutPipe = 0;
bool m_runningTransferLoop = false;
const std::string& m_devPath;
std::mutex m_initMutex;
std::condition_variable m_initCond;
std::thread* m_thread;
bool _sendInterruptTransfer(uint8_t pipe, const uint8_t* data, size_t length)
{
if (m_devFd)
{
usbdevfs_bulktransfer xfer =
{
m_usbIntfOutPipe | USB_DIR_OUT,
(unsigned)length,
0,
(void*)data
};
int ret = ioctl(m_devFd, USBDEVFS_BULK, &xfer);
if (ret != length)
return false;
return true;
}
return false;
}
size_t _receiveInterruptTransfer(uint8_t pipe, uint8_t* data, size_t length)
{
if (m_devFd)
{
usbdevfs_bulktransfer xfer =
{
m_usbIntfInPipe | USB_DIR_IN,
(unsigned)length,
0,
data
};
return ioctl(m_devFd, USBDEVFS_BULK, &xfer);
}
return 0;
}
static void _threadProcLL(CHIDDeviceUdev* device)
{
unsigned i;
std::unique_lock<std::mutex> lk(device->m_initMutex);
udev_device* hidDev = udev_device_new_from_syspath(BooGetUdev(), device->m_devPath.c_str());
/* Get the HID element's parent (USB interrupt transfer-interface) */
udev_device* usbDev = udev_device_get_parent_with_subsystem_devtype(hidDev, "usb", "usb_device");
const char* dp = udev_device_get_devnode(usbDev);
device->m_devFd = open(dp, O_RDONLY);
usb_device_descriptor devDesc = {0};
read(device->m_devFd, &devDesc, 1);
read(device->m_devFd, &devDesc.bDescriptorType, devDesc.bLength-1);
if (devDesc.bNumConfigurations)
{
usb_config_descriptor confDesc = {0};
read(device->m_devFd, &confDesc, 1);
read(device->m_devFd, &confDesc.bDescriptorType, confDesc.bLength-1);
if (confDesc.bNumInterfaces)
{
usb_interface_descriptor intfDesc = {0};
read(device->m_devFd, &intfDesc, 1);
read(device->m_devFd, &intfDesc.bDescriptorType, intfDesc.bLength-1);
for (i=0 ; i<intfDesc.bNumEndpoints+1 ; ++i)
{
usb_endpoint_descriptor endpDesc = {0};
read(device->m_devFd, &endpDesc, 1);
read(device->m_devFd, &endpDesc.bDescriptorType, endpDesc.bLength-1);
if ((endpDesc.bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) == USB_ENDPOINT_XFER_INT)
{
if ((endpDesc.bEndpointAddress & USB_ENDPOINT_DIR_MASK) == USB_DIR_IN)
device->m_usbIntfInPipe = endpDesc.bEndpointAddress & USB_ENDPOINT_NUMBER_MASK;
else if ((endpDesc.bEndpointAddress & USB_ENDPOINT_DIR_MASK) == USB_DIR_OUT)
device->m_usbIntfOutPipe = endpDesc.bEndpointAddress & USB_ENDPOINT_NUMBER_MASK;
}
}
}
}
/* Request that kernel disconnects existing driver */
usbdevfs_ioctl disconnectReq = {
0,
USBDEVFS_DISCONNECT,
NULL
};
ioctl(device->m_devFd, USBDEVFS_IOCTL, &disconnectReq);
/* Return control to main thread */
device->m_runningTransferLoop = true;
lk.unlock();
device->m_initCond.notify_one();
/* Start transfer loop */
while (device->m_runningTransferLoop)
device->m_devImp.transferCycle();
device->m_devImp.finalCycle();
/* Cleanup */
close(device->m_devFd);
device->m_devFd = NULL;
udev_device_unref(hidDev);
}
static void _threadProcHL(CHIDDeviceUdev* device)
{
}
void _deviceDisconnected()
{
m_runningTransferLoop = false;
}
bool _sendReport(const uint8_t* data, size_t length)
{
return false;
}
public:
CHIDDeviceUdev(CDeviceToken& token, CDeviceBase& devImp, bool lowLevel)
: m_token(token),
m_devImp(devImp),
m_devPath(token.getDevicePath())
{
devImp.m_hidDev = this;
std::unique_lock<std::mutex> lk(m_initMutex);
if (lowLevel)
m_thread = new std::thread(_threadProcLL, this);
else
m_thread = new std::thread(_threadProcHL, this);
m_initCond.wait(lk);
}
~CHIDDeviceUdev()
{
m_runningTransferLoop = false;
m_thread->detach();
delete m_thread;
}
};
IHIDDevice* IHIDDeviceNew(CDeviceToken& token, CDeviceBase& devImp, bool lowLevel)
{
return new CHIDDeviceUdev(token, devImp, lowLevel);
}

View File

@ -1 +1,166 @@
#include "IHIDListener.hpp"
#include "inputdev/IHIDListener.hpp"
#include "inputdev/CDeviceFinder.hpp"
#include <libudev.h>
#include <string.h>
#include <thread>
static udev* UDEV_INST = NULL;
udev* BooGetUdev()
{
if (!UDEV_INST)
UDEV_INST = udev_new();
return UDEV_INST;
}
class CHIDListenerUdev final : public IHIDListener
{
CDeviceFinder& m_finder;
udev_monitor* m_udevMon;
std::thread* m_udevThread;
bool m_udevRunning;
bool m_scanningEnabled;
static void deviceConnected(CHIDListenerUdev* listener,
udev_device* device)
{
if (!listener->m_scanningEnabled)
return;
const char* devPath = udev_device_get_syspath(device);
if (listener->m_finder._hasToken(devPath))
return;
udev_device* devCast = udev_device_get_parent_with_subsystem_devtype(device, "bluetooth", "bluetooth_device");
if (!devCast)
devCast = udev_device_get_parent_with_subsystem_devtype(device, "usb", "usb_device");
if (!devCast)
return;
int vid = 0, pid = 0;
udev_list_entry* attrs = udev_device_get_properties_list_entry(devCast);
udev_list_entry* vide = udev_list_entry_get_by_name(attrs, "ID_VENDOR_ID");
if (vide)
vid = strtol(udev_list_entry_get_value(vide), NULL, 16);
udev_list_entry* pide = udev_list_entry_get_by_name(attrs, "ID_MODEL_ID");
if (pide)
pid = strtol(udev_list_entry_get_value(pide), NULL, 16);
const char* manuf = NULL;
udev_list_entry* manufe = udev_list_entry_get_by_name(attrs, "ID_VENDOR");
if (manufe)
manuf = udev_list_entry_get_value(manufe);
const char* product = NULL;
udev_list_entry* producte = udev_list_entry_get_by_name(attrs, "ID_MODEL");
if (producte)
product = udev_list_entry_get_value(producte);
listener->m_finder._insertToken(CDeviceToken(vid, pid, manuf, product, devPath));
}
static void deviceDisconnected(CHIDListenerUdev* listener,
udev_device* device)
{
const char* devPath = udev_device_get_syspath(device);
listener->m_finder._removeToken(devPath);
}
static void _udevProc(CHIDListenerUdev* listener)
{
udev_monitor_enable_receiving(listener->m_udevMon);
int fd = udev_monitor_get_fd(listener->m_udevMon);
while (listener->m_udevRunning)
{
fd_set fds;
FD_ZERO(&fds);
FD_SET(fd, &fds);
select(fd+1, &fds, NULL, NULL, NULL);
udev_device* dev = udev_monitor_receive_device(listener->m_udevMon);
if (dev)
{
const char* action = udev_device_get_action(dev);
if (!strcmp(action, "add"))
{
deviceConnected(listener, dev);
}
else if (!strcmp(action, "remove"))
{
deviceDisconnected(listener, dev);
}
udev_device_unref(dev);
}
}
}
public:
CHIDListenerUdev(CDeviceFinder& finder)
: m_finder(finder)
{
/* Setup hiddev and hotplug events */
m_udevMon = udev_monitor_new_from_netlink(BooGetUdev(), "udev");
if (!m_udevMon)
throw std::runtime_error("unable to init udev_monitor");
udev_monitor_filter_add_match_subsystem_devtype(m_udevMon, "hid", NULL);
udev_monitor_filter_update(m_udevMon);
/* Initial HID Device Add */
m_scanningEnabled = true;
scanNow();
m_scanningEnabled = false;
/* Start hotplug thread */
m_udevRunning = true;
m_udevThread = new std::thread(_udevProc, this);
}
~CHIDListenerUdev()
{
m_udevRunning = false;
m_udevThread->join();
udev_monitor_unref(m_udevMon);
}
/* Automatic device scanning */
bool startScanning()
{
m_scanningEnabled = true;
return true;
}
bool stopScanning()
{
m_scanningEnabled = false;
return true;
}
/* Manual device scanning */
bool scanNow()
{
udev_enumerate* uenum = udev_enumerate_new(BooGetUdev());
udev_enumerate_add_match_subsystem(uenum, "hid");
udev_enumerate_scan_devices(uenum);
udev_list_entry* uenumList = udev_enumerate_get_list_entry(uenum);
udev_list_entry* uenumItem;
udev_list_entry_foreach(uenumItem, uenumList)
{
const char* devPath = udev_list_entry_get_name(uenumItem);
udev_device* dev = udev_device_new_from_syspath(UDEV_INST, devPath);
if (dev)
deviceConnected(this, dev);
udev_device_unref(dev);
}
udev_enumerate_unref(uenum);
return true;
}
};
IHIDListener* IHIDListenerNew(CDeviceFinder& finder)
{
return new CHIDListenerUdev(finder);
}

View File

@ -4,6 +4,7 @@
#else
#endif
#include <stdio.h>
#include <unistd.h>
#include <boo.hpp>
class CDolphinSmashAdapterCallback : public IDolphinSmashAdapterCallback
@ -61,6 +62,7 @@ int main(int argc, char** argv)
#if __APPLE__
CFRunLoopRun();
#else
while (true) {sleep(1);}
#endif
delete ctx;

View File

@ -1,2 +1,4 @@
SOURCES += \
$$PWD/main.cpp
CONFIG += c++11