| // Copyright 2022 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "bt_hci_broadcom.h" |
| |
| #include <assert.h> |
| #include <endian.h> |
| #include <fidl/fuchsia.driver.compat/cpp/wire.h> |
| #include <lib/async-loop/default.h> |
| #include <lib/async/cpp/task.h> |
| #include <lib/async/default.h> |
| #include <lib/ddk/binding_driver.h> |
| #include <lib/ddk/driver.h> |
| #include <lib/ddk/metadata.h> |
| #include <lib/ddk/platform-defs.h> |
| #include <lib/driver/compat/cpp/device_server.h> |
| #include <lib/driver/component/cpp/driver_export.h> |
| #include <lib/fdf/cpp/dispatcher.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <sys/random.h> |
| #include <threads.h> |
| #include <zircon/assert.h> |
| #include <zircon/errors.h> |
| #include <zircon/status.h> |
| #include <zircon/threads.h> |
| |
| namespace bt_hci_broadcom { |
| |
| constexpr uint32_t kTargetBaudRate = 2000000; |
| constexpr uint32_t kDefaultBaudRate = 115200; |
| |
| constexpr zx::duration kFirmwareDownloadDelay = zx::msec(50); |
| |
| // Hardcoded. Better to parameterize on chipset. Broadcom chips need a few hundred msec delay after |
| // firmware load. |
| constexpr zx::duration kBaudRateSwitchDelay = zx::msec(200); |
| |
| const std::unordered_map<uint16_t, std::string> BtHciBroadcom::kFirmwareMap = { |
| {PDEV_PID_BCM43458, "BCM4345C5.hcd"}, |
| {PDEV_PID_BCM4359, "BCM4359C0.hcd"}, |
| }; |
| |
| BtHciBroadcom::BtHciBroadcom(fdf::DriverStartArgs start_args, |
| fdf::UnownedSynchronizedDispatcher driver_dispatcher) |
| : DriverBase("bt-hci-broadcom", std::move(start_args), std::move(driver_dispatcher)), |
| node_(fidl::WireClient(std::move(node()), dispatcher())), |
| devfs_connector_(fit::bind_member<&BtHciBroadcom::Connect>(this)) {} |
| |
| void BtHciBroadcom::Start(fdf::StartCompleter completer) { |
| zx_status_t status = ConnectToHciFidlProtocol(); |
| if (status != ZX_OK) { |
| completer(zx::error(status)); |
| return; |
| } |
| |
| status = ConnectToSerialFidlProtocol(); |
| if (status == ZX_OK) { |
| is_uart_ = true; |
| } |
| |
| fdf::Arena arena('INFO'); |
| auto result = serial_client_.buffer(arena)->GetInfo(); |
| if (!result.ok()) { |
| FDF_LOG(ERROR, "Read failed FIDL error: %s", result.status_string()); |
| completer(zx::error(result.status())); |
| return; |
| } |
| |
| if (result->is_error()) { |
| FDF_LOG(ERROR, "Read failed : %s", zx_status_get_string(result->error_value())); |
| completer(zx::error(result->error_value())); |
| return; |
| } |
| |
| serial_pid_ = result.value()->info.serial_pid; |
| |
| // Continue initialization through the fpromise executor. |
| start_completer_.emplace(std::move(completer)); |
| executor_.emplace(dispatcher()); |
| executor_->schedule_task(Initialize().then([this](fpromise::result<void, zx_status_t>& result) { |
| if (result.is_ok()) { |
| CompleteStart(ZX_OK); |
| } else { |
| CompleteStart(result.take_error()); |
| } |
| })); |
| } |
| |
| void BtHciBroadcom::PrepareStop(fdf::PrepareStopCompleter completer) { |
| command_channel_.reset(); |
| completer(zx::ok()); |
| } |
| |
| void BtHciBroadcom::EncodeCommand(EncodeCommandRequestView request, |
| EncodeCommandCompleter::Sync& completer) { |
| uint8_t data_buffer[kBcmSetAclPriorityCmdSize]; |
| switch (request->Which()) { |
| case fuchsia_hardware_bluetooth::wire::VendorCommand::Tag::kSetAclPriority: { |
| EncodeSetAclPriorityCommand(request->set_acl_priority(), data_buffer); |
| auto encoded_cmd = |
| fidl::VectorView<uint8_t>::FromExternal(data_buffer, kBcmSetAclPriorityCmdSize); |
| completer.ReplySuccess(encoded_cmd); |
| return; |
| } |
| default: { |
| completer.ReplyError(ZX_ERR_INVALID_ARGS); |
| return; |
| } |
| } |
| } |
| |
| void BtHciBroadcom::OpenHci(OpenHciCompleter::Sync& completer) { |
| auto endpoints = fidl::CreateEndpoints<fuchsia_hardware_bluetooth::Hci>(); |
| if (endpoints.is_error()) { |
| FDF_LOG(ERROR, "Failed to create endpoints: %s", zx_status_get_string(endpoints.error_value())); |
| completer.ReplyError(endpoints.error_value()); |
| return; |
| } |
| |
| hci_server_bindings_.AddBinding(fdf::Dispatcher::GetCurrent()->async_dispatcher(), |
| std::move(endpoints->server), this, fidl::kIgnoreBindingClosure); |
| completer.ReplySuccess(std::move(endpoints->client)); |
| } |
| |
| void BtHciBroadcom::handle_unknown_method( |
| fidl::UnknownMethodMetadata<fuchsia_hardware_bluetooth::Vendor> metadata, |
| fidl::UnknownMethodCompleter::Sync& completer) { |
| FDF_LOG(ERROR, "Unknown method in Vendor protocol, closing with ZX_ERR_NOT_SUPPORTED"); |
| completer.Close(ZX_ERR_NOT_SUPPORTED); |
| } |
| |
| void BtHciBroadcom::OpenCommandChannel(OpenCommandChannelRequestView request, |
| OpenCommandChannelCompleter::Sync& completer) { |
| hci_client_->OpenCommandChannel(std::move(request->channel)) |
| .ThenExactlyOnce( |
| [completer = completer.ToAsync()]( |
| fidl::WireUnownedResult<fuchsia_hardware_bluetooth::Hci::OpenCommandChannel>& |
| result) mutable { |
| if (!result.ok()) { |
| FDF_LOG(ERROR, "OpenCommandChannel failed with FIDL error %s", |
| result.status_string()); |
| completer.ReplyError(result.status()); |
| return; |
| } |
| if (result->is_error()) { |
| FDF_LOG(ERROR, "OpenCommandChannel failed with error %s", |
| zx_status_get_string(result->error_value())); |
| completer.ReplyError(result->error_value()); |
| return; |
| } |
| completer.ReplySuccess(); |
| }); |
| } |
| |
| void BtHciBroadcom::OpenAclDataChannel(OpenAclDataChannelRequestView request, |
| OpenAclDataChannelCompleter::Sync& completer) { |
| hci_client_->OpenAclDataChannel(std::move(request->channel)) |
| .ThenExactlyOnce( |
| [completer = completer.ToAsync()]( |
| fidl::WireUnownedResult<fuchsia_hardware_bluetooth::Hci::OpenAclDataChannel>& |
| result) mutable { |
| if (!result.ok()) { |
| FDF_LOG(ERROR, "OpenAclDataChannel failed with FIDL error %s", |
| result.status_string()); |
| completer.ReplyError(result.status()); |
| return; |
| } |
| if (result->is_error()) { |
| FDF_LOG(ERROR, "OpenAclDataChannel failed with error %s", |
| zx_status_get_string(result->error_value())); |
| completer.ReplyError(result->error_value()); |
| return; |
| } |
| completer.ReplySuccess(); |
| }); |
| } |
| void BtHciBroadcom::OpenScoDataChannel(OpenScoDataChannelRequestView request, |
| OpenScoDataChannelCompleter::Sync& completer) { |
| // This interface is not implemented. |
| completer.ReplyError(ZX_ERR_NOT_SUPPORTED); |
| } |
| void BtHciBroadcom::ConfigureSco(ConfigureScoRequestView request, |
| ConfigureScoCompleter::Sync& completer) { |
| // This interface is not implemented. |
| completer.ReplyError(ZX_ERR_NOT_SUPPORTED); |
| } |
| void BtHciBroadcom::ResetSco(ResetScoCompleter::Sync& completer) { |
| // This interface is not implemented. |
| completer.ReplyError(ZX_ERR_NOT_SUPPORTED); |
| } |
| void BtHciBroadcom::OpenIsoDataChannel(OpenIsoDataChannelRequestView request, |
| OpenIsoDataChannelCompleter::Sync& completer) { |
| // This interface is not implemented. |
| completer.ReplyError(ZX_ERR_NOT_SUPPORTED); |
| } |
| void BtHciBroadcom::OpenSnoopChannel(OpenSnoopChannelRequestView request, |
| OpenSnoopChannelCompleter::Sync& completer) { |
| hci_client_->OpenSnoopChannel(std::move(request->channel)) |
| .ThenExactlyOnce( |
| [completer = completer.ToAsync()]( |
| fidl::WireUnownedResult<fuchsia_hardware_bluetooth::Hci::OpenSnoopChannel>& |
| result) mutable { |
| if (!result.ok()) { |
| FDF_LOG(ERROR, "OpenSnoopChannel failed with FIDL error %s", result.status_string()); |
| completer.ReplyError(result.status()); |
| return; |
| } |
| if (result->is_error()) { |
| FDF_LOG(ERROR, "OpenSnoopChannel failed with error %s", |
| zx_status_get_string(result->error_value())); |
| completer.ReplyError(result->error_value()); |
| return; |
| } |
| completer.ReplySuccess(); |
| }); |
| } |
| |
| void BtHciBroadcom::handle_unknown_method( |
| fidl::UnknownMethodMetadata<fuchsia_hardware_bluetooth::Hci> metadata, |
| fidl::UnknownMethodCompleter::Sync& completer) { |
| FDF_LOG(ERROR, "Unknown method in Hci protocol, closing with ZX_ERR_NOT_SUPPORTED"); |
| completer.Close(ZX_ERR_NOT_SUPPORTED); |
| } |
| |
| // driver_devfs::Connector<fuchsia_hardware_bluetooth::Vendor> |
| void BtHciBroadcom::Connect(fidl::ServerEnd<fuchsia_hardware_bluetooth::Vendor> request) { |
| vendor_binding_group_.AddBinding(dispatcher(), std::move(request), this, |
| fidl::kIgnoreBindingClosure); |
| |
| vendor_binding_group_.ForEachBinding( |
| [](const fidl::ServerBinding<fuchsia_hardware_bluetooth::Vendor>& binding) { |
| fidl::Arena arena; |
| auto builder = fuchsia_hardware_bluetooth::wire::VendorFeatures::Builder(arena); |
| builder.acl_priority_command(true); |
| fidl::Status status = fidl::WireSendEvent(binding)->OnFeatures(builder.Build()); |
| |
| if (status.status() != ZX_OK) { |
| FDF_LOG(ERROR, "Failed to send vendor features to bt-host: %s", status.status_string()); |
| } |
| }); |
| } |
| |
| zx_status_t BtHciBroadcom::ConnectToHciFidlProtocol() { |
| zx::result<fidl::ClientEnd<fuchsia_hardware_bluetooth::Hci>> client_end = |
| incoming()->Connect<fuchsia_hardware_bluetooth::HciService::Hci>(); |
| if (client_end.is_error()) { |
| FDF_LOG(ERROR, "Connect to fuchsia_hardware_bluetooth::Hci protocol failed: %s", |
| client_end.status_string()); |
| return client_end.status_value(); |
| } |
| |
| hci_client_ = |
| fidl::WireClient(*std::move(client_end), fdf::Dispatcher::GetCurrent()->async_dispatcher()); |
| |
| return ZX_OK; |
| } |
| |
| zx_status_t BtHciBroadcom::ConnectToSerialFidlProtocol() { |
| zx::result<fdf::ClientEnd<fuchsia_hardware_serialimpl::Device>> client_end = |
| incoming()->Connect<fuchsia_hardware_serialimpl::Service::Device>(); |
| if (client_end.is_error()) { |
| FDF_LOG(ERROR, "Connect to fuchsia_hardware_serialimpl::Device protocol failed: %s", |
| client_end.status_string()); |
| return client_end.status_value(); |
| } |
| |
| serial_client_ = fdf::WireSyncClient(*std::move(client_end)); |
| return ZX_OK; |
| } |
| |
| void BtHciBroadcom::EncodeSetAclPriorityCommand( |
| fuchsia_hardware_bluetooth::wire::VendorSetAclPriorityParams params, void* out_buffer) { |
| if (!params.has_connection_handle() || !params.has_priority() || !params.has_direction()) { |
| FDF_LOG(ERROR, |
| "The command cannot be encoded because the following fields are missing: %s %s %s", |
| params.has_connection_handle() ? "" : "connection_handle", |
| params.has_priority() ? "" : "priority", params.has_direction() ? "" : "direction"); |
| return; |
| } |
| BcmSetAclPriorityCmd command = { |
| .header = |
| { |
| .opcode = htole16(kBcmSetAclPriorityCmdOpCode), |
| .parameter_total_size = sizeof(BcmSetAclPriorityCmd) - sizeof(HciCommandHeader), |
| }, |
| .connection_handle = htole16(params.connection_handle()), |
| .priority = (params.priority() == fuchsia_hardware_bluetooth::VendorAclPriority::kNormal) |
| ? kBcmAclPriorityNormal |
| : kBcmAclPriorityHigh, |
| .direction = (params.direction() == fuchsia_hardware_bluetooth::VendorAclDirection::kSource) |
| ? kBcmAclDirectionSource |
| : kBcmAclDirectionSink, |
| }; |
| |
| memcpy(out_buffer, &command, sizeof(command)); |
| } |
| |
| fpromise::promise<std::vector<uint8_t>, zx_status_t> BtHciBroadcom::SendCommand(const void* command, |
| size_t length) { |
| // send HCI command |
| zx_status_t status = command_channel_.write(/*flags=*/0, command, static_cast<uint32_t>(length), |
| /*handles=*/nullptr, /*num_handles=*/0); |
| if (status != ZX_OK) { |
| FDF_LOG(ERROR, "command channel write failed %s", zx_status_get_string(status)); |
| return fpromise::make_result_promise<std::vector<uint8_t>, zx_status_t>( |
| fpromise::error(status)); |
| } |
| |
| return ReadEvent(); |
| } |
| |
| fpromise::promise<std::vector<uint8_t>, zx_status_t> BtHciBroadcom::ReadEvent() { |
| return executor_ |
| ->MakePromiseWaitHandle(zx::unowned_handle(command_channel_.get()), |
| ZX_CHANNEL_READABLE | ZX_CHANNEL_PEER_CLOSED) |
| .then([this](fpromise::result<zx_packet_signal_t, zx_status_t>&) |
| -> fpromise::result<std::vector<uint8_t>, zx_status_t> { |
| std::vector<uint8_t> read_buf(kChanReadBufLen, 0u); |
| uint32_t actual = 0; |
| zx_status_t status = command_channel_.read( |
| /*flags=*/0, read_buf.data(), /*handles=*/nullptr, kChanReadBufLen, |
| /*num_handles=*/0, &actual, /*actual_handles=*/nullptr); |
| if (status != ZX_OK) { |
| return fpromise::error(status); |
| } |
| |
| if (actual < sizeof(HciCommandComplete)) { |
| FDF_LOG(ERROR, "command channel read too short: %d < %lu", actual, |
| sizeof(HciCommandComplete)); |
| return fpromise::error(ZX_ERR_INTERNAL); |
| } |
| |
| HciCommandComplete event; |
| std::memcpy(&event, read_buf.data(), sizeof(HciCommandComplete)); |
| if (event.header.event_code != kHciEvtCommandCompleteEventCode || |
| event.header.parameter_total_size < kMinEvtParamSize) { |
| FDF_LOG(ERROR, "did not receive command complete or params too small"); |
| return fpromise::error(ZX_ERR_INTERNAL); |
| } |
| |
| if (event.return_code != 0) { |
| FDF_LOG(ERROR, "got command complete error %u", event.return_code); |
| return fpromise::error(ZX_ERR_INTERNAL); |
| } |
| |
| read_buf.resize(actual); |
| return fpromise::ok(std::move(read_buf)); |
| }); |
| } |
| |
| fpromise::promise<void, zx_status_t> BtHciBroadcom::SetBaudRate(uint32_t baud_rate) { |
| BcmSetBaudRateCmd command = { |
| .header = |
| { |
| .opcode = kBcmSetBaudRateCmdOpCode, |
| .parameter_total_size = sizeof(BcmSetBaudRateCmd) - sizeof(HciCommandHeader), |
| }, |
| .unused = 0, |
| .baud_rate = htole32(baud_rate), |
| }; |
| |
| return SendCommand(&command, sizeof(command)) |
| .and_then( |
| [this, baud_rate](const std::vector<uint8_t>&) -> fpromise::result<void, zx_status_t> { |
| fdf::Arena arena('CONF'); |
| auto result = serial_client_.buffer(arena)->Config( |
| baud_rate, fuchsia_hardware_serialimpl::wire::kSerialSetBaudRateOnly); |
| if (!result.ok()) { |
| return fpromise::error(result.status()); |
| } |
| if (result->is_error()) { |
| return fpromise::error(result->error_value()); |
| } |
| return fpromise::ok(); |
| }); |
| } |
| |
| fpromise::promise<void, zx_status_t> BtHciBroadcom::SetBdaddr( |
| const std::array<uint8_t, kMacAddrLen>& bdaddr) { |
| BcmSetBdaddrCmd command = { |
| .header = |
| { |
| .opcode = kBcmSetBdaddrCmdOpCode, |
| .parameter_total_size = sizeof(BcmSetBdaddrCmd) - sizeof(HciCommandHeader), |
| }, |
| .bdaddr = |
| {// HCI expects little endian. Swap bytes |
| bdaddr[5], bdaddr[4], bdaddr[3], bdaddr[2], bdaddr[1], bdaddr[0]}, |
| }; |
| |
| return SendCommand(&command.header, sizeof(command)).and_then([](std::vector<uint8_t>&) {}); |
| } |
| |
| fpromise::result<std::array<uint8_t, kMacAddrLen>, zx_status_t> |
| BtHciBroadcom::GetBdaddrFromBootloader() { |
| std::array<uint8_t, kMacAddrLen> mac_addr; |
| size_t actual_len; |
| |
| zx::result compat_device = incoming()->Connect<fuchsia_driver_compat::Service::Device>(); |
| if (compat_device.is_error()) { |
| return fpromise::error(compat_device.status_value()); |
| } |
| |
| fidl::WireResult metadata = fidl::WireCall(compat_device.value())->GetMetadata(); |
| if (!metadata.ok()) { |
| FDF_LOG(WARNING, "GetMetadata failed: %s", metadata.FormatDescription().c_str()); |
| return fpromise::error(metadata.error().status()); |
| } |
| |
| if (metadata.value().is_error()) { |
| FDF_LOG(WARNING, "GetMetadata failed: %s", |
| zx_status_get_string(metadata.value().error_value())); |
| return fpromise::error(metadata.value().error_value()); |
| } |
| |
| auto meta_vec = metadata.value().value(); |
| for (auto& entry : meta_vec->metadata) { |
| if (entry.type == DEVICE_METADATA_MAC_ADDRESS) { |
| entry.data.get_size(&actual_len); |
| if (actual_len < kMacAddrLen) { |
| return fpromise::error(ZX_ERR_INTERNAL); |
| } |
| entry.data.read(mac_addr.data(), 0, kMacAddrLen); |
| break; |
| } |
| } |
| |
| FDF_LOG(INFO, "got bootloader mac address %02x:%02x:%02x:%02x:%02x:%02x", mac_addr[0], |
| mac_addr[1], mac_addr[2], mac_addr[3], mac_addr[4], mac_addr[5]); |
| |
| return fpromise::ok(mac_addr); |
| } |
| |
| fpromise::promise<> BtHciBroadcom::LogControllerFallbackBdaddr() { |
| return SendCommand(&kReadBdaddrCmd, sizeof(kReadBdaddrCmd)) |
| .then([](fpromise::result<std::vector<uint8_t>, zx_status_t>& result) { |
| char fallback_addr[18] = "<unknown>"; |
| |
| if (result.is_ok() && sizeof(ReadBdaddrCommandComplete) == result.value().size()) { |
| ReadBdaddrCommandComplete event; |
| std::memcpy(&event, result.value().data(), result.value().size()); |
| // HCI returns data as little endian. Swap bytes |
| snprintf(fallback_addr, sizeof(fallback_addr), "%02x:%02x:%02x:%02x:%02x:%02x", |
| event.bdaddr[5], event.bdaddr[4], event.bdaddr[3], event.bdaddr[2], |
| event.bdaddr[1], event.bdaddr[0]); |
| } |
| |
| FDF_LOG(ERROR, "error getting mac address from bootloader: %s. Fallback address: %s.", |
| zx_status_get_string(result.is_ok() ? ZX_OK : result.error()), fallback_addr); |
| }); |
| } |
| |
| constexpr auto kOpenFlags = |
| fuchsia_io::wire::OpenFlags::kRightReadable | fuchsia_io::wire::OpenFlags::kNotDirectory; |
| |
| fpromise::promise<void, zx_status_t> BtHciBroadcom::LoadFirmware() { |
| zx::vmo fw_vmo; |
| size_t fw_size; |
| |
| // If there's no firmware for this PID, we don't expect the bind to happen without a |
| // corresponding entry in the firmware table. Please double-check the PID value and add an entry |
| // to the firmware table if it's valid. |
| ZX_ASSERT_MSG(kFirmwareMap.find(serial_pid_) != kFirmwareMap.end(), "no mapping for PID: %u", |
| serial_pid_); |
| |
| std::string full_filename = "/pkg/lib/firmware/"; |
| full_filename.append(kFirmwareMap.at(serial_pid_).c_str()); |
| |
| auto client = incoming()->Open<fuchsia_io::File>(full_filename.c_str(), kOpenFlags); |
| if (client.is_error()) { |
| FDF_LOG(WARNING, "Open firmware file failed: %s", zx_status_get_string(client.error_value())); |
| return fpromise::make_error_promise(client.error_value()); |
| } |
| |
| fidl::WireResult backing_memory_result = |
| fidl::WireCall(*client)->GetBackingMemory(fuchsia_io::wire::VmoFlags::kRead); |
| if (!backing_memory_result.ok()) { |
| if (backing_memory_result.is_peer_closed()) { |
| FDF_LOG(WARNING, "Failed to get backing memory: Peer closed"); |
| return fpromise::make_error_promise(ZX_ERR_NOT_FOUND); |
| } |
| FDF_LOG(WARNING, "Failed to get backing memory: %s", |
| zx_status_get_string(backing_memory_result.status())); |
| return fpromise::make_error_promise(backing_memory_result.status()); |
| } |
| |
| const auto* backing_memory = backing_memory_result.Unwrap(); |
| if (backing_memory->is_error()) { |
| FDF_LOG(WARNING, "Failed to get backing memory: %s", |
| zx_status_get_string(backing_memory->error_value())); |
| return fpromise::make_error_promise(backing_memory->error_value()); |
| } |
| |
| zx::vmo& backing_vmo = backing_memory->value()->vmo; |
| if (zx_status_t status = backing_vmo.get_prop_content_size(&fw_size); status != ZX_OK) { |
| FDF_LOG(WARNING, "Failed to get vmo size: %s", zx_status_get_string(status)); |
| return fpromise::make_error_promise(status); |
| } |
| fw_vmo.reset(backing_vmo.release()); |
| |
| return SendCommand(&kStartFirmwareDownloadCmd, sizeof(kStartFirmwareDownloadCmd)) |
| .or_else([](zx_status_t& status) -> fpromise::result<std::vector<uint8_t>, zx_status_t> { |
| FDF_LOG(ERROR, "could not load firmware file"); |
| return fpromise::error(status); |
| }) |
| .and_then([this](std::vector<uint8_t>& /*event*/) mutable { |
| // give time for placing firmware in download mode |
| return executor_->MakeDelayedPromise(zx::duration(kFirmwareDownloadDelay)) |
| .then([](fpromise::result<>& /*result*/) { |
| return fpromise::result<void, zx_status_t>(fpromise::ok()); |
| }); |
| }) |
| .and_then([this, fw_vmo = std::move(fw_vmo), fw_size]() mutable { |
| // The firmware is a sequence of HCI commands containing the firmware data as payloads. |
| return SendVmoAsCommands(std::move(fw_vmo), fw_size, /*offset=*/0); |
| }) |
| .and_then([this]() -> fpromise::promise<void, zx_status_t> { |
| if (is_uart_) { |
| // firmware switched us back to 115200. switch back to kTargetBaudRate. |
| fdf::Arena arena('CONF'); |
| auto result = serial_client_.buffer(arena)->Config( |
| kDefaultBaudRate, fuchsia_hardware_serialimpl::wire::kSerialSetBaudRateOnly); |
| if (!result.ok()) { |
| return fpromise::make_result_promise(fpromise::error(result.status())); |
| } |
| if (result->is_error()) { |
| return fpromise::make_result_promise(fpromise::error(result->error_value())); |
| } |
| |
| return executor_->MakeDelayedPromise(kBaudRateSwitchDelay) |
| .then( |
| [this](fpromise::result<>& /*result*/) { return SetBaudRate(kTargetBaudRate); }); |
| } |
| return fpromise::make_result_promise<void, zx_status_t>(fpromise::ok()); |
| }) |
| .and_then([]() { FDF_LOG(INFO, "firmware loaded"); }); |
| } |
| |
| fpromise::promise<void, zx_status_t> BtHciBroadcom::SendVmoAsCommands(zx::vmo vmo, size_t size, |
| size_t offset) { |
| if (offset == size) { |
| return fpromise::make_result_promise<void, zx_status_t>(fpromise::ok()); |
| } |
| |
| uint8_t buffer[kMaxHciCommandSize]; |
| |
| size_t remaining = size - offset; |
| size_t read_amount = (remaining > sizeof(buffer) ? sizeof(buffer) : remaining); |
| |
| if (read_amount < sizeof(HciCommandHeader)) { |
| FDF_LOG(ERROR, "short HCI command in firmware download"); |
| return fpromise::make_error_promise(ZX_ERR_INTERNAL); |
| } |
| |
| zx_status_t status = vmo.read(buffer, offset, read_amount); |
| if (status != ZX_OK) { |
| return fpromise::make_error_promise(status); |
| } |
| |
| HciCommandHeader header; |
| std::memcpy(&header, buffer, sizeof(HciCommandHeader)); |
| size_t length = header.parameter_total_size + sizeof(header); |
| if (read_amount < length) { |
| FDF_LOG(ERROR, "short HCI command in firmware download"); |
| return fpromise::make_error_promise(ZX_ERR_INTERNAL); |
| } |
| |
| offset += length; |
| |
| return SendCommand(buffer, length) |
| .then([this, vmo = std::move(vmo), size, |
| offset](fpromise::result<std::vector<uint8_t>, zx_status_t>& result) mutable |
| -> fpromise::promise<void, zx_status_t> { |
| if (result.is_error()) { |
| FDF_LOG(ERROR, "SendCommand failed in firmware download: %s", |
| zx_status_get_string(result.error())); |
| return fpromise::make_error_promise<zx_status_t>(result.error()); |
| } |
| |
| // Send the next command |
| return SendVmoAsCommands(std::move(vmo), size, offset); |
| }); |
| } |
| |
| fpromise::promise<void, zx_status_t> BtHciBroadcom::Initialize() { |
| zx::channel theirs; |
| zx_status_t status = zx::channel::create(/*flags=*/0, &command_channel_, &theirs); |
| if (status != ZX_OK) { |
| return OnInitializeComplete(status); |
| } |
| |
| FDF_LOG(DEBUG, "opening command channel"); |
| auto result = hci_client_.sync()->OpenCommandChannel(std::move(theirs)); |
| if (!result.ok()) { |
| FDF_LOG(ERROR, "OpenCommandChannel failed FIDL error: %s", result.status_string()); |
| return OnInitializeComplete(result.status()); |
| } |
| if (result->is_error()) { |
| FDF_LOG(ERROR, "OpenCommandChannel failed : %s", zx_status_get_string(result->error_value())); |
| return OnInitializeComplete(result->error_value()); |
| } |
| |
| FDF_LOG(DEBUG, "sending initial reset command"); |
| return SendCommand(&kResetCmd, sizeof(kResetCmd)) |
| .and_then([this](std::vector<uint8_t>&) -> fpromise::promise<void, zx_status_t> { |
| if (is_uart_) { |
| FDF_LOG(DEBUG, "setting baud rate to %u", kTargetBaudRate); |
| // switch baud rate to TARGET_BAUD_RATE |
| return SetBaudRate(kTargetBaudRate); |
| } |
| return fpromise::make_result_promise<void, zx_status_t>(fpromise::ok()); |
| }) |
| .and_then([this]() { |
| FDF_LOG(DEBUG, "loading firmware"); |
| return LoadFirmware(); |
| }) |
| .and_then([this]() { |
| FDF_LOG(DEBUG, "sending reset command"); |
| return SendCommand(&kResetCmd, sizeof(kResetCmd)); |
| }) |
| .and_then([this](std::vector<uint8_t>&) -> fpromise::promise<void, zx_status_t> { |
| FDF_LOG(DEBUG, "setting BDADDR to value from bootloader"); |
| fpromise::result<std::array<uint8_t, kMacAddrLen>, zx_status_t> bdaddr = |
| GetBdaddrFromBootloader(); |
| |
| if (bdaddr.is_error()) { |
| return LogControllerFallbackBdaddr().then( |
| [](fpromise::result<>&) -> fpromise::result<void, zx_status_t> { |
| return fpromise::ok(); |
| }); |
| } |
| |
| // send Set BDADDR command |
| return SetBdaddr(bdaddr.value()); |
| }) |
| .and_then([this]() { return AddNode(); }) |
| .then([this](fpromise::result<void, zx_status_t>& result) { |
| zx_status_t status = result.is_ok() ? ZX_OK : result.error(); |
| return OnInitializeComplete(status); |
| }); |
| } |
| |
| fpromise::promise<void, zx_status_t> BtHciBroadcom::OnInitializeComplete(zx_status_t status) { |
| // We're done with the command channel. Close it so that it can be opened by |
| // the host stack after the device becomes visible. |
| if (command_channel_.is_valid()) { |
| FDF_LOG(DEBUG, "closing command channel"); |
| command_channel_.reset(); |
| } |
| |
| if (status != ZX_OK) { |
| FDF_LOG(ERROR, "device initialization failed: %s", zx_status_get_string(status)); |
| return fpromise::make_error_promise(status); |
| } |
| |
| FDF_LOG(INFO, "initialization completed successfully."); |
| return fpromise::make_result_promise<void, zx_status_t>(fpromise::ok()); |
| } |
| |
| fpromise::promise<void, zx_status_t> BtHciBroadcom::AddNode() { |
| zx::result connector = devfs_connector_.Bind(dispatcher()); |
| if (connector.is_error()) { |
| FDF_LOG(ERROR, "Failed to bind devfs connecter to dispatcher: %s", connector.status_string()); |
| return fpromise::make_error_promise(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("bt-hci-broadcom") |
| .devfs_args(devfs) |
| .Build(); |
| |
| auto controller_endpoints = fidl::CreateEndpoints<fuchsia_driver_framework::NodeController>(); |
| if (controller_endpoints.is_error()) { |
| FDF_LOG(ERROR, "Create node controller end points failed: %s", |
| zx_status_get_string(controller_endpoints.error_value())); |
| return fpromise::make_error_promise(controller_endpoints.error_value()); |
| } |
| |
| // Create the endpoints of fuchsia_driver_framework::Node protocol for the child node, and hold |
| // the client end of it, because no driver will bind to the child node. |
| auto child_node_endpoints = fidl::CreateEndpoints<fuchsia_driver_framework::Node>(); |
| if (child_node_endpoints.is_error()) { |
| FDF_LOG(ERROR, "Create child node end points failed: %s", |
| zx_status_get_string(child_node_endpoints.error_value())); |
| return fpromise::make_error_promise(child_node_endpoints.error_value()); |
| } |
| |
| // Add bt-hci-broadcom child node. |
| fpromise::bridge<void, zx_status_t> bridge; |
| node_ |
| ->AddChild(args, std::move(controller_endpoints->server), |
| std::move(child_node_endpoints->server)) |
| .Then([this, completer = std::move(bridge.completer), |
| child_node_client = std::move(child_node_endpoints->client), |
| child_controller_client = std::move(controller_endpoints->client)]( |
| fidl::WireUnownedResult<fuchsia_driver_framework::Node::AddChild>& |
| child_result) mutable { |
| if (!child_result.ok()) { |
| FDF_LOG(ERROR, "Failed to add bt-hci-broadcom node, FIDL error: %s", |
| child_result.status_string()); |
| completer.complete_error(child_result.status()); |
| return; |
| } |
| |
| if (child_result->is_error()) { |
| FDF_LOG(ERROR, "Failed to add bt-hci-broadcom node: %u", |
| static_cast<uint32_t>(child_result->error_value())); |
| completer.complete_error(ZX_ERR_INTERNAL); |
| return; |
| } |
| |
| child_node_.Bind(std::move(child_node_client), dispatcher(), this); |
| node_controller_.Bind(std::move(child_controller_client), dispatcher(), this); |
| completer.complete_ok(); |
| }); |
| |
| return bridge.consumer.promise(); |
| } |
| |
| void BtHciBroadcom::CompleteStart(zx_status_t status) { |
| if (start_completer_.has_value()) { |
| start_completer_.value()(zx::make_result(status)); |
| start_completer_.reset(); |
| } else { |
| FDF_LOG(ERROR, "CompleteStart called without start_completer_."); |
| } |
| } |
| |
| } // namespace bt_hci_broadcom |
| |
| FUCHSIA_DRIVER_EXPORT(bt_hci_broadcom::BtHciBroadcom); |