[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