[sw/silicon_creator] Introduce the sec_mmio module

The sec_mmio module is responsible for tracking critical register
valuesfor an initialized sec_mmio_ctx_t context, and provides
a mechanism to check expected values and trigger shutdown escalation on
fault detection.

Signed-off-by: Miguel Osorio <miguelosorio@google.com>
diff --git a/sw/device/silicon_creator/lib/base/meson.build b/sw/device/silicon_creator/lib/base/meson.build
index 6f12e45..782a3c4 100644
--- a/sw/device/silicon_creator/lib/base/meson.build
+++ b/sw/device/silicon_creator/lib/base/meson.build
@@ -2,7 +2,6 @@
 # Licensed under the Apache License, Version 2.0, see LICENSE for details.
 # SPDX-License-Identifier: Apache-2.0
 
-# Mask ROM Secure MMIO module
 sw_silicon_creator_lib_base_abs_mmio = declare_dependency(
   link_with: static_library(
     'sw_silicon_creator_lib_base_abs_mmio',
@@ -28,3 +27,47 @@
     cpp_args: ['-DMOCK_ABS_MMIO'],
   )
 )
+
+# Mask ROM Secure MMIO module
+sw_silicon_creator_lib_base_sec_mmio = declare_dependency(
+  link_with: static_library(
+    'sw_silicon_creator_lib_base_sec_mmio',
+    sources: [
+      'sec_mmio.c',
+    ],
+    dependencies: [
+      sw_silicon_creator_lib_base_abs_mmio,
+    ],
+  ),
+)
+
+sw_silicon_creator_lib_base_mock_sec_mmio = declare_dependency(
+  link_with: static_library(
+    'mock_sec_mmio',
+    sources: [
+      'mock_sec_mmio.h',
+    ],
+    dependencies: [
+      sw_vendor_gtest,
+      sw_lib_testing_bitfield,
+    ],
+    native: true,
+  )
+)
+
+test('sw_silicon_creator_lib_base_sec_mmio_unittest', executable(
+    'sw_silicon_creator_lib_base_sec_mmio_unittest',
+    sources: [
+      'sec_mmio_unittest.cc',
+      'sec_mmio.c',
+    ],
+    dependencies: [
+      sw_vendor_gtest,
+      sw_silicon_creator_lib_base_mock_abs_mmio
+    ],
+    native: true,
+    c_args: ['-DMOCK_ABS_MMIO'],
+    cpp_args: ['-DMOCK_ABS_MMIO'],
+  ),
+  suite: 'mask_rom',
+)
diff --git a/sw/device/silicon_creator/lib/base/mock_sec_mmio.h b/sw/device/silicon_creator/lib/base/mock_sec_mmio.h
new file mode 100644
index 0000000..1d8794fc
--- /dev/null
+++ b/sw/device/silicon_creator/lib/base/mock_sec_mmio.h
@@ -0,0 +1,90 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+
+#ifndef OPENTITAN_SW_DEVICE_SILICON_CREATOR_LIB_BASE_MOCK_SEC_MMIO_H_
+#define OPENTITAN_SW_DEVICE_SILICON_CREATOR_LIB_BASE_MOCK_SEC_MMIO_H_
+
+#include "sw/device/lib/base/testing/mock_mmio_test_utils.h"
+#include "sw/device/lib/testing/mask_rom_test.h"
+#include "sw/device/silicon_creator/lib/base/sec_mmio.h"
+
+namespace mask_rom_test {
+namespace internal {
+/**
+ * Mock class for abs_mmio.c.
+ */
+class MockSecMmio {
+ public:
+  MOCK_METHOD(void, Init, (sec_mmio_shutdown_handler callee));
+  MOCK_METHOD(uint32_t, Read32, (uint32_t addr));
+  MOCK_METHOD(void, Write32, (uint32_t addr, uint32_t value));
+  MOCK_METHOD(void, WriteIncrement, (uint32_t value));
+  MOCK_METHOD(void, CheckValues, (uint32_t rnd_offset));
+  MOCK_METHOD(void, CheckCounters, (uint32_t expected_check_count));
+
+  virtual ~MockSecMmio() {}
+};
+}  // namespace internal
+
+using MockSecMmio = GlobalMock<testing::StrictMock<internal::MockSecMmio>>;
+
+/**
+ * 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_SEC_READ` and
+ * `EXPECT_SEC_WRITE` calls.
+ */
+#define EXPECT_SEC_READ32(mmio, addr, ...) \
+  EXPECT_CALL(mmio, Read32(addr))          \
+      .WillOnce(testing::Return(mock_mmio::ToInt<uint32_t>(__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
+ * `MmioTest`.
+ *
+ * This expectation is sequenced with all other `EXPECT_SEC_READ` and
+ * `EXPECT_SEC_WRITE` calls.
+ */
+#define EXPECT_SEC_WRITE32(mmio, addr, ...) \
+  EXPECT_CALL(mmio, Write32(addr, mock_mmio::ToInt<uint32_t>(__VA_ARGS__)));
+
+extern "C" {
+
+void sec_mmio_init(sec_mmio_shutdown_handler callee) {
+  MockSecMmio::Instance().Init(callee);
+}
+
+uint32_t sec_mmio_read32(uint32_t addr) {
+  MockSecMmio::Instance().Read32(addr);
+}
+
+void sec_mmio_write32(uint32_t addr, uint32_t value) {
+  MockSecMmio::Instance().Write32(addr, value);
+}
+
+void sec_mmio_write_increment(uint32_t value) {
+  MockSecMmio::Instance().WriteIncrement(value);
+}
+
+void sec_mmio_check_values(uint32_t rnd_offset) {
+  MockSecMmio::Instance().CheckValues(rnd_offset);
+}
+
+void sec_mmio_check_counters(uint32_t expected_check_count) {
+  MockSecMmio::Instance().CheckCounters(expected_check_count);
+}
+
+}  // extern "C"
+}  // namespace mask_rom_test
+
+#endif  // OPENTITAN_SW_DEVICE_SILICON_CREATOR_LIB_BASE_MOCK_SEC_MMIO_H_
diff --git a/sw/device/silicon_creator/lib/base/sec_mmio.c b/sw/device/silicon_creator/lib/base/sec_mmio.c
new file mode 100644
index 0000000..0c99d2b
--- /dev/null
+++ b/sw/device/silicon_creator/lib/base/sec_mmio.c
@@ -0,0 +1,139 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+
+#include "sw/device/silicon_creator/lib/base/sec_mmio.h"
+
+#include "sw/device/lib/base/memory.h"
+#include "sw/device/silicon_creator/lib/base/abs_mmio.h"
+
+// FIXME: Linker configuration.
+extern sec_mmio_ctx_t sec_mmio_ctx;
+
+// FIXME: Replace for shutdown module handler.
+static sec_mmio_shutdown_handler sec_mmio_shutdown_cb;
+
+// Value with good hamming weight used to mask the stored expected value.
+static const uint32_t kSecMmioMaskVal = 0x21692436u;
+
+// This must be set to a prime number greater than the number of items in
+// `sec_mmio_ctx.addrs`. Used to generate random read order permutations.
+static const uint32_t kSecMmioRndStep = 103u;
+
+/**
+ * Updates or inserts the register entry pointed to by MMIO `addr` with the
+ * given `value`.
+ *
+ * Increments the `sec_mmio_ctx.last_index`.
+ */
+static void upsert_register(uint32_t addr, uint32_t value) {
+  size_t i = 0;
+  for (; i < sec_mmio_ctx.last_index; ++i) {
+    if (sec_mmio_ctx.addrs[i] == addr) {
+      sec_mmio_ctx.values[i] = value;
+      break;
+    }
+  }
+  if (i == sec_mmio_ctx.last_index && i < kSecMmioRegFileSize) {
+    sec_mmio_ctx.addrs[i] = addr;
+    sec_mmio_ctx.values[i] = value;
+    ++sec_mmio_ctx.last_index;
+  }
+  // The following condition check serves as an additional fault detection
+  // mechanism.
+  if (i >= kSecMmioRegFileSize) {
+    sec_mmio_shutdown_cb();
+    __builtin_unreachable();
+  }
+}
+
+void sec_mmio_init(sec_mmio_shutdown_handler cb) {
+  sec_mmio_shutdown_cb = cb;
+  sec_mmio_ctx.last_index = 0;
+  sec_mmio_ctx.write_count = 0;
+  sec_mmio_ctx.check_count = 0;
+  sec_mmio_ctx.expected_write_count = 0;
+  for (size_t i = 0; i < ARRAYSIZE(sec_mmio_ctx.addrs); ++i) {
+    sec_mmio_ctx.addrs[i] = UINT32_MAX;
+  }
+}
+
+uint32_t sec_mmio_read32(uint32_t addr) {
+  uint32_t value = abs_mmio_read32(addr);
+  uint32_t masked_value = value ^ kSecMmioMaskVal;
+
+  upsert_register(addr, value);
+
+  if ((abs_mmio_read32(addr) ^ kSecMmioMaskVal) != masked_value) {
+    sec_mmio_shutdown_cb();
+    __builtin_unreachable();
+  }
+  return value;
+}
+
+void sec_mmio_write32(uint32_t addr, uint32_t value) {
+  abs_mmio_write32(addr, value);
+  uint32_t masked_value = value ^ kSecMmioMaskVal;
+
+  upsert_register(addr, masked_value);
+
+  if ((abs_mmio_read32(addr) ^ kSecMmioMaskVal) != masked_value) {
+    sec_mmio_shutdown_cb();
+    __builtin_unreachable();
+  }
+  ++sec_mmio_ctx.write_count;
+}
+
+void sec_mmio_write_increment(uint32_t value) {
+  sec_mmio_ctx.expected_write_count += value;
+}
+
+void sec_mmio_check_values(uint32_t rnd_offset) {
+  size_t offset = rnd_offset;
+  size_t i;
+  for (i = 0; i < sec_mmio_ctx.last_index; ++i) {
+    // FIXME: Remove dependency on __udivdi3.
+    offset = (offset + kSecMmioRndStep) % sec_mmio_ctx.last_index;
+    uint32_t read_value = abs_mmio_read32(sec_mmio_ctx.addrs[offset]);
+    if ((read_value ^ kSecMmioMaskVal) != sec_mmio_ctx.values[offset]) {
+      sec_mmio_shutdown_cb();
+      __builtin_unreachable();
+    }
+  }
+  // Check for loop completion.
+  if (i != sec_mmio_ctx.last_index) {
+    sec_mmio_shutdown_cb();
+    __builtin_unreachable();
+  }
+  ++sec_mmio_ctx.check_count;
+}
+
+void sec_mmio_check_counters(uint32_t expected_check_count) {
+  // Generous use of volatile in critical variables to avoid compiler
+  // optimizations, and map "zero" to a value with good hamming weight
+  // and with a good hamming distance to "all-ones".
+  // TODO(#6610): Update based on implementation guidance.
+  static volatile const uint32_t kValZero = 0x3ca5965a;
+  static volatile const uint32_t kValOnes = 0xc35a69a5;
+
+  uint32_t result = kValZero ^ sec_mmio_ctx.write_count;
+  result ^= sec_mmio_ctx.expected_write_count;
+
+  // Check the expected write count. This is equivalent to
+  // sec_mmio_ctx.write_count == sec_mmio_ctx.expected_write_count
+  if (result != kValZero) {
+    sec_mmio_shutdown_cb();
+    __builtin_unreachable();
+  }
+
+  // Check the expected check counts. This is equivalent to
+  // sec_mmio_ctx.check_count == expected_check_count. This check is expected to
+  // fail if the previous check failed.
+  result ^= sec_mmio_ctx.check_count;
+  result ^= expected_check_count;
+  if (~result != kValOnes) {
+    sec_mmio_shutdown_cb();
+    __builtin_unreachable();
+  }
+  ++sec_mmio_ctx.check_count;
+}
diff --git a/sw/device/silicon_creator/lib/base/sec_mmio.h b/sw/device/silicon_creator/lib/base/sec_mmio.h
new file mode 100644
index 0000000..6a9aa51
--- /dev/null
+++ b/sw/device/silicon_creator/lib/base/sec_mmio.h
@@ -0,0 +1,197 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+
+#ifndef OPENTITAN_SW_DEVICE_SILICON_CREATOR_LIB_BASE_SEC_MMIO_H_
+#define OPENTITAN_SW_DEVICE_SILICON_CREATOR_LIB_BASE_SEC_MMIO_H_
+
+#include <stddef.h>
+#include <stdint.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * @file
+ * @brief Secure Memory-mapped IO functions, for volatile access.
+ *
+ * This module is responsible for tracking critical register values for an
+ * initialized context `sec_mmio_ctx_t`, and provides a mechanism to evaluate
+ * expectations and trigger shutdown escalation on fault detection.
+ *
+ * Initialization
+ *
+ * - `sec_mmio_init()`.
+ *
+ * Register writes
+ *
+ * - Perform a number (N) of calls to `sec_mmio_write32()`.
+ * - Increment the expected number of writes by N by calling
+ *   `sec_mmio_write_increment()`. This is done using a separate function call
+ *   to be able to detect skip instruciton faults on `sec_mmio_write32()`
+ *   calls.
+ *
+ * Register reads
+ *
+ * Use the `sec_mmio_read32()`.
+ *
+ * Expectation checks
+ *
+ * See the following:
+ *
+ * - `sec_mmio_check_values()`
+ * - `sec_mmio_check_counters()`
+ *
+ * Opens:
+ *
+ * - sec_mmio_ctx is currently defined as an extern to simplify testing. For the
+ *   actual target, we need to define a memory region to share the data with the
+ *   ROM_EXT.
+ * - Currently fault detection escalations are performed by calling a handler
+ *   that is registered at `sec_mmio_init()` call time. Need to determine if we
+ *   want to move to a mock_shutdown implementation, or if we want to refactor
+ *   the code to return error codes.
+ */
+
+enum {
+  /**
+   * Number of registers stored in the sec_mmio context.
+   *
+   * This value must be less than the `kSecMmioRndStep` in sec_mmio.c.
+   */
+  // TODO(#6609): Update size of expectations table.
+  kSecMmioRegFileSize = 100,
+};
+
+/**
+ * Working context.
+ *
+ * Contains list of expected register addresses and associated values, as well
+ * as expected counters.
+ */
+typedef struct sec_mmio_ctx {
+  /**
+   * List of expected register values.
+   */
+  uint32_t values[kSecMmioRegFileSize];
+
+  /**
+   * List of expected register addresses.
+   */
+  uint32_t addrs[kSecMmioRegFileSize];
+
+  /**
+   * Represents the expected number of register values.
+   */
+  uint32_t last_index;
+  /**
+   * Represents the number of register write operations. Incremented by the
+   * `sec_mmio_write32()` function.
+   */
+  uint32_t write_count;
+  /**
+   * Represents the expected number of register write operations. Incremented by
+   * the `sec_mmio_write_increment()` function.
+   */
+  uint32_t expected_write_count;
+  /**
+   * Represents the number of times the check functions have been called.
+   * Incremented by the `sec_mmio_check_values()` and the
+   * `sec_mmio_check_counters()` functions.
+   */
+  uint32_t check_count;
+} sec_mmio_ctx_t;
+
+/**
+ * Shutdown module callback handler.
+ */
+typedef void (*sec_mmio_shutdown_handler)(void);
+
+/**
+ * Initializes the module.
+ *
+ * Registers the `cb` callback handler and initializes the internal
+ * `sec_mmio_ctx_t` context.
+ *
+ * @param cb Shutdown module callback handler.
+ */
+void sec_mmio_init(sec_mmio_shutdown_handler cb);
+
+/**
+ * Reads an aligned uint32_t from the MMIO region `addr`.
+ *
+ * This function implements a read-read-comparison operation. The first read
+ * is stored in the list of expected register values for later comparison
+ * via `sec_mmio_check_values()`.
+ *
+ * A shutdown sequence is initiated if the comparison operation fails.
+ *
+ * @param addr The address to read from.
+ * @return the read value.
+ */
+uint32_t sec_mmio_read32(uint32_t addr);
+
+/**
+ * Writes an aligned uint32_t to the MMIO region `base` at the give byte
+ * `offset`.
+ *
+ * This function implements a write-read-comparison operation. The first write
+ * value is stored in the list of expected register values for later comparison
+ * via `sec_mmio_check_values()`.
+ *
+ * On successful calls, this function will increment the internal count of
+ * writes. The caller is responsible to setting the expected write count by
+ * calling `sec_mmio_write_increment()`.
+ *
+ * A shutdown sequence is initiated if the comparison operation fails.
+ *
+ * @param addr The address to write to.
+ * @param value The value to write.
+ */
+void sec_mmio_write32(uint32_t addr, uint32_t value);
+
+/**
+ * Increment the expected count of register writes by `value`.
+ *
+ * @param value The expected write count increment.
+ */
+void sec_mmio_write_increment(uint32_t value);
+
+/**
+ * Checks the expected list of register values.
+ *
+ * All expected register values are verified against expectations. A shutdown
+ * sequence is initiated if any of the comparison fails.
+ *
+ * The `rnd_offset` parameter can be set to a random value to randomize the
+ * order of reads.
+ *
+ * Calling this function will increment the check function counter on a
+ * successful call.
+ *
+ * The `rnd_offset` parameter can be generated by calling the entropy source or
+ * the CSRNG driver.
+ *
+ * @param rnd_offset A random value used to generate a random read sequence.
+ */
+void sec_mmio_check_values(uint32_t rnd_offset);
+
+/**
+ * Checks the expected counter state.
+ *
+ * Checks the expected number of register writes and check counts. A shutdown
+ * sequence is initiated if the counters fail to match expectations.
+ *
+ * Calling this function will increment the check function counter on a
+ * successful
+ *
+ * @param expected_check_count The expected check counter.
+ */
+void sec_mmio_check_counters(uint32_t expected_check_count);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif  // OPENTITAN_SW_DEVICE_SILICON_CREATOR_LIB_BASE_SEC_MMIO_H_
diff --git a/sw/device/silicon_creator/lib/base/sec_mmio_unittest.cc b/sw/device/silicon_creator/lib/base/sec_mmio_unittest.cc
new file mode 100644
index 0000000..10ede39
--- /dev/null
+++ b/sw/device/silicon_creator/lib/base/sec_mmio_unittest.cc
@@ -0,0 +1,192 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+
+#include "sw/device/silicon_creator/lib/base/sec_mmio.h"
+
+#include <array>
+#include <cstdlib>
+#include <vector>
+
+#include "gtest/gtest.h"
+#include "sw/device/silicon_creator/lib/base/mock_abs_mmio.h"
+
+extern "C" {
+// This is an extern in the sec_mmio module.
+sec_mmio_ctx_t sec_mmio_ctx;
+}
+
+namespace sec_mmio_unittest {
+namespace {
+using ::testing::Each;
+using ::testing::Eq;
+using ::testing::Test;
+
+class SecMmioTest : public mask_rom_test::MaskRomTest {
+ protected:
+  void SetUp() override {
+    sec_mmio_init(+[] { std::abort(); });
+  }
+  sec_mmio_ctx_t *ctx_ = &::sec_mmio_ctx;
+  mask_rom_test::MockAbsMmio mmio_;
+};
+
+TEST_F(SecMmioTest, Initialize) {
+  // Write non-zero values to critical fields before calling `sec_mmio_init()`.
+  ctx_->check_count = 1;
+  ctx_->expected_write_count = 1;
+  ctx_->last_index = 1;
+  ctx_->write_count = 1;
+  ctx_->addrs[0] = 0;
+  sec_mmio_init(+[] { std::abort(); });
+
+  EXPECT_EQ(ctx_->check_count, 0);
+  EXPECT_EQ(ctx_->expected_write_count, 0);
+  EXPECT_EQ(ctx_->last_index, 0);
+  EXPECT_EQ(ctx_->write_count, 0);
+  EXPECT_THAT(ctx_->addrs, Each(Eq(UINT32_MAX)));
+}
+
+TEST_F(SecMmioTest, Read32OrDie) {
+  EXPECT_ABS_READ32(mmio_, 0, 0x12345678);
+  EXPECT_ABS_READ32(mmio_, 0, 0x12345678);
+  EXPECT_EQ(sec_mmio_read32(0), 0x12345678);
+
+  EXPECT_ABS_READ32(mmio_, 4, 0x87654321);
+  EXPECT_ABS_READ32(mmio_, 4, 0x87654321);
+  EXPECT_EQ(sec_mmio_read32(4), 0x87654321);
+
+  EXPECT_ABS_READ32(mmio_, 0, 0x87654321);
+  EXPECT_ABS_READ32(mmio_, 0, 0x87654321);
+  EXPECT_EQ(sec_mmio_read32(0), 0x87654321);
+
+  // Two of the operations were targeting the same offset, so we only expect two
+  // operations and zero shutdown attempts.
+  EXPECT_EQ(ctx_->write_count, 0);
+  EXPECT_EQ(ctx_->last_index, 2);
+}
+
+TEST_F(SecMmioTest, Write32) {
+  EXPECT_ABS_WRITE32(mmio_, 0, 0x12345678);
+  EXPECT_ABS_READ32(mmio_, 0, 0x12345678);
+  sec_mmio_write32(0, 0x12345678);
+  EXPECT_EQ(ctx_->write_count, 1);
+
+  EXPECT_ABS_WRITE32(mmio_, 4, 0x87654321);
+  EXPECT_ABS_READ32(mmio_, 4, 0x87654321);
+  sec_mmio_write32(4, 0x87654321);
+  EXPECT_EQ(ctx_->write_count, 2);
+
+  EXPECT_ABS_WRITE32(mmio_, 0, 0x87654321);
+  EXPECT_ABS_READ32(mmio_, 0, 0x87654321);
+  sec_mmio_write32(0, 0x87654321);
+  EXPECT_EQ(ctx_->write_count, 3);
+
+  // Two of the operations were targeting the same offset, so we only expect two
+  // operations.
+  EXPECT_EQ(ctx_->last_index, 2);
+}
+
+TEST_F(SecMmioTest, CounterInc) {
+  sec_mmio_write_increment(5);
+  EXPECT_EQ(ctx_->expected_write_count, 5);
+
+  sec_mmio_write_increment(10);
+  EXPECT_EQ(ctx_->expected_write_count, 15);
+}
+
+TEST_F(SecMmioTest, CheckValues) {
+  EXPECT_ABS_WRITE32(mmio_, 0, 0x12345678);
+  EXPECT_ABS_READ32(mmio_, 0, 0x12345678);
+  sec_mmio_write32(0, 0x12345678);
+
+  EXPECT_ABS_WRITE32(mmio_, 4, 0x87654321);
+  EXPECT_ABS_READ32(mmio_, 4, 0x87654321);
+  sec_mmio_write32(4, 0x87654321);
+
+  EXPECT_ABS_WRITE32(mmio_, 8, 0);
+  EXPECT_ABS_READ32(mmio_, 8, 0);
+  sec_mmio_write32(8, 0);
+
+  // The expected permutation order for rnd_offset=0 is {1, 2, 0}.
+  EXPECT_ABS_READ32(mmio_, 4, 0x87654321);
+  EXPECT_ABS_READ32(mmio_, 8, 0);
+  EXPECT_ABS_READ32(mmio_, 0, 0x12345678);
+  sec_mmio_check_values(/*rnd_offset=*/0);
+  EXPECT_EQ(ctx_->check_count, 1);
+
+  // The expected permutation order for rnd_offset=1 is {2, 0, 1}.
+  EXPECT_ABS_READ32(mmio_, 8, 0);
+  EXPECT_ABS_READ32(mmio_, 0, 0x12345678);
+  EXPECT_ABS_READ32(mmio_, 4, 0x87654321);
+  sec_mmio_check_values(/*rnd_offset=*/1);
+  EXPECT_EQ(ctx_->check_count, 2);
+
+  // The expected permutation order for rnd_offset=32 is {0, 1, 2}.
+  EXPECT_ABS_READ32(mmio_, 0, 0x12345678);
+  EXPECT_ABS_READ32(mmio_, 4, 0x87654321);
+  EXPECT_ABS_READ32(mmio_, 8, 0);
+  sec_mmio_check_values(/*rnd_offset=*/32);
+  EXPECT_EQ(ctx_->check_count, 3);
+}
+
+TEST_F(SecMmioTest, CheckCount) {
+  EXPECT_ABS_WRITE32(mmio_, 0, 0x12345678);
+  EXPECT_ABS_READ32(mmio_, 0, 0x12345678);
+  sec_mmio_write32(0, 0x12345678);
+  sec_mmio_write_increment(1);
+
+  sec_mmio_check_counters(/*expected_check_count=*/0);
+  sec_mmio_check_counters(/*expected_check_count=*/1);
+  EXPECT_EQ(ctx_->check_count, 2);
+}
+
+// Negative test cases trigger assertions, which are caugth by `ASSERT_DEATH`
+// calls. All test cases use lambda functions to wrap expectations and work
+// around issue google/googletest#1004.
+class SecMmioDeathTest : public SecMmioTest {};
+
+TEST_F(SecMmioDeathTest, Read32OrDieSimulatedFault) {
+  auto deadly_ops = [this] {
+    EXPECT_ABS_READ32(mmio_, 0, 0x12345678);
+    EXPECT_ABS_READ32(mmio_, 0, 0);
+    sec_mmio_read32(0);
+  };
+  ASSERT_DEATH(deadly_ops(), "");
+}
+
+TEST_F(SecMmioDeathTest, Write32SimulatedFault) {
+  auto deadly_ops = [this] {
+    EXPECT_ABS_WRITE32(mmio_, 0, 0x12345678);
+    EXPECT_ABS_READ32(mmio_, 0, 0);
+    sec_mmio_write32(0, 0x12345678);
+  };
+  ASSERT_DEATH(deadly_ops(), "");
+}
+
+TEST_F(SecMmioDeathTest, CheckValuesSimulatedFault) {
+  auto deadly_ops = [this] {
+    EXPECT_ABS_WRITE32(mmio_, 0, 0x12345678);
+    EXPECT_ABS_READ32(mmio_, 0, 0x12345678);
+    sec_mmio_write32(0, 0x12345678);
+
+    EXPECT_ABS_READ32(mmio_, 0, 0);
+    sec_mmio_check_values(/*rnd_offset=*/0);
+  };
+  ASSERT_DEATH(deadly_ops(), "");
+}
+
+TEST_F(SecMmioDeathTest, CheckCountWriteMismatch) {
+  auto deadly_ops = [this] {
+    EXPECT_ABS_WRITE32(mmio_, 0, 0x12345678);
+    EXPECT_ABS_READ32(mmio_, 0, 0x12345678);
+    sec_mmio_write32(0, 0x12345678);
+    sec_mmio_check_counters(/*expected_check_count=*/0);
+  };
+  // The developer forgot to increment the write counter, or an attacker
+  // glitched the sec write operation.
+  ASSERT_DEATH(deadly_ops(), "");
+}
+
+}  // namespace
+}  // namespace sec_mmio_unittest