Hi, all

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

Reply via email to