Arduino leonardoはUSB機器としてちょっと特殊だ、という話

ひとつの機器に複数の機能が付いてるようなUSB機器には、実は異なる構成があり、一例としてArduino leonardoはちょっと特殊だ、という話です。

USB compound device

まず、論理的にはハブにぶら下がってるようなのがcompound deviceですが、これはOS側から問題になるようなことはないでしょうので省略します。

USB composite device

これはひとつのコンフィギュレーションの下に、複数のインタフェースがある、というような機器で、よく見られるようなものではないかと思います。たとえば手元のマウスG9がこの方式で、マウスだけでなく同時にキーボードとしても機能します。手元の環境でコンフィグ情報を見ると次のようになります。

$ sudo usbconfig -d 0.2 dump_curr_config_desc
ugen0.2: <G9 Laser Mouse Logitech> at usbus0, cfg=0 md=HOST spd=FULL (12Mbps) pwr=ON (98mA)


 Configuration index 0

    bLength = 0x0009
    bDescriptorType = 0x0002
    wTotalLength = 0x003b
    bNumInterfaces = 0x0002
    bConfigurationValue = 0x0001
    iConfiguration = 0x0004  <U50.00_B0029>
    bmAttributes = 0x00a0
    bMaxPower = 0x0031

    Interface 0
      bLength = 0x0009
      bDescriptorType = 0x0004
      bInterfaceNumber = 0x0000
      bAlternateSetting = 0x0000
      bNumEndpoints = 0x0001
      bInterfaceClass = 0x0003 ← クラス = 3(HID)
      bInterfaceSubClass = 0x0001 ← サブクラス = 1(ブートインタフェース)
      bInterfaceProtocol = 0x0002 ← プロトコル = 2(マウス)
      iInterface = 0x0000  <no string>

      Additional Descriptor

      bLength = 0x09
      bDescriptorType = 0x21
      bDescriptorSubType = 0x11
       RAW dump:
       0x00 | 0x09, 0x21, 0x11, 0x01, 0x00, 0x01, 0x22, 0x43,
       0x08 | 0x00

     Endpoint 0
        bLength = 0x0007
        bDescriptorType = 0x0005
        bEndpointAddress = 0x0081  <IN>
        bmAttributes = 0x0003  <INTERRUPT>
        wMaxPacketSize = 0x0008
        bInterval = 0x0001
        bRefresh = 0x0000
        bSynchAddress = 0x0000


    Interface 1
      bLength = 0x0009
      bDescriptorType = 0x0004
      bInterfaceNumber = 0x0001
      bAlternateSetting = 0x0000
      bNumEndpoints = 0x0001
      bInterfaceClass = 0x0003
      bInterfaceSubClass = 0x0000
      bInterfaceProtocol = 0x0000
      iInterface = 0x0000  <no string>

      Additional Descriptor

      bLength = 0x09
      bDescriptorType = 0x21
      bDescriptorSubType = 0x11
       RAW dump:
       0x00 | 0x09, 0x21, 0x11, 0x01, 0x00, 0x01, 0x22, 0x7a,
       0x08 | 0x00

     Endpoint 0
        bLength = 0x0007
        bDescriptorType = 0x0005
        bEndpointAddress = 0x0082  <IN>
        bmAttributes = 0x0003  <INTERRUPT>
        wMaxPacketSize = 0x0014
        bInterval = 0x0001
        bRefresh = 0x0000
        bSynchAddress = 0x0000

書き加えてあるように、インタフェース0はブートインタフェースとしてマウスが見えています。インタフェース1はブートインタフェースでないため(TBD:ブートインタフェースでないデバイスの識別方法)ですが、キーボードが見えています。
compound deviceはこのように、機能の数だけインタフェースがあるので、OS側ではインタフェース毎にデバドラが対応すれば良いということになりますが、そのようになってないデバイスもあります。

Multiple Top-Level

