blob: 12071fd49b6ea1700bf9681cea70ea85c6411964 [file] [log] [blame]
// Copyright 2016 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <ddk/binding.h>
#include <ddk/debug.h>
#include <ddk/device.h>
#include <ddk/driver.h>
#include <ddk/protocol/bt/hci.h>
#include <ddk/protocol/usb-old.h>
#include <ddk/usb/usb.h>
#include <usb/usb-request.h>
#include <zircon/device/bt-hci.h>
#include <zircon/listnode.h>
#include <zircon/status.h>
#include <zircon/syscalls/port.h>
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <threads.h>
#include <unistd.h>
#define EVENT_REQ_COUNT 8
// TODO(armansito): Consider increasing these.
#define ACL_READ_REQ_COUNT 8
#define ACL_WRITE_REQ_COUNT 8
// The maximum HCI ACL frame size used for data transactions
#define ACL_MAX_FRAME_SIZE 1028 // (1024 + 4 bytes for the ACL header)
#define CMD_BUF_SIZE 255 + 3 // 3 byte header + payload
#define EVENT_BUF_SIZE 255 + 2 // 2 byte header + payload
// The number of currently supported HCI channel endpoints. We currently have
// one channel for command/event flow and one for ACL data flow. The sniff channel is managed
// separately.
#define NUM_CHANNELS 2
#define NUM_WAIT_ITEMS NUM_CHANNELS + 1 // add one item for the changed event
// TODO(jamuraa): move these to hw/usb.h (or hw/bluetooth.h if that exists)
#define USB_SUBCLASS_BLUETOOTH 1
#define USB_PROTOCOL_BLUETOOTH 1
typedef struct {
zx_device_t* zxdev;
zx_device_t* usb_zxdev;
usb_protocol_t usb;
zx_handle_t cmd_channel;
zx_handle_t acl_channel;
zx_handle_t snoop_channel;
// Port to queue PEER_CLOSED signals on
zx_handle_t snoop_watch;
// Signaled when a channel opens or closes
zx_handle_t channels_changed_evt;
zx_wait_item_t read_wait_items[NUM_WAIT_ITEMS];
uint32_t read_wait_item_count;
bool read_thread_running;
void* intr_queue;
// for accumulating HCI events
uint8_t event_buffer[EVENT_BUF_SIZE];
size_t event_buffer_offset;
size_t event_buffer_packet_length;
// pool of free USB requests
list_node_t free_event_reqs;
list_node_t free_acl_read_reqs;
list_node_t free_acl_write_reqs;
mtx_t mutex;
size_t parent_req_size;
} hci_t;
static void hci_event_complete(usb_request_t* req, void* cookie);
static void hci_acl_read_complete(usb_request_t* req, void* cookie);
static void queue_acl_read_requests_locked(hci_t* hci) {
usb_request_t* req = NULL;
while ((req = usb_req_list_remove_head(&hci->free_acl_read_reqs, hci->parent_req_size)) != NULL) {
usb_request_queue(&hci->usb, req, hci_acl_read_complete, hci);
}
}
static void queue_interrupt_requests_locked(hci_t* hci) {
usb_request_t* req = NULL;
while ((req = usb_req_list_remove_head(&hci->free_event_reqs, hci->parent_req_size)) != NULL) {
usb_request_queue(&hci->usb, req, hci_event_complete, hci);
}
}
static void channel_cleanup_locked(hci_t* hci, zx_handle_t* channel) {
if (*channel == ZX_HANDLE_INVALID)
return;
zx_handle_close(*channel);
*channel = ZX_HANDLE_INVALID;
zx_object_signal(hci->channels_changed_evt, 0, ZX_EVENT_SIGNALED);
}
static void snoop_channel_write_locked(hci_t* hci, uint8_t flags, uint8_t* bytes, size_t length) {
if (hci->snoop_channel == ZX_HANDLE_INVALID)
return;
// We tack on a flags byte to the beginning of the payload.
uint8_t snoop_buffer[length + 1];
snoop_buffer[0] = flags;
memcpy(snoop_buffer + 1, bytes, length);
zx_status_t status = zx_channel_write(hci->snoop_channel, 0, snoop_buffer, length + 1, NULL, 0);
if (status < 0) {
zxlogf(ERROR, "bt-transport-usb: failed to write to snoop channel: %s\n", zx_status_get_string(status));
channel_cleanup_locked(hci, &hci->snoop_channel);
}
}
static void hci_event_complete(usb_request_t* req, void* cookie) {
hci_t* hci = (hci_t*)cookie;
zxlogf(SPEW, "bt-transport-usb: Event received\n");
mtx_lock(&hci->mutex);
zx_status_t status;
// Handle the interrupt as long as either the command channel or the snoop
// channel is open.
if (hci->cmd_channel == ZX_HANDLE_INVALID && hci->snoop_channel == ZX_HANDLE_INVALID)
goto out2;
if (req->response.status == ZX_OK) {
uint8_t* buffer;
zx_status_t status = usb_request_mmap(req, (void*)&buffer);
if (status != ZX_OK) {
zxlogf(ERROR, "bt-transport-usb: usb_req_mmap failed: %s\n", zx_status_get_string(status));
goto out2;
}
size_t length = req->response.actual;
size_t packet_size = buffer[1] + 2;
// simple case - packet fits in received data
if (hci->event_buffer_offset == 0 && length >= 2) {
if (packet_size == length) {
if (hci->cmd_channel != ZX_HANDLE_INVALID) {
zx_status_t status = zx_channel_write(hci->cmd_channel, 0, buffer, length, NULL, 0);
if (status < 0) {
zxlogf(ERROR, "bt-transport-usb: hci_event_complete failed to write: %s\n", zx_status_get_string(status));
}
}
snoop_channel_write_locked(hci, bt_hci_snoop_flags(BT_HCI_SNOOP_TYPE_EVT, true), buffer, length);
goto out;
}
}
// complicated case - need to accumulate into hci->event_buffer
if (hci->event_buffer_offset + length > sizeof(hci->event_buffer)) {
zxlogf(ERROR, "bt-transport-usb: event_buffer would overflow!\n");
goto out2;
}
memcpy(&hci->event_buffer[hci->event_buffer_offset], buffer, length);
if (hci->event_buffer_offset == 0) {
hci->event_buffer_packet_length = packet_size;
} else {
packet_size = hci->event_buffer_packet_length;
}
hci->event_buffer_offset += length;
// check to see if we have a full packet
if (packet_size <= hci->event_buffer_offset) {
zx_status_t status = zx_channel_write(hci->cmd_channel, 0, hci->event_buffer,
packet_size, NULL, 0);
if (status < 0) {
zxlogf(ERROR, "bt-transport-usb: failed to write: %s\n", zx_status_get_string(status));
}
snoop_channel_write_locked(hci, bt_hci_snoop_flags(BT_HCI_SNOOP_TYPE_EVT, true), hci->event_buffer, packet_size);
uint32_t remaining = hci->event_buffer_offset - packet_size;
memmove(hci->event_buffer, hci->event_buffer + packet_size, remaining);
hci->event_buffer_offset = 0;
hci->event_buffer_packet_length = 0;
}
}
out:
status = usb_req_list_add_head(&hci->free_event_reqs, req, hci->parent_req_size);
ZX_DEBUG_ASSERT(status == ZX_OK);
queue_interrupt_requests_locked(hci);
out2:
mtx_unlock(&hci->mutex);
}
static void hci_acl_read_complete(usb_request_t* req, void* cookie) {
hci_t* hci = (hci_t*)cookie;
zxlogf(SPEW, "bt-transport-usb: ACL frame received\n");
mtx_lock(&hci->mutex);
if (req->response.status == ZX_OK) {
void* buffer;
zx_status_t status = usb_request_mmap(req, &buffer);
if (status != ZX_OK) {
zxlogf(ERROR, "bt-transport-usb: usb_req_mmap failed: %s\n", zx_status_get_string(status));
mtx_unlock(&hci->mutex);
return;
}
if (hci->acl_channel == ZX_HANDLE_INVALID) {
zxlogf(ERROR, "bt-transport-usb: ACL data received while channel is closed");
} else {
status = zx_channel_write(hci->acl_channel, 0, buffer, req->response.actual, NULL, 0);
if (status < 0) {
zxlogf(ERROR, "bt-transport-usb: hci_acl_read_complete failed to write: %s\n", zx_status_get_string(status));
}
}
// If the snoop channel is open then try to write the packet even if acl_channel was closed.
snoop_channel_write_locked(
hci, bt_hci_snoop_flags(BT_HCI_SNOOP_TYPE_ACL, true), buffer, req->response.actual);
}
zx_status_t status = usb_req_list_add_head(&hci->free_acl_read_reqs, req, hci->parent_req_size);
ZX_DEBUG_ASSERT(status == ZX_OK);
queue_acl_read_requests_locked(hci);
mtx_unlock(&hci->mutex);
}
static void hci_acl_write_complete(usb_request_t* req, void* cookie) {
hci_t* hci = (hci_t*)cookie;
// FIXME what to do with error here?
mtx_lock(&hci->mutex);
zx_status_t status = usb_req_list_add_tail(&hci->free_acl_write_reqs, req,
hci->parent_req_size);
ZX_DEBUG_ASSERT(status == ZX_OK);
if (hci->snoop_channel) {
void* buffer;
zx_status_t status = usb_request_mmap(req, &buffer);
if (status != ZX_OK) {
zxlogf(ERROR, "bt-transport-usb: usb_req_mmap failed: %s\n", zx_status_get_string(status));
mtx_unlock(&hci->mutex);
return;
}
snoop_channel_write_locked(
hci, bt_hci_snoop_flags(BT_HCI_SNOOP_TYPE_ACL, false), buffer, req->response.actual);
}
mtx_unlock(&hci->mutex);
}
static void hci_build_read_wait_items_locked(hci_t* hci) {
zx_wait_item_t* items = hci->read_wait_items;
memset(items, 0, sizeof(hci->read_wait_items));
uint32_t count = 0;
if (hci->cmd_channel != ZX_HANDLE_INVALID) {
items[count].handle = hci->cmd_channel;
items[count].waitfor = ZX_CHANNEL_READABLE | ZX_CHANNEL_PEER_CLOSED;
count++;
}
if (hci->acl_channel != ZX_HANDLE_INVALID) {
items[count].handle = hci->acl_channel;
items[count].waitfor = ZX_CHANNEL_READABLE | ZX_CHANNEL_PEER_CLOSED;
count++;
}
items[count].handle = hci->channels_changed_evt;
items[count].waitfor = ZX_EVENT_SIGNALED;
count++;
hci->read_wait_item_count = count;
zx_object_signal(hci->channels_changed_evt, ZX_EVENT_SIGNALED, 0);
}
static void hci_build_read_wait_items(hci_t* hci) {
mtx_lock(&hci->mutex);
hci_build_read_wait_items_locked(hci);
mtx_unlock(&hci->mutex);
}
// Returns false if there's an error while sending the packet to the hardware or
// if the channel peer closed its endpoint.
static void hci_handle_cmd_read_events(hci_t* hci, zx_wait_item_t* cmd_item) {
if (cmd_item->pending & (ZX_CHANNEL_READABLE | ZX_CHANNEL_PEER_CLOSED)) {
uint8_t buf[CMD_BUF_SIZE];
uint32_t length = sizeof(buf);
zx_status_t status =
zx_channel_read(cmd_item->handle, 0, buf, NULL, length, 0, &length, NULL);
if (status < 0) {
zxlogf(ERROR, "hci_read_thread: failed to read from command channel %s\n",
zx_status_get_string(status));
goto fail;
}
status = usb_control(&hci->usb, USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_DEVICE,
0, 0, 0, buf, length, ZX_TIME_INFINITE, NULL);
if (status < 0) {
zxlogf(ERROR, "hci_read_thread: usb_control failed: %s\n", zx_status_get_string(status));
goto fail;
}
mtx_lock(&hci->mutex);
snoop_channel_write_locked(hci, bt_hci_snoop_flags(BT_HCI_SNOOP_TYPE_CMD, false), buf, length);
mtx_unlock(&hci->mutex);
}
return;
fail:
mtx_lock(&hci->mutex);
channel_cleanup_locked(hci, &hci->cmd_channel);
mtx_unlock(&hci->mutex);
}
static void hci_handle_acl_read_events(hci_t* hci, zx_wait_item_t* acl_item) {
if (acl_item->pending & (ZX_CHANNEL_READABLE | ZX_CHANNEL_PEER_CLOSED)) {
mtx_lock(&hci->mutex);
list_node_t* node = list_peek_head(&hci->free_acl_write_reqs);
mtx_unlock(&hci->mutex);
// We don't have enough reqs. Simply punt the channel read until later.
if (!node)
return;
uint8_t buf[ACL_MAX_FRAME_SIZE];
uint32_t length = sizeof(buf);
zx_status_t status =
zx_channel_read(acl_item->handle, 0, buf, NULL, length, 0, &length, NULL);
if (status < 0) {
zxlogf(ERROR, "hci_read_thread: failed to read from ACL channel %s\n",
zx_status_get_string(status));
goto fail;
}
mtx_lock(&hci->mutex);
node = list_remove_head(&hci->free_acl_write_reqs);
mtx_unlock(&hci->mutex);
// At this point if we don't get a free node from |free_acl_write_reqs| that means that
// they were cleaned up in hci_release(). Just drop the packet.
if (!node)
return;
usb_req_internal_t* req_int = containerof(node, usb_req_internal_t, node);
usb_request_t* req = REQ_INTERNAL_TO_USB_REQ(req_int, hci->parent_req_size);
usb_request_copy_to(req, buf, length, 0);
req->header.length = length;
usb_request_queue(&hci->usb, req, hci_acl_write_complete, hci);
}
return;
fail:
mtx_lock(&hci->mutex);
channel_cleanup_locked(hci, &hci->acl_channel);
mtx_unlock(&hci->mutex);
}
static bool hci_has_read_channels_locked(hci_t* hci) {
// One for the signal event, any additional are read channels.
return hci->read_wait_item_count > 1;
}
static int hci_read_thread(void* arg) {
hci_t* hci = (hci_t*)arg;
mtx_lock(&hci->mutex);
if (!hci_has_read_channels_locked(hci)) {
zxlogf(ERROR, "bt-transport-usb: no channels are open - exiting\n");
hci->read_thread_running = false;
mtx_unlock(&hci->mutex);
return 0;
}
mtx_unlock(&hci->mutex);
while (1) {
zx_status_t status = zx_object_wait_many(
hci->read_wait_items, hci->read_wait_item_count, ZX_TIME_INFINITE);
if (status < 0) {
zxlogf(ERROR, "bt-transport-usb: zx_object_wait_many failed (%s) - exiting\n",
zx_status_get_string(status));
mtx_lock(&hci->mutex);
channel_cleanup_locked(hci, &hci->cmd_channel);
channel_cleanup_locked(hci, &hci->acl_channel);
mtx_unlock(&hci->mutex);
break;
}
for (unsigned i = 0; i < hci->read_wait_item_count; ++i) {
mtx_lock(&hci->mutex);
zx_wait_item_t item = hci->read_wait_items[i];
mtx_unlock(&hci->mutex);
if (item.handle == hci->cmd_channel) {
hci_handle_cmd_read_events(hci, &item);
} else if (item.handle == hci->acl_channel) {
hci_handle_acl_read_events(hci, &item);
}
}
// The channels might have been changed by the *_read_events, recheck the event.
status = zx_object_wait_one(hci->channels_changed_evt, ZX_EVENT_SIGNALED, 0u, NULL);
if (status == ZX_OK) {
hci_build_read_wait_items(hci);
if (!hci_has_read_channels_locked(hci)) {
zxlogf(ERROR, "bt-transport-usb: all channels closed - exiting\n");
break;
}
}
}
mtx_lock(&hci->mutex);
hci->read_thread_running = false;
mtx_unlock(&hci->mutex);
return 0;
}
static zx_status_t hci_open_channel(hci_t* hci, zx_handle_t* in_channel, zx_handle_t* out_channel) {
zx_status_t result = ZX_OK;
mtx_lock(&hci->mutex);
if (*in_channel != ZX_HANDLE_INVALID) {
zxlogf(ERROR, "bt-transport-usb: already bound, failing\n");
result = ZX_ERR_ALREADY_BOUND;
goto done;
}
zx_status_t status = zx_channel_create(0, in_channel, out_channel);
if (status < 0) {
zxlogf(ERROR, "bt-transport-usb: Failed to create channel: %s\n",
zx_status_get_string(status));
result = ZX_ERR_INTERNAL;
goto done;
}
// Kick off the hci_read_thread if it's not already running.
if (!hci->read_thread_running) {
hci_build_read_wait_items_locked(hci);
thrd_t read_thread;
thrd_create_with_name(&read_thread, hci_read_thread, hci, "bt_usb_read_thread");
hci->read_thread_running = true;
thrd_detach(read_thread);
} else {
// Poke the changed event to get the new channel.
zx_object_signal(hci->channels_changed_evt, 0, ZX_EVENT_SIGNALED);
}
done:
mtx_unlock(&hci->mutex);
return result;
}
static void hci_unbind(void* ctx) {
hci_t* hci = ctx;
// Close the transport channels so that the host stack is notified of device removal.
mtx_lock(&hci->mutex);
channel_cleanup_locked(hci, &hci->cmd_channel);
channel_cleanup_locked(hci, &hci->acl_channel);
channel_cleanup_locked(hci, &hci->snoop_channel);
mtx_unlock(&hci->mutex);
device_remove(hci->zxdev);
}
static void hci_release(void* ctx) {
hci_t* hci = ctx;
mtx_lock(&hci->mutex);
usb_request_t* req;
while ((req = usb_req_list_remove_head(&hci->free_event_reqs, hci->parent_req_size)) != NULL) {
usb_request_release(req);
}
while ((req = usb_req_list_remove_head(&hci->free_acl_read_reqs,
hci->parent_req_size)) != NULL) {
usb_request_release(req);
}
while ((req = usb_req_list_remove_head(&hci->free_acl_write_reqs,
hci->parent_req_size)) != NULL) {
usb_request_release(req);
}
mtx_unlock(&hci->mutex);
free(hci);
}
static zx_status_t hci_open_command_channel(void* ctx, zx_handle_t* out_channel) {
hci_t* hci = ctx;
return hci_open_channel(hci, &hci->cmd_channel, out_channel);
}
static zx_status_t hci_open_acl_data_channel(void* ctx, zx_handle_t* out_channel) {
hci_t* hci = ctx;
return hci_open_channel(hci, &hci->acl_channel, out_channel);
}
static zx_status_t hci_open_snoop_channel(void* ctx, zx_handle_t* out_channel) {
hci_t* hci = ctx;
if (hci->snoop_watch == ZX_HANDLE_INVALID) {
zx_status_t status = zx_port_create(0, &hci->snoop_watch);
if (status != ZX_OK) {
zxlogf(ERROR,
"bt-transport-usb: failed to create a port to watch snoop channel: "
"%s\n",
zx_status_get_string(status));
return status;
}
}
zx_port_packet_t packet;
zx_status_t status = zx_port_wait(hci->snoop_watch, 0, &packet);
if (status == ZX_ERR_TIMED_OUT) {
zxlogf(ERROR, "bt-transport-usb: timed out: %s\n",
zx_status_get_string(status));
} else if (packet.signal.observed & ZX_CHANNEL_PEER_CLOSED) {
hci->snoop_channel = ZX_HANDLE_INVALID;
}
zx_status_t ret = hci_open_channel(hci, &hci->snoop_channel, out_channel);
if (ret == ZX_OK) {
zx_signals_t sigs = ZX_CHANNEL_READABLE | ZX_CHANNEL_PEER_CLOSED;
zx_object_wait_async(hci->snoop_channel, hci->snoop_watch, 0, sigs,
ZX_WAIT_ASYNC_REPEATING);
}
return ret;
}
static bt_hci_protocol_ops_t hci_protocol_ops = {
.open_command_channel = hci_open_command_channel,
.open_acl_data_channel = hci_open_acl_data_channel,
.open_snoop_channel = hci_open_snoop_channel,
};
static zx_status_t hci_get_protocol(void* ctx, uint32_t proto_id, void* protocol) {
hci_t* hci = ctx;
if (proto_id != ZX_PROTOCOL_BT_HCI) {
// Pass this on for drivers to load firmware / initialize
return device_get_protocol(hci->usb_zxdev, proto_id, protocol);
}
bt_hci_protocol_t* hci_proto = protocol;
hci_proto->ops = &hci_protocol_ops;
hci_proto->ctx = ctx;
return ZX_OK;
};
static zx_protocol_device_t hci_device_proto = {
.version = DEVICE_OPS_VERSION,
.get_protocol = hci_get_protocol,
.unbind = hci_unbind,
.release = hci_release,
};
static zx_status_t hci_bind(void* ctx, zx_device_t* device) {
zxlogf(TRACE, "hci_bind\n");
usb_protocol_t usb;
zx_status_t status = device_get_protocol(device, ZX_PROTOCOL_USB_OLD, &usb);
if (status != ZX_OK) {
zxlogf(ERROR, "bt-transport-usb: get protocol failed: %s\n", zx_status_get_string(status));
return status;
}
// find our endpoints
usb_desc_iter_t iter;
zx_status_t result = usb_desc_iter_init(&usb, &iter);
if (result < 0) {
zxlogf(ERROR, "bt-transport-usb: usb iterator failed: %s\n", zx_status_get_string(status));
return result;
}
usb_interface_descriptor_t* intf = usb_desc_iter_next_interface(&iter, true);
if (!intf || intf->bNumEndpoints != 3) {
usb_desc_iter_release(&iter);
return ZX_ERR_NOT_SUPPORTED;
}
uint8_t bulk_in_addr = 0;
uint8_t bulk_out_addr = 0;
uint8_t intr_addr = 0;
uint16_t intr_max_packet = 0;
usb_endpoint_descriptor_t* endp = usb_desc_iter_next_endpoint(&iter);
while (endp) {
if (usb_ep_direction(endp) == USB_ENDPOINT_OUT) {
if (usb_ep_type(endp) == USB_ENDPOINT_BULK) {
bulk_out_addr = endp->bEndpointAddress;
}
} else {
if (usb_ep_type(endp) == USB_ENDPOINT_BULK) {
bulk_in_addr = endp->bEndpointAddress;
} else if (usb_ep_type(endp) == USB_ENDPOINT_INTERRUPT) {
intr_addr = endp->bEndpointAddress;
intr_max_packet = usb_ep_max_packet(endp);
}
}
endp = usb_desc_iter_next_endpoint(&iter);
}
usb_desc_iter_release(&iter);
if (!bulk_in_addr || !bulk_out_addr || !intr_addr) {
zxlogf(ERROR, "bt-transport-usb: bind could not find endpoints\n");
return ZX_ERR_NOT_SUPPORTED;
}
hci_t* hci = calloc(1, sizeof(hci_t));
if (!hci) {
zxlogf(ERROR, "bt-transport-usb: Not enough memory for hci_t\n");
return ZX_ERR_NO_MEMORY;
}
list_initialize(&hci->free_event_reqs);
list_initialize(&hci->free_acl_read_reqs);
list_initialize(&hci->free_acl_write_reqs);
zx_event_create(0, &hci->channels_changed_evt);
mtx_init(&hci->mutex, mtx_plain);
hci->usb_zxdev = device;
memcpy(&hci->usb, &usb, sizeof(hci->usb));
hci->parent_req_size = usb_get_request_size(&hci->usb);
size_t req_size = hci->parent_req_size + sizeof(usb_req_internal_t);
for (int i = 0; i < EVENT_REQ_COUNT; i++) {
usb_request_t* req;
status = usb_request_alloc(&req, intr_max_packet, intr_addr, req_size);
if (status != ZX_OK) {
goto fail;
}
status = usb_req_list_add_head(&hci->free_event_reqs, req, hci->parent_req_size);
ZX_DEBUG_ASSERT(status == ZX_OK);
}
for (int i = 0; i < ACL_READ_REQ_COUNT; i++) {
usb_request_t* req;
status = usb_request_alloc(&req, ACL_MAX_FRAME_SIZE, bulk_in_addr, req_size);
if (status != ZX_OK) {
goto fail;
}
status = usb_req_list_add_head(&hci->free_acl_read_reqs, req, hci->parent_req_size);
ZX_DEBUG_ASSERT(status == ZX_OK);
}
for (int i = 0; i < ACL_WRITE_REQ_COUNT; i++) {
usb_request_t* req;
status = usb_request_alloc(&req, ACL_MAX_FRAME_SIZE, bulk_out_addr, req_size);
if (status != ZX_OK) {
goto fail;
}
status = usb_req_list_add_head(&hci->free_acl_write_reqs, req, hci->parent_req_size);
ZX_DEBUG_ASSERT(status == ZX_OK);
}
mtx_lock(&hci->mutex);
queue_interrupt_requests_locked(hci);
queue_acl_read_requests_locked(hci);
mtx_unlock(&hci->mutex);
// Copy the PID and VID from the underlying BT so that it can be filtered on
// for HCI drivers
usb_device_descriptor_t dev_desc;
usb_get_device_descriptor(&usb, &dev_desc);
zx_device_prop_t props[] = {
{ BIND_PROTOCOL, 0, ZX_PROTOCOL_BT_TRANSPORT },
{ BIND_USB_VID, 0, dev_desc.idVendor },
{ BIND_USB_PID, 0, dev_desc.idProduct },
};
device_add_args_t args = {
.version = DEVICE_ADD_ARGS_VERSION,
.name = "bt_transport_usb",
.ctx = hci,
.ops = &hci_device_proto,
.proto_id = ZX_PROTOCOL_BT_TRANSPORT,
.props = props,
.prop_count = countof(props),
};
status = device_add(device, &args, &hci->zxdev);
if (status == ZX_OK) {
return ZX_OK;
}
fail:
zxlogf(ERROR, "bt-transport-usb: bind failed: %s\n", zx_status_get_string(status));
hci_release(hci);
return status;
}
static zx_driver_ops_t usb_bt_hci_driver_ops = {
.version = DRIVER_OPS_VERSION,
.bind = hci_bind,
};
// clang-format off
ZIRCON_DRIVER_BEGIN(bt_transport_usb, usb_bt_hci_driver_ops, "zircon", "0.1", 4)
BI_ABORT_IF(NE, BIND_PROTOCOL, ZX_PROTOCOL_USB_OLD),
BI_ABORT_IF(NE, BIND_USB_CLASS, USB_CLASS_WIRELESS),
BI_ABORT_IF(NE, BIND_USB_SUBCLASS, USB_SUBCLASS_BLUETOOTH),
BI_MATCH_IF(EQ, BIND_USB_PROTOCOL, USB_PROTOCOL_BLUETOOTH),
ZIRCON_DRIVER_END(bt_transport_usb)