libusbでBluetooth USBドングルを動かす

libusbを使ってBluetooth USBドングルを動かしてみました。

コマンドの送信とイベントの受信だけです。USB Bluetoothクラスの仕様でコマンド送信はコントロール転送(EP 0)、イベント受信はインタラプト転送(EP 0x81)と決まっています。

libusbの非同期APIがちょっと理解できました。転送のリクエストをした後にlibusb_handle_events_timeout()を呼ぶと転送完了とインタラプト転送樹脂などのコールバックが発生するというのが分かったら、非同期処理の動きが分かってきました。

# ソースコード
githubのgistに置いときました。
https://gist.github.com/eggman/2528d559c6c8fabf5aa212c4eea54391


# 実行例
HCI_RESET ( 03 0c 00 )とHCI_READ_BD_ADDR ( 09 10 00 )を送信してます。

```
$ sudo ./btusb 
CMD 03 0c 00
EVT 0e 04 01 03 0c 00
CMD 09 10 00
EVT 0e 0a 01 09 10 00 79 c1 f8 11 33 00
```

# libusbの準備
Windows の vmplayer 上で動くUbuntu 18.04 LTSを使いました。

libusbは

```
$ sudo apt install libusb-1.0-0-dev
```
とすると、/usr/include/libusb-1.0 に libusb.hが、/usr/lib/x86_64-linux-gnu に libusb-1.0.a　がインストールされました。

Bluetooth USBドングルはエレコムのLBT-UAN05C2を使いました。このデバイスはCSR8510を搭載しています。

PCにBluetooth USBドングルを挿して、vmware playerでUSBを使うように設定をすると Ubunt上のlsusbで表示できます。

```
$ lsusb
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
Bus 002 Device 006: ID 0a12:0001 Cambridge Silicon Radio, Ltd Bluetooth Dongle (HCI mode)
```

Ubuntuのデフォルト設定だとbluezのドライバがロードされてしまいます。libusbで使いたいのでrmmodでbluezのドライバをアンロードします。hciconfigでデバイスが表示されなくなればbluezのドライバがアンロードされています。

```
$ sudo rmmod btusb
$ sudo hciconfig
```

# Bluetooth USBドングルの検出

```
static int is_btusb_device (struct libusb_device *dev)
{
    struct libusb_device_descriptor desc;
    int    ret;

    ret = libusb_get_device_descriptor(dev, &desc);
    if (ret < 0) { return false; }

    if ((
         (0xe0 == libusb_le16_to_cpu(desc.bDeviceClass))    &&
         (0x01 == libusb_le16_to_cpu(desc.bDeviceSubClass)) &&
         (0x01 == libusb_le16_to_cpu(desc.bDeviceProtocol))
        ) || ( 
         (0xef == libusb_le16_to_cpu(desc.bDeviceClass))    &&
         (0x02 == libusb_le16_to_cpu(desc.bDeviceSubClass)) &&
         (0x01 == libusb_le16_to_cpu(desc.bDeviceProtocol))
       ))
    {   
        return true;
    }

    return false;
}

static bool btusb_open(void)
{
    int ret;

    ret = libusb_init(NULL);
    if (ret < 0) { return -1; }

    libusb_device *dev;
    libusb_device **devs;
    ssize_t num_devices;
    completed_transfer_list = NULL;
    handle = NULL;
    num_devices = libusb_get_device_list(NULL, &devs);
    if (num_devices < 0) { return -1; }

    do {
        for (int i = 0; (dev = devs[i]) != NULL; i++) {
            //check USB class.
            if (is_btusb_device (dev) == true) { break; }
            dev = NULL;
        }
        if (dev == NULL) { break; }

        ret = libusb_open(dev, &handle);
        if (ret < 0 || handle==NULL) { break; }

        ret = libusb_reset_device(handle);
        if (ret < 0) { break; }

        ret = libusb_claim_interface(handle, 0);
        if (ret < 0) { break; }

        struct libusb_config_descriptor *config_descriptor;
        ret = libusb_get_active_config_descriptor(dev, &config_descriptor);
        if (ret < 0) { break; }

        evt_ep_addr = 0;

        for (int i = 0; i < config_descriptor->bNumInterfaces; i++) {
            const struct libusb_interface_descriptor * interface_descriptor = config_descriptor->interface[i].altsetting;
            const struct libusb_endpoint_descriptor *endpoint = interface_descriptor->endpoint;

            for (int r = 0; r < interface_descriptor->bNumEndpoints; r++, endpoint++) {
                switch (endpoint->bmAttributes & 0x3) {
                    case LIBUSB_TRANSFER_TYPE_INTERRUPT:
                        if (evt_ep_addr) continue;
                        evt_ep_addr = endpoint->bEndpointAddress;
                        break;
                    default:
                        break;
                }
            }
        }
    } while (0);
`
    libusb_free_device_list(devs, 1);

    if (handle == NULL) {
        return false;
    }
    return true;
}

