blob: 5a996c730cf8ea8c4a8e6c49041a26801adee626 [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 "loopback.h"
#include <assert.h>
#include <lib/ddk/debug.h>
#include <lib/ddk/device.h>
#include <lib/ddk/driver.h>
#include <lib/ddk/platform-defs.h>
#include <lib/syslog/cpp/macros.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <threads.h>
#include <unistd.h>
#include <zircon/assert.h>
#include <zircon/status.h>
namespace bt_hci_virtual {
LoopbackDevice::LoopbackDevice(zx_device_t* parent, async_dispatcher_t* dispatcher)
: LoopbackDeviceType(parent), dispatcher_(dispatcher) {}
// NOLINTNEXTLINE(bugprone-easily-swappable-parameters)
LoopbackDevice::Wait::Wait(LoopbackDevice* uart, zx::channel* channel) {
this->state = ASYNC_STATE_INIT;
this->handler = Handler;
this->object = ZX_HANDLE_INVALID;
this->trigger = ZX_SIGNAL_NONE;
this->options = 0;
this->uart = uart;
this->channel = channel;
}
void LoopbackDevice::Wait::Handler(async_dispatcher_t* dispatcher, async_wait_t* async_wait,
zx_status_t status, const zx_packet_signal_t* signal) {
auto wait = static_cast<Wait*>(async_wait);
wait->uart->OnChannelSignal(wait, status, signal);
}
size_t LoopbackDevice::EventPacketLength() {
// payload length is in byte 2 of the packet
// add 3 bytes for packet indicator, event code and length byte
return event_buffer_offset_ > 2 ? event_buffer_[2] + 3 : 0;
}
size_t LoopbackDevice::AclPacketLength() {
// length is in bytes 3 and 4 of the packet
// add 5 bytes for packet indicator, control info and length fields
return acl_buffer_offset_ > 4 ? (acl_buffer_[3] | (acl_buffer_[4] << 8)) + 5 : 0;
}
size_t LoopbackDevice::ScoPacketLength() {
// payload length is byte 3 of the packet
// add 4 bytes for packet indicator, handle, and length byte
return sco_buffer_offset_ > 3 ? (sco_buffer_[3] + 4) : 0;
}
zx_status_t LoopbackDevice::BtHciOpenCommandChannel(zx::channel in) {
FX_LOGS(TRACE) << "LoopbackDevice::BtHciOpenCommandChannel";
return HciOpenChannel(&cmd_channel_, in.release());
}
zx_status_t LoopbackDevice::BtHciOpenAclDataChannel(zx::channel in) {
FX_LOGS(TRACE) << "LoopbackDevice::BtHciOpenAclDataChannel";
return HciOpenChannel(&acl_channel_, in.release());
}
zx_status_t LoopbackDevice::BtHciOpenSnoopChannel(zx::channel in) {
FX_LOGS(TRACE) << "LoopbackDevice::BtHciOpenSnoopChannel";
return HciOpenChannel(&snoop_channel_, in.release());
}
zx_status_t LoopbackDevice::BtHciOpenScoChannel(zx::channel in) {
FX_LOGS(TRACE) << "LoopbackDevice::BtHciOpenScoChannel";
return HciOpenChannel(&sco_channel_, in.release());
}
zx_status_t LoopbackDevice::BtHciOpenIsoChannel(zx::channel in) { return ZX_ERR_NOT_SUPPORTED; }
void LoopbackDevice::ChannelCleanupLocked(zx::channel* channel) {
FX_LOGS(TRACE) << "LoopbackDevice::ChannelCleanupLocked";
if (!channel->is_valid()) {
return;
}
if (channel == &cmd_channel_ && cmd_channel_wait_.pending) {
async_cancel_wait(dispatcher_, &cmd_channel_wait_);
cmd_channel_wait_.pending = false;
} else if (channel == &acl_channel_ && acl_channel_wait_.pending) {
async_cancel_wait(dispatcher_, &acl_channel_wait_);
acl_channel_wait_.pending = false;
} else if (channel == &sco_channel_ && sco_channel_wait_.pending) {
async_cancel_wait(dispatcher_, &sco_channel_wait_);
sco_channel_wait_.pending = false;
} else if (channel == &in_channel_ && in_channel_wait_.pending) {
async_cancel_wait(dispatcher_, &in_channel_wait_);
in_channel_wait_.pending = false;
}
channel->reset();
}
void LoopbackDevice::SnoopChannelWriteLocked(uint8_t flags, uint8_t* bytes, size_t length) {
FX_LOGS(TRACE) << "LoopbackDevice::SnoopChannelWriteLocked";
if (!snoop_channel_.is_valid()) {
return;
}
// We tack on a flags byte to the beginning of the payload.
// Use an iovec to avoid a large allocation + copy.
zx_channel_iovec_t iovs[2];
iovs[0] = {.buffer = &flags, .capacity = sizeof(flags), .reserved = 0};
iovs[1] = {.buffer = bytes, .capacity = static_cast<uint32_t>(length), .reserved = 0};
zx_status_t status =
snoop_channel_.write(/*flags=*/ZX_CHANNEL_WRITE_USE_IOVEC, /*bytes=*/iovs,
/*num_bytes=*/2, /*handles=*/nullptr, /*num_handles=*/0);
if (status != ZX_OK) {
if (status != ZX_ERR_PEER_CLOSED) {
FX_LOGS(ERROR) << "bt-loopback-device: failed to write to snoop channel"
<< zx_status_get_string(status);
}
// It should be safe to clean up the channel right here as the work thread
// never waits on this channel from outside of the lock.
ChannelCleanupLocked(&snoop_channel_);
}
}
void LoopbackDevice::HciBeginShutdown() {
FX_LOGS(TRACE) << "LoopbackDevice::HciBeginShutdown";
bool was_shutting_down = shutting_down_.exchange(true, std::memory_order_relaxed);
if (!was_shutting_down) {
FX_LOGS(TRACE) << "LoopbackDevice::HciBeginShutdown !was_shutting_down";
DdkAsyncRemove();
}
}
void LoopbackDevice::OnChannelSignal(Wait* wait, zx_status_t status,
const zx_packet_signal_t* signal) {
FX_LOGS(TRACE) << "OnChannelSignal";
{
std::lock_guard guard(mutex_);
wait->pending = false;
}
if (wait->channel == &in_channel_) {
HciHandleIncomingChannel(wait->channel, signal->observed);
} else {
HciHandleClientChannel(wait->channel, signal->observed);
}
// Reset waiters
{
std::lock_guard guard(mutex_);
// Resume waiting for channel signals. If a packet was queued while the write was processing,
// it should be immediately signaled.
if (wait->channel->is_valid() && !wait->pending) {
ZX_ASSERT(async_begin_wait(dispatcher_, wait) == ZX_OK);
wait->pending = true;
}
}
}
zx_status_t LoopbackDevice::HciOpenChannel(zx::channel* in_channel, zx_handle_t in) {
FX_LOGS(TRACE) << "LoopbackDevice::HciOpenChannel";
std::lock_guard guard(mutex_);
zx_status_t result = ZX_OK;
if (in_channel->is_valid()) {
FX_LOGS(ERROR) << "LoopbackDevice: already bound, failing";
result = ZX_ERR_ALREADY_BOUND;
return result;
}
in_channel->reset(in);
Wait* wait = nullptr;
if (in_channel == &cmd_channel_) {
wait = &cmd_channel_wait_;
} else if (in_channel == &acl_channel_) {
wait = &acl_channel_wait_;
} else if (in_channel == &sco_channel_) {
wait = &sco_channel_wait_;
} else if (in_channel == &snoop_channel_) {
return ZX_OK;
}
ZX_ASSERT(wait);
wait->object = in_channel->get();
wait->trigger = ZX_CHANNEL_READABLE | ZX_CHANNEL_PEER_CLOSED;
ZX_ASSERT(async_begin_wait(dispatcher_, wait) == ZX_OK);
wait->pending = true;
return result;
}
void LoopbackDevice::HciHandleIncomingChannel(zx::channel* chan, zx_signals_t pending) {
FX_LOGS(TRACE) << "HciHandleIncomingChannel readable:" << int(pending & ZX_CHANNEL_READABLE)
<< ", closed: " << int(pending & ZX_CHANNEL_PEER_CLOSED);
// If we are in the process of shutting down, we are done.
if (atomic_load_explicit(&shutting_down_, std::memory_order_relaxed)) {
return;
}
// Channel may have been closed since signal was received.
if (!chan->is_valid()) {
FX_LOGS(ERROR) << "channel is invalid";
return;
}
// Handle the read signal first. If we are also peer closed, we want to make
// sure that we have processed all of the pending messages before cleaning up.
if (pending & ZX_CHANNEL_READABLE) {
uint32_t length;
uint8_t read_buffer[kAclMaxFrameSize];
{
std::lock_guard guard(mutex_);
zx_status_t status;
status = zx_channel_read(chan->get(), 0, read_buffer, nullptr, kAclMaxFrameSize, 0, &length,
nullptr);
if (status == ZX_ERR_SHOULD_WAIT) {
FX_LOGS(WARNING) << "ignoring ZX_ERR_SHOULD_WAIT when reading incoming channel";
return;
}
if (status != ZX_OK) {
FX_LOGS(ERROR) << "failed to read from incoming channel " << zx_status_get_string(status);
ChannelCleanupLocked(chan);
return;
}
}
const uint8_t* buf = read_buffer;
const uint8_t* const end = read_buffer + length;
while (buf < end) {
if (cur_uart_packet_type_ == kHciNone) {
// start of new packet. read packet type
cur_uart_packet_type_ = static_cast<BtHciPacketIndicator>(*buf++);
}
switch (cur_uart_packet_type_) {
case kHciEvent:
ProcessNextUartPacketFromReadBuffer(
event_buffer_, sizeof(event_buffer_), &event_buffer_offset_, &buf, end,
&LoopbackDevice::EventPacketLength, &cmd_channel_, BT_HCI_SNOOP_TYPE_EVT);
break;
case kHciAclData:
ProcessNextUartPacketFromReadBuffer(acl_buffer_, sizeof(acl_buffer_), &acl_buffer_offset_,
&buf, end, &LoopbackDevice::AclPacketLength,
&acl_channel_, BT_HCI_SNOOP_TYPE_ACL);
break;
case kHciSco:
ProcessNextUartPacketFromReadBuffer(sco_buffer_, sizeof(sco_buffer_), &sco_buffer_offset_,
&buf, end, &LoopbackDevice::ScoPacketLength,
&sco_channel_, BT_HCI_SNOOP_TYPE_SCO);
break;
default:
FX_LOGS(ERROR) << "unsupported HCI packet type " << cur_uart_packet_type_
<< " received. We may be out of sync";
cur_uart_packet_type_ = kHciNone;
return;
}
}
}
if (pending & ZX_CHANNEL_PEER_CLOSED) {
{
std::lock_guard guard(mutex_);
ChannelCleanupLocked(chan);
}
HciBeginShutdown();
}
}
void LoopbackDevice::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) {
size_t packet_length = (this->*get_packet_length)();
while (!packet_length && *uart_src < uart_end) {
// read until we have enough to compute packet length
buffer[*buffer_offset] = **uart_src;
(*buffer_offset)++;
(*uart_src)++;
packet_length = (this->*get_packet_length)();
}
// Out of bytes, but we still don't know the packet length. Just wait for
// the next packet.
if (!packet_length) {
return;
}
if (packet_length > buffer_size) {
FX_LOGS(ERROR) << "packet_length is too large (" << packet_length << " > " << buffer_size
<< ") during packet reassembly. Dropping and "
"attempting to re-sync.";
// Reset the reassembly state machine.
*buffer_offset = 1;
cur_uart_packet_type_ = kHciNone;
// Consume the rest of the UART buffer to indicate that it is corrupt.
*uart_src = uart_end;
return;
}
size_t remaining = uart_end - *uart_src;
size_t copy_size = packet_length - *buffer_offset;
if (copy_size > remaining) {
copy_size = remaining;
}
ZX_ASSERT(*buffer_offset + copy_size <= buffer_size);
memcpy(buffer + *buffer_offset, *uart_src, copy_size);
*uart_src += copy_size;
*buffer_offset += copy_size;
if (*buffer_offset != packet_length) {
// The packet is incomplete, the next chunk should continue the same packet.
return;
}
std::lock_guard guard(mutex_);
// Attempt to send this packet to the channel. Do so in the lock so we don't shut down while
// writing.
if (channel->is_valid()) {
zx_status_t status = channel->write(/*flags=*/0, &buffer[1], packet_length - 1, nullptr, 0);
if (status != ZX_OK) {
FX_LOGS(ERROR) << "failed to write packet: " << zx_status_get_string(status);
ChannelCleanupLocked(&acl_channel_);
}
}
// If the snoop channel is open then try to write the packet even if |channel| was closed.
SnoopChannelWriteLocked(bt_hci_snoop_flags(snoop_type, true), &buffer[1], packet_length - 1);
// reset buffer
cur_uart_packet_type_ = kHciNone;
*buffer_offset = 1;
}
void LoopbackDevice::HciHandleClientChannel(zx::channel* chan, zx_signals_t pending) {
FX_LOGS(TRACE) << "LoopbackDevice::HciHandleClientChannel";
// Channel may have been closed since signal was received.
if (!chan->is_valid()) {
FX_LOGS(ERROR) << "chan invalid";
return;
}
// Figure out which channel we are dealing with and the constants which go
// along with it.
uint32_t max_buf_size;
BtHciPacketIndicator packet_type;
bt_hci_snoop_type_t snoop_type;
const char* chan_name = nullptr;
if (chan == &cmd_channel_) {
max_buf_size = kCmdBufSize;
packet_type = kHciCommand;
snoop_type = BT_HCI_SNOOP_TYPE_CMD;
chan_name = "command";
} else if (chan == &acl_channel_) {
max_buf_size = kAclMaxFrameSize;
packet_type = kHciAclData;
snoop_type = BT_HCI_SNOOP_TYPE_ACL;
chan_name = "ACL";
} else if (chan == &sco_channel_) {
max_buf_size = kScoMaxFrameSize;
packet_type = kHciSco;
snoop_type = BT_HCI_SNOOP_TYPE_SCO;
chan_name = "SCO";
} else {
// This should never happen, we only know about three packet types currently.
ZX_ASSERT(false);
return;
}
FX_LOGS(TRACE) << "LoopbackDevice::HciHandleClientChannel handling " << chan_name;
// Handle the read signal first. If we are also peer closed, we want to make
// sure that we have processed all of the pending messages before cleaning up.
if (pending & ZX_CHANNEL_READABLE) {
zx_status_t status = ZX_OK;
uint32_t length = max_buf_size - 1;
{
std::lock_guard guard(mutex_);
status =
zx_channel_read(chan->get(), 0, write_buffer_ + 1, nullptr, length, 0, &length, nullptr);
if (status == ZX_ERR_SHOULD_WAIT) {
FX_LOGS(WARNING) << "ignoring ZX_ERR_SHOULD_WAIT when reading " << chan_name << " channel";
return;
}
if (status != ZX_OK) {
FX_LOGS(ERROR) << "hci_read_thread: failed to read from " << chan_name << " channel "
<< zx_status_get_string(status);
ChannelCleanupLocked(chan);
return;
}
write_buffer_[0] = packet_type;
length++;
SnoopChannelWriteLocked(bt_hci_snoop_flags(snoop_type, false), write_buffer_ + 1, length - 1);
if (in_channel_.is_valid()) {
status = in_channel_.write(/*flags=*/0, write_buffer_, length, nullptr, 0);
if (status != ZX_OK) {
FX_LOGS(ERROR) << "failed to write packet: " << zx_status_get_string(status);
}
}
}
if (status != ZX_OK) {
HciBeginShutdown();
}
}
if (pending & ZX_CHANNEL_PEER_CLOSED) {
FX_LOGS(DEBUG) << "received closed signal for " << chan_name << " channel";
std::lock_guard guard(mutex_);
ChannelCleanupLocked(chan);
}
}
void LoopbackDevice::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) {
FX_LOGS(TRACE) << "LoopbackDevice::BtHciConfigureSco";
// UART doesn't require any SCO configuration.
callback(cookie, ZX_OK);
}
void LoopbackDevice::BtHciResetSco(bt_hci_reset_sco_callback callback, void* cookie) {
FX_LOGS(TRACE) << "LoopbackDevice::BtHciResetSco";
// UART doesn't require any SCO configuration, so there's nothing to do.
callback(cookie, ZX_OK);
}
void LoopbackDevice::DdkUnbind(ddk::UnbindTxn txn) {
FX_LOGS(TRACE) << "LoopbackDevice::DdkUnbind";
// We are now shutting down. Make sure that any pending callbacks in
// flight from the serial_impl are nerfed and that our thread is shut down.
std::atomic_store_explicit(&shutting_down_, true, std::memory_order_relaxed);
{
std::lock_guard guard(mutex_);
// Close the transport channels so that the host stack is notified of device
// removal and tasks aren't posted to work thread.
ChannelCleanupLocked(&cmd_channel_);
ChannelCleanupLocked(&acl_channel_);
ChannelCleanupLocked(&sco_channel_);
ChannelCleanupLocked(&snoop_channel_);
ChannelCleanupLocked(&in_channel_);
}
if (loop_) {
loop_->Quit();
loop_->JoinThreads();
}
// Tell the DDK we are done unbinding.
txn.Reply();
}
void LoopbackDevice::DdkRelease() {
FX_LOGS(TRACE) << "LoopbackDevice::DdkRelease";
// Driver manager is given a raw pointer to this dynamically allocated object in Create(), so
// when DdkRelease() is called we need to free the allocated memory.
delete this;
}
void LoopbackDevice::OpenCommandChannel(OpenCommandChannelRequestView request,
OpenCommandChannelCompleter::Sync& completer) {
BtHciOpenCommandChannel(zx::channel(request->channel.release()));
}
void LoopbackDevice::OpenAclDataChannel(OpenAclDataChannelRequestView request,
OpenAclDataChannelCompleter::Sync& completer) {
BtHciOpenAclDataChannel(zx::channel(request->channel.release()));
}
void LoopbackDevice::OpenSnoopChannel(OpenSnoopChannelRequestView request,
OpenSnoopChannelCompleter::Sync& completer) {
BtHciOpenSnoopChannel(zx::channel(request->channel.release()));
}
zx_status_t LoopbackDevice::DdkGetProtocol(uint32_t proto_id, void* out_proto) {
if (proto_id != ZX_PROTOCOL_BT_HCI) {
return ZX_ERR_NOT_SUPPORTED;
}
bt_hci_protocol_t* hci_proto = static_cast<bt_hci_protocol*>(out_proto);
hci_proto->ops = &bt_hci_protocol_ops_;
hci_proto->ctx = this;
return ZX_OK;
}
zx_status_t LoopbackDevice::Bind(zx_handle_t channel, std::string_view name) {
FX_LOGS(TRACE) << "LoopbackDevice::Bind";
zx_status_t result = ZX_OK;
{
std::lock_guard guard(mutex_);
// pre-populate event packet indicators
event_buffer_[0] = kHciEvent;
event_buffer_offset_ = 1;
acl_buffer_[0] = kHciAclData;
acl_buffer_offset_ = 1;
sco_buffer_[0] = kHciSco;
sco_buffer_offset_ = 1;
}
// Spawn a new thread in production. In tests, use the test dispatcher provided in the
// constructor.
if (!dispatcher_) {
loop_.emplace(&kAsyncLoopConfigNoAttachToCurrentThread);
result = loop_->StartThread("bt-loopback-device");
if (result != ZX_OK) {
FX_LOGS(ERROR) << "failed to start thread: " << zx_status_get_string(result);
DdkRelease();
return result;
}
dispatcher_ = loop_->dispatcher();
}
{
FX_LOGS(TRACE) << "LoopbackDevice::Bind setup in channel waiter";
std::lock_guard guard(mutex_);
// Setup up incoming channel waiter.
in_channel_.reset(channel);
in_channel_wait_.object = in_channel_.get();
in_channel_wait_.trigger = ZX_CHANNEL_READABLE | ZX_CHANNEL_PEER_CLOSED;
ZX_ASSERT(async_begin_wait(dispatcher_, &in_channel_wait_) == ZX_OK);
in_channel_wait_.pending = true;
}
ddk::DeviceAddArgs args(name.data());
args.set_proto_id(ZX_PROTOCOL_BT_HCI);
return DdkAdd(args);
}
} // namespace bt_hci_virtual