| // Copyright 2018 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 <assert.h> |
| #include <fuchsia/hardware/serial/c/fidl.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <threads.h> |
| #include <unistd.h> |
| #include <zircon/device/bt-hci.h> |
| #include <zircon/status.h> |
| |
| #include <ddk/binding.h> |
| #include <ddk/debug.h> |
| #include <ddk/device.h> |
| #include <ddk/driver.h> |
| #include <ddk/platform-defs.h> |
| #include <ddk/protocol/bt/hci.h> |
| #include <ddk/protocol/serial.h> |
| |
| // The maximum HCI ACL frame size used for data transactions |
| #define ACL_MAX_FRAME_SIZE 1029 // (1024 + 4 bytes for the ACL header + 1 byte packet indicator) |
| |
| #define CMD_BUF_SIZE 255 + 4 // 1 byte packet indicator + 3 byte header + payload |
| #define EVENT_BUF_SIZE 255 + 3 // 1 byte packet indicator + 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 + 2 // add one for the changed event and one for UART socket |
| |
| // HCI UART packet indicators |
| enum { |
| HCI_NONE = 0, |
| HCI_COMMAND = 1, |
| HCI_ACL_DATA = 2, |
| HCI_SCO = 3, |
| HCI_EVENT = 4, |
| }; |
| |
| typedef struct { |
| zx_device_t* zxdev; |
| zx_device_t* parent; |
| zx_handle_t uart_socket; |
| zx_handle_t cmd_channel; |
| zx_handle_t acl_channel; |
| zx_handle_t snoop_channel; |
| |
| // 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; |
| |
| // type of current packet being read from the UART |
| uint8_t cur_uart_packet_type; |
| |
| // for accumulating HCI events |
| uint8_t event_buffer[EVENT_BUF_SIZE]; |
| size_t event_buffer_offset; |
| |
| // for accumulating ACL data packets |
| uint8_t acl_buffer[ACL_MAX_FRAME_SIZE]; |
| size_t acl_buffer_offset; |
| |
| mtx_t mutex; |
| } hci_t; |
| |
| // macro for returning length of current event packet being received |
| // payload length is in byte 2 of the packet |
| // add 3 bytes for packet indicator, event code and length byte |
| #define EVENT_PACKET_LENGTH(hci) ((hci)->event_buffer_offset > 2 ? (hci)->event_buffer[2] + 3 : 0) |
| |
| // macro for returning length of current ACL data packet being received |
| // length is in bytes 3 and 4 of the packet |
| // add 5 bytes for packet indicator, control info and length fields |
| #define ACL_PACKET_LENGTH(hci) \ |
| ((hci)->acl_buffer_offset > 4 ? ((hci)->acl_buffer[3] | ((hci)->acl_buffer[4] << 8)) + 5 : 0) |
| |
| 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) { |
| if (status != ZX_ERR_PEER_CLOSED) { |
| zxlogf(ERROR, "bt-transport-uart: failed to write to snoop channel: %s\n", |
| zx_status_get_string(status)); |
| } |
| channel_cleanup_locked(hci, &hci->snoop_channel); |
| } |
| } |
| |
| 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->uart_socket; |
| 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* item) { |
| if (item->pending & (ZX_CHANNEL_READABLE | ZX_CHANNEL_PEER_CLOSED)) { |
| uint8_t buf[CMD_BUF_SIZE]; |
| uint32_t length = sizeof(buf) - 1; |
| zx_status_t status = zx_channel_read(item->handle, 0, buf + 1, NULL, length, 0, &length, NULL); |
| if (status < 0) { |
| if (status != ZX_ERR_PEER_CLOSED) { |
| zxlogf(ERROR, "hci_read_thread: failed to read from command channel %s\n", |
| zx_status_get_string(status)); |
| } |
| goto fail; |
| } |
| |
| buf[0] = HCI_COMMAND; |
| length++; |
| status = zx_socket_write(hci->uart_socket, 0, buf, length, NULL); |
| if (status < 0) { |
| zxlogf(ERROR, "hci_read_thread: zx_socket_write 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 + 1, |
| length - 1); |
| 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* item) { |
| if (item->pending & (ZX_CHANNEL_READABLE | ZX_CHANNEL_PEER_CLOSED)) { |
| uint8_t buf[ACL_MAX_FRAME_SIZE]; |
| uint32_t length = sizeof(buf) - 1; |
| zx_status_t status = zx_channel_read(item->handle, 0, buf + 1, 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; |
| } |
| |
| buf[0] = HCI_ACL_DATA; |
| length++; |
| status = zx_socket_write(hci->uart_socket, 0, buf, length, NULL); |
| if (status < 0) { |
| zxlogf(ERROR, "hci_read_thread: zx_socket_write failed: %s\n", zx_status_get_string(status)); |
| goto fail; |
| } |
| snoop_channel_write_locked(hci, bt_hci_snoop_flags(BT_HCI_SNOOP_TYPE_ACL, false), buf + 1, |
| length - 1); |
| } |
| |
| return; |
| |
| fail: |
| mtx_lock(&hci->mutex); |
| channel_cleanup_locked(hci, &hci->acl_channel); |
| mtx_unlock(&hci->mutex); |
| } |
| |
| static void hci_handle_uart_read_events(hci_t* hci, zx_wait_item_t* item) { |
| if (item->pending & (ZX_CHANNEL_READABLE | ZX_CHANNEL_PEER_CLOSED)) { |
| uint8_t buf[ACL_MAX_FRAME_SIZE]; |
| size_t length = sizeof(buf); |
| zx_status_t status = zx_socket_read(item->handle, 0, buf, length, &length); |
| if (status < 0) { |
| zxlogf(ERROR, "hci_read_thread: failed to read from ACL channel %s\n", |
| zx_status_get_string(status)); |
| goto fail; |
| } |
| |
| const uint8_t* src = buf; |
| const uint8_t* end = src + length; |
| uint8_t packet_type = hci->cur_uart_packet_type; |
| |
| while (src < end) { |
| if (packet_type == HCI_NONE) { |
| // start of new packet. read packet type |
| packet_type = *src++; |
| if (packet_type != HCI_EVENT && packet_type != HCI_ACL_DATA) { |
| zxlogf(INFO, "unsupported HCI packet type %u. We may be out of sync\n", packet_type); |
| return; |
| } |
| } |
| |
| if (packet_type == HCI_EVENT) { |
| size_t packet_length = EVENT_PACKET_LENGTH(hci); |
| |
| while (!packet_length && src < end) { |
| // read until we have enough to compute packet length |
| hci->event_buffer[hci->event_buffer_offset++] = *src++; |
| packet_length = EVENT_PACKET_LENGTH(hci); |
| } |
| if (!packet_length) { |
| break; |
| } |
| |
| size_t remaining = end - src; |
| size_t copy = packet_length - hci->event_buffer_offset; |
| if (copy > remaining) |
| copy = remaining; |
| memcpy(hci->event_buffer + hci->event_buffer_offset, src, copy); |
| src += copy; |
| hci->event_buffer_offset += copy; |
| |
| if (hci->event_buffer_offset == packet_length) { |
| // send accumulated event packet, minus the packet indicator |
| zx_status_t status = zx_channel_write(hci->cmd_channel, 0, &hci->event_buffer[1], |
| packet_length - 1, NULL, 0); |
| if (status < 0) { |
| zxlogf(ERROR, "bt-transport-uart: failed to write event packet: %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[1], packet_length - 1); |
| |
| // reset buffer |
| packet_type = HCI_NONE; |
| hci->event_buffer_offset = 1; |
| } |
| } else { // HCI_ACL_DATA |
| size_t packet_length = EVENT_PACKET_LENGTH(hci); |
| |
| while (!packet_length && src < end) { |
| // read until we have enough to compute packet length |
| hci->acl_buffer[hci->acl_buffer_offset++] = *src++; |
| packet_length = ACL_PACKET_LENGTH(hci); |
| } |
| if (!packet_length) { |
| break; |
| } |
| |
| size_t remaining = end - src; |
| size_t copy = packet_length - hci->acl_buffer_offset; |
| if (copy > remaining) |
| copy = remaining; |
| memcpy(hci->acl_buffer + hci->acl_buffer_offset, src, copy); |
| src += copy; |
| hci->acl_buffer_offset += copy; |
| |
| if (hci->acl_buffer_offset == packet_length) { |
| // send accumulated ACL data packet, minus the packet indicator |
| zx_status_t status = zx_channel_write(hci->acl_channel, 0, &hci->acl_buffer[1], |
| packet_length - 1, NULL, 0); |
| if (status < 0) { |
| zxlogf(ERROR, "bt-transport-uart: failed to write ACL packet: %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), |
| &hci->acl_buffer[1], packet_length - 1); |
| |
| // reset buffer |
| packet_type = HCI_NONE; |
| hci->acl_buffer_offset = 1; |
| } |
| } |
| } |
| |
| hci->cur_uart_packet_type = packet_type; |
| } |
| |
| 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 and one for uart socket, any additional are read channels. |
| return hci->read_wait_item_count > 2; |
| } |
| |
| 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-uart: 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-uart: 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); |
| } else if (item.handle == hci->uart_socket) { |
| hci_handle_uart_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(TRACE, "bt-transport-uart: 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 in) { |
| zx_status_t result = ZX_OK; |
| mtx_lock(&hci->mutex); |
| |
| if (*in_channel != ZX_HANDLE_INVALID) { |
| zxlogf(ERROR, "bt-transport-uart: already bound, failing\n"); |
| result = ZX_ERR_ALREADY_BOUND; |
| goto done; |
| } |
| |
| *in_channel = in; |
| |
| // 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; |
| zx_handle_close(hci->uart_socket); |
| free(hci); |
| } |
| |
| static zx_status_t hci_open_command_channel(void* ctx, zx_handle_t in) { |
| hci_t* hci = ctx; |
| return hci_open_channel(hci, &hci->cmd_channel, in); |
| } |
| |
| static zx_status_t hci_open_acl_data_channel(void* ctx, zx_handle_t in) { |
| hci_t* hci = ctx; |
| return hci_open_channel(hci, &hci->acl_channel, in); |
| } |
| |
| static zx_status_t hci_open_snoop_channel(void* ctx, zx_handle_t in) { |
| hci_t* hci = ctx; |
| return hci_open_channel(hci, &hci->snoop_channel, in); |
| } |
| |
| 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->parent, 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* parent) { |
| serial_protocol_t serial; |
| |
| zx_status_t status = device_get_protocol(parent, ZX_PROTOCOL_SERIAL, &serial); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "bt-transport-uart: get protocol ZX_PROTOCOL_SERIAL failed\n"); |
| return status; |
| } |
| |
| hci_t* hci = calloc(1, sizeof(hci_t)); |
| if (!hci) { |
| zxlogf(ERROR, "bt-transport-uart: Not enough memory for hci_t\n"); |
| return ZX_ERR_NO_MEMORY; |
| } |
| |
| status = serial_open_socket(&serial, &hci->uart_socket); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "bt-transport-uart: serial_open_socket failed: %s\n", |
| zx_status_get_string(status)); |
| goto fail; |
| } |
| |
| zx_event_create(0, &hci->channels_changed_evt); |
| mtx_init(&hci->mutex, mtx_plain); |
| hci->parent = parent; |
| hci->cur_uart_packet_type = HCI_NONE; |
| |
| // pre-populate event packet indicators |
| hci->event_buffer[0] = HCI_EVENT; |
| hci->event_buffer_offset = 1; |
| hci->acl_buffer[0] = HCI_ACL_DATA; |
| hci->acl_buffer_offset = 1; |
| |
| serial_port_info_t info; |
| status = serial_get_info(&serial, &info); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "hci_bind: serial_get_info failed\n"); |
| goto fail; |
| } |
| if (info.serial_class != fuchsia_hardware_serial_Class_BLUETOOTH_HCI) { |
| zxlogf(ERROR, "hci_bind: info.device_class != BLUETOOTH_HCI\n"); |
| status = ZX_ERR_INTERNAL; |
| goto fail; |
| } |
| |
| // Copy the PID and VID from the platform device info so it can be filtered on |
| // for HCI drivers |
| zx_device_prop_t props[] = { |
| {BIND_PROTOCOL, 0, ZX_PROTOCOL_BT_TRANSPORT}, |
| {BIND_SERIAL_VID, 0, info.serial_vid}, |
| {BIND_SERIAL_PID, 0, info.serial_pid}, |
| }; |
| |
| device_add_args_t args = { |
| .version = DEVICE_ADD_ARGS_VERSION, |
| .name = "bt-transport-uart", |
| .ctx = hci, |
| .ops = &hci_device_proto, |
| .proto_id = ZX_PROTOCOL_BT_TRANSPORT, |
| .props = props, |
| .prop_count = countof(props), |
| }; |
| |
| status = device_add(parent, &args, &hci->zxdev); |
| if (status == ZX_OK) { |
| return ZX_OK; |
| } |
| |
| fail: |
| zxlogf(ERROR, "hci_bind: bind failed: %s\n", zx_status_get_string(status)); |
| hci_release(hci); |
| return status; |
| } |
| |
| static zx_driver_ops_t bt_hci_driver_ops = { |
| .version = DRIVER_OPS_VERSION, |
| .bind = hci_bind, |
| }; |
| |
| // clang-format off |
| ZIRCON_DRIVER_BEGIN(bt_transport_uart, bt_hci_driver_ops, "zircon", "0.1", 2) |
| BI_ABORT_IF(NE, BIND_PROTOCOL, ZX_PROTOCOL_SERIAL), |
| BI_MATCH_IF(EQ, BIND_SERIAL_CLASS, fuchsia_hardware_serial_Class_BLUETOOTH_HCI), |
| ZIRCON_DRIVER_END(bt_transport_uart) |