blob: facc99fcb471a5b5d74fce54f25c800f941796ea [file] [log] [blame]
// Copyright 2021 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 "aml-spi.h"
#include <endian.h>
#include <fidl/fuchsia.hardware.platform.device/cpp/wire_test_base.h>
#include <fidl/fuchsia.scheduler/cpp/wire.h>
#include <lib/ddk/metadata.h>
#include <lib/driver/testing/cpp/driver_lifecycle.h>
#include <lib/driver/testing/cpp/driver_runtime.h>
#include <lib/driver/testing/cpp/test_environment.h>
#include <lib/driver/testing/cpp/test_node.h>
#include <lib/fake-bti/bti.h>
#include <lib/zx/clock.h>
#include <lib/zx/vmo.h>
#include <zircon/errors.h>
#include <fake-mmio-reg/fake-mmio-reg.h>
#include <zxtest/zxtest.h>
#include "registers.h"
#include "src/devices/gpio/testing/fake-gpio/fake-gpio.h"
#include "src/devices/registers/testing/mock-registers/mock-registers.h"
namespace spi {
class TestAmlSpiDriver : public AmlSpiDriver {
public:
TestAmlSpiDriver(fdf::DriverStartArgs start_args, fdf::UnownedSynchronizedDispatcher dispatcher)
: AmlSpiDriver(std::move(start_args), std::move(dispatcher)),
mmio_region_(sizeof(uint32_t), 17) {}
static DriverRegistration GetDriverRegistration() {
// Use a custom DriverRegistration to create the DUT. Without this, the non-test implementation
// will be used by default.
return FUCHSIA_DRIVER_REGISTRATION_V1(fdf_internal::DriverServer<TestAmlSpiDriver>::initialize,
fdf_internal::DriverServer<TestAmlSpiDriver>::destroy);
}
ddk_fake::FakeMmioRegRegion& mmio() { return mmio_region_; }
uint32_t conreg() const { return conreg_; }
uint32_t enhance_cntl() const { return enhance_cntl_; }
uint32_t testreg() const { return testreg_; }
protected:
fpromise::promise<fdf::MmioBuffer, zx_status_t> MapMmio(
fidl::WireClient<fuchsia_hardware_platform_device::Device>& pdev, uint32_t mmio_id) override {
return fpromise::make_promise([this]() -> fpromise::result<fdf::MmioBuffer, zx_status_t> {
// Set the transfer complete bit so the driver doesn't get stuck waiting on the interrupt.
mmio_region_[AML_SPI_STATREG].SetReadCallback(
[]() { return StatReg::Get().FromValue(0).set_tc(1).set_te(1).set_rr(1).reg_value(); });
mmio_region_[AML_SPI_CONREG].SetWriteCallback([this](uint32_t value) { conreg_ = value; });
mmio_region_[AML_SPI_CONREG].SetReadCallback([this]() { return conreg_; });
mmio_region_[AML_SPI_ENHANCE_CNTL].SetWriteCallback(
[this](uint32_t value) { enhance_cntl_ = value; });
mmio_region_[AML_SPI_TESTREG].SetWriteCallback([this](uint32_t value) { testreg_ = value; });
return fpromise::ok(mmio_region_.GetMmioBuffer());
});
}
private:
ddk_fake::FakeMmioRegRegion mmio_region_;
uint32_t conreg_{};
uint32_t enhance_cntl_{};
uint32_t testreg_{};
};
class FakePDev : public fidl::testing::WireTestBase<fuchsia_hardware_platform_device::Device> {
public:
fuchsia_hardware_platform_device::Service::InstanceHandler GetInstanceHandler(
async_dispatcher_t* dispatcher) {
return fuchsia_hardware_platform_device::Service::InstanceHandler({
.device = binding_group_.CreateHandler(this, dispatcher, fidl::kIgnoreBindingClosure),
});
}
void set_interrupt(zx::interrupt interrupt) { interrupt_ = std::move(interrupt); }
void set_bti(zx::bti bti) { bti_ = std::move(bti); }
private:
void NotImplemented_(const std::string& name, ::fidl::CompleterBase& completer) override {}
void GetInterruptById(
fuchsia_hardware_platform_device::wire::DeviceGetInterruptByIdRequest* request,
GetInterruptByIdCompleter::Sync& completer) override {
if (request->index != 0 || !interrupt_) {
return completer.ReplyError(ZX_ERR_NOT_FOUND);
}
zx::interrupt out_interrupt;
zx_status_t status = interrupt_.duplicate(ZX_RIGHT_SAME_RIGHTS, &out_interrupt);
if (status == ZX_OK) {
completer.ReplySuccess(std::move(out_interrupt));
} else {
completer.ReplyError(status);
}
}
void GetBtiById(fuchsia_hardware_platform_device::wire::DeviceGetBtiByIdRequest* request,
GetBtiByIdCompleter::Sync& completer) override {
if (request->index != 0 || !bti_) {
return completer.ReplyError(ZX_ERR_NOT_FOUND);
}
zx::bti out_bti;
zx_status_t status = bti_.duplicate(ZX_RIGHT_SAME_RIGHTS, &out_bti);
if (status == ZX_OK) {
completer.ReplySuccess(std::move(out_bti));
} else {
completer.ReplyError(status);
}
}
zx::interrupt interrupt_;
zx::bti bti_;
fidl::ServerBindingGroup<fuchsia_hardware_platform_device::Device> binding_group_;
};
class AmlSpiTest : public zxtest::Test {
public:
AmlSpiTest()
: registers_(fdf::Dispatcher::GetCurrent()->async_dispatcher()),
node_server_("root"),
dut_(TestAmlSpiDriver::GetDriverRegistration()) {}
virtual void SetUpInterrupt() {
ASSERT_OK(zx::interrupt::create({}, 0, ZX_INTERRUPT_VIRTUAL, &interrupt_));
zx::interrupt dut_interrupt;
ASSERT_OK(interrupt_.duplicate(ZX_RIGHT_SAME_RIGHTS, &dut_interrupt));
pdev_server_.set_interrupt(std::move(dut_interrupt));
interrupt_.trigger(0, zx::clock::get_monotonic());
}
virtual void SetUpBti() {}
virtual bool SetupResetRegister() { return true; }
virtual void SetMetadata(compat::DeviceServer& compat) {
EXPECT_OK(compat.AddMetadata(DEVICE_METADATA_AMLSPI_CONFIG, &kSpiConfig, sizeof(kSpiConfig)));
}
void SetUp() override {
SetUpInterrupt();
SetUpBti();
zx::result start_args = node_server_.CreateStartArgsAndServe();
ASSERT_TRUE(start_args.is_ok());
driver_outgoing_ = std::move(start_args->outgoing_directory_client);
ASSERT_TRUE(
test_environment_.Initialize(std::move(start_args->incoming_directory_server)).is_ok());
start_args_ = std::move(start_args->start_args);
auto& directory = test_environment_.incoming_directory();
auto result = directory.AddService<fuchsia_hardware_platform_device::Service>(
pdev_server_.GetInstanceHandler(fdf::Dispatcher::GetCurrent()->async_dispatcher()), "pdev");
ASSERT_TRUE(result.is_ok());
SetMetadata(compat_);
compat_.Init("pdev", {});
EXPECT_OK(compat_.Serve(fdf::Dispatcher::GetCurrent()->async_dispatcher(), &directory));
// Servec a second compat instance at default in order to satisfy AmlSpiDriver's compat server.
// Without this, metadata doesn't get forwarded.
compat_default_.Init("default", {});
EXPECT_OK(compat_default_.Serve(fdf::Dispatcher::GetCurrent()->async_dispatcher(), &directory));
result = directory.AddService<fuchsia_hardware_gpio::Service>(gpio_.CreateInstanceHandler(),
"gpio-cs-2");
ASSERT_TRUE(result.is_ok());
result = directory.AddService<fuchsia_hardware_gpio::Service>(gpio_.CreateInstanceHandler(),
"gpio-cs-3");
ASSERT_TRUE(result.is_ok());
result = directory.AddService<fuchsia_hardware_gpio::Service>(gpio_.CreateInstanceHandler(),
"gpio-cs-5");
ASSERT_TRUE(result.is_ok());
gpio_.SetCurrentState(fake_gpio::State{.polarity = fuchsia_hardware_gpio::GpioPolarity::kHigh,
.sub_state = fake_gpio::WriteSubState{.value = 0}});
gpio_.SetWriteCallback([this](fake_gpio::FakeGpio& gpio) {
if (gpio_writes_.empty()) {
EXPECT_FALSE(gpio_writes_.empty());
return ZX_ERR_INTERNAL;
}
auto [status, value] = gpio_writes_.front();
gpio_writes_.pop();
if (status != ZX_OK) {
EXPECT_EQ(value, gpio_.GetWriteValue());
}
return status;
});
if (SetupResetRegister()) {
auto result =
test_environment_.incoming_directory().AddService<fuchsia_hardware_registers::Service>(
registers_.GetInstanceHandler(), "reset");
ASSERT_TRUE(result.is_ok());
}
registers_.ExpectWrite<uint32_t>(0x1c, 1 << 1, 1 << 1);
}
void TearDown() override {
zx::result prepare_stop_result = runtime_.RunToCompletion(dut_.PrepareStop());
EXPECT_OK(prepare_stop_result.status_value());
EXPECT_TRUE(dut_.Stop().is_ok());
}
void ExpectGpioWrite(zx_status_t status, uint8_t value) { gpio_writes_.emplace(status, value); }
void VerifyGpioAndClear() {
EXPECT_EQ(gpio_writes_.size(), 0);
gpio_writes_ = {};
}
ddk_fake::FakeMmioRegRegion& mmio() { return dut_->mmio(); }
bool ControllerReset() {
zx_status_t status = registers_.VerifyAll();
if (status == ZX_OK) {
// Always keep a single expectation in the queue, that way we can verify when the controller
// is not reset.
registers_.ExpectWrite<uint32_t>(0x1c, 1 << 1, 1 << 1);
}
return status == ZX_OK;
}
protected:
zx::result<fdf::ClientEnd<fuchsia_hardware_spiimpl::SpiImpl>> GetFidlClient() {
// Connect to the driver through its outgoing directory and get a spiimpl client.
zx::result svc_endpoints = fidl::CreateEndpoints<fuchsia_io::Directory>();
if (svc_endpoints.is_error()) {
return svc_endpoints.take_error();
}
EXPECT_OK(fdio_open_at(driver_outgoing_.handle()->get(), "/svc",
static_cast<uint32_t>(fuchsia_io::OpenFlags::kDirectory),
svc_endpoints->server.TakeChannel().release()));
return fdf::internal::DriverTransportConnect<fuchsia_hardware_spiimpl::Service::Device>(
svc_endpoints->client, component::kDefaultInstance);
}
zx::result<fuchsia_scheduler::RoleName> GetSchedulerRoleName() {
zx::result svc_endpoints = fidl::CreateEndpoints<fuchsia_io::Directory>();
if (svc_endpoints.is_error()) {
return svc_endpoints.take_error();
}
EXPECT_OK(fdio_open_at(driver_outgoing_.handle()->get(), "/svc",
static_cast<uint32_t>(fuchsia_io::OpenFlags::kDirectory),
svc_endpoints->server.TakeChannel().release()));
zx::result compat_client_end = component::ConnectAt<fuchsia_driver_compat::Device>(
svc_endpoints->client,
component::MakeServiceMemberPath<fuchsia_driver_compat::Service::Device>(
component::kDefaultInstance));
if (compat_client_end.is_error()) {
return compat_client_end.take_error();
}
fidl::WireClient<fuchsia_driver_compat::Device> client(
*std::move(compat_client_end), fdf::Dispatcher::GetCurrent()->async_dispatcher());
zx::result<fuchsia_scheduler::RoleName> scheduler_role_name = zx::error(ZX_ERR_NOT_FOUND);
client->GetMetadata().Then(
[&](fidl::WireUnownedResult<fuchsia_driver_compat::Device::GetMetadata>& result) {
if (!result.ok()) {
scheduler_role_name = zx::error(result.status());
return;
}
if (result->is_error()) {
scheduler_role_name = result->take_error();
return;
}
for (auto& metadata : result->value()->metadata) {
if (metadata.type != DEVICE_METADATA_SCHEDULER_ROLE_NAME) {
continue;
}
size_t size = 0;
zx_status_t status = metadata.data.get_prop_content_size(&size);
if (status != ZX_OK) {
continue;
}
std::vector<uint8_t> data(size);
status = metadata.data.read(data.data(), 0, data.size());
if (status != ZX_OK) {
continue;
}
auto role_name = fidl::Unpersist<fuchsia_scheduler::RoleName>(std::move(data));
if (role_name.is_error()) {
continue;
}
scheduler_role_name = zx::ok(*std::move(role_name));
break;
}
runtime_.Quit();
});
runtime_.Run();
return scheduler_role_name;
}
fdf_testing::DriverRuntime runtime_;
FakePDev pdev_server_;
mock_registers::MockRegisters registers_;
std::queue<std::pair<zx_status_t, uint8_t>> gpio_writes_;
fake_gpio::FakeGpio gpio_;
fdf_testing::TestNode node_server_;
fdf_testing::TestEnvironment test_environment_;
fdf_testing::DriverUnderTest<TestAmlSpiDriver> dut_;
fuchsia_driver_framework::DriverStartArgs start_args_;
private:
static constexpr amlogic_spi::amlspi_config_t kSpiConfig = {
.bus_id = 0,
.cs_count = 3,
.cs = {5, 3, amlogic_spi::amlspi_config_t::kCsClientManaged},
.clock_divider_register_value = 0,
.use_enhanced_clock_mode = false,
};
zx::interrupt interrupt_;
fidl::ClientEnd<fuchsia_io::Directory> driver_outgoing_;
compat::DeviceServer compat_;
compat::DeviceServer compat_default_;
};
zx_koid_t GetVmoKoid(const zx::vmo& vmo) {
zx_info_handle_basic_t info = {};
size_t actual = 0;
size_t available = 0;
zx_status_t status = vmo.get_info(ZX_INFO_HANDLE_BASIC, &info, sizeof(info), &actual, &available);
if (status != ZX_OK || actual < 1) {
return ZX_KOID_INVALID;
}
return info.koid;
}
TEST_F(AmlSpiTest, DdkLifecycle) {
zx::result start_result = runtime_.RunToCompletion(dut_.Start(std::move(start_args_)));
ASSERT_TRUE(start_result.is_ok());
ASSERT_NE(node_server_.children().find("aml-spi-0"), node_server_.children().cend());
}
TEST_F(AmlSpiTest, ChipSelectCount) {
zx::result start_result = runtime_.RunToCompletion(dut_.Start(std::move(start_args_)));
ASSERT_TRUE(start_result.is_ok());
auto spiimpl_client = GetFidlClient();
ASSERT_TRUE(spiimpl_client.is_ok());
fdf::WireClient<fuchsia_hardware_spiimpl::SpiImpl> spiimpl(*std::move(spiimpl_client),
fdf::Dispatcher::GetCurrent()->get());
fdf::Arena arena('TEST');
spiimpl.buffer(arena)->GetChipSelectCount().Then([&](auto& result) {
ASSERT_TRUE(result.ok());
EXPECT_EQ(result->count, 3);
runtime_.Quit();
});
runtime_.Run();
}
TEST_F(AmlSpiTest, Exchange) {
uint8_t kTxData[] = {0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12};
constexpr uint8_t kExpectedRxData[] = {0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab};
zx::result start_result = runtime_.RunToCompletion(dut_.Start(std::move(start_args_)));
ASSERT_TRUE(start_result.is_ok());
auto spiimpl_client = GetFidlClient();
ASSERT_TRUE(spiimpl_client.is_ok());
fdf::WireClient<fuchsia_hardware_spiimpl::SpiImpl> spiimpl(*std::move(spiimpl_client),
fdf::Dispatcher::GetCurrent()->get());
mmio()[AML_SPI_RXDATA].SetReadCallback([]() { return kExpectedRxData[0]; });
uint64_t tx_data = 0;
mmio()[AML_SPI_TXDATA].SetWriteCallback([&tx_data](uint64_t value) { tx_data = value; });
ExpectGpioWrite(ZX_OK, 0);
ExpectGpioWrite(ZX_OK, 1);
fdf::Arena arena('TEST');
spiimpl.buffer(arena)
->ExchangeVector(0, fidl::VectorView<uint8_t>::FromExternal(kTxData, sizeof(kTxData)))
.Then([&](auto& result) {
ASSERT_TRUE(result.ok());
ASSERT_TRUE(result->is_ok());
ASSERT_EQ(result->value()->rxdata.count(), sizeof(kExpectedRxData));
EXPECT_BYTES_EQ(result->value()->rxdata.data(), kExpectedRxData, sizeof(kExpectedRxData));
runtime_.Quit();
});
runtime_.Run();
EXPECT_EQ(tx_data, kTxData[0]);
EXPECT_FALSE(ControllerReset());
ASSERT_NO_FATAL_FAILURE(VerifyGpioAndClear());
}
TEST_F(AmlSpiTest, ExchangeCsManagedByClient) {
uint8_t kTxData[] = {0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12};
constexpr uint8_t kExpectedRxData[] = {0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab};
zx::result start_result = runtime_.RunToCompletion(dut_.Start(std::move(start_args_)));
ASSERT_TRUE(start_result.is_ok());
auto spiimpl_client = GetFidlClient();
ASSERT_TRUE(spiimpl_client.is_ok());
fdf::WireClient<fuchsia_hardware_spiimpl::SpiImpl> spiimpl(*std::move(spiimpl_client),
fdf::Dispatcher::GetCurrent()->get());
mmio()[AML_SPI_RXDATA].SetReadCallback([]() { return kExpectedRxData[0]; });
uint64_t tx_data = 0;
mmio()[AML_SPI_TXDATA].SetWriteCallback([&tx_data](uint64_t value) { tx_data = value; });
fdf::Arena arena('TEST');
spiimpl.buffer(arena)
->ExchangeVector(2, fidl::VectorView<uint8_t>::FromExternal(kTxData, sizeof(kTxData)))
.Then([&](auto& result) {
ASSERT_TRUE(result.ok());
ASSERT_TRUE(result->is_ok());
ASSERT_EQ(result->value()->rxdata.count(), sizeof(kExpectedRxData));
EXPECT_BYTES_EQ(result->value()->rxdata.data(), kExpectedRxData, sizeof(kExpectedRxData));
runtime_.Quit();
});
runtime_.Run();
EXPECT_EQ(tx_data, kTxData[0]);
EXPECT_FALSE(ControllerReset());
// There should be no GPIO calls as the client manages CS for this device.
ASSERT_NO_FATAL_FAILURE(VerifyGpioAndClear());
}
TEST_F(AmlSpiTest, RegisterVmo) {
using fuchsia_hardware_sharedmemory::SharedVmoRight;
zx::result start_result = runtime_.RunToCompletion(dut_.Start(std::move(start_args_)));
ASSERT_TRUE(start_result.is_ok());
auto spiimpl_client = GetFidlClient();
ASSERT_TRUE(spiimpl_client.is_ok());
fdf::WireClient<fuchsia_hardware_spiimpl::SpiImpl> spiimpl(*std::move(spiimpl_client),
fdf::Dispatcher::GetCurrent()->get());
zx::vmo test_vmo;
EXPECT_OK(zx::vmo::create(PAGE_SIZE, 0, &test_vmo));
const zx_koid_t test_vmo_koid = GetVmoKoid(test_vmo);
fdf::Arena arena('TEST');
{
zx::vmo vmo;
EXPECT_OK(test_vmo.duplicate(ZX_RIGHT_SAME_RIGHTS, &vmo));
spiimpl.buffer(arena)
->RegisterVmo(0, 1, {std::move(vmo), 0, PAGE_SIZE}, SharedVmoRight::kRead)
.Then([&](auto& result) {
ASSERT_TRUE(result.ok());
EXPECT_TRUE(result->is_ok());
});
}
{
zx::vmo vmo;
EXPECT_OK(test_vmo.duplicate(ZX_RIGHT_SAME_RIGHTS, &vmo));
spiimpl.buffer(arena)
->RegisterVmo(0, 1, {std::move(vmo), 0, PAGE_SIZE}, SharedVmoRight::kRead)
.Then([&](auto& result) {
ASSERT_TRUE(result.ok());
EXPECT_TRUE(result->is_error());
});
}
spiimpl.buffer(arena)->UnregisterVmo(0, 1).Then([&](auto& result) {
ASSERT_TRUE(result.ok());
ASSERT_TRUE(result->is_ok());
EXPECT_EQ(test_vmo_koid, GetVmoKoid(result->value()->vmo));
});
spiimpl.buffer(arena)->UnregisterVmo(0, 1).Then([&](auto& result) {
ASSERT_TRUE(result.ok());
EXPECT_TRUE(result->is_error());
runtime_.Quit();
});
runtime_.Run();
}
TEST_F(AmlSpiTest, TransmitVmo) {
using fuchsia_hardware_sharedmemory::SharedVmoRight;
constexpr uint8_t kTxData[] = {0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5};
zx::result start_result = runtime_.RunToCompletion(dut_.Start(std::move(start_args_)));
ASSERT_TRUE(start_result.is_ok());
auto spiimpl_client = GetFidlClient();
ASSERT_TRUE(spiimpl_client.is_ok());
fdf::WireClient<fuchsia_hardware_spiimpl::SpiImpl> spiimpl(*std::move(spiimpl_client),
fdf::Dispatcher::GetCurrent()->get());
zx::vmo test_vmo;
EXPECT_OK(zx::vmo::create(PAGE_SIZE, 0, &test_vmo));
fdf::Arena arena('TEST');
{
zx::vmo vmo;
EXPECT_OK(test_vmo.duplicate(ZX_RIGHT_SAME_RIGHTS, &vmo));
spiimpl.buffer(arena)
->RegisterVmo(0, 1, {std::move(vmo), 256, PAGE_SIZE - 256}, SharedVmoRight::kRead)
.Then([&](auto& result) {
ASSERT_TRUE(result.ok());
EXPECT_TRUE(result->is_ok());
});
}
ExpectGpioWrite(ZX_OK, 0);
ExpectGpioWrite(ZX_OK, 1);
EXPECT_OK(test_vmo.write(kTxData, 512, sizeof(kTxData)));
uint64_t tx_data = 0;
mmio()[AML_SPI_TXDATA].SetWriteCallback([&tx_data](uint64_t value) { tx_data = value; });
spiimpl.buffer(arena)->TransmitVmo(0, {1, 256, sizeof(kTxData)}).Then([&](auto& result) {
ASSERT_TRUE(result.ok());
EXPECT_TRUE(result->is_ok());
runtime_.Quit();
});
runtime_.Run();
EXPECT_EQ(tx_data, kTxData[0]);
EXPECT_FALSE(ControllerReset());
ASSERT_NO_FATAL_FAILURE(VerifyGpioAndClear());
}
TEST_F(AmlSpiTest, ReceiveVmo) {
using fuchsia_hardware_sharedmemory::SharedVmoRight;
constexpr uint8_t kExpectedRxData[] = {0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78};
zx::result start_result = runtime_.RunToCompletion(dut_.Start(std::move(start_args_)));
ASSERT_TRUE(start_result.is_ok());
auto spiimpl_client = GetFidlClient();
ASSERT_TRUE(spiimpl_client.is_ok());
fdf::WireClient<fuchsia_hardware_spiimpl::SpiImpl> spiimpl(*std::move(spiimpl_client),
fdf::Dispatcher::GetCurrent()->get());
zx::vmo test_vmo;
EXPECT_OK(zx::vmo::create(PAGE_SIZE, 0, &test_vmo));
fdf::Arena arena('TEST');
{
zx::vmo vmo;
EXPECT_OK(test_vmo.duplicate(ZX_RIGHT_SAME_RIGHTS, &vmo));
spiimpl.buffer(arena)
->RegisterVmo(0, 1, {std::move(vmo), 256, PAGE_SIZE - 256},
SharedVmoRight::kRead | SharedVmoRight::kWrite)
.Then([&](auto& result) {
ASSERT_TRUE(result.ok());
EXPECT_TRUE(result->is_ok());
});
}
mmio()[AML_SPI_RXDATA].SetReadCallback([]() { return kExpectedRxData[0]; });
ExpectGpioWrite(ZX_OK, 0);
ExpectGpioWrite(ZX_OK, 1);
spiimpl.buffer(arena)->ReceiveVmo(0, {1, 512, sizeof(kExpectedRxData)}).Then([&](auto& result) {
ASSERT_TRUE(result.ok());
EXPECT_TRUE(result->is_ok());
runtime_.Quit();
});
runtime_.Run();
uint8_t rx_buffer[sizeof(kExpectedRxData)];
EXPECT_OK(test_vmo.read(rx_buffer, 768, sizeof(rx_buffer)));
EXPECT_BYTES_EQ(rx_buffer, kExpectedRxData, sizeof(rx_buffer));
EXPECT_FALSE(ControllerReset());
ASSERT_NO_FATAL_FAILURE(VerifyGpioAndClear());
}
TEST_F(AmlSpiTest, ExchangeVmo) {
using fuchsia_hardware_sharedmemory::SharedVmoRight;
constexpr uint8_t kTxData[] = {0xef, 0xef, 0xef, 0xef, 0xef, 0xef, 0xef};
constexpr uint8_t kExpectedRxData[] = {0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78};
zx::result start_result = runtime_.RunToCompletion(dut_.Start(std::move(start_args_)));
ASSERT_TRUE(start_result.is_ok());
auto spiimpl_client = GetFidlClient();
ASSERT_TRUE(spiimpl_client.is_ok());
fdf::WireClient<fuchsia_hardware_spiimpl::SpiImpl> spiimpl(*std::move(spiimpl_client),
fdf::Dispatcher::GetCurrent()->get());
zx::vmo test_vmo;
EXPECT_OK(zx::vmo::create(PAGE_SIZE, 0, &test_vmo));
fdf::Arena arena('TEST');
{
zx::vmo vmo;
EXPECT_OK(test_vmo.duplicate(ZX_RIGHT_SAME_RIGHTS, &vmo));
spiimpl.buffer(arena)
->RegisterVmo(0, 1, {std::move(vmo), 256, PAGE_SIZE - 256},
SharedVmoRight::kRead | SharedVmoRight::kWrite)
.Then([&](auto& result) {
ASSERT_TRUE(result.ok());
EXPECT_TRUE(result->is_ok());
});
}
mmio()[AML_SPI_RXDATA].SetReadCallback([]() { return kExpectedRxData[0]; });
uint64_t tx_data = 0;
mmio()[AML_SPI_TXDATA].SetWriteCallback([&tx_data](uint64_t value) { tx_data = value; });
ExpectGpioWrite(ZX_OK, 0);
ExpectGpioWrite(ZX_OK, 1);
EXPECT_OK(test_vmo.write(kTxData, 512, sizeof(kTxData)));
spiimpl.buffer(arena)
->ExchangeVmo(0, {1, 256, sizeof(kTxData)}, {1, 512, sizeof(kExpectedRxData)})
.Then([&](auto& result) {
ASSERT_TRUE(result.ok());
EXPECT_TRUE(result->is_ok());
runtime_.Quit();
});
runtime_.Run();
uint8_t rx_buffer[sizeof(kExpectedRxData)];
EXPECT_OK(test_vmo.read(rx_buffer, 768, sizeof(rx_buffer)));
EXPECT_BYTES_EQ(rx_buffer, kExpectedRxData, sizeof(rx_buffer));
EXPECT_EQ(tx_data, kTxData[0]);
EXPECT_FALSE(ControllerReset());
ASSERT_NO_FATAL_FAILURE(VerifyGpioAndClear());
}
TEST_F(AmlSpiTest, TransfersOutOfRange) {
using fuchsia_hardware_sharedmemory::SharedVmoRight;
zx::result start_result = runtime_.RunToCompletion(dut_.Start(std::move(start_args_)));
ASSERT_TRUE(start_result.is_ok());
auto spiimpl_client = GetFidlClient();
ASSERT_TRUE(spiimpl_client.is_ok());
fdf::WireClient<fuchsia_hardware_spiimpl::SpiImpl> spiimpl(*std::move(spiimpl_client),
fdf::Dispatcher::GetCurrent()->get());
zx::vmo test_vmo;
EXPECT_OK(zx::vmo::create(PAGE_SIZE, 0, &test_vmo));
fdf::Arena arena('TEST');
{
zx::vmo vmo;
EXPECT_OK(test_vmo.duplicate(ZX_RIGHT_SAME_RIGHTS, &vmo));
spiimpl.buffer(arena)
->RegisterVmo(1, 1, {std::move(vmo), PAGE_SIZE - 4, 4},
SharedVmoRight::kRead | SharedVmoRight::kWrite)
.Then([&](auto& result) {
ASSERT_TRUE(result.ok());
EXPECT_TRUE(result->is_ok());
});
}
ExpectGpioWrite(ZX_OK, 0);
ExpectGpioWrite(ZX_OK, 1);
spiimpl.buffer(arena)->ExchangeVmo(1, {1, 0, 2}, {1, 2, 2}).Then([&](auto& result) {
ASSERT_TRUE(result.ok());
EXPECT_TRUE(result->is_ok());
});
spiimpl.buffer(arena)->ExchangeVmo(1, {1, 0, 2}, {1, 3, 2}).Then([&](auto& result) {
ASSERT_TRUE(result.ok());
EXPECT_TRUE(result->is_error());
});
spiimpl.buffer(arena)->ExchangeVmo(1, {1, 3, 2}, {1, 0, 2}).Then([&](auto& result) {
ASSERT_TRUE(result.ok());
EXPECT_TRUE(result->is_error());
});
spiimpl.buffer(arena)->ExchangeVmo(1, {1, 0, 3}, {1, 2, 3}).Then([&](auto& result) {
ASSERT_TRUE(result.ok());
EXPECT_TRUE(result->is_error());
});
ExpectGpioWrite(ZX_OK, 0);
ExpectGpioWrite(ZX_OK, 1);
spiimpl.buffer(arena)->TransmitVmo(1, {1, 0, 4}).Then([&](auto& result) {
ASSERT_TRUE(result.ok());
EXPECT_TRUE(result->is_ok());
});
spiimpl.buffer(arena)->TransmitVmo(1, {1, 0, 5}).Then([&](auto& result) {
ASSERT_TRUE(result.ok());
EXPECT_TRUE(result->is_error());
});
spiimpl.buffer(arena)->TransmitVmo(1, {1, 3, 2}).Then([&](auto& result) {
ASSERT_TRUE(result.ok());
EXPECT_TRUE(result->is_error());
});
spiimpl.buffer(arena)->TransmitVmo(1, {1, 4, 1}).Then([&](auto& result) {
ASSERT_TRUE(result.ok());
EXPECT_TRUE(result->is_error());
});
spiimpl.buffer(arena)->TransmitVmo(1, {1, 5, 1}).Then([&](auto& result) {
ASSERT_TRUE(result.ok());
EXPECT_TRUE(result->is_error());
});
ExpectGpioWrite(ZX_OK, 0);
ExpectGpioWrite(ZX_OK, 1);
spiimpl.buffer(arena)->ReceiveVmo(1, {1, 0, 4}).Then([&](auto& result) {
ASSERT_TRUE(result.ok());
EXPECT_TRUE(result->is_ok());
});
ExpectGpioWrite(ZX_OK, 0);
ExpectGpioWrite(ZX_OK, 1);
spiimpl.buffer(arena)->ReceiveVmo(1, {1, 3, 1}).Then([&](auto& result) {
ASSERT_TRUE(result.ok());
EXPECT_TRUE(result->is_ok());
});
spiimpl.buffer(arena)->ReceiveVmo(1, {1, 3, 2}).Then([&](auto& result) {
ASSERT_TRUE(result.ok());
EXPECT_TRUE(result->is_error());
});
spiimpl.buffer(arena)->ReceiveVmo(1, {1, 4, 1}).Then([&](auto& result) {
ASSERT_TRUE(result.ok());
EXPECT_TRUE(result->is_error());
});
spiimpl.buffer(arena)->ReceiveVmo(1, {1, 5, 1}).Then([&](auto& result) {
ASSERT_TRUE(result.ok());
EXPECT_TRUE(result->is_error());
runtime_.Quit();
});
runtime_.Run();
ASSERT_NO_FATAL_FAILURE(VerifyGpioAndClear());
}
TEST_F(AmlSpiTest, VmoBadRights) {
using fuchsia_hardware_sharedmemory::SharedVmoRight;
zx::result start_result = runtime_.RunToCompletion(dut_.Start(std::move(start_args_)));
ASSERT_TRUE(start_result.is_ok());
auto spiimpl_client = GetFidlClient();
ASSERT_TRUE(spiimpl_client.is_ok());
fdf::WireClient<fuchsia_hardware_spiimpl::SpiImpl> spiimpl(*std::move(spiimpl_client),
fdf::Dispatcher::GetCurrent()->get());
zx::vmo test_vmo;
EXPECT_OK(zx::vmo::create(PAGE_SIZE, 0, &test_vmo));
fdf::Arena arena('TEST');
{
zx::vmo vmo;
EXPECT_OK(test_vmo.duplicate(ZX_RIGHT_SAME_RIGHTS, &vmo));
spiimpl.buffer(arena)
->RegisterVmo(0, 1, {std::move(vmo), 0, 256}, SharedVmoRight::kRead)
.Then([&](auto& result) {
ASSERT_TRUE(result.ok());
EXPECT_TRUE(result->is_ok());
});
}
{
zx::vmo vmo;
EXPECT_OK(test_vmo.duplicate(ZX_RIGHT_SAME_RIGHTS, &vmo));
spiimpl.buffer(arena)
->RegisterVmo(0, 2, {std::move(vmo), 0, 256},
SharedVmoRight::kRead | SharedVmoRight::kWrite)
.Then([&](auto& result) {
ASSERT_TRUE(result.ok());
EXPECT_TRUE(result->is_ok());
});
}
ExpectGpioWrite(ZX_OK, 0);
ExpectGpioWrite(ZX_OK, 1);
spiimpl.buffer(arena)->ExchangeVmo(0, {1, 0, 128}, {2, 128, 128}).Then([&](auto& result) {
ASSERT_TRUE(result.ok());
EXPECT_TRUE(result->is_ok());
});
spiimpl.buffer(arena)->ExchangeVmo(0, {2, 0, 128}, {1, 128, 128}).Then([&](auto& result) {
ASSERT_TRUE(result.ok());
EXPECT_EQ(result->error_value(), ZX_ERR_ACCESS_DENIED);
});
spiimpl.buffer(arena)->ExchangeVmo(0, {1, 0, 128}, {1, 128, 128}).Then([&](auto& result) {
ASSERT_TRUE(result.ok());
EXPECT_EQ(result->error_value(), ZX_ERR_ACCESS_DENIED);
});
spiimpl.buffer(arena)->ReceiveVmo(0, {1, 0, 128}).Then([&](auto& result) {
ASSERT_TRUE(result.ok());
EXPECT_EQ(result->error_value(), ZX_ERR_ACCESS_DENIED);
runtime_.Quit();
});
runtime_.Run();
ASSERT_NO_FATAL_FAILURE(VerifyGpioAndClear());
}
TEST_F(AmlSpiTest, Exchange64BitWords) {
uint8_t kTxData[] = {
0x3c, 0xa7, 0x5f, 0xc8, 0x4b, 0x0b, 0xdf, 0xef, 0xb9, 0xa0, 0xcb, 0xbd,
0xd4, 0xcf, 0xa8, 0xbf, 0x85, 0xf2, 0x6a, 0xe3, 0xba, 0xf1, 0x49, 0x00,
};
constexpr uint8_t kExpectedRxData[] = {
0xea, 0x2b, 0x8f, 0x8f, 0xea, 0x2b, 0x8f, 0x8f, 0xea, 0x2b, 0x8f, 0x8f,
0xea, 0x2b, 0x8f, 0x8f, 0xea, 0x2b, 0x8f, 0x8f, 0xea, 0x2b, 0x8f, 0x8f,
};
zx::result start_result = runtime_.RunToCompletion(dut_.Start(std::move(start_args_)));
ASSERT_TRUE(start_result.is_ok());
auto spiimpl_client = GetFidlClient();
ASSERT_TRUE(spiimpl_client.is_ok());
fdf::WireClient<fuchsia_hardware_spiimpl::SpiImpl> spiimpl(*std::move(spiimpl_client),
fdf::Dispatcher::GetCurrent()->get());
// First (and only) word of kExpectedRxData with bytes swapped.
mmio()[AML_SPI_RXDATA].SetReadCallback([]() { return 0xea2b'8f8f; });
uint64_t tx_data = 0;
mmio()[AML_SPI_TXDATA].SetWriteCallback([&tx_data](uint64_t value) { tx_data = value; });
ExpectGpioWrite(ZX_OK, 0);
ExpectGpioWrite(ZX_OK, 1);
fdf::Arena arena('TEST');
spiimpl.buffer(arena)
->ExchangeVector(0, fidl::VectorView<uint8_t>::FromExternal(kTxData, sizeof(kTxData)))
.Then([&](auto& result) {
ASSERT_TRUE(result.ok());
ASSERT_TRUE(result->is_ok());
ASSERT_EQ(result->value()->rxdata.count(), sizeof(kExpectedRxData));
EXPECT_BYTES_EQ(result->value()->rxdata.data(), kExpectedRxData, sizeof(kExpectedRxData));
runtime_.Quit();
});
runtime_.Run();
// Last word of kTxData with bytes swapped.
EXPECT_EQ(tx_data, 0xbaf1'4900);
EXPECT_FALSE(ControllerReset());
ASSERT_NO_FATAL_FAILURE(VerifyGpioAndClear());
}
TEST_F(AmlSpiTest, Exchange64Then8BitWords) {
uint8_t kTxData[] = {
0x3c, 0xa7, 0x5f, 0xc8, 0x4b, 0x0b, 0xdf, 0xef, 0xb9, 0xa0, 0xcb,
0xbd, 0xd4, 0xcf, 0xa8, 0xbf, 0x85, 0xf2, 0x6a, 0xe3, 0xba,
};
constexpr uint8_t kExpectedRxData[] = {
0x00, 0x00, 0x00, 0xea, 0x00, 0x00, 0x00, 0xea, 0x00, 0x00, 0x00,
0xea, 0x00, 0x00, 0x00, 0xea, 0xea, 0xea, 0xea, 0xea, 0xea,
};
zx::result start_result = runtime_.RunToCompletion(dut_.Start(std::move(start_args_)));
ASSERT_TRUE(start_result.is_ok());
auto spiimpl_client = GetFidlClient();
ASSERT_TRUE(spiimpl_client.is_ok());
fdf::WireClient<fuchsia_hardware_spiimpl::SpiImpl> spiimpl(*std::move(spiimpl_client),
fdf::Dispatcher::GetCurrent()->get());
mmio()[AML_SPI_RXDATA].SetReadCallback([]() { return 0xea; });
uint64_t tx_data = 0;
mmio()[AML_SPI_TXDATA].SetWriteCallback([&tx_data](uint64_t value) { tx_data = value; });
ExpectGpioWrite(ZX_OK, 0);
ExpectGpioWrite(ZX_OK, 1);
fdf::Arena arena('TEST');
spiimpl.buffer(arena)
->ExchangeVector(0, fidl::VectorView<uint8_t>::FromExternal(kTxData, sizeof(kTxData)))
.Then([&](auto& result) {
ASSERT_TRUE(result.ok());
ASSERT_TRUE(result->is_ok());
ASSERT_EQ(result->value()->rxdata.count(), sizeof(kExpectedRxData));
EXPECT_BYTES_EQ(result->value()->rxdata.data(), kExpectedRxData, sizeof(kExpectedRxData));
runtime_.Quit();
});
runtime_.Run();
EXPECT_EQ(tx_data, 0xba);
EXPECT_FALSE(ControllerReset());
ASSERT_NO_FATAL_FAILURE(VerifyGpioAndClear());
}
TEST_F(AmlSpiTest, ExchangeResetsController) {
zx::result start_result = runtime_.RunToCompletion(dut_.Start(std::move(start_args_)));
ASSERT_TRUE(start_result.is_ok());
auto spiimpl_client = GetFidlClient();
ASSERT_TRUE(spiimpl_client.is_ok());
fdf::WireClient<fuchsia_hardware_spiimpl::SpiImpl> spiimpl(*std::move(spiimpl_client),
fdf::Dispatcher::GetCurrent()->get());
ExpectGpioWrite(ZX_OK, 0);
ExpectGpioWrite(ZX_OK, 1);
fdf::Arena arena('TEST');
uint8_t buf[17] = {};
spiimpl.buffer(arena)
->ExchangeVector(0, fidl::VectorView<uint8_t>::FromExternal(buf, 17))
.Then([&](auto& result) {
ASSERT_TRUE(result.ok());
ASSERT_TRUE(result->is_ok());
EXPECT_EQ(result->value()->rxdata.count(), 17);
EXPECT_FALSE(ControllerReset());
});
ExpectGpioWrite(ZX_OK, 0);
ExpectGpioWrite(ZX_OK, 1);
// Controller should be reset because a 64-bit transfer was preceded by a transfer of an odd
// number of bytes.
spiimpl.buffer(arena)
->ExchangeVector(0, fidl::VectorView<uint8_t>::FromExternal(buf, 16))
.Then([&](auto& result) {
ASSERT_TRUE(result.ok());
ASSERT_TRUE(result->is_ok());
EXPECT_EQ(result->value()->rxdata.count(), 16);
EXPECT_TRUE(ControllerReset());
});
ExpectGpioWrite(ZX_OK, 0);
ExpectGpioWrite(ZX_OK, 1);
spiimpl.buffer(arena)
->ExchangeVector(0, fidl::VectorView<uint8_t>::FromExternal(buf, 3))
.Then([&](auto& result) {
ASSERT_TRUE(result.ok());
ASSERT_TRUE(result->is_ok());
EXPECT_EQ(result->value()->rxdata.count(), 3);
EXPECT_FALSE(ControllerReset());
});
ExpectGpioWrite(ZX_OK, 0);
ExpectGpioWrite(ZX_OK, 1);
spiimpl.buffer(arena)
->ExchangeVector(0, fidl::VectorView<uint8_t>::FromExternal(buf, 6))
.Then([&](auto& result) {
ASSERT_TRUE(result.ok());
ASSERT_TRUE(result->is_ok());
EXPECT_EQ(result->value()->rxdata.count(), 6);
EXPECT_FALSE(ControllerReset());
});
ExpectGpioWrite(ZX_OK, 0);
ExpectGpioWrite(ZX_OK, 1);
spiimpl.buffer(arena)
->ExchangeVector(0, fidl::VectorView<uint8_t>::FromExternal(buf, 8))
.Then([&](auto& result) {
ASSERT_TRUE(result.ok());
ASSERT_TRUE(result->is_ok());
EXPECT_EQ(result->value()->rxdata.count(), 8);
EXPECT_TRUE(ControllerReset());
runtime_.Quit();
});
runtime_.Run();
ASSERT_NO_FATAL_FAILURE(VerifyGpioAndClear());
}
TEST_F(AmlSpiTest, ReleaseVmos) {
using fuchsia_hardware_sharedmemory::SharedVmoRight;
zx::result start_result = runtime_.RunToCompletion(dut_.Start(std::move(start_args_)));
ASSERT_TRUE(start_result.is_ok());
auto spiimpl_client = GetFidlClient();
ASSERT_TRUE(spiimpl_client.is_ok());
fdf::WireClient<fuchsia_hardware_spiimpl::SpiImpl> spiimpl(*std::move(spiimpl_client),
fdf::Dispatcher::GetCurrent()->get());
fdf::Arena arena('TEST');
{
zx::vmo vmo;
EXPECT_OK(zx::vmo::create(PAGE_SIZE, 0, &vmo));
spiimpl.buffer(arena)
->RegisterVmo(0, 1, {std::move(vmo), 0, PAGE_SIZE}, SharedVmoRight::kRead)
.Then([&](auto& result) {
ASSERT_TRUE(result.ok());
EXPECT_TRUE(result->is_ok());
});
EXPECT_OK(zx::vmo::create(PAGE_SIZE, 0, &vmo));
spiimpl.buffer(arena)
->RegisterVmo(0, 2, {std::move(vmo), 0, PAGE_SIZE}, SharedVmoRight::kRead)
.Then([&](auto& result) {
ASSERT_TRUE(result.ok());
EXPECT_TRUE(result->is_ok());
});
}
spiimpl.buffer(arena)->UnregisterVmo(0, 2).Then([&](auto& result) {
ASSERT_TRUE(result.ok());
EXPECT_TRUE(result->is_ok());
});
// Release VMO 1 and make sure that a subsequent call to unregister it fails.
EXPECT_TRUE(spiimpl.buffer(arena)->ReleaseRegisteredVmos(0).ok());
spiimpl.buffer(arena)->UnregisterVmo(0, 2).Then([&](auto& result) {
ASSERT_TRUE(result.ok());
EXPECT_TRUE(result->is_error());
});
{
zx::vmo vmo;
EXPECT_OK(zx::vmo::create(PAGE_SIZE, 0, &vmo));
spiimpl.buffer(arena)
->RegisterVmo(0, 1, {std::move(vmo), 0, PAGE_SIZE}, SharedVmoRight::kRead)
.Then([&](auto& result) {
ASSERT_TRUE(result.ok());
EXPECT_TRUE(result->is_ok());
});
EXPECT_OK(zx::vmo::create(PAGE_SIZE, 0, &vmo));
spiimpl.buffer(arena)
->RegisterVmo(0, 2, {std::move(vmo), 0, PAGE_SIZE}, SharedVmoRight::kRead)
.Then([&](auto& result) {
ASSERT_TRUE(result.ok());
EXPECT_TRUE(result->is_ok());
});
}
// Release both VMOs and make sure that they can be registered again.
EXPECT_TRUE(spiimpl.buffer(arena)->ReleaseRegisteredVmos(0).ok());
{
zx::vmo vmo;
EXPECT_OK(zx::vmo::create(PAGE_SIZE, 0, &vmo));
spiimpl.buffer(arena)
->RegisterVmo(0, 1, {std::move(vmo), 0, PAGE_SIZE}, SharedVmoRight::kRead)
.Then([&](auto& result) {
ASSERT_TRUE(result.ok());
EXPECT_TRUE(result->is_ok());
});
EXPECT_OK(zx::vmo::create(PAGE_SIZE, 0, &vmo));
spiimpl.buffer(arena)
->RegisterVmo(0, 2, {std::move(vmo), 0, PAGE_SIZE}, SharedVmoRight::kRead)
.Then([&](auto& result) {
ASSERT_TRUE(result.ok());
EXPECT_TRUE(result->is_ok());
runtime_.Quit();
});
}
runtime_.Run();
}
TEST_F(AmlSpiTest, ReleaseVmosAfterClientsUnbind) {
using fuchsia_hardware_sharedmemory::SharedVmoRight;
zx::result start_result = runtime_.RunToCompletion(dut_.Start(std::move(start_args_)));
ASSERT_TRUE(start_result.is_ok());
auto spiimpl_client1 = GetFidlClient();
ASSERT_TRUE(spiimpl_client1.is_ok());
fdf::WireClient<fuchsia_hardware_spiimpl::SpiImpl> spiimpl1(*std::move(spiimpl_client1),
fdf::Dispatcher::GetCurrent()->get());
fdf::Arena arena('TEST');
// Register three VMOs through the first client.
for (uint32_t i = 1; i <= 3; i++) {
zx::vmo vmo;
EXPECT_OK(zx::vmo::create(PAGE_SIZE, 0, &vmo));
spiimpl1.buffer(arena)
->RegisterVmo(0, i, {std::move(vmo), 0, PAGE_SIZE}, SharedVmoRight::kRead)
.Then([&](auto& result) {
ASSERT_TRUE(result.ok());
EXPECT_TRUE(result->is_ok());
runtime_.Quit();
});
runtime_.Run();
runtime_.ResetQuit();
}
auto spiimpl_client2 = GetFidlClient();
ASSERT_TRUE(spiimpl_client2.is_ok());
fdf::WireClient<fuchsia_hardware_spiimpl::SpiImpl> spiimpl2(*std::move(spiimpl_client2),
fdf::Dispatcher::GetCurrent()->get());
// The second client should be able to see the registered VMOs.
spiimpl2.buffer(arena)->UnregisterVmo(0, 1).Then([&](auto& result) {
ASSERT_TRUE(result.ok());
EXPECT_TRUE(result->is_ok());
runtime_.Quit();
});
runtime_.Run();
runtime_.ResetQuit();
// Unbind the first client.
EXPECT_TRUE(spiimpl1.UnbindMaybeGetEndpoint().is_ok());
runtime_.RunUntilIdle();
// The VMOs registered by the first client should remain.
spiimpl2.buffer(arena)->UnregisterVmo(0, 2).Then([&](auto& result) {
ASSERT_TRUE(result.ok());
EXPECT_TRUE(result->is_ok());
runtime_.Quit();
});
runtime_.Run();
runtime_.ResetQuit();
// Unbind the second client, then connect a third client.
EXPECT_TRUE(spiimpl2.UnbindMaybeGetEndpoint().is_ok());
runtime_.RunUntilIdle();
auto spiimpl_client3 = GetFidlClient();
ASSERT_TRUE(spiimpl_client3.is_ok());
fdf::WireClient<fuchsia_hardware_spiimpl::SpiImpl> spiimpl3(*std::move(spiimpl_client3),
fdf::Dispatcher::GetCurrent()->get());
// All registered VMOs should have been released after the second client unbound.
spiimpl3.buffer(arena)->UnregisterVmo(0, 3).Then([&](auto& result) {
ASSERT_TRUE(result.ok());
EXPECT_TRUE(result->is_error());
runtime_.Quit();
});
runtime_.Run();
}
class AmlSpiNormalClockModeTest : public AmlSpiTest {
public:
void SetMetadata(compat::DeviceServer& compat) override {
constexpr amlogic_spi::amlspi_config_t kSpiConfig{
.bus_id = 0,
.cs_count = 2,
.cs = {5, 3},
.clock_divider_register_value = 0x5,
.use_enhanced_clock_mode = false,
};
EXPECT_OK(compat.AddMetadata(DEVICE_METADATA_AMLSPI_CONFIG, &kSpiConfig, sizeof(kSpiConfig)));
}
};
TEST_F(AmlSpiNormalClockModeTest, Test) {
zx::result start_result = runtime_.RunToCompletion(dut_.Start(std::move(start_args_)));
ASSERT_TRUE(start_result.is_ok());
auto conreg = ConReg::Get().FromValue(dut_->conreg());
auto enhanced_cntl = EnhanceCntl::Get().FromValue(dut_->enhance_cntl());
auto testreg = TestReg::Get().FromValue(dut_->testreg());
EXPECT_EQ(conreg.data_rate(), 0x5);
EXPECT_EQ(conreg.drctl(), 0);
EXPECT_EQ(conreg.ssctl(), 0);
EXPECT_EQ(conreg.smc(), 0);
EXPECT_EQ(conreg.xch(), 0);
EXPECT_EQ(conreg.mode(), ConReg::kModeMaster);
EXPECT_EQ(conreg.en(), 1);
EXPECT_EQ(enhanced_cntl.reg_value(), 0);
EXPECT_EQ(testreg.dlyctl(), 0x15);
EXPECT_EQ(testreg.clk_free_en(), 1);
}
class AmlSpiEnhancedClockModeTest : public AmlSpiTest {
public:
void SetMetadata(compat::DeviceServer& compat) override {
constexpr amlogic_spi::amlspi_config_t kSpiConfig{
.bus_id = 0,
.cs_count = 2,
.cs = {5, 3},
.clock_divider_register_value = 0xa5,
.use_enhanced_clock_mode = true,
.delay_control = 0b00'11'00,
};
EXPECT_OK(compat.AddMetadata(DEVICE_METADATA_AMLSPI_CONFIG, &kSpiConfig, sizeof(kSpiConfig)));
}
};
TEST_F(AmlSpiEnhancedClockModeTest, Test) {
zx::result start_result = runtime_.RunToCompletion(dut_.Start(std::move(start_args_)));
ASSERT_TRUE(start_result.is_ok());
auto conreg = ConReg::Get().FromValue(dut_->conreg());
auto enhanced_cntl = EnhanceCntl::Get().FromValue(dut_->enhance_cntl());
auto testreg = TestReg::Get().FromValue(dut_->testreg());
EXPECT_EQ(conreg.data_rate(), 0);
EXPECT_EQ(conreg.drctl(), 0);
EXPECT_EQ(conreg.ssctl(), 0);
EXPECT_EQ(conreg.smc(), 0);
EXPECT_EQ(conreg.xch(), 0);
EXPECT_EQ(conreg.mode(), ConReg::kModeMaster);
EXPECT_EQ(conreg.en(), 1);
EXPECT_EQ(enhanced_cntl.main_clock_always_on(), 0);
EXPECT_EQ(enhanced_cntl.clk_cs_delay_enable(), 1);
EXPECT_EQ(enhanced_cntl.cs_oen_enhance_enable(), 1);
EXPECT_EQ(enhanced_cntl.clk_oen_enhance_enable(), 1);
EXPECT_EQ(enhanced_cntl.mosi_oen_enhance_enable(), 1);
EXPECT_EQ(enhanced_cntl.spi_clk_select(), 1);
EXPECT_EQ(enhanced_cntl.enhance_clk_div(), 0xa5);
EXPECT_EQ(enhanced_cntl.clk_cs_delay(), 0);
EXPECT_EQ(testreg.dlyctl(), 0b00'11'00);
EXPECT_EQ(testreg.clk_free_en(), 1);
}
class AmlSpiNormalClockModeInvalidDividerTest : public AmlSpiTest {
public:
void SetMetadata(compat::DeviceServer& compat) override {
constexpr amlogic_spi::amlspi_config_t kSpiConfig{
.bus_id = 0,
.cs_count = 2,
.cs = {5, 3},
.clock_divider_register_value = 0xa5,
.use_enhanced_clock_mode = false,
};
EXPECT_OK(compat.AddMetadata(DEVICE_METADATA_AMLSPI_CONFIG, &kSpiConfig, sizeof(kSpiConfig)));
}
};
TEST_F(AmlSpiNormalClockModeInvalidDividerTest, Test) {
zx::result start_result = runtime_.RunToCompletion(dut_.Start(std::move(start_args_)));
EXPECT_TRUE(start_result.is_error());
}
class AmlSpiEnhancedClockModeInvalidDividerTest : public AmlSpiTest {
public:
void SetMetadata(compat::DeviceServer& compat) override {
constexpr amlogic_spi::amlspi_config_t kSpiConfig{
.bus_id = 0,
.cs_count = 2,
.cs = {5, 3},
.clock_divider_register_value = 0x1a5,
.use_enhanced_clock_mode = true,
};
EXPECT_OK(compat.AddMetadata(DEVICE_METADATA_AMLSPI_CONFIG, &kSpiConfig, sizeof(kSpiConfig)));
}
};
TEST_F(AmlSpiEnhancedClockModeInvalidDividerTest, Test) {
zx::result start_result = runtime_.RunToCompletion(dut_.Start(std::move(start_args_)));
EXPECT_TRUE(start_result.is_error());
}
class AmlSpiBtiPaddrTest : public AmlSpiTest {
public:
static constexpr zx_paddr_t kDmaPaddrs[] = {0x1212'0000, 0xabab'000};
virtual void SetUpBti() override {
zx::bti bti;
ASSERT_OK(fake_bti_create_with_paddrs(kDmaPaddrs, std::size(kDmaPaddrs),
bti.reset_and_get_address()));
bti_local_ = bti.borrow();
pdev_server_.set_bti(std::move(bti));
}
protected:
zx::unowned_bti bti_local_;
};
TEST_F(AmlSpiBtiPaddrTest, ExchangeDma) {
constexpr uint8_t kTxData[24] = {
0x3c, 0xa7, 0x5f, 0xc8, 0x4b, 0x0b, 0xdf, 0xef, 0xb9, 0xa0, 0xcb, 0xbd,
0xd4, 0xcf, 0xa8, 0xbf, 0x85, 0xf2, 0x6a, 0xe3, 0xba, 0xf1, 0x49, 0x00,
};
constexpr uint8_t kExpectedRxData[24] = {
0xea, 0x2b, 0x8f, 0x8f, 0xea, 0x2b, 0x8f, 0x8f, 0xea, 0x2b, 0x8f, 0x8f,
0xea, 0x2b, 0x8f, 0x8f, 0xea, 0x2b, 0x8f, 0x8f, 0xea, 0x2b, 0x8f, 0x8f,
};
uint8_t reversed_tx_data[24];
for (size_t i = 0; i < sizeof(kTxData); i += sizeof(uint64_t)) {
uint64_t tmp;
memcpy(&tmp, kTxData + i, sizeof(tmp));
tmp = htobe64(tmp);
memcpy(reversed_tx_data + i, &tmp, sizeof(tmp));
}
uint8_t reversed_expected_rx_data[24];
for (size_t i = 0; i < sizeof(kExpectedRxData); i += sizeof(uint64_t)) {
uint64_t tmp;
memcpy(&tmp, kExpectedRxData + i, sizeof(tmp));
tmp = htobe64(tmp);
memcpy(reversed_expected_rx_data + i, &tmp, sizeof(tmp));
}
zx::result start_result = runtime_.RunToCompletion(dut_.Start(std::move(start_args_)));
ASSERT_TRUE(start_result.is_ok());
auto spiimpl_client = GetFidlClient();
ASSERT_TRUE(spiimpl_client.is_ok());
fdf::WireClient<fuchsia_hardware_spiimpl::SpiImpl> spiimpl(*std::move(spiimpl_client),
fdf::Dispatcher::GetCurrent()->get());
fake_bti_pinned_vmo_info_t dma_vmos[2] = {};
size_t actual_vmos = 0;
EXPECT_OK(
fake_bti_get_pinned_vmos(bti_local_->get(), dma_vmos, std::size(dma_vmos), &actual_vmos));
EXPECT_EQ(actual_vmos, std::size(dma_vmos));
zx::vmo tx_dma_vmo(dma_vmos[0].vmo);
zx::vmo rx_dma_vmo(dma_vmos[1].vmo);
// Copy the reversed expected RX data to the RX VMO. The driver should copy this to the user
// output buffer with the correct endianness.
rx_dma_vmo.write(reversed_expected_rx_data, 0, sizeof(reversed_expected_rx_data));
zx_paddr_t tx_paddr = 0;
zx_paddr_t rx_paddr = 0;
mmio()[AML_SPI_DRADDR].SetWriteCallback([&tx_paddr](uint64_t value) { tx_paddr = value; });
mmio()[AML_SPI_DWADDR].SetWriteCallback([&rx_paddr](uint64_t value) { rx_paddr = value; });
ExpectGpioWrite(ZX_OK, 0);
ExpectGpioWrite(ZX_OK, 1);
uint8_t buf[24] = {};
memcpy(buf, kTxData, sizeof(buf));
fdf::Arena arena('TEST');
spiimpl.buffer(arena)
->ExchangeVector(0, fidl::VectorView<uint8_t>::FromExternal(buf, sizeof(buf)))
.Then([&](auto& result) {
ASSERT_TRUE(result.ok());
ASSERT_TRUE(result->is_ok());
ASSERT_EQ(result->value()->rxdata.count(), sizeof(buf));
EXPECT_BYTES_EQ(kExpectedRxData, result->value()->rxdata.data(), sizeof(buf));
runtime_.Quit();
});
runtime_.Run();
// Verify that the driver wrote the TX data to the TX VMO.
EXPECT_OK(tx_dma_vmo.read(buf, 0, sizeof(buf)));
EXPECT_BYTES_EQ(reversed_tx_data, buf, sizeof(buf));
EXPECT_EQ(tx_paddr, kDmaPaddrs[0]);
EXPECT_EQ(rx_paddr, kDmaPaddrs[1]);
EXPECT_FALSE(ControllerReset());
}
class AmlSpiBtiEmptyTest : public AmlSpiTest {
public:
virtual void SetUpBti() override {
zx::bti bti;
ASSERT_OK(fake_bti_create(bti.reset_and_get_address()));
bti_local_ = bti.borrow();
pdev_server_.set_bti(std::move(bti));
}
protected:
zx::unowned_bti bti_local_;
};
TEST_F(AmlSpiBtiEmptyTest, ExchangeFallBackToPio) {
constexpr uint8_t kTxData[15] = {
0x3c, 0xa7, 0x5f, 0xc8, 0x4b, 0x0b, 0xdf, 0xef, 0xb9, 0xa0, 0xcb, 0xbd, 0xd4, 0xcf, 0xa8,
};
constexpr uint8_t kExpectedRxData[15] = {
0xea, 0x2b, 0x8f, 0x8f, 0xea, 0x2b, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f,
};
zx::result start_result = runtime_.RunToCompletion(dut_.Start(std::move(start_args_)));
ASSERT_TRUE(start_result.is_ok());
auto spiimpl_client = GetFidlClient();
ASSERT_TRUE(spiimpl_client.is_ok());
fdf::WireClient<fuchsia_hardware_spiimpl::SpiImpl> spiimpl(*std::move(spiimpl_client),
fdf::Dispatcher::GetCurrent()->get());
fake_bti_pinned_vmo_info_t dma_vmos[2] = {};
size_t actual_vmos = 0;
EXPECT_OK(
fake_bti_get_pinned_vmos(bti_local_->get(), dma_vmos, std::size(dma_vmos), &actual_vmos));
EXPECT_EQ(actual_vmos, std::size(dma_vmos));
zx_paddr_t tx_paddr = 0;
zx_paddr_t rx_paddr = 0;
mmio()[AML_SPI_DRADDR].SetWriteCallback([&tx_paddr](uint64_t value) { tx_paddr = value; });
mmio()[AML_SPI_DWADDR].SetWriteCallback([&rx_paddr](uint64_t value) { rx_paddr = value; });
mmio()[AML_SPI_RXDATA].SetReadCallback([]() { return 0xea2b'8f8f; });
uint64_t tx_data = 0;
mmio()[AML_SPI_TXDATA].SetWriteCallback([&tx_data](uint64_t value) { tx_data = value; });
ExpectGpioWrite(ZX_OK, 0);
ExpectGpioWrite(ZX_OK, 1);
uint8_t buf[15] = {};
memcpy(buf, kTxData, sizeof(buf));
fdf::Arena arena('TEST');
spiimpl.buffer(arena)
->ExchangeVector(0, fidl::VectorView<uint8_t>::FromExternal(buf, sizeof(buf)))
.Then([&](auto& result) {
ASSERT_TRUE(result.ok());
ASSERT_TRUE(result->is_ok());
ASSERT_EQ(result->value()->rxdata.count(), sizeof(buf));
EXPECT_BYTES_EQ(kExpectedRxData, result->value()->rxdata.data(), sizeof(buf));
runtime_.Quit();
});
runtime_.Run();
EXPECT_EQ(tx_data, kTxData[14]);
// Verify that DMA was not used.
EXPECT_EQ(tx_paddr, 0);
EXPECT_EQ(rx_paddr, 0);
EXPECT_FALSE(ControllerReset());
}
class AmlSpiExchangeDmaClientReversesBufferTest : public AmlSpiBtiPaddrTest {
public:
void SetMetadata(compat::DeviceServer& compat) override {
constexpr amlogic_spi::amlspi_config_t kSpiConfig{
.bus_id = 0,
.cs_count = 3,
.cs = {5, 3, amlogic_spi::amlspi_config_t::kCsClientManaged},
.clock_divider_register_value = 0,
.use_enhanced_clock_mode = false,
.client_reverses_dma_transfers = true,
};
EXPECT_OK(compat.AddMetadata(DEVICE_METADATA_AMLSPI_CONFIG, &kSpiConfig, sizeof(kSpiConfig)));
}
};
TEST_F(AmlSpiExchangeDmaClientReversesBufferTest, Test) {
constexpr uint8_t kTxData[24] = {
0x3c, 0xa7, 0x5f, 0xc8, 0x4b, 0x0b, 0xdf, 0xef, 0xb9, 0xa0, 0xcb, 0xbd,
0xd4, 0xcf, 0xa8, 0xbf, 0x85, 0xf2, 0x6a, 0xe3, 0xba, 0xf1, 0x49, 0x00,
};
constexpr uint8_t kExpectedRxData[24] = {
0xea, 0x2b, 0x8f, 0x8f, 0xea, 0x2b, 0x8f, 0x8f, 0xea, 0x2b, 0x8f, 0x8f,
0xea, 0x2b, 0x8f, 0x8f, 0xea, 0x2b, 0x8f, 0x8f, 0xea, 0x2b, 0x8f, 0x8f,
};
zx::result start_result = runtime_.RunToCompletion(dut_.Start(std::move(start_args_)));
ASSERT_TRUE(start_result.is_ok());
auto spiimpl_client = GetFidlClient();
ASSERT_TRUE(spiimpl_client.is_ok());
fdf::WireClient<fuchsia_hardware_spiimpl::SpiImpl> spiimpl(*std::move(spiimpl_client),
fdf::Dispatcher::GetCurrent()->get());
fake_bti_pinned_vmo_info_t dma_vmos[2] = {};
size_t actual_vmos = 0;
EXPECT_OK(
fake_bti_get_pinned_vmos(bti_local_->get(), dma_vmos, std::size(dma_vmos), &actual_vmos));
EXPECT_EQ(actual_vmos, std::size(dma_vmos));
zx::vmo tx_dma_vmo(dma_vmos[0].vmo);
zx::vmo rx_dma_vmo(dma_vmos[1].vmo);
rx_dma_vmo.write(kExpectedRxData, 0, sizeof(kExpectedRxData));
zx_paddr_t tx_paddr = 0;
zx_paddr_t rx_paddr = 0;
mmio()[AML_SPI_DRADDR].SetWriteCallback([&tx_paddr](uint64_t value) { tx_paddr = value; });
mmio()[AML_SPI_DWADDR].SetWriteCallback([&rx_paddr](uint64_t value) { rx_paddr = value; });
ExpectGpioWrite(ZX_OK, 0);
ExpectGpioWrite(ZX_OK, 1);
uint8_t buf[sizeof(kTxData)] = {};
memcpy(buf, kTxData, sizeof(buf));
fdf::Arena arena('TEST');
spiimpl.buffer(arena)
->ExchangeVector(0, fidl::VectorView<uint8_t>::FromExternal(buf, sizeof(buf)))
.Then([&](auto& result) {
ASSERT_TRUE(result.ok());
ASSERT_TRUE(result->is_ok());
ASSERT_EQ(result->value()->rxdata.count(), sizeof(buf));
EXPECT_BYTES_EQ(kExpectedRxData, result->value()->rxdata.data(), sizeof(buf));
runtime_.Quit();
});
runtime_.Run();
// Verify that the driver wrote the TX data to the TX VMO with the original byte order.
EXPECT_OK(tx_dma_vmo.read(buf, 0, sizeof(buf)));
EXPECT_BYTES_EQ(kTxData, buf, sizeof(buf));
EXPECT_EQ(tx_paddr, kDmaPaddrs[0]);
EXPECT_EQ(rx_paddr, kDmaPaddrs[1]);
EXPECT_FALSE(ControllerReset());
}
class AmlSpiShutdownTest : public AmlSpiTest {
public:
// Override teardown so that the test case itself can call PrepareStop/Stop.
void TearDown() override {}
};
TEST_F(AmlSpiShutdownTest, Shutdown) {
// Must outlive AmlSpi device.
bool dmareg_cleared = false;
bool conreg_cleared = false;
zx::result start_result = runtime_.RunToCompletion(dut_.Start(std::move(start_args_)));
ASSERT_TRUE(start_result.is_ok());
auto spiimpl_client = GetFidlClient();
ASSERT_TRUE(spiimpl_client.is_ok());
fdf::WireClient<fuchsia_hardware_spiimpl::SpiImpl> spiimpl(*std::move(spiimpl_client),
fdf::Dispatcher::GetCurrent()->get());
ExpectGpioWrite(ZX_OK, 0);
ExpectGpioWrite(ZX_OK, 1);
uint8_t buf[16] = {};
fdf::Arena arena('TEST');
spiimpl.buffer(arena)
->ExchangeVector(0, fidl::VectorView<uint8_t>::FromExternal(buf, sizeof(buf)))
.Then([&](auto& result) {
ASSERT_TRUE(result.ok());
ASSERT_TRUE(result->is_ok());
runtime_.Quit();
});
runtime_.Run();
mmio()[AML_SPI_DMAREG].SetWriteCallback(
[&dmareg_cleared](uint64_t value) { dmareg_cleared = value == 0; });
mmio()[AML_SPI_CONREG].SetWriteCallback(
[&conreg_cleared](uint64_t value) { conreg_cleared = value == 0; });
zx::result prepare_stop_result = runtime_.RunToCompletion(dut_.PrepareStop());
EXPECT_OK(prepare_stop_result.status_value());
EXPECT_TRUE(dut_.Stop().is_ok());
EXPECT_TRUE(dmareg_cleared);
EXPECT_TRUE(conreg_cleared);
// All SPI devices have been released at this point, so no further calls can be made.
EXPECT_FALSE(ControllerReset());
ASSERT_NO_FATAL_FAILURE(VerifyGpioAndClear());
}
class AmlSpiNoResetFragmentTest : public AmlSpiTest {
public:
bool SetupResetRegister() override { return false; }
};
TEST_F(AmlSpiNoResetFragmentTest, ExchangeWithNoResetFragment) {
zx::result start_result = runtime_.RunToCompletion(dut_.Start(std::move(start_args_)));
ASSERT_TRUE(start_result.is_ok());
auto spiimpl_client = GetFidlClient();
ASSERT_TRUE(spiimpl_client.is_ok());
fdf::WireClient<fuchsia_hardware_spiimpl::SpiImpl> spiimpl(*std::move(spiimpl_client),
fdf::Dispatcher::GetCurrent()->get());
ExpectGpioWrite(ZX_OK, 0);
ExpectGpioWrite(ZX_OK, 1);
fdf::Arena arena('TEST');
uint8_t buf[17] = {};
spiimpl.buffer(arena)
->ExchangeVector(0, fidl::VectorView<uint8_t>::FromExternal(buf, 17))
.Then([&](auto& result) {
ASSERT_TRUE(result.ok());
ASSERT_TRUE(result->is_ok());
EXPECT_EQ(result->value()->rxdata.count(), 17);
EXPECT_FALSE(ControllerReset());
});
ExpectGpioWrite(ZX_OK, 0);
ExpectGpioWrite(ZX_OK, 1);
// Controller should not be reset because no reset fragment was provided.
spiimpl.buffer(arena)
->ExchangeVector(0, fidl::VectorView<uint8_t>::FromExternal(buf, 16))
.Then([&](auto& result) {
ASSERT_TRUE(result.ok());
ASSERT_TRUE(result->is_ok());
EXPECT_EQ(result->value()->rxdata.count(), 16);
EXPECT_FALSE(ControllerReset());
});
ExpectGpioWrite(ZX_OK, 0);
ExpectGpioWrite(ZX_OK, 1);
spiimpl.buffer(arena)
->ExchangeVector(0, fidl::VectorView<uint8_t>::FromExternal(buf, 3))
.Then([&](auto& result) {
ASSERT_TRUE(result.ok());
ASSERT_TRUE(result->is_ok());
EXPECT_EQ(result->value()->rxdata.count(), 3);
EXPECT_FALSE(ControllerReset());
});
ExpectGpioWrite(ZX_OK, 0);
ExpectGpioWrite(ZX_OK, 1);
spiimpl.buffer(arena)
->ExchangeVector(0, fidl::VectorView<uint8_t>::FromExternal(buf, 6))
.Then([&](auto& result) {
ASSERT_TRUE(result.ok());
ASSERT_TRUE(result->is_ok());
EXPECT_EQ(result->value()->rxdata.count(), 6);
EXPECT_FALSE(ControllerReset());
});
ExpectGpioWrite(ZX_OK, 0);
ExpectGpioWrite(ZX_OK, 1);
spiimpl.buffer(arena)
->ExchangeVector(0, fidl::VectorView<uint8_t>::FromExternal(buf, 8))
.Then([&](auto& result) {
ASSERT_TRUE(result.ok());
ASSERT_TRUE(result->is_ok());
EXPECT_EQ(result->value()->rxdata.count(), 8);
EXPECT_FALSE(ControllerReset());
runtime_.Quit();
});
runtime_.Run();
ASSERT_NO_FATAL_FAILURE(VerifyGpioAndClear());
}
class AmlSpiNoIrqTest : public AmlSpiTest {
public:
virtual void SetUpInterrupt() override {}
};
TEST_F(AmlSpiNoIrqTest, InterruptRequired) {
// Bind should fail if no interrupt was provided.
zx::result start_result = runtime_.RunToCompletion(dut_.Start(std::move(start_args_)));
EXPECT_TRUE(start_result.is_error());
}
TEST_F(AmlSpiTest, DefaultRoleMetadata) {
constexpr char kExpectedRoleName[] = "fuchsia.devices.spi.drivers.aml-spi.transaction";
zx::result start_result = runtime_.RunToCompletion(dut_.Start(std::move(start_args_)));
ASSERT_TRUE(start_result.is_ok());
zx::result<fuchsia_scheduler::RoleName> metadata = GetSchedulerRoleName();
ASSERT_TRUE(metadata.is_ok());
EXPECT_STREQ(metadata->role(), kExpectedRoleName);
}
class AmlSpiForwardRoleMetadataTest : public AmlSpiTest {
public:
void SetMetadata(compat::DeviceServer& compat) override {
constexpr amlogic_spi::amlspi_config_t kSpiConfig = {
.bus_id = 0,
.cs_count = 3,
.cs = {5, 3, amlogic_spi::amlspi_config_t::kCsClientManaged},
.clock_divider_register_value = 0,
.use_enhanced_clock_mode = false,
};
EXPECT_OK(compat.AddMetadata(DEVICE_METADATA_AMLSPI_CONFIG, &kSpiConfig, sizeof(kSpiConfig)));
const fuchsia_scheduler::wire::RoleName role{kExpectedRoleName};
fit::result result = fidl::Persist(role);
ASSERT_TRUE(result.is_ok());
EXPECT_OK(
compat.AddMetadata(DEVICE_METADATA_SCHEDULER_ROLE_NAME, result->data(), result->size()));
}
protected:
static constexpr char kExpectedRoleName[] = "no.such.scheduler.role";
};
TEST_F(AmlSpiForwardRoleMetadataTest, Test) {
zx::result start_result = runtime_.RunToCompletion(dut_.Start(std::move(start_args_)));
ASSERT_TRUE(start_result.is_ok());
zx::result<fuchsia_scheduler::RoleName> metadata = GetSchedulerRoleName();
ASSERT_TRUE(metadata.is_ok());
EXPECT_STREQ(metadata->role(), kExpectedRoleName);
}
} // namespace spi