blob: bc0c37e29399205acd22036c649bc2ff00b0ec89 [file] [log] [blame]
// Copyright 2017 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 "transport.h"
#include <lib/async/default.h>
#include <lib/zx/channel.h>
#include <zircon/assert.h>
#include <zircon/status.h>
#include "src/connectivity/bluetooth/core/bt-host/common/log.h"
#include "device_wrapper.h"
namespace bt {
namespace hci {
// static
fxl::RefPtr<Transport> Transport::Create(
std::unique_ptr<DeviceWrapper> hci_device) {
return AdoptRef(new Transport(std::move(hci_device)));
}
Transport::Transport(std::unique_ptr<DeviceWrapper> hci_device)
: hci_device_(std::move(hci_device)),
is_initialized_(false),
io_dispatcher_(nullptr),
closed_cb_dispatcher_(nullptr) {
ZX_DEBUG_ASSERT(hci_device_);
}
Transport::~Transport() {
// Do nothing. Since Transport is shared across threads, this can be called
// from any thread and calling ShutDown() would be unsafe.
}
bool Transport::Initialize(async_dispatcher_t* dispatcher) {
ZX_DEBUG_ASSERT(thread_checker_.IsCreationThreadCurrent());
ZX_DEBUG_ASSERT(hci_device_);
ZX_DEBUG_ASSERT(!command_channel_);
ZX_DEBUG_ASSERT(!acl_data_channel_);
ZX_DEBUG_ASSERT(!IsInitialized());
// Obtain command channel handle.
zx::channel channel = hci_device_->GetCommandChannel();
if (!channel.is_valid()) {
bt_log(ERROR, "hci", "failed to obtain command channel handle");
return false;
}
if (dispatcher) {
io_dispatcher_ = dispatcher;
} else {
io_loop_ = std::make_unique<async::Loop>(&kAsyncLoopConfigNoAttachToThread);
io_loop_->StartThread("hci-transport-io");
io_dispatcher_ = io_loop_->dispatcher();
}
// We watch for handle errors and closures to perform the necessary clean up.
WatchChannelClosed(channel, cmd_channel_wait_);
command_channel_ = std::make_unique<CommandChannel>(this, std::move(channel));
command_channel_->Initialize();
is_initialized_ = true;
return true;
}
bool Transport::InitializeACLDataChannel(
const DataBufferInfo& bredr_buffer_info,
const DataBufferInfo& le_buffer_info) {
ZX_DEBUG_ASSERT(hci_device_);
ZX_DEBUG_ASSERT(IsInitialized());
// Obtain ACL data channel handle.
zx::channel channel = hci_device_->GetACLDataChannel();
if (!channel.is_valid()) {
bt_log(ERROR, "hci", "failed to obtain ACL data channel handle");
return false;
}
// We watch for handle errors and closures to perform the necessary clean up.
WatchChannelClosed(channel, acl_channel_wait_);
acl_data_channel_ =
std::make_unique<ACLDataChannel>(this, std::move(channel));
acl_data_channel_->Initialize(bredr_buffer_info, le_buffer_info);
return true;
}
void Transport::SetTransportClosedCallback(
fit::closure callback,
async_dispatcher_t* dispatcher) {
ZX_DEBUG_ASSERT(callback);
ZX_DEBUG_ASSERT(dispatcher);
ZX_DEBUG_ASSERT(!closed_cb_);
ZX_DEBUG_ASSERT(!closed_cb_dispatcher_);
closed_cb_ = std::move(callback);
closed_cb_dispatcher_ = dispatcher;
}
void Transport::ShutDown() {
ZX_DEBUG_ASSERT(thread_checker_.IsCreationThreadCurrent());
ZX_DEBUG_ASSERT(IsInitialized());
bt_log(INFO, "hci", "transport shutting down");
if (acl_data_channel_) {
acl_data_channel_->ShutDown();
}
if (command_channel_) {
command_channel_->ShutDown();
}
async::PostTask(io_dispatcher_, [this] {
cmd_channel_wait_.Cancel();
if (acl_data_channel_) {
acl_channel_wait_.Cancel();
}
if (io_loop_) {
io_loop_->Quit();
}
});
if (io_loop_) {
io_loop_->JoinThreads();
}
// We avoid deallocating the channels here as they *could* still be accessed
// by other threads. It's OK to clear |io_dispatcher_| as the channels hold
// their own references to it.
//
// Once |io_loop_| joins above, |io_dispatcher_| may be defunct. However,
// the channels are allowed to keep posting tasks on it (which will never
// execute).
io_dispatcher_ = nullptr;
is_initialized_ = false;
bt_log(INFO, "hci", "I/O loop exited");
}
bool Transport::IsInitialized() const {
return is_initialized_;
}
void Transport::WatchChannelClosed(const zx::channel& channel,
Waiter& wait) {
async::PostTask(io_dispatcher_,
[handle = channel.get(), &wait, this, ref = fxl::Ref(this)] {
wait.set_object(handle);
wait.set_trigger(ZX_CHANNEL_PEER_CLOSED);
zx_status_t status = wait.Begin(async_get_default_dispatcher());
if (status != ZX_OK) {
bt_log(ERROR, "hci", "failed to set up closed handler: %s",
zx_status_get_string(status));
wait.set_object(ZX_HANDLE_INVALID);
}
});
}
void Transport::OnChannelClosed(
async_dispatcher_t* dispatcher,
async::WaitBase* wait,
zx_status_t status,
const zx_packet_signal_t* signal) {
if (status != ZX_OK) {
bt_log(ERROR, "hci", "channel error: %s", zx_status_get_string(status));
} else {
ZX_DEBUG_ASSERT(signal->observed & ZX_CHANNEL_PEER_CLOSED);
}
NotifyClosedCallback();
}
void Transport::NotifyClosedCallback() {
// Clear the handlers so that we stop receiving events.
cmd_channel_wait_.Cancel();
if (acl_data_channel_) {
acl_channel_wait_.Cancel();
}
bt_log(INFO, "hci", "channel(s) were closed");
if (closed_cb_) {
async::PostTask(closed_cb_dispatcher_, closed_cb_.share());
}
}
} // namespace hci
} // namespace bt