// Copyright 2024 The Pigweed Authors // // Licensed under the Apache License, Version 2.0 (the "License"); you may not // use this file except in compliance with the License. You may obtain a copy of // the License at // // https://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the // License for the specific language governing permissions and limitations under // the License. #include "loopback.h" #include namespace bt_hci_virtual { LoopbackDevice::LoopbackDevice() : dispatcher_(fdf::Dispatcher::GetCurrent()->async_dispatcher()), devfs_connector_(fit::bind_member<&LoopbackDevice::Connect>(this)) {} zx_status_t LoopbackDevice::Initialize(zx::channel channel, std::string_view name, AddChildCallback callback) { // Setup up incoming channel waiter loopback_chan_ = std::move(channel); loopback_chan_wait_.set_object(loopback_chan_.get()); loopback_chan_wait_.set_trigger(ZX_CHANNEL_READABLE | ZX_CHANNEL_PEER_CLOSED); ZX_ASSERT(loopback_chan_wait_.Begin(dispatcher_) == ZX_OK); // Create args to add loopback as a child node on behalf of VirtualController zx::result connector = devfs_connector_.Bind(dispatcher_); if (connector.is_error()) { FDF_LOG(ERROR, "Failed to bind devfs connecter to dispatcher: %u", connector.status_value()); return connector.error_value(); } fidl::Arena args_arena; auto devfs = fuchsia_driver_framework::wire::DevfsAddArgs::Builder(args_arena) .connector(std::move(connector.value())) .class_name("bt-hci") .Build(); auto args = fuchsia_driver_framework::wire::NodeAddArgs::Builder(args_arena) .name(name.data()) .devfs_args(devfs) .Build(); callback(args); return ZX_OK; } LoopbackDevice::SnoopServer::SnoopServer( fidl::ServerEnd server_end, LoopbackDevice* device) : binding_(device->dispatcher_, std::move(server_end), this, std::mem_fn(&LoopbackDevice::SnoopServer::OnFidlError)), device_(device) {} void LoopbackDevice::SnoopServer::QueueSnoopPacket( uint8_t* buffer, size_t length, PacketIndicator indicator, fuchsia_hardware_bluetooth::PacketDirection direction) { uint64_t sequence = next_sequence_++; if (sequence > acked_sequence_ + kMaxSnoopUnackedPackets) { if (queued_packets_.size() >= kMaxSnoopQueueSize) { // Drop oldest packet. fuchsia_hardware_bluetooth::PacketDirection direction = queued_packets_.front().direction; queued_packets_.pop(); if (direction == fuchsia_hardware_bluetooth::PacketDirection::kHostToController) { dropped_sent_++; } else { dropped_received_++; } } std::vector packet(buffer, buffer + length); queued_packets_.push( Packet{std::move(packet), sequence, indicator, direction}); return; } SendSnoopPacket(buffer, length, indicator, direction, sequence); } void LoopbackDevice::SnoopServer::SendSnoopPacket( uint8_t* buffer, size_t length, PacketIndicator indicator, fuchsia_hardware_bluetooth::PacketDirection direction, uint64_t sequence) { fidl::VectorView vec_view = fidl::VectorView::FromExternal(buffer, length); fidl::ObjectView obj_view = fidl::ObjectView>::FromExternal(&vec_view); fuchsia_hardware_bluetooth::wire::SnoopPacket packet; switch (indicator) { case PacketIndicator::kHciCommand: packet = fuchsia_hardware_bluetooth::wire::SnoopPacket::WithCommand(obj_view); break; case PacketIndicator::kHciAclData: packet = fuchsia_hardware_bluetooth::wire::SnoopPacket::WithAcl(obj_view); break; case PacketIndicator::kHciSco: packet = fuchsia_hardware_bluetooth::wire::SnoopPacket::WithSco(obj_view); break; case PacketIndicator::kHciEvent: packet = fuchsia_hardware_bluetooth::wire::SnoopPacket::WithEvent(obj_view); break; case PacketIndicator::kHciIso: packet = fuchsia_hardware_bluetooth::wire::SnoopPacket::WithIso(obj_view); break; default: FDF_LOG(WARNING, "Unknown indicator in %s", __func__); return; } fidl::Arena arena; fuchsia_hardware_bluetooth::wire::SnoopOnObservePacketRequest request = fuchsia_hardware_bluetooth::wire::SnoopOnObservePacketRequest::Builder( arena) .sequence(sequence) .direction(direction) .packet(packet) .Build(); fidl::OneWayStatus observe_status = fidl::WireSendEvent(binding_)->OnObservePacket(request); if (!observe_status.ok()) { FDF_LOG(WARNING, "Failed to send OnObservePacket on Snoop: %s", observe_status.status_string()); } } void LoopbackDevice::SnoopServer::AcknowledgePackets( AcknowledgePacketsRequest& request, AcknowledgePacketsCompleter::Sync& completer) { if (request.sequence() <= acked_sequence_) { return; } acked_sequence_ = request.sequence(); // Send Snoop.OnDroppedPackets if necessary before sending next // Snoop.ObservePacket. if (dropped_sent_ != 0 || dropped_received_ != 0) { fuchsia_hardware_bluetooth::SnoopOnDroppedPacketsRequest request; request.sent() = dropped_sent_; request.received() = dropped_received_; fit::result result = fidl::SendEvent(binding_)->OnDroppedPackets(request); if (result.is_error()) { FDF_LOG(WARNING, "Failed to send Snoop.OnDroppedPackets event: %s", result.error_value().status_string()); } dropped_sent_ = 0; dropped_received_ = 0; } while (!queued_packets_.empty() && queued_packets_.front().sequence <= acked_sequence_ + kMaxSnoopUnackedPackets) { Packet& packet = queued_packets_.front(); SendSnoopPacket(packet.packet.data(), packet.packet.size(), packet.indicator, packet.direction, packet.sequence); queued_packets_.pop(); } } void LoopbackDevice::SnoopServer::OnFidlError(fidl::UnbindInfo error) { if (!error.is_user_initiated()) { FDF_LOG(INFO, "Snoop closed: %s", error.status_string()); } device_->snoop_.reset(); } void LoopbackDevice::SnoopServer::handle_unknown_method( fidl::UnknownMethodMetadata metadata, fidl::UnknownMethodCompleter::Sync& completer) { FDF_LOG(WARNING, "Unknown Snoop method received"); } void LoopbackDevice::Connect( fidl::ServerEnd request) { vendor_binding_group_.AddBinding( dispatcher_, std::move(request), this, fidl::kIgnoreBindingClosure); } void LoopbackDevice::OnLoopbackChannelSignal(async_dispatcher_t* dispatcher, async::WaitBase* wait, zx_status_t status, const zx_packet_signal_t* signal) { if (status == ZX_ERR_CANCELED) { return; } if (status != ZX_OK) { FDF_LOG( ERROR, "Loopback channel wait error: %s", zx_status_get_string(status)); return; } if (signal->observed & ZX_CHANNEL_READABLE) { ReadLoopbackChannel(); } if (signal->observed & ZX_CHANNEL_PEER_CLOSED) { FDF_LOG(ERROR, "Loopback channel peer closed"); return; } loopback_chan_wait_.Begin(dispatcher_); } void LoopbackDevice::OnHciTransportUnbound(size_t binding_id, fidl::UnbindInfo info) { hci_transport_servers_.erase(binding_id); } void LoopbackDevice::ReadLoopbackChannel() { while (true) { uint32_t actual_bytes = 0; zx_status_t read_status = loopback_chan_.read( /*flags=*/0, read_buffer_.data(), /*handles=*/nullptr, static_cast(read_buffer_.size()), /*num_handles=*/0, &actual_bytes, /*actual_handles=*/nullptr); if (read_status == ZX_ERR_SHOULD_WAIT) { return; } if (read_status != ZX_OK) { FDF_LOG(ERROR, "failed to read from loopback channel %s", zx_status_get_string(read_status)); return; } if (actual_bytes == 0) { FDF_LOG(WARNING, "ignoring empty packet when reading loopback channel"); continue; } for (auto& [_, server] : hci_transport_servers_) { server.OnReceive(read_buffer_.data(), actual_bytes); } if (snoop_.has_value()) { snoop_->QueueSnoopPacket( read_buffer_.data() + 1, actual_bytes - 1, static_cast(read_buffer_[0]), fuchsia_hardware_bluetooth::PacketDirection::kControllerToHost); } } } void LoopbackDevice::WriteLoopbackChannel(PacketIndicator indicator, uint8_t* buffer, size_t length) { // We tack on an indicator byte to the beginning of the payload. // Use an iovec to avoid a large allocation + copy. zx_channel_iovec_t iovs[2]; iovs[0] = { .buffer = &indicator, .capacity = sizeof(indicator), .reserved = 0}; iovs[1] = {.buffer = buffer, .capacity = static_cast(length), .reserved = 0}; zx_status_t write_status = loopback_chan_.write(/*flags=*/ZX_CHANNEL_WRITE_USE_IOVEC, /*bytes=*/iovs, /*num_bytes=*/2, /*handles=*/nullptr, /*num_handles=*/0); if (write_status != ZX_OK) { FDF_LOG(ERROR, "Failed to write to loopback channel: %s", zx_status_get_string(write_status)); return; } if (snoop_.has_value()) { snoop_->QueueSnoopPacket( buffer, length, indicator, fuchsia_hardware_bluetooth::PacketDirection::kHostToController); } } void LoopbackDevice::GetFeatures(GetFeaturesCompleter::Sync& completer) { completer.Reply(fuchsia_hardware_bluetooth::VendorFeatures()); } void LoopbackDevice::EncodeCommand(EncodeCommandRequest& request, EncodeCommandCompleter::Sync& completer) { completer.Reply(fit::error(ZX_ERR_NOT_SUPPORTED)); } void LoopbackDevice::OpenHci(OpenHciCompleter::Sync& completer) { completer.Reply(fit::error(ZX_ERR_NOT_SUPPORTED)); } void LoopbackDevice::OpenHciTransport( OpenHciTransportCompleter::Sync& completer) { auto endpoints = fidl::CreateEndpoints(); if (endpoints.is_error()) { FDF_LOG(ERROR, "Failed to create HciTransport endpoints: %s", zx_status_get_string(endpoints.error_value())); completer.Reply(fit::error(ZX_ERR_INTERNAL)); return; } size_t binding_id = next_hci_transport_server_id_++; auto [_, inserted] = hci_transport_servers_.try_emplace( binding_id, this, binding_id, std::move(endpoints->server)); ZX_ASSERT(inserted); completer.Reply(fit::ok(std::move(endpoints->client))); } void LoopbackDevice::OpenSnoop(OpenSnoopCompleter::Sync& completer) { auto endpoints = fidl::CreateEndpoints(); if (endpoints.is_error()) { FDF_LOG(ERROR, "Failed to create Snoop endpoints: %s", zx_status_get_string(endpoints.error_value())); completer.Reply(fit::error(ZX_ERR_INTERNAL)); return; } snoop_.emplace(std::move(std::move(endpoints->server)), this); completer.Reply(fit::ok(std::move(endpoints->client))); } void LoopbackDevice::handle_unknown_method( fidl::UnknownMethodMetadata metadata, fidl::UnknownMethodCompleter::Sync& completer) { FDF_LOG( ERROR, "Unknown method in Vendor request, closing with ZX_ERR_NOT_SUPPORTED"); completer.Close(ZX_ERR_NOT_SUPPORTED); } LoopbackDevice::HciTransportServer::HciTransportServer( LoopbackDevice* device, size_t binding_id, fidl::ServerEnd server_end) : device_(device), binding_id_(binding_id), binding_(device_->dispatcher_, std::move(server_end), this, std::mem_fn(&HciTransportServer::OnUnbound)) {} void LoopbackDevice::HciTransportServer::OnReceive(uint8_t* buffer, size_t length) { if (receive_credits_ == 0 || !receive_queue_.empty()) { if (receive_queue_.size() == kMaxReceiveQueueSize) { FDF_LOG(ERROR, "Receive queue reached max size, dropping packet"); return; } std::vector packet(buffer, buffer + length); receive_queue_.push(std::move(packet)); return; } SendOnReceive(buffer, length); } void LoopbackDevice::HciTransportServer::OnUnbound(fidl::UnbindInfo info) { device_->hci_transport_servers_.erase(binding_id_); } void LoopbackDevice::HciTransportServer::SendOnReceive(uint8_t* buffer, size_t length) { ZX_ASSERT(receive_credits_ != 0); receive_credits_--; // Omit the indicator byte in ReceivedPacket. fidl::VectorView vec_view = fidl::VectorView::FromExternal(buffer + 1, length - 1); fidl::ObjectView obj_view = fidl::ObjectView>::FromExternal(&vec_view); fuchsia_hardware_bluetooth::wire::ReceivedPacket packet; switch (static_cast(buffer[0])) { case PacketIndicator::kHciEvent: packet = fuchsia_hardware_bluetooth::wire::ReceivedPacket::WithEvent(obj_view); break; case PacketIndicator::kHciAclData: packet = fuchsia_hardware_bluetooth::wire::ReceivedPacket::WithAcl(obj_view); break; case PacketIndicator::kHciIso: packet = fuchsia_hardware_bluetooth::wire::ReceivedPacket::WithIso(obj_view); break; default: FDF_LOG(WARNING, "Received invalid packet indicator on loopback channel"); return; } fidl::OneWayStatus status = fidl::WireSendEvent(binding_)->OnReceive(packet); if (!status.ok()) { FDF_LOG( WARNING, "Error sending OnReceive event: %s", status.status_string()); } } void LoopbackDevice::HciTransportServer::MaybeSendQueuedReceivePackets() { while (receive_credits_ != 0 && !receive_queue_.empty()) { SendOnReceive(receive_queue_.front().data(), receive_queue_.front().size()); receive_queue_.pop(); } } void LoopbackDevice::HciTransportServer::Send(SendRequest& request, SendCompleter::Sync& completer) { completer.Reply(); switch (request.Which()) { case fuchsia_hardware_bluetooth::SentPacket::Tag::kCommand: device_->WriteLoopbackChannel(PacketIndicator::kHciCommand, request.command()->data(), request.command()->size()); break; case fuchsia_hardware_bluetooth::SentPacket::Tag::kAcl: device_->WriteLoopbackChannel(PacketIndicator::kHciAclData, request.acl()->data(), request.acl()->size()); break; case fuchsia_hardware_bluetooth::SentPacket::Tag::kIso: device_->WriteLoopbackChannel(PacketIndicator::kHciIso, request.iso()->data(), request.iso()->size()); break; default: FDF_LOG(WARNING, "Unknown SentPacket type"); } } void LoopbackDevice::HciTransportServer::AckReceive( AckReceiveCompleter::Sync& completer) { receive_credits_++; MaybeSendQueuedReceivePackets(); } void LoopbackDevice::HciTransportServer::ConfigureSco( ConfigureScoRequest& request, ConfigureScoCompleter::Sync& completer) {} void LoopbackDevice::HciTransportServer::handle_unknown_method( fidl::UnknownMethodMetadata metadata, fidl::UnknownMethodCompleter::Sync& completer) { FDF_LOG(ERROR, "Unknown method in HciTransport request, closing with " "ZX_ERR_NOT_SUPPORTED"); completer.Close(ZX_ERR_NOT_SUPPORTED); } } // namespace bt_hci_virtual