blob: d7d5d726637ef8305d171f43c8cc0edc94405357 [file] [log] [blame]
// Copyright 2017 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 "device.h"
#include <fuchsia/hardware/bt/hci/c/banjo.h>
#include <fuchsia/hardware/usb/c/banjo.h>
#include <lib/ddk/binding_driver.h>
#include <lib/ddk/device.h>
#include <lib/ddk/driver.h>
#include <lib/zx/vmo.h>
#include <zircon/process.h>
#include <zircon/status.h>
#include <zircon/syscalls.h>
#include <future>
#include <fbl/string_printf.h>
#include "firmware_loader.h"
#include "logging.h"
namespace btintel {
// USB Product IDs that use the "secure" firmware method.
static constexpr uint16_t sfi_product_ids[] = {
0x0025, // Thunder Peak (9160/9260)
0x0a2b, // Snowfield Peak (8260)
0x0aaa, // Jefferson Peak (9460/9560)
0x0026, // Harrison Peak (AX201)
0x0032, // Sun Peak (AX210)
};
// USB Product IDs that use the "legacy" firmware loading method.
static constexpr uint16_t legacy_firmware_loading_ids[] = {
0x0025, // Thunder Peak (9160/9260)
0x0a2b, // Snowfield Peak (8260)
0x0aaa, // Jefferson Peak (9460/9560)
0x0026, // Harrison Peak (AX201)
};
Device::Device(zx_device_t* device, bt_hci_protocol_t* hci, bool secure,
bool legacy_firmware_loading)
: DeviceType(device),
devfs_connector_(fit::bind_member<&Device::Connect>(this)),
hci_(hci),
secure_(secure),
firmware_loaded_(false),
legacy_firmware_loading_(legacy_firmware_loading) {}
zx_status_t Device::bt_intel_bind(void* ctx, zx_device_t* device) {
tracef("bind\n");
usb_protocol_t usb;
zx_status_t result = device_get_protocol(device, ZX_PROTOCOL_USB, &usb);
if (result != ZX_OK) {
errorf("couldn't get USB protocol: %s\n", zx_status_get_string(result));
return result;
}
usb_device_descriptor_t dev_desc;
usb_get_device_descriptor(&usb, &dev_desc);
// Whether this device uses the "secure" firmware method.
bool secure = false;
for (uint16_t id : sfi_product_ids) {
if (dev_desc.id_product == id) {
secure = true;
break;
}
}
// Whether this device uses the "legacy" firmware loading method.
bool legacy_firmware_loading = false;
for (uint16_t id : legacy_firmware_loading_ids) {
if (dev_desc.id_product == id) {
legacy_firmware_loading = true;
break;
}
}
bt_hci_protocol_t hci;
result = device_get_protocol(device, ZX_PROTOCOL_BT_HCI, &hci);
if (result != ZX_OK) {
errorf("couldn't get BT_HCI protocol: %s\n", zx_status_get_string(result));
return result;
}
auto btdev = new btintel::Device(device, &hci, secure, legacy_firmware_loading);
result = btdev->Bind();
if (result != ZX_OK) {
errorf("failed binding device: %s\n", zx_status_get_string(result));
delete btdev;
return result;
}
// Bind succeeded and devmgr is now responsible for releasing |btdev|
// The device's init hook will load the firmware.
return ZX_OK;
}
zx_status_t Device::Bind() {
ddk::DeviceAddArgs args("bt-hci-intel");
args.set_flags(DEVICE_ADD_NON_BINDABLE);
return DdkAdd(args);
}
void Device::DdkInit(ddk::InitTxn init_txn) {
init_thread_ = std::thread(
[this, txn = std::move(init_txn)]() mutable { LoadFirmware(std::move(txn), secure_); });
}
zx_status_t Device::LoadFirmware(ddk::InitTxn init_txn, bool secure) {
infof("LoadFirmware(secure: %s, firmware_loading: %s)", (secure ? "yes" : "no"),
(legacy_firmware_loading_ ? "legacy" : "new"));
// TODO(armansito): Track metrics for initialization failures.
zx_status_t status;
zx::channel our_cmd, their_cmd, our_acl, their_acl;
status = zx::channel::create(0, &our_cmd, &their_cmd);
if (status != ZX_OK) {
return InitFailed(std::move(init_txn), status, "failed to create command channel");
}
status = zx::channel::create(0, &our_acl, &their_acl);
if (status != ZX_OK) {
return InitFailed(std::move(init_txn), status, "failed to create ACL channel");
}
// Get the channels
status = BtHciOpenCommandChannel(std::move(their_cmd));
if (status != ZX_OK) {
return InitFailed(std::move(init_txn), status, "failed to bind command channel");
}
status = BtHciOpenAclDataChannel(std::move(their_acl));
if (status != ZX_OK) {
return InitFailed(std::move(init_txn), status, "failed to bind ACL channel");
}
if (secure) {
status = LoadSecureFirmware(&our_cmd, &our_acl);
} else {
status = LoadLegacyFirmware(&our_cmd, &our_acl);
}
if (status != ZX_OK) {
return InitFailed(std::move(init_txn), status, "failed to initialize controller");
}
firmware_loaded_ = true;
init_txn.Reply(ZX_OK); // This will make the device visible and able to be unbound.
return ZX_OK;
}
zx_status_t Device::InitFailed(ddk::InitTxn init_txn, zx_status_t status, const char* note) {
errorf("%s: %s", note, zx_status_get_string(status));
init_txn.Reply(status);
return status;
}
zx_handle_t Device::MapFirmware(const char* name, uintptr_t* fw_addr, size_t* fw_size) {
zx_handle_t vmo = ZX_HANDLE_INVALID;
size_t size;
zx_status_t status = load_firmware(zxdev(), name, &vmo, &size);
if (status != ZX_OK) {
errorf("failed to load firmware '%s': %s", name, zx_status_get_string(status));
return ZX_HANDLE_INVALID;
}
status = zx_vmar_map(zx_vmar_root_self(), ZX_VM_PERM_READ, 0, vmo, 0, size, fw_addr);
if (status != ZX_OK) {
errorf("firmware map failed: %s", zx_status_get_string(status));
return ZX_HANDLE_INVALID;
}
*fw_size = size;
return vmo;
}
void Device::DdkUnbind(ddk::UnbindTxn txn) {
tracef("unbind");
txn.Reply();
}
void Device::DdkRelease() {
if (init_thread_.joinable()) {
init_thread_.join();
}
delete this;
}
zx_status_t Device::DdkGetProtocol(uint32_t proto_id, void* out_proto) {
if (proto_id != ZX_PROTOCOL_BT_HCI) {
return ZX_ERR_NOT_SUPPORTED;
}
bt_hci_protocol_t* hci_proto = static_cast<bt_hci_protocol_t*>(out_proto);
// Forward the underlying bt-transport ops.
hci_.GetProto(hci_proto);
return ZX_OK;
}
void Device::OpenCommandChannel(OpenCommandChannelRequestView request,
OpenCommandChannelCompleter::Sync& completer) {
if (zx_status_t status = BtHciOpenCommandChannel(std::move(request->channel)); status != ZX_OK) {
completer.ReplyError(status);
}
completer.ReplySuccess();
}
void Device::OpenAclDataChannel(OpenAclDataChannelRequestView request,
OpenAclDataChannelCompleter::Sync& completer) {
if (zx_status_t status = BtHciOpenAclDataChannel(std::move(request->channel)); status != ZX_OK) {
completer.ReplyError(status);
}
completer.ReplySuccess();
}
void Device::OpenScoDataChannel(OpenScoDataChannelRequestView request,
OpenScoDataChannelCompleter::Sync& completer) {
// This interface is not implemented.
completer.ReplyError(ZX_ERR_NOT_SUPPORTED);
}
void Device::ConfigureSco(ConfigureScoRequestView request, ConfigureScoCompleter::Sync& completer) {
// This interface is not implemented.
completer.ReplyError(ZX_ERR_NOT_SUPPORTED);
}
void Device::ResetSco(ResetScoCompleter::Sync& completer) {
// This interface is not implemented.
completer.ReplyError(ZX_ERR_NOT_SUPPORTED);
}
void Device::OpenIsoDataChannel(OpenIsoDataChannelRequestView request,
OpenIsoDataChannelCompleter::Sync& completer) {
// This interface is not implemented.
completer.ReplyError(ZX_ERR_NOT_SUPPORTED);
}
void Device::OpenSnoopChannel(OpenSnoopChannelRequestView request,
OpenSnoopChannelCompleter::Sync& completer) {
if (zx_status_t status = BtHciOpenSnoopChannel(std::move(request->channel)); status != ZX_OK) {
completer.ReplyError(status);
}
completer.ReplySuccess();
}
void Device::handle_unknown_method(
fidl::UnknownMethodMetadata<fuchsia_hardware_bluetooth::Hci> metadata,
fidl::UnknownMethodCompleter::Sync& completer) {
zxlogf(ERROR, "Unknown method in Hci request, closing with ZX_ERR_NOT_SUPPORTED");
completer.Close(ZX_ERR_NOT_SUPPORTED);
}
void Device::EncodeCommand(EncodeCommandRequestView request,
EncodeCommandCompleter::Sync& completer) {
completer.ReplyError(ZX_ERR_NOT_SUPPORTED);
}
void Device::OpenHci(OpenHciCompleter::Sync& completer) {
auto endpoints = fidl::CreateEndpoints<fuchsia_hardware_bluetooth::Hci>();
if (endpoints.is_error()) {
zxlogf(ERROR, "Failed to create endpoints: %s", zx_status_get_string(endpoints.error_value()));
completer.ReplyError(endpoints.error_value());
return;
}
hci_binding_group_.AddBinding(fdf::Dispatcher::GetCurrent()->async_dispatcher(),
std::move(endpoints->server), this, fidl::kIgnoreBindingClosure);
completer.ReplySuccess(std::move(endpoints->client));
}
void Device::handle_unknown_method(
fidl::UnknownMethodMetadata<fuchsia_hardware_bluetooth::Vendor> metadata,
fidl::UnknownMethodCompleter::Sync& completer) {
zxlogf(ERROR, "Unknown method in Vendor request, closing with ZX_ERR_NOT_SUPPORTED");
completer.Close(ZX_ERR_NOT_SUPPORTED);
}
// driver_devfs::Connector<fuchsia_hardware_bluetooth::Vendor>
void Device::Connect(fidl::ServerEnd<fuchsia_hardware_bluetooth::Vendor> request) {
vendor_binding_group_.AddBinding(fdf::Dispatcher::GetCurrent()->async_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(false);
fidl::Status status = fidl::WireSendEvent(binding)->OnFeatures(builder.Build());
if (status.status() != ZX_OK) {
zxlogf(ERROR, "Failed to send vendor features to bt-host: %s", status.status_string());
}
});
}
zx_status_t Device::LoadSecureFirmware(zx::channel* cmd, zx::channel* acl) {
ZX_DEBUG_ASSERT(cmd);
ZX_DEBUG_ASSERT(acl);
VendorHci hci(cmd);
// Bring the controller to a well-defined default state.
// Send an initial reset. If the controller sends a "command not supported"
// event on newer models, this likely means that the controller is in
// bootloader mode and we can ignore the error.
auto hci_status = hci.SendHciReset();
if (hci_status == pw::bluetooth::emboss::StatusCode::UNKNOWN_COMMAND) {
infof("Ignoring \"Unknown Command\" error while in bootloader mode");
} else if (hci_status != pw::bluetooth::emboss::StatusCode::SUCCESS) {
errorf("HCI_Reset failed (status: 0x%02hhx)", static_cast<unsigned char>(hci_status));
return ZX_ERR_BAD_STATE;
}
// Newer Intel controllers that use the "secure send" method can send HCI
// events over the bulk endpoint. Enable this before sending the initial
// ReadVersion command.
hci.enable_events_on_bulk(acl);
btintel::SecureBootEngineType engine_type = btintel::SecureBootEngineType::kRSA;
fbl::String fw_filename;
if (legacy_firmware_loading_) {
ReadVersionReturnParams version = hci.SendReadVersion();
if (version.status != pw::bluetooth::emboss::StatusCode::SUCCESS) {
errorf("failed to obtain version information");
return ZX_ERR_BAD_STATE;
}
// If we're already in firmware mode, we're done.
if (version.fw_variant == kFirmwareFirmwareVariant) {
infof("firmware loaded (variant %d, revision %d)", version.hw_variant, version.hw_revision);
return ZX_OK;
}
// If we reached here then the controller must be in bootloader mode.
if (version.fw_variant != kBootloaderFirmwareVariant) {
errorf("unsupported firmware variant (0x%x)", version.fw_variant);
return ZX_ERR_NOT_SUPPORTED;
}
ReadBootParamsReturnParams boot_params = hci.SendReadBootParams();
if (boot_params.status != pw::bluetooth::emboss::StatusCode::SUCCESS) {
errorf("failed to read boot parameters");
return ZX_ERR_BAD_STATE;
}
// Map the firmware file into memory.
fw_filename = fbl::StringPrintf("ibt-%d-%d.sfi", version.hw_variant, boot_params.dev_revid);
} else {
ReadVersionReturnParamsTlv version = hci.SendReadVersionTlv();
engine_type = (version.secure_boot_engine_type == 0x01) ? btintel::SecureBootEngineType::kECDSA
: btintel::SecureBootEngineType::kRSA;
// If we're already in firmware mode, we're done.
if (version.current_mode_of_operation == kCurrentModeOfOperationOperationalFirmware) {
infof("firmware already loaded");
return ZX_OK;
}
fw_filename = fbl::StringPrintf("ibt-%04x-%04x.sfi", 0x0041, 0x0041);
}
zx::vmo fw_vmo;
uintptr_t fw_addr;
size_t fw_size;
fw_vmo.reset(MapFirmware(fw_filename.c_str(), &fw_addr, &fw_size));
if (!fw_vmo) {
errorf("failed to map firmware");
return ZX_ERR_NOT_SUPPORTED;
}
FirmwareLoader loader(cmd, acl);
// The boot addr differs for different firmware. Save it for later.
uint32_t boot_addr = 0x00000000;
auto status = loader.LoadSfi(reinterpret_cast<void*>(fw_addr), fw_size, engine_type, &boot_addr);
zx_vmar_unmap(zx_vmar_root_self(), fw_addr, fw_size);
if (status == FirmwareLoader::LoadStatus::kError) {
errorf("failed to load SFI firmware");
return ZX_ERR_BAD_STATE;
}
// Switch the controller into firmware mode.
hci.SendVendorReset(boot_addr);
infof("firmware loaded using %s", fw_filename.c_str());
return ZX_OK;
}
zx_status_t Device::LoadLegacyFirmware(zx::channel* cmd, zx::channel* acl) {
ZX_DEBUG_ASSERT(cmd);
ZX_DEBUG_ASSERT(acl);
VendorHci hci(cmd);
// Bring the controller to a well-defined default state.
auto hci_status = hci.SendHciReset();
if (hci_status != pw::bluetooth::emboss::StatusCode::SUCCESS) {
errorf("HCI_Reset failed (status: 0x%02hhx)", static_cast<unsigned char>(hci_status));
return ZX_ERR_BAD_STATE;
}
ReadVersionReturnParams version = hci.SendReadVersion();
if (version.status != pw::bluetooth::emboss::StatusCode::SUCCESS) {
errorf("failed to obtain version information");
return ZX_ERR_BAD_STATE;
}
if (version.fw_patch_num > 0) {
infof("controller already patched");
return ZX_OK;
}
auto fw_filename = fbl::StringPrintf(
"ibt-hw-%x.%x.%x-fw-%x.%x.%x.%x.%x.bseq", version.hw_platform, version.hw_variant,
version.hw_revision, version.fw_variant, version.fw_revision, version.fw_build_num,
version.fw_build_week, version.fw_build_year);
zx::vmo fw_vmo;
uintptr_t fw_addr;
size_t fw_size;
fw_vmo.reset(MapFirmware(fw_filename.c_str(), &fw_addr, &fw_size));
// Try the fallback patch file on initial failure.
if (!fw_vmo) {
// Try the fallback patch file
fw_filename = fbl::StringPrintf("ibt-hw-%x.%x.bseq", version.hw_platform, version.hw_variant);
fw_vmo.reset(MapFirmware(fw_filename.c_str(), &fw_addr, &fw_size));
}
// Abort if the fallback file failed to load too.
if (!fw_vmo) {
errorf("failed to map firmware");
return ZX_ERR_NOT_SUPPORTED;
}
FirmwareLoader loader(cmd, acl);
hci.EnterManufacturerMode();
auto result = loader.LoadBseq(reinterpret_cast<void*>(fw_addr), fw_size);
hci.ExitManufacturerMode(result == FirmwareLoader::LoadStatus::kPatched
? MfgDisableMode::kPatchesEnabled
: MfgDisableMode::kNoPatches);
zx_vmar_unmap(zx_vmar_root_self(), fw_addr, fw_size);
if (result == FirmwareLoader::LoadStatus::kError) {
errorf("failed to patch controller");
return ZX_ERR_BAD_STATE;
}
infof("controller patched using %s", fw_filename.c_str());
return ZX_OK;
}
zx_status_t Device::BtHciOpenCommandChannel(zx::channel in) {
return hci_.OpenCommandChannel(std::move(in));
}
zx_status_t Device::BtHciOpenAclDataChannel(zx::channel in) {
return hci_.OpenAclDataChannel(std::move(in));
}
zx_status_t Device::BtHciOpenScoChannel(zx::channel in) {
return hci_.OpenScoChannel(std::move(in));
}
void Device::BtHciConfigureSco(sco_coding_format_t coding_format, sco_encoding_t encoding,
sco_sample_rate_t sample_rate,
bt_hci_configure_sco_callback callback, void* cookie) {
hci_.ConfigureSco(coding_format, encoding, sample_rate, callback, cookie);
}
void Device::BtHciResetSco(bt_hci_reset_sco_callback callback, void* cookie) {
hci_.ResetSco(callback, cookie);
}
zx_status_t Device::BtHciOpenIsoDataChannel(zx::channel in) { return ZX_ERR_NOT_SUPPORTED; }
zx_status_t Device::BtHciOpenSnoopChannel(zx::channel in) {
return hci_.OpenSnoopChannel(std::move(in));
}
static constexpr zx_driver_ops_t btintel_driver_ops = []() {
zx_driver_ops_t ops = {};
ops.version = DRIVER_OPS_VERSION;
ops.bind = btintel::Device::bt_intel_bind;
return ops;
}();
} // namespace btintel
ZIRCON_DRIVER(bt_hci_intel, btintel::btintel_driver_ops, "fuchsia", "0.1");