libusb1.0-0のデータ送受信 (Km2Net USB-IO2.0の例)

# 経緯とか
組み込みなんてやったこと無いのに業務で必要になったのでやってみた備忘録。
概ね [libusb関係のメモと、バルク転送サンプル](https://qiita.com/gpsnmeajp/items/b1282b2d3c14470bbae7) の二番煎じです。
具体的なコードも載せますが、どちらかというと作業フロー的な話をメインに。

[環境]

 - OS: debian 9
 - libusb: libusb-1.0-0
 - gcc6(64bit版)
 - 言語: C

僕の場合は購入した機器の公式のlibusbサポートがlibusb0.1(Windows用)で、Linux環境でlinusb1.0-0に合わせるのに難儀しました。Raspbianではなくdebianを使ったのはRaspberryPi以外の選択肢もあり得たからです。Raspbianでも適切に設定すると動作すると思います。

**USBIOを初めて触るような人を想定して書いています**。

# 用語
 - HID: Human Interface Device
    - 一般的にはマウスとかキーボードとか
    - 今回使用したKm2Netさんの[USB-IO2.0](http://km2net.com/usb-io2.0/index.shtml)はHIDデバイスとして認識されました。
 - EP: EndPoint
   - エンドポイント、要はアクセス先です。
   - EP IN がUSB→マシン方向の信号つまり読み込み・受信側
   - EP OUT がマシン→USB方向の信号つまり書き込み・送信側

# USB-IO周りの構造
```
                 ╭──────────────────────────────────────╮
    ┌──────┐     │   device     ┌─────┐  ┌───────────┐  │
    │ Port ├──┐  │            ┌─┤ EP0 ├──┤ control   │  │
    └──────┘  │  │ ┌────────┐ │ └─────┘  │┌─────────┐│  │
              ├────┤  addr  ├─┤ ┌─────┐  ││         ││  │
              │  │ └────────┘ ├─┤ EP1 ├──┼┤interface││  │
              │  │            │ └─────┘  ││   #0    ││  │
              │  │            │ ┌─────┐  │├─────────┤│  │
              │  │            ├─┤ EP2 ├──┼┤         ││  │
              │  │            │ └─────┘  ││interface││  │
              │  │            │ ┌─────┐  ││   #1    ││  │
              │  │            └─┤ EP3 ├──┼┤         ││  │
              │  │              └─────┘  │└─────────┘│  │
              │  │                       └───────────┘  │
              │  ╰──────────────────────────────────────╯
              :
```
ref) https://unix.stackexchange.com/questions/292933/how-can-i-write-raw-data-to-a-usb-device (ここの人はUSBからの出力信号を`cat`で読み込もうとして「そんなに単純じゃないよ」と言われています)

USBデバイスにはエンドポイントが複数定義されていて、この図でいうinterfaceまで正しく辿り着けなければならず、そのためにはいくつかの調査が必要です。

# 調査の基本的な流れ
0\. **PortとAddrを取得する**

この部分はUSBデバイスのVENDOR_IDとPRODUCT_IDが分かっていれば、libusbが上手くやってくれるので必要ありません。

1\. **USBデバイスのVENDOR_IDとPRODUCT_IDを取得する。**

これは該当デバイスの仕様が分かっていれば問題ありません。
調べる場合はUSBを抜き差しした直後に下記コマンドのそれっぽい出力からidVendorとidProductを確認します。
具体的にはこんな感じです。

```bash:commmand
sudo dmesg|grep USB
```

```txt:それっぽい出力
[69958.155195] usb 1-4: USB disconnect, device number 6
[69961.010124] usb 1-4: new full-speed USB device number 12 using xhci_hcd
[69961.403436] usb 1-4: New USB device found, idVendor=1352, idProduct=0121
[69961.403439] usb 1-4: New USB device strings: Mfr=1, Product=2, SerialNumber=0
[69961.403440] usb 1-4: Product: USB-IO2.0
[69961.417534] hid-generic 0003:1352:0121.0015: hiddev0,hidraw10: USB HID v1.11 Device [Km2Net Inc. USB-IO2.0] on usb-0000:01:00.0-4/input0
```

2\. **USBデバイスのEPを取得する。**

調べる場合はUSBを差している状態で下記コマンドの出力から先のidVendorとidProductの欄を確認します。
USBデバイス多いと出力が長くなるのでファイルにリダイレクトして検索をおすすめします。

```bash:commmand
sudo lsusb -v > out.txt
```

<details><summary>必要部分だけ抜き出した生の出力</summary><div>

```txt
Bus 001 Device 006: ID 1352:0121  
Device Descriptor:
  bLength                18
  bDescriptorType         1
  bcdUSB               2.00
  bDeviceClass            0 (Defined at Interface level)
  bDeviceSubClass         0 
  bDeviceProtocol         0 
  bMaxPacketSize0         8
  idVendor           0x1352 
  idProduct          0x0121 
  bcdDevice            0.01
  iManufacturer           1 Km2Net Inc.
  iProduct                2 USB-IO2.0
  iSerial                 0 
  bNumConfigurations      1
  Configuration Descriptor:
    bLength                 9
    bDescriptorType         2
    wTotalLength           41
    bNumInterfaces          1
    bConfigurationValue     1
    iConfiguration          0 
    bmAttributes         0xc0
      Self 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.11
          bCountryCode            0 Not supported
          bNumDescriptors         1
          bDescriptorType        34 Report
          wDescriptorLength      29
          Report Descriptor: (length is 29)
            Item(Global): Usage Page, data= [ 0x00 0xff ] 65280
                            (null)
            Item(Local ): Usage, data= [ 0x01 ] 1
                            (null)
            Item(Main  ): Collection, data= [ 0x01 ] 1
                            Application
            Item(Local ): Usage Minimum, data= [ 0x01 ] 1
                            (null)
            Item(Local ): Usage Maximum, data= [ 0x40 ] 64
                            (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= [ 0x40 ] 64
            Item(Main  ): Input, data= [ 0x00 ] 0
                            Data Array Absolute No_Wrap Linear
                            Preferred_State No_Null_Position Non_Volatile Bitfield
            Item(Local ): Usage Minimum, data= [ 0x01 ] 1
                            (null)
            Item(Local ): Usage Maximum, data= [ 0x40 ] 64
                            (null)
            Item(Main  ): Output, data= [ 0x00 ] 0
                            Data Array 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     0x0040  1x 64 bytes
        bInterval               1
      Endpoint Descriptor:
        bLength                 7
        bDescriptorType         5
        bEndpointAddress     0x01  EP 1 OUT
        bmAttributes            3
          Transfer Type            Interrupt
          Synch Type               None
          Usage Type               Data
        wMaxPacketSize     0x0040  1x 64 bytes
        bInterval               1
Device Status:     0x0001
  Self Powered
```
</div></details>

```txt:長い出力を必要なものだけのtreeにした図
 ─ Device Descriptor
    ├─ Configurtion Descriptor
    │  ├─ bConfigurationValue: 1 (*1)
    │  └─ Interface Descriptor
    │      ├─ Endpoint Descriptor
    │      │   ├─ bLength: 7 (*2)
    │      │   ├─ bEndpointAddress: 0x81 EP 1 IN (*3)
    │      │   └─ bmAttributes
    │      │       └─ Transfer Type: Interrupt (*4)
    │      ├─ Endpoint Descriptor
    │      │   ├─ bLength: 7 (*5)
    │      │   ├─ bEndpointAddress: 0x01 EP 1 OUT (*6)
    │      │   └─ bmAttributes
    │      │       └─ Transfer Type: Interrupt (*7)
```

これでこのデバイスは

 - 入力: **0x81 EP 1に size7で**(※後ほど訂正) **intterrupt transfer** (*2-4)
 - 出力: **0x01 EP 1に size7で**(※後ほど訂正) **intterrupt transfer** (*5-7)

すれば良いと分かります。



3\. **作法を調べる**

実際にUSBデバイスからの信号を受信するには、製造元が定めた形式でリクエストを送ってやる必要があります。製造元の仕様を調べましょう。データ受信前にデータ送信が必要となる仕様が全てのUSB共通の規格かは分かりませんが、少なくとも今回はそうでしたし、Webリクエストとかも*基本的に規格に沿った挨拶*から始まるので、そういうものなのかも知れません。
仕様: http://km2net.com/usb-fsio/command.shtml
先程「bLength 7だからsize7で」と書きましたが、仕様では64のようです。仕様に合わせることにします(64未満ではオーバーフローしました)。
**デジタル入出力には0バイト目に0x20を送信せよ**とあるので、そのようにします。


# コード

```cpp:read.cc
#include <libusb-1.0/libusb.h>
#include <stdlib.h> // for exit
#include <stdio.h> // for printf
#define VENDOR_ID 0x1352
#define PRODUCT_ID 0x0121
#define EP 0x1
#define DEVICE_CONFIGURATION 1

int main()
{
    libusb_device **list = NULL;
    ssize_t number_of_devices = 0;
    if(libusb_init(NULL) != 0) // libusb初期化
    {
        fprintf(stderr, "%s:%d: unable to initialize libusb\n", __FILE__, __LINE__);
        exit(EXIT_FAILURE);
    }

    number_of_devices = libusb_get_device_list(NULL, &list); // デバイス取得
    if(number_of_devices < 0)
    {
        fprintf(stderr, "%s:%d: unable to get the list of USB devices\n", __FILE__, __LINE__);
        libusb_exit(NULL);
        exit(EXIT_FAILURE);
    }
    printf("number_of_devices = %d\n", number_of_devices);
    int index = 0;
    for(index = 0; index < number_of_devices; ++index)
    {
        libusb_device *device = list[index];
        struct libusb_device_descriptor desc = {0};
        if(libusb_get_device_descriptor(device, &desc) != 0)
        {
            fprintf(stderr, "%s:%d: unable to get the USB device descriptor\n", __FILE__, __LINE__);
            libusb_free_device_list(list, 1);
            libusb_exit(NULL);
            exit(EXIT_FAILURE);
        }
        printf("%d/%d: idVendor: %04x, idProduct: %04x\n", index + 1, number_of_devices, desc.idVendor, desc.idProduct);
        // idVendorとidProductでデバイス指定
        if(desc.idVendor == VENDOR_ID && desc.idProduct == PRODUCT_ID)
        {
            printf("USB device found!\n");
            printf("Opening the USB device...\n");
            libusb_device_handle *handle = NULL;
            
            // デバイスオープン
            if(libusb_open(device, &handle) != 0)
            {
                fprintf(stderr, "%s:%d: unable to open the USB device\n", __FILE__, __LINE__);
                libusb_free_device_list(list, 1);
                libusb_exit(NULL);
                exit(EXIT_FAILURE);
            }
            printf("Done!\n");

            // カーネルドライバを良い感じに
            if(libusb_kernel_driver_active(handle, 0) != 0)
            {
                printf("Kernel driver is active!\n");
                printf("Detaching the kernel driver...\n");
                if(libusb_detach_kernel_driver(handle, 0) != 0)
                {
                    fprintf(stderr, "%s:%d: unable to detach the kernel driver from the interface of the USB device\n", __FILE__, __LINE__);
                    libusb_close(handle);
                    libusb_free_device_list(list, 1);
                    libusb_exit(NULL);
                    exit(EXIT_FAILURE);
                }
                printf("Done!\n");
            }
            
            // デバイスの使用権を宣言
            printf("Claiming the interface...\n");
            if(libusb_claim_interface(handle, 0) != 0)
            {
                fprintf(stderr, "%s:%d: unable to claim the interface of the USB device\n", __FILE__, __LINE__);
                libusb_close(handle);
                libusb_free_device_list(list, 1);
                libusb_exit(NULL);
                exit(EXIT_FAILURE);
            }
            printf("Done!\n");
            unsigned char data[64] = {0x20}; // 0x20, 0x00, 0x00, ...というデータになる。
            int actual_length;
            int r;
            printf("Gonna Reading data...\n");
            while(1)
            {
                // 受信のリクエストを送る
                r = libusb_interrupt_transfer(handle, 0x01|EP, data, sizeof(data), &actual_length, 0);
            
                // データの受信
                r = libusb_interrupt_transfer(handle, 0x81|EP, data, sizeof(data), &actual_length, 0);
                if(r == 0) {
                    printf("IN: %d: ", actual_length);
                    for (int _i=0; _i<actual_length; ++_i) {
                        printf("%02X ", data[_i]);
                    }
                    printf("\n");
                }
            }

            // デバイス開放など
            printf("Releasing the interface...\n");
            if(libusb_release_interface(handle, 0) != 0)
            {
                fprintf(stderr, "%s:%d: unable to detach the kernel driver from the interface of the USB device\n", __FILE__, __LINE__);
                libusb_close(handle);
                libusb_free_device_list(list, 1);
                libusb_exit(NULL);
                exit(EXIT_FAILURE);
            }
            printf("Done!\n");
            printf("Closing the USB device...\n");
            libusb_close(handle);
            printf("Done!\n");
        }
    }
    libusb_free_device_list(list, 1);
    libusb_exit(NULL);
    exit(EXIT_SUCCESS);
}

```

環境

```bash:bash
sudo apt install -y libusb-1.0-0-dev
```

コンパイルは好きなコンパイラで。。

```bash:例
gcc -lusb-1.0 read.cc
```

実行はsuで。。

```bash:例
sudo ./a.out 
```

# 正直
よく分かってないです。
今回は個人的な目的は達せたので良かったのですが
「動いたからこれが正しそう」以上の何物でもないです。
ネット上の情報は2010年前後を境に激減していてLinux関連の情報も少ないです。
詳しい人いれば教えてください(´・ω・｀)