blob: c184a1ff332756a16f597340ac836245a07a336c [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 "command_channel.h"
#include <endian.h>
#include <zircon/status.h>
#include "garnet/drivers/bluetooth/lib/common/run_task_sync.h"
#include "lib/fxl/functional/auto_call.h"
#include "lib/fxl/functional/make_copyable.h"
#include "lib/fxl/logging.h"
#include "lib/fxl/strings/string_printf.h"
#include "lib/fxl/time/time_delta.h"
#include "slab_allocators.h"
#include "transport.h"
namespace bluetooth {
namespace hci {
CommandChannel::QueuedCommand::QueuedCommand(
TransactionId id,
std::unique_ptr<CommandPacket> command_packet,
const CommandStatusCallback& status_callback,
const CommandCompleteCallback& complete_callback,
fxl::RefPtr<fxl::TaskRunner> task_runner,
const EventCode complete_event_code,
const EventMatcher& complete_event_matcher) {
transaction_data.id = id;
transaction_data.opcode = command_packet->opcode();
transaction_data.complete_event_code = complete_event_code;
transaction_data.complete_event_matcher = complete_event_matcher;
transaction_data.status_callback = status_callback;
transaction_data.complete_callback = complete_callback;
transaction_data.task_runner = task_runner;
packet = std::move(command_packet);
}
CommandChannel::CommandChannel(Transport* transport,
zx::channel hci_command_channel)
: next_transaction_id_(1u),
next_event_handler_id_(1u),
transport_(transport),
channel_(std::move(hci_command_channel)),
channel_wait_(channel_.get(), ZX_CHANNEL_READABLE),
is_initialized_(false) {
FXL_DCHECK(transport_);
FXL_DCHECK(channel_.is_valid());
}
CommandChannel::~CommandChannel() {
ShutDown();
}
void CommandChannel::Initialize() {
FXL_DCHECK(thread_checker_.IsCreationThreadCurrent());
FXL_DCHECK(!is_initialized_);
auto setup_handler_task = [this] {
channel_wait_.set_handler(
fbl::BindMember(this, &CommandChannel::OnChannelReady));
zx_status_t status =
channel_wait_.Begin(fsl::MessageLoop::GetCurrent()->async());
if (status != ZX_OK) {
FXL_LOG(ERROR) << "hci: CommandChannel: failed channel setup: "
<< zx_status_get_string(status);
channel_wait_.set_object(ZX_HANDLE_INVALID);
return;
}
FXL_LOG(INFO) << "hci: CommandChannel: started I/O handler";
};
io_task_runner_ = transport_->io_task_runner();
common::RunTaskSync(setup_handler_task, io_task_runner_);
if (channel_wait_.object() == ZX_HANDLE_INVALID)
return;
is_initialized_ = true;
FXL_LOG(INFO) << "hci: CommandChannel: initialized";
}
void CommandChannel::ShutDown() {
FXL_DCHECK(thread_checker_.IsCreationThreadCurrent());
if (!is_initialized_)
return;
FXL_LOG(INFO) << "hci: CommandChannel: shutting down";
auto handler_cleanup_task = [this] {
FXL_DCHECK(fsl::MessageLoop::GetCurrent());
FXL_LOG(INFO) << "hci: CommandChannel: Removing I/O handler";
SetPendingCommand(nullptr);
zx_status_t status =
channel_wait_.Cancel(fsl::MessageLoop::GetCurrent()->async());
if (status != ZX_OK) {
FXL_LOG(WARNING) << "Couldn't cancel wait on channel: "
<< zx_status_get_string(status);
}
};
common::RunTaskSync(handler_cleanup_task, io_task_runner_);
is_initialized_ = false;
{
std::lock_guard<std::mutex> lock(send_queue_mutex_);
send_queue_ = std::queue<QueuedCommand>();
}
{
std::lock_guard<std::mutex> lock(event_handler_mutex_);
event_handler_id_map_.clear();
event_code_handlers_.clear();
subevent_code_handlers_.clear();
}
io_task_runner_ = nullptr;
}
CommandChannel::TransactionId CommandChannel::SendCommand(
std::unique_ptr<CommandPacket> command_packet,
fxl::RefPtr<fxl::TaskRunner> task_runner,
const CommandCompleteCallback& complete_callback,
const CommandStatusCallback& status_callback,
const EventCode complete_event_code,
const EventMatcher& complete_event_matcher) {
if (!is_initialized_) {
FXL_VLOG(1)
<< "hci: CommandChannel: Cannot send commands while uninitialized";
return 0u;
}
FXL_DCHECK(command_packet);
std::lock_guard<std::mutex> lock(send_queue_mutex_);
if (next_transaction_id_ == 0u)
next_transaction_id_++;
TransactionId id = next_transaction_id_++;
send_queue_.push(QueuedCommand(id, std::move(command_packet), status_callback,
complete_callback, task_runner,
complete_event_code, complete_event_matcher));
io_task_runner_->PostTask(
std::bind(&CommandChannel::TrySendNextQueuedCommand, this));
return id;
}
CommandChannel::EventHandlerId CommandChannel::AddEventHandler(
EventCode event_code,
const EventCallback& event_callback,
fxl::RefPtr<fxl::TaskRunner> task_runner) {
FXL_DCHECK(event_code != kCommandStatusEventCode);
FXL_DCHECK(event_code != kCommandCompleteEventCode);
FXL_DCHECK(event_code != kLEMetaEventCode);
std::lock_guard<std::mutex> lock(event_handler_mutex_);
if (event_code_handlers_.find(event_code) != event_code_handlers_.end()) {
FXL_LOG(ERROR) << "hci: event handler already registered for event code: "
<< fxl::StringPrintf("0x%02x", event_code);
return 0u;
}
auto id = NewEventHandler(event_code, false /* is_le_meta */, event_callback,
task_runner);
event_code_handlers_[event_code] = id;
return id;
}
CommandChannel::EventHandlerId CommandChannel::AddLEMetaEventHandler(
EventCode subevent_code,
const EventCallback& event_callback,
fxl::RefPtr<fxl::TaskRunner> task_runner) {
std::lock_guard<std::mutex> lock(event_handler_mutex_);
if (subevent_code_handlers_.find(subevent_code) !=
subevent_code_handlers_.end()) {
FXL_LOG(ERROR)
<< "hci: event handler already registered for LE Meta subevent code: "
<< fxl::StringPrintf("0x%02x", subevent_code);
return 0u;
}
auto id = NewEventHandler(subevent_code, true /* is_le_meta */,
event_callback, task_runner);
subevent_code_handlers_[subevent_code] = id;
return id;
}
void CommandChannel::RemoveEventHandler(const EventHandlerId id) {
std::lock_guard<std::mutex> lock(event_handler_mutex_);
auto iter = event_handler_id_map_.find(id);
if (iter == event_handler_id_map_.end())
return;
if (iter->second.event_code != 0) {
if (iter->second.is_le_meta_subevent) {
subevent_code_handlers_.erase(iter->second.event_code);
} else {
event_code_handlers_.erase(iter->second.event_code);
}
}
event_handler_id_map_.erase(iter);
}
void CommandChannel::TrySendNextQueuedCommand() {
if (!is_initialized_)
return;
FXL_DCHECK(io_task_runner_->RunsTasksOnCurrentThread());
// If a command is currently pending, then we have nothing to do.
if (GetPendingCommand())
return;
QueuedCommand cmd;
{
std::lock_guard<std::mutex> lock(send_queue_mutex_);
if (send_queue_.empty())
return;
cmd = std::move(send_queue_.front());
send_queue_.pop();
}
auto packet_bytes = cmd.packet->view().data();
zx_status_t status =
channel_.write(0, packet_bytes.data(), packet_bytes.size(), nullptr, 0);
if (status < 0) {
// TODO(armansito): We should notify the |status_callback| of the pending
// command with a special error code in this case.
FXL_LOG(ERROR) << "hci: CommandChannel: Failed to send command: "
<< zx_status_get_string(status);
return;
}
SetPendingCommand(&cmd.transaction_data);
// Set a callback to execute if this HCI command times out (i.e the controller
// does not send back a response in time). Once the command is completed (due
// to HCI_Command_Status or the corresponding completion callback) this
// timeout callback will be cancelled when SetPendingCommand() is called to
// clear the pending command.
pending_cmd_timeout_.Reset([ this, id = cmd.transaction_data.id ] {
auto pending_cmd = GetPendingCommand();
// If this callback is ever invoked then the command that timed out should
// still be pending.
FXL_DCHECK(pending_cmd);
FXL_DCHECK(pending_cmd->id == id);
if (pending_cmd->status_callback) {
pending_cmd->task_runner->PostTask(
std::bind(pending_cmd->status_callback, id, Status::kCommandTimeout));
}
SetPendingCommand(nullptr);
TrySendNextQueuedCommand();
});
io_task_runner_->PostDelayedTask(
pending_cmd_timeout_.callback(),
fxl::TimeDelta::FromMilliseconds(kCommandTimeoutMs));
}
bool CommandChannel::HandlePendingCommandComplete(
std::unique_ptr<EventPacket>&& event) {
FXL_DCHECK(io_task_runner_->RunsTasksOnCurrentThread());
PendingTransactionData* pending_command = GetPendingCommand();
FXL_DCHECK(pending_command);
FXL_DCHECK(event->event_code() == pending_command->complete_event_code);
// In case that this is a CommandComplete event, make sure that the command
// opcode actually matches the pending command.
if (event->event_code() == kCommandCompleteEventCode &&
le16toh(
event->view().payload<CommandCompleteEventParams>().command_opcode) !=
pending_command->opcode) {
FXL_LOG(ERROR) << fxl::StringPrintf(
"hci: CommandChannel: Unmatched CommandComplete event - opcode: "
"0x%04x, pending: 0x%04x",
le16toh(
event->view().payload<CommandCompleteEventParams>().command_opcode),
pending_command->opcode);
return false;
}
// In case that this is a CommandComplete event, make sure that the command
// opcode actually matches the pending command.
if (event->event_code() == kCommandStatusEventCode &&
le16toh(
event->view().payload<CommandStatusEventParams>().command_opcode) !=
pending_command->opcode) {
FXL_LOG(ERROR) << fxl::StringPrintf(
"hci: CommandChannel: Unmatched CommandStatus event - opcode: "
"0x%04x, pending: 0x%04x",
le16toh(
event->view().payload<CommandStatusEventParams>().command_opcode),
pending_command->opcode);
return false;
}
// Do not handle the event if it does not pass the matcher (if one was
// provided).
if (pending_command->complete_event_matcher &&
!pending_command->complete_event_matcher(*event)) {
return false;
}
// Clear the pending command and process the next queued command when this
// goes out of scope.
auto ac = fxl::MakeAutoCall([this] {
SetPendingCommand(nullptr);
TrySendNextQueuedCommand();
});
// If no command complete callback was provided, then the caller does not care
// about the result.
if (!pending_command->complete_callback)
return true;
// If the command callback needs to run on the I/O thread (i.e. the current
// thread), then no need for an async task; invoke the callback immediately.
if (pending_command->task_runner->RunsTasksOnCurrentThread()) {
pending_command->complete_callback(pending_command->id, *event);
return true;
}
pending_command->task_runner->PostTask(fxl::MakeCopyable([
event = std::move(event),
complete_callback = pending_command->complete_callback,
transaction_id = pending_command->id
]() mutable { complete_callback(transaction_id, *event); }));
return true;
}
void CommandChannel::HandlePendingCommandStatus(const EventPacket& event) {
FXL_DCHECK(io_task_runner_->RunsTasksOnCurrentThread());
PendingTransactionData* pending_command = GetPendingCommand();
FXL_DCHECK(pending_command);
FXL_DCHECK(event.event_code() == kCommandStatusEventCode);
FXL_DCHECK(pending_command->complete_event_code != kCommandStatusEventCode);
// Make sure that the command opcode actually matches the pending command.
if (le16toh(
event.view().payload<CommandStatusEventParams>().command_opcode) !=
pending_command->opcode) {
FXL_LOG(ERROR) << "hci: CommandChannel: Unmatched CommandStatus event";
return;
}
if (pending_command->status_callback) {
auto status_cb =
std::bind(pending_command->status_callback, pending_command->id,
static_cast<Status>(
event.view().payload<CommandStatusEventParams>().status));
// If the command callback needs to run on the I/O thread, then invoke it
// immediately.
if (pending_command->task_runner->RunsTasksOnCurrentThread()) {
status_cb();
} else {
pending_command->task_runner->PostTask(status_cb);
}
}
// Success in this case means that the command will be completed later when we
// receive an event that matches |pending_command->complete_event_code|.
if (event.view().payload<CommandStatusEventParams>().status ==
Status::kSuccess)
return;
// A CommandStatus event with an error status usually means that the command
// that was in progress could not be executed. Complete the transaction and
// move on to the next queued command.
SetPendingCommand(nullptr);
TrySendNextQueuedCommand();
}
CommandChannel::PendingTransactionData* CommandChannel::GetPendingCommand() {
FXL_DCHECK(io_task_runner_->RunsTasksOnCurrentThread());
return pending_command_.value();
}
void CommandChannel::SetPendingCommand(PendingTransactionData* command) {
FXL_DCHECK(io_task_runner_->RunsTasksOnCurrentThread());
// Cancel the pending command timeout handler as the pending command is being
// reset.
pending_cmd_timeout_.Cancel();
if (!command) {
pending_command_.Reset();
return;
}
FXL_DCHECK(!pending_command_);
pending_command_ = *command;
}
CommandChannel::EventHandlerId CommandChannel::NewEventHandler(
EventCode event_code,
bool is_le_meta,
const EventCallback& event_callback,
fxl::RefPtr<fxl::TaskRunner> task_runner) {
FXL_DCHECK(event_code);
FXL_DCHECK(event_callback);
FXL_DCHECK(task_runner);
auto id = next_event_handler_id_++;
EventHandlerData data;
data.id = id;
data.event_code = event_code;
data.event_callback = event_callback;
data.task_runner = task_runner;
data.is_le_meta_subevent = is_le_meta;
FXL_DCHECK(event_handler_id_map_.find(id) == event_handler_id_map_.end());
event_handler_id_map_[id] = data;
return id;
}
void CommandChannel::NotifyEventHandler(std::unique_ptr<EventPacket> event) {
FXL_DCHECK(io_task_runner_->RunsTasksOnCurrentThread());
// Ignore HCI_CommandComplete and HCI_CommandStatus events.
if (event->event_code() == kCommandCompleteEventCode ||
event->event_code() == kCommandStatusEventCode) {
FXL_LOG(ERROR) << "hci: Ignoring unhandled "
<< (event->event_code() == kCommandCompleteEventCode
? "HCI_CommandComplete"
: "HCI_CommandStatus")
<< " event";
return;
}
std::lock_guard<std::mutex> lock(event_handler_mutex_);
EventCode event_code;
const std::unordered_map<EventCode, EventHandlerId>* event_handlers;
if (event->event_code() == kLEMetaEventCode) {
event_code = event->view().payload<LEMetaEventParams>().subevent_code;
event_handlers = &subevent_code_handlers_;
} else {
event_code = event->event_code();
event_handlers = &event_code_handlers_;
}
auto iter = event_handlers->find(event_code);
if (iter == event_handlers->end()) {
// No handler registered for event.
return;
}
auto handler_iter = event_handler_id_map_.find(iter->second);
FXL_DCHECK(handler_iter != event_handler_id_map_.end());
auto& handler = handler_iter->second;
FXL_DCHECK(handler.event_code == event_code);
// If the given task runner is the I/O task runner, then immediately execute
// the callback as there is no need to delay the execution.
if (handler.task_runner.get() == io_task_runner_.get()) {
handler.event_callback(*event);
return;
}
// Post the event on the requested task runner.
handler.task_runner->PostTask(fxl::MakeCopyable([
event = std::move(event), event_callback = handler.event_callback
]() mutable { event_callback(*event); }));
}
async_wait_result_t CommandChannel::OnChannelReady(
async_t* async,
zx_status_t status,
const zx_packet_signal_t* signal) {
FXL_DCHECK(io_task_runner_->RunsTasksOnCurrentThread());
FXL_DCHECK(signal->observed & ZX_CHANNEL_READABLE);
if (status != ZX_OK) {
FXL_VLOG(1) << "hci: CommandChannel: channel error: "
<< zx_status_get_string(status);
return ASYNC_WAIT_FINISHED;
}
// Allocate a buffer for the event. Since we don't know the size beforehand we
// allocate the largest possible buffer.
// TODO(armansito): We could first try to read into a small buffer and retry
// if the syscall returns ZX_ERR_BUFFER_TOO_SMALL. Not sure if the second
// syscall would be worth it but investigate.
for (size_t count = 0; count < signal->count; count++) {
uint32_t read_size;
auto packet = EventPacket::New(slab_allocators::kLargeControlPayloadSize);
if (!packet) {
FXL_LOG(ERROR) << "Failed to allocate event packet!";
return ASYNC_WAIT_FINISHED;
}
auto packet_bytes = packet->mutable_view()->mutable_data();
zx_status_t read_status =
channel_.read(0u, packet_bytes.mutable_data(), packet_bytes.size(),
&read_size, nullptr, 0, nullptr);
if (read_status < 0) {
FXL_VLOG(1) << "hci: CommandChannel: Failed to read event bytes: "
<< zx_status_get_string(read_status);
// Clear the handler so that we stop receiving events from it.
// TODO(jamuraa): signal upper layers that we can't read the channel.
return ASYNC_WAIT_FINISHED;
}
if (read_size < sizeof(EventHeader)) {
FXL_LOG(ERROR) << "hci: CommandChannel: Malformed event packet - "
<< "expected at least " << sizeof(EventHeader)
<< " bytes, "
<< "got " << read_size;
// TODO(armansito): Should this be fatal? Ignore for now.
continue;
}
// Compare the received payload size to what is in the header.
const size_t rx_payload_size = read_size - sizeof(EventHeader);
const size_t size_from_header =
packet->view().header().parameter_total_size;
if (size_from_header != rx_payload_size) {
FXL_LOG(ERROR) << "hci: CommandChannel: Malformed event packet - "
<< "payload size from header (" << size_from_header << ")"
<< " does not match received payload size: "
<< rx_payload_size;
continue;
}
packet->InitializeFromBuffer();
// Check to see if this event is in response to the currently pending
// command.
PendingTransactionData* pending_command = GetPendingCommand();
if (pending_command) {
if (pending_command->complete_event_code == packet->event_code()) {
if (HandlePendingCommandComplete(std::move(packet)))
continue;
// |packet| should not have been moved in this case. It will be accessed
// below.
FXL_DCHECK(packet);
} else if (packet->event_code() == kCommandStatusEventCode) {
HandlePendingCommandStatus(*packet);
continue;
}
// Fall through if the event did not match the currently pending command.
}
// The event did not match a pending command OR no command is currently
// pending. Notify the upper layers.
NotifyEventHandler(std::move(packet));
}
return ASYNC_WAIT_AGAIN;
}
} // namespace hci
} // namespace bluetooth