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" );
}
