blob: af207ae7e8b629410751998a5abfa515dd64d457 [file] [log] [blame] [edit]
// Copyright 2023 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-i2c.h"
#include <fidl/fuchsia.hardware.i2c.businfo/cpp/wire.h>
#include <fuchsia/hardware/i2cimpl/cpp/banjo.h>
#include <lib/async-loop/default.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/zx/clock.h>
#include <optional>
#include <span>
#include <vector>
#include <fake-mmio-reg/fake-mmio-reg.h>
#include <soc/aml-common/aml-i2c.h>
#include <zxtest/zxtest.h>
#include "aml-i2c-regs.h"
#include "src/devices/bus/testing/fake-pdev/fake-pdev.h"
#include "src/devices/testing/mock-ddk/mock-device.h"
namespace aml_i2c {
class FakeAmlI2cController {
public:
struct Transfer {
std::vector<uint8_t> write_data;
std::vector<uint8_t> token_list;
uint32_t target_addr;
bool is_read;
void ExpectTokenListEq(const std::vector<uint8_t>& expected) const {
ASSERT_EQ(token_list.size(), expected.size());
EXPECT_BYTES_EQ(token_list.data(), expected.data(), expected.size());
}
};
explicit FakeAmlI2cController(zx::unowned_interrupt irq)
: mmio_(sizeof(reg_values_[0]), 8), irq_(std::move(irq)) {
for (size_t i = 0; i < std::size(reg_values_); i++) {
mmio_[i * sizeof(reg_values_[0])].SetReadCallback(ReadRegCallback(i));
mmio_[i * sizeof(reg_values_[0])].SetWriteCallback(WriteRegCallback(i));
}
mmio_[kControlReg].SetWriteCallback([&](uint64_t value) { WriteControlReg(value); });
}
FakeAmlI2cController(FakeAmlI2cController&& other) = delete;
FakeAmlI2cController& operator=(FakeAmlI2cController&& other) = delete;
FakeAmlI2cController(const FakeAmlI2cController& other) = delete;
FakeAmlI2cController& operator=(const FakeAmlI2cController& other) = delete;
fdf::MmioBuffer GetMmioBuffer() { return mmio_.GetMmioBuffer(); }
void SetReadData(cpp20::span<const uint8_t> read_data) { read_data_ = read_data; }
std::vector<Transfer> GetTransfers() { return std::move(transfers_); }
cpp20::span<uint32_t> mmio() { return {reg_values_, std::size(reg_values_)}; }
private:
std::function<uint64_t(void)> ReadRegCallback(size_t offset) {
return [this, offset]() { return reg_values_[offset]; };
}
std::function<void(uint64_t)> WriteRegCallback(size_t offset) {
return [this, offset](uint64_t value) {
EXPECT_EQ(value & 0xffff'ffff, value);
reg_values_[offset] = static_cast<uint32_t>(value);
};
}
void WriteControlReg(uint64_t value) {
EXPECT_EQ(value & 0xffff'ffff, value);
if (value & 1) {
// Start flag -- process the token list (saving the target address and/or data if needed),
// then trigger the interrupt.
ProcessTokenList();
irq_->trigger(0, zx::clock::get_monotonic());
}
reg_values_[kControlReg / sizeof(uint32_t)] = static_cast<uint32_t>(value);
}
void ProcessTokenList() {
uint64_t token_list = Get64BitReg(kTokenList0Reg);
uint64_t write_data = Get64BitReg(kWriteData0Reg);
uint64_t read_data = 0;
size_t data_count = 0;
for (uint64_t token = token_list & 0xf;; token_list >>= 4, token = token_list & 0xf) {
// Skip most token validation as test cases can check against the expected token sequence.
switch (static_cast<TokenList::Token>(token)) {
case TokenList::Token::kEnd:
case TokenList::Token::kStop:
break;
case TokenList::Token::kStart: {
const uint32_t target_addr = reg_values_[kTargetAddrReg / sizeof(uint32_t)];
EXPECT_EQ(target_addr & 1, 0);
transfers_.push_back({.target_addr = (target_addr >> 1) & 0x7f});
break;
}
case TokenList::Token::kTargetAddrWr:
ASSERT_FALSE(transfers_.empty());
transfers_.back().is_read = false;
break;
case TokenList::Token::kTargetAddrRd:
ASSERT_FALSE(transfers_.empty());
transfers_.back().is_read = true;
break;
case TokenList::Token::kData:
case TokenList::Token::kDataLast:
ASSERT_FALSE(transfers_.empty());
if (transfers_.back().is_read) {
ASSERT_FALSE(read_data_.empty());
read_data |= static_cast<uint64_t>(read_data_[0]) << (8 * data_count++);
read_data_ = read_data_.subspan(1);
} else {
transfers_.back().write_data.push_back(write_data & 0xff);
write_data >>= 8;
}
break;
default:
ASSERT_TRUE(false, "Invalid token %lu", token_list & 0xf);
break;
}
ASSERT_FALSE(transfers_.empty());
transfers_.back().token_list.push_back(token_list & 0xf);
if (static_cast<TokenList::Token>(token) == TokenList::Token::kEnd) {
break;
}
}
EXPECT_EQ(token_list, 0); // There should be no tokens after the end token.
reg_values_[kReadData0Reg / sizeof(uint32_t)] = read_data & 0xffff'ffff;
reg_values_[kReadData1Reg / sizeof(uint32_t)] = read_data >> 32;
}
uint64_t Get64BitReg(size_t offset) const {
return reg_values_[offset / sizeof(uint32_t)] |
(static_cast<uint64_t>(reg_values_[(offset / sizeof(uint32_t)) + 1]) << 32);
}
ddk_fake::FakeMmioRegRegion mmio_;
uint32_t reg_values_[8]{};
zx::unowned_interrupt irq_;
std::vector<Transfer> transfers_;
cpp20::span<const uint8_t> read_data_;
};
struct IncomingNamespace {
fake_pdev::FakePDevFidl pdev_server;
component::OutgoingDirectory outgoing{async_get_default_dispatcher()};
};
class AmlI2cTest : public zxtest::Test {
protected:
static constexpr size_t kMmioSize = sizeof(uint32_t) * 8;
// Convenience definitions that don't require casting.
enum Token : uint8_t {
kEnd,
kStart,
kTargetAddrWr,
kTargetAddrRd,
kData,
kDataLast,
kStop,
};
cpp20::span<uint32_t> mmio() { return controller_->mmio(); }
void InitResources() {
fake_pdev::FakePDevFidl::Config config;
config.irqs[0] = {};
ASSERT_OK(zx::interrupt::create(zx::resource(), 0, ZX_INTERRUPT_VIRTUAL, &config.irqs[0]));
controller_.emplace(config.irqs[0].borrow());
config.mmios[0] = controller_->GetMmioBuffer();
config.device_info = {
.mmio_count = 1,
.irq_count = 1,
};
zx::result outgoing_endpoints = fidl::CreateEndpoints<fuchsia_io::Directory>();
ASSERT_OK(outgoing_endpoints);
ASSERT_OK(incoming_loop_.StartThread("incoming-ns-thread"));
incoming_.SyncCall([config = std::move(config), server = std::move(outgoing_endpoints->server)](
IncomingNamespace* infra) mutable {
infra->pdev_server.SetConfig(std::move(config));
ASSERT_OK(infra->outgoing.AddService<fuchsia_hardware_platform_device::Service>(
infra->pdev_server.GetInstanceHandler()));
ASSERT_OK(infra->outgoing.Serve(std::move(server)));
});
ASSERT_NO_FATAL_FAILURE();
root_->AddFidlService(fuchsia_hardware_platform_device::Service::Name,
std::move(outgoing_endpoints->client), "pdev");
}
void Init() {
EXPECT_NO_FATAL_FAILURE(InitResources());
EXPECT_OK(AmlI2c::Bind(nullptr, root_.get()));
ASSERT_EQ(root_->child_count(), 1);
root_->GetLatestChild()->GetDeviceContext<AmlI2c>()->timeout_ = zx::duration(ZX_TIME_INFINITE);
}
async::Loop incoming_loop_{&kAsyncLoopConfigNoAttachToCurrentThread};
async_patterns::TestDispatcherBound<IncomingNamespace> incoming_{incoming_loop_.dispatcher(),
std::in_place};
std::shared_ptr<MockDevice> root_ = MockDevice::FakeRootParent();
std::optional<FakeAmlI2cController> controller_;
};
TEST_F(AmlI2cTest, SmallWrite) {
EXPECT_NO_FATAL_FAILURE(Init());
ddk::I2cImplProtocolClient i2c(root_->GetLatestChild());
EXPECT_TRUE(i2c.is_valid());
constexpr uint8_t kWriteData[]{0x45, 0xd9, 0x65, 0xbc, 0x31, 0x26, 0xd7, 0xe5};
uint8_t write_buffer[std::size(kWriteData)];
memcpy(write_buffer, kWriteData, sizeof(write_buffer));
const i2c_impl_op_t op{
.address = 0x13,
.data_buffer = write_buffer,
.data_size = sizeof(write_buffer),
.is_read = false,
.stop = true,
};
EXPECT_OK(i2c.Transact(&op, 1));
const std::vector transfers = controller_->GetTransfers();
ASSERT_EQ(transfers.size(), 1);
EXPECT_EQ(transfers[0].target_addr, 0x13);
ASSERT_EQ(transfers[0].write_data.size(), std::size(kWriteData));
EXPECT_BYTES_EQ(transfers[0].write_data.data(), kWriteData, sizeof(kWriteData));
transfers[0].ExpectTokenListEq({
kStart,
kTargetAddrWr,
kData,
kData,
kData,
kData,
kData,
kData,
kData,
kData,
kStop,
kEnd,
});
}
TEST_F(AmlI2cTest, BigWrite) {
EXPECT_NO_FATAL_FAILURE(Init());
ddk::I2cImplProtocolClient i2c(root_->GetLatestChild());
EXPECT_TRUE(i2c.is_valid());
constexpr uint8_t kWriteData[]{0xb9, 0x17, 0x32, 0xba, 0x8e, 0xf7, 0x19, 0xf2, 0x78, 0xbf,
0xcb, 0xd3, 0xdc, 0xad, 0xbd, 0x78, 0x1b, 0xa8, 0xef, 0x1a};
uint8_t write_buffer[std::size(kWriteData)];
memcpy(write_buffer, kWriteData, sizeof(write_buffer));
const i2c_impl_op_t op{
.address = 0x5f,
.data_buffer = write_buffer,
.data_size = sizeof(write_buffer),
.is_read = false,
.stop = true,
};
EXPECT_OK(i2c.Transact(&op, 1));
const std::vector transfers = controller_->GetTransfers();
ASSERT_EQ(transfers.size(), 1);
EXPECT_EQ(transfers[0].target_addr, 0x5f);
ASSERT_EQ(transfers[0].write_data.size(), std::size(kWriteData));
EXPECT_BYTES_EQ(transfers[0].write_data.data(), kWriteData, sizeof(kWriteData));
transfers[0].ExpectTokenListEq({
// First transfer
kStart,
kTargetAddrWr,
kData,
kData,
kData,
kData,
kData,
kData,
kData,
kData,
kEnd,
// Second transfer
kData,
kData,
kData,
kData,
kData,
kData,
kData,
kData,
kEnd,
// Third transfer
kData,
kData,
kData,
kData,
kStop,
kEnd,
});
}
TEST_F(AmlI2cTest, SmallRead) {
EXPECT_NO_FATAL_FAILURE(Init());
ddk::I2cImplProtocolClient i2c(root_->GetLatestChild());
EXPECT_TRUE(i2c.is_valid());
constexpr uint8_t kExpectedReadData[]{0xf0, 0xdb, 0xdf, 0x6b, 0xb9, 0x3e, 0xa6, 0xfa};
controller_->SetReadData({kExpectedReadData, std::size(kExpectedReadData)});
uint8_t read_buffer[std::size(kExpectedReadData)];
memset(read_buffer, 0xaa, sizeof(read_buffer));
const i2c_impl_op_t op{
.address = 0x41,
.data_buffer = read_buffer,
.data_size = sizeof(read_buffer),
.is_read = true,
.stop = true,
};
EXPECT_OK(i2c.Transact(&op, 1));
const std::vector transfers = controller_->GetTransfers();
ASSERT_EQ(transfers.size(), 1);
EXPECT_EQ(transfers[0].target_addr, 0x41);
EXPECT_BYTES_EQ(read_buffer, kExpectedReadData, sizeof(read_buffer));
transfers[0].ExpectTokenListEq({
kStart,
kTargetAddrRd,
kData,
kData,
kData,
kData,
kData,
kData,
kData,
kDataLast,
kStop,
kEnd,
});
}
TEST_F(AmlI2cTest, BigRead) {
EXPECT_NO_FATAL_FAILURE(Init());
ddk::I2cImplProtocolClient i2c(root_->GetLatestChild());
EXPECT_TRUE(i2c.is_valid());
constexpr uint8_t kExpectedReadData[]{0xb9, 0x17, 0x32, 0xba, 0x8e, 0xf7, 0x19, 0xf2, 0x78, 0xbf,
0xcb, 0xd3, 0xdc, 0xad, 0xbd, 0x78, 0x1b, 0xa8, 0xef, 0x1a};
controller_->SetReadData({kExpectedReadData, std::size(kExpectedReadData)});
uint8_t read_buffer[std::size(kExpectedReadData)];
memset(read_buffer, 0xaa, sizeof(read_buffer));
const i2c_impl_op_t op{
.address = 0x29,
.data_buffer = read_buffer,
.data_size = sizeof(read_buffer),
.is_read = true,
.stop = true,
};
EXPECT_OK(i2c.Transact(&op, 1));
const std::vector transfers = controller_->GetTransfers();
ASSERT_EQ(transfers.size(), 1);
EXPECT_EQ(transfers[0].target_addr, 0x29);
EXPECT_BYTES_EQ(read_buffer, kExpectedReadData, sizeof(read_buffer));
transfers[0].ExpectTokenListEq({
// First transfer
kStart,
kTargetAddrRd,
kData,
kData,
kData,
kData,
kData,
kData,
kData,
kData,
kEnd,
// Second transfer
kData,
kData,
kData,
kData,
kData,
kData,
kData,
kData,
kEnd,
// Third transfer
kData,
kData,
kData,
kDataLast,
kStop,
kEnd,
});
}
TEST_F(AmlI2cTest, NoStopFlag) {
EXPECT_NO_FATAL_FAILURE(Init());
ddk::I2cImplProtocolClient i2c(root_->GetLatestChild());
EXPECT_TRUE(i2c.is_valid());
uint8_t buffer[4];
const i2c_impl_op_t op{
.data_buffer = buffer,
.data_size = sizeof(buffer),
.is_read = false,
.stop = false,
};
EXPECT_OK(i2c.Transact(&op, 1));
const std::vector transfers = controller_->GetTransfers();
ASSERT_EQ(transfers.size(), 1);
transfers[0].ExpectTokenListEq({kStart, kTargetAddrWr, kData, kData, kData, kData, kEnd});
}
TEST_F(AmlI2cTest, TransferError) {
EXPECT_NO_FATAL_FAILURE(Init());
ddk::I2cImplProtocolClient i2c(root_->GetLatestChild());
EXPECT_TRUE(i2c.is_valid());
uint8_t buffer[4];
controller_->SetReadData({buffer, std::size(buffer)});
const i2c_impl_op_t op{
.data_buffer = buffer,
.data_size = sizeof(buffer),
.is_read = true,
.stop = false,
};
mmio()[kControlReg / sizeof(uint32_t)] = 1 << 3;
EXPECT_NOT_OK(i2c.Transact(&op, 1));
}
TEST_F(AmlI2cTest, ManyTransactions) {
EXPECT_NO_FATAL_FAILURE(Init());
ddk::I2cImplProtocolClient i2c(root_->GetLatestChild());
EXPECT_TRUE(i2c.is_valid());
constexpr uint8_t kExpectedReadData[]{0x85, 0xb0, 0xd0, 0x1c, 0xc6, 0x8a, 0x35, 0xfc,
0xcf, 0xca, 0x95, 0x01, 0x61, 0x42, 0x60, 0x8c,
0xa6, 0x01, 0xd6, 0x2e, 0x38, 0x20, 0x09, 0xfa};
controller_->SetReadData({kExpectedReadData, std::size(kExpectedReadData)});
constexpr uint8_t kExpectedWriteData[]{0x39, 0xf0, 0xf9, 0x17, 0xad, 0x51, 0xdc, 0x30, 0xe5};
uint8_t read_buffer_1[20];
uint8_t read_buffer_2[4];
memset(read_buffer_1, 0xaa, sizeof(read_buffer_1));
memset(read_buffer_2, 0xaa, sizeof(read_buffer_2));
uint8_t write_buffer_1[1]{kExpectedWriteData[0]};
uint8_t write_buffer_2[8];
static_assert(sizeof(kExpectedWriteData) == sizeof(write_buffer_1) + sizeof(write_buffer_2));
memcpy(write_buffer_2, kExpectedWriteData + 1, sizeof(write_buffer_2));
const i2c_impl_op_t ops[]{
{
.address = 0x1c,
.data_buffer = write_buffer_1,
.data_size = sizeof(write_buffer_1),
.is_read = false,
.stop = false,
},
{
.address = 0x2d,
.data_buffer = read_buffer_1,
.data_size = sizeof(read_buffer_1),
.is_read = true,
.stop = true,
},
{
.address = 0x3e,
.data_buffer = write_buffer_2,
.data_size = sizeof(write_buffer_2),
.is_read = false,
.stop = true,
},
{
.address = 0x4f,
.data_buffer = read_buffer_2,
.data_size = sizeof(read_buffer_2),
.is_read = true,
.stop = false,
},
};
EXPECT_OK(i2c.Transact(ops, std::size(ops)));
const std::vector transfers = controller_->GetTransfers();
ASSERT_EQ(transfers.size(), 4);
EXPECT_EQ(transfers[0].target_addr, 0x1c);
ASSERT_EQ(transfers[0].write_data.size(), 1);
EXPECT_EQ(transfers[0].write_data[0], kExpectedWriteData[0]);
transfers[0].ExpectTokenListEq({kStart, kTargetAddrWr, kData, kEnd});
EXPECT_EQ(transfers[1].target_addr, 0x2d);
EXPECT_BYTES_EQ(read_buffer_1, kExpectedReadData, sizeof(read_buffer_1));
transfers[1].ExpectTokenListEq({
// First transfer
kStart,
kTargetAddrRd,
kData,
kData,
kData,
kData,
kData,
kData,
kData,
kData,
kEnd,
// Second transfer
kData,
kData,
kData,
kData,
kData,
kData,
kData,
kData,
kEnd,
// Third transfer
kData,
kData,
kData,
kDataLast,
kStop,
kEnd,
});
EXPECT_EQ(transfers[2].target_addr, 0x3e);
ASSERT_EQ(transfers[2].write_data.size(), std::size(write_buffer_2));
EXPECT_BYTES_EQ(transfers[2].write_data.data(), kExpectedWriteData + 1, sizeof(write_buffer_2));
transfers[2].ExpectTokenListEq({
kStart,
kTargetAddrWr,
kData,
kData,
kData,
kData,
kData,
kData,
kData,
kData,
kStop,
kEnd,
});
EXPECT_EQ(transfers[3].target_addr, 0x4f);
EXPECT_BYTES_EQ(read_buffer_2, kExpectedReadData + sizeof(read_buffer_1), sizeof(read_buffer_2));
transfers[3].ExpectTokenListEq({
Token::kStart,
Token::kTargetAddrRd,
Token::kData,
Token::kData,
Token::kData,
Token::kDataLast,
Token::kEnd,
});
}
TEST_F(AmlI2cTest, TransactionTooBig) {
EXPECT_NO_FATAL_FAILURE(Init());
ddk::I2cImplProtocolClient i2c(root_->GetLatestChild());
EXPECT_TRUE(i2c.is_valid());
uint8_t buffer[513];
i2c_impl_op_t op{
.data_buffer = buffer,
.data_size = 512,
.is_read = false,
};
EXPECT_OK(i2c.Transact(&op, 1));
op.data_size = 513;
EXPECT_NOT_OK(i2c.Transact(&op, 1));
}
TEST_F(AmlI2cTest, Metadata) {
constexpr aml_i2c_delay_values metadata{.quarter_clock_delay = 0x3cd, .clock_low_delay = 0xf12};
root_->SetMetadata(DEVICE_METADATA_PRIVATE, &metadata, sizeof(metadata));
EXPECT_NO_FATAL_FAILURE(Init());
EXPECT_EQ(mmio()[kControlReg / sizeof(uint32_t)], 0x3cd << 12);
EXPECT_EQ(mmio()[kTargetAddrReg / sizeof(uint32_t)], (0xf12 << 16) | (1 << 28));
}
TEST_F(AmlI2cTest, NoMetadata) {
EXPECT_NO_FATAL_FAILURE(Init());
EXPECT_EQ(mmio()[kControlReg / sizeof(uint32_t)], 0);
EXPECT_EQ(mmio()[kTargetAddrReg / sizeof(uint32_t)], 0);
}
TEST_F(AmlI2cTest, CanUsePDevFragment) {
fake_pdev::FakePDevFidl::Config config;
config.irqs[0] = {};
ASSERT_OK(zx::interrupt::create(zx::resource(), 0, ZX_INTERRUPT_VIRTUAL, &config.irqs[0]));
controller_.emplace(config.irqs[0].borrow());
config.mmios[0] = controller_->GetMmioBuffer();
config.device_info = {
.mmio_count = 1,
.irq_count = 1,
};
zx::result outgoing_endpoints = fidl::CreateEndpoints<fuchsia_io::Directory>();
ASSERT_OK(outgoing_endpoints);
ASSERT_OK(incoming_loop_.StartThread("incoming-ns-thread"));
incoming_.SyncCall([config = std::move(config), server = std::move(outgoing_endpoints->server)](
IncomingNamespace* infra) mutable {
infra->pdev_server.SetConfig(std::move(config));
ASSERT_OK(infra->outgoing.AddService<fuchsia_hardware_platform_device::Service>(
infra->pdev_server.GetInstanceHandler()));
ASSERT_OK(infra->outgoing.Serve(std::move(server)));
});
ASSERT_NO_FATAL_FAILURE();
root_->AddFidlService(fuchsia_hardware_platform_device::Service::Name,
std::move(outgoing_endpoints->client), "pdev");
EXPECT_OK(AmlI2c::Bind(nullptr, root_.get()));
ASSERT_EQ(root_->child_count(), 1);
}
TEST_F(AmlI2cTest, MmioIrqCountInvalid) {
zx::result outgoing_endpoints = fidl::CreateEndpoints<fuchsia_io::Directory>();
ASSERT_OK(outgoing_endpoints);
ASSERT_OK(incoming_loop_.StartThread("incoming-ns-thread"));
incoming_.SyncCall(
[server = std::move(outgoing_endpoints->server)](IncomingNamespace* infra) mutable {
infra->pdev_server.SetConfig(fake_pdev::FakePDevFidl::Config{
.device_info =
pdev_device_info_t{
.mmio_count = 2,
.irq_count = 2,
},
});
ASSERT_OK(infra->outgoing.AddService<fuchsia_hardware_platform_device::Service>(
infra->pdev_server.GetInstanceHandler()));
ASSERT_OK(infra->outgoing.Serve(std::move(server)));
});
ASSERT_NO_FATAL_FAILURE();
root_->AddFidlService(fuchsia_hardware_platform_device::Service::Name,
std::move(outgoing_endpoints->client), "pdev");
EXPECT_NOT_OK(AmlI2c::Bind(nullptr, root_.get()));
}
} // namespace aml_i2c