/************************************************************************************ Filename : OVR_Linux_HIDDevice.cpp Content : Linux HID device implementation. Created : February 26, 2013 Authors : Lee Cooper Copyright : Copyright 2014 Oculus VR, Inc. All Rights reserved. Licensed under the Oculus VR Rift SDK License Version 3.1 (the "License"); you may not use the Oculus VR Rift SDK except in compliance with the License, which is provided at the time of installation or download, or which otherwise accompanies this software in either electronic or hard copy form. You may obtain a copy of the License at http://www.oculusvr.com/licenses/LICENSE-3.1 Unless required by applicable law or agreed to in writing, the Oculus VR SDK distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. *************************************************************************************/ #include "OVR_Linux_HIDDevice.h" #include #include #include #include #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; if (getIntProperty(device, "bcdDevice", &result)) pDevDesc->VersionNumber = 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 existingDevice = DevManager->FindHIDDevice(devDesc, true); // 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 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) { OVR_UNUSED(i); OVR_UNUSED(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) : InMinimalMode(false), HIDManager(manager) { 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) : InMinimalMode(true), HIDManager(manager), DeviceHandle(device_handle) { } //----------------------------------------------------------------------------- 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_UNUSED(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); } //----------------------------------------------------------------------------- double HIDDevice::OnTicks(double tickSeconds) { if (Handler) { return Handler->OnTicks(tickSeconds); } return DeviceManagerThread::Notifier::OnTicks(tickSeconds); } //----------------------------------------------------------------------------- void HIDDevice::OnEvent(int i, int fd) { OVR_UNUSED(i); // 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 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(Ptr& deviceManager) { 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 deviceManagerLinux = *new Linux::DeviceManager; if (!deviceManagerLinux) { return NULL; } if (!deviceManagerLinux->Initialize(NULL)) { return NULL; } deviceManager = deviceManagerLinux; return deviceManagerLinux->GetHIDDeviceManager(); } } // namespace OVR