The device is configured to send
and receive 32 byte reports. The designer of the USB project has code
written in Windows Pascal (Delphi?) to control the device. The code
is a rework from the code generated by EasyHID and is a USB controlled
car radio. Here is my output of lsusb:
------------------------------------------------------------
llamabox hqct # lsusb -d 04d8:000a -vvv
Bus 001 Device 002: ID 04d8:000a Microchip Technology, Inc.
Device Descriptor:
bLength 18
bDescriptorType 1
bcdUSB 2.00
bDeviceClass 0 (Defined at Interface level)
bDeviceSubClass 0
bDeviceProtocol 0
bMaxPacketSize0 8
idVendor 0x04d8 Microchip Technology, Inc.
idProduct 0x000a
bcdDevice 0.01
iManufacturer 1 Datalex
iProduct 2 Car-Radio
iSerial 0
bNumConfigurations 1
Configuration Descriptor:
bLength 9
bDescriptorType 2
wTotalLength 41
bNumInterfaces 1
bConfigurationValue 1
iConfiguration 0
bmAttributes 0xa0
Remote Wakeup
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.01
bCountryCode 0 Not supported
bNumDescriptors 1
bDescriptorType 34 Report
wDescriptorLength 47
Report Descriptor: (length is 47)
Item(Global): Usage Page, data= [ 0xa0 0xff ] 65440
(null)
Item(Local ): Usage, data= [ 0x01 ] 1
(null)
Item(Main ): Collection, data= [ 0x01 ] 1
Application
Item(Local ): Usage, data= [ 0x03 ] 3
(null)
Item(Global): Logical Minimum, data= [ 0x00 ] 0
Item(Global): Logical Maximum, data= [ 0x00 0xff ] 65280
Item(Global): Report Size, data= [ 0x08 ] 8
Item(Global): Report Count, data= [ 0x20 ] 32
Item(Main ): Input, data= [ 0x02 ] 2
Data Variable Absolute No_Wrap Linear
Preferred_State No_Null_Position
Non_Volatile Bitfield
Item(Local ): Usage, data= [ 0x04 ] 4
(null)
Item(Global): Logical Minimum, data= [ 0x00 ] 0
Item(Global): Logical Maximum, data= [ 0x00 0xff ] 65280
Item(Global): Report Size, data= [ 0x08 ] 8
Item(Global): Report Count, data= [ 0x20 ] 32
Item(Main ): Output, data= [ 0x02 ] 2
Data Variable Absolute No_Wrap Linear
Preferred_State No_Null_Position
Non_Volatile Bitfield
Item(Local ): Usage, data= [ 0x05 ] 5
(null)
Item(Global): Logical Minimum, data= [ 0x00 ] 0
Item(Global): Logical Maximum, data= [ 0x00 0xff ] 65280
Item(Global): Report Size, data= [ 0x08 ] 8
Item(Global): Report Count, data= [ 0x02 ] 2
Item(Main ): Feature, data= [ 0x02 ] 2
Data Variable Absolute No_Wrap Linear
Preferred_State No_Null_Position
Non_Volatile Bitfield
Item(Main ): End Collection, data=none
Endpoint Descriptor:
bLength 7
bDescriptorType 5
bEndpointAddress 0x81 EP 1 IN
bmAttributes 3
Transfer Type Interrupt
Synch Type None
Usage Type Data
wMaxPacketSize 0x0040 1x 64 bytes
bInterval 1
Endpoint Descriptor:
bLength 7
bDescriptorType 5
bEndpointAddress 0x01 EP 1 OUT
bmAttributes 3
Transfer Type Interrupt
Synch Type None
Usage Type Data
wMaxPacketSize 0x0040 1x 64 bytes
bInterval 1
------------------------------------------------------------
As you can see, the device is rather simple. 3 reports (input, output,
and feature) and contains two interrupt endpoints.
I tried writing a kernel module based around usb-skel.c. I changed the
skeleton to write to the interrupt endpoints instead of using bulk
transfer since that is not supported by the PIC. I was able to
noticeably change the state of the device, but the results varied and
I haven't been able to alter the device like this since then.
I then tried using libhid, but it seems like the library is immature
and simply not the solution I was hoping for. While using that
solution I tried several paths, rebooting between each one:
{ 0xffa00001, 0xffa00003 }
{ 0xffa00003 }
{ 0xffa00001, 0xffa00004 }
{ 0xffa00004 }
{ 0xffa10001, 0xffa00003 }
I have even tried using three different functions for writing while
using the libhid approach: hid_set_output_report(),
usb_interrupt_write(), and hid_interrupt_write().
So, that brings us to the HIDDEV. It took me a long time to make
sense of hiddev.txt, but I'm pretty sure I understand it. I wrote a
simple demo application which I have included below. The program does
not seem to affect the unit at all. I can hear some crosstalk from the
device's headphone jack while the report is being sent to the device
though.
Here is the source, it isn't the best but it should work:
------------------------------------------------------------
// Simple HQCT test program using HIDDEV
#include <stdlib.h>
#include <stdio.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <asm/types.h>
#include <fcntl.h>
#include <unistd.h>
#include <linux/hiddev.h>
struct hiddev_usage_ref uref_out; // The output usage ref
struct hiddev_report_info rinfo_out; // The output report info
int found_out = 0; // 1 = found an output, 0 =
no output found
unsigned char go_am[] =
"\x18\x30\x40\x0f\x9f\x01\x00\x30\x50\x50\x74\x6f\x6a\x00\x03\x03\x10\x0A\x0c\x01\x01\x00\xa0\x00\x00\x00\x00\x00\x00\x00\x00\x00";
// Control string
static void showReports(int fd, unsigned report_type)
{
struct hiddev_report_info rinfo;
struct hiddev_field_info finfo;
struct hiddev_usage_ref uref;
int i, j, ret;
rinfo.report_type = report_type;
rinfo.report_id = HID_REPORT_ID_FIRST;
ret = ioctl(fd, HIDIOCGREPORTINFO, &rinfo);
// Get Reports
while (ret >= 0)
{
printf("HIDIOCGREPORTINFO: report_id=0x%X (%u fields)\n",
rinfo.report_id, rinfo.num_fields);
// Copy the output report
if( report_type == HID_REPORT_TYPE_OUTPUT ) {
memcpy(&rinfo_out, &rinfo, sizeof(rinfo));
found_out = 1;
}
// Get Fields
for (i = 0; i < rinfo.num_fields; i++)
{
finfo.report_type = rinfo.report_type;
finfo.report_id = rinfo.report_id;
finfo.field_index = i;
ioctl(fd, HIDIOCGFIELDINFO, &finfo);
printf("HIDIOCGFIELDINFO: field_index=%u maxusage=%u
flags=0x%X\n"
"\tphysical=0x%X logical=0x%X application=0x%X\n"
"\tlogical_minimum=%d,maximum=%d
physical_minimum=%d,maximum=%d\n",
finfo.field_index, finfo.maxusage, finfo.flags,
finfo.physical, finfo.logical, finfo.application,
finfo.logical_minimum, finfo.logical_maximum,
finfo.physical_minimum, finfo.physical_maximum);
// Get usages
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);
printf(" >> usage_index=%u usage_code=0x%X ()
value=0x%X\n",
uref.usage_index,
uref.usage_code,
uref.value);
// First output usage: grab it.
if(uref.report_type == HID_REPORT_TYPE_OUTPUT && j==0) {
memcpy(&uref_out, &uref, sizeof(uref));
}
}
}
printf("\n");
rinfo.report_id |= HID_REPORT_ID_NEXT;
ret = ioctl(fd, HIDIOCGREPORTINFO, &rinfo);
}
}
int main (int argc, char **argv) {
int fd = -1;
int i;
if (argc != 2) {
fprintf(stderr, "usage: %s hiddevice - probably
/dev/usb/hiddev0\n", argv[0]);
exit(1);
}
if ((fd = open(argv[1], O_RDONLY)) < 0) {
perror("hiddev open");
exit(1);
}
printf("\n*** INPUT:\n"); showReports(fd, HID_REPORT_TYPE_INPUT);
printf("\n*** OUTPUT:\n"); showReports(fd, HID_REPORT_TYPE_OUTPUT);
printf("\n*** FEATURE:\n"); showReports(fd, HID_REPORT_TYPE_FEATURE);
if( found_out ) {
printf("The output report was found, trying to send: \n");
printf("HIDIOCGREPORTINFO: report_id=0x%X (%u fields)\n",
rinfo_out.report_id, rinfo_out.num_fields);
// There are 32 usages in and out
for(i = 0; i < 32; i++) {
uref_out.usage_index = i;
uref_out.value = go_am[i];
printf(" << usage_index=%u usage_code=0x%X () value=0x%X\n",
uref_out.usage_index,
uref_out.usage_code,
uref_out.value);
if( ioctl(fd,HIDIOCSUSAGE, &uref_out) != 0 ) {
perror(" !! SUSAGE failed. ");
}
}
printf(" << sending report ...\n");
if( ioctl(fd,HIDIOCSREPORT,&rinfo_out) != 0 ) {
perror(" !! SREPORT ");
} else {
printf(" << report sent.\n");
}
} else {
printf(" !! Could not find HQCT Radio.\n");
}
close(fd);
return 0;
}
------------------------------------------------------------
Here is what happens: The program is run and the report is sent in a
split second. The second time I run the program, the output from the
previous run is still contained in the output report (since it is read
by showReports). However, the call to HIDIOCSREPORT takes around
5-8seconds instead of < 1s like the first run. All successive runs
exhibit this behavior. Oh yeah, and the radio does not respond to my
report at all. I have even tried changing HID_REPORT_TYPE_OUTPUT to
HID_REPORT_TYPE_INPUT.
Here are my questions: Am I using the best method? Is there a simpler
way to do this? Is my demo program written correctly? And finally, if
everything seems fine, what could the problem possibly be (besides a
faulty command string)? I have included the output from two
consecutive runs to my demo program below.
Thanks,
Paul Giblock
------------------------------------------------------------