blob: dcf5ab8ec6487fcdda8cf8d58a9b9787c367569e [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 "sequential_command_runner.h"
#include "command_channel.h"
#include "hci.h"
#include "transport.h"
#include "lib/fxl/functional/make_copyable.h"
namespace btlib {
namespace hci {
using common::HostError;
SequentialCommandRunner::SequentialCommandRunner(
async_t* dispatcher,
fxl::RefPtr<Transport> transport)
: dispatcher_(dispatcher), transport_(transport), sequence_number_(0u) {
FXL_DCHECK(dispatcher_);
FXL_DCHECK(transport_);
FXL_DCHECK(thread_checker_.IsCreationThreadCurrent());
}
SequentialCommandRunner::~SequentialCommandRunner() {
FXL_DCHECK(thread_checker_.IsCreationThreadCurrent());
}
void SequentialCommandRunner::QueueCommand(
std::unique_ptr<CommandPacket> command_packet,
CommandCompleteCallback callback) {
FXL_DCHECK(!status_callback_);
FXL_DCHECK(thread_checker_.IsCreationThreadCurrent());
FXL_DCHECK(sizeof(CommandHeader) <= command_packet->view().size());
command_queue_.push(std::make_pair(std::move(command_packet), std::move(callback)));
}
void SequentialCommandRunner::RunCommands(StatusCallback status_callback) {
FXL_DCHECK(!status_callback_);
FXL_DCHECK(status_callback);
FXL_DCHECK(!command_queue_.empty());
FXL_DCHECK(thread_checker_.IsCreationThreadCurrent());
status_callback_ = std::move(status_callback);
sequence_number_++;
RunNextQueuedCommand();
}
bool SequentialCommandRunner::IsReady() const {
FXL_DCHECK(thread_checker_.IsCreationThreadCurrent());
return !status_callback_;
}
void SequentialCommandRunner::Cancel() {
FXL_DCHECK(status_callback_);
FXL_DCHECK(!command_callback_.IsCanceled());
Reset();
}
bool SequentialCommandRunner::HasQueuedCommands() const {
FXL_DCHECK(thread_checker_.IsCreationThreadCurrent());
return !command_queue_.empty();
}
void SequentialCommandRunner::RunNextQueuedCommand() {
FXL_DCHECK(status_callback_);
if (command_queue_.empty()) {
NotifyStatusAndReset(Status());
return;
}
auto next = std::move(command_queue_.front());
command_queue_.pop();
command_callback_.Reset(fxl::MakeCopyable(
[this, cmd_cb = std::move(next.second)](CommandChannel::TransactionId,
const EventPacket& event_packet) mutable {
auto status = event_packet.ToStatus();
if (!status) {
NotifyStatusAndReset(status);
return;
}
if (event_packet.event_code() == kCommandStatusEventCode) {
return;
}
// TODO(NET-682): Allow async commands to be chained.
FXL_DCHECK(event_packet.event_code() == kCommandCompleteEventCode);
if (cmd_cb) {
// We allow the command completion callback (i.e. |cmd_cb|) to cancel
// its sequence and even immediately start up a new one.
// SequentialCommandRunner::Cancel() relies on
// CancelableCallback::Cancel() which would in effect delete this
// lambda and potentially corrupt its captured environment while
// executing itself.
//
// To prevent that we push the current sequence number and |cmd_cb|
// itself onto the stack.
uint64_t prev_seq_no = sequence_number_;
auto cb = std::move(cmd_cb);
cb(event_packet);
// The sequence could have been cancelled by |cmd_cb| (and a new
// sequence could have also started). We make sure here that we are in
// the correct sequence and terminate if necessary.
if (!status_callback_ || prev_seq_no != sequence_number_)
return;
}
RunNextQueuedCommand();
}));
if (!transport_->command_channel()->SendCommand(
std::move(next.first), dispatcher_, command_callback_.callback())) {
NotifyStatusAndReset(Status(HostError::kFailed));
}
}
void SequentialCommandRunner::Reset() {
if (!command_queue_.empty())
command_queue_ = {};
status_callback_ = nullptr;
command_callback_.Cancel();
}
void SequentialCommandRunner::NotifyStatusAndReset(Status status) {
FXL_DCHECK(status_callback_);
auto status_cb = std::move(status_callback_);
Reset();
status_cb(status);
}
} // namespace hci
} // namespace btlib