| // 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 <lib/async/default.h> |
| #include <lib/trace/event.h> |
| #include <zircon/assert.h> |
| #include <zircon/status.h> |
| |
| #include "slab_allocators.h" |
| #include "src/connectivity/bluetooth/core/bt-host/common/log.h" |
| #include "src/connectivity/bluetooth/core/bt-host/common/run_or_post.h" |
| #include "src/connectivity/bluetooth/core/bt-host/common/run_task_sync.h" |
| #include "src/lib/fxl/strings/string_printf.h" |
| #include "transport.h" |
| |
| namespace bt::hci { |
| |
| namespace { |
| |
| bool IsAsync(EventCode code) { |
| return code != kCommandCompleteEventCode && code != kCommandStatusEventCode; |
| } |
| |
| } // namespace |
| |
| CommandChannel::QueuedCommand::QueuedCommand(std::unique_ptr<CommandPacket> command_packet, |
| std::unique_ptr<TransactionData> transaction_data) |
| : packet(std::move(command_packet)), data(std::move(transaction_data)) { |
| ZX_DEBUG_ASSERT(data); |
| ZX_DEBUG_ASSERT(packet); |
| } |
| |
| CommandChannel::TransactionData::TransactionData(TransactionId id, OpCode opcode, |
| EventCode complete_event_code, |
| std::optional<EventCode> subevent_code, |
| std::unordered_set<OpCode> exclusions, |
| CommandCallback callback) |
| : id_(id), |
| opcode_(opcode), |
| complete_event_code_(complete_event_code), |
| subevent_code_(subevent_code), |
| exclusions_(std::move(exclusions)), |
| callback_(std::move(callback)), |
| handler_id_(0u) { |
| ZX_DEBUG_ASSERT(id != 0u); |
| exclusions_.insert(opcode_); |
| } |
| |
| CommandChannel::TransactionData::~TransactionData() { |
| if (!callback_) { |
| return; |
| } |
| |
| bt_log(DEBUG, "hci", "sending kUnspecifiedError for unfinished transaction %zu", id_); |
| auto event = EventPacket::New(sizeof(CommandStatusEventParams)); |
| // Init buffer to prevent stale data in buffer. |
| event->mutable_view()->mutable_data().SetToZeros(); |
| |
| auto* header = event->mutable_view()->mutable_header(); |
| auto* params = event->mutable_view()->mutable_payload<CommandStatusEventParams>(); |
| |
| // TODO(armansito): Instead of lying about receiving a Command Status event, |
| // report this error in a different way. This can be highly misleading during |
| // debugging. |
| header->event_code = kCommandStatusEventCode; |
| header->parameter_total_size = sizeof(CommandStatusEventParams); |
| params->status = kUnspecifiedError; |
| params->command_opcode = opcode_; |
| |
| Complete(std::move(event)); |
| } |
| |
| void CommandChannel::TransactionData::Start(fit::closure timeout_cb, zx::duration timeout) { |
| // Transactions should only ever be started once. |
| ZX_DEBUG_ASSERT(!timeout_task_.is_pending()); |
| |
| timeout_task_.set_handler(std::move(timeout_cb)); |
| timeout_task_.PostDelayed(async_get_default_dispatcher(), timeout); |
| } |
| |
| void CommandChannel::TransactionData::Complete(std::unique_ptr<EventPacket> event) { |
| timeout_task_.Cancel(); |
| if (!callback_) { |
| return; |
| } |
| async::PostTask(async_get_default_dispatcher(), |
| [event = std::move(event), callback = std::move(callback_), |
| transaction_id = id_]() mutable { callback(transaction_id, *event); }); |
| callback_ = nullptr; |
| } |
| |
| void CommandChannel::TransactionData::Cancel() { |
| timeout_task_.Cancel(); |
| callback_ = nullptr; |
| } |
| |
| CommandChannel::EventCallback CommandChannel::TransactionData::MakeCallback() { |
| return [id = id_, cb = callback_.share()](const EventPacket& event) { |
| cb(id, event); |
| return CommandChannel::EventCallbackResult::kContinue; |
| }; |
| } |
| |
| fit::result<std::unique_ptr<CommandChannel>> CommandChannel::Create( |
| Transport* transport, zx::channel hci_command_channel) { |
| auto channel = std::unique_ptr<CommandChannel>( |
| new CommandChannel(transport, std::move(hci_command_channel))); |
| |
| if (!channel->is_initialized_) { |
| return fit::error(); |
| } |
| return fit::ok(std::move(channel)); |
| } |
| |
| 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_(this, channel_.get(), ZX_CHANNEL_READABLE), |
| is_initialized_(false), |
| allowed_command_packets_(1u), |
| weak_ptr_factory_(this) { |
| ZX_ASSERT(transport_); |
| ZX_ASSERT(channel_.is_valid()); |
| |
| zx_status_t status = channel_wait_.Begin(async_get_default_dispatcher()); |
| if (status != ZX_OK) { |
| bt_log(ERROR, "hci", "failed channel setup: %s", zx_status_get_string(status)); |
| channel_wait_.set_object(ZX_HANDLE_INVALID); |
| return; |
| } |
| bt_log(INFO, "hci", "initialized"); |
| |
| is_initialized_ = true; |
| } |
| |
| CommandChannel::~CommandChannel() { |
| // Do nothing. Since Transport is shared across threads, this can be called |
| // from any thread and calling ShutDown() would be unsafe. |
| } |
| |
| void CommandChannel::ShutDown() { |
| ZX_DEBUG_ASSERT(thread_checker_.is_thread_valid()); |
| if (!is_initialized_) |
| return; |
| |
| bt_log(INFO, "hci", "shutting down"); |
| |
| ShutDownInternal(); |
| } |
| |
| void CommandChannel::ShutDownInternal() { |
| bt_log(DEBUG, "hci", "removing I/O handler"); |
| |
| // Prevent new command packets from being queued. |
| is_initialized_ = false; |
| |
| // Stop listening for HCI events. |
| zx_status_t status = channel_wait_.Cancel(); |
| if (status != ZX_OK) { |
| bt_log(WARN, "hci", "could not cancel wait on channel: %s", zx_status_get_string(status)); |
| } |
| |
| // Drop all queued commands and event handlers. Pending HCI commands will be |
| // resolved with an "UnspecifiedError" error code upon destruction. |
| send_queue_ = std::list<QueuedCommand>(); |
| event_handler_id_map_.clear(); |
| event_code_handlers_.clear(); |
| subevent_code_handlers_.clear(); |
| pending_transactions_.clear(); |
| } |
| |
| CommandChannel::TransactionId CommandChannel::SendCommand( |
| std::unique_ptr<CommandPacket> command_packet, CommandCallback callback, |
| const EventCode complete_event_code) { |
| return SendExclusiveCommand(std::move(command_packet), std::move(callback), complete_event_code); |
| } |
| |
| CommandChannel::TransactionId CommandChannel::SendLeAsyncCommand( |
| std::unique_ptr<CommandPacket> command_packet, CommandCallback callback, |
| EventCode le_meta_subevent_code) { |
| return SendLeAsyncExclusiveCommand(std::move(command_packet), std::move(callback), |
| le_meta_subevent_code); |
| } |
| |
| CommandChannel::TransactionId CommandChannel::SendExclusiveCommand( |
| std::unique_ptr<CommandPacket> command_packet, CommandCallback callback, |
| const EventCode complete_event_code, std::unordered_set<OpCode> exclusions) { |
| return SendExclusiveCommandInternal(std::move(command_packet), std::move(callback), |
| complete_event_code, std::nullopt, std::move(exclusions)); |
| } |
| |
| CommandChannel::TransactionId CommandChannel::SendLeAsyncExclusiveCommand( |
| std::unique_ptr<CommandPacket> command_packet, CommandCallback callback, |
| std::optional<EventCode> le_meta_subevent_code, std::unordered_set<OpCode> exclusions) { |
| return SendExclusiveCommandInternal(std::move(command_packet), std::move(callback), |
| hci::kLEMetaEventCode, le_meta_subevent_code, |
| std::move(exclusions)); |
| } |
| |
| CommandChannel::TransactionId CommandChannel::SendExclusiveCommandInternal( |
| std::unique_ptr<CommandPacket> command_packet, CommandCallback callback, |
| const EventCode complete_event_code, std::optional<EventCode> subevent_code, |
| std::unordered_set<OpCode> exclusions) { |
| if (!is_initialized_) { |
| bt_log(DEBUG, "hci", "can't send commands while uninitialized"); |
| return 0u; |
| } |
| |
| ZX_ASSERT(command_packet); |
| ZX_ASSERT_MSG((complete_event_code == hci::kLEMetaEventCode) == subevent_code.has_value(), |
| "only LE Meta Event subevents are supported"); |
| |
| if (IsAsync(complete_event_code)) { |
| // Cannot send an asynchronous command if there's an external event handler |
| // registered for the completion event. |
| EventHandlerData* handler = nullptr; |
| if (subevent_code.has_value()) { |
| handler = FindLEMetaEventHandler(*subevent_code); |
| } else { |
| handler = FindEventHandler(complete_event_code); |
| } |
| |
| if (handler && !handler->is_async()) { |
| bt_log(DEBUG, "hci", "event handler already handling this event"); |
| return 0u; |
| } |
| } |
| |
| if (next_transaction_id_ == 0u) { |
| next_transaction_id_++; |
| } |
| |
| TransactionId id = next_transaction_id_++; |
| auto data = |
| std::make_unique<TransactionData>(id, command_packet->opcode(), complete_event_code, |
| subevent_code, std::move(exclusions), std::move(callback)); |
| |
| QueuedCommand command(std::move(command_packet), std::move(data)); |
| |
| if (IsAsync(complete_event_code)) { |
| MaybeAddTransactionHandler(command.data.get()); |
| } |
| |
| send_queue_.push_back(std::move(command)); |
| |
| async::PostTask(async_get_default_dispatcher(), |
| std::bind(&CommandChannel::TrySendQueuedCommands, this)); |
| |
| return id; |
| } |
| |
| bool CommandChannel::RemoveQueuedCommand(TransactionId id) { |
| ZX_ASSERT(id != 0u); |
| |
| auto it = std::find_if(send_queue_.begin(), send_queue_.end(), |
| [id](const auto& cmd) { return cmd.data->id() == id; }); |
| if (it != send_queue_.end()) { |
| bt_log(TRACE, "hci", "removing queued command id: %zu", id); |
| TransactionData& data = *it->data; |
| data.Cancel(); |
| if (data.handler_id() != 0u) { |
| RemoveEventHandlerInternal(data.handler_id()); |
| } |
| send_queue_.erase(it); |
| return true; |
| } |
| |
| // The transaction to remove has already finished or never existed. |
| bt_log(TRACE, "hci", "command to remove not found, id: %zu", id); |
| return false; |
| } |
| |
| CommandChannel::EventHandlerId CommandChannel::AddEventHandler(EventCode event_code, |
| EventCallback event_callback) { |
| if (event_code == kCommandStatusEventCode || event_code == kCommandCompleteEventCode || |
| event_code == kLEMetaEventCode) { |
| return 0u; |
| } |
| |
| auto* handler = FindEventHandler(event_code); |
| if (handler && handler->is_async()) { |
| bt_log(ERROR, "hci", "async event handler %zu already registered for event code %#.2x", |
| handler->id, event_code); |
| return 0u; |
| } |
| |
| auto id = NewEventHandler(event_code, false /* is_le_meta */, kNoOp, std::move(event_callback)); |
| event_code_handlers_.emplace(event_code, id); |
| return id; |
| } |
| |
| CommandChannel::EventHandlerId CommandChannel::AddLEMetaEventHandler(EventCode subevent_code, |
| EventCallback event_callback) { |
| auto* handler = FindLEMetaEventHandler(subevent_code); |
| if (handler && handler->is_async()) { |
| bt_log(ERROR, "hci", |
| "async event handler %zu already registered for LE Meta Event subevent code %#.2x", |
| handler->id, subevent_code); |
| return 0u; |
| } |
| |
| auto id = NewEventHandler(subevent_code, true /* is_le_meta */, kNoOp, std::move(event_callback)); |
| subevent_code_handlers_.emplace(subevent_code, id); |
| return id; |
| } |
| |
| void CommandChannel::RemoveEventHandler(EventHandlerId id) { |
| // If the ID doesn't exist or it is internal. it can't be removed. |
| auto iter = event_handler_id_map_.find(id); |
| if (iter == event_handler_id_map_.end() || iter->second.is_async()) { |
| return; |
| } |
| |
| RemoveEventHandlerInternal(id); |
| } |
| |
| CommandChannel::EventHandlerData* CommandChannel::FindEventHandler(EventCode code) { |
| auto it = event_code_handlers_.find(code); |
| if (it == event_code_handlers_.end()) { |
| return nullptr; |
| } |
| return &event_handler_id_map_[it->second]; |
| } |
| |
| CommandChannel::EventHandlerData* CommandChannel::FindLEMetaEventHandler(EventCode subevent_code) { |
| auto it = subevent_code_handlers_.find(subevent_code); |
| if (it == subevent_code_handlers_.end()) { |
| return nullptr; |
| } |
| return &event_handler_id_map_[it->second]; |
| } |
| |
| void CommandChannel::RemoveEventHandlerInternal(EventHandlerId id) { |
| auto iter = event_handler_id_map_.find(id); |
| if (iter == event_handler_id_map_.end()) { |
| return; |
| } |
| |
| if (iter->second.event_code != 0) { |
| auto* event_handlers = |
| iter->second.is_le_meta_subevent ? &subevent_code_handlers_ : &event_code_handlers_; |
| |
| bt_log(TRACE, "hci", "removing handler for %sevent code %#.2x", |
| (iter->second.is_le_meta_subevent ? "LE " : ""), iter->second.event_code); |
| |
| auto range = event_handlers->equal_range(iter->second.event_code); |
| for (auto it = range.first; it != range.second; ++it) { |
| if (it->second == id) { |
| event_handlers->erase(it); |
| break; |
| } |
| } |
| } |
| event_handler_id_map_.erase(iter); |
| } |
| |
| void CommandChannel::TrySendQueuedCommands() { |
| if (!is_initialized_) |
| return; |
| |
| if (allowed_command_packets_ == 0) { |
| bt_log(TRACE, "hci", "controller queue full, waiting"); |
| return; |
| } |
| |
| // Walk the waiting and see if any are sendable. |
| for (auto it = send_queue_.begin(); allowed_command_packets_ > 0 && it != send_queue_.end();) { |
| // Care must be taken not to dangle this reference if its owner QueuedCommand is destroyed. |
| const TransactionData& data = *it->data; |
| |
| // Can't send if another is running with an opcode this can't coexist with. |
| bool excluded = false; |
| for (const auto& excluded_opcode : data.exclusions()) { |
| if (pending_transactions_.count(excluded_opcode) != 0) { |
| bt_log(TRACE, "hci", "pending command (%#.4x) delayed due to running opcode %#.4x", |
| it->data->opcode(), excluded_opcode); |
| excluded = true; |
| break; |
| } |
| } |
| if (excluded) { |
| ++it; |
| continue; |
| } |
| |
| bool transaction_waiting_on_event = event_code_handlers_.count(data.complete_event_code()); |
| bool transaction_waiting_on_subevent = |
| data.subevent_code() && subevent_code_handlers_.count(*data.subevent_code()); |
| bool waiting_for_other_transaction = |
| transaction_waiting_on_event || transaction_waiting_on_subevent; |
| |
| // We can send this if we only expect one update, or if we aren't |
| // waiting for another transaction to complete on the same event. |
| // It is unlikely but possible to have commands with different opcodes |
| // wait on the same completion event. |
| if (!IsAsync(data.complete_event_code()) || data.handler_id() != 0 || |
| !waiting_for_other_transaction) { |
| bt_log(TRACE, "hci", "sending previously queued command id %zu", data.id()); |
| SendQueuedCommand(std::move(*it)); |
| it = send_queue_.erase(it); |
| continue; |
| } |
| ++it; |
| } |
| } |
| |
| void CommandChannel::SendQueuedCommand(QueuedCommand&& cmd) { |
| 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. |
| bt_log(ERROR, "hci", "failed to send command: %s", zx_status_get_string(status)); |
| return; |
| } |
| allowed_command_packets_--; |
| |
| auto& transaction = cmd.data; |
| |
| transaction->Start( |
| [this, id = cmd.data->id()] { |
| bt_log(ERROR, "hci", "command %zu timed out, notifying error", id); |
| if (channel_timeout_cb_) { |
| channel_timeout_cb_(); |
| } |
| }, |
| kCommandTimeout); |
| |
| MaybeAddTransactionHandler(transaction.get()); |
| |
| pending_transactions_.insert(std::make_pair(transaction->opcode(), std::move(transaction))); |
| } |
| |
| void CommandChannel::MaybeAddTransactionHandler(TransactionData* data) { |
| // We don't need to add a transaction handler for synchronous transactions. |
| if (!IsAsync(data->complete_event_code())) { |
| return; |
| } |
| |
| const bool is_le_meta = data->subevent_code().has_value(); |
| auto* const code_handlers = is_le_meta ? &subevent_code_handlers_ : &event_code_handlers_; |
| const EventCode code = data->subevent_code().value_or(data->complete_event_code()); |
| |
| // We already have a handler for this transaction, or another transaction |
| // is already waiting and it will be queued. |
| if (code_handlers->count(code)) { |
| bt_log(TRACE, "hci", "async command %zu: already has handler", data->id()); |
| return; |
| } |
| |
| // The handler hasn't been added yet. |
| EventHandlerId id = NewEventHandler(code, is_le_meta, data->opcode(), data->MakeCallback()); |
| |
| ZX_ASSERT(id != 0u); |
| data->set_handler_id(id); |
| code_handlers->emplace(code, id); |
| bt_log(TRACE, "hci", "async command %zu assigned handler %zu", data->id(), id); |
| } |
| |
| CommandChannel::EventHandlerId CommandChannel::NewEventHandler(EventCode event_code, |
| bool is_le_meta, |
| OpCode pending_opcode, |
| EventCallback event_callback) { |
| ZX_DEBUG_ASSERT(event_code); |
| ZX_DEBUG_ASSERT(event_callback); |
| |
| auto id = next_event_handler_id_++; |
| EventHandlerData data; |
| data.id = id; |
| data.event_code = event_code; |
| data.pending_opcode = pending_opcode; |
| data.event_callback = std::move(event_callback); |
| data.is_le_meta_subevent = is_le_meta; |
| |
| bt_log(TRACE, "hci", "adding event handler %zu for %sevent code %#.2x", id, |
| (is_le_meta ? "LE sub" : ""), event_code); |
| ZX_DEBUG_ASSERT(event_handler_id_map_.find(id) == event_handler_id_map_.end()); |
| event_handler_id_map_[id] = std::move(data); |
| |
| return id; |
| } |
| |
| void CommandChannel::UpdateTransaction(std::unique_ptr<EventPacket> event) { |
| hci::EventCode event_code = event->event_code(); |
| |
| ZX_DEBUG_ASSERT(event_code == kCommandStatusEventCode || event_code == kCommandCompleteEventCode); |
| |
| OpCode matching_opcode; |
| |
| // The HCI Command Status event with an error status might indicate that an |
| // async command failed. We use this to unregister async command handlers |
| // below. |
| bool unregister_async_handler = false; |
| |
| if (event->event_code() == kCommandCompleteEventCode) { |
| const auto& params = event->params<CommandCompleteEventParams>(); |
| matching_opcode = le16toh(params.command_opcode); |
| allowed_command_packets_ = params.num_hci_command_packets; |
| } else { // kCommandStatusEventCode |
| const auto& params = event->params<CommandStatusEventParams>(); |
| matching_opcode = le16toh(params.command_opcode); |
| allowed_command_packets_ = params.num_hci_command_packets; |
| unregister_async_handler = params.status != StatusCode::kSuccess; |
| } |
| bt_log(TRACE, "hci", "allowed packets update: %zu", allowed_command_packets_); |
| |
| if (matching_opcode == kNoOp) { |
| return; |
| } |
| |
| auto it = pending_transactions_.find(matching_opcode); |
| if (it == pending_transactions_.end()) { |
| bt_log(ERROR, "hci", "update for unexpected opcode: %#.4x", matching_opcode); |
| return; |
| } |
| |
| auto& pending = it->second; |
| ZX_DEBUG_ASSERT(pending->opcode() == matching_opcode); |
| |
| pending->Complete(std::move(event)); |
| |
| // If the command is synchronous or there's no handler to cleanup, we're done. |
| if (pending->handler_id() == 0u) { |
| pending_transactions_.erase(it); |
| return; |
| } |
| |
| // TODO(fxbug.dev/1109): Do not allow asynchronous commands to finish with Command |
| // Complete. |
| if (event_code == kCommandCompleteEventCode) { |
| bt_log(WARN, "hci", "async command received CommandComplete"); |
| unregister_async_handler = true; |
| } |
| |
| // If an asynchronous command failed, then remove its event handler. |
| if (unregister_async_handler) { |
| bt_log(TRACE, "hci", "async command failed; removing its handler"); |
| RemoveEventHandlerInternal(pending->handler_id()); |
| pending_transactions_.erase(it); |
| } |
| } |
| |
| void CommandChannel::NotifyEventHandler(std::unique_ptr<EventPacket> event) { |
| struct PendingCallback { |
| EventCallback callback; |
| EventHandlerId id; |
| }; |
| std::vector<PendingCallback> pending_callbacks; |
| |
| EventCode event_code; |
| const std::unordered_multimap<EventCode, EventHandlerId>* event_handlers; |
| |
| bool is_le_event = false; |
| if (event->event_code() == kLEMetaEventCode) { |
| is_le_event = true; |
| event_code = event->params<LEMetaEventParams>().subevent_code; |
| event_handlers = &subevent_code_handlers_; |
| } else { |
| event_code = event->event_code(); |
| event_handlers = &event_code_handlers_; |
| } |
| |
| auto range = event_handlers->equal_range(event_code); |
| if (range.first == range.second) { |
| bt_log(DEBUG, "hci", "%sevent %#.2x received with no handler", (is_le_event ? "LE " : ""), |
| event_code); |
| return; |
| } |
| |
| auto iter = range.first; |
| while (iter != range.second) { |
| EventHandlerId event_id = iter->second; |
| bt_log(TRACE, "hci", "notifying handler (id %zu) for event code %#.2x", event_id, event_code); |
| auto handler_iter = event_handler_id_map_.find(event_id); |
| ZX_DEBUG_ASSERT(handler_iter != event_handler_id_map_.end()); |
| |
| auto& handler = handler_iter->second; |
| ZX_DEBUG_ASSERT(handler.event_code == event_code); |
| |
| EventCallback callback = handler.event_callback.share(); |
| |
| ++iter; // Advance so we don't point to an invalid iterator. |
| |
| if (handler.is_async()) { |
| bt_log(TRACE, "hci", "removing completed async handler (id %zu, event code: %#.2x)", event_id, |
| event_code); |
| pending_transactions_.erase(handler.pending_opcode); |
| RemoveEventHandlerInternal(event_id); // |handler| is now dangling. |
| } |
| |
| pending_callbacks.push_back({std::move(callback), event_id}); |
| } |
| |
| // Process queue so callbacks can't add a handler if another queued command |
| // finishes on the same event. |
| TrySendQueuedCommands(); |
| |
| for (auto it = pending_callbacks.begin(); it != pending_callbacks.end(); ++it) { |
| std::unique_ptr<EventPacket> ev = nullptr; |
| |
| // Don't copy event for last callback. |
| if (it == pending_callbacks.end() - 1) { |
| ev = std::move(event); |
| } else { |
| ev = EventPacket::New(event->view().payload_size()); |
| MutableBufferView buf = ev->mutable_view()->mutable_data(); |
| event->view().data().Copy(&buf); |
| } |
| |
| ExecuteEventCallback(std::move(it->callback), it->id, std::move(ev)); |
| } |
| } |
| |
| void CommandChannel::ExecuteEventCallback(EventCallback cb, EventHandlerId id, |
| std::unique_ptr<EventPacket> event) { |
| auto result = cb(*event); |
| if (result == EventCallbackResult::kRemove) { |
| RemoveEventHandler(id); |
| } |
| } |
| |
| void CommandChannel::OnChannelReady(async_dispatcher_t* dispatcher, async::WaitBase* wait, |
| zx_status_t status, const zx_packet_signal_t* signal) { |
| ZX_DEBUG_ASSERT(signal->observed & ZX_CHANNEL_READABLE); |
| |
| TRACE_DURATION("bluetooth", "CommandChannel::OnChannelReady", "signal->count", signal->count); |
| |
| if (status != ZX_OK) { |
| bt_log(DEBUG, "hci", "channel error: %s", zx_status_get_string(status)); |
| return; |
| } |
| |
| // 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++) { |
| auto packet = EventPacket::New(slab_allocators::kLargeControlPayloadSize); |
| if (!packet) { |
| bt_log(ERROR, "hci", "failed to allocate event packet!"); |
| return; |
| } |
| zx_status_t status = ReadEventPacketFromChannel(channel_, packet); |
| if (status == ZX_ERR_INVALID_ARGS) { |
| continue; |
| } else if (status != ZX_OK) { |
| return; |
| } |
| if (packet->event_code() == kCommandStatusEventCode || |
| packet->event_code() == kCommandCompleteEventCode) { |
| UpdateTransaction(std::move(packet)); |
| TrySendQueuedCommands(); |
| } else { |
| NotifyEventHandler(std::move(packet)); |
| } |
| } |
| |
| status = wait->Begin(dispatcher); |
| if (status != ZX_OK) { |
| bt_log(DEBUG, "hci", "wait error: %s", zx_status_get_string(status)); |
| } |
| } |
| |
| zx_status_t CommandChannel::ReadEventPacketFromChannel(const zx::channel& channel, |
| const EventPacketPtr& packet) { |
| uint32_t read_size; |
| auto packet_bytes = packet->mutable_view()->mutable_data(); |
| zx_status_t read_status = channel.read(0u, packet_bytes.mutable_data(), nullptr, |
| packet_bytes.size(), 0, &read_size, nullptr); |
| if (read_status < 0) { |
| bt_log(DEBUG, "hci", "Failed to read event bytes: %s", 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 ZX_ERR_IO; |
| } |
| |
| if (read_size < sizeof(EventHeader)) { |
| bt_log(ERROR, "hci", "malformed data packet - expected at least %zu bytes, got %u", |
| sizeof(EventHeader), read_size); |
| // TODO(armansito): Should this be fatal? Ignore for now. |
| return ZX_ERR_INVALID_ARGS; |
| } |
| |
| // 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) { |
| bt_log(ERROR, "hci", |
| "malformed packet - payload size from header (%zu) does not match" |
| " received payload size: %zu", |
| size_from_header, rx_payload_size); |
| return ZX_ERR_INVALID_ARGS; |
| } |
| |
| packet->InitializeFromBuffer(); |
| return ZX_OK; |
| } |
| |
| } // namespace bt::hci |