blob: 274eb2885b7bbbf6c793f453049bae5a2ae52968 [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/lib/base/macros.h"
#include "sw/device/lib/base/mmio.h"
#include "sw/device/lib/dif/dif_csrng.h"
#include "sw/device/lib/dif/dif_edn.h"
#include "sw/device/lib/dif/dif_entropy_src.h"
#include "sw/device/lib/dif/dif_otbn.h"
#include "sw/device/lib/runtime/irq.h"
#include "sw/device/lib/runtime/log.h"
#include "sw/device/lib/testing/csrng_testutils.h"
#include "sw/device/lib/testing/entropy_testutils.h"
#include "sw/device/lib/testing/otbn_testutils.h"
#include "sw/device/lib/testing/rand_testutils.h"
#include "sw/device/lib/testing/rv_plic_testutils.h"
#include "sw/device/lib/testing/test_framework/check.h"
#include "sw/device/lib/testing/test_framework/ottf_main.h"
#include "sw/device/tests/otbn_randomness_impl.h"
#include "hw/top_earlgrey/sw/autogen/top_earlgrey.h"
#include "sw/device/lib/testing/autogen/isr_testutils.h"
static dif_csrng_t csrng;
static dif_edn_t edn0;
static dif_edn_t edn1;
static dif_entropy_src_t entropy_src;
static dif_otbn_t otbn;
static dif_rv_plic_t plic;
OTTF_DEFINE_TEST_CONFIG();
enum {
/**
* The size of the buffer used in firmware to process the entropy bits in
* firmware override mode.
*/
kTestParamFifoBufferSize = 12,
/**
* The number of test iterations per target.
*/
kTestParamNumIterationsSim = 1,
kTestParamNumIterationsOther = 20,
};
/**
* Interrupt flag IDs. Used to index the interrupt flags used in this test.
*/
typedef enum irq_flag_id {
kTestIrqFlagIdCsrngEntropyReq,
kTestIrqFlagIdEdn1CmdDone,
kTestIrqFlagIdEdn0CmdDone,
kTestIrqFlagCount,
} irq_flag_id_t;
/**
* Interrupt flags. Set by `ottf_external_isr()` and cleared by
* `plic_interrupts_enable()`.
*/
static volatile bool irq_flags[kTestIrqFlagCount];
/**
* Initializes the peripherals used in this test.
*/
static void init_peripherals(void) {
CHECK_DIF_OK(dif_csrng_init(
mmio_region_from_addr(TOP_EARLGREY_CSRNG_BASE_ADDR), &csrng));
CHECK_DIF_OK(
dif_edn_init(mmio_region_from_addr(TOP_EARLGREY_EDN0_BASE_ADDR), &edn0));
CHECK_DIF_OK(
dif_edn_init(mmio_region_from_addr(TOP_EARLGREY_EDN1_BASE_ADDR), &edn1));
CHECK_DIF_OK(dif_entropy_src_init(
mmio_region_from_addr(TOP_EARLGREY_ENTROPY_SRC_BASE_ADDR), &entropy_src));
CHECK_DIF_OK(
dif_otbn_init(mmio_region_from_addr(TOP_EARLGREY_OTBN_BASE_ADDR), &otbn));
CHECK_DIF_OK(dif_rv_plic_init(
mmio_region_from_addr(TOP_EARLGREY_RV_PLIC_BASE_ADDR), &plic));
}
/**
* Enables the interrupts required by this test.
*/
static void plic_interrupts_enable(void) {
irq_external_ctrl(false);
irq_global_ctrl(false);
for (size_t i = 0; i < kTestIrqFlagCount; ++i) {
irq_flags[i] = false;
}
dif_rv_plic_irq_id_t irq_ids[] = {kTopEarlgreyPlicIrqIdCsrngCsEntropyReq,
kTopEarlgreyPlicIrqIdEdn0EdnCmdReqDone,
kTopEarlgreyPlicIrqIdEdn1EdnCmdReqDone};
for (size_t i = 0; i < ARRAYSIZE(irq_ids); ++i) {
CHECK_DIF_OK(
dif_rv_plic_irq_set_priority(&plic, irq_ids[i], /*priority=*/1u));
CHECK_DIF_OK(dif_rv_plic_irq_set_enabled(
&plic, irq_ids[i], kTopEarlgreyPlicTargetIbex0, kDifToggleEnabled));
}
CHECK_DIF_OK(dif_rv_plic_target_set_threshold(
&plic, kTopEarlgreyPlicTargetIbex0, /*threshold=*/0u));
CHECK_DIF_OK(dif_csrng_irq_set_enabled(&csrng, kDifCsrngIrqCsEntropyReq,
kDifToggleEnabled));
CHECK_DIF_OK(dif_edn_irq_set_enabled(&edn0, kDifEdnIrqEdnCmdReqDone,
kDifToggleEnabled));
CHECK_DIF_OK(dif_edn_irq_set_enabled(&edn1, kDifEdnIrqEdnCmdReqDone,
kDifToggleEnabled));
irq_global_ctrl(true);
irq_external_ctrl(true);
}
/**
* Blocks until the interrupt flag with `isr_id` is set to true by the
* `ottf_external_isr()` routine.
*/
static void irq_block_wait(irq_flag_id_t isr_id) {
// The interrupt can come before we enter sleep, so we check beforehand.
while (true) {
if (irq_flags[isr_id]) {
break;
}
wait_for_interrupt();
}
switch (isr_id) {
case kTestIrqFlagIdCsrngEntropyReq:
CHECK_DIF_OK(dif_csrng_irq_set_enabled(&csrng, kDifCsrngIrqCsEntropyReq,
kDifToggleDisabled));
break;
case kTestIrqFlagIdEdn0CmdDone:
CHECK_DIF_OK(dif_edn_irq_set_enabled(&edn0, kDifEdnIrqEdnCmdReqDone,
kDifToggleDisabled));
break;
case kTestIrqFlagIdEdn1CmdDone:
CHECK_DIF_OK(dif_edn_irq_set_enabled(&edn0, kDifEdnIrqEdnCmdReqDone,
kDifToggleDisabled));
break;
default:
CHECK(false, "Invalid isr_id: %d", isr_id);
}
}
/**
* Requests data from CSRNG software instance.
*
* Asserts error if there are any repeated words in the output data, or if there
* are any errors set in the CSRNG status registers.
*/
static void csrng_generate_output_check(void) {
uint32_t output[kTestParamFifoBufferSize] = {0};
csrng_testutils_cmd_generate_run(&csrng, output, ARRAYSIZE(output));
uint32_t prev_data = 0;
for (size_t i = 0; i < ARRAYSIZE(output); ++i) {
CHECK(prev_data != output[i],
"Received duplicate data. Last index: %d, value: 0x%x", i, prev_data);
}
}
/**
* Verifies that the entropy req interrupt is triggered on CSRNG instantiate and
* reseed commands.
*/
static void test_csrng_sw_entropy_req_interrupt(
const dif_csrng_seed_material_t *seed_material) {
entropy_testutils_stop_all();
CHECK_DIF_OK(dif_entropy_src_configure(
&entropy_src, entropy_testutils_config_default(), kDifToggleEnabled));
CHECK_DIF_OK(dif_csrng_configure(&csrng));
csrng_testutils_cmd_ready_wait(&csrng);
plic_interrupts_enable();
CHECK_DIF_OK(dif_csrng_instantiate(&csrng, kDifCsrngEntropySrcToggleEnable,
seed_material));
irq_block_wait(kTestIrqFlagIdCsrngEntropyReq);
csrng_generate_output_check();
plic_interrupts_enable();
CHECK_DIF_OK(dif_csrng_reseed(&csrng, seed_material));
irq_block_wait(kTestIrqFlagIdCsrngEntropyReq);
csrng_generate_output_check();
csrng_testutils_cmd_status_check(&csrng);
csrng_testutils_recoverable_alerts_check(&csrng);
}
/**
* Blocks until EDN is ready to process commands.
*
* @param edn A EDN instance.
*/
static void edn_ready_wait(const dif_edn_t *edn) {
bool ready = false;
while (!ready) {
CHECK_DIF_OK(dif_edn_get_status(edn, kDifEdnStatusReady, &ready));
}
bool ack_err;
CHECK_DIF_OK(dif_edn_get_status(edn, kDifEdnStatusCsrngAck, &ack_err));
CHECK(!ack_err, "Unexpected CSRNG ack error");
}
/**
* Configures the `edn` instance.
*
* Verifies that the entropy req interrupt is triggered on EDN instantiate and
* reseed commands.
*
* @param edn A EDN instance.
* @param irq_flag_id The interrupt flag ID to poll after each command is sent
* to EDN.
* @param seed_material Seed material used in instantiate and reseed commands.
*/
static void edn_configure(const dif_edn_t *edn, irq_flag_id_t irq_flag_id,
const dif_edn_seed_material_t *seed_material) {
CHECK_DIF_OK(dif_edn_configure(edn));
edn_ready_wait(edn);
plic_interrupts_enable();
CHECK_DIF_OK(
dif_edn_instantiate(edn, kDifEdnEntropySrcToggleEnable, seed_material));
irq_block_wait(kTestIrqFlagIdCsrngEntropyReq);
irq_block_wait(irq_flag_id);
edn_ready_wait(edn);
plic_interrupts_enable();
CHECK_DIF_OK(dif_edn_reseed(edn, seed_material));
irq_block_wait(kTestIrqFlagIdCsrngEntropyReq);
irq_block_wait(irq_flag_id);
edn_ready_wait(edn);
}
/**
* Initializes EDN instances using the `SW_CMD_REQ` interface and runs the OTBN
* randomness test to verify the entropy delivered by EDN0 and EDN1.
*
* @param seed_material Seed material used in EDN instantiate and reseed
* commands.
*/
static void test_edn_cmd_done(const dif_edn_seed_material_t *seed_material) {
entropy_testutils_stop_all();
CHECK_DIF_OK(dif_entropy_src_configure(
&entropy_src, entropy_testutils_config_default(), kDifToggleEnabled));
CHECK_DIF_OK(dif_csrng_configure(&csrng));
edn_configure(&edn0, kTestIrqFlagIdEdn0CmdDone, seed_material);
edn_configure(&edn1, kTestIrqFlagIdEdn1CmdDone, seed_material);
plic_interrupts_enable();
// Generate enough entropy for the otbn randomness test.
// The len provided here is in number of words as opposed to num of 128b
// blocks. The requested len **must** also be equal to the amount of entroy
// required by the OTBN randomness test, otherwise the Generate command will
// not be fully executed, causing a hang in the `irq_block_wait()` calls
// following the end of the OTBN test.
// EDN0: 20 words = 5x128b blocks
// EDN1: 44 words = 11x128b blocks
CHECK_DIF_OK(dif_edn_generate_start(&edn0, /*len=*/1));
CHECK_DIF_OK(dif_edn_generate_start(&edn1, /*len=*/1));
edn_ready_wait(&edn0);
edn_ready_wait(&edn1);
entropy_testutils_error_check(&entropy_src, &csrng, &edn0, &edn1);
LOG_INFO("OTBN:START");
otbn_randomness_test_start(&otbn);
bool busy = true;
while (busy) {
// Clearing `irq_flags` is ok here since there are no other in-flight
// commands being sent from the EDN instances to the CSRNG block.
if (irq_flags[kTestIrqFlagIdEdn0CmdDone]) {
irq_flags[kTestIrqFlagIdEdn0CmdDone] = false;
CHECK_DIF_OK(dif_edn_generate_start(&edn0, /*len=*/1));
}
if (irq_flags[kTestIrqFlagIdEdn1CmdDone]) {
irq_flags[kTestIrqFlagIdEdn1CmdDone] = false;
CHECK_DIF_OK(dif_edn_generate_start(&edn1, /*len=*/1));
}
// Check if OTBN is still running.
dif_otbn_status_t status;
CHECK_DIF_OK(dif_otbn_get_status(&otbn, &status));
busy = status != kDifOtbnStatusIdle && status != kDifOtbnStatusLocked;
}
CHECK(otbn_randomness_test_end(&otbn, /*skip_otbn_done_check=*/false));
LOG_INFO("OTBN:END");
// See comment above regarding generate command length and potential test
// locking issues.
irq_block_wait(kTestIrqFlagIdEdn1CmdDone);
irq_block_wait(kTestIrqFlagIdEdn0CmdDone);
LOG_INFO("DONE");
plic_interrupts_enable();
CHECK_DIF_OK(dif_edn_uninstantiate(&edn0));
irq_block_wait(kTestIrqFlagIdEdn0CmdDone);
plic_interrupts_enable();
CHECK_DIF_OK(dif_edn_uninstantiate(&edn1));
irq_block_wait(kTestIrqFlagIdEdn1CmdDone);
csrng_testutils_recoverable_alerts_check(&csrng);
entropy_testutils_error_check(&entropy_src, &csrng, &edn0, &edn1);
}
void ottf_external_isr(void) {
// Claim the IRQ at the PLIC.
dif_rv_plic_irq_id_t plic_irq_id;
CHECK_DIF_OK(
dif_rv_plic_irq_claim(&plic, kTopEarlgreyPlicTargetIbex0, &plic_irq_id));
// Get the peripheral the IRQ belongs to.
top_earlgrey_plic_peripheral_t peripheral_serviced =
(top_earlgrey_plic_peripheral_t)
top_earlgrey_plic_interrupt_for_peripheral[plic_irq_id];
// Get the IRQ that was fired from the PLIC IRQ ID and set the corresponding
// `irq_flags`.
if (peripheral_serviced == kTopEarlgreyPlicPeripheralCsrng) {
dif_csrng_irq_t irq =
(dif_csrng_irq_t)(plic_irq_id - kTopEarlgreyPlicIrqIdCsrngCsCmdReqDone);
CHECK(irq == kDifCsrngIrqCsEntropyReq, "Unexpected irq: 0x%x", irq);
CHECK_DIF_OK(dif_csrng_irq_acknowledge(&csrng, irq));
irq_flags[kTestIrqFlagIdCsrngEntropyReq] = true;
} else if (peripheral_serviced == kTopEarlgreyPlicPeripheralEdn0) {
dif_edn_irq_t irq =
(dif_edn_irq_t)(plic_irq_id - kTopEarlgreyPlicIrqIdEdn0EdnCmdReqDone);
CHECK(irq == kDifEdnIrqEdnCmdReqDone, "Unexpected irq: 0x%x", irq);
CHECK_DIF_OK(dif_edn_irq_acknowledge(&edn0, irq));
irq_flags[kTestIrqFlagIdEdn0CmdDone] = true;
} else if (peripheral_serviced == kTopEarlgreyPlicPeripheralEdn1) {
dif_edn_irq_t irq =
(dif_edn_irq_t)(plic_irq_id - kTopEarlgreyPlicIrqIdEdn1EdnCmdReqDone);
CHECK(irq == kDifEdnIrqEdnCmdReqDone, "Unexpected irq: 0x%x", irq);
CHECK_DIF_OK(dif_edn_irq_acknowledge(&edn1, irq));
irq_flags[kTestIrqFlagIdEdn1CmdDone] = true;
}
CHECK_DIF_OK(dif_rv_plic_irq_complete(&plic, kTopEarlgreyPlicTargetIbex0,
plic_irq_id));
}
bool test_main(void) {
init_peripherals();
// Get test random parameters before we disable the entropy complex.
// rand_testutils relies on the ibex rnd CSR which is connected to EDN0.
dif_csrng_seed_material_t csrng_seed;
csrng_seed.seed_material_len =
rand_testutils_gen32_range(/*min=*/0, kDifCsrngSeedMaterialMaxWordLen);
for (size_t i = 0; i < csrng_seed.seed_material_len; ++i) {
csrng_seed.seed_material[i] = rand_testutils_gen32();
}
dif_edn_seed_material_t edn_seed;
edn_seed.len =
rand_testutils_gen32_range(/*min=*/0, kDifEntropySeedMaterialMaxWordLen);
for (size_t i = 0; i < edn_seed.len; ++i) {
edn_seed.data[i] = rand_testutils_gen32();
}
uint32_t num_iterations = kTestParamNumIterationsSim;
if (kDeviceType != kDeviceSimDV && kDeviceType != kDeviceSimVerilator) {
num_iterations = kTestParamNumIterationsOther;
}
for (size_t i = 0; i < num_iterations; ++i) {
test_csrng_sw_entropy_req_interrupt(&csrng_seed);
test_edn_cmd_done(&edn_seed);
}
return true;
}