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