diff options
Diffstat (limited to 'LibOVR/Src/OVR_Linux_HIDDevice.cpp')
-rw-r--r-- | LibOVR/Src/OVR_Linux_HIDDevice.cpp | 799 |
1 files changed, 799 insertions, 0 deletions
diff --git a/LibOVR/Src/OVR_Linux_HIDDevice.cpp b/LibOVR/Src/OVR_Linux_HIDDevice.cpp new file mode 100644 index 0000000..6656e9a --- /dev/null +++ b/LibOVR/Src/OVR_Linux_HIDDevice.cpp @@ -0,0 +1,799 @@ +/************************************************************************************ +Filename : OVR_Linux_HIDDevice.cpp +Content : Linux HID device implementation. +Created : February 26, 2013 +Authors : Lee Cooper + +Copyright : Copyright 2013 Oculus VR, Inc. All Rights reserved. + +Use of this software is subject to the terms of the Oculus license +agreement provided at the time of installation or download, or which +otherwise accompanies this software in either electronic or hard copy form. + +*************************************************************************************/ + +#include "OVR_Linux_HIDDevice.h" + +#include <sys/ioctl.h> +#include <fcntl.h> +#include <errno.h> +#include <linux/hidraw.h> +#include "OVR_HIDDeviceImpl.h" + +namespace OVR { namespace Linux { + +static const UInt32 MAX_QUEUED_INPUT_REPORTS = 5; + +//------------------------------------------------------------------------------------- +// **** Linux::DeviceManager +//----------------------------------------------------------------------------- +HIDDeviceManager::HIDDeviceManager(DeviceManager* manager) : DevManager(manager) +{ + UdevInstance = NULL; + HIDMonitor = NULL; + HIDMonHandle = -1; +} + +//----------------------------------------------------------------------------- +HIDDeviceManager::~HIDDeviceManager() +{ +} + +//----------------------------------------------------------------------------- +bool HIDDeviceManager::initializeManager() +{ + if (HIDMonitor) + { + return true; + } + + // Create a udev_monitor handle to watch for device changes (hot-plug detection) + HIDMonitor = udev_monitor_new_from_netlink(UdevInstance, "udev"); + if (HIDMonitor == NULL) + { + return false; + } + + udev_monitor_filter_add_match_subsystem_devtype(HIDMonitor, "hidraw", NULL); // filter for hidraw only + + int err = udev_monitor_enable_receiving(HIDMonitor); + if (err) + { + udev_monitor_unref(HIDMonitor); + HIDMonitor = NULL; + return false; + } + + // Get the file descriptor (fd) for the monitor. + HIDMonHandle = udev_monitor_get_fd(HIDMonitor); + if (HIDMonHandle < 0) + { + udev_monitor_unref(HIDMonitor); + HIDMonitor = NULL; + return false; + } + + // This file handle will be polled along-side with the device hid handles for changes + // Add the handle to the polling list + if (!DevManager->pThread->AddSelectFd(this, HIDMonHandle)) + { + close(HIDMonHandle); + HIDMonHandle = -1; + + udev_monitor_unref(HIDMonitor); + HIDMonitor = NULL; + return false; + } + + return true; +} + +//----------------------------------------------------------------------------- +bool HIDDeviceManager::Initialize() +{ + // Get a udev library handle. This handle must stay active during the + // duration the lifetime of device monitoring handles + UdevInstance = udev_new(); + if (!UdevInstance) + return false; + + return initializeManager(); +} + +//----------------------------------------------------------------------------- +void HIDDeviceManager::Shutdown() +{ + OVR_ASSERT_LOG((UdevInstance), ("Should have called 'Initialize' before 'Shutdown'.")); + + if (HIDMonitor) + { + DevManager->pThread->RemoveSelectFd(this, HIDMonHandle); + close(HIDMonHandle); + HIDMonHandle = -1; + + udev_monitor_unref(HIDMonitor); + HIDMonitor = NULL; + } + + udev_unref(UdevInstance); // release the library + + LogText("OVR::Linux::HIDDeviceManager - shutting down.\n"); +} + +//------------------------------------------------------------------------------- +bool HIDDeviceManager::AddNotificationDevice(HIDDevice* device) +{ + NotificationDevices.PushBack(device); + return true; +} + +//------------------------------------------------------------------------------- +bool HIDDeviceManager::RemoveNotificationDevice(HIDDevice* device) +{ + for (UPInt i = 0; i < NotificationDevices.GetSize(); i++) + { + if (NotificationDevices[i] == device) + { + NotificationDevices.RemoveAt(i); + return true; + } + } + return false; +} + +//----------------------------------------------------------------------------- +bool HIDDeviceManager::getIntProperty(udev_device* device, + const char* propertyName, + SInt32* pResult) +{ + const char* str = udev_device_get_sysattr_value(device, propertyName); + if (str) + { + *pResult = strtol(str, NULL, 16); + return true; + } + else + { + *pResult = 0; + return true; + } +} + +//----------------------------------------------------------------------------- +bool HIDDeviceManager::initVendorProductVersion(udev_device* device, HIDDeviceDesc* pDevDesc) +{ + SInt32 result; + if (getIntProperty(device, "idVendor", &result)) + pDevDesc->VendorId = result; + else + return false; + + if (getIntProperty(device, "idProduct", &result)) + pDevDesc->ProductId = result; + else + return false; + + return true; +} + +//----------------------------------------------------------------------------- +bool HIDDeviceManager::getStringProperty(udev_device* device, + const char* propertyName, + OVR::String* pResult) +{ + // Get the attribute in UTF8 + const char* str = udev_device_get_sysattr_value(device, propertyName); + if (str) + { // Copy the string into the return value + *pResult = String(str); + return true; + } + else + { + return false; + } +} + +//----------------------------------------------------------------------------- +bool HIDDeviceManager::Enumerate(HIDEnumerateVisitor* enumVisitor) +{ + + if (!initializeManager()) + { + return false; + } + + // Get a list of hid devices + udev_enumerate* devices = udev_enumerate_new(UdevInstance); + udev_enumerate_add_match_subsystem(devices, "hidraw"); + udev_enumerate_scan_devices(devices); + + udev_list_entry* entry = udev_enumerate_get_list_entry(devices); + + // Search each device for the matching vid/pid + while (entry != NULL) + { + // Get the device file name + const char* sysfs_path = udev_list_entry_get_name(entry); + udev_device* hid; // The device's HID udev node. + hid = udev_device_new_from_syspath(UdevInstance, sysfs_path); + const char* dev_path = udev_device_get_devnode(hid); + + // Get the USB device + hid = udev_device_get_parent_with_subsystem_devtype(hid, "usb", "usb_device"); + if (hid) + { + HIDDeviceDesc devDesc; + + // Check the VID/PID for a match + if (dev_path && + initVendorProductVersion(hid, &devDesc) && + enumVisitor->MatchVendorProduct(devDesc.VendorId, devDesc.ProductId)) + { + devDesc.Path = dev_path; + getFullDesc(hid, &devDesc); + + // Look for the device to check if it is already opened. + Ptr<DeviceCreateDesc> existingDevice = DevManager->FindHIDDevice(devDesc); + // if device exists and it is opened then most likely the device open() + // will fail; therefore, we just set Enumerated to 'true' and continue. + if (existingDevice && existingDevice->pDevice) + { + existingDevice->Enumerated = true; + } + else + { // open the device temporarily for startup communication + int device_handle = open(dev_path, O_RDWR); + if (device_handle >= 0) + { + // Construct minimal device that the visitor callback can get feature reports from + Linux::HIDDevice device(this, device_handle); + enumVisitor->Visit(device, devDesc); + + close(device_handle); // close the file handle + } + } + } + + udev_device_unref(hid); + entry = udev_list_entry_get_next(entry); + } + } + + // Free the enumerator and udev objects + udev_enumerate_unref(devices); + + return true; +} + +//----------------------------------------------------------------------------- +OVR::HIDDevice* HIDDeviceManager::Open(const String& path) +{ + Ptr<Linux::HIDDevice> device = *new Linux::HIDDevice(this); + + if (device->HIDInitialize(path)) + { + device->AddRef(); + return device; + } + + return NULL; +} + +//----------------------------------------------------------------------------- +bool HIDDeviceManager::getFullDesc(udev_device* device, HIDDeviceDesc* desc) +{ + + if (!initVendorProductVersion(device, desc)) + { + return false; + } + + if (!getStringProperty(device, "serial", &(desc->SerialNumber))) + { + return false; + } + + getStringProperty(device, "manufacturer", &(desc->Manufacturer)); + getStringProperty(device, "product", &(desc->Product)); + + return true; +} + +//----------------------------------------------------------------------------- +bool HIDDeviceManager::GetDescriptorFromPath(const char* dev_path, HIDDeviceDesc* desc) +{ + if (!initializeManager()) + { + return false; + } + + // Search for the udev device from the given pathname so we can + // have a handle to query device properties + + udev_enumerate* devices = udev_enumerate_new(UdevInstance); + udev_enumerate_add_match_subsystem(devices, "hidraw"); + udev_enumerate_scan_devices(devices); + + udev_list_entry* entry = udev_enumerate_get_list_entry(devices); + + bool success = false; + // Search for the device with the matching path + while (entry != NULL) + { + // Get the device file name + const char* sysfs_path = udev_list_entry_get_name(entry); + udev_device* hid; // The device's HID udev node. + hid = udev_device_new_from_syspath(UdevInstance, sysfs_path); + const char* path = udev_device_get_devnode(hid); + + if (OVR_strcmp(dev_path, path) == 0) + { // Found the device so lets collect the device descriptor + + // Get the USB device + hid = udev_device_get_parent_with_subsystem_devtype(hid, "usb", "usb_device"); + if (hid) + { + desc->Path = dev_path; + success = getFullDesc(hid, desc); + } + + } + + udev_device_unref(hid); + entry = udev_list_entry_get_next(entry); + } + + // Free the enumerator + udev_enumerate_unref(devices); + + return success; +} + +//----------------------------------------------------------------------------- +void HIDDeviceManager::OnEvent(int i, int fd) +{ + // There is a device status change + udev_device* hid = udev_monitor_receive_device(HIDMonitor); + if (hid) + { + const char* dev_path = udev_device_get_devnode(hid); + const char* action = udev_device_get_action(hid); + + HIDDeviceDesc device_info; + device_info.Path = dev_path; + + MessageType notify_type; + if (OVR_strcmp(action, "add") == 0) + { + notify_type = Message_DeviceAdded; + + // Retrieve the device info. This can only be done on a connected + // device and is invalid for a disconnected device + + // Get the USB device + hid = udev_device_get_parent_with_subsystem_devtype(hid, "usb", "usb_device"); + if (!hid) + { + return; + } + + getFullDesc(hid, &device_info); + } + else if (OVR_strcmp(action, "remove") == 0) + { + notify_type = Message_DeviceRemoved; + } + else + { + return; + } + + bool error = false; + bool deviceFound = false; + for (UPInt i = 0; i < NotificationDevices.GetSize(); i++) + { + if (NotificationDevices[i] && + NotificationDevices[i]->OnDeviceNotification(notify_type, &device_info, &error)) + { + // The notification was for an existing device + deviceFound = true; + break; + } + } + + if (notify_type == Message_DeviceAdded && !deviceFound) + { + DevManager->DetectHIDDevice(device_info); + } + + udev_device_unref(hid); + } +} + +//============================================================================= +// Linux::HIDDevice +//============================================================================= +HIDDevice::HIDDevice(HIDDeviceManager* manager) + : HIDManager(manager), InMinimalMode(false) +{ + DeviceHandle = -1; +} + +//----------------------------------------------------------------------------- +// This is a minimal constructor used during enumeration for us to pass +// a HIDDevice to the visit function (so that it can query feature reports). +HIDDevice::HIDDevice(HIDDeviceManager* manager, int device_handle) +: HIDManager(manager), DeviceHandle(device_handle), InMinimalMode(true) +{ +} + +//----------------------------------------------------------------------------- +HIDDevice::~HIDDevice() +{ + if (!InMinimalMode) + { + HIDShutdown(); + } +} + +//----------------------------------------------------------------------------- +bool HIDDevice::HIDInitialize(const String& path) +{ + const char* hid_path = path.ToCStr(); + if (!openDevice(hid_path)) + { + LogText("OVR::Linux::HIDDevice - Failed to open HIDDevice: %s", hid_path); + return false; + } + + HIDManager->DevManager->pThread->AddTicksNotifier(this); + HIDManager->AddNotificationDevice(this); + + LogText("OVR::Linux::HIDDevice - Opened '%s'\n" + " Manufacturer:'%s' Product:'%s' Serial#:'%s'\n", + DevDesc.Path.ToCStr(), + DevDesc.Manufacturer.ToCStr(), DevDesc.Product.ToCStr(), + DevDesc.SerialNumber.ToCStr()); + + return true; +} + +//----------------------------------------------------------------------------- +bool HIDDevice::initInfo() +{ + // Device must have been successfully opened. + OVR_ASSERT(DeviceHandle >= 0); + + int desc_size = 0; + hidraw_report_descriptor rpt_desc; + memset(&rpt_desc, 0, sizeof(rpt_desc)); + + // get report descriptor size + int r = ioctl(DeviceHandle, HIDIOCGRDESCSIZE, &desc_size); + if (r < 0) + { + OVR_ASSERT_LOG(false, ("Failed to get report descriptor size.")); + return false; + } + + // Get the report descriptor + rpt_desc.size = desc_size; + r = ioctl(DeviceHandle, HIDIOCGRDESC, &rpt_desc); + if (r < 0) + { + OVR_ASSERT_LOG(false, ("Failed to get report descriptor.")); + return false; + } + + /* + // Get report lengths. + SInt32 bufferLength; + bool getResult = HIDManager->getIntProperty(Device, CFSTR(kIOHIDMaxInputReportSizeKey), &bufferLength); + OVR_ASSERT(getResult); + InputReportBufferLength = (UInt16) bufferLength; + + getResult = HIDManager->getIntProperty(Device, CFSTR(kIOHIDMaxOutputReportSizeKey), &bufferLength); + OVR_ASSERT(getResult); + OutputReportBufferLength = (UInt16) bufferLength; + + getResult = HIDManager->getIntProperty(Device, CFSTR(kIOHIDMaxFeatureReportSizeKey), &bufferLength); + OVR_ASSERT(getResult); + FeatureReportBufferLength = (UInt16) bufferLength; + + + if (ReadBufferSize < InputReportBufferLength) + { + OVR_ASSERT_LOG(false, ("Input report buffer length is bigger than read buffer.")); + return false; + } + + // Get device desc. + if (!HIDManager->getFullDesc(Device, &DevDesc)) + { + OVR_ASSERT_LOG(false, ("Failed to get device desc while initializing device.")); + return false; + } + + return true; + */ + + // Get report lengths. +// TODO: hard-coded for now. Need to interpret these values from the report descriptor + InputReportBufferLength = 62; + OutputReportBufferLength = 0; + FeatureReportBufferLength = 69; + + if (ReadBufferSize < InputReportBufferLength) + { + OVR_ASSERT_LOG(false, ("Input report buffer length is bigger than read buffer.")); + return false; + } + + return true; +} + +//----------------------------------------------------------------------------- +bool HIDDevice::openDevice(const char* device_path) +{ + // First fill out the device descriptor + if (!HIDManager->GetDescriptorFromPath(device_path, &DevDesc)) + { + return false; + } + + // Now open the device + DeviceHandle = open(device_path, O_RDWR); + if (DeviceHandle < 0) + { + OVR_DEBUG_LOG(("Failed 'CreateHIDFile' while opening device, error = 0x%X.", errno)); + DeviceHandle = -1; + return false; + } + + // fill out some values from the feature report descriptor + if (!initInfo()) + { + OVR_ASSERT_LOG(false, ("Failed to get HIDDevice info.")); + + close(DeviceHandle); + DeviceHandle = -1; + return false; + } + + // Add the device to the polling list + if (!HIDManager->DevManager->pThread->AddSelectFd(this, DeviceHandle)) + { + OVR_ASSERT_LOG(false, ("Failed to initialize polling for HIDDevice.")); + + close(DeviceHandle); + DeviceHandle = -1; + return false; + } + + return true; +} + +//----------------------------------------------------------------------------- +void HIDDevice::HIDShutdown() +{ + + HIDManager->DevManager->pThread->RemoveTicksNotifier(this); + HIDManager->RemoveNotificationDevice(this); + + if (DeviceHandle >= 0) // Device may already have been closed if unplugged. + { + closeDevice(false); + } + + LogText("OVR::Linux::HIDDevice - HIDShutdown '%s'\n", DevDesc.Path.ToCStr()); +} + +//----------------------------------------------------------------------------- +void HIDDevice::closeDevice(bool wasUnplugged) +{ + OVR_ASSERT(DeviceHandle >= 0); + + + HIDManager->DevManager->pThread->RemoveSelectFd(this, DeviceHandle); + + close(DeviceHandle); // close the file handle + DeviceHandle = -1; + + LogText("OVR::Linux::HIDDevice - HID Device Closed '%s'\n", DevDesc.Path.ToCStr()); +} + +//----------------------------------------------------------------------------- +void HIDDevice::closeDeviceOnIOError() +{ + LogText("OVR::Linux::HIDDevice - Lost connection to '%s'\n", DevDesc.Path.ToCStr()); + closeDevice(false); +} + +//----------------------------------------------------------------------------- +bool HIDDevice::SetFeatureReport(UByte* data, UInt32 length) +{ + + if (DeviceHandle < 0) + return false; + + UByte reportID = data[0]; + + if (reportID == 0) + { + // Not using reports so remove from data packet. + data++; + length--; + } + + int r = ioctl(DeviceHandle, HIDIOCSFEATURE(length), data); + return (r >= 0); +} + +//----------------------------------------------------------------------------- +bool HIDDevice::GetFeatureReport(UByte* data, UInt32 length) +{ + if (DeviceHandle < 0) + return false; + + int r = ioctl(DeviceHandle, HIDIOCGFEATURE(length), data); + return (r >= 0); +} + +//----------------------------------------------------------------------------- +UInt64 HIDDevice::OnTicks(UInt64 ticksMks) +{ + if (Handler) + { + return Handler->OnTicks(ticksMks); + } + + return DeviceManagerThread::Notifier::OnTicks(ticksMks); +} + +//----------------------------------------------------------------------------- +void HIDDevice::OnEvent(int i, int fd) +{ + // We have data to read from the device + int bytes = read(fd, ReadBuffer, ReadBufferSize); + if (bytes >= 0) + { +// TODO: I need to handle partial messages and package reconstruction + if (Handler) + { + Handler->OnInputReport(ReadBuffer, bytes); + } + } + else + { // Close the device on read error. + closeDeviceOnIOError(); + } +} + +//----------------------------------------------------------------------------- +bool HIDDevice::OnDeviceNotification(MessageType messageType, + HIDDeviceDesc* device_info, + bool* error) +{ + const char* device_path = device_info->Path.ToCStr(); + + if (messageType == Message_DeviceAdded && DeviceHandle < 0) + { + // Is this the correct device? + if (!(device_info->VendorId == DevDesc.VendorId + && device_info->ProductId == DevDesc.ProductId + && device_info->SerialNumber == DevDesc.SerialNumber)) + { + return false; + } + + // A closed device has been re-added. Try to reopen. + if (!openDevice(device_path)) + { + LogError("OVR::Linux::HIDDevice - Failed to reopen a device '%s' that was re-added.\n", + device_path); + *error = true; + return true; + } + + LogText("OVR::Linux::HIDDevice - Reopened device '%s'\n", device_path); + + if (Handler) + { + Handler->OnDeviceMessage(HIDHandler::HIDDeviceMessage_DeviceAdded); + } + } + else if (messageType == Message_DeviceRemoved) + { + // Is this the correct device? + // For disconnected device, the device description will be invalid so + // checking the path is the only way to match them + if (DevDesc.Path.CompareNoCase(device_path) != 0) + { + return false; + } + + if (DeviceHandle >= 0) + { + closeDevice(true); + } + + if (Handler) + { + Handler->OnDeviceMessage(HIDHandler::HIDDeviceMessage_DeviceRemoved); + } + } + else + { + OVR_ASSERT(0); + } + + *error = false; + return true; +} + +//----------------------------------------------------------------------------- +HIDDeviceManager* HIDDeviceManager::CreateInternal(Linux::DeviceManager* devManager) +{ + + if (!System::IsInitialized()) + { + // Use custom message, since Log is not yet installed. + OVR_DEBUG_STATEMENT(Log::GetDefaultLog()-> + LogMessage(Log_Debug, "HIDDeviceManager::Create failed - OVR::System not initialized"); ); + return 0; + } + + Ptr<Linux::HIDDeviceManager> manager = *new Linux::HIDDeviceManager(devManager); + + if (manager) + { + if (manager->Initialize()) + { + manager->AddRef(); + } + else + { + manager.Clear(); + } + } + + return manager.GetPtr(); +} + +} // namespace Linux + +//------------------------------------------------------------------------------------- +// ***** Creation + +// Creates a new HIDDeviceManager and initializes OVR. +HIDDeviceManager* HIDDeviceManager::Create() +{ + OVR_ASSERT_LOG(false, ("Standalone mode not implemented yet.")); + + if (!System::IsInitialized()) + { + // Use custom message, since Log is not yet installed. + OVR_DEBUG_STATEMENT(Log::GetDefaultLog()-> + LogMessage(Log_Debug, "HIDDeviceManager::Create failed - OVR::System not initialized"); ); + return 0; + } + + Ptr<Linux::HIDDeviceManager> manager = *new Linux::HIDDeviceManager(NULL); + + if (manager) + { + if (manager->Initialize()) + { + manager->AddRef(); + } + else + { + manager.Clear(); + } + } + + return manager.GetPtr(); +} + +} // namespace OVR |