blob: 46b4b2aba10cf7ccef56495f2bc1a3445c572cdb [file] [log] [blame] [edit]
// 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;
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_) {
// Increment the epoch.
epoch_++;
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