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/l2cap/a2dp_offload_manager.h"
16
17 #include <pw_assert/check.h>
18 #include <pw_bluetooth/hci_android.emb.h>
19 #include <pw_preprocessor/compiler.h>
20
21 #include <cstdint>
22 #include <utility>
23
24 #include "pw_bluetooth_sapphire/internal/host/common/host_error.h"
25 #include "pw_bluetooth_sapphire/internal/host/hci-spec/constants.h"
26 #include "pw_bluetooth_sapphire/internal/host/hci-spec/protocol.h"
27 #include "pw_bluetooth_sapphire/internal/host/hci-spec/vendor_protocol.h"
28 #include "pw_bluetooth_sapphire/internal/host/l2cap/channel.h"
29 #include "pw_bluetooth_sapphire/internal/host/l2cap/l2cap_defs.h"
30 #include "pw_bluetooth_sapphire/internal/host/transport/control_packets.h"
31
32 namespace bt::l2cap {
33 namespace android_hci = bt::hci_spec::vendor::android;
34 namespace android_emb = pw::bluetooth::vendor::android_hci;
35
StartA2dpOffload(const Configuration & config,ChannelId local_id,ChannelId remote_id,hci_spec::ConnectionHandle link_handle,uint16_t max_tx_sdu_size,hci::ResultCallback<> callback)36 void A2dpOffloadManager::StartA2dpOffload(
37 const Configuration& config,
38 ChannelId local_id,
39 ChannelId remote_id,
40 hci_spec::ConnectionHandle link_handle,
41 uint16_t max_tx_sdu_size,
42 hci::ResultCallback<> callback) {
43 PW_DCHECK(cmd_channel_.is_alive());
44
45 switch (a2dp_offload_status_) {
46 case A2dpOffloadStatus::kStarted: {
47 bt_log(WARN,
48 "l2cap",
49 "Only one channel can offload A2DP at a time; already offloaded "
50 "(handle: %#.4x, local id: %#.4x",
51 *offloaded_link_handle_,
52 *offloaded_channel_id_);
53 callback(ToResult(HostError::kInProgress));
54 return;
55 }
56 case A2dpOffloadStatus::kStarting: {
57 bt_log(WARN,
58 "l2cap",
59 "A2DP offload is currently starting (status: %hhu)",
60 static_cast<unsigned char>(a2dp_offload_status_));
61 callback(ToResult(HostError::kInProgress));
62 return;
63 }
64 case A2dpOffloadStatus::kStopping: {
65 bt_log(WARN,
66 "l2cap",
67 "A2DP offload is stopping... wait until stopped before starting "
68 "(status: %hhu)",
69 static_cast<unsigned char>(a2dp_offload_status_));
70 callback(ToResult(HostError::kInProgress));
71 return;
72 }
73 case A2dpOffloadStatus::kStopped:
74 break;
75 }
76
77 offloaded_link_handle_ = link_handle;
78 offloaded_channel_id_ = local_id;
79 a2dp_offload_status_ = A2dpOffloadStatus::kStarting;
80
81 constexpr size_t kPacketSize =
82 android_emb::StartA2dpOffloadCommand::MaxSizeInBytes();
83 auto packet =
84 hci::CommandPacket::New<android_emb::StartA2dpOffloadCommandWriter>(
85 android_hci::kA2dpOffloadCommand, kPacketSize);
86 auto view = packet.view_t();
87
88 view.vendor_command().sub_opcode().Write(
89 android_hci::kStartA2dpOffloadCommandSubopcode);
90 view.codec_type().Write(config.codec);
91 view.max_latency().Write(config.max_latency);
92 view.scms_t_enable().CopyFrom(
93 const_cast<Configuration&>(config).scms_t_enable.view());
94 view.sampling_frequency().Write(config.sampling_frequency);
95 view.bits_per_sample().Write(config.bits_per_sample);
96 view.channel_mode().Write(config.channel_mode);
97 view.encoded_audio_bitrate().Write(config.encoded_audio_bit_rate);
98 view.connection_handle().Write(link_handle);
99 view.l2cap_channel_id().Write(remote_id);
100 view.l2cap_mtu_size().Write(max_tx_sdu_size);
101
102 // kAptx and kAptxhd codecs not yet handled
103 PW_MODIFY_DIAGNOSTICS_PUSH();
104 PW_MODIFY_DIAGNOSTIC(ignored, "-Wswitch-enum");
105 switch (config.codec) {
106 case android_emb::A2dpCodecType::SBC:
107 view.sbc_codec_information().CopyFrom(
108 const_cast<Configuration&>(config).sbc_configuration.view());
109 break;
110 case android_emb::A2dpCodecType::AAC:
111 view.aac_codec_information().CopyFrom(
112 const_cast<Configuration&>(config).aac_configuration.view());
113 break;
114 case android_emb::A2dpCodecType::LDAC:
115 view.ldac_codec_information().CopyFrom(
116 const_cast<Configuration&>(config).ldac_configuration.view());
117 break;
118 case android_emb::A2dpCodecType::APTX:
119 case android_emb::A2dpCodecType::APTX_HD:
120 default:
121 bt_log(ERROR,
122 "l2cap",
123 "a2dp offload codec type (%hhu) not supported",
124 static_cast<uint8_t>(config.codec));
125 callback(ToResult(HostError::kNotSupported));
126 return;
127 }
128 PW_MODIFY_DIAGNOSTICS_POP();
129
130 cmd_channel_->SendCommand(
131 std::move(packet),
132 [cb = std::move(callback),
133 id = local_id,
134 handle = link_handle,
135 self = weak_self_.GetWeakPtr(),
136 this](auto /*transaction_id*/, const hci::EventPacket& event) mutable {
137 if (!self.is_alive()) {
138 return;
139 }
140
141 if (event.ToResult().is_error()) {
142 bt_log(WARN,
143 "l2cap",
144 "Start A2DP offload command failed (result: %s, handle: "
145 "%#.4x, local id: %#.4x)",
146 bt_str(event.ToResult()),
147 handle,
148 id);
149 a2dp_offload_status_ = A2dpOffloadStatus::kStopped;
150 } else {
151 bt_log(INFO,
152 "l2cap",
153 "A2DP offload started (handle: %#.4x, local id: %#.4x",
154 handle,
155 id);
156 a2dp_offload_status_ = A2dpOffloadStatus::kStarted;
157 }
158 cb(event.ToResult());
159
160 // If we tried to stop while A2DP was still starting, perform the stop
161 // command now
162 if (pending_stop_a2dp_offload_request_.has_value()) {
163 auto pending_request_callback =
164 std::move(pending_stop_a2dp_offload_request_.value());
165 pending_stop_a2dp_offload_request_.reset();
166
167 RequestStopA2dpOffload(
168 id, handle, std::move(pending_request_callback));
169 }
170 });
171 }
172
RequestStopA2dpOffload(ChannelId local_id,hci_spec::ConnectionHandle link_handle,hci::ResultCallback<> callback)173 void A2dpOffloadManager::RequestStopA2dpOffload(
174 ChannelId local_id,
175 hci_spec::ConnectionHandle link_handle,
176 hci::ResultCallback<> callback) {
177 PW_DCHECK(cmd_channel_.is_alive());
178
179 switch (a2dp_offload_status_) {
180 case A2dpOffloadStatus::kStopped: {
181 bt_log(DEBUG,
182 "l2cap",
183 "No channels are offloading A2DP (status: %hhu)",
184 static_cast<unsigned char>(a2dp_offload_status_));
185 callback(fit::success());
186 return;
187 }
188 case A2dpOffloadStatus::kStopping: {
189 bt_log(WARN,
190 "l2cap",
191 "A2DP offload is currently stopping (status: %hhu)",
192 static_cast<unsigned char>(a2dp_offload_status_));
193 callback(ToResult(HostError::kInProgress));
194 return;
195 }
196 case A2dpOffloadStatus::kStarting:
197 case A2dpOffloadStatus::kStarted:
198 break;
199 }
200
201 if (!IsChannelOffloaded(local_id, link_handle)) {
202 callback(fit::success());
203 return;
204 }
205
206 // Wait until offloading status is |kStarted| before sending stop command
207 if (a2dp_offload_status_ == A2dpOffloadStatus::kStarting) {
208 pending_stop_a2dp_offload_request_ = std::move(callback);
209 return;
210 }
211
212 a2dp_offload_status_ = A2dpOffloadStatus::kStopping;
213
214 auto packet =
215 hci::CommandPacket::New<android_emb::StopA2dpOffloadCommandWriter>(
216 android_hci::kA2dpOffloadCommand);
217 auto packet_view = packet.view_t();
218
219 packet_view.vendor_command().sub_opcode().Write(
220 android_hci::kStopA2dpOffloadCommandSubopcode);
221
222 cmd_channel_->SendCommand(
223 std::move(packet),
224 [cb = std::move(callback),
225 self = weak_self_.GetWeakPtr(),
226 id = local_id,
227 handle = link_handle,
228 this](auto /*transaction_id*/, const hci::EventPacket& event) mutable {
229 if (!self.is_alive()) {
230 return;
231 }
232
233 if (event.ToResult().is_error()) {
234 bt_log(WARN,
235 "l2cap",
236 "Stop A2DP offload command failed (result: %s, handle: %#.4x, "
237 "local id: %#.4x)",
238 bt_str(event.ToResult()),
239 handle,
240 id);
241 } else {
242 bt_log(INFO,
243 "l2cap",
244 "A2DP offload stopped (handle: %#.4x, local id: %#.4x",
245 handle,
246 id);
247 }
248 cb(event.ToResult());
249
250 a2dp_offload_status_ = A2dpOffloadStatus::kStopped;
251 });
252 }
253
IsChannelOffloaded(ChannelId id,hci_spec::ConnectionHandle link_handle) const254 bool A2dpOffloadManager::IsChannelOffloaded(
255 ChannelId id, hci_spec::ConnectionHandle link_handle) const {
256 if (!offloaded_channel_id_.has_value() ||
257 !offloaded_link_handle_.has_value()) {
258 bt_log(DEBUG,
259 "l2cap",
260 "Channel is not offloaded (handle: %#.4x, local id: %#.4x) ",
261 link_handle,
262 id);
263 return false;
264 }
265
266 // Same channel that requested start A2DP offloading must request stop
267 // offloading
268 if (id != offloaded_channel_id_ || link_handle != offloaded_link_handle_) {
269 bt_log(WARN,
270 "l2cap",
271 "Offloaded channel must request stop offloading; offloaded channel "
272 "(handle: %#.4x, local id: %#.4x)",
273 *offloaded_link_handle_,
274 *offloaded_channel_id_);
275 return false;
276 }
277
278 return id == *offloaded_channel_id_ &&
279 link_handle == *offloaded_link_handle_ &&
280 (a2dp_offload_status_ == A2dpOffloadStatus::kStarted ||
281 a2dp_offload_status_ == A2dpOffloadStatus::kStarting);
282 }
283
284 } // namespace bt::l2cap
285