blob: 2bef2c72cda9973ae4b4c3bf38ae34fc01b90b5f [file] [log] [blame]
// 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);