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_bluetooth_sapphire/internal/host/hci-spec/protocol.h"
18 #include "pw_bluetooth_sapphire/internal/host/transport/command_channel.h"
19
20 #pragma clang diagnostic ignored "-Wshadow"
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 BT_DEBUG_ASSERT(cmd_.is_alive());
31 }
32
QueueCommand(CommandPacketVariant command_packet,CommandCompleteCallbackVariant callback,bool wait,hci_spec::EventCode complete_event_code,std::unordered_set<hci_spec::OpCode> exclusions)33 void SequentialCommandRunner::QueueCommand(
34 CommandPacketVariant command_packet,
35 CommandCompleteCallbackVariant callback,
36 bool wait,
37 hci_spec::EventCode complete_event_code,
38 std::unordered_set<hci_spec::OpCode> exclusions) {
39 if (std::holds_alternative<std::unique_ptr<CommandPacket>>(command_packet)) {
40 BT_DEBUG_ASSERT(sizeof(hci_spec::CommandHeader) <=
41 std::get<std::unique_ptr<CommandPacket>>(command_packet)
42 ->view()
43 .size());
44 }
45
46 command_queue_.emplace(
47 QueuedCommand{.packet = std::move(command_packet),
48 .complete_event_code = complete_event_code,
49 .is_le_async_command = false,
50 .callback = std::move(callback),
51 .wait = wait,
52 .exclusions = std::move(exclusions)});
53
54 if (status_callback_) {
55 TryRunNextQueuedCommand();
56 }
57 }
58
QueueLeAsyncCommand(CommandPacketVariant command_packet,hci_spec::EventCode le_meta_subevent_code,CommandCompleteCallbackVariant callback,bool wait)59 void SequentialCommandRunner::QueueLeAsyncCommand(
60 CommandPacketVariant command_packet,
61 hci_spec::EventCode le_meta_subevent_code,
62 CommandCompleteCallbackVariant callback,
63 bool wait) {
64 command_queue_.emplace(
65 QueuedCommand{.packet = std::move(command_packet),
66 .complete_event_code = le_meta_subevent_code,
67 .is_le_async_command = true,
68 .callback = std::move(callback),
69 .wait = wait,
70 .exclusions = {}});
71
72 if (status_callback_) {
73 TryRunNextQueuedCommand();
74 }
75 }
76
RunCommands(ResultFunction<> status_callback)77 void SequentialCommandRunner::RunCommands(ResultFunction<> status_callback) {
78 BT_DEBUG_ASSERT(!status_callback_);
79 BT_DEBUG_ASSERT(status_callback);
80 BT_DEBUG_ASSERT(!command_queue_.empty());
81
82 status_callback_ = std::move(status_callback);
83 sequence_number_++;
84
85 TryRunNextQueuedCommand();
86 }
87
IsReady() const88 bool SequentialCommandRunner::IsReady() const { return !status_callback_; }
89
Cancel()90 void SequentialCommandRunner::Cancel() {
91 NotifyStatusAndReset(ToResult(HostError::kCanceled));
92 }
93
HasQueuedCommands() const94 bool SequentialCommandRunner::HasQueuedCommands() const {
95 return !command_queue_.empty();
96 }
97
TryRunNextQueuedCommand(Result<> status)98 void SequentialCommandRunner::TryRunNextQueuedCommand(Result<> status) {
99 BT_DEBUG_ASSERT(status_callback_);
100
101 // If an error occurred or we're done, reset.
102 if (status.is_error() || (command_queue_.empty() && running_commands_ == 0)) {
103 NotifyStatusAndReset(status);
104 return;
105 }
106
107 // Wait for the rest of the running commands to finish if we need to.
108 if (command_queue_.empty() ||
109 (running_commands_ > 0 && command_queue_.front().wait)) {
110 return;
111 }
112
113 QueuedCommand next = std::move(command_queue_.front());
114 command_queue_.pop();
115
116 auto self = weak_ptr_factory_.GetWeakPtr();
117 auto command_callback = [self,
118 cmd_cb = std::move(next.callback),
119 complete_event_code = next.complete_event_code,
120 seq_no = sequence_number_](
121 auto, const EventPacket& event_packet) {
122 std::optional<EmbossEventPacket> emboss_packet;
123 hci::Result<> status =
124 bt::ToResult(pw::bluetooth::emboss::StatusCode::SUCCESS);
125 using T = std::decay_t<decltype(cmd_cb)>;
126 if constexpr (std::is_same_v<T, CommandCompleteCallback>) {
127 status = event_packet.ToResult();
128 } else {
129 emboss_packet = EmbossEventPacket::New(event_packet.view().size());
130 MutableBufferView buffer = emboss_packet->mutable_data();
131 event_packet.view().data().Copy(&buffer);
132 status = emboss_packet->ToResult();
133 }
134
135 if (self.is_alive() && seq_no != self->sequence_number_) {
136 bt_log(TRACE,
137 "hci",
138 "Ignoring event for previous sequence (event code: %#.2x, status: "
139 "%s)",
140 event_packet.event_code(),
141 bt_str(status));
142 }
143
144 // The sequence could have failed or been canceled, and a new sequence could
145 // have started. This check prevents calling cmd_cb if the corresponding
146 // sequence is no longer running.
147 if (!self.is_alive() || !self->status_callback_ ||
148 seq_no != self->sequence_number_) {
149 return;
150 }
151
152 if (status.is_ok() &&
153 event_packet.event_code() == hci_spec::kCommandStatusEventCode &&
154 complete_event_code != hci_spec::kCommandStatusEventCode) {
155 return;
156 }
157
158 std::visit(
159 [&event_packet, &emboss_packet](auto& cmd_cb) {
160 using T = std::decay_t<decltype(cmd_cb)>;
161 if constexpr (std::is_same_v<T, CommandCompleteCallback>) {
162 if (cmd_cb) {
163 cmd_cb(event_packet);
164 }
165 } else if constexpr (std::is_same_v<T,
166 EmbossCommandCompleteCallback>) {
167 if (cmd_cb) {
168 cmd_cb(*emboss_packet);
169 }
170 }
171 },
172 cmd_cb);
173
174 // The callback could have destroyed, canceled, or restarted the command
175 // runner. While this check looks redundant to the above check, the state
176 // could have changed in cmd_cb.
177 if (!self.is_alive() || !self->status_callback_ ||
178 seq_no != self->sequence_number_) {
179 return;
180 }
181
182 BT_DEBUG_ASSERT(self->running_commands_ > 0);
183 self->running_commands_--;
184 self->TryRunNextQueuedCommand(status);
185 };
186
187 running_commands_++;
188 if (!SendQueuedCommand(std::move(next), std::move(command_callback))) {
189 NotifyStatusAndReset(ToResult(HostError::kFailed));
190 } else {
191 TryRunNextQueuedCommand();
192 }
193 }
194
SendQueuedCommand(QueuedCommand command,CommandChannel::CommandCallback callback)195 bool SequentialCommandRunner::SendQueuedCommand(
196 QueuedCommand command, CommandChannel::CommandCallback callback) {
197 if (!cmd_.is_alive()) {
198 bt_log(
199 INFO, "hci", "SequentialCommandRunner command channel died, aborting");
200 return false;
201 }
202 if (command.is_le_async_command) {
203 return cmd_->SendLeAsyncCommand(std::move(command.packet),
204 std::move(callback),
205 command.complete_event_code);
206 }
207
208 return cmd_->SendExclusiveCommand(std::move(command.packet),
209 std::move(callback),
210 command.complete_event_code,
211 std::move(command.exclusions));
212 }
213
Reset()214 void SequentialCommandRunner::Reset() {
215 if (!command_queue_.empty()) {
216 command_queue_ = {};
217 }
218 running_commands_ = 0;
219 status_callback_ = nullptr;
220 }
221
NotifyStatusAndReset(Result<> status)222 void SequentialCommandRunner::NotifyStatusAndReset(Result<> status) {
223 BT_DEBUG_ASSERT(status_callback_);
224 auto status_cb = std::move(status_callback_);
225 Reset();
226 status_cb(status);
227 }
228
229 } // namespace bt::hci
230