LoginSignup
1
1

More than 3 years have passed since last update.

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

Last updated at Posted at 2020-02-22

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;
}

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1