Search code examples
usbdriverreverse-engineeringhidusb-hid

Accessing USB device data based only on DESCRIPTOR HID Report


I have a Digital Sound Level Meter (sonometer) GM1356 with USB. There is some software to handle it on Windows, however I don't have CD and it's not available on the internet. What I want do to is to read it's data about current noise level on Linux.

I found already a library that allows me to do this in a language I know (ruby, libusb). In next step I installed wireshark to check out what it sends do the pc. It doesn't send too much. The most interesting packet I found is DESCRIPTOR HID Report. I wonder what next steps should I take to read data that is interesting for me. How can I determine what requests I should send to get it?

HID Report
    Global item (Usage)
        Header
            .... ..10 = bSize: 2 bytes (2)
            .... 01.. = bType: Global (1)
            0000 .... = bTag: Usage (0x0)
        Usage page: [Vendor-defined] (0xffa0)
    Local item (Usage)
        Header
            .... ..01 = bSize: 1 byte (1)
            .... 10.. = bType: Local (2)
            0000 .... = bTag: Usage (0x0)
        Usage: [Vendor-defined] (0xffa00001)
    Main item (Collection)
        Header
            .... ..01 = bSize: 1 byte (1)
            .... 00.. = bType: Main (0)
            1010 .... = bTag: Collection (0xa)
        Collection type: Application (0x01)
        Local item (Usage)
            Header
                .... ..01 = bSize: 1 byte (1)
                .... 10.. = bType: Local (2)
                0000 .... = bTag: Usage (0x0)
            Usage: [Vendor-defined] (0xffa00002)
        Main item (Collection)
            Header
                .... ..01 = bSize: 1 byte (1)
                .... 00.. = bType: Main (0)
                1010 .... = bTag: Collection (0xa)
            Collection type: Physical (0x00)
            Global item (Usage)
                Header
                    .... ..10 = bSize: 2 bytes (2)
                    .... 01.. = bType: Global (1)
                    0000 .... = bTag: Usage (0x0)
                Usage page: [Vendor-defined] (0xffa1)
            Local item (Usage)
                Header
                    .... ..01 = bSize: 1 byte (1)
                    .... 10.. = bType: Local (2)
                    0000 .... = bTag: Usage (0x0)
                Usage: [Vendor-defined] (0xffa10003)
            Local item (Usage)
                Header
                    .... ..01 = bSize: 1 byte (1)
                    .... 10.. = bType: Local (2)
                    0000 .... = bTag: Usage (0x0)
                Usage: [Vendor-defined] (0xffa10004)
            Global item (Logical minimum)
                Header
                    .... ..01 = bSize: 1 byte (1)
                    .... 01.. = bType: Global (1)
                    0001 .... = bTag: Logical minimum (0x1)
                Logical minimum: 128
            Global item (Logical maximum)
                Header
                    .... ..01 = bSize: 1 byte (1)
                    .... 01.. = bType: Global (1)
                    0010 .... = bTag: Logical maximum (0x2)
                Logical maximum: 127
            Global item (Physical minimum)
                Header
                    .... ..01 = bSize: 1 byte (1)
                    .... 01.. = bType: Global (1)
                    0011 .... = bTag: Physical minimum (0x3)
                Physical minimum: 0
            Global item (Physical maximum)
                Header
                    .... ..01 = bSize: 1 byte (1)
                    .... 01.. = bType: Global (1)
                    0100 .... = bTag: Physical maximum (0x4)
                Physical maximum: 255
            Global item (Report size)
                Header
                    .... ..01 = bSize: 1 byte (1)
                    .... 01.. = bType: Global (1)
                    0111 .... = bTag: Report size (0x7)
                Report size: 8
            Global item (Report count)
                Header
                    .... ..01 = bSize: 1 byte (1)
                    .... 01.. = bType: Global (1)
                    1001 .... = bTag: Report count (0x9)
                Report count: 8
            Main item (Input)
                Header
                    .... ..01 = bSize: 1 byte (1)
                    .... 00.. = bType: Main (0)
                    1000 .... = bTag: Input (0x8)
                .... .... 0 = Data/constant: Data
                .... ...1 . = Data type: Variable
                .... ..0. . = Coordinates: Absolute
                .... .0.. . = Min/max wraparound: No Wrap
                .... 0... . = Physical relationship to data: Linear
                ...0 .... . = Preferred state: Preferred State
                ..0. .... . = Has null position: No Null position
                .0.. .... . = [Reserved]: False
                0... .... . = Bits or bytes: Buffered bytes (default, no second byte present)
            Local item (Usage)
                Header
                    .... ..01 = bSize: 1 byte (1)
                    .... 10.. = bType: Local (2)
                    0000 .... = bTag: Usage (0x0)
                Usage: [Vendor-defined] (0xffa10005)
            Local item (Usage)
                Header
                    .... ..01 = bSize: 1 byte (1)
                    .... 10.. = bType: Local (2)
                    0000 .... = bTag: Usage (0x0)
                Usage: [Vendor-defined] (0xffa10006)
            Global item (Logical minimum)
                Header
                    .... ..01 = bSize: 1 byte (1)
                    .... 01.. = bType: Global (1)
                    0001 .... = bTag: Logical minimum (0x1)
                Logical minimum: 128
            Global item (Logical maximum)
                Header
                    .... ..01 = bSize: 1 byte (1)
                    .... 01.. = bType: Global (1)
                    0010 .... = bTag: Logical maximum (0x2)
                Logical maximum: 127
            Global item (Physical minimum)
                Header
                    .... ..01 = bSize: 1 byte (1)
                    .... 01.. = bType: Global (1)
                    0011 .... = bTag: Physical minimum (0x3)
                Physical minimum: 0
            Global item (Physical maximum)
                Header
                    .... ..01 = bSize: 1 byte (1)
                    .... 01.. = bType: Global (1)
                    0100 .... = bTag: Physical maximum (0x4)
                Physical maximum: 255
            Global item (Report size)
                Header
                    .... ..01 = bSize: 1 byte (1)
                    .... 01.. = bType: Global (1)
                    0111 .... = bTag: Report size (0x7)
                Report size: 8
            Global item (Report count)
                Header
                    .... ..01 = bSize: 1 byte (1)
                    .... 01.. = bType: Global (1)
                    1001 .... = bTag: Report count (0x9)
                Report count: 8
            Main item (Output)
                Header
                    .... ..01 = bSize: 1 byte (1)
                    .... 00.. = bType: Main (0)
                    1001 .... = bTag: Output (0x9)
                .... .... 0 = Data/constant: Data
                .... ...1 . = Data type: Variable
                .... ..0. . = Coordinates: Absolute
                .... .0.. . = Min/max wraparound: No Wrap
                .... 0... . = Physical relationship to data: Linear
                ...0 .... . = Preferred state: Preferred State
                ..0. .... . = Has null position: No Null position
                .0.. .... . = (Non)-volatile: Non Volatile
                0... .... . = Bits or bytes: Buffered bytes (default, no second byte present)
            Main item (End collection)
                Header
                    .... ..00 = bSize: 0 bytes (0)
                    .... 00.. = bType: Main (0)
                    1100 .... = bTag: End collection (0xc)
        Main item (End collection)
            Header
                .... ..00 = bSize: 0 bytes (0)
                .... 00.. = bType: Main (0)
                1100 .... = bTag: End collection (0xc)

