blob: 4f44d0ff479ac543177a15b8d7ab9685e8f5107e [file] [log] [blame]
// Copyright 2022 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 <fuchsia/hardware/bt/hci/cpp/banjo.h>
#include <fuchsia/hardware/serialimpl/async/c/banjo.h>
#include <lib/async-loop/cpp/loop.h>
#include <lib/async-loop/default.h>
#include <lib/async/cpp/task.h>
#include <lib/async/cpp/wait.h>
#include <lib/fit/thread_checker.h>
#include <lib/zx/event.h>
#include <threads.h>
#include <zircon/device/bt-hci.h>
#include <mutex>
#include <ddktl/device.h>
#include <ddktl/fidl.h>
namespace bt_transport_uart {
class BtTransportUart;
using BtTransportUartType = ddk::Device<BtTransportUart, ddk::GetProtocolable, ddk::Unbindable>;
class BtTransportUart : public BtTransportUartType, public ddk::BtHciProtocol<BtTransportUart> {
// If |dispatcher| is non-null, it will be used instead of a new work thread.
// tests.
explicit BtTransportUart(zx_device_t* parent, async_dispatcher_t* dispatcher);
// Static bind function for the ZIRCON_DRIVER() declaration. Binds this device and passes
// ownership to the driver manager.
static zx_status_t Create(void* ctx, zx_device_t* parent);
// Constructor for tests to inject a dispatcher for the work thread.
static zx_status_t Create(zx_device_t* parent, async_dispatcher_t* dispatcher);
// DDK mixins:
void DdkUnbind(ddk::UnbindTxn txn);
void DdkRelease();
zx_status_t DdkGetProtocol(uint32_t proto_id, void* out_proto);
// ddk::BtHciProtocol mixins:
zx_status_t BtHciOpenCommandChannel(zx::channel in);
zx_status_t BtHciOpenAclDataChannel(zx::channel in);
zx_status_t BtHciOpenSnoopChannel(zx::channel in);
zx_status_t BtHciOpenScoChannel(zx::channel in);
zx_status_t BtHciOpenIsoChannel(zx::channel in);
void BtHciConfigureSco(sco_coding_format_t coding_format, sco_encoding_t encoding,
sco_sample_rate_t sample_rate, bt_hci_configure_sco_callback callback,
void* cookie);
void BtHciResetSco(bt_hci_reset_sco_callback callback, void* cookie);
// HCI UART packet indicators
enum BtHciPacketIndicator : uint8_t {
kHciNone = 0,
kHciCommand = 1,
kHciAclData = 2,
kHciSco = 3,
kHciEvent = 4,
struct HciWriteCtx {
BtTransportUart* ctx;
// Owned.
uint8_t* buffer;
// This wrapper around async_wait enables us to get a BtTransportUart* in the handler.
// We use this instead of async::WaitMethod because async::WaitBase isn't thread safe.
struct Wait : public async_wait {
explicit Wait(BtTransportUart* uart, zx::channel* channel);
static void Handler(async_dispatcher_t* dispatcher, async_wait_t* async_wait,
zx_status_t status, const zx_packet_signal_t* signal);
BtTransportUart* uart;
// Indicates whether a wait has begun and not ended.
bool pending = false;
// The channel that this wait waits on.
zx::channel* channel;
// Returns length of current event packet being received
// Must only be called in the read callback (HciHandleUartReadEvents).
size_t EventPacketLength();
// Returns length of current ACL data packet being received
// Must only be called in the read callback (HciHandleUartReadEvents).
size_t AclPacketLength();
// Returns length of current SCO data packet being received
// Must only be called in the read callback (HciHandleUartReadEvents).
size_t ScoPacketLength();
void ChannelCleanupLocked(zx::channel* channel) __TA_REQUIRES(mutex_);
void SnoopChannelWriteLocked(uint8_t flags, uint8_t* bytes, size_t length) __TA_REQUIRES(mutex_);
void HciBeginShutdown() __TA_EXCLUDES(mutex_);
void SerialWrite(uint8_t* buffer, size_t length) __TA_EXCLUDES(mutex_);
void HciHandleClientChannel(zx::channel* chan, zx_signals_t pending) __TA_EXCLUDES(mutex_);
// Queues a read callback for async serial on the dispatcher.
void QueueUartRead();
void HciHandleUartReadEvents(const uint8_t* buf, size_t length) __TA_EXCLUDES(mutex_);
// Reads the next packet chunk from |uart_src| into |buffer| and increments |buffer_offset| and
// |uart_src| by the number of bytes read. If a complete packet is read, it will be written to
// |channel|.
using PacketLengthFunction = size_t (BtTransportUart::*)();
void ProcessNextUartPacketFromReadBuffer(uint8_t* buffer, size_t buffer_size,
size_t* buffer_offset, const uint8_t** uart_src,
const uint8_t* uart_end,
PacketLengthFunction get_packet_length,
zx::channel* channel, bt_hci_snoop_type_t snoop_type);
void HciReadComplete(zx_status_t status, const uint8_t* buffer, size_t length)
void HciWriteComplete(zx_status_t status) __TA_EXCLUDES(mutex_);
static int HciThread(void* arg) __TA_EXCLUDES(mutex_);
void OnChannelSignal(Wait* wait, zx_status_t status, const zx_packet_signal_t* signal);
zx_status_t HciOpenChannel(zx::channel* in_channel, zx_handle_t in) __TA_EXCLUDES(mutex_);
// Adds the device.
zx_status_t Bind() __TA_EXCLUDES(mutex_);
// 1 byte packet indicator + 3 byte header + payload
static constexpr uint32_t kCmdBufSize = 255 + 4;
// 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.
static constexpr uint8_t kNumChannels = 2;
// add one for the wakeup event
static constexpr uint8_t kNumWaitItems = kNumChannels + 1;
// The maximum HCI ACL frame size used for data transactions
// (1024 + 4 bytes for the ACL header + 1 byte packet indicator)
static constexpr uint32_t kAclMaxFrameSize = 1029;
// The maximum HCI SCO frame size used for data transactions.
// (255 byte payload + 3 bytes for the SCO header + 1 byte packet indicator)
static constexpr uint32_t kScoMaxFrameSize = 259;
// 1 byte packet indicator + 2 byte header + payload
static constexpr uint32_t kEventBufSize = 255 + 3;
serial_impl_async_protocol_t serial_ __TA_GUARDED(mutex_);
zx::channel cmd_channel_ __TA_GUARDED(mutex_);
Wait cmd_channel_wait_ __TA_GUARDED(mutex_){this, &cmd_channel_};
zx::channel acl_channel_ __TA_GUARDED(mutex_);
Wait acl_channel_wait_ __TA_GUARDED(mutex_){this, &acl_channel_};
zx::channel sco_channel_ __TA_GUARDED(mutex_);
Wait sco_channel_wait_ __TA_GUARDED(mutex_){this, &sco_channel_};
zx::channel snoop_channel_ __TA_GUARDED(mutex_);
std::atomic_bool shutting_down_ = false;
// True if there is not a UART write pending. Set to false when a write is initiated, and set to
// true when the write completes.
bool can_write_ __TA_GUARDED(mutex_) = true;
// type of current packet being read from the UART
// Must only be used in the UART read callback (HciHandleUartReadEvents).
BtHciPacketIndicator cur_uart_packet_type_ = kHciNone;
// for accumulating HCI events
// Must only be used in the UART read callback (HciHandleUartReadEvents).
uint8_t event_buffer_[kEventBufSize];
// Must only be used in the UART read callback (HciHandleUartReadEvents).
size_t event_buffer_offset_ = 0;
// for accumulating ACL data packets
// Must only be used in the UART read callback (HciHandleUartReadEvents).
uint8_t acl_buffer_[kAclMaxFrameSize];
// Must only be used in the UART read callback (HciHandleUartReadEvents).
size_t acl_buffer_offset_ = 0;
// For accumulating SCO packets
// Must only be used in the UART read callback (HciHandleUartReadEvents).
uint8_t sco_buffer_[kScoMaxFrameSize];
// Must only be used in the UART read callback (HciHandleUartReadEvents).
size_t sco_buffer_offset_ = 0;
// for sending outbound packets to the UART
// kAclMaxFrameSize is the largest frame size sent.
uint8_t write_buffer_[kAclMaxFrameSize] __TA_GUARDED(mutex_);
std::mutex mutex_;
std::optional<async::Loop> loop_;
// In production, this is loop_.dispatcher(). In tests, this is the test dispatcher.
async_dispatcher_t* dispatcher_ = nullptr;
// The task which runs to queue a uart read.
async::TaskClosureMethod<BtTransportUart, &BtTransportUart::QueueUartRead> queue_read_task_{this};
} // namespace bt_transport_uart