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/sco/sco_connection.h"
16
17 namespace bt::sco {
18
ScoConnection(std::unique_ptr<hci::Connection> connection,fit::closure deactivated_cb,bt::StaticPacket<pw::bluetooth::emboss::SynchronousConnectionParametersWriter> parameters,hci::ScoDataChannel * channel)19 ScoConnection::ScoConnection(
20 std::unique_ptr<hci::Connection> connection,
21 fit::closure deactivated_cb,
22 bt::StaticPacket<
23 pw::bluetooth::emboss::SynchronousConnectionParametersWriter>
24 parameters,
25 hci::ScoDataChannel* channel)
26 : active_(false),
27 connection_(std::move(connection)),
28 deactivated_cb_(std::move(deactivated_cb)),
29 channel_(channel),
30 parameters_(std::move(parameters)),
31 weak_self_(this) {
32 BT_ASSERT(connection_);
33 BT_ASSERT(!channel_ || channel_->max_data_length() <=
34 hci_spec::kMaxSynchronousDataPacketPayloadSize);
35
36 handle_ = connection_->handle();
37
38 connection_->set_peer_disconnect_callback([this](const auto&, auto) {
39 // Notifies activator that this connection has been disconnected.
40 // Activator will call Deactivate().
41 Close();
42 });
43 }
44
unique_id() const45 ScoConnection::UniqueId ScoConnection::unique_id() const {
46 // HCI connection handles are unique per controller.
47 return handle();
48 }
49
id() const50 ScoConnection::UniqueId ScoConnection::id() const { return unique_id(); }
51
Close()52 void ScoConnection::Close() {
53 bt_log(TRACE, "gap-sco", "closing sco connection (handle: %.4x)", handle());
54
55 bool active = active_;
56 CleanUp();
57
58 if (!active) {
59 return;
60 }
61
62 BT_ASSERT(activator_closed_cb_);
63 // Move cb out of this, since cb may destroy this.
64 auto cb = std::move(activator_closed_cb_);
65 cb();
66 }
67
Activate(fit::closure rx_callback,fit::closure closed_callback)68 bool ScoConnection::Activate(fit::closure rx_callback,
69 fit::closure closed_callback) {
70 // TODO(fxbug.dev/42136417): Handle Activate() called on a connection that has
71 // been closed already.
72 BT_ASSERT(closed_callback);
73 BT_ASSERT(!active_);
74 BT_ASSERT(rx_callback);
75 activator_closed_cb_ = std::move(closed_callback);
76 rx_callback_ = std::move(rx_callback);
77 active_ = true;
78 if (channel_ && parameters_.view().input_data_path().Read() ==
79 pw::bluetooth::emboss::ScoDataPath::HCI) {
80 channel_->RegisterConnection(GetWeakPtr());
81 }
82 return true;
83 }
84
Deactivate()85 void ScoConnection::Deactivate() {
86 bt_log(
87 TRACE, "gap-sco", "deactivating sco connection (handle: %.4x)", handle());
88 CleanUp();
89 if (deactivated_cb_) {
90 // Move cb out of this, since cb may destroy this.
91 auto cb = std::move(deactivated_cb_);
92 cb();
93 }
94 }
95
max_tx_sdu_size() const96 uint16_t ScoConnection::max_tx_sdu_size() const {
97 return channel_ ? channel_->max_data_length() : 0u;
98 }
99
Send(ByteBufferPtr payload)100 bool ScoConnection::Send(ByteBufferPtr payload) {
101 if (!active_) {
102 bt_log(WARN,
103 "gap-sco",
104 "dropping SCO packet for inactive connection (handle: %#.4x)",
105 handle_);
106 return false;
107 }
108
109 if (!channel_) {
110 bt_log(
111 WARN,
112 "gap-sco",
113 "dropping SCO packet because HCI SCO is not supported (handle: %#.4x)",
114 handle_);
115 return false;
116 }
117
118 if (payload->size() > channel_->max_data_length()) {
119 bt_log(WARN,
120 "gap-sco",
121 "dropping SCO packet larger than the buffer data packet length "
122 "(packet size: %zu, max "
123 "data length: "
124 "%hu)",
125 payload->size(),
126 channel_->max_data_length());
127 return false;
128 }
129
130 outbound_queue_.push(std::move(payload));
131
132 // Notify ScoDataChannel that a packet is available. This is only necessary
133 // for the first packet of an empty queue (flow control will poll this
134 // connection otherwise).
135 if (outbound_queue_.size() == 1u) {
136 channel_->OnOutboundPacketReadable();
137 }
138 return true;
139 }
140
Read()141 std::unique_ptr<hci::ScoDataPacket> ScoConnection::Read() {
142 if (inbound_queue_.empty()) {
143 return nullptr;
144 }
145 std::unique_ptr<hci::ScoDataPacket> packet =
146 std::move(inbound_queue_.front());
147 inbound_queue_.pop();
148 return packet;
149 }
150
151 bt::StaticPacket<pw::bluetooth::emboss::SynchronousConnectionParametersWriter>
parameters()152 ScoConnection::parameters() {
153 return parameters_;
154 }
155
GetNextOutboundPacket()156 std::unique_ptr<hci::ScoDataPacket> ScoConnection::GetNextOutboundPacket() {
157 if (outbound_queue_.empty()) {
158 return nullptr;
159 }
160
161 std::unique_ptr<hci::ScoDataPacket> out = hci::ScoDataPacket::New(
162 handle(), static_cast<uint8_t>(outbound_queue_.front()->size()));
163 if (!out) {
164 bt_log(ERROR, "gap-sco", "failed to allocate SCO data packet");
165 return nullptr;
166 }
167 out->mutable_view()->mutable_payload_data().Write(
168 outbound_queue_.front()->view());
169 outbound_queue_.pop();
170 return out;
171 }
172
ReceiveInboundPacket(std::unique_ptr<hci::ScoDataPacket> packet)173 void ScoConnection::ReceiveInboundPacket(
174 std::unique_ptr<hci::ScoDataPacket> packet) {
175 BT_ASSERT(packet->connection_handle() == handle_);
176
177 if (!active_ || !rx_callback_) {
178 bt_log(TRACE, "gap-sco", "dropping inbound SCO packet");
179 return;
180 }
181
182 inbound_queue_.push(std::move(packet));
183 // It's only necessary to notify activator of the first packet queued (flow
184 // control will poll this connection otherwise).
185 if (inbound_queue_.size() == 1u) {
186 rx_callback_();
187 }
188 }
189
OnHciError()190 void ScoConnection::OnHciError() {
191 // Notify activator that this connection should be deactivated.
192 Close();
193 }
194
CleanUp()195 void ScoConnection::CleanUp() {
196 if (active_ && channel_ &&
197 parameters_.view().input_data_path().Read() ==
198 pw::bluetooth::emboss::ScoDataPath::HCI) {
199 channel_->UnregisterConnection(handle_);
200 }
201 active_ = false;
202 connection_ = nullptr;
203 }
204
205 } // namespace bt::sco
206