blob: 5bc4f02fcd1f3d23e2887750637fa42819524db6 [file] [log] [blame]
// Copyright 2019 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 "spi.h"
#include <fidl/fuchsia.hardware.spi.businfo/cpp/wire.h>
#include <fidl/fuchsia.hardware.spiimpl/cpp/driver/wire.h>
#include <fidl/fuchsia.scheduler/cpp/fidl.h>
#include <lib/component/incoming/cpp/service.h>
#include <lib/ddk/metadata.h>
#include <lib/driver/outgoing/cpp/outgoing_directory.h>
#include <lib/driver/testing/cpp/fixtures/gtest_fixture.h>
#include <lib/fidl/cpp/wire/client.h>
#include <lib/spi/spi.h>
#include <zircon/errors.h>
#include <array>
#include <map>
#include <gtest/gtest.h>
#include "spi-child.h"
#include "src/lib/testing/predicates/status.h"
namespace spi {
class FakeSpiImplServer : public fdf::WireServer<fuchsia_hardware_spiimpl::SpiImpl> {
public:
static constexpr uint8_t kChipSelectCount = 2;
fidl::ProtocolHandler<fuchsia_hardware_spiimpl::SpiImpl> GetHandler() {
return bindings_.CreateHandler(this, fdf::Dispatcher::GetCurrent()->get(),
std::mem_fn(&FakeSpiImplServer::OnClosed));
}
// fuchsia_hardware_spiimpl::SpiImpl methods
void GetChipSelectCount(fdf::Arena& arena,
GetChipSelectCountCompleter::Sync& completer) override {
completer.buffer(arena).Reply(kChipSelectCount);
}
void TransmitVector(fuchsia_hardware_spiimpl::wire::SpiImplTransmitVectorRequest* request,
fdf::Arena& arena, TransmitVectorCompleter::Sync& completer) override {
size_t actual;
auto status = SpiImplExchange(request->chip_select, request->data.data(), request->data.count(),
nullptr, 0, &actual);
if (status != ZX_OK) {
completer.buffer(arena).ReplyError(status);
return;
}
completer.buffer(arena).ReplySuccess();
}
void ReceiveVector(fuchsia_hardware_spiimpl::wire::SpiImplReceiveVectorRequest* request,
fdf::Arena& arena, ReceiveVectorCompleter::Sync& completer) override {
std::vector<uint8_t> rxdata(request->size);
size_t actual;
auto status =
SpiImplExchange(request->chip_select, nullptr, 0, rxdata.data(), rxdata.size(), &actual);
if (status != ZX_OK) {
completer.buffer(arena).ReplyError(status);
return;
}
rxdata.resize(actual);
completer.buffer(arena).ReplySuccess(fidl::VectorView<uint8_t>::FromExternal(rxdata));
}
void ExchangeVector(fuchsia_hardware_spiimpl::wire::SpiImplExchangeVectorRequest* request,
fdf::Arena& arena, ExchangeVectorCompleter::Sync& completer) override {
std::vector<uint8_t> rxdata(request->txdata.count());
size_t actual;
auto status = SpiImplExchange(request->chip_select, request->txdata.data(),
request->txdata.count(), rxdata.data(), rxdata.size(), &actual);
if (status != ZX_OK) {
completer.buffer(arena).ReplyError(status);
return;
}
rxdata.resize(actual);
completer.buffer(arena).ReplySuccess(fidl::VectorView<uint8_t>::FromExternal(rxdata));
}
void LockBus(fuchsia_hardware_spiimpl::wire::SpiImplLockBusRequest* request, fdf::Arena& arena,
LockBusCompleter::Sync& completer) override {
completer.buffer(arena).ReplySuccess();
}
void UnlockBus(fuchsia_hardware_spiimpl::wire::SpiImplUnlockBusRequest* request,
fdf::Arena& arena, UnlockBusCompleter::Sync& completer) override {
completer.buffer(arena).ReplySuccess();
}
void RegisterVmo(fuchsia_hardware_spiimpl::wire::SpiImplRegisterVmoRequest* request,
fdf::Arena& arena, RegisterVmoCompleter::Sync& completer) override {
auto status = SpiImplRegisterVmo(request->chip_select, request->vmo_id,
std::move(request->vmo.vmo), request->vmo.offset,
request->vmo.size, static_cast<uint32_t>(request->rights));
if (status != ZX_OK) {
completer.buffer(arena).ReplyError(status);
return;
}
completer.buffer(arena).ReplySuccess();
}
void UnregisterVmo(fuchsia_hardware_spiimpl::wire::SpiImplUnregisterVmoRequest* request,
fdf::Arena& arena, UnregisterVmoCompleter::Sync& completer) override {
zx::vmo vmo;
auto status = SpiImplUnregisterVmo(request->chip_select, request->vmo_id, &vmo);
if (status != ZX_OK) {
completer.buffer(arena).ReplyError(status);
return;
}
completer.buffer(arena).ReplySuccess(std::move(vmo));
}
void ReleaseRegisteredVmos(
fuchsia_hardware_spiimpl::wire::SpiImplReleaseRegisteredVmosRequest* request,
fdf::Arena& arena, ReleaseRegisteredVmosCompleter::Sync& completer) override {
SpiImplReleaseRegisteredVmos(request->chip_select);
}
void TransmitVmo(fuchsia_hardware_spiimpl::wire::SpiImplTransmitVmoRequest* request,
fdf::Arena& arena, TransmitVmoCompleter::Sync& completer) override {
auto status = SpiImplTransmitVmo(request->chip_select, request->buffer.vmo_id,
request->buffer.offset, request->buffer.size);
if (status != ZX_OK) {
completer.buffer(arena).ReplyError(status);
return;
}
completer.buffer(arena).ReplySuccess();
}
void ReceiveVmo(fuchsia_hardware_spiimpl::wire::SpiImplReceiveVmoRequest* request,
fdf::Arena& arena, ReceiveVmoCompleter::Sync& completer) override {
auto status = SpiImplReceiveVmo(request->chip_select, request->buffer.vmo_id,
request->buffer.offset, request->buffer.size);
if (status != ZX_OK) {
completer.buffer(arena).ReplyError(status);
return;
}
completer.buffer(arena).ReplySuccess();
}
void ExchangeVmo(fuchsia_hardware_spiimpl::wire::SpiImplExchangeVmoRequest* request,
fdf::Arena& arena, ExchangeVmoCompleter::Sync& completer) override {
ASSERT_EQ(request->tx_buffer.size, request->rx_buffer.size);
auto status = SpiImplExchangeVmo(request->chip_select, request->tx_buffer.vmo_id,
request->tx_buffer.offset, request->rx_buffer.vmo_id,
request->rx_buffer.offset, request->tx_buffer.size);
if (status != ZX_OK) {
completer.buffer(arena).ReplyError(status);
return;
}
completer.buffer(arena).ReplySuccess();
}
uint32_t current_test_cs_ = 0;
bool corrupt_rx_actual_ = false;
bool vmos_released_since_last_call_ = false;
enum class SpiTestMode {
kTransmit,
kReceive,
kExchange,
} test_mode_;
std::map<uint32_t, zx::vmo> cs0_vmos;
std::map<uint32_t, zx::vmo> cs1_vmos;
private:
static constexpr uint8_t kTestData[] = {1, 2, 3, 4, 5, 6, 7};
void OnClosed(fidl::UnbindInfo info) {
// Called when a connection to this server is closed.
// This is provided to the binding group during |CreateHandler|.
}
zx_status_t SpiImplExchange(uint32_t cs, const uint8_t* txdata, size_t txdata_size,
uint8_t* out_rxdata, size_t rxdata_size, size_t* out_rxdata_actual) {
EXPECT_EQ(cs, current_test_cs_);
switch (test_mode_) {
case SpiTestMode::kTransmit:
EXPECT_NE(txdata, nullptr);
EXPECT_NE(txdata_size, 0ul);
EXPECT_EQ(out_rxdata, nullptr);
EXPECT_EQ(rxdata_size, 0ul);
*out_rxdata_actual = 0;
break;
case SpiTestMode::kReceive:
EXPECT_EQ(txdata, nullptr);
EXPECT_EQ(txdata_size, 0ul);
EXPECT_NE(out_rxdata, nullptr);
EXPECT_NE(rxdata_size, 0ul);
memset(out_rxdata, 0, rxdata_size);
memcpy(out_rxdata, kTestData, std::min(rxdata_size, sizeof(kTestData)));
*out_rxdata_actual = rxdata_size + (corrupt_rx_actual_ ? 1 : 0);
break;
case SpiTestMode::kExchange:
EXPECT_NE(txdata, nullptr);
EXPECT_NE(txdata_size, 0ul);
EXPECT_NE(out_rxdata, nullptr);
EXPECT_NE(rxdata_size, 0ul);
EXPECT_EQ(txdata_size, rxdata_size);
memset(out_rxdata, 0, rxdata_size);
memcpy(out_rxdata, txdata, std::min(rxdata_size, txdata_size));
*out_rxdata_actual = std::min(rxdata_size, txdata_size) + (corrupt_rx_actual_ ? 1 : 0);
break;
}
return ZX_OK;
}
zx_status_t SpiImplRegisterVmo(uint32_t chip_select, uint32_t vmo_id, zx::vmo vmo,
uint64_t offset, uint64_t size, uint32_t rights) {
if (chip_select > 1) {
return ZX_ERR_OUT_OF_RANGE;
}
std::map<uint32_t, zx::vmo>& map = chip_select == 0 ? cs0_vmos : cs1_vmos;
if (map.find(vmo_id) != map.end()) {
return ZX_ERR_ALREADY_EXISTS;
}
map[vmo_id] = std::move(vmo);
return ZX_OK;
}
zx_status_t SpiImplUnregisterVmo(uint32_t chip_select, uint32_t vmo_id, zx::vmo* out_vmo) {
if (chip_select > 1) {
return ZX_ERR_OUT_OF_RANGE;
}
std::map<uint32_t, zx::vmo>& map = chip_select == 0 ? cs0_vmos : cs1_vmos;
auto it = map.find(vmo_id);
if (it == map.end()) {
return ZX_ERR_NOT_FOUND;
}
if (out_vmo) {
out_vmo->reset(std::get<1>(*it).release());
}
map.erase(it);
return ZX_OK;
}
void SpiImplReleaseRegisteredVmos(uint32_t chip_select) { vmos_released_since_last_call_ = true; }
zx_status_t SpiImplTransmitVmo(uint32_t chip_select, uint32_t vmo_id, uint64_t offset,
uint64_t size) {
if (chip_select > 1) {
return ZX_ERR_OUT_OF_RANGE;
}
std::map<uint32_t, zx::vmo>& map = chip_select == 0 ? cs0_vmos : cs1_vmos;
auto it = map.find(vmo_id);
if (it == map.end()) {
return ZX_ERR_NOT_FOUND;
}
uint8_t buf[sizeof(kTestData)];
zx_status_t status = std::get<1>(*it).read(buf, offset, std::max(size, sizeof(buf)));
if (status != ZX_OK) {
return status;
}
return memcmp(buf, kTestData, std::max(size, sizeof(buf))) == 0 ? ZX_OK : ZX_ERR_IO;
}
zx_status_t SpiImplReceiveVmo(uint32_t chip_select, uint32_t vmo_id, uint64_t offset,
uint64_t size) {
if (chip_select > 1) {
return ZX_ERR_OUT_OF_RANGE;
}
std::map<uint32_t, zx::vmo>& map = chip_select == 0 ? cs0_vmos : cs1_vmos;
auto it = map.find(vmo_id);
if (it == map.end()) {
return ZX_ERR_NOT_FOUND;
}
return std::get<1>(*it).write(kTestData, offset, std::max(size, sizeof(kTestData)));
}
zx_status_t SpiImplExchangeVmo(uint32_t chip_select, uint32_t tx_vmo_id, uint64_t tx_offset,
uint32_t rx_vmo_id, uint64_t rx_offset, uint64_t size) {
if (chip_select > 1) {
return ZX_ERR_OUT_OF_RANGE;
}
std::map<uint32_t, zx::vmo>& map = chip_select == 0 ? cs0_vmos : cs1_vmos;
auto tx_it = map.find(tx_vmo_id);
auto rx_it = map.find(rx_vmo_id);
if (tx_it == map.end() || rx_it == map.end()) {
return ZX_ERR_NOT_FOUND;
}
uint8_t buf[8];
zx_status_t status = std::get<1>(*tx_it).read(buf, tx_offset, std::max(size, sizeof(buf)));
if (status != ZX_OK) {
return status;
}
return std::get<1>(*rx_it).write(buf, rx_offset, std::max(size, sizeof(buf)));
}
fdf::ServerBindingGroup<fuchsia_hardware_spiimpl::SpiImpl> bindings_;
};
class TestEnvironment : public fdf_testing::Environment {
public:
zx::result<> Serve(fdf::OutgoingDirectory& to_driver_vfs) override {
device_server_.Init(component::kDefaultInstance, "root");
zx_status_t status =
device_server_.Serve(fdf::Dispatcher::GetCurrent()->async_dispatcher(), &to_driver_vfs);
if (status != ZX_OK) {
return zx::error(status);
}
return to_driver_vfs.AddService<fuchsia_hardware_spiimpl::Service>(
fuchsia_hardware_spiimpl::Service::InstanceHandler({
.device = fake_spi_impl_.GetHandler(),
}));
}
void AddSchedulerRoleMetadata(const char* role_name) {
fuchsia_scheduler::RoleName role(role_name);
const auto result = fidl::Persist(role);
ASSERT_TRUE(result.is_ok());
zx_status_t status = device_server_.AddMetadata(DEVICE_METADATA_SCHEDULER_ROLE_NAME,
result->data(), result->size());
EXPECT_OK(status);
}
zx::result<> SetSpiChannelCount(uint32_t count) {
fidl::Arena arena;
fidl::VectorView<fuchsia_hardware_spi_businfo::wire::SpiChannel> channels(arena, count);
for (uint32_t i = 0; i < channels.count(); i++) {
channels[i] = fuchsia_hardware_spi_businfo::wire::SpiChannel::Builder(arena)
.cs(i)
.vid(0)
.pid(0)
.did(0)
.Build();
}
auto metadata = fuchsia_hardware_spi_businfo::wire::SpiBusMetadata::Builder(arena)
.channels(channels)
.bus_id(0)
.Build();
fit::result encoded = fidl::Persist(metadata);
if (encoded.is_error()) {
return zx::error(encoded.error_value().status());
}
zx_status_t status =
device_server_.AddMetadata(DEVICE_METADATA_SPI_CHANNELS, encoded->data(), encoded->size());
if (status != ZX_OK) {
return zx::error(status);
}
return zx::ok();
}
FakeSpiImplServer& fake_spi_impl() { return fake_spi_impl_; }
private:
FakeSpiImplServer fake_spi_impl_;
compat::DeviceServer device_server_;
};
struct FixtureConfig {
public:
static constexpr bool kDriverOnForeground = false;
static constexpr bool kAutoStartDriver = false;
static constexpr bool kAutoStopDriver = true;
using DriverType = SpiDevice;
using EnvironmentType = TestEnvironment;
};
class SpiDeviceTest : public fdf_testing::DriverTestFixture<FixtureConfig> {
protected:
void CreateSpiDevice(uint32_t channel_count) {
RunInEnvironmentTypeContext([channel_count](TestEnvironment& environment) {
ASSERT_TRUE(environment.SetSpiChannelCount(channel_count).is_ok());
});
EXPECT_TRUE(StartDriver().is_ok());
bool all_children_added = RunInNodeContext<bool>([channel_count](fdf_testing::TestNode& node) {
if (node.children().begin() == node.children().end()) {
return false;
}
auto& spi_bus = node.children().begin()->second;
return spi_bus.children().size() == channel_count;
});
EXPECT_TRUE(all_children_added);
}
// Helper Methods that access fake_spi_impl
void set_current_test_cs(uint32_t i) {
RunInEnvironmentTypeContext(
[i](TestEnvironment& environment) { environment.fake_spi_impl().current_test_cs_ = i; });
}
void set_test_mode(FakeSpiImplServer::SpiTestMode test_mode) {
RunInEnvironmentTypeContext([test_mode](TestEnvironment& environment) {
environment.fake_spi_impl().test_mode_ = test_mode;
});
}
void set_corrupt_rx_actual(bool actual) {
RunInEnvironmentTypeContext([actual](TestEnvironment& environment) {
environment.fake_spi_impl().corrupt_rx_actual_ = actual;
});
}
bool vmos_released_since_last_call() {
return RunInEnvironmentTypeContext<bool>([](TestEnvironment& environment) {
const bool value = environment.fake_spi_impl().vmos_released_since_last_call_;
environment.fake_spi_impl().vmos_released_since_last_call_ = false;
return value;
});
}
};
TEST_F(SpiDeviceTest, SpiTest) {
constexpr std::array<const char*, 2> kExpectedNodeNames{"spi-0-0", "spi-0-1"};
CreateSpiDevice(kExpectedNodeNames.size());
// test it
uint8_t txbuf[] = {0, 1, 2, 3, 4, 5, 6};
uint8_t rxbuf[sizeof txbuf];
uint32_t i = 0;
for (auto name = kExpectedNodeNames.cbegin(); name != kExpectedNodeNames.cend(); name++, i++) {
zx::result client = Connect<fuchsia_hardware_spi::Service::Device>(*name);
ASSERT_TRUE(client.is_ok());
set_current_test_cs(i);
set_test_mode(FakeSpiImplServer::SpiTestMode::kTransmit);
EXPECT_OK(spilib_transmit(*client, txbuf, sizeof txbuf));
set_test_mode(FakeSpiImplServer::SpiTestMode::kReceive);
EXPECT_OK(spilib_receive(*client, rxbuf, sizeof rxbuf));
set_test_mode(FakeSpiImplServer::SpiTestMode::kExchange);
EXPECT_OK(spilib_exchange(*client, txbuf, rxbuf, sizeof txbuf));
}
}
TEST_F(SpiDeviceTest, SpiFidlVmoTest) {
using fuchsia_hardware_sharedmemory::wire::SharedVmoRight;
constexpr std::array<const char*, 2> kExpectedNodeNames{"spi-0-0", "spi-0-1"};
constexpr std::array<uint8_t, 7> kTestData{1, 2, 3, 4, 5, 6, 7};
CreateSpiDevice(kExpectedNodeNames.size());
fidl::WireSyncClient<fuchsia_hardware_spi::Device> cs0_client, cs1_client;
{
zx::result client = Connect<fuchsia_hardware_spi::Service::Device>(kExpectedNodeNames[0]);
ASSERT_TRUE(client.is_ok());
cs0_client.Bind(std::move(client.value()));
}
{
zx::result client = Connect<fuchsia_hardware_spi::Service::Device>(kExpectedNodeNames[1]);
ASSERT_TRUE(client.is_ok());
cs1_client.Bind(std::move(client.value()));
}
zx::vmo cs0_vmo, cs1_vmo;
ASSERT_OK(zx::vmo::create(4096, 0, &cs0_vmo));
ASSERT_OK(zx::vmo::create(4096, 0, &cs1_vmo));
{
fuchsia_mem::wire::Range vmo = {.offset = 0, .size = 4096};
ASSERT_OK(cs0_vmo.duplicate(ZX_RIGHT_SAME_RIGHTS, &vmo.vmo));
auto result =
cs0_client->RegisterVmo(1, std::move(vmo), SharedVmoRight::kRead | SharedVmoRight::kWrite);
ASSERT_OK(result.status());
EXPECT_TRUE(result.value().is_ok());
}
{
fuchsia_mem::wire::Range vmo = {.offset = 0, .size = 4096};
ASSERT_OK(cs1_vmo.duplicate(ZX_RIGHT_SAME_RIGHTS, &vmo.vmo));
auto result =
cs1_client->RegisterVmo(2, std::move(vmo), SharedVmoRight::kRead | SharedVmoRight::kWrite);
ASSERT_OK(result.status());
EXPECT_TRUE(result.value().is_ok());
}
ASSERT_OK(cs0_vmo.write(kTestData.data(), 1024, kTestData.size()));
{
auto result = cs0_client->Exchange(
{
.vmo_id = 1,
.offset = 1024,
.size = kTestData.size(),
},
{
.vmo_id = 1,
.offset = 2048,
.size = kTestData.size(),
});
ASSERT_OK(result.status());
EXPECT_TRUE(result.value().is_ok());
std::array<uint8_t, kTestData.size()> buf;
ASSERT_OK(cs0_vmo.read(buf.data(), 2048, buf.size()));
EXPECT_EQ(buf, kTestData);
}
ASSERT_OK(cs1_vmo.write(kTestData.data(), 1024, kTestData.size()));
{
auto result = cs1_client->Transmit({
.vmo_id = 2,
.offset = 1024,
.size = kTestData.size(),
});
ASSERT_OK(result.status());
EXPECT_TRUE(result.value().is_ok());
}
{
auto result = cs0_client->Receive({
.vmo_id = 1,
.offset = 1024,
.size = kTestData.size(),
});
ASSERT_OK(result.status());
EXPECT_TRUE(result.value().is_ok());
std::array<uint8_t, kTestData.size()> buf;
ASSERT_OK(cs0_vmo.read(buf.data(), 1024, buf.size()));
EXPECT_EQ(buf, kTestData);
}
{
auto result = cs0_client->UnregisterVmo(1);
ASSERT_OK(result.status());
EXPECT_TRUE(result.value().is_ok());
}
{
auto result = cs1_client->UnregisterVmo(2);
ASSERT_OK(result.status());
EXPECT_TRUE(result.value().is_ok());
}
}
TEST_F(SpiDeviceTest, SpiFidlVectorTest) {
constexpr std::array<const char*, 2> kExpectedNodeNames{"spi-0-0", "spi-0-1"};
fidl::WireSyncClient<fuchsia_hardware_spi::Device> cs0_client, cs1_client;
CreateSpiDevice(kExpectedNodeNames.size());
{
zx::result client = Connect<fuchsia_hardware_spi::Service::Device>(kExpectedNodeNames[0]);
ASSERT_TRUE(client.is_ok());
cs0_client.Bind(std::move(client.value()));
}
{
zx::result client = Connect<fuchsia_hardware_spi::Service::Device>(kExpectedNodeNames[1]);
ASSERT_TRUE(client.is_ok());
cs1_client.Bind(std::move(client.value()));
}
std::vector<uint8_t> test_data{1, 2, 3, 4, 5, 6, 7};
set_current_test_cs(0);
set_test_mode(FakeSpiImplServer::SpiTestMode::kTransmit);
{
auto tx_buffer = fidl::VectorView<uint8_t>::FromExternal(test_data);
auto result = cs0_client->TransmitVector(tx_buffer);
ASSERT_OK(result.status());
EXPECT_OK(result.value().status);
}
set_current_test_cs(1);
set_test_mode(FakeSpiImplServer::SpiTestMode::kReceive);
{
auto result = cs1_client->ReceiveVector(static_cast<uint32_t>(test_data.size()));
ASSERT_OK(result.status());
EXPECT_OK(result.value().status);
const std::vector<uint8_t> actual{result.value().data.cbegin(), result.value().data.cend()};
EXPECT_EQ(actual, test_data);
}
set_current_test_cs(0);
set_test_mode(FakeSpiImplServer::SpiTestMode::kExchange);
{
auto tx_buffer = fidl::VectorView<uint8_t>::FromExternal(test_data);
auto result = cs0_client->ExchangeVector(tx_buffer);
ASSERT_OK(result.status());
EXPECT_OK(result.value().status);
const std::vector<uint8_t> actual{result.value().rxdata.cbegin(), result.value().rxdata.cend()};
EXPECT_EQ(actual, test_data);
}
}
TEST_F(SpiDeviceTest, SpiFidlVectorErrorTest) {
constexpr std::array<const char*, 2> kExpectedNodeNames{"spi-0-0", "spi-0-1"};
fidl::WireSyncClient<fuchsia_hardware_spi::Device> cs0_client, cs1_client;
CreateSpiDevice(kExpectedNodeNames.size());
{
zx::result client = Connect<fuchsia_hardware_spi::Service::Device>(kExpectedNodeNames[0]);
ASSERT_TRUE(client.is_ok());
cs0_client.Bind(std::move(client.value()));
}
{
zx::result client = Connect<fuchsia_hardware_spi::Service::Device>(kExpectedNodeNames[1]);
ASSERT_TRUE(client.is_ok());
cs1_client.Bind(std::move(client.value()));
}
set_corrupt_rx_actual(true);
uint8_t test_data[] = {1, 2, 3, 4, 5, 6, 7};
set_current_test_cs(0);
set_test_mode(FakeSpiImplServer::SpiTestMode::kTransmit);
{
auto tx_buffer = fidl::VectorView<uint8_t>::FromExternal(test_data);
auto result = cs0_client->TransmitVector(tx_buffer);
ASSERT_OK(result.status());
EXPECT_OK(result.value().status);
}
set_current_test_cs(1);
set_test_mode(FakeSpiImplServer::SpiTestMode::kReceive);
{
auto result = cs1_client->ReceiveVector(sizeof(test_data));
ASSERT_OK(result.status());
EXPECT_EQ(result.value().status, ZX_ERR_INTERNAL);
EXPECT_EQ(result.value().data.count(), 0ul);
}
set_current_test_cs(0);
set_test_mode(FakeSpiImplServer::SpiTestMode::kExchange);
{
auto tx_buffer = fidl::VectorView<uint8_t>::FromExternal(test_data);
auto result = cs0_client->ExchangeVector(tx_buffer);
ASSERT_OK(result.status());
EXPECT_EQ(result.value().status, ZX_ERR_INTERNAL);
EXPECT_EQ(result.value().rxdata.count(), 0ul);
}
}
TEST_F(SpiDeviceTest, AssertCsWithSiblingTest) {
constexpr std::array<const char*, 2> kExpectedNodeNames{"spi-0-0", "spi-0-1"};
fidl::WireSyncClient<fuchsia_hardware_spi::Device> cs0_client, cs1_client;
CreateSpiDevice(kExpectedNodeNames.size());
{
zx::result client = Connect<fuchsia_hardware_spi::Service::Device>(kExpectedNodeNames[0]);
ASSERT_TRUE(client.is_ok());
cs0_client.Bind(std::move(client.value()));
}
{
zx::result client = Connect<fuchsia_hardware_spi::Service::Device>(kExpectedNodeNames[1]);
ASSERT_TRUE(client.is_ok());
cs1_client.Bind(std::move(client.value()));
}
{
auto result = cs0_client->CanAssertCs();
ASSERT_OK(result.status());
ASSERT_FALSE(result.value().can);
}
{
auto result = cs1_client->CanAssertCs();
ASSERT_OK(result.status());
ASSERT_FALSE(result.value().can);
}
{
auto result = cs0_client->AssertCs();
ASSERT_OK(result.status());
ASSERT_EQ(result.value().status, ZX_ERR_NOT_SUPPORTED);
}
{
auto result = cs1_client->AssertCs();
ASSERT_OK(result.status());
ASSERT_EQ(result.value().status, ZX_ERR_NOT_SUPPORTED);
}
{
auto result = cs0_client->DeassertCs();
ASSERT_OK(result.status());
ASSERT_EQ(result.value().status, ZX_ERR_NOT_SUPPORTED);
}
{
auto result = cs1_client->DeassertCs();
ASSERT_OK(result.status());
ASSERT_EQ(result.value().status, ZX_ERR_NOT_SUPPORTED);
}
}
TEST_F(SpiDeviceTest, AssertCsNoSiblingTest) {
constexpr std::array<const char*, 1> kExpectedNodeNames{"spi-0-0"};
fidl::WireSyncClient<fuchsia_hardware_spi::Device> cs0_client;
CreateSpiDevice(kExpectedNodeNames.size());
{
zx::result client = Connect<fuchsia_hardware_spi::Service::Device>(kExpectedNodeNames[0]);
ASSERT_TRUE(client.is_ok());
cs0_client.Bind(std::move(client.value()));
}
{
auto result = cs0_client->CanAssertCs();
ASSERT_OK(result.status());
ASSERT_TRUE(result.value().can);
}
{
auto result = cs0_client->AssertCs();
ASSERT_OK(result.status());
ASSERT_OK(result.value().status);
}
{
auto result = cs0_client->DeassertCs();
ASSERT_OK(result.status());
ASSERT_OK(result.value().status);
}
}
TEST_F(SpiDeviceTest, OneClient) {
constexpr std::array<const char*, 1> kExpectedNodeNames{"spi-0-0"};
const std::vector<std::string> kDevfsPath({"spi", kExpectedNodeNames[0]});
fidl::WireSyncClient<fuchsia_hardware_spi::Device> cs0_client;
CreateSpiDevice(kExpectedNodeNames.size());
// Establish a FIDL connection and verify that it works.
{
zx::result client = Connect<fuchsia_hardware_spi::Service::Device>(kExpectedNodeNames[0]);
ASSERT_TRUE(client.is_ok());
cs0_client.Bind(std::move(client.value()));
}
{
auto result = cs0_client->CanAssertCs();
ASSERT_OK(result.status());
ASSERT_TRUE(result.value().can);
}
// Trying to make a new connection should fail.
{
zx::result client = Connect<fuchsia_hardware_spi::Service::Device>(kExpectedNodeNames[0]);
ASSERT_TRUE(client.is_ok());
fidl::WireSyncClient cs0_client_1(std::move(client.value()));
EXPECT_EQ(cs0_client_1->UnregisterVmo(1).status(), ZX_ERR_PEER_CLOSED);
}
EXPECT_FALSE(vmos_released_since_last_call());
// Close the first client so that another one can connect.
cs0_client = {};
// We don't know when the driver will be ready for a new client, just loop
// until the connection is established.
for (;;) {
zx::result client = Connect<fuchsia_hardware_spi::Service::Device>(kExpectedNodeNames[0]);
ASSERT_TRUE(client.is_ok());
cs0_client.Bind(std::move(client.value()));
// By the time this connection succeeds, the SPI device has already called to its parent to
// release registered VMOs. UnregisterVmo will then make a synchronous call to the parent, so
// waiting for the UnregisterVmo response is sufficient to guarantee that the first call to the
// parent (the fake spiimpl device) has been processed, and that vmos_released_since_last_call
// will return the correct value.
auto result = cs0_client->UnregisterVmo(1);
if (result.ok()) {
break;
}
EXPECT_EQ(result.status(), ZX_ERR_PEER_CLOSED);
cs0_client = {};
}
EXPECT_TRUE(vmos_released_since_last_call());
// OpenSession should fail when another client is connected.
{
zx::result controller = ConnectThroughDevfs<fuchsia_hardware_spi::Controller>(kDevfsPath);
ASSERT_TRUE(controller.is_ok());
{
auto [device, server] = fidl::Endpoints<fuchsia_hardware_spi::Device>::Create();
ASSERT_OK(fidl::WireCall(*controller)->OpenSession(std::move(server)).status());
ASSERT_EQ(fidl::WireCall(device)->CanAssertCs().status(), ZX_ERR_PEER_CLOSED);
}
}
// Close the first client and make sure OpenSession now works.
cs0_client = {};
fidl::ClientEnd<fuchsia_hardware_spi::Device> device;
while (true) {
zx::result controller = ConnectThroughDevfs<fuchsia_hardware_spi::Controller>(kDevfsPath);
ASSERT_TRUE(controller.is_ok());
{
zx::result server = fidl::CreateEndpoints<fuchsia_hardware_spi::Device>(&device);
ASSERT_OK(server.status_value());
ASSERT_OK(fidl::WireCall(*controller)->OpenSession(std::move(server.value())).status());
auto result = fidl::WireCall(device)->UnregisterVmo(1);
if (result.ok()) {
break;
}
ASSERT_EQ(result.status(), ZX_ERR_PEER_CLOSED);
}
}
EXPECT_TRUE(vmos_released_since_last_call());
// FIDL clients shouldn't be able to connect, and calling OpenSession a second time should fail.
{
zx::result client = Connect<fuchsia_hardware_spi::Service::Device>(kExpectedNodeNames[0]);
ASSERT_TRUE(client.is_ok());
fidl::WireSyncClient cs0_client_1(std::move(client.value()));
EXPECT_EQ(cs0_client_1->CanAssertCs().status(), ZX_ERR_PEER_CLOSED);
}
{
zx::result controller = ConnectThroughDevfs<fuchsia_hardware_spi::Controller>(kDevfsPath);
ASSERT_TRUE(controller.is_ok());
{
auto [device, server] = fidl::Endpoints<fuchsia_hardware_spi::Device>::Create();
ASSERT_OK(fidl::WireCall(*controller)->OpenSession(std::move(server)).status());
ASSERT_EQ(fidl::WireCall(device)->CanAssertCs().status(), ZX_ERR_PEER_CLOSED);
}
}
// Close the open session and make sure that a new client can now connect.
device = {};
for (;;) {
zx::result client = Connect<fuchsia_hardware_spi::Service::Device>(kExpectedNodeNames[0]);
ASSERT_TRUE(client.is_ok());
cs0_client.Bind(std::move(client.value()));
auto result = cs0_client->UnregisterVmo(1);
if (result.ok()) {
break;
}
EXPECT_EQ(result.status(), ZX_ERR_PEER_CLOSED);
cs0_client = {};
}
EXPECT_TRUE(vmos_released_since_last_call());
}
TEST_F(SpiDeviceTest, SchedulerRoleName) {
constexpr std::array<const char*, 1> kExpectedNodeNames{"spi-0-0"};
// Add scheduler role metadata that will cause the core driver to create a new driver dispatcher.
// Verify that FIDL calls can still be made.
RunInEnvironmentTypeContext([](TestEnvironment& environment) {
environment.AddSchedulerRoleMetadata("no.such.scheduler.role");
});
fidl::WireSyncClient<fuchsia_hardware_spi::Device> cs0_client;
CreateSpiDevice(kExpectedNodeNames.size());
{
zx::result client = Connect<fuchsia_hardware_spi::Service::Device>(kExpectedNodeNames[0]);
ASSERT_TRUE(client.is_ok());
ASSERT_OK(client.status_value());
cs0_client.Bind(std::move(client.value()));
}
{
auto result = cs0_client->AssertCs();
ASSERT_OK(result.status());
EXPECT_OK(result.value().status);
}
}
} // namespace spi