1 // Copyright 2024 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 "loopback.h"
16
17 #include <lib/driver/logging/cpp/logger.h>
18
19 namespace bt_hci_virtual {
20
LoopbackDevice()21 LoopbackDevice::LoopbackDevice()
22 : dispatcher_(fdf::Dispatcher::GetCurrent()->async_dispatcher()),
23 devfs_connector_(fit::bind_member<&LoopbackDevice::Connect>(this)) {}
24
Initialize(zx::channel channel,std::string_view name,AddChildCallback callback)25 zx_status_t LoopbackDevice::Initialize(zx::channel channel,
26 std::string_view name,
27 AddChildCallback callback) {
28 // Setup up incoming channel waiter
29 loopback_chan_ = std::move(channel);
30 loopback_chan_wait_.set_object(loopback_chan_.get());
31 loopback_chan_wait_.set_trigger(ZX_CHANNEL_READABLE | ZX_CHANNEL_PEER_CLOSED);
32 ZX_ASSERT(loopback_chan_wait_.Begin(dispatcher_) == ZX_OK);
33
34 // Create args to add loopback as a child node on behalf of VirtualController
35 zx::result connector = devfs_connector_.Bind(dispatcher_);
36 if (connector.is_error()) {
37 FDF_LOG(ERROR,
38 "Failed to bind devfs connecter to dispatcher: %u",
39 connector.status_value());
40 return connector.error_value();
41 }
42
43 fidl::Arena args_arena;
44 auto devfs = fuchsia_driver_framework::wire::DevfsAddArgs::Builder(args_arena)
45 .connector(std::move(connector.value()))
46 .class_name("bt-hci")
47 .Build();
48 auto args = fuchsia_driver_framework::wire::NodeAddArgs::Builder(args_arena)
49 .name(name.data())
50 .devfs_args(devfs)
51 .Build();
52
53 callback(args);
54
55 return ZX_OK;
56 }
57
SnoopServer(fidl::ServerEnd<fuchsia_hardware_bluetooth::Snoop> server_end,LoopbackDevice * device)58 LoopbackDevice::SnoopServer::SnoopServer(
59 fidl::ServerEnd<fuchsia_hardware_bluetooth::Snoop> server_end,
60 LoopbackDevice* device)
61 : binding_(device->dispatcher_,
62 std::move(server_end),
63 this,
64 std::mem_fn(&LoopbackDevice::SnoopServer::OnFidlError)),
65 device_(device) {}
66
QueueSnoopPacket(uint8_t * buffer,size_t length,PacketIndicator indicator,fuchsia_hardware_bluetooth::PacketDirection direction)67 void LoopbackDevice::SnoopServer::QueueSnoopPacket(
68 uint8_t* buffer,
69 size_t length,
70 PacketIndicator indicator,
71 fuchsia_hardware_bluetooth::PacketDirection direction) {
72 uint64_t sequence = next_sequence_++;
73
74 if (sequence > acked_sequence_ + kMaxSnoopUnackedPackets) {
75 if (queued_packets_.size() >= kMaxSnoopQueueSize) {
76 // Drop oldest packet.
77 fuchsia_hardware_bluetooth::PacketDirection direction =
78 queued_packets_.front().direction;
79 queued_packets_.pop();
80 if (direction ==
81 fuchsia_hardware_bluetooth::PacketDirection::kHostToController) {
82 dropped_sent_++;
83 } else {
84 dropped_received_++;
85 }
86 }
87 std::vector<uint8_t> packet(buffer, buffer + length);
88 queued_packets_.push(
89 Packet{std::move(packet), sequence, indicator, direction});
90 return;
91 }
92
93 SendSnoopPacket(buffer, length, indicator, direction, sequence);
94 }
95
SendSnoopPacket(uint8_t * buffer,size_t length,PacketIndicator indicator,fuchsia_hardware_bluetooth::PacketDirection direction,uint64_t sequence)96 void LoopbackDevice::SnoopServer::SendSnoopPacket(
97 uint8_t* buffer,
98 size_t length,
99 PacketIndicator indicator,
100 fuchsia_hardware_bluetooth::PacketDirection direction,
101 uint64_t sequence) {
102 fidl::VectorView vec_view =
103 fidl::VectorView<uint8_t>::FromExternal(buffer, length);
104 fidl::ObjectView obj_view =
105 fidl::ObjectView<fidl::VectorView<uint8_t>>::FromExternal(&vec_view);
106 fuchsia_hardware_bluetooth::wire::SnoopPacket packet;
107 switch (indicator) {
108 case PacketIndicator::kHciCommand:
109 packet =
110 fuchsia_hardware_bluetooth::wire::SnoopPacket::WithCommand(obj_view);
111 break;
112 case PacketIndicator::kHciAclData:
113 packet = fuchsia_hardware_bluetooth::wire::SnoopPacket::WithAcl(obj_view);
114 break;
115 case PacketIndicator::kHciSco:
116 packet = fuchsia_hardware_bluetooth::wire::SnoopPacket::WithSco(obj_view);
117 break;
118 case PacketIndicator::kHciEvent:
119 packet =
120 fuchsia_hardware_bluetooth::wire::SnoopPacket::WithEvent(obj_view);
121 break;
122 case PacketIndicator::kHciIso:
123 packet = fuchsia_hardware_bluetooth::wire::SnoopPacket::WithIso(obj_view);
124 break;
125 default:
126 FDF_LOG(WARNING, "Unknown indicator in %s", __func__);
127 return;
128 }
129
130 fidl::Arena arena;
131 fuchsia_hardware_bluetooth::wire::SnoopOnObservePacketRequest request =
132 fuchsia_hardware_bluetooth::wire::SnoopOnObservePacketRequest::Builder(
133 arena)
134 .sequence(sequence)
135 .direction(direction)
136 .packet(packet)
137 .Build();
138
139 fidl::OneWayStatus observe_status =
140 fidl::WireSendEvent(binding_)->OnObservePacket(request);
141 if (!observe_status.ok()) {
142 FDF_LOG(WARNING,
143 "Failed to send OnObservePacket on Snoop: %s",
144 observe_status.status_string());
145 }
146 }
147
AcknowledgePackets(AcknowledgePacketsRequest & request,AcknowledgePacketsCompleter::Sync & completer)148 void LoopbackDevice::SnoopServer::AcknowledgePackets(
149 AcknowledgePacketsRequest& request,
150 AcknowledgePacketsCompleter::Sync& completer) {
151 if (request.sequence() <= acked_sequence_) {
152 return;
153 }
154 acked_sequence_ = request.sequence();
155
156 // Send Snoop.OnDroppedPackets if necessary before sending next
157 // Snoop.ObservePacket.
158 if (dropped_sent_ != 0 || dropped_received_ != 0) {
159 fuchsia_hardware_bluetooth::SnoopOnDroppedPacketsRequest request;
160 request.sent() = dropped_sent_;
161 request.received() = dropped_received_;
162 fit::result<fidl::OneWayError> result =
163 fidl::SendEvent(binding_)->OnDroppedPackets(request);
164 if (result.is_error()) {
165 FDF_LOG(WARNING,
166 "Failed to send Snoop.OnDroppedPackets event: %s",
167 result.error_value().status_string());
168 }
169 dropped_sent_ = 0;
170 dropped_received_ = 0;
171 }
172
173 while (!queued_packets_.empty() &&
174 queued_packets_.front().sequence <=
175 acked_sequence_ + kMaxSnoopUnackedPackets) {
176 Packet& packet = queued_packets_.front();
177 SendSnoopPacket(packet.packet.data(),
178 packet.packet.size(),
179 packet.indicator,
180 packet.direction,
181 packet.sequence);
182 queued_packets_.pop();
183 }
184 }
185
OnFidlError(fidl::UnbindInfo error)186 void LoopbackDevice::SnoopServer::OnFidlError(fidl::UnbindInfo error) {
187 if (!error.is_user_initiated()) {
188 FDF_LOG(INFO, "Snoop closed: %s", error.status_string());
189 }
190 device_->snoop_.reset();
191 }
192
handle_unknown_method(fidl::UnknownMethodMetadata<fuchsia_hardware_bluetooth::Snoop> metadata,fidl::UnknownMethodCompleter::Sync & completer)193 void LoopbackDevice::SnoopServer::handle_unknown_method(
194 fidl::UnknownMethodMetadata<fuchsia_hardware_bluetooth::Snoop> metadata,
195 fidl::UnknownMethodCompleter::Sync& completer) {
196 FDF_LOG(WARNING, "Unknown Snoop method received");
197 }
198
Connect(fidl::ServerEnd<fuchsia_hardware_bluetooth::Vendor> request)199 void LoopbackDevice::Connect(
200 fidl::ServerEnd<fuchsia_hardware_bluetooth::Vendor> request) {
201 vendor_binding_group_.AddBinding(
202 dispatcher_, std::move(request), this, fidl::kIgnoreBindingClosure);
203 }
204
OnLoopbackChannelSignal(async_dispatcher_t * dispatcher,async::WaitBase * wait,zx_status_t status,const zx_packet_signal_t * signal)205 void LoopbackDevice::OnLoopbackChannelSignal(async_dispatcher_t* dispatcher,
206 async::WaitBase* wait,
207 zx_status_t status,
208 const zx_packet_signal_t* signal) {
209 if (status == ZX_ERR_CANCELED) {
210 return;
211 }
212 if (status != ZX_OK) {
213 FDF_LOG(
214 ERROR, "Loopback channel wait error: %s", zx_status_get_string(status));
215 return;
216 }
217
218 if (signal->observed & ZX_CHANNEL_READABLE) {
219 ReadLoopbackChannel();
220 }
221
222 if (signal->observed & ZX_CHANNEL_PEER_CLOSED) {
223 FDF_LOG(ERROR, "Loopback channel peer closed");
224 return;
225 }
226
227 loopback_chan_wait_.Begin(dispatcher_);
228 }
229
OnHciTransportUnbound(size_t binding_id,fidl::UnbindInfo info)230 void LoopbackDevice::OnHciTransportUnbound(size_t binding_id,
231 fidl::UnbindInfo info) {
232 hci_transport_servers_.erase(binding_id);
233 }
234
ReadLoopbackChannel()235 void LoopbackDevice::ReadLoopbackChannel() {
236 while (true) {
237 uint32_t actual_bytes = 0;
238 zx_status_t read_status = loopback_chan_.read(
239 /*flags=*/0,
240 read_buffer_.data(),
241 /*handles=*/nullptr,
242 static_cast<uint32_t>(read_buffer_.size()),
243 /*num_handles=*/0,
244 &actual_bytes,
245 /*actual_handles=*/nullptr);
246
247 if (read_status == ZX_ERR_SHOULD_WAIT) {
248 return;
249 }
250
251 if (read_status != ZX_OK) {
252 FDF_LOG(ERROR,
253 "failed to read from loopback channel %s",
254 zx_status_get_string(read_status));
255 return;
256 }
257
258 if (actual_bytes == 0) {
259 FDF_LOG(WARNING, "ignoring empty packet when reading loopback channel");
260 continue;
261 }
262
263 for (auto& [_, server] : hci_transport_servers_) {
264 server.OnReceive(read_buffer_.data(), actual_bytes);
265 }
266
267 if (snoop_.has_value()) {
268 snoop_->QueueSnoopPacket(
269 read_buffer_.data() + 1,
270 actual_bytes - 1,
271 static_cast<PacketIndicator>(read_buffer_[0]),
272 fuchsia_hardware_bluetooth::PacketDirection::kControllerToHost);
273 }
274 }
275 }
276
WriteLoopbackChannel(PacketIndicator indicator,uint8_t * buffer,size_t length)277 void LoopbackDevice::WriteLoopbackChannel(PacketIndicator indicator,
278 uint8_t* buffer,
279 size_t length) {
280 // We tack on an indicator byte to the beginning of the payload.
281 // Use an iovec to avoid a large allocation + copy.
282 zx_channel_iovec_t iovs[2];
283 iovs[0] = {
284 .buffer = &indicator, .capacity = sizeof(indicator), .reserved = 0};
285 iovs[1] = {.buffer = buffer,
286 .capacity = static_cast<uint32_t>(length),
287 .reserved = 0};
288
289 zx_status_t write_status =
290 loopback_chan_.write(/*flags=*/ZX_CHANNEL_WRITE_USE_IOVEC,
291 /*bytes=*/iovs,
292 /*num_bytes=*/2,
293 /*handles=*/nullptr,
294 /*num_handles=*/0);
295 if (write_status != ZX_OK) {
296 FDF_LOG(ERROR,
297 "Failed to write to loopback channel: %s",
298 zx_status_get_string(write_status));
299 return;
300 }
301
302 if (snoop_.has_value()) {
303 snoop_->QueueSnoopPacket(
304 buffer,
305 length,
306 indicator,
307 fuchsia_hardware_bluetooth::PacketDirection::kHostToController);
308 }
309 }
310
GetFeatures(GetFeaturesCompleter::Sync & completer)311 void LoopbackDevice::GetFeatures(GetFeaturesCompleter::Sync& completer) {
312 completer.Reply(fuchsia_hardware_bluetooth::VendorFeatures());
313 }
314
EncodeCommand(EncodeCommandRequest & request,EncodeCommandCompleter::Sync & completer)315 void LoopbackDevice::EncodeCommand(EncodeCommandRequest& request,
316 EncodeCommandCompleter::Sync& completer) {
317 completer.Reply(fit::error(ZX_ERR_NOT_SUPPORTED));
318 }
319
OpenHci(OpenHciCompleter::Sync & completer)320 void LoopbackDevice::OpenHci(OpenHciCompleter::Sync& completer) {
321 completer.Reply(fit::error(ZX_ERR_NOT_SUPPORTED));
322 }
323
OpenHciTransport(OpenHciTransportCompleter::Sync & completer)324 void LoopbackDevice::OpenHciTransport(
325 OpenHciTransportCompleter::Sync& completer) {
326 auto endpoints =
327 fidl::CreateEndpoints<fuchsia_hardware_bluetooth::HciTransport>();
328 if (endpoints.is_error()) {
329 FDF_LOG(ERROR,
330 "Failed to create HciTransport endpoints: %s",
331 zx_status_get_string(endpoints.error_value()));
332 completer.Reply(fit::error(ZX_ERR_INTERNAL));
333 return;
334 }
335
336 size_t binding_id = next_hci_transport_server_id_++;
337 auto [_, inserted] = hci_transport_servers_.try_emplace(
338 binding_id, this, binding_id, std::move(endpoints->server));
339 ZX_ASSERT(inserted);
340 completer.Reply(fit::ok(std::move(endpoints->client)));
341 }
342
OpenSnoop(OpenSnoopCompleter::Sync & completer)343 void LoopbackDevice::OpenSnoop(OpenSnoopCompleter::Sync& completer) {
344 auto endpoints = fidl::CreateEndpoints<fuchsia_hardware_bluetooth::Snoop>();
345 if (endpoints.is_error()) {
346 FDF_LOG(ERROR,
347 "Failed to create Snoop endpoints: %s",
348 zx_status_get_string(endpoints.error_value()));
349 completer.Reply(fit::error(ZX_ERR_INTERNAL));
350 return;
351 }
352 snoop_.emplace(std::move(std::move(endpoints->server)), this);
353 completer.Reply(fit::ok(std::move(endpoints->client)));
354 }
355
handle_unknown_method(fidl::UnknownMethodMetadata<fuchsia_hardware_bluetooth::Vendor> metadata,fidl::UnknownMethodCompleter::Sync & completer)356 void LoopbackDevice::handle_unknown_method(
357 fidl::UnknownMethodMetadata<fuchsia_hardware_bluetooth::Vendor> metadata,
358 fidl::UnknownMethodCompleter::Sync& completer) {
359 FDF_LOG(
360 ERROR,
361 "Unknown method in Vendor request, closing with ZX_ERR_NOT_SUPPORTED");
362 completer.Close(ZX_ERR_NOT_SUPPORTED);
363 }
364
HciTransportServer(LoopbackDevice * device,size_t binding_id,fidl::ServerEnd<fuchsia_hardware_bluetooth::HciTransport> server_end)365 LoopbackDevice::HciTransportServer::HciTransportServer(
366 LoopbackDevice* device,
367 size_t binding_id,
368 fidl::ServerEnd<fuchsia_hardware_bluetooth::HciTransport> server_end)
369 : device_(device),
370 binding_id_(binding_id),
371 binding_(device_->dispatcher_,
372 std::move(server_end),
373 this,
374 std::mem_fn(&HciTransportServer::OnUnbound)) {}
375
OnReceive(uint8_t * buffer,size_t length)376 void LoopbackDevice::HciTransportServer::OnReceive(uint8_t* buffer,
377 size_t length) {
378 if (receive_credits_ == 0 || !receive_queue_.empty()) {
379 if (receive_queue_.size() == kMaxReceiveQueueSize) {
380 FDF_LOG(ERROR, "Receive queue reached max size, dropping packet");
381 return;
382 }
383 std::vector<uint8_t> packet(buffer, buffer + length);
384 receive_queue_.push(std::move(packet));
385 return;
386 }
387 SendOnReceive(buffer, length);
388 }
389
OnUnbound(fidl::UnbindInfo info)390 void LoopbackDevice::HciTransportServer::OnUnbound(fidl::UnbindInfo info) {
391 device_->hci_transport_servers_.erase(binding_id_);
392 }
393
SendOnReceive(uint8_t * buffer,size_t length)394 void LoopbackDevice::HciTransportServer::SendOnReceive(uint8_t* buffer,
395 size_t length) {
396 ZX_ASSERT(receive_credits_ != 0);
397 receive_credits_--;
398
399 // Omit the indicator byte in ReceivedPacket.
400 fidl::VectorView vec_view =
401 fidl::VectorView<uint8_t>::FromExternal(buffer + 1, length - 1);
402 fidl::ObjectView obj_view =
403 fidl::ObjectView<fidl::VectorView<uint8_t>>::FromExternal(&vec_view);
404 fuchsia_hardware_bluetooth::wire::ReceivedPacket packet;
405 switch (static_cast<PacketIndicator>(buffer[0])) {
406 case PacketIndicator::kHciEvent:
407 packet =
408 fuchsia_hardware_bluetooth::wire::ReceivedPacket::WithEvent(obj_view);
409 break;
410 case PacketIndicator::kHciAclData:
411 packet =
412 fuchsia_hardware_bluetooth::wire::ReceivedPacket::WithAcl(obj_view);
413 break;
414 case PacketIndicator::kHciIso:
415 packet =
416 fuchsia_hardware_bluetooth::wire::ReceivedPacket::WithIso(obj_view);
417 break;
418 default:
419 FDF_LOG(WARNING, "Received invalid packet indicator on loopback channel");
420 return;
421 }
422 fidl::OneWayStatus status = fidl::WireSendEvent(binding_)->OnReceive(packet);
423 if (!status.ok()) {
424 FDF_LOG(
425 WARNING, "Error sending OnReceive event: %s", status.status_string());
426 }
427 }
428
MaybeSendQueuedReceivePackets()429 void LoopbackDevice::HciTransportServer::MaybeSendQueuedReceivePackets() {
430 while (receive_credits_ != 0 && !receive_queue_.empty()) {
431 SendOnReceive(receive_queue_.front().data(), receive_queue_.front().size());
432 receive_queue_.pop();
433 }
434 }
435
Send(SendRequest & request,SendCompleter::Sync & completer)436 void LoopbackDevice::HciTransportServer::Send(SendRequest& request,
437 SendCompleter::Sync& completer) {
438 completer.Reply();
439
440 switch (request.Which()) {
441 case fuchsia_hardware_bluetooth::SentPacket::Tag::kCommand:
442 device_->WriteLoopbackChannel(PacketIndicator::kHciCommand,
443 request.command()->data(),
444 request.command()->size());
445 break;
446 case fuchsia_hardware_bluetooth::SentPacket::Tag::kAcl:
447 device_->WriteLoopbackChannel(PacketIndicator::kHciAclData,
448 request.acl()->data(),
449 request.acl()->size());
450 break;
451 case fuchsia_hardware_bluetooth::SentPacket::Tag::kIso:
452 device_->WriteLoopbackChannel(PacketIndicator::kHciIso,
453 request.iso()->data(),
454 request.iso()->size());
455 break;
456 default:
457 FDF_LOG(WARNING, "Unknown SentPacket type");
458 }
459 }
460
AckReceive(AckReceiveCompleter::Sync & completer)461 void LoopbackDevice::HciTransportServer::AckReceive(
462 AckReceiveCompleter::Sync& completer) {
463 receive_credits_++;
464 MaybeSendQueuedReceivePackets();
465 }
466
ConfigureSco(ConfigureScoRequest & request,ConfigureScoCompleter::Sync & completer)467 void LoopbackDevice::HciTransportServer::ConfigureSco(
468 ConfigureScoRequest& request, ConfigureScoCompleter::Sync& completer) {}
469
handle_unknown_method(fidl::UnknownMethodMetadata<fuchsia_hardware_bluetooth::HciTransport> metadata,fidl::UnknownMethodCompleter::Sync & completer)470 void LoopbackDevice::HciTransportServer::handle_unknown_method(
471 fidl::UnknownMethodMetadata<fuchsia_hardware_bluetooth::HciTransport>
472 metadata,
473 fidl::UnknownMethodCompleter::Sync& completer) {
474 FDF_LOG(ERROR,
475 "Unknown method in HciTransport request, closing with "
476 "ZX_ERR_NOT_SUPPORTED");
477 completer.Close(ZX_ERR_NOT_SUPPORTED);
478 }
479
480 } // namespace bt_hci_virtual
481