どうも一般名称がまだ定まってない感じなのですが、HID機器などで、単一のインタフェースの下に複数の機能がぶら下がっているものがあり、手元の環境である FreeBSD では、そういったHID機器に対応するためには、ベースシステムに入っていないuhiddというデーモンが必要になります( https://wiki.freebsd.org/uhidd )。
Arduino leonardoが、ホストに対してキーボードやマウスをエミュレーションする機能を持っていますが、そのキーボードやマウスがこの方式でした。以下に、コンフィグ情報を示します。

$ sudo usbconfig -d 0.4 dump_curr_config_desc
ugen0.4: <Arduino Leonardo Arduino LLC> at usbus0, cfg=0 md=HOST spd=FULL (12Mbps) pwr=ON (500mA)


 Configuration index 0

    bLength = 0x0009
    bDescriptorType = 0x0002
    wTotalLength = 0x0064
    bNumInterfaces = 0x0003
    bConfigurationValue = 0x0001
    iConfiguration = 0x0000  <no string>
    bmAttributes = 0x0080
    bMaxPower = 0x00fa

    Additional Descriptor

    bLength = 0x08
    bDescriptorType = 0x0b
    bDescriptorSubType = 0x00
     RAW dump:
     0x00 | 0x08, 0x0b, 0x00, 0x02, 0x02, 0x02, 0x01, 0x00


    Interface 0
      bLength = 0x0009
      bDescriptorType = 0x0004
      bInterfaceNumber = 0x0000
      bAlternateSetting = 0x0000
      bNumEndpoints = 0x0001
      bInterfaceClass = 0x0002 ← クラス = 2(CDC)
      bInterfaceSubClass = 0x0002
      bInterfaceProtocol = 0x0000
      iInterface = 0x0000  <no string>

      Additional Descriptor

      bLength = 0x05
      bDescriptorType = 0x24
      bDescriptorSubType = 0x00
       RAW dump:
       0x00 | 0x05, 0x24, 0x00, 0x10, 0x01


      Additional Descriptor

      bLength = 0x05
      bDescriptorType = 0x24
      bDescriptorSubType = 0x01
       RAW dump:
       0x00 | 0x05, 0x24, 0x01, 0x01, 0x01


      Additional Descriptor

      bLength = 0x04
      bDescriptorType = 0x24
      bDescriptorSubType = 0x02
       RAW dump:
       0x00 | 0x04, 0x24, 0x02, 0x06


      Additional Descriptor

      bLength = 0x05
      bDescriptorType = 0x24
      bDescriptorSubType = 0x06
       RAW dump:
       0x00 | 0x05, 0x24, 0x06, 0x00, 0x01


     Endpoint 0
        bLength = 0x0007
        bDescriptorType = 0x0005
        bEndpointAddress = 0x0081  <IN>
        bmAttributes = 0x0003  <INTERRUPT>
        wMaxPacketSize = 0x0010
        bInterval = 0x0040
        bRefresh = 0x0000
        bSynchAddress = 0x0000


    Interface 1
      bLength = 0x0009
      bDescriptorType = 0x0004
      bInterfaceNumber = 0x0001
      bAlternateSetting = 0x0000
      bNumEndpoints = 0x0002
      bInterfaceClass = 0x000a ← クラス = 0xa(CDC-Data)
      bInterfaceSubClass = 0x0000
      bInterfaceProtocol = 0x0000
      iInterface = 0x0000  <no string>

     Endpoint 0
        bLength = 0x0007
        bDescriptorType = 0x0005
        bEndpointAddress = 0x0002  <OUT>
        bmAttributes = 0x0002  <BULK>
        wMaxPacketSize = 0x0040
        bInterval = 0x0000
        bRefresh = 0x0000
        bSynchAddress = 0x0000

     Endpoint 1
        bLength = 0x0007
        bDescriptorType = 0x0005
        bEndpointAddress = 0x0083  <IN>
        bmAttributes = 0x0002  <BULK>
        wMaxPacketSize = 0x0040
        bInterval = 0x0000
        bRefresh = 0x0000
        bSynchAddress = 0x0000


    Interface 2
      bLength = 0x0009
      bDescriptorType = 0x0004
      bInterfaceNumber = 0x0002
      bAlternateSetting = 0x0000
      bNumEndpoints = 0x0001
      bInterfaceClass = 0x0003 ← クラス = 3(HID)
      bInterfaceSubClass = 0x0000
      bInterfaceProtocol = 0x0000
      iInterface = 0x0000  <no string>

      Additional Descriptor

      bLength = 0x09
      bDescriptorType = 0x21
      bDescriptorSubType = 0x01
       RAW dump:
       0x00 | 0x09, 0x21, 0x01, 0x01, 0x00, 0x01, 0x22, 0x65,
       0x08 | 0x00

     Endpoint 0
        bLength = 0x0007
        bDescriptorType = 0x0005
        bEndpointAddress = 0x0084  <IN>
        bmAttributes = 0x0003  <INTERRUPT>
        wMaxPacketSize = 0x0040
        bInterval = 0x0001
        bRefresh = 0x0000
        bSynchAddress = 0x0000

書き加えてあるように、インタフェースは3個ありますが、クラスがHIDなのは1個だけです。Arduino leonardoのキーボードとマウスのエミュレーション機能は、この1個のHIDインタフェースに両方がぶら下がっています。なので、FreeBSDArduino leonardoのこの機能を使うには、前述のuhiddが必要になります。