blob: 6b3262093e67d747bf6050a57bdece51419cd549 [file] [log] [blame]
// Copyright 2018 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.
#ifndef SRC_DEVICES_TESTING_MOCK_MMIO_REG_INCLUDE_MOCK_MMIO_REG_MOCK_MMIO_REG_H_
#define SRC_DEVICES_TESTING_MOCK_MMIO_REG_INCLUDE_MOCK_MMIO_REG_MOCK_MMIO_REG_H_
#include <lib/mmio-ptr/fake.h>
#include <lib/mmio/mmio.h>
#include <memory>
#include <fbl/vector.h>
#include <zxtest/zxtest.h>
namespace ddk_mock {
// Mocks a single MMIO register. This class is intended to be used with a fdf::MmioBuffer;
// operations on an instance of that class will be directed to the mock if the mock-mmio-reg library
// is a dependency of the test. The base address used by the MmioBuffer should be an array of
// MockMmioReg objects. See the following example test:
//
// ddk_mock::MockMmioReg register_array[number_of_registers];
// ddk_mock::MockMmioRegRegion mock_registers(register_array, register_size, number_of_registers);
// fdf::MmioBuffer mmio_buffer(mock_registers.GetMmioBuffer());
//
// SomeDriver dut(mmio_buffer);
// mock_registers[0]
// .ExpectRead()
// .ExpectWrite(0xdeadbeef)
// .ExpectRead(0xcafecafe)
// .ExpectWrite()
// .ExpectRead();
// mock_registers[5]
// .ExpectWrite(0)
// .ExpectWrite(1024)
// .ReadReturns(0);
//
// EXPECT_OK(dut.SomeMethod());
// mock_registers.VerifyAll();
class MockMmioReg {
public:
// Reads from the mocked register. Returns the value set by the next expectation, or the default
// value. The default is initially zero and can be set by calling ReadReturns() or Write(). This
// method is expected to be called (indirectly) by the code under test.
uint64_t Read() {
if (read_expectations_index_ >= read_expectations_.size()) {
return last_value_;
}
MmioExpectation& exp = read_expectations_[read_expectations_index_++];
if (exp.match == MmioExpectation::Match::kAny) {
return last_value_;
}
return last_value_ = exp.value;
}
// Writes to the mocked register. This method is expected to be called (indirectly) by the code
// under test.
void Write(uint64_t value) {
last_value_ = value;
if (write_expectations_index_ >= write_expectations_.size()) {
return;
}
MmioExpectation& exp = write_expectations_[write_expectations_index_++];
if (exp.match != MmioExpectation::Match::kAny) {
EXPECT_EQ(exp.value, value);
}
}
// Matches a register read and returns the specified value.
MockMmioReg& ExpectRead(uint64_t value) {
read_expectations_.push_back(
MmioExpectation{.match = MmioExpectation::Match::kEqual, .value = value});
return *this;
}
// Matches a register read and returns the default value.
MockMmioReg& ExpectRead() {
read_expectations_.push_back(
MmioExpectation{.match = MmioExpectation::Match::kAny, .value = 0});
return *this;
}
// Sets the default register read value.
MockMmioReg& ReadReturns(uint64_t value) {
last_value_ = value;
return *this;
}
// Matches a register write with the specified value.
MockMmioReg& ExpectWrite(uint64_t value) {
write_expectations_.push_back(
MmioExpectation{.match = MmioExpectation::Match::kEqual, .value = value});
return *this;
}
// Matches any register write.
MockMmioReg& ExpectWrite() {
write_expectations_.push_back(
MmioExpectation{.match = MmioExpectation::Match::kAny, .value = 0});
return *this;
}
// Removes and ignores all expectations and resets the default read value.
void Clear() {
last_value_ = 0;
read_expectations_index_ = 0;
while (read_expectations_.size() > 0) {
read_expectations_.pop_back();
}
write_expectations_index_ = 0;
while (write_expectations_.size() > 0) {
write_expectations_.pop_back();
}
}
// Removes all expectations and resets the default value. The presence of any outstanding
// expectations causes a test failure.
void VerifyAndClear() {
EXPECT_GE(read_expectations_index_, read_expectations_.size());
EXPECT_GE(write_expectations_index_, write_expectations_.size());
Clear();
}
private:
struct MmioExpectation {
enum class Match { kEqual, kAny } match;
uint64_t value;
};
uint64_t last_value_ = 0;
size_t read_expectations_index_ = 0;
fbl::Vector<MmioExpectation> read_expectations_;
size_t write_expectations_index_ = 0;
fbl::Vector<MmioExpectation> write_expectations_;
};
// Represents an array of MockMmioReg objects.
class MockMmioRegRegion {
public:
// Constructs a MockMmioRegRegion backed by the given array. reg_size is the size of each
// register in bytes, reg_count is the total size of the region in bytes. Ownership of mock_regs
// is not transferred.
MockMmioRegRegion(MockMmioReg* mock_regs, size_t reg_size, size_t reg_count)
: mock_regs_(mock_regs), reg_size_(reg_size), reg_count_(reg_count) {
ASSERT_GT(reg_size_, 0);
}
// Accesses the MockMmioReg at the given offset. Note that this is the _offset_, not the
// _index_.
MockMmioReg& operator[](size_t offset) const {
CheckOffset(offset);
return mock_regs_[offset / reg_size_];
}
// Calls VerifyAndClear() on all MockMmioReg objects.
void VerifyAll() {
for (size_t i = 0; i < reg_count_; i++) {
mock_regs_[i].VerifyAndClear();
}
}
fdf::MmioBuffer GetMmioBuffer() {
return fdf::MmioBuffer(
mmio_buffer_t{
.vaddr = FakeMmioPtr(this),
.offset = 0,
.size = reg_size_ * reg_count_,
.vmo = ZX_HANDLE_INVALID,
},
&kMockMmioOps, this);
}
private:
static uint8_t Read8(const void* ctx, const mmio_buffer_t& mmio, zx_off_t offs) {
const auto& reg_region = *reinterpret_cast<const MockMmioRegRegion*>(ctx);
return static_cast<uint8_t>(reg_region[offs + mmio.offset].Read());
}
static uint16_t Read16(const void* ctx, const mmio_buffer_t& mmio, zx_off_t offs) {
const auto& reg_region = *reinterpret_cast<const MockMmioRegRegion*>(ctx);
return static_cast<uint16_t>(reg_region[offs + mmio.offset].Read());
}
static uint32_t Read32(const void* ctx, const mmio_buffer_t& mmio, zx_off_t offs) {
const auto& reg_region = *reinterpret_cast<const MockMmioRegRegion*>(ctx);
return static_cast<uint32_t>(reg_region[offs + mmio.offset].Read());
}
static uint64_t Read64(const void* ctx, const mmio_buffer_t& mmio, zx_off_t offs) {
const auto& reg_region = *reinterpret_cast<const MockMmioRegRegion*>(ctx);
return reg_region[offs + mmio.offset].Read();
}
static void Write8(const void* ctx, const mmio_buffer_t& mmio, uint8_t val, zx_off_t offs) {
Write64(ctx, mmio, val, offs);
}
static void Write16(const void* ctx, const mmio_buffer_t& mmio, uint16_t val, zx_off_t offs) {
Write64(ctx, mmio, val, offs);
}
static void Write32(const void* ctx, const mmio_buffer_t& mmio, uint32_t val, zx_off_t offs) {
Write64(ctx, mmio, val, offs);
}
static void Write64(const void* ctx, const mmio_buffer_t& mmio, uint64_t val, zx_off_t offs) {
const auto& reg_region = *reinterpret_cast<const MockMmioRegRegion*>(ctx);
reg_region[offs + mmio.offset].Write(val);
}
static constexpr fdf::internal::MmioBufferOps kMockMmioOps = {
.Read8 = Read8,
.Read16 = Read16,
.Read32 = Read32,
.Read64 = Read64,
.Write8 = Write8,
.Write16 = Write16,
.Write32 = Write32,
.Write64 = Write64,
};
void CheckOffset(zx_off_t offs) const { ASSERT_LT(offs / reg_size_, reg_count_); }
MockMmioReg* const mock_regs_;
const size_t reg_size_;
const size_t reg_count_;
};
} // namespace ddk_mock
#endif // SRC_DEVICES_TESTING_MOCK_MMIO_REG_INCLUDE_MOCK_MMIO_REG_MOCK_MMIO_REG_H_