Solution

  • When you decode the HID descriptor it will show the packet formats. Unfortunately, in this case the usage pages are vendor-defined so it is not possible to say exactly how each usage is to be interpreted.

    I decoded it using hidrdd (disclaimer: I wrote it, but it is free open source so I have no conflict of interest) as:

    //--------------------------------------------------------------------------------
    // Decoded Application Collection
    //--------------------------------------------------------------------------------
    
    /*
    06 A0FF      (GLOBAL) USAGE_PAGE         0xFFA0 Vendor-defined 
    09 01        (LOCAL)  USAGE              0xFFA00001 <-- Warning: Undocumented usage (document it by inserting 0001 into file FFA0.conf)
    A1 01        (MAIN)   COLLECTION         0x01 Application (Usage=0xFFA00001: Page=Vendor-defined, Usage=, Type=) <-- Error: COLLECTION must be preceded by a known USAGE
    09 02          (LOCAL)  USAGE              0xFFA00002 <-- Warning: Undocumented usage (document it by inserting 0002 into file FFA0.conf)
    A1 00          (MAIN)   COLLECTION         0x00 Physical (Usage=0xFFA00002: Page=Vendor-defined, Usage=, Type=) <-- Error: COLLECTION must be preceded by a known USAGE
    06 A1FF          (GLOBAL) USAGE_PAGE         0xFFA1 Vendor-defined 
    09 03            (LOCAL)  USAGE              0xFFA10003 <-- Warning: Undocumented usage (document it by inserting 0003 into file FFA1.conf)
    09 04            (LOCAL)  USAGE              0xFFA10004 <-- Warning: Undocumented usage (document it by inserting 0004 into file FFA1.conf)
    15 80            (GLOBAL) LOGICAL_MINIMUM    0x80 (-128)  
    25 7F            (GLOBAL) LOGICAL_MAXIMUM    0x7F (127)  
    35 00            (GLOBAL) PHYSICAL_MINIMUM   0x00 (0)  <-- Info: Consider replacing 35 00 with 34
    45 FF            (GLOBAL) PHYSICAL_MAXIMUM   0xFF (-1)  
    75 08            (GLOBAL) REPORT_SIZE        0x08 (8) Number of bits per field  
    95 08            (GLOBAL) REPORT_COUNT       0x08 (8) Number of fields  
    81 02            (MAIN)   INPUT              0x00000002 (8 fields x 8 bits) 0=Data 1=Variable 0=Absolute 0=NoWrap 0=Linear 0=PrefState 0=NoNull 0=NonVolatile 0=Bitmap  <-- Error: PHYSICAL_MAXIMUM (-1) is less than PHYSICAL_MINIMUM (0)
    09 05            (LOCAL)  USAGE              0xFFA10005 <-- Warning: Undocumented usage (document it by inserting 0005 into file FFA1.conf)
    09 06            (LOCAL)  USAGE              0xFFA10006 <-- Warning: Undocumented usage (document it by inserting 0006 into file FFA1.conf)
    15 80            (GLOBAL) LOGICAL_MINIMUM    0x80 (-128) <-- Redundant: LOGICAL_MINIMUM is already -128 
    25 7F            (GLOBAL) LOGICAL_MAXIMUM    0x7F (127) <-- Redundant: LOGICAL_MAXIMUM is already 127 
    35 00            (GLOBAL) PHYSICAL_MINIMUM   0x00 (0) <-- Redundant: PHYSICAL_MINIMUM is already 0 <-- Info: Consider replacing 35 00 with 34
    45 FF            (GLOBAL) PHYSICAL_MAXIMUM   0xFF (-1) <-- Redundant: PHYSICAL_MAXIMUM is already -1 
    75 08            (GLOBAL) REPORT_SIZE        0x08 (8) Number of bits per field <-- Redundant: REPORT_SIZE is already 8 
    95 08            (GLOBAL) REPORT_COUNT       0x08 (8) Number of fields <-- Redundant: REPORT_COUNT is already 8 
    91 02            (MAIN)   OUTPUT             0x00000002 (8 fields x 8 bits) 0=Data 1=Variable 0=Absolute 0=NoWrap 0=Linear 0=PrefState 0=NoNull 0=NonVolatile 0=Bitmap  <-- Error: PHYSICAL_MAXIMUM (-1) is less than PHYSICAL_MINIMUM (0)
    C0             (MAIN)   END_COLLECTION     Physical  <-- Warning: Physical units are still in effect PHYSICAL(MIN=0,MAX=-1) UNIT(0x,EXP=0)
    C0           (MAIN)   END_COLLECTION     Application  <-- Warning: Physical units are still in effect PHYSICAL(MIN=0,MAX=-1) UNIT(0x,EXP=0)
    */
    
    //--------------------------------------------------------------------------------
    // Vendor-defined inputReport (Device --> Host)
    //--------------------------------------------------------------------------------
    
    typedef struct
    {
                                                         // No REPORT ID byte
                                                         // Collection: CA: CP:
      int8_t   VEN_0003;                                 // Usage 0xFFA10003: , Value = -128 to 127, Physical = (Value + 128) x -1 / 255
      int8_t   VEN_0004[7];                              // Usage 0xFFA10004: , Value = -128 to 127, Physical = (Value + 128) x -1 / 255
    } inputReport_t;
    
    
    //--------------------------------------------------------------------------------
    // Vendor-defined outputReport (Device <-- Host)
    //--------------------------------------------------------------------------------
    
    typedef struct
    {
                                                         // No REPORT ID byte
                                                         // Collection: CA: CP:
      int8_t   VEN_0005;                                 // Usage 0xFFA10005: , Value = -128 to 127, Physical = (Value + 128) x -1 / 255
      int8_t   VEN_0006[7];                              // Usage 0xFFA10006: , Value = -128 to 127, Physical = (Value + 128) x -1 / 255
    } outputReport_t;
    

    As you can see, the above HID descriptor has some issues (for example, physical maximum 45 FF is -1, but I think they meant 255 - which should be represented as 46 FF 00) but the problem remains that it tells you nothing about the meaning of the usages. BTW, even Wireshark has not reported the logical minimum correctly: 15 80 is -128 not 128.

    All we can tell from it is that the reports are 8-bytes long and that the first byte seems to be some kind of id (well, its usage is different from the remaining 7 bytes).

    Only the vendor's driver knows how to interpret the reports, but with a sufficient number of Wireshark packet captures obtained under controlled conditions you may be able reverse engineer a workable interpretation.

    Sorry, but that's the best I can do with this.