xref: /aosp_15_r20/external/pigweed/pw_bluetooth_sapphire/host/l2cap/a2dp_offload_manager.cc (revision 61c4878ac05f98d0ceed94b57d316916de578985)
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