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