/* Raw HID I/O Routines * Copyright 2008, PJRC.COM, LLC * paul@pjrc.com * * You may redistribute this program and/or modify it under the terms * of the GNU General Public License as published by the Free Software * Foundation, either version 3 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see http://www.gnu.org/licenses/ */ // This code will someday be turned into "librawhid", an easy-to-use // and truly cross platform library for accessing HID reports. But // there are many complexities not properly handled by this simple // code that would be expected from a high quality library. In // particular, how report IDs are handled is not uniform on the 3 // platforms. The mac code uses a single buffer which assumes no // other functions can cause the "run loop" to process HID callbacks. // The linux version doesn't extract usage and usage page from the // report descriptor and just hardcodes a signature for the Teensy // USB debug example. Lacking from all platforms are functions to // manage multiple devices and robust detection of device removal // and attachment. There are probably lots of other issues... this // code has really only been used in 2 projects. If you use it, // please report bugs to paul@pjrc.com #include #include #include #include #include "rawhid.h" #ifdef OPERATING_SYSTEM #undef OPERATING_SYSTEM #endif /*************************************************************************/ /** **/ /** Linux **/ /** **/ /*************************************************************************/ #if defined(LINUX) || defined(__LINUX__) || #system(linux) #define OPERATING_SYSTEM linux #include #include #include #include #include #include #include struct rawhid_struct { int fd; int name; int isok; }; rawhid_t * rawhid_open_only1(int vid, int pid, int usage_page, int usage) { struct rawhid_struct *hid; struct stat devstat; struct hidraw_devinfo info; struct hidraw_report_descriptor *desc; char buf[512]; // TODO; inputs are ignored and hardcoded into this signature.... const unsigned char signature[]={0x06,0x31,0xFF,0x09,0x74}; int r, i, fd=-1, len, found=0; //printf("Searching for device using hidraw....\n"); for (i=0; i 0) close(fd); snprintf(buf, sizeof(buf), "/dev/hidraw%d", i); r = stat(buf, &devstat); if (r < 0) continue; //printf("device: %s\n", buf); fd = open(buf, O_RDWR); if (fd < 0) continue; //printf(" opened\n"); r = ioctl(fd, HIDIOCGRAWINFO, &info); if (r < 0) continue; //printf(" vid=%04X, pid=%04X\n", info.vendor & 0xFFFF, info.product & 0xFFFF); r = ioctl(fd, HIDIOCGRDESCSIZE, &len); if (r < 0 || len < 1) continue; //printf(" len=%u\n", len); desc = (struct hidraw_report_descriptor *)buf; if (len > sizeof(buf)-sizeof(int)) len = sizeof(buf)-sizeof(int); desc->size = len; r = ioctl(fd, HIDIOCGRDESC, desc); if (r < 0) continue; if (len >= sizeof(signature) && memcmp(desc->value, signature, sizeof(signature)) == 0) { //printf(" Match\n"); found = 1; break; } } if (!found) { if (fd > 0) close(fd); return NULL; } hid = (struct rawhid_struct *)malloc(sizeof(struct rawhid_struct)); if (!hid) { close(fd); return NULL; } hid->fd = fd; return hid; } int rawhid_status(rawhid_t *hid) { // TODO: how to check if device is still online? return -1; } int rawhid_read(rawhid_t *h, void *buf, int bufsize, int timeout_ms) { struct rawhid_struct *hid; int num; hid = (struct rawhid_struct *)h; if (!hid || hid->fd < 0) return -1; while (1) { num = read(hid->fd, buf, bufsize); if (num < 0) { if (errno == EINTR || errno == EAGAIN) continue; if (errno == EIO) { return -1; printf("I/O Error\n"); } printf("read error, r=%d, errno=%d\n", num, errno); return -1; } //printf("read %d bytes\n", num); return num; } } void rawhid_close(rawhid_t *h) { struct rawhid_struct *hid; hid = (struct rawhid_struct *)h; if (!hid || hid->fd < 0) return; close(hid->fd); hid->fd = -1; } #if 0 struct rawhid_list_struct { int count; struct rawhid_list_entry { int name; int desc_length; const char *desc; } dev[HIDRAW_MAX_DEVICES]; }; rawhid_list_t * rawhid_list_open(int vid, int pid, int usage_page, int usage) { struct rawhid_list_struct *list; struct stat devstat; struct hidraw_devinfo info; struct hidraw_report_descriptor *desc; char buf[512], *p; const unsigned char signature[]={0x06,0x31,0xFF,0x09,0x74}; int r, i, fd=-1, len; list = (struct rawhid_list_struct *)malloc(sizeof(struct rawhid_list_struct)); if (!list) return NULL; list->count = 0; printf("Searching for device using hidraw....\n"); for (i=0; i 0) close(fd); snprintf(buf, sizeof(buf), "/dev/hidraw%d", i); r = stat(buf, &devstat); if (r < 0) continue; printf("device: %s\n", buf); fd = open(buf, O_RDWR); if (fd < 0) continue; printf(" opened\n"); r = ioctl(fd, HIDIOCGRAWINFO, &info); if (r < 0) continue; printf(" vid=%04X, pid=%04X\n", info.vendor & 0xFFFF, info.product & 0xFFFF); r = ioctl(fd, HIDIOCGRDESCSIZE, &len); if (r < 0 || len < 1) continue; printf(" len=%u\n", len); desc = (struct hidraw_report_descriptor *)buf; if (len > sizeof(buf)-sizeof(int)) len = sizeof(buf)-sizeof(int); desc->size = len; r = ioctl(fd, HIDIOCGRDESC, desc); if (r < 0) continue; if (len < sizeof(signature)) continue; // TODO: actual report parsing would be nice! if (memcmp(desc->value, signature, sizeof(signature)) != 0) continue; p = (char *)malloc(len); if (!p) continue; printf(" Match\n"); memcpy(p, desc->value, len); list->dev[list->count].desc = p; list->dev[list->count].desc_length = len; list->dev[list->count].name = i; list->count++; } if (fd > 0) close(fd); return list; } int rawhid_list_count(rawhid_list_t *list) { if (!list) return 0; return ((struct rawhid_list_struct *)list)->count; } void rawhid_list_close(rawhid_list_t *list) { if (list) free(list); } #endif #endif // linux /*************************************************************************/ /** **/ /** Mac OS X 10.5 **/ /** **/ /*************************************************************************/ #if (defined(DARWIN) || defined(__DARWIN__)) && !defined(OPERATING_SYSTEM) #define OPERATING_SYSTEM darwin #include #include #include #include // http://developer.apple.com/technotes/tn2007/tn2187.html struct rawhid_struct { IOHIDDeviceRef ref; int disconnected; uint8_t *buffer; int buffer_used; int buffer_report_id; }; static void unplug_callback(void *hid, IOReturn ret, void *ref) { // This callback can only be called when the "run loop" (managed by macos) // is run. If the GUI is running it when idle, this will get called // automatically. If not, the run loop needs to be run explicitly // before checking the result of this function. //printf("HID/macos: unplugged callback!\n"); ((struct rawhid_struct *)hid)->disconnected = 1; } static void input_callback(void *context, IOReturn result, void *sender, IOHIDReportType type, uint32_t reportID, uint8_t *report, CFIndex reportLength) { struct rawhid_struct *hid; //printf("input callback\n"); if (!context) return; hid = (struct rawhid_struct *)context; hid->buffer_used = reportLength; hid->buffer_report_id = reportID; //printf("id = %d, reportLength = %d\n", reportID, (int)reportLength); //if (hid->ref == sender) printf("ref matches :-)\n"); //if (report == hid->buffer) printf("buffer matches :)\n"); } rawhid_t * rawhid_open_only1(int vid, int pid, int usage_page, int usage) { IOHIDManagerRef hid_manager; CFMutableDictionaryRef dict; IOReturn ret; CFSetRef device_set; IOHIDDeviceRef device_list[256]; uint8_t *buf; struct rawhid_struct *hid; int num_devices; // get access to the HID Manager hid_manager = IOHIDManagerCreate(kCFAllocatorDefault, kIOHIDOptionsTypeNone); if (hid_manager == NULL || CFGetTypeID(hid_manager) != IOHIDManagerGetTypeID()) { printf("HID/macos: unable to access HID manager"); return NULL; } // configure it to look for our type of device dict = IOServiceMatching(kIOHIDDeviceKey); if (dict == NULL) { printf("HID/macos: unable to create iokit dictionary"); return NULL; } if (vid > 0) { CFDictionarySetValue(dict, CFSTR(kIOHIDVendorIDKey), CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &vid)); } if (pid > 0) { CFDictionarySetValue(dict, CFSTR(kIOHIDProductIDKey), CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &pid)); } if (usage_page > 0) { CFDictionarySetValue(dict, CFSTR(kIOHIDPrimaryUsagePageKey), CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &usage_page)); } if (usage > 0) { CFDictionarySetValue(dict, CFSTR(kIOHIDPrimaryUsageKey), CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &usage)); } IOHIDManagerSetDeviceMatching(hid_manager, dict); // now open the HID manager ret = IOHIDManagerOpen(hid_manager, kIOHIDOptionsTypeNone); if (ret != kIOReturnSuccess) { printf("HID/macos: Unable to open HID manager (IOHIDManagerOpen failed)"); return NULL; } // get a list of devices that match our requirements device_set = IOHIDManagerCopyDevices(hid_manager); if (device_set == NULL) { //printf("HID/macos: no devices found\n"); return NULL; } num_devices = (int)CFSetGetCount(device_set); //printf("number of devices found = %d\n", num_devices); if (num_devices < 1) { CFRelease(device_set); printf("HID/macos: no devices found, even though HID manager returned a set\n"); return NULL; } if (num_devices > 256) { CFRelease(device_set); printf("HID/macos: too many devices, we get confused if more than 256!\n"); return NULL; } CFSetGetValues(device_set, (const void **)&device_list); CFRelease(device_set); // open the first device in the list ret = IOHIDDeviceOpen(device_list[0], kIOHIDOptionsTypeNone); if (ret != kIOReturnSuccess) { printf("HID/macos: error opening device\n"); return NULL; } // return this device hid = (struct rawhid_struct *)malloc(sizeof(struct rawhid_struct)); buf = (uint8_t *)malloc(0x1000); if (hid == NULL || buf == NULL) { IOHIDDeviceRegisterRemovalCallback(device_list[0], NULL, NULL); IOHIDDeviceClose(device_list[0], kIOHIDOptionsTypeNone); printf("HID/macos: Unable to allocate memory\n"); return NULL; } hid->ref = device_list[0]; hid->disconnected = 0; hid->buffer = buf; hid->buffer_used = 0; // register a callback to receive input IOHIDDeviceRegisterInputReportCallback(hid->ref, hid->buffer, 0x1000, input_callback, hid); // register a callback to find out when it's unplugged IOHIDDeviceScheduleWithRunLoop(hid->ref, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode); IOHIDDeviceRegisterRemovalCallback(hid->ref, unplug_callback, hid); return hid; } int rawhid_status(rawhid_t *hid) { if (!hid) return -1; // if a GUI is causing the run loop run, this will likely mess it up. Just // comment it out and if the callback still gets called without this, then // there's no need to run the run loop here! while (CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, true) == kCFRunLoopRunHandledSource) ; if (((struct rawhid_struct *)hid)->disconnected) { //printf("HID/macos: status: disconnected\n"); return -1; } //printf("HID/macos: status: ok\n"); return 0; } void rawhid_close(rawhid_t *hid) { IOHIDDeviceRef ref; if (!hid) return; ref = ((struct rawhid_struct *)hid)->ref; IOHIDDeviceRegisterRemovalCallback(ref, NULL, NULL); IOHIDDeviceClose(ref, kIOHIDOptionsTypeNone); free(hid); } int rawhid_read(rawhid_t *h, void *buf, int bufsize, int timeout_ms) { struct rawhid_struct *hid; int r, len; //printf("begin read\n"); hid = (struct rawhid_struct *)h; if (!hid || hid->disconnected) return -1; while (CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, true) == kCFRunLoopRunHandledSource) { if (hid->buffer_used) { len = hid->buffer_used; if (len > bufsize) len = bufsize; memcpy(buf, hid->buffer, len); hid->buffer_used = 0; return len; } if (hid->disconnected) { return -1; } } r = CFRunLoopRunInMode(kCFRunLoopDefaultMode, (double)timeout_ms / 1000.0, true); if (r == kCFRunLoopRunTimedOut) { //printf("read timeout\n"); return 0; } if (hid->buffer_used) { len = hid->buffer_used; if (len > bufsize) len = bufsize; memcpy(buf, hid->buffer, len); hid->buffer_used = 0; return len; } if (hid->disconnected) return -1; return 0; //num = bufsize; //ret = IOHIDDeviceGetReport(ref, kIOHIDReportTypeInput, 0, buf, &num); //if (!ret) return -1; //return num; } int rawhid_write(rawhid_t *hid, const void *buf, int len, int timeout_ms) { IOReturn ret; if (((struct rawhid_struct *)hid)->disconnected) return -1; ret = IOHIDDeviceSetReport(((struct rawhid_struct *)hid)->ref, kIOHIDReportTypeOutput, 0, buf, len); if (ret != kIOReturnSuccess) return -1; return 0; } #endif // Darwin - Mac OS X /*************************************************************************/ /** **/ /** Windows 2000/XP/Vista **/ /** **/ /*************************************************************************/ #if (defined(WIN32) || defined(WINDOWS) || defined(__WINDOWS__)) && !defined(OPERATING_SYSTEM) #define OPERATING_SYSTEM windows #include #include #include #include // http://msdn.microsoft.com/en-us/library/ms790932.aspx struct rawhid_struct { HANDLE handle; }; rawhid_t * rawhid_open_only1(int vid, int pid, int usage_page, int usage) { GUID guid; HDEVINFO info; DWORD index=0, required_size; SP_DEVICE_INTERFACE_DATA iface; SP_DEVICE_INTERFACE_DETAIL_DATA *details; HIDD_ATTRIBUTES attrib; PHIDP_PREPARSED_DATA hid_data; HIDP_CAPS capabilities; struct rawhid_struct *hid; HANDLE h; BOOL ret; HidD_GetHidGuid(&guid); info = SetupDiGetClassDevs(&guid, NULL, NULL, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE); if (info == INVALID_HANDLE_VALUE) { printf("HID/win32: SetupDiGetClassDevs failed"); return NULL; } for (index=0; ;index++) { iface.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA); ret = SetupDiEnumDeviceInterfaces(info, NULL, &guid, index, &iface); if (!ret) { // end of list SetupDiDestroyDeviceInfoList(info); return NULL; } SetupDiGetInterfaceDeviceDetail(info, &iface, NULL, 0, &required_size, NULL); details = (SP_DEVICE_INTERFACE_DETAIL_DATA *)malloc(required_size); if (details == NULL) continue; memset(details, 0, required_size); details->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA); ret = SetupDiGetDeviceInterfaceDetail(info, &iface, details, required_size, NULL, NULL); if (!ret) { free(details); continue; } h = CreateFile(details->DevicePath, GENERIC_READ|GENERIC_WRITE, FILE_SHARE_READ|FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL); free(details); if (h == INVALID_HANDLE_VALUE) continue; attrib.Size = sizeof(HIDD_ATTRIBUTES); ret = HidD_GetAttributes(h, &attrib); if (!ret) { CloseHandle(h); continue; } //printf("HID/win32: USB Device:\n"); //printf("HID/win32: vid = 0x%04X\n", (int)(attrib.VendorID)); //printf("HID/win32: pid = 0x%04X\n", (int)(attrib.ProductID)); if (vid > 0 && vid != (int)(attrib.VendorID)) { CloseHandle(h); continue; } if (pid > 0 && pid != (int)(attrib.ProductID)) { CloseHandle(h); continue; } if (!HidD_GetPreparsedData(h, &hid_data)) { printf("HID/win32: HidD_GetPreparsedData failed\n"); CloseHandle(h); continue; } if (!HidP_GetCaps(hid_data, &capabilities)) { printf("HID/win32: HidP_GetCaps failed\n"); HidD_FreePreparsedData(hid_data); CloseHandle(h); continue; } //printf("HID/win32: usage_page = 0x%04X\n", (int)(capabilities.UsagePage)); //printf("HID/win32: usage = 0x%04X\n", (int)(capabilities.Usage)); if (usage_page > 0 && usage_page != (int)(capabilities.UsagePage)) { HidD_FreePreparsedData(hid_data); CloseHandle(h); continue; } if (usage > 0 && usage != (int)(capabilities.Usage)) { HidD_FreePreparsedData(hid_data); CloseHandle(h); continue; } HidD_FreePreparsedData(hid_data); hid = (struct rawhid_struct *)malloc(sizeof(struct rawhid_struct)); if (!hid) { CloseHandle(h); printf("HID/win32: Unable to get %d bytes", sizeof(struct rawhid_struct)); continue; } hid->handle = h; return hid; } } int rawhid_status(rawhid_t *hid) { PHIDP_PREPARSED_DATA hid_data; if (!hid) return -1; if (!HidD_GetPreparsedData(((struct rawhid_struct *)hid)->handle, &hid_data)) { printf("HID/win32: HidD_GetPreparsedData failed, device assumed disconnected\n"); return -1; } printf("HID/win32: HidD_GetPreparsedData ok, device still online :-)\n"); HidD_FreePreparsedData(hid_data); return 0; } void rawhid_close(rawhid_t *hid) { if (!hid) return; CloseHandle(((struct rawhid_struct *)hid)->handle); free(hid); } int rawhid_read(rawhid_t *h, void *buf, int bufsize, int timeout_ms) { DWORD num=0, result; BOOL ret; OVERLAPPED ov; struct rawhid_struct *hid; int r; hid = (struct rawhid_struct *)h; if (!hid) return -1; memset(&ov, 0, sizeof(OVERLAPPED)); ov.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); if (ov.hEvent == NULL) return -1; ret = ReadFile(hid->handle, buf, bufsize, &num, &ov); if (ret) { //printf("HID/win32: read success (immediate)\n"); r = num; } else { if (GetLastError() == ERROR_IO_PENDING) { result = WaitForSingleObject(ov.hEvent, timeout_ms); if (result == WAIT_OBJECT_0) { if (GetOverlappedResult(hid->handle, &ov, &num, FALSE)) { //printf("HID/win32: read success (delayed)\n"); r = num; } else { //printf("HID/win32: read failure (delayed)\n"); r = -1; } } else { //printf("HID/win32: read timeout, %lx\n", result); CancelIo(hid->handle); r = 0; } } else { //printf("HID/win32: read error (immediate)\n"); r = -1; } } CloseHandle(ov.hEvent); return r; } int rawhid_write(rawhid_t *h, const void *buf, int len, int timeout_ms) { DWORD num=0; BOOL ret; OVERLAPPED ov; struct rawhid_struct *hid; int r; hid = (struct rawhid_struct *)h; if (!hid) return -1; memset(&ov, 0, sizeof(OVERLAPPED)); ov.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); if (ov.hEvent == NULL) return -1; // first byte is report ID, must be zero if report IDs not used ret = WriteFile(hid->handle, buf, len, &num, &ov); if (ret) { if (num == len) { //printf("HID/win32: write success (immediate)\n"); r = 0; } else { //printf("HID/win32: partial write (immediate)\n"); r = -1; } } else { if (GetLastError() == ERROR_IO_PENDING) { if (GetOverlappedResult(hid->handle, &ov, &num, TRUE)) { if (num == len) { //printf("HID/win32: write success (delayed)\n"); r = 0; } else { //printf("HID/win32: partial write (delayed)\n"); r = -1; } } else { //printf("HID/win32: write error (delayed)\n"); r = -1; } } else { //printf("HID/win32: write error (immediate)\n"); r = -1; } } CloseHandle(ov.hEvent); return r; } #endif // windows #ifndef OPERATING_SYSTEM #error Unknown operating system #endif