// 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 "pw_bluetooth_sapphire/fuchsia/host/controllers/fidl_controller.h" #include #include "pw_bluetooth_sapphire/fuchsia/host/controllers/helpers.h" #include "pw_bluetooth_sapphire/internal/host/common/byte_buffer.h" #include "pw_bluetooth_sapphire/internal/host/common/log.h" #include "zircon/status.h" namespace bt::controllers { namespace fhbt = fuchsia_hardware_bluetooth; using ReceivedPacket = fhbt::ReceivedPacket; namespace { pw::bluetooth::Controller::FeaturesBits VendorFeaturesToFeaturesBits( fhbt::VendorFeatures features) { pw::bluetooth::Controller::FeaturesBits out{0}; if (features.acl_priority_command().has_value() && features.acl_priority_command()) { out |= pw::bluetooth::Controller::FeaturesBits::kSetAclPriorityCommand; } if (features.android_vendor_extensions().has_value()) { // Ignore the content of android_vendor_extension field now. out |= pw::bluetooth::Controller::FeaturesBits::kAndroidVendorExtensions; } return out; } fhbt::VendorAclPriority AclPriorityToFidl(pw::bluetooth::AclPriority priority) { switch (priority) { case pw::bluetooth::AclPriority::kNormal: return fhbt::VendorAclPriority::kNormal; case pw::bluetooth::AclPriority::kSource: case pw::bluetooth::AclPriority::kSink: return fhbt::VendorAclPriority::kHigh; } } fhbt::VendorAclDirection AclPriorityToFidlAclDirection( pw::bluetooth::AclPriority priority) { switch (priority) { // The direction for kNormal is arbitrary. case pw::bluetooth::AclPriority::kNormal: case pw::bluetooth::AclPriority::kSource: return fhbt::VendorAclDirection::kSource; case pw::bluetooth::AclPriority::kSink: return fhbt::VendorAclDirection::kSink; } } fhbt::ScoCodingFormat ScoCodingFormatToFidl( pw::bluetooth::Controller::ScoCodingFormat coding_format) { switch (coding_format) { case pw::bluetooth::Controller::ScoCodingFormat::kCvsd: return fhbt::ScoCodingFormat::kCvsd; case pw::bluetooth::Controller::ScoCodingFormat::kMsbc: return fhbt::ScoCodingFormat::kMsbc; default: PW_CRASH("invalid SCO coding format"); } } fhbt::ScoEncoding ScoEncodingToFidl( pw::bluetooth::Controller::ScoEncoding encoding) { switch (encoding) { case pw::bluetooth::Controller::ScoEncoding::k8Bits: return fhbt::ScoEncoding::kBits8; case pw::bluetooth::Controller::ScoEncoding::k16Bits: return fhbt::ScoEncoding::kBits16; default: PW_CRASH("invalid SCO encoding"); } } fhbt::ScoSampleRate ScoSampleRateToFidl( pw::bluetooth::Controller::ScoSampleRate sample_rate) { switch (sample_rate) { case pw::bluetooth::Controller::ScoSampleRate::k8Khz: return fhbt::ScoSampleRate::kKhz8; case pw::bluetooth::Controller::ScoSampleRate::k16Khz: return fhbt::ScoSampleRate::kKhz16; default: PW_CRASH("invalid SCO sample rate"); } } } // namespace VendorEventHandler::VendorEventHandler( std::function unbind_callback) : unbind_callback_(std::move(unbind_callback)) {} void VendorEventHandler::handle_unknown_event( fidl::UnknownEventMetadata metadata) { bt_log(WARN, "controllers", "Unknown event from Vendor server: %lu", metadata.event_ordinal); } void VendorEventHandler::on_fidl_error(fidl::UnbindInfo error) { bt_log(ERROR, "controllers", "Vendor protocol closed: %s", error.FormatDescription().c_str()); unbind_callback_(ZX_ERR_PEER_CLOSED); } HciEventHandler::HciEventHandler( std::function unbind_callback, std::function on_receive_callback) : on_receive_callback_(std::move(on_receive_callback)), unbind_callback_(std::move(unbind_callback)) {} void HciEventHandler::OnReceive( fuchsia_hardware_bluetooth::ReceivedPacket& packet) { on_receive_callback_(std::move(packet)); } void HciEventHandler::handle_unknown_event( fidl::UnknownEventMetadata metadata) { bt_log(WARN, "controllers", "Unknown event from Hci server: %lu", metadata.event_ordinal); } void HciEventHandler::on_fidl_error(fidl::UnbindInfo error) { bt_log(ERROR, "controllers", "Hci protocol closed: %s", error.FormatDescription().c_str()); unbind_callback_(ZX_ERR_PEER_CLOSED); } FidlController::FidlController(fidl::ClientEnd vendor_client_end, async_dispatcher_t* dispatcher) : vendor_event_handler_([this](zx_status_t status) { OnError(status); }), hci_event_handler_([this](zx_status_t status) { OnError(status); }, [this](fhbt::ReceivedPacket packet) { OnReceive(std::move(packet)); }), sco_event_handler_( [this](zx_status_t status) { OnScoUnbind(status); }, [this](fhbt::ScoPacket packet) { OnReceiveSco(std::move(packet)); }), dispatcher_(dispatcher) { PW_CHECK(vendor_client_end.is_valid()); vendor_client_end_ = std::move(vendor_client_end); } FidlController::~FidlController() { CleanUp(); } void FidlController::Initialize(PwStatusCallback complete_callback, PwStatusCallback error_callback) { initialize_complete_cb_ = std::move(complete_callback); error_cb_ = std::move(error_callback); vendor_ = fidl::Client( std::move(vendor_client_end_), dispatcher_, &vendor_event_handler_); // Connect to Hci protocol vendor_->OpenHciTransport().Then( [this](fidl::Result& result) { if (!result.is_ok()) { bt_log(ERROR, "bt-host", "Failed to open HciTransport: %s", result.error_value().FormatDescription().c_str()); if (result.error_value().is_framework_error()) { OnError(result.error_value().framework_error().status()); } else { OnError(result.error_value().domain_error()); } return; } InitializeHci(std::move(result->channel())); }); } void FidlController::InitializeHci( fidl::ClientEnd hci_client_end) { hci_ = fidl::Client( std::move(hci_client_end), dispatcher_, &hci_event_handler_); initialize_complete_cb_(PW_STATUS_OK); } void FidlController::Close(PwStatusCallback callback) { CleanUp(); callback(PW_STATUS_OK); } void FidlController::SendCommand(pw::span command) { BufferView view = BufferView(command); fidl::VectorView vec_view = fidl::VectorView::FromExternal( const_cast(view.data()), view.size()); fidl::ObjectView obj_view = fidl::ObjectView>::FromExternal(&vec_view); // Use Wire bindings to avoid copying `command`. hci_.wire() ->Send(fhbt::wire::SentPacket::WithCommand(obj_view)) .Then([this](fidl::WireUnownedResult& result) { if (!result.ok()) { bt_log(ERROR, "controllers", "failed to send command: %s", result.status_string()); OnError(result.status()); return; } }); } void FidlController::SendAclData(pw::span data) { BufferView view = BufferView(data); fidl::VectorView vec_view = fidl::VectorView::FromExternal( const_cast(view.data()), view.size()); fidl::ObjectView obj_view = fidl::ObjectView>::FromExternal(&vec_view); // Use Wire bindings to avoid copying `data`. hci_.wire() ->Send(fhbt::wire::SentPacket::WithAcl(obj_view)) .Then([this](fidl::WireUnownedResult& result) { if (!result.ok()) { bt_log(ERROR, "controllers", "failed to send ACL: %s", result.status_string()); OnError(result.status()); return; } }); } void FidlController::SendScoData(pw::span data) { if (!sco_connection_) { bt_log(ERROR, "controllers", "SendScoData() called when SCO is not configured"); OnError(ZX_ERR_BAD_STATE); return; } BufferView view = BufferView(data); fidl::VectorView vec_view = fidl::VectorView::FromExternal( const_cast(view.data()), view.size()); // Use Wire bindings to avoid copying `data`. sco_connection_.value().wire()->Send(vec_view).Then( [this](fidl::WireUnownedResult& result) { if (!result.ok()) { bt_log(ERROR, "controllers", "failed to send SCO: %s", result.status_string()); OnError(result.status()); return; } }); } void FidlController::SendIsoData(pw::span data) { BufferView view = BufferView(data); fidl::VectorView vec_view = fidl::VectorView::FromExternal( const_cast(view.data()), view.size()); fidl::ObjectView obj_view = fidl::ObjectView>::FromExternal(&vec_view); // Use Wire bindings to avoid copying `data`. hci_.wire() ->Send(fhbt::wire::SentPacket::WithIso(obj_view)) .Then([this](fidl::WireUnownedResult& result) { if (!result.ok()) { bt_log(ERROR, "controllers", "failed to send ISO: %s", result.status_string()); OnError(result.status()); return; } }); } void FidlController::ConfigureSco(ScoCodingFormat coding_format, ScoEncoding encoding, ScoSampleRate sample_rate, PwStatusCallback callback) { if (sco_connection_) { callback(pw::Status::AlreadyExists()); return; } fidl::Request request; request.coding_format() = ScoCodingFormatToFidl(coding_format); request.encoding() = ScoEncodingToFidl(encoding); request.sample_rate() = ScoSampleRateToFidl(sample_rate); auto [client_end, server_end] = fidl::CreateEndpoints().value(); request.connection() = std::move(server_end); sco_connection_ = fidl::Client(std::move(client_end), dispatcher_, &sco_event_handler_); fit::result<::fidl::OneWayError> result = hci_->ConfigureSco(std::move(request)); if (!result.is_ok()) { bt_log(WARN, "controllers", "Failed to configure SCO: %s", result.error_value().FormatDescription().c_str()); sco_connection_.reset(); callback(ZxStatusToPwStatus(result.error_value().status())); return; } callback(ZxStatusToPwStatus(ZX_OK)); } void FidlController::ResetSco(pw::Callback callback) { if (!sco_connection_) { bt_log(WARN, "controllers", "ResetSco(): no SCO connection configured"); callback(pw::Status::FailedPrecondition()); return; } if (reset_sco_cb_) { bt_log(WARN, "controllers", "ResetSco(): already pending", ); callback(pw::Status::AlreadyExists()); return; } reset_sco_cb_ = std::move(callback); fit::result result = sco_connection_.value()->Stop(); if (result.is_error()) { OnError(ZX_ERR_BAD_STATE); return; } } void FidlController::GetFeatures( pw::Callback callback) { vendor_->GetFeatures().Then( [this, cb = std::move(callback)]( fidl::Result& result) mutable { if (result.is_error()) { bt_log(WARN, "controllers", "GetFeatures(): %s", result.error_value().status_string()); OnError(ZX_ERR_BAD_STATE); return; } FidlController::FeaturesBits features_bits = VendorFeaturesToFeaturesBits(result.value()); cb(features_bits); }); } void FidlController::EncodeVendorCommand( pw::bluetooth::VendorCommandParameters parameters, pw::Callback>)> callback) { PW_CHECK(vendor_); if (!std::holds_alternative( parameters)) { callback(pw::Status::Unimplemented()); return; } pw::bluetooth::SetAclPriorityCommandParameters params = std::get(parameters); fhbt::VendorSetAclPriorityParams priority_params; priority_params.connection_handle(params.connection_handle); priority_params.priority(AclPriorityToFidl(params.priority)); priority_params.direction(AclPriorityToFidlAclDirection(params.priority)); auto command = fhbt::VendorCommand::WithSetAclPriority(priority_params); vendor_->EncodeCommand(command).Then( [cb = std::move(callback)]( fidl::Result& result) mutable { if (!result.is_ok()) { bt_log(ERROR, "controllers", "Failed to encode vendor command: %s", result.error_value().FormatDescription().c_str()); if (result.error_value().is_framework_error()) { cb(ZxStatusToPwStatus( result.error_value().framework_error().status())); } else { cb(ZxStatusToPwStatus(result.error_value().domain_error())); } return; } auto span = pw::as_bytes(pw::span(result->encoded())); cb(span); }); } void FidlController::ScoEventHandler::OnReceive(fhbt::ScoPacket& packet) { on_receive_callback_(std::move(packet)); } void FidlController::ScoEventHandler::on_fidl_error(fidl::UnbindInfo error) { bt_log(DEBUG, "controllers", "SCO protocol closed: %s", error.FormatDescription().c_str()); unbind_callback_(ZX_ERR_PEER_CLOSED); } void FidlController::ScoEventHandler::handle_unknown_event( fidl::UnknownEventMetadata metadata) { bt_log(WARN, "controllers", "Unknown event from ScoConnection server: %lu", metadata.event_ordinal); } FidlController::ScoEventHandler::ScoEventHandler( pw::Function unbind_callback, pw::Function on_receive_callback) : on_receive_callback_(std::move(on_receive_callback)), unbind_callback_(std::move(unbind_callback)) {} void FidlController::OnReceive(ReceivedPacket packet) { switch (packet.Which()) { case ReceivedPacket::Tag::kEvent: event_cb_(BufferView(packet.event().value()).subspan()); break; case ReceivedPacket::Tag::kAcl: acl_cb_(BufferView(packet.acl().value()).subspan()); break; case ReceivedPacket::Tag::kIso: iso_cb_(BufferView(packet.iso().value()).subspan()); break; default: bt_log(WARN, "controllers", "OnReceive: unknown packet type %lu", static_cast(packet.Which())); } fit::result result = hci_->AckReceive(); if (result.is_error()) { OnError(ZX_ERR_IO); } } void FidlController::OnReceiveSco( fuchsia_hardware_bluetooth::ScoPacket packet) { fit::result result = sco_connection_.value()->AckReceive(); if (result.is_error()) { OnError(ZX_ERR_IO); return; } sco_cb_(BufferView(packet.packet()).subspan()); } void FidlController::OnScoUnbind(zx_status_t status) { // The server shouldn't close a ScoConnection on its own. It should only close // after the host calls Stop(). if (!reset_sco_cb_) { bt_log(ERROR, "controllers", "ScoConnection closed unexpectedly (Stop() not called): %s", zx_status_get_string(status)); OnError(ZX_ERR_BAD_STATE); return; } bt_log(DEBUG, "controllers", "ScoConnection closed"); sco_connection_.reset(); reset_sco_cb_(pw::OkStatus()); reset_sco_cb_ = nullptr; } void FidlController::OnError(zx_status_t status) { CleanUp(); // If |initialize_complete_cb_| has already been called, then initialization // is complete and we use |error_cb_| if (initialize_complete_cb_) { initialize_complete_cb_(ZxStatusToPwStatus(status)); // Clean up |error_cb_| since we only need one callback to be called during // initialization. error_cb_ = nullptr; } else if (error_cb_) { error_cb_(ZxStatusToPwStatus(status)); } } void FidlController::CleanUp() { if (shutting_down_) { return; } shutting_down_ = true; if (hci_) { auto hci_endpoint = hci_.UnbindMaybeGetEndpoint(); } if (vendor_) { auto vendor_endpoint = vendor_.UnbindMaybeGetEndpoint(); } if (sco_connection_) { auto sco_endpoint = sco_connection_->UnbindMaybeGetEndpoint(); sco_connection_.reset(); } } } // namespace bt::controllers