blob: b31939e5936aa848a362f6df27c2a8ff1ae6a79e [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 "vendor_hci.h"
#include <lib/zx/clock.h>
#include <lib/zx/object.h>
#include <lib/zx/time.h>
#include <zircon/status.h>
#include <zircon/syscalls.h>
#include <algorithm>
#include <fbl/algorithm.h>
#include "logging.h"
namespace btintel {
using ::bt::hci::CommandPacket;
namespace {
constexpr size_t kMaxSecureSendArgLen = 252;
constexpr auto kInitTimeoutMs = zx::sec(10);
} // namespace
VendorHci::VendorHci(zx::channel* ctrl) : ctrl_(ctrl), acl_(nullptr), manufacturer_(false) {}
// Fetch unsigned integer values from 'p' with 'fetch_len' bytes at maximum (little-endian).
uint32_t fetch_tlv_value(const uint8_t* p, size_t fetch_len) {
size_t len = p[1];
uint32_t val = 0; // store the return value.
ZX_DEBUG_ASSERT(len <= sizeof(val));
ZX_DEBUG_ASSERT(len >= fetch_len);
// Only load the actual number of bytes .
fetch_len = fetch_len > len ? len : fetch_len;
p += 2; // Skip 'type' and 'length'. Now points to 'value'.
for (size_t i = fetch_len; i > 0; i--) {
val = (val << 8) + p[i - 1];
}
return val;
}
ReadVersionReturnParamsTlv parse_tlv_version_return_params(const uint8_t* p, size_t len) {
ReadVersionReturnParamsTlv params = {};
// Ensure the given byte stream contains the status code, type, and length fields.
if (len <= 2)
return ReadVersionReturnParamsTlv{
.status = pw::bluetooth::emboss::StatusCode::PARAMETER_OUT_OF_MANDATORY_RANGE};
// The first byte is the status code. Extract and strip it before we traverse the TLVs.
params.status = static_cast<pw::bluetooth::emboss::StatusCode>(*(p++));
len--;
for (size_t idx = 0; idx < len;) {
// ensure at least the Tag and the Length fields.
size_t remain_len = len - idx;
if (remain_len < 2)
return ReadVersionReturnParamsTlv{
.status = pw::bluetooth::emboss::StatusCode::PARAMETER_OUT_OF_MANDATORY_RANGE};
// After excluding the Type and the Length field, ensure there is still room for the Value
// field.
size_t len_of_value = p[idx + 1];
if ((remain_len - 2) < len_of_value)
return ReadVersionReturnParamsTlv{
.status = pw::bluetooth::emboss::StatusCode::PARAMETER_OUT_OF_MANDATORY_RANGE};
switch (p[idx]) {
uint32_t v;
case 0x00: // End of the TLV records.
break;
case 0x10: // CNVi hardware version
v = fetch_tlv_value(&p[idx], 4);
params.CNVi = (((v >> 0) & 0xf) << 12) | (((v >> 4) & 0xf) << 0) | (((v >> 8) & 0xf) << 4) |
(((v >> 24) & 0xf) << 8);
break;
case 0x11: // CNVR hardware version
v = fetch_tlv_value(&p[idx], 4);
params.CNVR = (((v >> 0) & 0xf) << 12) | (((v >> 4) & 0xf) << 0) | (((v >> 8) & 0xf) << 4) |
(((v >> 24) & 0xf) << 8);
break;
case 0x12: // hardware info
v = fetch_tlv_value(&p[idx], 4);
params.hw_platform = (v >> 8) & 0xff; // 0x37 for now.
params.hw_variant = (v >> 16) & 0x3f; // 0x17 -- Typhoon Peak
// 0x1c -- Gale Peak
break;
case 0x16: // Device revision
params.device_revision = fetch_tlv_value(&p[idx], 2);
break;
case 0x1c: // Current mode of operation
params.current_mode_of_operation = fetch_tlv_value(&p[idx], 1);
break;
case 0x1d: // Timestamp
v = fetch_tlv_value(&p[idx], 2);
params.timestamp_calendar_week = v >> 0;
params.timestamp_year = v >> 8;
break;
case 0x1e: // Build type
params.build_type = fetch_tlv_value(&p[idx], 1);
break;
case 0x1f: // Build number (it can be either 1 or 4 bytes).
params.build_number = fetch_tlv_value(&p[idx], 4);
break;
case 0x28: // Secure boot
params.secure_boot = fetch_tlv_value(&p[idx], 1);
break;
case 0x2a: // OTP lock
params.otp_lock = fetch_tlv_value(&p[idx], 1);
break;
case 0x2b: // API lock
params.api_lock = fetch_tlv_value(&p[idx], 1);
break;
case 0x2c: // debug lock
params.debug_lock = fetch_tlv_value(&p[idx], 1);
break;
case 0x2d: // Firmware build
v = fetch_tlv_value(&p[idx], 3);
params.firmware_build_number = v >> 0;
params.firmware_build_calendar_week = v >> 8;
params.firmware_build_year = v >> 16;
break;
case 0x2f: // Secure boot engine type
params.secure_boot_engine_type = fetch_tlv_value(&p[idx], 1);
infof("Secure boot engine type: 0x%02x", params.secure_boot_engine_type);
break;
case 0x30: // Bluetooth device address
ZX_DEBUG_ASSERT(len_of_value == 6); // expect the address length is 6.
memcpy(params.bluetooth_address, &p[idx + 2], sizeof(params.bluetooth_address));
break;
default:
// unknown tag. skip it.
warnf("Unknown firmware version TLV tag=0x%02x", p[idx]);
break;
}
idx += 2 + len_of_value; // Skip the 'length' and 'value'.
}
return params;
}
ReadVersionReturnParams VendorHci::SendReadVersion() const {
auto packet = CommandPacket::New(kReadVersion);
SendCommand(packet->view());
auto evt_packet = WaitForEventPacket();
if (evt_packet) {
auto params = evt_packet->return_params<ReadVersionReturnParams>();
if (params)
return *params;
}
errorf("VendorHci: ReadVersion: Error reading response!");
return ReadVersionReturnParams{.status = pw::bluetooth::emboss::StatusCode::UNSPECIFIED_ERROR};
}
ReadVersionReturnParamsTlv VendorHci::SendReadVersionTlv() const {
auto packet = CommandPacket::New(kReadVersion, sizeof(VersionCommandParams));
auto params = packet->mutable_payload<VersionCommandParams>();
params->para0 = kVersionSupportTlv; // Only meaningful for AX210 and later.
SendCommand(packet->view());
auto evt_packet = WaitForEventPacket();
if (evt_packet) {
size_t packet_size =
evt_packet->view().payload_size() - sizeof(bt::hci_spec::CommandCompleteEventParams);
const uint8_t* p = evt_packet->return_params<uint8_t>();
if (p)
return parse_tlv_version_return_params(p, packet_size);
}
errorf("VendorHci: ReadVersionTlv: Error reading response!");
return ReadVersionReturnParamsTlv{.status = pw::bluetooth::emboss::StatusCode::UNSPECIFIED_ERROR};
}
ReadBootParamsReturnParams VendorHci::SendReadBootParams() const {
auto packet = CommandPacket::New(kReadBootParams);
SendCommand(packet->view());
auto evt_packet = WaitForEventPacket();
if (evt_packet) {
auto params = evt_packet->return_params<ReadBootParamsReturnParams>();
if (params)
return *params;
}
errorf("VendorHci: ReadBootParams: Error reading response!");
return ReadBootParamsReturnParams{.status = pw::bluetooth::emboss::StatusCode::UNSPECIFIED_ERROR};
}
pw::bluetooth::emboss::StatusCode VendorHci::SendHciReset() const {
auto packet = CommandPacket::New(bt::hci_spec::kReset);
SendCommand(packet->view());
// TODO(armansito): Consider collecting a metric for initialization time
// (successful and failing) to provide us with a better sense of how long
// these timeouts should be.
auto evt_packet = WaitForEventPacket(kInitTimeoutMs, bt::hci_spec::kCommandCompleteEventCode);
if (!evt_packet) {
errorf("VendorHci: failed while waiting for HCI_Reset response");
return pw::bluetooth::emboss::StatusCode::UNSPECIFIED_ERROR;
}
const auto* params = evt_packet->return_params<bt::hci_spec::SimpleReturnParams>();
if (!params) {
errorf("VendorHci: HCI_Reset: received malformed response");
return pw::bluetooth::emboss::StatusCode::UNSPECIFIED_ERROR;
}
return params->status;
}
void VendorHci::SendVendorReset(uint32_t boot_address) const {
auto packet = CommandPacket::New(kReset, sizeof(ResetCommandParams));
auto params = packet->mutable_payload<ResetCommandParams>();
params->reset_type = 0x00;
params->patch_enable = 0x01;
params->ddc_reload = 0x00;
params->boot_option = 0x01;
params->boot_address = htobe32(boot_address);
SendCommand(packet->view());
// Sleep for 2 seconds to let the controller process the reset.
zx_nanosleep(zx_deadline_after(ZX_SEC(2)));
}
bool VendorHci::SendSecureSend(uint8_t type, const bt::BufferView& bytes) const {
size_t left = bytes.size();
while (left > 0) {
size_t frag_len = std::min(left, kMaxSecureSendArgLen);
auto cmd = CommandPacket::New(kSecureSend, frag_len + 1);
auto data = cmd->mutable_view()->mutable_payload_data();
data[0] = type;
data.Write(bytes.view(bytes.size() - left, frag_len), 1);
SendCommand(cmd->view());
std::unique_ptr<bt::hci::EventPacket> event = WaitForEventPacket();
if (!event) {
errorf("VendorHci: SecureSend: Error reading response!");
return false;
}
if (event->event_code() == bt::hci_spec::kCommandCompleteEventCode) {
const auto& event_params = event->view().payload<bt::hci_spec::CommandCompleteEventParams>();
if (le16toh(event_params.command_opcode) != kSecureSend) {
errorf("VendorHci: Received command complete for something else!");
} else if (event_params.return_parameters[0] != 0x00) {
errorf("VendorHci: Received 0x%x instead of zero in command complete!",
event_params.return_parameters[0]);
return false;
}
} else if (event->event_code() == bt::hci_spec::kVendorDebugEventCode) {
const auto& params = event->view().template payload<SecureSendEventParams>();
infof("VendorHci: SecureSend result 0x%x, opcode: 0x%x, status: 0x%x", params.result,
params.opcode, params.status);
if (params.result) {
errorf("VendorHci: Result of %d indicates some error!", params.result);
return false;
}
}
left -= frag_len;
}
return true;
}
bool VendorHci::SendAndExpect(const bt::PacketView<bt::hci_spec::CommandHeader>& command,
std::deque<bt::BufferView> events) const {
SendCommand(command);
while (events.size() > 0) {
auto evt_packet = WaitForEventPacket();
if (!evt_packet) {
return false;
}
auto expected = events.front();
if ((evt_packet->view().size() != expected.size()) ||
(memcmp(evt_packet->view().data().data(), expected.data(), expected.size()) != 0)) {
errorf("VendorHci: SendAndExpect: unexpected event received");
return false;
}
events.pop_front();
}
return true;
}
void VendorHci::EnterManufacturerMode() {
if (manufacturer_)
return;
auto packet = CommandPacket::New(kMfgModeChange, sizeof(MfgModeChangeCommandParams));
auto params = packet->mutable_payload<MfgModeChangeCommandParams>();
params->enable = pw::bluetooth::emboss::GenericEnableParam::ENABLE;
params->disable_mode = MfgDisableMode::kNoPatches;
SendCommand(packet->view());
auto evt_packet = WaitForEventPacket();
if (!evt_packet || evt_packet->event_code() != bt::hci_spec::kCommandCompleteEventCode) {
errorf("VendorHci: EnterManufacturerMode failed");
return;
}
manufacturer_ = true;
}
bool VendorHci::ExitManufacturerMode(MfgDisableMode mode) {
if (!manufacturer_)
return false;
manufacturer_ = false;
auto packet = CommandPacket::New(kMfgModeChange, sizeof(MfgModeChangeCommandParams));
auto params = packet->mutable_payload<MfgModeChangeCommandParams>();
params->enable = pw::bluetooth::emboss::GenericEnableParam::DISABLE;
params->disable_mode = mode;
SendCommand(packet->view());
auto evt_packet = WaitForEventPacket();
if (!evt_packet || evt_packet->event_code() != bt::hci_spec::kCommandCompleteEventCode) {
errorf("VendorHci: ExitManufacturerMode failed");
return false;
}
return true;
}
void VendorHci::SendCommand(const bt::PacketView<bt::hci_spec::CommandHeader>& command) const {
zx_status_t status = ctrl_->write(0, command.data().data(), command.size(), nullptr, 0);
if (status < 0) {
errorf("VendorHci: SendCommand failed: %s", zx_status_get_string(status));
}
}
std::unique_ptr<bt::hci::EventPacket> VendorHci::WaitForEventPacket(
zx::duration timeout, bt::hci_spec::EventCode expected_event) const {
zx_wait_item_t wait_items[2];
uint32_t wait_item_count = 1;
ZX_DEBUG_ASSERT(ctrl_);
wait_items[0].handle = ctrl_->get();
wait_items[0].waitfor = ZX_CHANNEL_READABLE;
wait_items[0].pending = 0;
if (acl_) {
wait_items[1].handle = acl_->get();
wait_items[1].waitfor = ZX_CHANNEL_READABLE;
wait_items[1].pending = 0;
wait_item_count++;
}
auto begin = zx::clock::get_monotonic();
for (zx::duration elapsed; elapsed < timeout; elapsed = zx::clock::get_monotonic() - begin) {
zx_status_t status = zx_object_wait_many(wait_items, wait_item_count,
zx::deadline_after(timeout - elapsed).get());
if (status != ZX_OK) {
errorf("VendorHci: channel error: %s", zx_status_get_string(status));
return nullptr;
}
// Determine which channel caused the event.
zx_handle_t evt_handle = 0;
for (unsigned i = 0; i < wait_item_count; ++i) {
if (wait_items[i].pending & ZX_CHANNEL_READABLE) {
evt_handle = wait_items[0].handle;
break;
}
}
ZX_DEBUG_ASSERT(evt_handle);
// Allocate a buffer for the event. We don't know the size
// beforehand we allocate the largest possible buffer.
auto packet = bt::hci::EventPacket::New(bt::hci_spec::kMaxCommandPacketPayloadSize);
if (!packet) {
errorf("VendorHci: Failed to allocate event packet!");
return nullptr;
}
uint32_t read_size;
auto packet_bytes = packet->mutable_view()->mutable_data();
zx_status_t read_status = zx_channel_read(evt_handle, 0u, packet_bytes.mutable_data(), nullptr,
packet_bytes.size(), 0, &read_size, nullptr);
if (read_status < 0) {
errorf("VendorHci: Failed to read event bytes: %sn", zx_status_get_string(read_status));
return nullptr;
}
if (read_size < sizeof(bt::hci_spec::EventHeader)) {
errorf("VendorHci: Malformed event packet expected >%zu bytes, got %d",
sizeof(bt::hci_spec::EventHeader), read_size);
return nullptr;
}
// Compare the received payload size to what is in the header.
const size_t rx_payload_size = read_size - sizeof(bt::hci_spec::EventHeader);
const size_t size_from_header = packet->view().header().parameter_total_size;
if (size_from_header != rx_payload_size) {
errorf(
"VendorHci: Malformed event packet - header payload size (%zu) != "
"received (%zu)",
size_from_header, rx_payload_size);
return nullptr;
}
if (expected_event && expected_event != packet->event_code()) {
tracef("VendorHci: keep waiting (expected: 0x%02x, got: 0x%02x)", expected_event,
packet->event_code());
continue;
}
packet->InitializeFromBuffer();
return packet;
}
errorf("VendorHci: timed out waiting for event");
return nullptr;
}
} // namespace btintel