[sw/testing] Add more EXPECT_*s to mock_mmio.h This change expands the types available to use as "integers" for EXPECT_READ and EXPECT_WRITE, which includes character arrays, BitField, and MaskedBitField. It also introduces EXPECT_MASKn(), which can leverage MaskedBitField to compactly expect read-mask-write operations. Signed-off-by: Miguel Young de la Sota <mcyoung@google.com>
diff --git a/sw/device/lib/testing/mock_mmio.cc b/sw/device/lib/testing/mock_mmio.cc index 51a13cf..fbbe121 100644 --- a/sw/device/lib/testing/mock_mmio.cc +++ b/sw/device/lib/testing/mock_mmio.cc
@@ -7,6 +7,8 @@ #include "sw/device/lib/base/mmio.h" namespace mock_mmio { +std::random_device MockDevice::rd; + // Definitions for the MOCK_MMIO-mode declarations in |mmio.h|. extern "C" { uint8_t mmio_region_read8(mmio_region_t base, ptrdiff_t offset) { @@ -39,4 +41,4 @@ dev->Write32(offset, value); } } // extern "C" -} // namespace mock_mmio \ No newline at end of file +} // namespace mock_mmio
diff --git a/sw/device/lib/testing/mock_mmio.h b/sw/device/lib/testing/mock_mmio.h index 1866655..e1461c3 100644 --- a/sw/device/lib/testing/mock_mmio.h +++ b/sw/device/lib/testing/mock_mmio.h
@@ -6,8 +6,11 @@ #define OPENTITAN_SW_DEVICE_LIB_TESTING_MOCK_MMIO_H_ #include <stdint.h> +#include <string.h> +#include <initializer_list> #include <memory> +#include <random> #include <vector> #include "gmock/gmock.h" @@ -16,6 +19,129 @@ namespace mock_mmio { /** + * Represents a single bit field in an integer, useable with EXPECT_* macros + * defined in this file. + * + * An integer can be expressed as a list of BitField values, and can be more + * convenient to use than 0b or 0x literals in some cases. For example, the + * integer 0b0000'0000'1100'0101 could be expressed as + * {{0x0, 1}, {0x2, 1}, {0x4, 12}} + * This form makes it clearer to the reader that 0x0, 0x2, and 04 are indices + * to bitfields, which are set to particular values. + * + * In practice, this might use generated register constants, and look like + * {{FIELD_FOO_OFFSET, 1}, {FIELD_BAR_OFFSET, 1}, {FIELD_BAZ_OFFSET, 12}} + * + * This type does not specify the lengths of bitfields; MaskedBitField should be + * used for that, instead. + */ +struct BitField { + uintptr_t offset; + uintptr_t value; +}; + +/** + * Represents a single bit field in an integer, similar to BitField. It can be + * used in most places that need a BitField, as well as in |EXPECT_MASK| macros. + * + * Like with BitFields, we can express the integer 0b0000'0000'1100'0101 as a + * list of BitFieldMasks: + * {{0x0, 0x1, 1}, {0x1, 0x1, 0}, {0x2, 0x1, 1}, {0x3, 0x1, 0}, + * {0x4, 0xff, 12}} + * + * In addition to showing how the integer is broken up, it also expresses + * the lengths of fields, so it is clear that 0x0 and 0x2 are one-bit fields. + * This also allows us to formally express that the fields 0x1 and 0x3 are + * *unset*. + * + * In practice, this might use generated register constants, and look like + * {{FIELD_FOO_OFFSET, FIELD_FOO_MASK, 1}, ...} + */ +struct MaskedBitField { + uintptr_t offset; + uintptr_t mask; + uintptr_t value; +}; + +namespace internal { +/** + * Implicit conversion guard around |char *|. See |LeInt()|. + */ +struct LittleEndianBytes { + const char *bytes; +}; + +/** + * Converts the argument into an unsigned integer of type |Int|. + * + * This overload is simply the identity on integers, and allows integers to be + * converted into themselves. This enables the basic EXPECT_* macros: + * EXPECT_READ32(offset, 0xcafecafe); + * + * @param val an integer. + * @return the value |val|. + */ +template <typename Int> +Int ToInt(Int val) { + return val; +} + +/** + * Converts the argument into an unsinged integer of type |Int|. + * + * This overload assumes that |str| is a valid pointer to a buffer of at least + * |sizeof(Int)| bytes, which are memcpy'd out as an |Int|. This enables + * memcpy-like EXPECT_* macros: + * EXPECT_READ32(offset, LeInt("rv32")); + * EXPECT_READ32(offset, LeInt("imc\0")); + * + * @param str a pointer to a valid buffer of length at least |sizeof(Int)|. + * @return a value of type |Int| memcpy'd out of |str|. + */ +template <typename Int> +Int ToInt(LittleEndianBytes str) { + Int val; + memcpy(&val, str.bytes, sizeof(Int)); + return val; +} + +/** + * Converts the argument into an unsigned integer of type |Int|. + * + * This overload performs the shifts and ors described by |fields|. See + * |BitField|'s documentation for details one what this means. This overload + * enables bitfield EXPECT_* macros: + * EXPECT_READ32(offset, {{A_OFFSET, 0x55}, {B_OFFSET, 0xaa}}); + * + * @param fields a list of bit field entries. + * @return a value of type |Int| built out of |fields|. + */ +template <typename Int> +Int ToInt(std::initializer_list<BitField> fields) { + Int val = 0; + for (auto field : fields) { + // Due to the way that gtest ASSERT_* works, and the fact that this must be a + // function (since we use function overloading), these cannot be ASSERTs, and + // must be EXPECTs. + EXPECT_LE(field.offset, sizeof(Int) * 8); + val |= static_cast<Int>(field.value << field.offset); + } + return val; +} +} // namespace internal + +/** + * Reads a little-endian integer from |bytes|. This function is lazy, and will + * only perform the converion when used with an EXPECT_* macro. For example: + * EXPECT_READ32(offset, LeInt("abcd")); + * + * It is not possible to directly pass in string literals into EXPECT_* macros; + * this is a limitation of C++'s implicit conversion rules and overload + * resolution order. + */ +inline internal::LittleEndianBytes LeInt(const char *bytes) { return {bytes}; } + +/** * A MockDevice represents a mock implementation of an MMIO device. * * MockDevice provides two mockable member functions, representing a read and a @@ -53,6 +179,19 @@ MOCK_METHOD(void, Write8, (ptrdiff_t offset, uint8_t value)); MOCK_METHOD(void, Write16, (ptrdiff_t offset, uint16_t value)); MOCK_METHOD(void, Write32, (ptrdiff_t offset, uint32_t value)); + + /** + * Generates "garbage memory" for use in tests. This function should not + * be called directly. + */ + template <typename Int> + Int GarbageMemory() { + return std::uniform_int_distribution<Int>()(gen_); + } + + private: + static std::random_device rd; + std::mt19937 gen_ = std::mt19937(rd()); }; /** @@ -74,138 +213,290 @@ std::unique_ptr<MockDevice> dev_ = std::make_unique<MockDevice>(); testing::InSequence seq_; }; - } // namespace mock_mmio /** * Expect a read to the device |dev| at the given offset, returning the given * 8-bit value. * + * The value may be given as an integer, a pointer to little-endian data, + * or a |std::initializer_list<BitField>|. + * * This expectation is sequenced with all other |EXPECT_READ| and |EXPECT_WRITE| * calls. */ -#define EXPECT_READ8_AT(dev, offset, value) \ - EXPECT_CALL(dev, Read8(offset)).WillOnce(testing::Return(value)) +#define EXPECT_READ8_AT(dev, offset, ...) \ + EXPECT_CALL(dev, Read8(offset)) \ + .WillOnce( \ + testing::Return(mock_mmio::internal::ToInt<uint8_t>(__VA_ARGS__))) /** * Expect a read to the device |dev| at the given offset, returning the given * 16-bit value. * + * The value may be given as an integer, a pointer to little-endian data, + * or a |std::initializer_list<BitField>|. + * * This expectation is sequenced with all other |EXPECT_READ| and |EXPECT_WRITE| * calls. */ -#define EXPECT_READ16_AT(dev, offset, value) \ - EXPECT_CALL(dev, Read16(offset)).WillOnce(testing::Return(value)) +#define EXPECT_READ16_AT(dev, offset, ...) \ + EXPECT_CALL(dev, Read16(offset)) \ + .WillOnce( \ + testing::Return(mock_mmio::internal::ToInt<uint16_t>(__VA_ARGS__))) /** * Expect a read to the device |dev| at the given offset, returning the given * 32-bit value. * + * The value may be given as an integer, a pointer to little-endian data, + * or a |std::initializer_list<BitField>|. + * * This expectation is sequenced with all other |EXPECT_READ| and |EXPECT_WRITE| * calls. */ -#define EXPECT_READ32_AT(dev, offset, value) \ - EXPECT_CALL(dev, Read32(offset)).WillOnce(testing::Return(value)) +#define EXPECT_READ32_AT(dev, offset, ...) \ + EXPECT_CALL(dev, Read32(offset)) \ + .WillOnce( \ + testing::Return(mock_mmio::internal::ToInt<uint32_t>(__VA_ARGS__))) /** * Expect a write to the device |dev| at the given offset with the given 8-bit * value. * + * The value may be given as an integer, a pointer to little-endian data, + * or a |std::initializer_list<BitField>|. + * * This expectation is sequenced with all other |EXPECT_READ| and |EXPECT_WRITE| * calls. */ -#define EXPECT_WRITE8_AT(dev, offset, value) \ - EXPECT_CALL(dev, Write8(offset, value)) +#define EXPECT_WRITE8_AT(dev, offset, ...) \ + EXPECT_CALL( \ + dev, Write8(offset, mock_mmio::internal::ToInt<uint8_t>(__VA_ARGS__))) /** * Expect a write to the device |dev| at the given offset with the given 16-bit * value. * + * The value may be given as an integer, a pointer to little-endian data, + * or a |std::initializer_list<BitField>|. + * * This expectation is sequenced with all other |EXPECT_READ| and |EXPECT_WRITE| * calls. */ -#define EXPECT_WRITE16_AT(dev, offset, value) \ - EXPECT_CALL(dev, Write16(offset, value)) +#define EXPECT_WRITE16_AT(dev, offset, ...) \ + EXPECT_CALL( \ + dev, Write16(offset, mock_mmio::internal::ToInt<uint16_t>(__VA_ARGS__))) /** * Expect a write to the device |dev| at the given offset with the given 32-bit * value. * + * The value may be given as an integer, a pointer to little-endian data, + * or a |std::initializer_list<BitField>|. + * * This expectation is sequenced with all other |EXPECT_READ| and |EXPECT_WRITE| * calls. */ -#define EXPECT_WRITE32_AT(dev, offset, value) \ - EXPECT_CALL(dev, Write32(offset, value)) +#define EXPECT_WRITE32_AT(dev, offset, ...) \ + EXPECT_CALL( \ + dev, Write32(offset, mock_mmio::internal::ToInt<uint32_t>(__VA_ARGS__))) /** * Expect a read at the given offset, returning the given 8-bit value. * + * The value may be given as an integer, a pointer to little-endian data, + * or a |std::initializer_list<BitField>|. + * * This function is only available in tests using a fixture that derives - * |DeviceTest|. + * |MmioTest|. * * This expectation is sequenced with all other |EXPECT_READ| and |EXPECT_WRITE| * calls. */ -#define EXPECT_READ8(offset, value) EXPECT_READ8_AT(this->dev(), offset, value) +#define EXPECT_READ8(offset, ...) \ + EXPECT_READ8_AT(this->dev(), offset, __VA_ARGS__) /** * Expect a read at the given offset, returning the given 16-bit value. * + * The value may be given as an integer, a pointer to little-endian data, + * or a |std::initializer_list<BitField>|. + * * This function is only available in tests using a fixture that derives - * |DeviceTest|. + * |MmioTest|. * * This expectation is sequenced with all other |EXPECT_READ| and |EXPECT_WRITE| * calls. */ -#define EXPECT_READ16(offset, value) \ - EXPECT_READ16_AT(this->dev(), offset, value) +#define EXPECT_READ16(offset, ...) \ + EXPECT_READ16_AT(this->dev(), offset, __VA_ARGS__) /** * Expect a read at the given offset, returning the given 32-bit value. * + * The value may be given as an integer, a pointer to little-endian data, + * or a |std::initializer_list<BitField>|. + * * This function is only available in tests using a fixture that derives - * |DeviceTest|. + * |MmioTest|. * * This expectation is sequenced with all other |EXPECT_READ| and |EXPECT_WRITE| * calls. */ -#define EXPECT_READ32(offset, value) \ - EXPECT_READ32_AT(this->dev(), offset, value) +#define EXPECT_READ32(offset, ...) \ + EXPECT_READ32_AT(this->dev(), offset, __VA_ARGS__) /** * Expect a write to the given offset with the given 8-bit value. * + * The value may be given as an integer, a pointer to little-endian data, + * or a |std::initializer_list<BitField>|. + * * This function is only available in tests using a fixture that derives - * |DeviceTest|. + * |MmioTest|. * * This expectation is sequenced with all other |EXPECT_READ| and |EXPECT_WRITE| * calls. */ -#define EXPECT_WRITE8(offset, value) \ - EXPECT_WRITE8_AT(this->dev(), offset, value); +#define EXPECT_WRITE8(offset, ...) \ + EXPECT_WRITE8_AT(this->dev(), offset, __VA_ARGS__); /** * Expect a write to the given offset with the given 16-bit value. * + * The value may be given as an integer, a pointer to little-endian data, + * or a |std::initializer_list<BitField>|. + * * This function is only available in tests using a fixture that derives - * |DeviceTest|. + * |MmioTest|. * * This expectation is sequenced with all other |EXPECT_READ| and |EXPECT_WRITE| * calls. */ -#define EXPECT_WRITE16(offset, value) \ - EXPECT_WRITE16_AT(this->dev(), offset, value); +#define EXPECT_WRITE16(offset, ...) \ + EXPECT_WRITE16_AT(this->dev(), offset, __VA_ARGS__); /** * Expect a write to the given offset with the given 32-bit value. * + * The value may be given as an integer, a pointer to little-endian data, + * or a |std::initializer_list<BitField>|. + * * This function is only available in tests using a fixture that derives - * |DeviceTest|. + * |MmioTest|. * * This expectation is sequenced with all other |EXPECT_READ| and |EXPECT_WRITE| * calls. */ -#define EXPECT_WRITE32(offset, value) \ - EXPECT_WRITE32_AT(this->dev(), offset, value); +#define EXPECT_WRITE32(offset, ...) \ + EXPECT_WRITE32_AT(this->dev(), offset, __VA_ARGS__); + +#define EXPECT_MASK_INTERAL_(width, dev, off, ...) \ + do { \ + auto &device = dev; \ + std::initializer_list<mock_mmio::MaskedBitField> fields = __VA_ARGS__; \ + \ + using Int = uint##width##_t; \ + auto val = device.GarbageMemory<Int>(); \ + EXPECT_READ##width##_AT(device, off, val); \ + \ + for (auto field : fields) { \ + ASSERT_LT(field.offset, sizeof(Int) * 8); \ + ASSERT_LE(field.mask, std::numeric_limits<Int>::max()); \ + ASSERT_LE(field.value, field.mask); \ + \ + val &= ~static_cast<Int>(field.mask << field.offset); \ + val |= static_cast<Int>(field.value << field.offset); \ + } \ + EXPECT_WRITE##width##_AT(device, off, val); \ + } while (false) + +/** + * Expect an unspecified 8-bit read to the device |dev| at the given offset, + * followed by a write to the same location, with the same value but with some + * bits changed; the remaining bits must be untouched. + * + * The changed bits are specified by a |std::initializer_list<MaskedBitField>|. + * + * This expectation is sequenced with all other |EXPECT_READ| and |EXPECT_WRITE| + * calls. + */ +#define EXPECT_MASK8_AT(dev, offset, ...) \ + EXPECT_MASK_INTERAL_(8, dev, offset, __VA_ARGS__) + +/** + * Expect an unspecified 16-bit read to the device |dev| at the given offset, + * followed by a write to the same location, with the same value but with some + * bits changed; the remaining bits must be untouched. + * + * The changed bits are specified by a |std::initializer_list<MaskedBitField>|. + * + * This expectation is sequenced with all other |EXPECT_READ| and |EXPECT_WRITE| + * calls. + */ +#define EXPECT_MASK16_AT(dev, offset, ...) \ + EXPECT_MASK_INTERAL_(16, dev, offset, __VA_ARGS__) + +/** + * Expect an unspecified 32-bit read to the device |dev| at the given offset, + * followed by a write to the same location, with the same value but with some + * bits changed; the remaining bits must be untouched. + * + * The changed bits are specified by a |std::initializer_list<MaskedBitField>|. + * + * This expectation is sequenced with all other |EXPECT_READ| and |EXPECT_WRITE| + * calls. + */ +#define EXPECT_MASK32_AT(dev, offset, ...) \ + EXPECT_MASK_INTERAL_(32, dev, offset, __VA_ARGS__) + +/** + * Expect an unspecified 8-bit read at the given offset, followed by a write to + * the same location, with the same value but with some bits changed; the + * remaining bits must be untouched. + * + * The changed bits are specified by a |std::initializer_list<MaskedBitField>|. + * + * This function is only available in tests using a fixture that derives + * |MmioTest|. + * + * This expectation is sequenced with all other |EXPECT_READ| and |EXPECT_WRITE| + * calls. + */ +#define EXPECT_MASK8(offset, ...) \ + EXPECT_MASK32_AT(this->dev(), offset, __VA_ARGS__) + +/** + * Expect an unspecified 16-bit read at the given offset, followed by a write to + * the same location, with the same value but with some bits changed; the + * remaining bits must be untouched. + * + * The changed bits are specified by a |std::initializer_list<MaskedBitField>|. + * + * This function is only available in tests using a fixture that derives + * |MmioTest|. + * + * This expectation is sequenced with all other |EXPECT_READ| and |EXPECT_WRITE| + * calls. + */ +#define EXPECT_MASK16(offset, ...) \ + EXPECT_MASK32_AT(this->dev(), offset, __VA_ARGS__) + +/** + * Expect an unspecified 32-bit read at the given offset, followed by a write to + * the same location, with the same value but with some bits changed; the + * remaining bits must be untouched. + * + * The changed bits are specified by a |std::initializer_list<MaskedBitField>|. + * + * This function is only available in tests using a fixture that derives + * |MmioTest|. + * + * This expectation is sequenced with all other |EXPECT_READ| and |EXPECT_WRITE| + * calls. + */ +#define EXPECT_MASK32(offset, ...) \ + EXPECT_MASK32_AT(this->dev(), offset, __VA_ARGS__) #endif // OPENTITAN_SW_DEVICE_LIB_TESTING_MOCK_MMIO_H_
diff --git a/sw/device/lib/testing/mock_mmio_test.cc b/sw/device/lib/testing/mock_mmio_test.cc index b232e02..03f3735 100644 --- a/sw/device/lib/testing/mock_mmio_test.cc +++ b/sw/device/lib/testing/mock_mmio_test.cc
@@ -8,6 +8,7 @@ #include "sw/device/lib/base/mmio.h" namespace { +using ::mock_mmio::LeInt; using ::mock_mmio::MmioTest; using ::testing::Test; @@ -24,8 +25,9 @@ return value; } -class WriteTwiceTest : public Test, public MmioTest {}; -TEST_F(WriteTwiceTest, WriteTwice) { +class MockMmioTest : public Test, public MmioTest {}; + +TEST_F(MockMmioTest, WriteTwice) { EXPECT_READ32(0x0, 0xdeadbeef); EXPECT_WRITE32(0x4, 0x21524110) EXPECT_WRITE16(0x8, 0xdead); @@ -33,4 +35,43 @@ EXPECT_EQ(WriteTwice(dev().region()), 0xdeadbeef); } + +// This test mostly exists to guard against |0x0| being interpreted as a NULL +// pointer literal. C++ overloading is unpredictable in choosing between T* and +// int here. +TEST_F(MockMmioTest, ExpectReadZero) { + EXPECT_READ8(0x0, 0x0); + EXPECT_EQ(mmio_region_read8(dev().region(), 0x0), 0x0); +} + +TEST_F(MockMmioTest, ExpectWithString) { + EXPECT_READ32(0xc, LeInt("*\0\0*")); + EXPECT_EQ(mmio_region_read32(dev().region(), 0xc), 0x2a00002a); + + EXPECT_WRITE32(0x12, LeInt("abcd")); + mmio_region_write32(dev().region(), 0x12, 0x64636261); +} + +TEST_F(MockMmioTest, ExpectWithBits) { + EXPECT_READ8(0xc, {{0x0, false}, {0x1, true}, {0x3, true}}); + EXPECT_EQ(mmio_region_read8(dev().region(), 0xc), 0b1010); + + EXPECT_WRITE16(0x12, {{0x0, 0xfe}, {0x8, 0xca}}); + mmio_region_write16(dev().region(), 0x12, 0xcafe); +} + +TEST_F(MockMmioTest, ExpectMask) { + EXPECT_MASK32(0x8, { + {0x0, 0xfff, 0xabc}, + {0x10, 0x1, 0x0}, + }); + + auto value = mmio_region_read32(dev().region(), 0x8); + // Set the first three nybbles. + value &= ~0xfff; + value |= 0xabc; + // Clear the 16th bit. + value &= ~(1 << 0x10); + mmio_region_write32(dev().region(), 0x8, value); +} } // namespace