```

# Bluetooth USBドングルのパイプ準備

```
static int btusb_prepare_pipe(void)
{
    int ret;
    if (handle == NULL) { return -1; }

    // allocate control transfer handler
    cmd_transfer = libusb_alloc_transfer(0);
    if (!cmd_transfer) {
        return LIBUSB_ERROR_NO_MEM;
    }

    // allocate interrupt transfer handler
    for (int i = 0 ; i < EVT_BUFFER_COUNT ; i++) {
        evt_transfer[i] = libusb_alloc_transfer(0); // 0 isochronous transfers Events
        if (!evt_transfer[i]) {
            return LIBUSB_ERROR_NO_MEM;
        }
    }

    // configure interrupt transfer handler
    for (int i = 0 ; i < EVT_BUFFER_COUNT ; i++) {
        libusb_fill_interrupt_transfer(evt_transfer[i], handle, evt_ep_addr,
                hci_evt_buffer[i], HCI_EVT_BUFFER_SIZE, btusb_async_callback, NULL, 0) ;
        ret = libusb_submit_transfer(evt_transfer[i]);
        if (ret) {
            printf("Error submitting interrupt transfer %d\n", ret);
            return ret;
        }
    }

    return 0;
}
```


# Bluetooth USBドングルへHCIコマンド送信

コマンド送信後にコマンド応答をポーリングしてます。

```
static int btusb_send_cmd(uint8_t *packet, int size)
{
    int ret;

    printf("CMD");
    for (int i = 0; i < size; i++) {
        printf(" %02x", packet[i]);
    }
    printf("\n");

    // use async API
    libusb_fill_control_setup(hci_cmd_buffer, LIBUSB_REQUEST_TYPE_CLASS | LIBUSB_RECIPIENT_INTERFACE, 0, 0, 0, size);
    memcpy(hci_cmd_buffer + LIBUSB_CONTROL_SETUP_SIZE, packet, size);

    // configure controll transfer handler
    libusb_fill_control_transfer(cmd_transfer, handle, hci_cmd_buffer, btusb_async_callback, NULL, 0);
    cmd_transfer->flags = LIBUSB_TRANSFER_FREE_BUFFER;

    usb_command_resp = 0;
    usb_command_active = 1;
    ret = libusb_submit_transfer(cmd_transfer);
    if (ret < 0) {
        usb_command_active = 0;
        printf("Error submitting cmd transfer %d\n", ret);
        return ret;
    }

    // wait command resp event
    while(usb_command_resp == 0) {
        usleep(100*1000);
        btusb_polling();
    }
    usb_command_resp = 0;

    return 0;
}
```

# Bluetooth USBドングルからHCIイベント受信

受信はポーリングするとインタラプト転送のコールバックが呼ばれるので、コールバックでlibusb_transferのポインタを保存しておき、そのあとで処理してます。

```
LIBUSB_CALL static void btusb_async_callback(struct libusb_transfer *transfer)
{
    if (transfer->status == LIBUSB_TRANSFER_COMPLETED) {
        transfer->user_data = NULL; // terminate linked list

        if (completed_transfer_list == NULL) {
            completed_transfer_list = transfer;
            return;
        }

        struct libusb_transfer *temp = completed_transfer_list;
        while (temp->user_data) {
            temp = (struct libusb_transfer*)temp->user_data;
        }
        temp->user_data = transfer;
    }
    return;
}

static void btusb_polling(void)
{
    // polling. if transfer done, call async callback.
    struct timeval tv;
    memset(&tv, 0, sizeof(struct timeval));
    libusb_handle_events_timeout(NULL, &tv);

    // handle transfer
    while (completed_transfer_list) {
        handle_completed_transfer(completed_transfer_list);
        completed_transfer_list = (struct libusb_transfer*) completed_transfer_list->user_data;
    }
}

static void handle_completed_transfer(struct libusb_transfer *transfer)
{
    if (transfer->endpoint == evt_ep_addr) {
        printf("EVT");
        for (int i = 0; i < transfer->actual_length; i++) {
            printf(" %02x", transfer->buffer[i]);
        }
        printf("\n");
        usb_command_resp = 1;
    } else if (transfer->endpoint == 0) {
        usb_command_active = 0;
    } else {
    }
}

```

# main

```
int main(void)
{
    int ret;
    ret = btusb_open();
    if (!ret) { return -1; }

    ret = btusb_prepare_pipe();
    if (ret < 0) { return -1; }

    //send HCI_CMD_RESET
    uint8_t  reset_dat[] = {0x03, 0x0c, 0x00};
    uint32_t reset_len   = 3;
    btusb_send_cmd(reset_dat, reset_len);

    //send HCI_READ_BD_ADDR
    uint8_t  read_bda_dat[] = {0x09, 0x10, 0x00};
    uint32_t read_bda_len   = 3;
    btusb_send_cmd(read_bda_dat, read_bda_len);

    return 0;
}
   
```