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