blob: f3930d2e2f590cafea5db7665479e623687e787b [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 <fidl/fuchsia.hardware.bluetooth/cpp/wire.h>
#include <lib/async/cpp/task.h>
#include <lib/async/cpp/wait.h>
#include <lib/async_patterns/testing/cpp/dispatcher_bound.h>
#include <lib/component/outgoing/cpp/outgoing_directory.h>
#include <lib/ddk/metadata.h>
#include <lib/ddk/platform-defs.h>
#include <lib/driver/compat/cpp/device_server.h>
#include <lib/driver/outgoing/cpp/outgoing_directory.h>
#include <lib/driver/testing/cpp/fixtures/gtest_fixture.h>
#include <lib/sync/cpp/completion.h>
#include "src/storage/lib/vfs/cpp/pseudo_dir.h"
#include "src/storage/lib/vfs/cpp/synchronous_vfs.h"
#include "src/storage/lib/vfs/cpp/vmo_file.h"
namespace bt_hci_broadcom {
namespace {
// Firmware binaries are a sequence of HCI commands containing the firmware as payloads. For
// testing, we use 1 HCI command with a 1 byte payload.
const std::vector<uint8_t> kFirmware = {
0x01, 0x02, // arbitrary "firmware opcode"
0x01, // parameter_total_size
0x03 // payload
};
const char* kFirmwarePath = "BCM4345C5.hcd";
const std::vector<uint8_t> kMacAddress = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05};
const std::array<uint8_t, 6> kCommandCompleteEvent = {
0x0e, // command complete event code
0x04, // parameter_total_size
0x01, // num_hci_command_packets
0x00, 0x00, // command opcode (hardcoded for simplicity since this isn't checked by the driver)
0x00, // return_code (success)
};
class FakeTransportDevice : public fidl::WireServer<fuchsia_hardware_bluetooth::Hci>,
public fdf::WireServer<fuchsia_hardware_serialimpl::Device> {
public:
explicit FakeTransportDevice() = default;
fuchsia_hardware_serialimpl::Service::InstanceHandler GetSerialInstanceHandler() {
return fuchsia_hardware_serialimpl::Service::InstanceHandler({
.device = serial_binding_group_.CreateHandler(this, fdf::Dispatcher::GetCurrent()->get(),
fidl::kIgnoreBindingClosure),
});
}
fuchsia_hardware_bluetooth::HciService::InstanceHandler GetHciInstanceHandler() {
return fuchsia_hardware_bluetooth::HciService::InstanceHandler({
.hci = hci_binding_group_.CreateHandler(
this, fdf::Dispatcher::GetCurrent()->async_dispatcher(), fidl::kIgnoreBindingClosure),
});
}
// Set a custom handler for commands. If null, command complete events will be automatically sent.
void SetCommandHandler(fit::function<void(std::vector<uint8_t>)> command_callback) {
command_callback_ = std::move(command_callback);
}
zx::channel& command_chan() { return command_channel_; }
// fucshia_hardware_bluetooth::Hci request handler implementations:
void OpenCommandChannel(OpenCommandChannelRequestView request,
OpenCommandChannelCompleter::Sync& completer) override {
command_channel_ = std::move(request->channel);
cmd_chan_wait_.set_object(command_channel_.get());
cmd_chan_wait_.set_trigger(ZX_CHANNEL_READABLE | ZX_CHANNEL_PEER_CLOSED);
cmd_chan_wait_.Begin(fdf::Dispatcher::GetCurrent()->async_dispatcher());
completer.ReplySuccess();
}
void OpenAclDataChannel(OpenAclDataChannelRequestView request,
OpenAclDataChannelCompleter::Sync& completer) override {
completer.ReplyError(ZX_ERR_NOT_SUPPORTED);
}
void OpenScoDataChannel(OpenScoDataChannelRequestView request,
OpenScoDataChannelCompleter::Sync& completer) override {
completer.ReplyError(ZX_ERR_NOT_SUPPORTED);
}
void ConfigureSco(ConfigureScoRequestView request,
ConfigureScoCompleter::Sync& completer) override {
completer.ReplyError(ZX_ERR_NOT_SUPPORTED);
}
void ResetSco(ResetScoCompleter::Sync& completer) override {}
void OpenIsoDataChannel(OpenIsoDataChannelRequestView request,
OpenIsoDataChannelCompleter::Sync& completer) override {
completer.ReplyError(ZX_ERR_NOT_SUPPORTED);
}
void OpenSnoopChannel(OpenSnoopChannelRequestView request,
OpenSnoopChannelCompleter::Sync& completer) override {
completer.ReplyError(ZX_ERR_NOT_SUPPORTED);
}
void handle_unknown_method(fidl::UnknownMethodMetadata<fuchsia_hardware_bluetooth::Hci> metadata,
fidl::UnknownMethodCompleter::Sync& completer) override {
ZX_PANIC("Unknown method in HCI requests");
}
// fuchsia_hardware_serialimpl::Device FIDL request handler implementation.
void GetInfo(fdf::Arena& arena, GetInfoCompleter::Sync& completer) override {
fuchsia_hardware_serial::wire::SerialPortInfo info = {
.serial_class = fuchsia_hardware_serial::Class::kBluetoothHci,
.serial_pid = PDEV_PID_BCM43458,
};
completer.buffer(arena).ReplySuccess(info);
}
void Config(ConfigRequestView request, fdf::Arena& arena,
ConfigCompleter::Sync& completer) override {
completer.buffer(arena).ReplySuccess();
}
void Enable(EnableRequestView request, fdf::Arena& arena,
EnableCompleter::Sync& completer) override {
completer.buffer(arena).ReplySuccess();
}
void Read(fdf::Arena& arena, ReadCompleter::Sync& completer) override {
fidl::VectorView<uint8_t> data;
completer.buffer(arena).ReplySuccess(data);
}
void Write(WriteRequestView request, fdf::Arena& arena,
WriteCompleter::Sync& completer) override {
completer.buffer(arena).ReplySuccess();
}
void CancelAll(fdf::Arena& arena, CancelAllCompleter::Sync& completer) override {}
void handle_unknown_method(
fidl::UnknownMethodMetadata<fuchsia_hardware_serialimpl::Device> metadata,
fidl::UnknownMethodCompleter::Sync& completer) override {
ZX_PANIC("Unknown method in Serial requests");
}
private:
void OnCommandChannelSignal(async_dispatcher_t*, async::WaitBase* wait, zx_status_t status,
const zx_packet_signal_t* signal) {
ASSERT_EQ(status, ZX_OK);
if (signal->observed & ZX_CHANNEL_PEER_CLOSED) {
command_channel_.reset();
return;
}
ASSERT_TRUE(signal->observed & ZX_CHANNEL_READABLE);
// Make buffer large enough to hold largest command packet.
std::vector<uint8_t> bytes(
sizeof(HciCommandHeader) +
std::numeric_limits<decltype(HciCommandHeader::parameter_total_size)>::max());
uint32_t actual_bytes = 0;
zx_status_t read_status = command_channel_.read(
/*flags=*/0, bytes.data(), /*handles=*/nullptr, static_cast<uint32_t>(bytes.size()),
/*num_handles=*/0, &actual_bytes, /*actual_handles=*/nullptr);
ASSERT_EQ(read_status, ZX_OK);
bytes.resize(actual_bytes);
cmd_chan_received_packets_.push_back(bytes);
if (command_callback_) {
command_callback_(std::move(bytes));
} else {
zx_status_t write_status = command_channel_.write(/*flags=*/0, kCommandCompleteEvent.data(),
kCommandCompleteEvent.size(),
/*handles=*/nullptr, /*num_handles=*/0);
EXPECT_EQ(write_status, ZX_OK);
}
// The wait needs to be restarted.
zx_status_t wait_begin_status = wait->Begin(fdf::Dispatcher::GetCurrent()->async_dispatcher());
ASSERT_EQ(wait_begin_status, ZX_OK) << zx_status_get_string(wait_begin_status);
}
fit::function<void(std::vector<uint8_t>)> command_callback_;
zx::channel command_channel_;
std::vector<std::vector<uint8_t>> cmd_chan_received_packets_;
async::WaitMethod<FakeTransportDevice, &FakeTransportDevice::OnCommandChannelSignal>
cmd_chan_wait_{this};
fdf::ServerBindingGroup<fuchsia_hardware_serialimpl::Device> serial_binding_group_;
fidl::ServerBindingGroup<fuchsia_hardware_bluetooth::Hci> hci_binding_group_;
};
class TestEnvironment : fdf_testing::Environment {
public:
zx::result<> Serve(fdf::OutgoingDirectory& to_driver_vfs) override {
firmware_dir_ = fbl::MakeRefCounted<fs::PseudoDir>();
auto dir_endpoints = fidl::Endpoints<fuchsia_io::Directory>::Create();
firmware_server_.SetDispatcher(fdf::Dispatcher::GetCurrent()->async_dispatcher());
// Serve our firmware directory (will start serving FIDL requests on dir_endpoints with
// dispatcher on previous line)
ZX_ASSERT(firmware_server_.ServeDirectory(firmware_dir_, std::move(dir_endpoints.server)) ==
ZX_OK);
// Attach the firmware directory endpoint to "pkg/lib"
ZX_ASSERT(to_driver_vfs.component()
.AddDirectoryAt(std::move(dir_endpoints.client), "pkg/lib", "firmware")
.is_ok());
// Add the services that the fake parent driver exposes to the incoming directory of the driver
// under test.
zx::result result = to_driver_vfs.AddService<fuchsia_hardware_serialimpl::Service>(
transport_device_.GetSerialInstanceHandler());
EXPECT_TRUE(result.is_ok());
result = to_driver_vfs.AddService<fuchsia_hardware_bluetooth::HciService>(
transport_device_.GetHciInstanceHandler());
EXPECT_TRUE(result.is_ok());
device_server_.Init(component::kDefaultInstance, "root");
EXPECT_EQ(ZX_OK, device_server_.Serve(fdf::Dispatcher::GetCurrent()->async_dispatcher(),
&to_driver_vfs));
return zx::ok();
}
void AddFirmwareFile(const std::vector<uint8_t> firmware) {
// Create vmo for firmware file.
zx::vmo vmo;
zx::vmo::create(4096, 0, &vmo);
vmo.write(firmware.data(), 0, firmware.size());
vmo.set_prop_content_size(firmware.size());
// Create firmware file, and add it to the "firmware" directory we added under pkg/lib.
fbl::RefPtr<fs::VmoFile> firmware_file =
fbl::MakeRefCounted<fs::VmoFile>(std::move(vmo), firmware.size());
ZX_ASSERT(firmware_dir_->AddEntry(kFirmwarePath, firmware_file) == ZX_OK);
}
zx_status_t SetMetadata(uint32_t name, const std::vector<uint8_t> data, const size_t size) {
// Serve metadata.
return device_server_.AddMetadata(name, data.data(), size);
}
FakeTransportDevice transport_device_;
private:
compat::DeviceServer device_server_;
fbl::RefPtr<fs::PseudoDir> firmware_dir_;
fs::SynchronousVfs firmware_server_;
};
class FixtureConfig final {
public:
static constexpr bool kDriverOnForeground = false;
static constexpr bool kAutoStartDriver = false;
static constexpr bool kAutoStopDriver = true;
using DriverType = BtHciBroadcom;
using EnvironmentType = TestEnvironment;
};
class BtHciBroadcomTest : public fdf_testing::DriverTestFixture<FixtureConfig> {
public:
BtHciBroadcomTest() = default;
protected:
void SetFirmware(const std::vector<uint8_t> firmware = kFirmware) {
RunInEnvironmentTypeContext([&](TestEnvironment& env) { env.AddFirmwareFile(firmware); });
}
void SetMetadata(uint32_t name = DEVICE_METADATA_MAC_ADDRESS,
const std::vector<uint8_t> data = kMacAddress, const size_t size = kMacAddrLen) {
ASSERT_EQ(ZX_OK, RunInEnvironmentTypeContext<zx_status_t>(
[&](TestEnvironment& env) { return env.SetMetadata(name, data, size); }));
}
void OpenVendor() {
class EventHandler : public fidl::WireSyncEventHandler<fuchsia_hardware_bluetooth::Vendor> {
public:
void OnFeatures(
fidl::WireEvent<fuchsia_hardware_bluetooth::Vendor::OnFeatures>* event) override {
EXPECT_TRUE(event->acl_priority_command());
on_vendor_connect_handled_ = true;
}
void handle_unknown_event(
fidl::UnknownEventMetadata<fuchsia_hardware_bluetooth::Vendor> metadata) override {}
bool on_vendor_connect_handled_ = false;
};
EventHandler event_handler;
// Connect to Vendor protocol through devfs, get the channel handle from node server.
zx::result connect_result =
ConnectThroughDevfs<fuchsia_hardware_bluetooth::Vendor>("bt-hci-broadcom");
ASSERT_EQ(ZX_OK, connect_result.status_value());
// Bind the channel to a Vendor client end.
vendor_client_.Bind(std::move(connect_result.value()));
EXPECT_TRUE(vendor_client_.HandleOneEvent(event_handler).ok());
EXPECT_TRUE(event_handler.on_vendor_connect_handled_);
}
void OpenVendorWithHciClient() {
// Connect to Vendor protocol through devfs, get the channel handle from node server.
zx::result connect_result =
ConnectThroughDevfs<fuchsia_hardware_bluetooth::Vendor>("bt-hci-broadcom");
ASSERT_EQ(ZX_OK, connect_result.status_value());
fidl::ClientEnd<fuchsia_hardware_bluetooth::Hci> hci_end(connect_result.value().TakeChannel());
hci_client_.Bind(std::move(hci_end));
}
void OpenHci() {
// Connect to Hci through vendor protocol
auto open_hci_result = vendor_client_->OpenHci();
EXPECT_TRUE(open_hci_result.ok());
EXPECT_FALSE(open_hci_result->is_error());
hci_client_.Bind(std::move(open_hci_result->value()->channel));
}
fidl::WireSyncClient<fuchsia_hardware_bluetooth::Vendor> vendor_client_;
fidl::WireSyncClient<fuchsia_hardware_bluetooth::Hci> hci_client_;
};
class BtHciBroadcomInitializedTest : public BtHciBroadcomTest {
public:
void SetUp() override {
BtHciBroadcomTest::SetUp();
SetFirmware();
SetMetadata();
ASSERT_TRUE(StartDriver().is_ok());
OpenVendor();
}
};
TEST_F(BtHciBroadcomInitializedTest, Lifecycle) {}
TEST_F(BtHciBroadcomTest, ReportLoadFirmwareError) {
// Ensure reading metadata succeeds.
SetMetadata();
// No firmware has been set, so load_firmware() should fail during initialization.
ASSERT_EQ(StartDriver().status_value(), ZX_ERR_NOT_FOUND);
}
TEST_F(BtHciBroadcomTest, TooSmallFirmwareBuffer) {
// Ensure reading metadata succeeds.
SetMetadata();
SetFirmware(std::vector<uint8_t>{0x00});
ASSERT_EQ(StartDriver().status_value(), ZX_ERR_INTERNAL);
}
TEST_F(BtHciBroadcomTest, ControllerReturnsEventSmallerThanEventHeader) {
RunInEnvironmentTypeContext([](TestEnvironment& env) {
env.transport_device_.SetCommandHandler([&](const std::vector<uint8_t>& command) {
zx_status_t write_status =
env.transport_device_.command_chan().write(/*flags=*/0, kCommandCompleteEvent.data(),
/*num_bytes=*/1,
/*handles=*/nullptr, /*num_handles=*/0);
EXPECT_EQ(write_status, ZX_OK);
});
});
SetFirmware();
SetMetadata();
ASSERT_NE(StartDriver().status_value(), ZX_OK);
}
TEST_F(BtHciBroadcomTest, ControllerReturnsEventSmallerThanCommandComplete) {
RunInEnvironmentTypeContext([](TestEnvironment& env) {
env.transport_device_.SetCommandHandler([&](const std::vector<uint8_t>& command) {
zx_status_t write_status =
env.transport_device_.command_chan().write(/*flags=*/0, kCommandCompleteEvent.data(),
/*num_bytes=*/sizeof(HciEventHeader),
/*handles=*/nullptr, /*num_handles=*/0);
EXPECT_EQ(write_status, ZX_OK);
});
});
SetFirmware();
SetMetadata();
ASSERT_FALSE(StartDriver().is_ok());
}
TEST_F(BtHciBroadcomTest, ControllerReturnsBdaddrEventWithoutBdaddrParam) {
// Set an invalid mac address in the metadata so that a ReadBdaddr command is sent to get
// fallback address.
SetMetadata(DEVICE_METADATA_MAC_ADDRESS, kMacAddress, kMacAddress.size() - 1);
// Respond to ReadBdaddr command with a command complete (which doesn't include the bdaddr).
RunInEnvironmentTypeContext([](TestEnvironment& env) {
env.transport_device_.SetCommandHandler([&](const std::vector<uint8_t>& command) {
zx_status_t write_status =
env.transport_device_.command_chan().write(/*flags=*/0, kCommandCompleteEvent.data(),
/*num_bytes=*/kCommandCompleteEvent.size(),
/*handles=*/nullptr, /*num_handles=*/0);
EXPECT_EQ(write_status, ZX_OK);
});
});
// Ensure loading the firmware succeeds.
SetFirmware();
// Initialization should still succeed (an error will be logged, but it's not fatal).
ASSERT_TRUE(StartDriver().is_ok());
}
TEST_F(BtHciBroadcomTest, VendorProtocolUnknownMethod) {
SetFirmware();
SetMetadata();
ASSERT_TRUE(StartDriver().is_ok());
OpenVendorWithHciClient();
auto result = hci_client_->ResetSco();
ASSERT_EQ(result.status(), ZX_ERR_NOT_SUPPORTED);
}
TEST_F(BtHciBroadcomTest, HciProtocolUnknownMethod) {
SetFirmware();
SetMetadata();
ASSERT_TRUE(StartDriver().is_ok());
OpenVendor();
// Connect to Hci through Vendor protocol
auto open_hci_result = vendor_client_->OpenHci();
EXPECT_TRUE(open_hci_result.ok());
EXPECT_FALSE(open_hci_result->is_error());
// Bind the channel to Vendor client end.
fidl::ClientEnd<fuchsia_hardware_bluetooth::Vendor> vendor_client_end(
open_hci_result->value()->channel.TakeChannel());
fidl::WireSyncClient vendor_client(std::move(vendor_client_end));
fidl::Arena arena;
auto builder = fuchsia_hardware_bluetooth::wire::VendorSetAclPriorityParams::Builder(arena);
builder.connection_handle(0);
builder.priority(fuchsia_hardware_bluetooth::wire::VendorAclPriority::kNormal);
builder.direction(fuchsia_hardware_bluetooth::wire::VendorAclDirection::kSource);
auto command =
fuchsia_hardware_bluetooth::wire::VendorCommand::WithSetAclPriority(arena, builder.Build());
auto result = vendor_client->EncodeCommand(command);
ASSERT_EQ(result.status(), ZX_ERR_NOT_SUPPORTED);
}
TEST_F(BtHciBroadcomInitializedTest, EncodeSetAclPrioritySuccessWithParametersHighSink) {
std::array<uint8_t, kBcmSetAclPriorityCmdSize> result_buffer;
fidl::Arena arena;
auto builder = fuchsia_hardware_bluetooth::wire::VendorSetAclPriorityParams::Builder(arena);
builder.connection_handle(0xFF00);
builder.priority(fuchsia_hardware_bluetooth::wire::VendorAclPriority::kHigh);
builder.direction(fuchsia_hardware_bluetooth::wire::VendorAclDirection::kSink);
auto command =
fuchsia_hardware_bluetooth::wire::VendorCommand::WithSetAclPriority(arena, builder.Build());
auto result = vendor_client_->EncodeCommand(command);
ASSERT_TRUE(result.ok());
ASSERT_FALSE(result->is_error());
std::copy(result->value()->encoded.begin(), result->value()->encoded.end(),
result_buffer.begin());
const std::array<uint8_t, kBcmSetAclPriorityCmdSize> kExpectedBuffer = {
0x1A,
0xFD, // OpCode
0x04, // size
0x00,
0xFF, // handle
kBcmAclPriorityHigh, // priority
kBcmAclDirectionSink, // direction
};
EXPECT_EQ(result_buffer, kExpectedBuffer);
}
TEST_F(BtHciBroadcomInitializedTest, EncodeSetAclPrioritySuccessWithParametersNormalSource) {
std::array<uint8_t, kBcmSetAclPriorityCmdSize> result_buffer;
fidl::Arena arena;
auto builder = fuchsia_hardware_bluetooth::wire::VendorSetAclPriorityParams::Builder(arena);
builder.connection_handle(0xFF00);
builder.priority(fuchsia_hardware_bluetooth::wire::VendorAclPriority::kNormal);
builder.direction(fuchsia_hardware_bluetooth::wire::VendorAclDirection::kSource);
auto command =
fuchsia_hardware_bluetooth::wire::VendorCommand::WithSetAclPriority(arena, builder.Build());
auto result = vendor_client_->EncodeCommand(command);
ASSERT_TRUE(result.ok());
ASSERT_FALSE(result->is_error());
std::copy(result->value()->encoded.begin(), result->value()->encoded.end(),
result_buffer.begin());
const std::array<uint8_t, kBcmSetAclPriorityCmdSize> kExpectedBuffer = {
0x1A,
0xFD, // OpCode
0x04, // size
0x00,
0xFF, // handle
kBcmAclPriorityNormal, // priority
kBcmAclDirectionSource, // direction
};
EXPECT_EQ(result_buffer, kExpectedBuffer);
}
} // namespace
} // namespace bt_hci_broadcom