1 // Copyright 2023 The Pigweed Authors
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License"); you may not
4 // use this file except in compliance with the License. You may obtain a copy of
5 // the License at
6 //
7 // https://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11 // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12 // License for the specific language governing permissions and limitations under
13 // the License.
14
15 #include "pw_bluetooth_sapphire/internal/host/hci/sequential_command_runner.h"
16
17 #include <pw_assert/check.h>
18
19 #include "pw_bluetooth_sapphire/internal/host/hci-spec/protocol.h"
20 #include "pw_bluetooth_sapphire/internal/host/transport/command_channel.h"
21
22 namespace bt::hci {
23
SequentialCommandRunner(hci::CommandChannel::WeakPtr cmd_channel)24 SequentialCommandRunner::SequentialCommandRunner(
25 hci::CommandChannel::WeakPtr cmd_channel)
26 : cmd_(std::move(cmd_channel)),
27 sequence_number_(0u),
28 running_commands_(0u),
29 weak_ptr_factory_(this) {
30 PW_DCHECK(cmd_.is_alive());
31 }
32
QueueCommand(CommandPacket command_packet,EmbossCommandCompleteCallback callback,bool wait,hci_spec::EventCode complete_event_code,std::unordered_set<hci_spec::OpCode> exclusions)33 void SequentialCommandRunner::QueueCommand(
34 CommandPacket command_packet,
35 EmbossCommandCompleteCallback callback,
36 bool wait,
37 hci_spec::EventCode complete_event_code,
38 std::unordered_set<hci_spec::OpCode> exclusions) {
39 command_queue_.emplace(
40 QueuedCommand{.packet = std::move(command_packet),
41 .complete_event_code = complete_event_code,
42 .is_le_async_command = false,
43 .callback = std::move(callback),
44 .wait = wait,
45 .exclusions = std::move(exclusions)});
46
47 if (status_callback_) {
48 TryRunNextQueuedCommand();
49 }
50 }
51
QueueLeAsyncCommand(CommandPacket command_packet,hci_spec::EventCode le_meta_subevent_code,EmbossCommandCompleteCallback callback,bool wait)52 void SequentialCommandRunner::QueueLeAsyncCommand(
53 CommandPacket command_packet,
54 hci_spec::EventCode le_meta_subevent_code,
55 EmbossCommandCompleteCallback callback,
56 bool wait) {
57 command_queue_.emplace(
58 QueuedCommand{.packet = std::move(command_packet),
59 .complete_event_code = le_meta_subevent_code,
60 .is_le_async_command = true,
61 .callback = std::move(callback),
62 .wait = wait,
63 .exclusions = {}});
64
65 if (status_callback_) {
66 TryRunNextQueuedCommand();
67 }
68 }
69
RunCommands(ResultFunction<> status_callback)70 void SequentialCommandRunner::RunCommands(ResultFunction<> status_callback) {
71 PW_DCHECK(!status_callback_);
72 PW_DCHECK(status_callback);
73 PW_DCHECK(!command_queue_.empty());
74
75 status_callback_ = std::move(status_callback);
76 sequence_number_++;
77
78 TryRunNextQueuedCommand();
79 }
80
IsReady() const81 bool SequentialCommandRunner::IsReady() const { return !status_callback_; }
82
Cancel()83 void SequentialCommandRunner::Cancel() {
84 NotifyStatusAndReset(ToResult(HostError::kCanceled));
85 }
86
HasQueuedCommands() const87 bool SequentialCommandRunner::HasQueuedCommands() const {
88 return !command_queue_.empty();
89 }
90
TryRunNextQueuedCommand(Result<> status)91 void SequentialCommandRunner::TryRunNextQueuedCommand(Result<> status) {
92 PW_DCHECK(status_callback_);
93
94 // If an error occurred or we're done, reset.
95 if (status.is_error() || (command_queue_.empty() && running_commands_ == 0)) {
96 NotifyStatusAndReset(status);
97 return;
98 }
99
100 // Wait for the rest of the running commands to finish if we need to.
101 if (command_queue_.empty() ||
102 (running_commands_ > 0 && command_queue_.front().wait)) {
103 return;
104 }
105
106 QueuedCommand next = std::move(command_queue_.front());
107 command_queue_.pop();
108
109 auto self = weak_ptr_factory_.GetWeakPtr();
110 auto command_callback = [self,
111 cmd_cb = std::move(next.callback),
112 complete_event_code = next.complete_event_code,
113 seq_no = sequence_number_](
114 auto, const EventPacket& event) {
115 hci::Result<> event_result = event.ToResult();
116
117 if (self.is_alive() && seq_no != self->sequence_number_) {
118 bt_log(TRACE,
119 "hci",
120 "Ignoring event for previous sequence (event code: %#.2x, status: "
121 "%s)",
122 event.event_code(),
123 bt_str(event_result));
124 }
125
126 // The sequence could have failed or been canceled, and a new sequence could
127 // have started. This check prevents calling cmd_cb if the corresponding
128 // sequence is no longer running.
129 if (!self.is_alive() || !self->status_callback_ ||
130 seq_no != self->sequence_number_) {
131 return;
132 }
133
134 if (event_result.is_ok() &&
135 event.event_code() == hci_spec::kCommandStatusEventCode &&
136 complete_event_code != hci_spec::kCommandStatusEventCode) {
137 return;
138 }
139
140 if (cmd_cb) {
141 cmd_cb(event);
142 }
143
144 // The callback could have destroyed, canceled, or restarted the command
145 // runner. While this check looks redundant to the above check, the state
146 // could have changed in cmd_cb.
147 if (!self.is_alive() || !self->status_callback_ ||
148 seq_no != self->sequence_number_) {
149 return;
150 }
151
152 PW_DCHECK(self->running_commands_ > 0);
153 self->running_commands_--;
154 self->TryRunNextQueuedCommand(event_result);
155 };
156
157 running_commands_++;
158 if (!SendQueuedCommand(std::move(next), std::move(command_callback))) {
159 NotifyStatusAndReset(ToResult(HostError::kFailed));
160 } else {
161 TryRunNextQueuedCommand();
162 }
163 }
164
SendQueuedCommand(QueuedCommand command,CommandChannel::CommandCallback callback)165 bool SequentialCommandRunner::SendQueuedCommand(
166 QueuedCommand command, CommandChannel::CommandCallback callback) {
167 if (!cmd_.is_alive()) {
168 bt_log(
169 INFO, "hci", "SequentialCommandRunner command channel died, aborting");
170 return false;
171 }
172 if (command.is_le_async_command) {
173 return cmd_->SendLeAsyncCommand(std::move(command.packet),
174 std::move(callback),
175 command.complete_event_code);
176 }
177
178 return cmd_->SendExclusiveCommand(std::move(command.packet),
179 std::move(callback),
180 command.complete_event_code,
181 std::move(command.exclusions));
182 }
183
Reset()184 void SequentialCommandRunner::Reset() {
185 if (!command_queue_.empty()) {
186 command_queue_ = {};
187 }
188 running_commands_ = 0;
189 status_callback_ = nullptr;
190 }
191
NotifyStatusAndReset(Result<> status)192 void SequentialCommandRunner::NotifyStatusAndReset(Result<> status) {
193 PW_DCHECK(status_callback_);
194 auto status_cb = std::move(status_callback_);
195 Reset();
196 status_cb(status);
197 }
198
199 } // namespace bt::hci
200