Michael Tokarev
2008-Jan-21 12:59 UTC
[Nut-upsdev] help writing a usb hid driver for existing ups
I've got a Powercom Imperial UPS, with an internal
USB<=>serial converter. It implements the same
protocol as other powercom devices implements, but
only when talking over serial port (there's no such
port on the device). Someone wrote a draft version
of usbserial driver for it, and the UPS works with
this kernel-level driver and with powercom driver
from nut (using /dev/ttyUSBx device).
The UPS comes with OEM program, upsmon, that talks
to the device using /dev/hiddevX device node, i.e.
using USB HID protocol. I captured some (s)traces
of it when at work(*).
The question is how to write proper driver for it
(because using kernel-mode usbserial driver looks
a bit.. too much and ugly). I don't understand
how USB and HID works.
(megatec_usb doesn't work with it - the serial
protocol is different).
What's the place to start with all this stuff,
having in mind that I don't want to learn all
the HID stuff - just some basics (probably just
an example) necessary to implement the driver.
(*) their program is looping doing the following
at regular intervals:
654 time(NULL) = 1200921228
654 ioctl(3, HIDIOCGUSAGE, {report_type=1, report_id=0, field_index=0,
usage_index=0, usage_code=ffa00001, value=240}) = 0
654 select(4, [3], NULL, NULL, {3, 0}) = 1 (in [3], left {2, 990000})
654 read(3,
"\1\0\240\377\360\0\0\0\1\0\240\377\0\0\0\0\1\0\240\377\0\0\0\0\1\0\240\377\0\0\0\0\1\0\240\377\0\0\0\0\1\0\240\377\0\0\0\0\1\0\240\377\0\0\0\0\1\0\240\377\0\0\0\0",
512) = 64
654 time(NULL) = 1200921228
654 ioctl(3, HIDIOCGUSAGE, {report_type=1, report_id=0, field_index=0,
usage_index=0, usage_code=ffa00001, value=240}) = 0
654 select(4, [3], NULL, NULL, {3, 0}) = 1 (in [3], left {2, 990000})
654 read(3,
"\1\0\240\377\360\0\0\0\1\0\240\377\0\0\0\0\1\0\240\377\0\0\0\0\1\0\240\377\0\0\0\0\1\0\240\377\0\0\0\0\1\0\240\377\0\0\0\0\1\0\240\377\0\0\0\0\1\0\240\377\0\0\0\0",
512) = 64
654 time(NULL) = 1200921228
...
with some init stuff before (fd#3 is /dev/hiddev0).
What it reads looks pretty much the same as the stuff
returned by similar UPS connected over normal serial
port (but 4x more - there are 4 identical blocks of
data read, each of 16bytes in size).
Here's the init stuff:
653 open("/dev/hiddev0", O_RDONLY) = 3
653 ioctl(3, HIDIOCAPPLICATION, 0) = -6291455
653 ioctl(3, HIDIOCAPPLICATION, 0) = -6291455
653 ioctl(3, HIDIOCGVERSION, 0xbfb09fdc) = 0
653 write(1, "hiddev driver version is 1.0.4\n", 31) = 31
653 ioctl(3, HIDIOCGDEVINFO, 0xbfb0a480) = 0
653 ioctl(3, HIDIOCAPPLICATION, 0) = -6291455
653 write(1, "HID: vendor 0xd9f product 0x2 version 0x0 applications [1]:
ffa00001\n", 69) = 69
653 write(1, "HID: bus: 2 devnum: 4 ifnum: 0\n", 31) = 31
653 ioctl(3, 0x81004806, 0xbfb0a380) = 33
653 write(1, "UPS HID device name: \"POWERCOM CO., LTD. USB to
Serial\"\n", 56) = 56
653 ioctl(3, HIDIOCINITREPORT, 0) = 0
653 ioctl(3, HIDIOCGUSAGE, {report_type=3, report_id=ffffffff, field_index=0,
usage_index=0, usage_code=850089, value=0}) = -1 EINVAL (Invalid argument)
653 ioctl(3, HIDIOCGUSAGE, {report_type=3, report_id=ffffffff, field_index=0,
usage_index=0, usage_code=8400ff, value=0}) = -1 EINVAL (Invalid argument)
653 ioctl(3, HIDIOCSUSAGE, {report_type=3, report_id=0, field_index=0,
usage_index=0, usage_code=ffa00003, value=95}) = 0
653 ioctl(3, HIDIOCSREPORT, {report_type=3,report_id=0,num_fields=0}) = 0
653 ioctl(3, HIDIOCSUSAGE, {report_type=3, report_id=0, field_index=0,
usage_index=1, usage_code=ffa00003, value=4}) = 0
653 ioctl(3, HIDIOCSREPORT, {report_type=3,report_id=0,num_fields=0}) = 0
653 ioctl(3, HIDIOCSUSAGE, {report_type=3, report_id=0, field_index=0,
usage_index=2, usage_code=ffa00003, value=0}) = 0
653 ioctl(3, HIDIOCSREPORT, {report_type=3,report_id=0,num_fields=0}) = 0
653 ioctl(3, HIDIOCSUSAGE, {report_type=3, report_id=0, field_index=0,
usage_index=3, usage_code=ffa00003, value=0}) = 0
653 ioctl(3, HIDIOCSREPORT, {report_type=3,report_id=0,num_fields=0}) = 0
653 ioctl(3, HIDIOCSUSAGE, {report_type=3, report_id=0, field_index=0,
usage_index=4, usage_code=ffa00003, value=3}) = 0
653 ioctl(3, HIDIOCSREPORT, {report_type=3,report_id=0,num_fields=0}) = 0
653 ioctl(3, HIDIOCSUSAGE, {report_type=2, report_id=0, field_index=0,
usage_index=0, usage_code=ffa00002, value=1}) = 0
653 ioctl(3, HIDIOCSUSAGE, {report_type=2, report_id=0, field_index=0,
usage_index=1, usage_code=ffa00002, value=1}) = 0
653 ioctl(3, HIDIOCSREPORT, {report_type=2,report_id=0,num_fields=0}) = 0
As I stated above, I don't know anything about all
this HID stuff and how it works, even the basic
principles...
Thanks!
/mjt
Charles Lepple
2008-Jan-22 04:25 UTC
[Nut-upsdev] help writing a usb hid driver for existing ups
Hi Michael, On Jan 21, 2008 7:59 AM, Michael Tokarev <mjt at tls.msk.ru> wrote:> I've got a Powercom Imperial UPS, with an internal > USB<=>serial converter. It implements the same > protocol as other powercom devices implements, but > only when talking over serial port (there's no such > port on the device). Someone wrote a draft version > of usbserial driver for it, and the UPS works with > this kernel-level driver and with powercom driver > from nut (using /dev/ttyUSBx device).It sounds like you may want to talk with Alexey Sidirov to coordinate support for this device. Can you post the output of 'lsusb -vvv' (run as root) for this device? (You will get a little more information from lsusb if you run 'usbhid-ups' once with the "-x explore" option, although usbhid-ups will probably not tell us much since they seem to not follow the USB Power Device Class (PDC) HID protocol.) Since it is the same protocol as the serial powercom driver, you can use the same technique as in the megatec and megatec_usb code: link the core powercom code with the serial functions for the regular powercom driver, and with USB HID code in powercom_usb.c (for instance). There are two ways to write the USB interface code. One is to use libusb functions, which are portable to other OSes like FreeBSD, NetBSD, OS X, and potentially Solaris. The other way (not recommended, but may be easier) is to just use the ioctl() calls that you found from strace. The first method is used by megatec_usb and usbhid-ups. The second method is used by energizerups.> 654 time(NULL) = 1200921228 > 654 ioctl(3, HIDIOCGUSAGE, {report_type=1, report_id=0, field_index=0, usage_index=0, usage_code=ffa00001, value=240}) = 0You will have to do a little reverse engineering here to figure out which fields are being passed in, and what they mean. I think that report_type, report_id and usage_code are passed in, and the others are returned by this ioctl() call.> 654 select(4, [3], NULL, NULL, {3, 0}) = 1 (in [3], left {2, 990000}) > 654 read(3, "\1\0\240\377\360\0\0\0\1\0\240\377\0\0\0\0\1\0\240\377\0\0\0\0\1\0\240\377\0\0\0\0\1\0\240\377\0\0\0\0\1\0\240\377\0\0\0\0\1\0\240\377\0\0\0\0\1\0\240\377\0\0\0\0", 512) = 64This is most likely an interrupt read over whatever the input pipe is.> Here's the init stuff: > > 653 open("/dev/hiddev0", O_RDONLY) = 3 > 653 ioctl(3, HIDIOCAPPLICATION, 0) = -6291455 > 653 ioctl(3, HIDIOCAPPLICATION, 0) = -6291455 > 653 ioctl(3, HIDIOCGVERSION, 0xbfb09fdc) = 0 > 653 write(1, "hiddev driver version is 1.0.4\n", 31) = 31 > 653 ioctl(3, HIDIOCGDEVINFO, 0xbfb0a480) = 0 > 653 ioctl(3, HIDIOCAPPLICATION, 0) = -6291455 > 653 write(1, "HID: vendor 0xd9f product 0x2 version 0x0 applications [1]: ffa00001\n", 69) = 69 > 653 write(1, "HID: bus: 2 devnum: 4 ifnum: 0\n", 31) = 31 > 653 ioctl(3, 0x81004806, 0xbfb0a380) = 33 > 653 write(1, "UPS HID device name: \"POWERCOM CO., LTD. USB to Serial\"\n", 56) = 56This is just getting some basic information about the device.> 653 ioctl(3, HIDIOCINITREPORT, 0) = 0 > 653 ioctl(3, HIDIOCGUSAGE, {report_type=3, report_id=ffffffff, field_index=0, usage_index=0, usage_code=850089, value=0}) = -1 EINVAL (Invalid argument) > 653 ioctl(3, HIDIOCGUSAGE, {report_type=3, report_id=ffffffff, field_index=0, usage_index=0, usage_code=8400ff, value=0}) = -1 EINVAL (Invalid argument)These last two calls seem to be looking for HID usage codes that are not supported by this particular device.> 653 ioctl(3, HIDIOCSUSAGE, {report_type=3, report_id=0, field_index=0, usage_index=0, usage_code=ffa00003, value=95}) = 0 > 653 ioctl(3, HIDIOCSREPORT, {report_type=3,report_id=0,num_fields=0}) = 0 > 653 ioctl(3, HIDIOCSUSAGE, {report_type=3, report_id=0, field_index=0, usage_index=1, usage_code=ffa00003, value=4}) = 0 > 653 ioctl(3, HIDIOCSREPORT, {report_type=3,report_id=0,num_fields=0}) = 0 > 653 ioctl(3, HIDIOCSUSAGE, {report_type=3, report_id=0, field_index=0, usage_index=2, usage_code=ffa00003, value=0}) = 0 > 653 ioctl(3, HIDIOCSREPORT, {report_type=3,report_id=0,num_fields=0}) = 0 > 653 ioctl(3, HIDIOCSUSAGE, {report_type=3, report_id=0, field_index=0, usage_index=3, usage_code=ffa00003, value=0}) = 0 > 653 ioctl(3, HIDIOCSREPORT, {report_type=3,report_id=0,num_fields=0}) = 0 > 653 ioctl(3, HIDIOCSUSAGE, {report_type=3, report_id=0, field_index=0, usage_index=4, usage_code=ffa00003, value=3}) = 0 > 653 ioctl(3, HIDIOCSREPORT, {report_type=3,report_id=0,num_fields=0}) = 0 > 653 ioctl(3, HIDIOCSUSAGE, {report_type=2, report_id=0, field_index=0, usage_index=0, usage_code=ffa00002, value=1}) = 0 > 653 ioctl(3, HIDIOCSUSAGE, {report_type=2, report_id=0, field_index=0, usage_index=1, usage_code=ffa00002, value=1}) = 0 > 653 ioctl(3, HIDIOCSREPORT, {report_type=2,report_id=0,num_fields=0}) = 0Not sure exactly what's going on here, but it looks like they are writing to the device (In HIDIOC + S + USAGE/REPORT, the 'S' is for "set", and 'G' stands for "get"). -- - Charles Lepple