Greetings.
I am trying to program a USB GPS device through HIDDEV interface on Linux. I believe the device is built upon Cypress CY7C64013 full speed USB-to-Serial chip and it shows as a generic HID device when plugged in. It require a feature report with char[5] data to change settings.
On Windows, I successfully did my work with WriteFile/ReadFile on hiddev driver interface. However, I found that Linux USB HIDDEV interface only accept signed integer as report data. I tried two ways but neither of them worked:
1. send these char[5] in two integer with two HIDIOCSUSAGE calls 2. with 5 HIDIOCSUSAGE calls to pass in int( char ) values
I searched the list and saw several people had similar questions about sending char array data to hiddev device. If there are walk-rounds, could you please share your knowledge?
Or is there any other ways should I approach in order to access the device on Linux? A USB driver that bypass the HID layer? devfs?
If I start the device on windows with my program, it will continue to send GPS data when I reboot the computer into Linux. However, reading from the device with linux-hiddev event interface has problems, too. This device returns a 32 bytes input report. I tried to rebuild the structure with returned values of multiple hiddev_event structures, but found out that some data showed up in the hid field randomly.
I attached the device information and my ugly testing program based the evtest.c and ups.c at the end. If anyone kindly has suggestions or comments, please let me know. Thanks a lot!
Tested system: kernel 2.4.22 and 2.6.0-test9 on Redhat 9 system
========= program output ============ hiddev driver version is 1.0.4 HID: vendor 0x1163 product 0x100 version 0x4 applications [1]: ffa00001 HID: bus: 4 devnum: 2 ifnum: 0 HID device name: "DeLorme Publishing DeLorme USB Earthmate" Reports of type Input (1): Report id: 0 (1 fields) Field: 0: app: ffa00001 phys 0000 flags 2 (1 usages) unit 0 exp 0 Usage: ffa00001 val 0 Reports of type Output (2): Report id: 0 (1 fields) Field: 0: app: ffa00001 phys 0000 flags 2 (1 usages) unit 0 exp 0 Usage: ffa00002 val 0 Reports of type Feature (3): Report id: 0 (1 fields) Field: 0: app: ffa00001 phys 0000 flags 2 (1 usages) unit 0 exp 0 Usage: ffa00003 val 129
========= usb devfs info =========== T: Bus=04 Lev=01 Prnt=01 Port=00 Cnt=01 Dev#= 2 Spd=12 MxCh= 0 D: Ver= 1.00 Cls=00(>ifc ) Sub=00 Prot=00 MxPS= 8 #Cfgs= 1 P: Vendor=1163 ProdID=0100 Rev= 0.04 S: Manufacturer=DeLorme Publishing S: Product=DeLorme USB Earthmate C:* #Ifs= 1 Cfg#= 1 Atr=80 MxPwr=100mA I: If#= 0 Alt= 0 #EPs= 2 Cls=03(HID ) Sub=00 Prot=00 Driver=hid E: Ad=81(I) Atr=03(Int.) MxPS= 32 Ivl=6ms E: Ad=02(O) Atr=03(Int.) MxPS= 32 Ivl=6ms
======== lsusb -vv output: ========== Bus 004 Device 002: ID 1163:0100 DeLorme Publishing, Inc. Device Descriptor: bLength 18 bDescriptorType 1 bcdUSB 1.00 bDeviceClass 0 Interface bDeviceSubClass 0 bDeviceProtocol 0 bMaxPacketSize0 8 idVendor 0x1163 DeLorme Publishing, Inc. idProduct 0x0100 bcdDevice 0.04 iManufacturer 1 DeLorme Publishing iProduct 2 DeLorme USB Earthmate iSerial 0 bNumConfigurations 1
Configuration Descriptor: bLength 9 bDescriptorType 2 wTotalLength 41 bNumInterfaces 1 bConfigurationValue 1 iConfiguration 4 bmAttributes 0x80 MaxPower 100mA Interface Descriptor: bLength 9 bDescriptorType 4 bInterfaceNumber 0 bAlternateSetting 0 bNumEndpoints 2 bInterfaceClass 3 Human Interface Devices bInterfaceSubClass 0 No Subclass bInterfaceProtocol 0 None iInterface 0 HID Device Descriptor: bLength 9 bDescriptorType 33 bcdHID 1.00 bCountryCode 0 bNumDescriptors 1 bDescriptorType 34 Report wDescriptorLength 37 cannot get report descriptor Endpoint Descriptor: bLength 7 bDescriptorType 5 bEndpointAddress 0x81 EP 1 IN bmAttributes 3 Transfer Type Interrupt Synch Type none wMaxPacketSize 32 bInterval 6 Endpoint Descriptor: bLength 7 bDescriptorType 5 bEndpointAddress 0x02 EP 2 OUT bmAttributes 3 Transfer Type Interrupt Synch Type none wMaxPacketSize 32 bInterval 6 Language IDs: (length=4) 0409 English(US)
/* * $Id: evtest.c,v 1.10 2000/08/17 18:56:37 vojtech Exp $ * * Copyright (c) 1999-2000 Vojtech Pavlik * * Event device test program */
/* * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 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, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * * Should you need to contact me, the author, you can do so either by * e-mail - mail your message to <[EMAIL PROTECTED]>, or by paper mail: * Vojtech Pavlik, Ucitelska 1576, Prague 8, 182 00 Czech Republic */ #include <stdlib.h> #include <string.h> #include <fcntl.h> #include <unistd.h> #include <stdio.h> #include <sys/types.h> #include <sys/time.h> #include <sys/ioctl.h> #include <sys/signal.h> #include <asm/types.h> #include <linux/hiddev.h> #define PWRSTAT "/etc/powerstatus" #define LOGFILE "/var/log/usbupsd.log" #define PIDFILE "/var/run/usbupsd.pid" #define DEBOUNCE_TIMEOUT 60 #define GPS_USAGE 0xffa00001 #define GPS_INPUT 0xffa00001 #define GPS_OUTPUT 0xffa00002 #define GPS_FEATURE 0xffa00003 #define UPS_USAGE 0x840004 #define UPS_SERIAL 0x8400ff #define BAT_CHEMISTRY 0x850089 #define UPS_SHUTDOWN_IMMINENT 0x840069 #define UPS_BATTERY_VOLTAGE 0x840030 #define UPS_BELOW_RCL 0x850042 #define UPS_CHARGING 0x850044 #define UPS_DISCHARGING 0x850045 #define UPS_REMAINING_CAPACITY 0x850066 #define UPS_RUNTIME_TO_EMPTY 0x850068 #define UPS_AC_PRESENT 0x8500d0 #define STATE_NORMAL 0 #define STATE_DEBOUNCE 1 #define STATE_BATTERY 2 static FILE *log_output; /* The Report format is described in the USB to Serial application note */ /* baud rate : 128, 37, 0 , 0 for 9600 0x4B, 0, 0 , 0 for 19200 0xC0, 0x12, 0, 0 for 4800 */ char feature_buffer[8] = { /* first byte is the report id */ 0xc0, 0x12, 0, 0, /* Baud Rate for 4800 */ 3, /* Word length = 8 bits: no parity, 1 stop bit*/ 0, 0, 0 /* place holder */ }; int feature_bufferi[5] = { 0xc0, 0x12, 0, 0, 3 }; union setting_data { int idata[2]; char cdata[8]; } mysettings; //int feature_buffer[6] = {0, /* first byte is the report id */ // 0xc0, 0x12, 0, 0, /* Baud Rate for 4800 */ // 3 /* Word length = 8 bits: no parity, 1 stop bit*/ // }; #define DEBUG #ifdef DEBUG struct { unsigned usage; char *label; } ups_info[] = { /* UPS System Page */ { UPS_SHUTDOWN_IMMINENT, "ShutdownImminent (bool)" }, { UPS_BATTERY_VOLTAGE, "Battery Voltage (centivolts)" }, /* Battery System Page */ { UPS_BELOW_RCL, "BelowRemainingCapacityLimit (bool)" }, { UPS_CHARGING, "Charging (bool)" }, { UPS_DISCHARGING, "Discharging (bool)" }, { UPS_REMAINING_CAPACITY, "RemainingCapacity (percent)" }, { UPS_RUNTIME_TO_EMPTY, "RunTimeToEmpty (seconds)" }, { UPS_AC_PRESENT, "ACPresent (bool)" }, }; #define UPS_INFO_SZ (sizeof(ups_info)/sizeof(ups_info[0])) void log_status(char *msg) { char buf[256]; fprintf(log_output, "[Log message \"%s\"]\n", msg); system(buf); } static inline int info_idx(unsigned int detail) { int i; for (i = 0; i < UPS_INFO_SZ; i++) { if (ups_info[i].usage == detail) { return i; } } return -1; } #else /* DEBUG */ #define log_status(s) #endif /* DEBUG */ /* Tell init the power has either gone or is back. */ void powerfail(int state) { int fd; /* Create an info file needed by init to shutdown/cancel shutdown */ unlink(PWRSTAT); if ((fd = open(PWRSTAT, O_CREAT|O_WRONLY, 0644)) >= 0) { if (state > 0) write(fd, "FAIL\n", 5); else if (state < 0) write(fd, "LOW\n", 4); else write(fd, "OK\n", 3); close(fd); } kill(1, SIGPWR); } static inline int find_application(int fd, unsigned usage) { int i = 0, ret; while ((ret = ioctl(fd, HIDIOCAPPLICATION, i)) > 0 && ret != usage) i++; return (ret == usage); } void hupSignal(int sig) { time_t ttime = time(NULL); if (log_output == stdout) return; fclose(log_output); log_output = fopen(LOGFILE, "a"); if (log_output == NULL) exit(1); fprintf(log_output, "Log restarted %s\n", ctime(&ttime)); } int main (int argc, char **argv) { int fd = -1, rd, i; struct hiddev_event ev[64]; struct hiddev_devinfo dinfo; char name[256] = "Unknown"; int state = 0; int run_as_daemon = 0; fd_set fdset; struct timeval timev, *tv = NULL; struct hiddev_usage_ref uref; struct hiddev_string_descriptor sdesc; time_t curr_time; if (argc > 1 && !strcmp(argv[1], "-daemon")) { run_as_daemon = 1; argc--; argv++; } if (argc < 2) { char evdev[20]; for (i = 0; i < 4; i++) { sprintf(evdev, "/dev/usb/hiddev%d", i); if ((fd = open(evdev, O_RDONLY)) >= 0) { /*if (find_application(fd, UPS_USAGE)) break;*/ if (find_application(fd, GPS_USAGE)) break; close(fd); } } if (i >= 4) { fprintf(stderr, "Couldn't find UPS device.\n"); exit(1); } } else { if ((fd = open(argv[argc - 1], O_RDONLY)) < 0) { perror("hiddev open"); exit(1); } if (!find_application(fd, UPS_USAGE)) { fprintf(stderr, "%s is not a UPS\n", argv[argc - 1]); exit(1); } } #ifdef DEBUG { int version; ioctl(fd, HIDIOCGVERSION, &version); printf("hiddev driver version is %d.%d.%d\n", version >> 16, (version >> 8) & 0xff, version & 0xff); ioctl(fd, HIDIOCGDEVINFO, &dinfo); printf("HID: vendor 0x%x product 0x%x version 0x%x ", dinfo.vendor, dinfo.product, dinfo.version); printf("applications [%d]: %04x", dinfo.num_applications, ioctl(fd, HIDIOCAPPLICATION, 0)); for (i = 1; i < dinfo.num_applications; i++) printf(", %04x", ioctl(fd, HIDIOCAPPLICATION, i)); printf("\n"); printf("HID: bus: %d devnum: %d ifnum: %d\n", dinfo.busnum, dinfo.devnum, dinfo.ifnum); } #endif if (run_as_daemon) { FILE *pidFile; struct sigaction hupAction; daemon(0, 0); log_output = fopen(LOGFILE, "a"); if (log_output == NULL) log_output = stdout; pidFile = fopen(PIDFILE, "w"); if (pidFile != NULL) { fprintf(pidFile, "%d\n", getpid()); fclose(pidFile); } memset(&hupAction, 0, sizeof(hupAction)); hupAction.sa_handler = hupSignal; sigaction(SIGHUP, &hupAction, NULL); } else { log_output = stdout; } ioctl(fd, HIDIOCGNAME(sizeof(name)), name); printf("GPS HID device name: \"%s\"\n", name); ioctl(fd, HIDIOCINITREPORT, 0); /* To traverse the report descriptor info */ { fprintf( log_output, "====== Before changing config ============\n"); struct hiddev_report_info rinfo; struct hiddev_field_info finfo; struct hiddev_usage_ref uref; int rtype, i, j; char *rtype_str; for (rtype = HID_REPORT_TYPE_MIN; rtype <= HID_REPORT_TYPE_MAX; rtype++) { switch (rtype) { case HID_REPORT_TYPE_INPUT: rtype_str = "Input"; break; case HID_REPORT_TYPE_OUTPUT: rtype_str = "Output"; break; case HID_REPORT_TYPE_FEATURE: rtype_str = "Feature"; break; default: rtype_str = "Unknown"; break; } fprintf(log_output, "Reports of type %s (%d):\n", rtype_str, rtype); rinfo.report_type = rtype; rinfo.report_id = HID_REPORT_ID_FIRST; while (ioctl(fd, HIDIOCGREPORTINFO, &rinfo) >= 0) { fprintf(log_output, " Report id: %d (%d fields)\n", rinfo.report_id, rinfo.num_fields); for (i = 0; i < rinfo.num_fields; i++) { memset(&finfo, 0, sizeof(finfo)); finfo.report_type = rinfo.report_type; finfo.report_id = rinfo.report_id; finfo.field_index = i; ioctl(fd, HIDIOCGFIELDINFO, &finfo); fprintf(log_output, " Field: %d: app: %04x phys %04x " "flags %x (%d usages) unit %x exp %d\n", i, finfo.application, finfo.physical, finfo.flags, finfo.maxusage, finfo.unit, finfo.unit_exponent); memset(&uref, 0, sizeof(uref)); for (j = 0; j < finfo.maxusage; j++) { uref.report_type = finfo.report_type; uref.report_id = finfo.report_id; uref.field_index = i; uref.usage_index = j; ioctl(fd, HIDIOCGUCODE, &uref); ioctl(fd, HIDIOCGUSAGE, &uref); fprintf(log_output, " Usage: %04x val %d\n", uref.usage_code, uref.value); } } rinfo.report_id |= HID_REPORT_ID_NEXT; } } } fflush(log_output); /* memset(&uref, 0, sizeof(uref)); uref.report_type = HID_REPORT_TYPE_FEATURE; uref.report_id = HID_REPORT_ID_UNKNOWN; uref.usage_code = GPS_FEATURE; if (ioctl(fd, HIDIOCGUSAGE, &uref) == 0) { sdesc.index = uref.value; if (ioctl(fd, HIDIOCGSTRING, &sdesc) < 0) strcpy(sdesc.value, "Unknown"); printf("Battery Chemistry: \"%s\" (%d)\n", sdesc.value, uref.value); } */ if (0) { struct hiddev_report_info rep_info; struct hiddev_usage_ref usage_ref; memset( &rep_info, 0, sizeof(rep_info) ); memset( &usage_ref, 0, sizeof(usage_ref) ); memcpy( mysettings.cdata, feature_buffer, 8 ); int i; for( i=0; i< 2; ++i ) { usage_ref.report_type =HID_REPORT_TYPE_FEATURE; //HID_REPORT_TYPE_OUTPUT; usage_ref.report_id = 0;//HID_REPORT_ID_UNKNOWN; usage_ref.field_index = i; usage_ref.usage_index = 0; usage_ref.usage_code = 0 ; /* no one cares? */ usage_ref.value = mysettings.idata[i]; ioctl(fd, HIDIOCSUSAGE, &usage_ref); } rep_info.report_type = HID_REPORT_TYPE_FEATURE; //HID_REPORT_TYPE_OUTPUT; rep_info.report_id = HID_REPORT_ID_UNKNOWN; rep_info.num_fields = 2; ioctl(fd, HIDIOCSREPORT, &rep_info); } if (0) { struct hiddev_report_info rep_info; struct hiddev_usage_ref usage_ref; memset( &rep_info, 0, sizeof(rep_info) ); memset( &usage_ref, 0, sizeof(usage_ref) ); int i; for( i=0; i< 5; ++i ) { usage_ref.report_type =HID_REPORT_TYPE_FEATURE; //HID_REPORT_TYPE_OUTPUT; usage_ref.report_id = 0;//HID_REPORT_ID_UNKNOWN; usage_ref.field_index = i; usage_ref.usage_index = 0; usage_ref.usage_code = 0 ; /* no one cares? */ usage_ref.value = feature_bufferi[i]; ioctl(fd, HIDIOCSUSAGE, &usage_ref); } rep_info.report_type = HID_REPORT_TYPE_FEATURE; //HID_REPORT_TYPE_OUTPUT; rep_info.report_id = HID_REPORT_ID_UNKNOWN; rep_info.num_fields = 5; ioctl(fd, HIDIOCSREPORT, &rep_info); } #ifdef DEBUG /* To traverse the report descriptor info */ { fprintf( log_output, "====== after changing config ============\n"); struct hiddev_report_info rinfo; struct hiddev_field_info finfo; struct hiddev_usage_ref uref; int rtype, i, j; char *rtype_str; for (rtype = HID_REPORT_TYPE_MIN; rtype <= HID_REPORT_TYPE_MAX; rtype++) { switch (rtype) { case HID_REPORT_TYPE_INPUT: rtype_str = "Input"; break; case HID_REPORT_TYPE_OUTPUT: rtype_str = "Output"; break; case HID_REPORT_TYPE_FEATURE: rtype_str = "Feature"; break; default: rtype_str = "Unknown"; break; } fprintf(log_output, "Reports of type %s (%d):\n", rtype_str, rtype); rinfo.report_type = rtype; rinfo.report_id = HID_REPORT_ID_FIRST; while (ioctl(fd, HIDIOCGREPORTINFO, &rinfo) >= 0) { fprintf(log_output, " Report id: %d (%d fields)\n", rinfo.report_id, rinfo.num_fields); for (i = 0; i < rinfo.num_fields; i++) { memset(&finfo, 0, sizeof(finfo)); finfo.report_type = rinfo.report_type; finfo.report_id = rinfo.report_id; finfo.field_index = i; ioctl(fd, HIDIOCGFIELDINFO, &finfo); fprintf(log_output, " Field: %d: app: %04x phys %04x " "flags %x (%d usages) unit %x exp %d\n", i, finfo.application, finfo.physical, finfo.flags, finfo.maxusage, finfo.unit, finfo.unit_exponent); memset(&uref, 0, sizeof(uref)); for (j = 0; j < finfo.maxusage; j++) { uref.report_type = finfo.report_type; uref.report_id = finfo.report_id; uref.field_index = i; uref.usage_index = j; ioctl(fd, HIDIOCGUCODE, &uref); ioctl(fd, HIDIOCGUSAGE, &uref); fprintf(log_output, " Usage: %04x val %d\n", uref.usage_code, uref.value); } } rinfo.report_id |= HID_REPORT_ID_NEXT; } } if (!run_as_daemon) fprintf(log_output, "Waiting for events ... (interrupt to exit)\n"); } fflush(log_output); #endif FD_ZERO(&fdset); int mycount=0; union { int ivalue; char cvalue[4]; } gpsdata; union { int ivalue; char cvalue[4]; }hiddata; gpsdata.ivalue = 0; while ( mycount < 10) { FD_SET(fd, &fdset); rd = select(fd+1, &fdset, NULL, NULL, tv); if (rd > 0) { rd = read(fd, ev, sizeof(ev)); if (rd < (int) sizeof(ev[0])) { if (rd < 0) perror("\nevtest: error reading"); else fprintf(log_output, "\nevtest: got short read from device!\n"); if (run_as_daemon) unlink(PIDFILE); exit (1); } int nread = rd/sizeof( ev[0] ); for (i = 0; i < nread; i++) { #ifdef DEBUG { int idx = info_idx(ev[i].hid); int value = ev[i].value; gpsdata.ivalue = ev[i].value; curr_time = time(NULL); strftime(name, sizeof(name), "%b %d %T", localtime(&curr_time)); hiddata.ivalue = ev[i].hid; fprintf(log_output, "%s: read %d Event(%d): usage %08x (%c %c %c %c),", name, mycount, i, ev[i].hid, hiddata.cvalue[0], hiddata.cvalue[1], hiddata.cvalue[2], hiddata.cvalue[3]); fprintf(log_output, "value %08x (%c %c %c %c)\n ", value, gpsdata.cvalue[0], gpsdata.cvalue[1], gpsdata.cvalue[2], gpsdata.cvalue[3]); } #endif /* DEBUG */ if (ev[i].hid == UPS_DISCHARGING) { if (ev[i].value == 1) { state = STATE_DEBOUNCE; timev.tv_sec = DEBOUNCE_TIMEOUT; timev.tv_usec = 0; } else { if (state == STATE_BATTERY) { log_status("System back on AC power"); //powerfail(0); } state = STATE_NORMAL; } } else if (ev[i].hid == UPS_SHUTDOWN_IMMINENT && ev[i].value == 1) { log_status("UPS shutdown imminent!"); //powerfail(-1); } } //fprintf( log_output, "\n"); } else { if (state == STATE_DEBOUNCE) { log_status("System switched to battery power"); state = STATE_BATTERY; //powerfail(1); } printf( " rd = 0 \n" ); } fflush(log_output); mycount ++ ; } fprintf( log_output, "\n" ); }