blob: 411e9b972a98040708621ee2ec8211c6c5b6508e [file] [log] [blame]
// 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/epmp.h"
#include <vector>
#include "gtest/gtest.h"
#include "sw/device/lib/base/bitfield.h"
#include "sw/device/lib/base/csr.h"
#include "sw/device/silicon_creator/lib/base/mock_csr.h"
#include "sw/device/silicon_creator/testing/mask_rom_test.h"
namespace epmp_unittest {
/**
* Representation of the hardware PMP control register state.
*/
struct PmpCsrs {
/**
* The unpacked PMP configuration (`pmpNcfg`) registers, one entry for each
* PMP entry.
*/
std::array<uint8_t, kEpmpNumRegions> pmpcfg{};
/**
* The PMP address registers, one for each PMP entry.
*/
std::array<uint32_t, kEpmpNumRegions> pmpaddr{};
/**
* The Machine Security Configuration register.
*/
uint64_t mseccfg = 0;
/**
* Pack the individual (`pmpNcfg`) PMP entry configuration register
* values into hardware register (`pmpcfgN`) values.
*
* @returns An array representing the packed `pmpcfgN` values.
*/
std::array<uint32_t, kEpmpNumRegions / 4> PackCfg() const {
// There are four 8-bit `pmpNcfg` values packed into each `pmpcfgN`
// register.
std::array<uint32_t, kEpmpNumRegions / 4> packed{};
for (int i = 0; i < pmpcfg.size(); ++i) {
bitfield_field32_t field = {.mask = 0xff,
.index = (static_cast<uint32_t>(i) % 4) * 8};
packed.at(i / 4) = bitfield_field32_write(
packed.at(i / 4), field, static_cast<uint32_t>(pmpcfg.at(i)));
}
return packed;
}
/**
* Generate a state that is a snapshot of the current PMP control register
* values.
*
* @returns An `epmp_state_t` value.
*/
epmp_state_t State() const {
epmp_state_t state;
auto packed = PackCfg();
for (int i = 0; i < packed.size(); ++i) {
state.pmpcfg[i] = packed.at(i);
}
for (int i = 0; i < pmpaddr.size(); ++i) {
state.pmpaddr[i] = pmpaddr.at(i);
}
state.mseccfg = static_cast<uint32_t>(mseccfg);
return state;
}
};
/**
* Map from PMP config register index to CSR_REG_PMPCFGN value.
*/
const std::vector<uint32_t> kPmpcfgMap = {
CSR_REG_PMPCFG0,
CSR_REG_PMPCFG1,
CSR_REG_PMPCFG2,
CSR_REG_PMPCFG3,
};
/**
* Map from PMP address register index to CSR_REG_PMPADDRN value.
*/
const std::vector<uint32_t> kPmpaddrMap = {
CSR_REG_PMPADDR0, CSR_REG_PMPADDR1, CSR_REG_PMPADDR2, CSR_REG_PMPADDR3,
CSR_REG_PMPADDR4, CSR_REG_PMPADDR5, CSR_REG_PMPADDR6, CSR_REG_PMPADDR7,
CSR_REG_PMPADDR8, CSR_REG_PMPADDR9, CSR_REG_PMPADDR10, CSR_REG_PMPADDR11,
CSR_REG_PMPADDR12, CSR_REG_PMPADDR13, CSR_REG_PMPADDR14, CSR_REG_PMPADDR15,
};
/**
* Helper function to call EXPECT_CSR_READ on all pmpcfg registers
* returning the values provided.
*/
static void ExpectPmpcfgRead(const PmpCsrs &expect) {
auto packed = expect.PackCfg();
for (int i = 0; i < packed.size(); ++i) {
EXPECT_CSR_READ(kPmpcfgMap.at(i), packed.at(i));
}
}
/**
* Helper function to call EXPECT_CSR_READ on all pmpaddr registers
* returning the values provided.
*/
static void ExpectPmpaddrRead(const PmpCsrs &expect) {
for (int i = 0; i < expect.pmpaddr.size(); ++i) {
EXPECT_CSR_READ(kPmpaddrMap.at(i), expect.pmpaddr.at(i));
}
}
/**
* Helper function to call EXPECT_CSR_READ on the mseccfg register
* returning the value provided.
*/
static void ExpectMseccfgRead(const PmpCsrs &expect) {
EXPECT_CSR_READ(CSR_REG_MSECCFG, static_cast<uint32_t>(expect.mseccfg));
EXPECT_CSR_READ(CSR_REG_MSECCFGH,
static_cast<uint32_t>(expect.mseccfg >> 32));
}
/**
* Helper function to call EXPECT_CSR_READ as necessary for an
* internal `read_state` call with the expected values provided.
*/
static void ExpectReadState(const PmpCsrs &expect) {
ExpectPmpaddrRead(expect);
ExpectPmpcfgRead(expect);
ExpectMseccfgRead(expect);
}
/**
* Encode a region using the NAPOT address mode. This does not
* do any validation of the values provided.
*
* Note: other address modes just require the address to be right
* shifted by 2.
*/
static uint32_t napot(epmp_region_t region) {
return (region.start >> 2) | ((region.end - region.start - 1) >> 3);
}
class EpmpTest : public mask_rom_test::MaskRomTest {
protected:
mock_csr::MockCsr csr_;
/**
* Default reset value for the PMP CSRs.
*/
const PmpCsrs reset_ = PmpCsrs{};
epmp_state_t state_ = reset_.State();
};
class EpmpCheckTest : public EpmpTest {};
TEST_F(EpmpCheckTest, Default) {
// Check CSRs are set as expected.
ExpectReadState(reset_);
EXPECT_EQ(epmp_state_check(&state_), kErrorOk);
}
TEST_F(EpmpCheckTest, ErrorPmpaddr) {
// Inject an error.
PmpCsrs bad = reset_;
bad.pmpaddr.at(15) ^= 1 << 31;
// Check should fail.
ExpectReadState(bad);
EXPECT_EQ(epmp_state_check(&state_), kErrorEpmpBadCheck);
}
TEST_F(EpmpCheckTest, ErrorPmpcfg) {
// Inject an error.
PmpCsrs bad = reset_;
bad.pmpcfg.at(0) ^= 1;
// Check should fail.
ExpectReadState(bad);
EXPECT_EQ(epmp_state_check(&state_), kErrorEpmpBadCheck);
}
TEST_F(EpmpCheckTest, ErrorMseccfg) {
// Inject an error.
PmpCsrs bad = reset_;
bad.mseccfg ^= 1;
// Check should fail.
ExpectReadState(bad);
EXPECT_EQ(epmp_state_check(&state_), kErrorEpmpBadCheck);
}
class EpmpTorTest : public EpmpTest {};
TEST_F(EpmpTorTest, Entry0) {
// Region for entry.
epmp_region_t region = {
.start = 0,
.end = 0,
};
// Configure registers for entry 0 (base address implicitly 0).
PmpCsrs csrs = reset_;
csrs.pmpcfg[0] = EPMP_CFG_L | EPMP_CFG_A_TOR | EPMP_CFG_R;
csrs.pmpaddr[0] = region.end >> 2;
// Configure same entry in state.
epmp_state_configure_tor(&state_, 0, region, kEpmpPermLockedReadOnly);
// Check CSRs match.
ExpectReadState(csrs);
EXPECT_EQ(epmp_state_check(&state_), kErrorOk);
}
TEST_F(EpmpTorTest, Entry1) {
// Region for entry.
epmp_region_t region = {
.start = 0x120,
.end = 0x140,
};
// Configure registers for entry 1, with base address in entry 0.
PmpCsrs csrs = reset_;
csrs.pmpaddr[0] = region.start >> 2;
csrs.pmpcfg[1] = EPMP_CFG_L | EPMP_CFG_A_TOR | EPMP_CFG_R;
csrs.pmpaddr[1] = region.end >> 2;
// Configure same entry in state.
epmp_state_configure_tor(&state_, 1, region, kEpmpPermLockedReadOnly);
// Check CSRs match.
ExpectReadState(csrs);
EXPECT_EQ(epmp_state_check(&state_), kErrorOk);
}
TEST_F(EpmpTorTest, Entry15) {
// Region for entry.
epmp_region_t region = {
.start = 0x120,
.end = 0x140,
};
// Configure registers for entry 15, with base address in entry 14.
PmpCsrs csrs = reset_;
csrs.pmpaddr[14] = region.start >> 2;
csrs.pmpcfg[15] = EPMP_CFG_L | EPMP_CFG_A_TOR | EPMP_CFG_R;
csrs.pmpaddr[15] = region.end >> 2;
// Configure same entry in state.
epmp_state_configure_tor(&state_, 15, region, kEpmpPermLockedReadOnly);
// Check CSRs match.
ExpectReadState(csrs);
EXPECT_EQ(epmp_state_check(&state_), kErrorOk);
}
TEST_F(EpmpTorTest, SharedAddress) {
// Adjacent regions for entries.
epmp_region_t region1 = {
.start = 0x120,
.end = 0x140,
};
epmp_region_t region2 = {
.start = 0x140,
.end = 0x180,
};
// Configure registers for entries 1 and 2, with base address in entry 0.
PmpCsrs csrs = reset_;
csrs.pmpaddr[0] = region1.start >> 2;
csrs.pmpcfg[1] = EPMP_CFG_L | EPMP_CFG_A_TOR | EPMP_CFG_R | EPMP_CFG_X;
csrs.pmpaddr[1] = region1.end >> 2;
csrs.pmpcfg[2] = EPMP_CFG_L | EPMP_CFG_A_TOR | EPMP_CFG_R;
csrs.pmpaddr[2] = region2.end >> 2;
// Configure same entry in state.
epmp_state_configure_tor(&state_, 1, region1, kEpmpPermLockedReadExecute);
epmp_state_configure_tor(&state_, 2, region2, kEpmpPermLockedReadOnly);
// Check CSRs match.
ExpectReadState(csrs);
EXPECT_EQ(epmp_state_check(&state_), kErrorOk);
}
class EpmpNa4Test : public EpmpTest {};
TEST_F(EpmpNa4Test, Entry0) {
// Region for entry.
epmp_region_t region = {
.start = 0xF4,
.end = 0xF8,
};
// Configure registers for entry 0.
PmpCsrs csrs = reset_;
csrs.pmpcfg[0] = EPMP_CFG_L | EPMP_CFG_A_NA4 | EPMP_CFG_R | EPMP_CFG_X;
csrs.pmpaddr[0] = region.start >> 2;
// Configure same entry in state.
epmp_state_configure_na4(&state_, 0, region, kEpmpPermLockedReadExecute);
// Check CSRs match.
ExpectReadState(csrs);
EXPECT_EQ(epmp_state_check(&state_), kErrorOk);
}
TEST_F(EpmpNa4Test, Entry15) {
// Region for entry.
epmp_region_t region = {
.start = 0x0,
.end = 0x4,
};
// Configure registers for entry 15.
PmpCsrs csrs = reset_;
csrs.pmpcfg[15] = EPMP_CFG_L | EPMP_CFG_A_NA4 | EPMP_CFG_R | EPMP_CFG_W;
csrs.pmpaddr[15] = region.start >> 2;
// Configure same entry in state.
epmp_state_configure_na4(&state_, 15, region, kEpmpPermLockedReadWrite);
// Check CSRs match.
ExpectReadState(csrs);
EXPECT_EQ(epmp_state_check(&state_), kErrorOk);
}
class EpmpNapotTest : public EpmpTest {};
TEST_F(EpmpNapotTest, Entry0) {
// Region for entry.
epmp_region_t region = {
.start = 0xFF00,
.end = 0xFF10,
};
// Configure registers for entry 0.
PmpCsrs csrs = reset_;
csrs.pmpcfg[0] = EPMP_CFG_L | EPMP_CFG_A_NAPOT | EPMP_CFG_R | EPMP_CFG_X;
csrs.pmpaddr[0] = napot(region);
// Configure same entry in state.
epmp_state_configure_napot(&state_, 0, region, kEpmpPermLockedReadExecute);
// Check CSRs match.
ExpectReadState(csrs);
EXPECT_EQ(epmp_state_check(&state_), kErrorOk);
}
TEST_F(EpmpNapotTest, Entry15) {
// Region for entry.
epmp_region_t region = {
.start = 0x0,
.end = 0x8,
};
// Configure registers for entry 15.
PmpCsrs csrs = reset_;
csrs.pmpcfg[15] = EPMP_CFG_L | EPMP_CFG_A_NAPOT | EPMP_CFG_R | EPMP_CFG_W;
csrs.pmpaddr[15] = napot(region);
// Configure same entry in state.
epmp_state_configure_napot(&state_, 15, region, kEpmpPermLockedReadWrite);
// Check CSRs match.
ExpectReadState(csrs);
EXPECT_EQ(epmp_state_check(&state_), kErrorOk);
}
} // namespace epmp_unittest