Adds a model of the Ibex HW invoker.
PiperOrigin-RevId: 709106784
Change-Id: Ie7d0ebf1156a0fa805908313f14b5ae8205986e0
diff --git a/cheriot/BUILD b/cheriot/BUILD
index 6bb599d..f4717c8 100644
--- a/cheriot/BUILD
+++ b/cheriot/BUILD
@@ -639,6 +639,27 @@
)
cc_library(
+ name = "cheriot_ibex_hw_revoker",
+ srcs = [
+ "cheriot_ibex_hw_revoker.cc",
+ ],
+ hdrs = [
+ "cheriot_ibex_hw_revoker.h",
+ ],
+ deps = [
+ ":cheriot_state",
+ "@com_google_absl//absl/log",
+ "@com_google_absl//absl/log:check",
+ "@com_google_absl//absl/strings",
+ "@com_google_mpact-riscv//riscv:riscv_plic",
+ "@com_google_mpact-sim//mpact/sim/generic:core",
+ "@com_google_mpact-sim//mpact/sim/generic:counters",
+ "@com_google_mpact-sim//mpact/sim/generic:instruction",
+ "@com_google_mpact-sim//mpact/sim/util/memory",
+ ],
+)
+
+cc_library(
name = "instrumentation",
srcs = [
"cheriot_instrumentation_control.cc",
diff --git a/cheriot/cheriot_ibex_hw_revoker.cc b/cheriot/cheriot_ibex_hw_revoker.cc
new file mode 100644
index 0000000..4df1238
--- /dev/null
+++ b/cheriot/cheriot_ibex_hw_revoker.cc
@@ -0,0 +1,324 @@
+// Copyright 2024 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "cheriot/cheriot_ibex_hw_revoker.h"
+
+#include <cstdint>
+#include <cstring>
+
+#include "absl/log/log.h"
+#include "cheriot/cheriot_register.h"
+#include "mpact/sim/generic/data_buffer.h"
+#include "mpact/sim/generic/instruction.h"
+#include "mpact/sim/generic/ref_count.h"
+#include "mpact/sim/util/memory/memory_interface.h"
+#include "mpact/sim/util/memory/tagged_memory_interface.h"
+#include "riscv//riscv_plic.h"
+
+namespace mpact {
+namespace sim {
+namespace cheriot {
+
+using ::mpact::sim::generic::DataBuffer;
+using ::mpact::sim::generic::Instruction;
+using ::mpact::sim::generic::ReferenceCount;
+using ::mpact::sim::riscv::RiscVPlicIrqInterface;
+using ::mpact::sim::util::MemoryInterface;
+using ::mpact::sim::util::TaggedMemoryInterface;
+
+CheriotIbexHWRevoker::CheriotIbexHWRevoker(RiscVPlicIrqInterface *plic_irq,
+ uint64_t heap_base,
+ uint64_t heap_size,
+ TaggedMemoryInterface *heap_memory,
+ uint64_t revocation_bits_base,
+ MemoryInterface *revocation_memory)
+ : plic_irq_(plic_irq),
+ heap_base_(heap_base),
+ heap_max_(heap_base + heap_size),
+ heap_memory_(heap_memory),
+ revocation_memory_(revocation_memory),
+ revocation_bits_base_(revocation_bits_base) {
+ cap_reg_ = new CheriotRegister(nullptr, "filter_cap");
+ db_ = db_factory_.Allocate<uint32_t>(2);
+ db_->Set<uint32_t>(0, 0);
+ db_->Set<uint32_t>(1, 0);
+ db_->set_latency(0);
+ cap_reg_->SetDataBuffer(db_);
+ db_->DecRef();
+ // Allocate data buffers used in loads/stores.
+ db_ = db_factory_.Allocate<uint8_t>(CheriotRegister::kCapabilitySizeInBytes);
+ db_->set_latency(0);
+ tag_db_ = db_factory_.Allocate<uint8_t>(1);
+ tag_db_->set_latency(0);
+ Reset();
+}
+
+CheriotIbexHWRevoker::CheriotIbexHWRevoker(uint64_t heap_base,
+ uint64_t heap_size,
+ TaggedMemoryInterface *heap_memory,
+ uint64_t revocation_bits_base,
+ MemoryInterface *revocation_memory)
+ : CheriotIbexHWRevoker(nullptr, heap_base, heap_size, heap_memory,
+ revocation_bits_base, revocation_memory) {}
+
+CheriotIbexHWRevoker::~CheriotIbexHWRevoker() {
+ delete cap_reg_;
+ db_->DecRef();
+ tag_db_->DecRef();
+}
+
+// Reset state to initial values.
+void CheriotIbexHWRevoker::Reset() {
+ num_calls_ = 0;
+ start_address_ = 0;
+ end_address_ = 0;
+ go_ = 0;
+ epoch_ = 0;
+ interrupt_enable_ = 0;
+ interrupt_status_ = 0;
+}
+
+// This is called by the counter using the CounterValueSetInterface interface.
+void CheriotIbexHWRevoker::SetValue(const uint64_t &val) {
+ if (interrupt_status_) SetInterrupt(false);
+ if (!sweep_in_progress_) return;
+ num_calls_++;
+ if (num_calls_ >= period_) {
+ num_calls_ = 0;
+ Revoke();
+ }
+}
+
+// Reads from the MMRs.
+void CheriotIbexHWRevoker::Load(uint64_t address, DataBuffer *db,
+ DataBuffer *tags, Instruction *inst,
+ ReferenceCount *context) {
+ if (tags != nullptr) std::memset(tags->raw_ptr(), 0, tags->size<uint8_t>());
+ Load(address, db, inst, context);
+}
+
+// Reads from the MMRs.
+void CheriotIbexHWRevoker::Load(uint64_t address, DataBuffer *db,
+ Instruction *inst, ReferenceCount *context) {
+ uint32_t offset = address & 0xffff;
+ switch (db->size<uint8_t>()) {
+ case 1:
+ db->Set<uint32_t>(0, static_cast<uint8_t>(Read(offset)));
+ break;
+ case 2:
+ db->Set<uint32_t>(0, static_cast<uint16_t>(Read(offset)));
+ break;
+ case 4:
+ db->Set<uint32_t>(0, static_cast<uint32_t>(Read(offset)));
+ break;
+ case 8:
+ db->Set<uint32_t>(0, static_cast<uint64_t>(Read(offset)));
+ break;
+ default:
+ ::memset(db->raw_ptr(), 0, sizeof(db->size<uint8_t>()));
+ break;
+ }
+ // Execute the instruction to process and write back the load data.
+ if (nullptr != inst) {
+ if (db->latency() > 0) {
+ inst->IncRef();
+ if (context != nullptr) context->IncRef();
+ inst->state()->function_delay_line()->Add(db->latency(),
+ [inst, context]() {
+ inst->Execute(context);
+ if (context != nullptr)
+ context->DecRef();
+ inst->DecRef();
+ });
+ } else {
+ inst->Execute(context);
+ }
+ }
+}
+
+// Vector load is not supported.
+void CheriotIbexHWRevoker::Load(DataBuffer *address_db, DataBuffer *mask_db,
+ int el_size, DataBuffer *db, Instruction *inst,
+ ReferenceCount *context) {
+ // This is left unimplemented. Vector load is not supported.
+ LOG(FATAL) << "Vector load not supported"; // Crash OK
+}
+
+// Writes to the MMRs.
+void CheriotIbexHWRevoker::Store(uint64_t address, DataBuffer *db,
+ DataBuffer *tags) {
+ Store(address, db);
+}
+
+// Writes to the MMRs.
+void CheriotIbexHWRevoker::Store(uint64_t address, DataBuffer *db) {
+ uint32_t offset = address & 0xffff;
+ switch (db->size<uint8_t>()) {
+ case 1:
+ return Write(offset, static_cast<uint32_t>(db->Get<uint8_t>(0)));
+ case 2:
+ return Write(offset, static_cast<uint32_t>(db->Get<uint16_t>(0)));
+ case 4:
+ return Write(offset, static_cast<uint32_t>(db->Get<uint32_t>(0)));
+ case 8:
+ return Write(offset, static_cast<uint32_t>(db->Get<uint32_t>(0)));
+ return Write(offset + 4, static_cast<uint32_t>(db->Get<uint32_t>(1)));
+ default:
+ return;
+ }
+}
+
+// Vector accesses are not supported.
+void CheriotIbexHWRevoker::Store(DataBuffer *address, DataBuffer *mask,
+ int el_size, DataBuffer *db) {
+ // This is left unimplemented. Vector store is not supported.
+ LOG(FATAL) << "Vector store not supported"; // Crash OK
+}
+
+uint32_t CheriotIbexHWRevoker::Read(uint32_t offset) {
+ uint32_t value = 0;
+ switch (offset) {
+ case kStartAddressOffset: // start address
+ value = start_address_;
+ break;
+ case kEndAddressOffset: // end address
+ value = end_address_;
+ break;
+ case kGoOffset: // go
+ value = 0x5500'0000 | (go_ & 0x00ff'ffff);
+ break;
+ case kEpochOffset: // epoch
+ value = (epoch_ << 1) | (sweep_in_progress_ ? 0b1 : 0b0);
+ break;
+ case kStatusOffset: // stat q
+ value = interrupt_enable_ ? interrupt_status_ : 0;
+ break;
+ case kInterruptEnableOffset: // interrupt enable
+ value = interrupt_enable_ & 0b1;
+ break;
+ default:
+ value = 0;
+ break;
+ }
+ return value;
+}
+
+// Vector store is not supported.
+void CheriotIbexHWRevoker::Write(uint32_t offset, uint32_t value) {
+ switch (offset) {
+ case kStartAddressOffset: // start address
+ start_address_ = value;
+ break;
+ case kEndAddressOffset: // end address
+ end_address_ = value;
+ break;
+ case kGoOffset: // go
+ WriteGo();
+ go_ = value;
+ break;
+ // 0x000c: Epoch is not writable.
+ case kStatusOffset:
+ interrupt_status_ = 0;
+ plic_irq_->SetIrq(false);
+ break;
+ case kInterruptEnableOffset: // interrupt enable
+ interrupt_enable_ = value & 0b1;
+ ;
+ break;
+ default:
+ return;
+ }
+}
+
+void CheriotIbexHWRevoker::WriteGo() {
+ if (sweep_in_progress_) return;
+ sweep_in_progress_ = true;
+ current_cap_ = 0;
+ num_calls_ = 0;
+ epoch_ = 0;
+}
+
+void CheriotIbexHWRevoker::Revoke() {
+ if (!sweep_in_progress_) return;
+ // Increment the epoch.
+ epoch_++;
+ uint64_t cap_address = start_address_ + (current_cap_++ << 3);
+ // Align address to the capability size.
+ cap_address &= ~0b111ULL;
+ ProcessCapability(cap_address);
+ // Check to see if we have reached the end of the region.
+ if (cap_address >= end_address_) {
+ sweep_in_progress_ = false;
+ SetInterrupt(true);
+ }
+}
+
+// Process the capability at the given address.
+void CheriotIbexHWRevoker::ProcessCapability(uint64_t address) {
+ if ((address < start_address_) || (address >= end_address_)) return;
+ // Load the capability.
+ heap_memory_->Load(address, db_, tag_db_, nullptr, nullptr);
+ // If the tag is 0, no need to go on.
+ auto tag = tag_db_->Get<uint8_t>(0);
+ if (tag == 0) return;
+
+ // Expand the capability. Return if the tag is not valid.
+ cap_reg_->Expand(db_->Get<uint32_t>(0), db_->Get<uint32_t>(1), tag);
+ if (!cap_reg_->tag()) return;
+
+ // Check for revocation.
+ if (!MustRevoke(cap_reg_->base())) return;
+
+ // Invalidate and store the capability back to memory.
+ cap_reg_->Invalidate();
+ db_->Set<uint32_t>(0, cap_reg_->address());
+ db_->Set<uint32_t>(1, cap_reg_->Compress());
+ tag_db_->Set<uint8_t>(0, cap_reg_->tag());
+ heap_memory_->Store(address, db_, tag_db_);
+}
+
+// Check if the capability must be revoked.
+bool CheriotIbexHWRevoker::MustRevoke(uint64_t address) {
+ if (address < heap_base_) return false;
+ if (address >= heap_max_) return false;
+ // Compute the address of the byte containing the revocation information.
+ uint64_t offset = address - heap_base_;
+ // Shift by 3 for the size of the capability (8), and by 3 for 8 bits in a
+ // byte.
+ uint64_t revocation_offset = offset >> 6;
+ revocation_memory_->Load(revocation_bits_base_ + revocation_offset, tag_db_,
+ nullptr, nullptr);
+ // Get the revocation bit.
+ uint8_t revocation_bits = tag_db_->Get<uint8_t>(0);
+ int bit_offset = (offset >> 3) & 0b111;
+ bool result = revocation_bits & (1 << bit_offset);
+ return result;
+}
+
+void CheriotIbexHWRevoker::SetInterrupt(bool value) {
+ if (!value) {
+ plic_irq_->SetIrq(false);
+ interrupt_status_ = 0;
+ return;
+ }
+ interrupt_status_ = static_cast<uint32_t>(value);
+
+ if (!interrupt_enable_) return;
+
+ if (plic_irq_ != nullptr) plic_irq_->SetIrq(value);
+}
+
+} // namespace cheriot
+} // namespace sim
+} // namespace mpact
diff --git a/cheriot/cheriot_ibex_hw_revoker.h b/cheriot/cheriot_ibex_hw_revoker.h
new file mode 100644
index 0000000..2fd7f5d
--- /dev/null
+++ b/cheriot/cheriot_ibex_hw_revoker.h
@@ -0,0 +1,168 @@
+// Copyright 2024 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef MPACT_CHERIOT_CHERIOT_IBEX_HW_REVOKER_H_
+#define MPACT_CHERIOT_CHERIOT_IBEX_HW_REVOKER_H_
+
+#include <cstdint>
+
+#include "cheriot/cheriot_register.h"
+#include "mpact/sim/generic/counters_base.h"
+#include "mpact/sim/generic/data_buffer.h"
+#include "mpact/sim/generic/instruction.h"
+#include "mpact/sim/generic/ref_count.h"
+#include "mpact/sim/util/memory/memory_interface.h"
+#include "mpact/sim/util/memory/tagged_memory_interface.h"
+#include "riscv//riscv_plic.h"
+
+// This file contains the class declaration for the model of the Ibex HW
+// revoker for Cheriot. The HW revoker is a module that is used to invalidate
+// (or revoke the validity of) capabilities pointing to a freed portion of heap
+// memory. It is controlled by a set of memory mapped registers.
+//
+// The HW revoker is implemented as a counter value set object. It is bound
+// to a counter that is incremented whenever an instruction is executed, and,
+// when active, performs an action every 'period' number of increments
+// (configurable).
+//
+// The HW revoker is programmed using a memory interface. It supports non-vector
+// loads and stores only.
+//
+// The HW revoker is described in more detail in the following documents:
+// https://lowrisc.github.io/sonata-system/doc/ip/revoker.html
+// https://github.com/microsoft/cheriot-safe/blob/main/src/msft_cheri_subsystem/msftDvIp_mmreg.sv
+
+namespace mpact {
+namespace sim {
+namespace cheriot {
+
+using ::mpact::sim::generic::CounterValueSetInterface;
+using ::mpact::sim::generic::DataBuffer;
+using ::mpact::sim::generic::DataBufferFactory;
+using ::mpact::sim::generic::Instruction;
+using ::mpact::sim::generic::ReferenceCount;
+using ::mpact::sim::riscv::RiscVPlic;
+using ::mpact::sim::riscv::RiscVPlicIrqInterface;
+using ::mpact::sim::util::MemoryInterface;
+using ::mpact::sim::util::TaggedMemoryInterface;
+
+class CheriotIbexHWRevoker : public CounterValueSetInterface<uint64_t>,
+ public TaggedMemoryInterface {
+ public:
+ static constexpr uint32_t kStartAddressOffset = 0x0000;
+ static constexpr uint32_t kEndAddressOffset = 0x0004;
+ static constexpr uint32_t kGoOffset = 0x0008;
+ static constexpr uint32_t kEpochOffset = 0x000c;
+ static constexpr uint32_t kStatusOffset = 0x0010;
+ static constexpr uint32_t kInterruptEnableOffset = 0x0014;
+
+ CheriotIbexHWRevoker() = delete;
+ CheriotIbexHWRevoker(const CheriotIbexHWRevoker &) = delete;
+ CheriotIbexHWRevoker &operator=(const CheriotIbexHWRevoker &) = delete;
+ CheriotIbexHWRevoker(RiscVPlicIrqInterface *plic_irq, uint64_t heap_base,
+ uint64_t heap_size, TaggedMemoryInterface *heap_memory,
+ uint64_t revocation_bits_base,
+ MemoryInterface *revocation_memory);
+ CheriotIbexHWRevoker(uint64_t heap_base, uint64_t heap_size,
+ TaggedMemoryInterface *heap_memory,
+ uint64_t revocation_bits_base,
+ MemoryInterface *revocation_memory);
+ ~CheriotIbexHWRevoker();
+ // Resets the interrupt controller.
+ void Reset();
+ // CounterValueSetInterface override. This is called when the value of the
+ // bound counter is modified.
+ void SetValue(const uint64_t &val) override;
+
+ // MemoryInterface overrides.
+ // Non-vector load method.
+ void Load(uint64_t address, DataBuffer *db, DataBuffer *tags,
+ Instruction *inst, ReferenceCount *context) override;
+ void Load(uint64_t address, DataBuffer *db, Instruction *inst,
+ ReferenceCount *context) override;
+ // Vector load method - this is stubbed out.
+ void Load(DataBuffer *address_db, DataBuffer *mask_db, int el_size,
+ DataBuffer *db, Instruction *inst,
+ ReferenceCount *context) override;
+ // Non-vector store method.
+ void Store(uint64_t address, DataBuffer *db, DataBuffer *tags) override;
+ void Store(uint64_t address, DataBuffer *dbs) override;
+ // Vector store method - this is stubbed out.
+ void Store(DataBuffer *address, DataBuffer *mask, int el_size,
+ DataBuffer *db) override;
+
+ // Getters & setters.
+ void set_plic_irq(RiscVPlicIrqInterface *plic_irq) { plic_irq_ = plic_irq; }
+ int period() const { return period_; }
+ void set_period(int period) { period_ = period; }
+ int cap_count() const { return cap_count_; }
+ void set_cap_count(int cap_count) { cap_count_ = cap_count; }
+ uint64_t revocation_bits_base() const { return revocation_bits_base_; }
+ void set_revocation_bits_base(uint64_t revocation_bits_base) {
+ revocation_bits_base_ = revocation_bits_base;
+ }
+
+ private:
+ // MMR read/write methods.
+ uint32_t Read(uint32_t offset);
+ void Write(uint32_t offset, uint32_t value);
+ void WriteGo();
+ // Perform an iteration of revocation.
+ void Revoke();
+ void ProcessCapability(uint64_t address);
+ bool MustRevoke(uint64_t address);
+ void SetInterrupt(bool value);
+ // The number of times SetValue is called before triggering a revocation
+ // operation.
+ int period_ = 1;
+ // Tracker for the number of times SetValue is called.
+ int num_calls_ = 0;
+ // The max number of capabilities to revoke in a single operation.
+ int cap_count_ = 0;
+ // Current capability index.
+ uint64_t current_cap_ = 0;
+ // Sweep in progress.
+ bool sweep_in_progress_ = false;
+ RiscVPlicIrqInterface *plic_irq_ = nullptr;
+ // Heap range.
+ uint64_t heap_base_ = 0;
+ uint64_t heap_max_ = 0;
+ // Memory interface to use for the tagged heap.
+ TaggedMemoryInterface *heap_memory_ = nullptr;
+ // Memory interface to use for the revocation bits.
+ MemoryInterface *revocation_memory_ = nullptr;
+ // Data buffers.
+ DataBuffer *db_ = nullptr;
+ DataBuffer *tag_db_ = nullptr;
+ // Capability register.
+ CheriotRegister *cap_reg_ = nullptr;
+ // Base address of the revocation bits.
+ uint64_t revocation_bits_base_ = 0;
+ // DB factory.
+ DataBufferFactory db_factory_;
+
+ // MMRs
+ uint64_t start_address_ = 0;
+ uint64_t end_address_ = 0;
+ uint32_t go_ = 0;
+ uint32_t epoch_ = 0;
+ uint32_t interrupt_enable_ = 0;
+ uint32_t interrupt_status_ = 0;
+};
+
+} // namespace cheriot
+} // namespace sim
+} // namespace mpact
+
+#endif // MPACT_CHERIOT_CHERIOT_IBEX_HW_REVOKER_H_
diff --git a/cheriot/cheriot_load_filter.cc b/cheriot/cheriot_load_filter.cc
index 8ef1ca9..f7f3439 100644
--- a/cheriot/cheriot_load_filter.cc
+++ b/cheriot/cheriot_load_filter.cc
@@ -89,7 +89,7 @@
db_->Set<uint32_t>(0, cap_reg_->address());
db_->Set<uint32_t>(1, cap_reg_->Compress());
tag_db_->Set<uint8_t>(0, cap_reg_->tag());
- tagged_memory_->Store(cap_address_, db_, tag_db_);
+ tagged_memory_->Store(address, db_, tag_db_);
}
// Check if the capability must be revoked.
diff --git a/cheriot/test/BUILD b/cheriot/test/BUILD
index 4ba3cbb..5cbf103 100644
--- a/cheriot/test/BUILD
+++ b/cheriot/test/BUILD
@@ -541,3 +541,20 @@
"@com_google_mpact-sim//mpact/sim/generic:instruction",
],
)
+
+cc_test(
+ name = "cheriot_ibex_hw_revoker_test",
+ size = "small",
+ srcs = [
+ "cheriot_ibex_hw_revoker_test.cc",
+ ],
+ deps = [
+ "//cheriot:cheriot_ibex_hw_revoker",
+ "//cheriot:cheriot_state",
+ "@com_google_googletest//:gtest_main",
+ "@com_google_mpact-riscv//riscv:riscv_plic",
+ "@com_google_mpact-sim//mpact/sim/generic:core",
+ "@com_google_mpact-sim//mpact/sim/generic:instruction",
+ "@com_google_mpact-sim//mpact/sim/util/memory",
+ ],
+)
diff --git a/cheriot/test/cheriot_ibex_hw_revoker_test.cc b/cheriot/test/cheriot_ibex_hw_revoker_test.cc
new file mode 100644
index 0000000..cb5c7f6
--- /dev/null
+++ b/cheriot/test/cheriot_ibex_hw_revoker_test.cc
@@ -0,0 +1,357 @@
+// Copyright 2024 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "cheriot/cheriot_ibex_hw_revoker.h"
+
+#include <sys/types.h>
+
+#include <cstdint>
+#include <cstring>
+
+#include "cheriot/cheriot_register.h"
+#include "googlemock/include/gmock/gmock.h"
+#include "mpact/sim/generic/data_buffer.h"
+#include "mpact/sim/generic/instruction.h"
+#include "mpact/sim/util/memory/flat_demand_memory.h"
+#include "mpact/sim/util/memory/tagged_flat_demand_memory.h"
+#include "mpact/sim/util/memory/tagged_memory_interface.h"
+#include "riscv//riscv_plic.h"
+
+// This file contains unit tests for the CheriotIbexHWRevoker class.
+
+namespace {
+
+using ::mpact::sim::cheriot::CheriotIbexHWRevoker;
+using ::mpact::sim::cheriot::CheriotRegister;
+using ::mpact::sim::generic::DataBuffer;
+using ::mpact::sim::generic::DataBufferFactory;
+using ::mpact::sim::generic::Instruction;
+using ::mpact::sim::generic::ReferenceCount;
+using ::mpact::sim::riscv::RiscVPlicIrqInterface;
+using ::mpact::sim::util::FlatDemandMemory;
+using ::mpact::sim::util::TaggedFlatDemandMemory;
+using ::mpact::sim::util::TaggedMemoryInterface;
+
+constexpr uint64_t kRevocationBase = 0x200'0000;
+constexpr uint64_t kHeapBase = 0x8001'0000;
+constexpr uint64_t kSweepBase = 0x8000'0000;
+
+// Mock plic source interface.
+class MockPlicSource : public RiscVPlicIrqInterface {
+ public:
+ MockPlicSource() = default;
+ ~MockPlicSource() override = default;
+ void SetIrq(bool irq_value) override { irq_value_ = irq_value; }
+
+ bool irq_value() const { return irq_value_; }
+ void set_irq_value(bool value) { irq_value_ = value; }
+
+ private:
+ bool irq_value_ = false;
+};
+
+// This class is used to capture memory load/store addresses.
+class MemoryViewer : public TaggedMemoryInterface {
+ public:
+ MemoryViewer() = delete;
+ MemoryViewer(TaggedMemoryInterface *memory) : memory_(memory) {}
+ ~MemoryViewer() override = default;
+
+ void Load(uint64_t address, DataBuffer *db, DataBuffer *tags,
+ Instruction *inst, ReferenceCount *context) override {
+ ld_address_ = address;
+ memory_->Load(address, db, tags, inst, context);
+ }
+ void Load(uint64_t address, DataBuffer *db, Instruction *inst,
+ ReferenceCount *context) override {
+ ld_address_ = address;
+ memory_->Load(address, db, inst, context);
+ }
+ void Load(DataBuffer *address_db, DataBuffer *mask_db, int el_size,
+ DataBuffer *db, Instruction *inst,
+ ReferenceCount *context) override {
+ ld_address_ = address_db->Get<uint64_t>(0);
+ memory_->Load(address_db, mask_db, el_size, db, inst, context);
+ }
+ void Store(uint64_t address, DataBuffer *db, DataBuffer *tags) override {
+ st_address_ = address;
+ memory_->Store(address, db, tags);
+ }
+ void Store(uint64_t address, DataBuffer *db) override {
+ st_address_ = address;
+ memory_->Store(address, db);
+ }
+ void Store(DataBuffer *address_db, DataBuffer *mask_db, int el_size,
+ DataBuffer *db) override {
+ st_address_ = address_db->Get<uint64_t>(0);
+ memory_->Store(address_db, mask_db, el_size, db);
+ }
+
+ uint64_t ld_address() const { return ld_address_; }
+ uint64_t st_address() const { return st_address_; }
+
+ private:
+ TaggedMemoryInterface *memory_ = nullptr;
+ uint64_t ld_address_ = 0;
+ uint64_t st_address_ = 0;
+};
+
+class CheriotIbexHwRevokerTest : public ::testing::Test {
+ protected:
+ CheriotIbexHwRevokerTest() {
+ db1_ = db_factory_.Allocate<uint8_t>(1);
+ db4_ = db_factory_.Allocate<uint32_t>(1);
+ db8_ = db_factory_.Allocate<uint32_t>(2);
+ db128_ = db_factory_.Allocate<uint8_t>(128);
+ plic_irq_ = new MockPlicSource();
+ heap_memory_ = new TaggedFlatDemandMemory(8);
+ memory_viewer_ = new MemoryViewer(heap_memory_);
+ revocation_memory_ = new FlatDemandMemory();
+ revoker_ =
+ new CheriotIbexHWRevoker(plic_irq_, kHeapBase, 0x8000, memory_viewer_,
+ kRevocationBase, revocation_memory_);
+ cap_reg_ = new CheriotRegister(nullptr, "cap");
+ cap_db_ = db_factory_.Allocate<uint32_t>(1);
+ cap_reg_->SetDataBuffer(cap_db_);
+ }
+
+ ~CheriotIbexHwRevokerTest() override {
+ db1_->DecRef();
+ db4_->DecRef();
+ db8_->DecRef();
+ db128_->DecRef();
+ cap_db_->DecRef();
+ delete plic_irq_;
+ delete revoker_;
+ delete heap_memory_;
+ delete memory_viewer_;
+ delete revocation_memory_;
+ delete cap_reg_;
+ }
+
+ // Call to advance the revoker.
+ void AdvanceRevoker() { revoker_->SetValue(0); }
+
+ // Convenience method to set the revocation bit for the given address.
+ void RevokeAddress(uint64_t address) {
+ if (address < kHeapBase) return;
+ uint64_t offset = address - kHeapBase;
+ offset >>= 3;
+ auto bit = offset & 0x7;
+ offset >>= 3;
+ revocation_memory_->Load(kRevocationBase + offset, db1_, nullptr, nullptr);
+ uint8_t val = db1_->Get<uint8_t>(0);
+ val |= 1 << bit;
+ db1_->Set<uint8_t>(0, val);
+ revocation_memory_->Store(kRevocationBase + offset, db1_);
+ }
+
+ // This clears the revocation bits for the memory range [kHeapBase, kHeapBase
+ // + 0x8000].
+ void ClearRevocationBits() {
+ std::memset(db128_->raw_ptr(), 0, db128_->size<uint8_t>());
+ uint64_t address = kRevocationBase;
+ for (uint64_t i = 0; i < 0x100; ++i) {
+ revocation_memory_->Store(address, db128_);
+ address += db128_->size<uint8_t>();
+ }
+ }
+
+ // The following methods are convenience methods for accessing the MMRs of
+ // the hw revoker using the revoker's memory interface.
+ void SetStartAddress(uint32_t address) {
+ db4_->Set<uint32_t>(0, address);
+ revoker_->Store(CheriotIbexHWRevoker::kStartAddressOffset, db4_);
+ }
+ uint32_t GetStartAddress() {
+ revoker_->Load(CheriotIbexHWRevoker::kStartAddressOffset, db4_, nullptr,
+ nullptr);
+ return db4_->Get<uint32_t>(0);
+ }
+ void SetEndAddress(uint32_t address) {
+ db4_->Set<uint32_t>(0, address);
+ revoker_->Store(CheriotIbexHWRevoker::kEndAddressOffset, db4_);
+ }
+ uint32_t GetEndAddress() {
+ revoker_->Load(CheriotIbexHWRevoker::kEndAddressOffset, db4_, nullptr,
+ nullptr);
+ return db4_->Get<uint32_t>(0);
+ }
+ void SetGo(uint32_t go) {
+ db4_->Set<uint32_t>(0, go);
+ revoker_->Store(CheriotIbexHWRevoker::kGoOffset, db4_);
+ }
+ uint32_t GetGo() {
+ revoker_->Load(CheriotIbexHWRevoker::kGoOffset, db4_, nullptr, nullptr);
+ return db4_->Get<uint32_t>(0);
+ }
+ void SetEpoch(uint32_t epoch) {
+ db4_->Set<uint32_t>(0, epoch);
+ revoker_->Store(CheriotIbexHWRevoker::kEpochOffset, db4_);
+ }
+ uint32_t GetEpoch() {
+ revoker_->Load(CheriotIbexHWRevoker::kEpochOffset, db4_, nullptr, nullptr);
+ return db4_->Get<uint32_t>(0);
+ }
+ void SetStatus(uint32_t status) {
+ db4_->Set<uint32_t>(0, status);
+ revoker_->Store(CheriotIbexHWRevoker::kStatusOffset, db4_);
+ }
+ uint32_t GetStatus() {
+ revoker_->Load(CheriotIbexHWRevoker::kStatusOffset, db4_, nullptr, nullptr);
+ return db4_->Get<uint32_t>(0);
+ }
+ void SetInterruptEnable(uint32_t enable) {
+ db4_->Set<uint32_t>(0, enable);
+ revoker_->Store(CheriotIbexHWRevoker::kInterruptEnableOffset, db4_);
+ }
+ uint32_t GetInterruptEnable() {
+ revoker_->Load(CheriotIbexHWRevoker::kInterruptEnableOffset, db4_, nullptr,
+ nullptr);
+ return db4_->Get<uint32_t>(0);
+ }
+
+ // Convenience method to write a valid capability to memory with the given
+ // base.
+ void WriteCapability(uint64_t address, uint64_t base) {
+ cap_reg_->ResetMemoryRoot();
+ cap_reg_->SetAddress(base);
+ cap_reg_->SetBounds(base, 0x10);
+ db8_->Set<uint32_t>(0, cap_reg_->address());
+ db8_->Set<uint32_t>(1, cap_reg_->Compress());
+ db1_->Set<uint8_t>(0, true);
+ heap_memory_->Store(address, db8_, db1_);
+ }
+
+ // Convenience method to read a capability from memory with the given base.
+ CheriotRegister *ReadCapability(uint64_t address) {
+ heap_memory_->Load(address, db8_, db1_, nullptr, nullptr);
+ cap_reg_->Expand(db8_->Get<uint32_t>(0), db8_->Get<uint32_t>(1),
+ db1_->Get<uint8_t>(0));
+ return cap_reg_;
+ }
+
+ uint64_t GetLoadAddress() { return memory_viewer_->ld_address(); }
+ uint64_t GetStoreAddress() { return memory_viewer_->st_address(); }
+ MockPlicSource *plic_irq() { return plic_irq_; }
+
+ private:
+ CheriotRegister *cap_reg_ = nullptr;
+ DataBufferFactory db_factory_;
+ DataBuffer *db1_;
+ DataBuffer *db4_;
+ DataBuffer *db8_;
+ DataBuffer *db128_;
+ DataBuffer *cap_db_;
+ CheriotIbexHWRevoker *revoker_ = nullptr;
+ MockPlicSource *plic_irq_ = nullptr;
+ TaggedFlatDemandMemory *heap_memory_ = nullptr;
+ FlatDemandMemory *revocation_memory_ = nullptr;
+ MemoryViewer *memory_viewer_ = nullptr;
+};
+
+// Initial state should all be clear.
+TEST_F(CheriotIbexHwRevokerTest, TestInitial) {
+ EXPECT_EQ(GetStartAddress(), 0);
+ EXPECT_EQ(GetEndAddress(), 0);
+ EXPECT_EQ(GetGo(), 0x5500'0000);
+ EXPECT_EQ(GetEpoch(), 0);
+ EXPECT_EQ(GetStatus(), 0);
+ EXPECT_EQ(GetInterruptEnable(), 0);
+}
+
+// No valid capabilities in the sweep range.
+TEST_F(CheriotIbexHwRevokerTest, RevokeNone) {
+ SetStartAddress(kSweepBase);
+ SetEndAddress(kSweepBase + 0x100);
+ SetGo(1);
+ EXPECT_EQ(GetGo(), 0x5500'0001);
+ // Expect zero status.
+ EXPECT_EQ(GetStatus(), 0);
+ // Expect sweep to be started.
+ EXPECT_EQ(GetEpoch(), 1);
+ // Step through 256/8 - 1 capabilities.
+ int num = 0x100 / 8;
+ for (int i = 0; i < num; ++i) {
+ AdvanceRevoker();
+ EXPECT_EQ(GetLoadAddress(), kSweepBase + (i << 3));
+ EXPECT_EQ(GetEpoch(), ((i + 1) << 1) | 1);
+ EXPECT_EQ(GetStatus(), 0);
+ }
+ // Step through the next capability. The sweep should be done.
+ AdvanceRevoker();
+ // Notice the in progress bit is cleared.
+ EXPECT_EQ(GetEpoch(), ((num + 1) << 1) | 0);
+ // Interrupt status should be 0, as interrupt enable is off.
+ EXPECT_EQ(GetStatus(), 0);
+}
+
+TEST_F(CheriotIbexHwRevokerTest, RevokeOne) {
+ // Write a capability at the sweep base.
+ for (auto offset = 0; offset < 0x100; offset += 0x8) {
+ WriteCapability(kSweepBase + offset, kHeapBase + offset);
+ auto *cap = ReadCapability(kSweepBase);
+ EXPECT_TRUE(cap->tag());
+ }
+ // Revoke one capability.
+ RevokeAddress(kHeapBase + 0x20);
+ // Set the sweep range to include the capability.
+ SetStartAddress(kSweepBase);
+ SetEndAddress(kSweepBase + 0x100);
+ SetGo(1);
+ // Expect zero status.
+ EXPECT_EQ(GetStatus(), 0);
+ // Expect sweep to be started.
+ EXPECT_EQ(GetEpoch(), 1);
+ // Step through the sweep.
+ while ((GetEpoch() & 0x1) == 1) {
+ AdvanceRevoker();
+ }
+ // Since interrupt enable is not set, the status should be zero.
+ EXPECT_EQ(GetStatus(), 0);
+ // Verify that only the one revoked capability was invalidated.
+ for (auto offset = 0; offset < 0x100; offset += 0x8) {
+ auto *cap = ReadCapability(kSweepBase + offset);
+ if (offset == 0x20) {
+ EXPECT_FALSE(cap->tag());
+ } else {
+ EXPECT_TRUE(cap->tag());
+ }
+ }
+}
+
+TEST_F(CheriotIbexHwRevokerTest, RevokeWithInterrupt) {
+ // Write a capability at the sweep base.
+ for (auto offset = 0; offset < 0x100; offset += 0x8) {
+ WriteCapability(kSweepBase + offset, kHeapBase + offset);
+ auto *cap = ReadCapability(kSweepBase);
+ EXPECT_TRUE(cap->tag());
+ }
+ // Revoke one capability.
+ RevokeAddress(kHeapBase + 0x20);
+ // Set the sweep range to include the capability.
+ SetStartAddress(kSweepBase);
+ SetEndAddress(kSweepBase + 0x100);
+ // Enable interrupt.
+ SetInterruptEnable(1);
+ SetGo(1);
+ while ((GetEpoch() & 0x1) == 1) {
+ AdvanceRevoker();
+ }
+ EXPECT_EQ(GetStatus(), 1);
+ // Verify that the interrupt was set.
+ EXPECT_TRUE(plic_irq()->irq_value());
+}
+
+} // namespace