• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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