• 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/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