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