[Nut-upsdev] help writing a usb hid driver for existing ups

Michael Tokarev mjt+nut at tls.msk.ru
Tue Jan 22 12:53:04 UTC 2008


Charles Lepple wrote:
> 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.

Yeah, I tried to reach him - to no avail so far... ;)
[Update: he replied, telling he has urgent work to do before
he will be able to work with all this stuff again]

> 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.)

Here it is:

# usbhid-ups -DDD -a hid -u root -x explore -x vendorid=0d9f -x productid=0002
Network UPS Tools: 0.29 USB communication driver - core 0.32 (2.2.1)

debug level is '3'
upsdrv_initups...
Checking device (0D9F/0002) (002/004)
- VendorID: 0d9f
- ProductID: 0002
- Manufacturer: POWERCOM CO., LTD.
- Product: USB to Serial
- Serial Number: unknown
- Bus: 002
Trying to match device
Device matches
failed to claim USB device, trying 2 more time(s)...
detaching kernel driver from USB device...
trying again to claim USB device...
HID descriptor, method 1: (9 bytes) => 09 21 00 01 00 01 22 25 00
HID descriptor, method 2: (9 bytes) => 09 21 00 01 00 01 22 25 00
HID descriptor length 37
Report Descriptor size = 37
Report Descriptor: (37 bytes) => 06 a0 ff 09 01 a1 01 09 01 15 00 26 ff 00
75 08 95 08 81 02 09 02 75 08 95 08 91 02 09 03 75 08 95 05 b1 02 c0
Using subdriver: EXPLORE HID 0.1
Report[get]: (2 bytes) => 00 00
Path: ffa00001.ffa00001, Type: Input, ReportID: 0x00, Offset: 0, Size: 8, Value: 0.000000
Report[buf]: (2 bytes) => 00 00
Path: ffa00001.ffa00002, Type: Output, ReportID: 0x00, Offset: 0, Size: 8, Value: 0.000000
Report[buf]: (2 bytes) => 00 00
Path: ffa00001.ffa00003, Type: Feature, ReportID: 0x00, Offset: 0, Size: 8, Value: 0.000000
Report descriptor retrieved (Reportlen = 37)
Found HID device
Detected a UPS: POWERCOM CO., LTD./USB to Serial
^C


# lsusb -vvv
Bus 002 Device 004: ID 0d9f:0002 Powercom Co., Ltd
Device Descriptor:
  bLength                18
  bDescriptorType         1
  bcdUSB               1.00
  bDeviceClass            0 (Defined at Interface level)
  bDeviceSubClass         0
  bDeviceProtocol         0
  bMaxPacketSize0         8
  idVendor           0x0d9f Powercom Co., Ltd
  idProduct          0x0002
  bcdDevice            0.00
  iManufacturer           1 POWERCOM CO., LTD.
  iProduct                2 USB to Serial
  iSerial                 0
  bNumConfigurations      1
  Configuration Descriptor:
    bLength                 9
    bDescriptorType         2
    wTotalLength           41
    bNumInterfaces          1
    bConfigurationValue     1
    iConfiguration          4 Sample HID
    bmAttributes         0x80
      (Bus Powered)
    MaxPower              100mA
    Interface Descriptor:
      bLength                 9
      bDescriptorType         4
      bInterfaceNumber        0
      bAlternateSetting       0
      bNumEndpoints           2
      bInterfaceClass         3 Human Interface Device
      bInterfaceSubClass      0 No Subclass
      bInterfaceProtocol      0 None
      iInterface              0
        HID Device Descriptor:
          bLength                 9
          bDescriptorType        33
          bcdHID               1.00
          bCountryCode            0 Not supported
          bNumDescriptors         1
          bDescriptorType        34 Report
          wDescriptorLength      37
          Report Descriptor: (length is 37)
            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= [ 0x01 ] 1
                            (null)
            Item(Global): Logical Minimum, data= [ 0x00 ] 0
            Item(Global): Logical Maximum, data= [ 0xff 0x00 ] 255
            Item(Global): Report Size, data= [ 0x08 ] 8
            Item(Global): Report Count, data= [ 0x08 ] 8
            Item(Main  ): Input, data= [ 0x02 ] 2
                            Data Variable Absolute No_Wrap Linear
                            Preferred_State No_Null_Position Non_Volatile Bitfield
            Item(Local ): Usage, data= [ 0x02 ] 2
                            (null)
            Item(Global): Report Size, data= [ 0x08 ] 8
            Item(Global): Report Count, data= [ 0x08 ] 8
            Item(Main  ): Output, data= [ 0x02 ] 2
                            Data Variable Absolute No_Wrap Linear
                            Preferred_State No_Null_Position Non_Volatile Bitfield
            Item(Local ): Usage, data= [ 0x03 ] 3
                            (null)
            Item(Global): Report Size, data= [ 0x08 ] 8
            Item(Global): Report Count, data= [ 0x05 ] 5
            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     0x0008  1x 8 bytes
        bInterval              10
      Endpoint Descriptor:
        bLength                 7
        bDescriptorType         5
        bEndpointAddress     0x02  EP 2 OUT
        bmAttributes            3
          Transfer Type            Interrupt
          Synch Type               None
          Usage Type               Data
        wMaxPacketSize     0x0008  1x 8 bytes
        bInterval              10
Device Status:     0x0000
  (Bus Powered)


> Since it is the same protocol as the serial powercom driver, you can

Well, I hope it is.  But looking at the strings read from the
device by their program (upsmon) more closely, I'm not very
sure anymore.  I asked their support, maybe I'll have some
more info on this.

> 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.

Ok, I'll try to explore those.  The thing is that I don't know a bit
about the whole USB thing, and don't have intention to learn it at
full, either... ;)

By the way, there's a kernel-level usbserial kernel-level driver
for this device, written by another guy in Russia (I think I
mentioned that before), -- with that driver and powercom.c
from nut the device works.  So I wonder if it will be simpler
to write a user-level serial driver and embed it into
powercom.c module, instead of fidding with hid.  Either
way, it's still black magic for me ;)

>> 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
> 
> You 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.

Note I patched strace(1) utility to print the info here.  Before,
it just printed `ioctl(3, HIDIOCGUSAGE, 0x123456789)' - ie, an
address of the structure.  I looked the ioctl in the linux kernel
headers and added some code into strace to show the argument.

But again, since I don't know anything about USB and how it works
internally, the structure fields is of no great use to me... ;)

>> 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
> 
> This is most likely an interrupt read over whatever the input pipe is.

Funny thing is that the data is available almost immediately - the
select() call never sleeps for too much.  It looks like the device
is constantly feeding something to the host.  UNLIKE the "pure"
serial protocol.

>> Here's the init stuff:
[]
>> 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
> 
> Not 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").

Heh.  I figured it out that G vs S thing.. ;)  What I need is
someone who knows what it all does, and who's willing to help
a bit to get me started... ;)  I hope anyway :)

Thanks!

/mjt



More information about the Nut-upsdev mailing list