[test-point] Implement chip_sw_csrng_edn_cmd

The chip_sw_entropy_src_csrng test is updated to check for edn command
done irqs in addition to csrng entropy request irqs. A generate command
is sent using the EDN SW_CMD_REQ register to generate enough entropy for
the otbn randomness test. The otbn randomness test is used to check the
EDN output.

Signed-off-by: Miguel Osorio <miguelosorio@google.com>
diff --git a/hw/top_earlgrey/data/chip_testplan.hjson b/hw/top_earlgrey/data/chip_testplan.hjson
index e414d0d..a2dfb64 100644
--- a/hw/top_earlgrey/data/chip_testplan.hjson
+++ b/hw/top_earlgrey/data/chip_testplan.hjson
@@ -2235,14 +2235,13 @@
       name: chip_sw_csrng_edn_cmd
       desc: '''Verify incoming command interface from EDN.
 
-            - Have each EDN instance issue an instantiate command to CSRNG.
-            - When done, verify the reception of cmd req done interrupt.
+            - Have each EDN instance issue an instantiate, reseed and generate command to CSRNG.
+            - On each command done, verify the reception of edn cmd req done interrupt.
+            - Run OTBN randomness test to test the output from EDN0 and EDN1.
             - Check the data returned to EDN via connectivity assertion checks.
-            - TODO: explore the ability to generate predictable data and verify the received value.
-            Details TBD.
             '''
       stage: V2
-      tests: []
+      tests: ["chip_sw_entropy_src_csrng"]
     }
     {
       name: chip_sw_csrng_fuse_en_sw_app_read
diff --git a/sw/device/lib/dif/dif_edn.h b/sw/device/lib/dif/dif_edn.h
index ee8d16d..905ae61 100644
--- a/sw/device/lib/dif/dif_edn.h
+++ b/sw/device/lib/dif/dif_edn.h
@@ -284,9 +284,9 @@
  * Queries the EDN error flags.
  *
  * @param edn An EDN handle.
- * @param unhealthy_fifos Bitset of FIFOs in an unhealthy state; indices are
- * `dif_edn_fifo_t`.
- * @param errors Bitset of errors relating to the above FIFOs; indices are
+ * @param[out] unhealthy_fifos Bitset of FIFOs in an unhealthy state; indices
+ * are `dif_edn_fifo_t`.
+ * @param[out] errors Bitset of errors relating to the above FIFOs; indices are
  * `dif_edn_error_t`.
  * @return The result of the operation.
  */
diff --git a/sw/device/tests/BUILD b/sw/device/tests/BUILD
index 55e10b9..05805e6 100644
--- a/sw/device/tests/BUILD
+++ b/sw/device/tests/BUILD
@@ -726,6 +726,7 @@
         timeout = "long",
     ),
     deps = [
+        ":otbn_randomness_impl",
         "//hw/top_earlgrey/sw/autogen:top_earlgrey",
         "//sw/device/lib/base:memory",
         "//sw/device/lib/base:mmio",
@@ -733,7 +734,9 @@
         "//sw/device/lib/dif:csrng",
         "//sw/device/lib/dif:edn",
         "//sw/device/lib/dif:entropy_src",
+        "//sw/device/lib/dif:otbn",
         "//sw/device/lib/runtime:log",
+        "//sw/device/lib/runtime:otbn",
         "//sw/device/lib/testing:csrng_testutils",
         "//sw/device/lib/testing:entropy_testutils",
         "//sw/device/lib/testing:isr_testutils",
diff --git a/sw/device/tests/entropy_src_csrng_test.c b/sw/device/tests/entropy_src_csrng_test.c
index d7b8796..b27642e 100644
--- a/sw/device/tests/entropy_src_csrng_test.c
+++ b/sw/device/tests/entropy_src_csrng_test.c
@@ -7,14 +7,17 @@
 #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/runtime/otbn.h"
 #include "sw/device/lib/testing/csrng_testutils.h"
 #include "sw/device/lib/testing/entropy_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"
@@ -25,9 +28,7 @@
 static dif_entropy_src_t entropy_src;
 static dif_rv_plic_t plic;
 
-// Set by `ottf_external_isr()` and cleared by
-// `csrng_entropy_req_irq_enable()`.
-static volatile bool entropy_src_isr_csrng_req_set;
+static otbn_t otbn;
 
 OTTF_DEFINE_TEST_CONFIG();
 
@@ -41,10 +42,26 @@
    * The number of test iterations per target.
    */
   kTestParamNumIterationsSim = 1,
-  kTestParamNumIterationsOther = 100,
+  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) {
@@ -58,45 +75,74 @@
       mmio_region_from_addr(TOP_EARLGREY_ENTROPY_SRC_BASE_ADDR), &entropy_src));
   CHECK_DIF_OK(dif_rv_plic_init(
       mmio_region_from_addr(TOP_EARLGREY_RV_PLIC_BASE_ADDR), &plic));
+
+  CHECK(otbn_init(&otbn, mmio_region_from_addr(TOP_EARLGREY_OTBN_BASE_ADDR)) ==
+        kOtbnOk);
 }
 
 /**
- * Enables the CSRNG entropy request interrupt.
+ * Enables the interrupts required by this test.
  */
-static void csrng_entropy_req_irq_enable(void) {
+static void plic_interrupts_enable(void) {
   irq_external_ctrl(false);
   irq_global_ctrl(false);
 
-  entropy_src_isr_csrng_req_set = false;
+  for (size_t i = 0; i < kTestIrqFlagCount; ++i) {
+    irq_flags[i] = false;
+  }
 
-  dif_rv_plic_irq_id_t irq_id = kTopEarlgreyPlicIrqIdCsrngCsEntropyReq;
-  CHECK_DIF_OK(dif_rv_plic_irq_set_priority(&plic, irq_id, /*priority=*/1u));
+  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_irq_set_enabled(
-      &plic, irq_id, 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 a CSRNG entropy request interrupt is received.
+ * Blocks until the interrupt flag with `isr_id` is set to true by the
+ * `ottf_external_isr()` routine.
  */
-static void csrng_entropy_req_irq_block_wait(void) {
+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 (entropy_src_isr_csrng_req_set) {
+    if (irq_flags[isr_id]) {
       break;
     }
     wait_for_interrupt();
   }
-  CHECK_DIF_OK(dif_csrng_irq_set_enabled(&csrng, kDifCsrngIrqCsEntropyReq,
-                                         kDifToggleDisabled));
+  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);
+  }
 }
 
 /**
@@ -128,15 +174,15 @@
   CHECK_DIF_OK(dif_csrng_configure(&csrng));
 
   csrng_testutils_cmd_ready_wait(&csrng);
-  csrng_entropy_req_irq_enable();
+  plic_interrupts_enable();
   CHECK_DIF_OK(dif_csrng_instantiate(&csrng, kDifCsrngEntropySrcToggleEnable,
                                      seed_material));
-  csrng_entropy_req_irq_block_wait();
+  irq_block_wait(kTestIrqFlagIdCsrngEntropyReq);
   csrng_generate_output_check();
 
-  csrng_entropy_req_irq_enable();
+  plic_interrupts_enable();
   CHECK_DIF_OK(dif_csrng_reseed(&csrng, seed_material));
-  csrng_entropy_req_irq_block_wait();
+  irq_block_wait(kTestIrqFlagIdCsrngEntropyReq);
   csrng_generate_output_check();
 
   csrng_testutils_cmd_status_check(&csrng);
@@ -144,61 +190,176 @@
 }
 
 /**
+ * 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");
+}
+
+/**
+ * Throws test assertion if there are any errors detected in the EDN blocks.
+ */
+static void edn_errors_check(void) {
+  uint32_t fifo_errors;
+  uint32_t edn_errors;
+
+  CHECK_DIF_OK(dif_edn_get_errors(&edn0, &fifo_errors, &edn_errors));
+  CHECK(edn_errors == 0, "edn0 unexpected err: 0x%x", edn_errors);
+  CHECK(fifo_errors == 0, "edn0 unexpected fifo err: 0x%x", fifo_errors);
+
+  CHECK_DIF_OK(dif_edn_get_errors(&edn1, &fifo_errors, &edn_errors));
+  CHECK(edn_errors == 0, "edn1 unexpected err: 0x%x", edn_errors);
+  CHECK(fifo_errors == 0, "edn1 unexpected fifo err: 0x%x", fifo_errors);
+}
+
+/**
+ * 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 test_csrng_edn_entropy_req_interrupt(
-    const dif_edn_seed_material_t *seed_material) {
+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));
 
-  CHECK_DIF_OK(dif_edn_configure(&edn0));
-  csrng_entropy_req_irq_enable();
-  CHECK_DIF_OK(
-      dif_edn_instantiate(&edn0, kDifEdnEntropySrcToggleEnable, seed_material));
-  csrng_entropy_req_irq_block_wait();
+  edn_configure(&edn0, kTestIrqFlagIdEdn0CmdDone, seed_material);
+  edn_configure(&edn1, kTestIrqFlagIdEdn1CmdDone, seed_material);
 
-  csrng_entropy_req_irq_enable();
-  CHECK_DIF_OK(dif_edn_reseed(&edn0, seed_material));
-  csrng_entropy_req_irq_block_wait();
+  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);
+  edn_errors_check();
+
+  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.dif, &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);
 
-  CHECK_DIF_OK(dif_edn_configure(&edn1));
-  csrng_entropy_req_irq_enable();
-  CHECK_DIF_OK(
-      dif_edn_instantiate(&edn1, kDifEdnEntropySrcToggleEnable, seed_material));
-  csrng_entropy_req_irq_block_wait();
-
-  csrng_entropy_req_irq_enable();
-  CHECK_DIF_OK(dif_edn_reseed(&edn1, seed_material));
-  csrng_entropy_req_irq_block_wait();
+  plic_interrupts_enable();
   CHECK_DIF_OK(dif_edn_uninstantiate(&edn1));
+  irq_block_wait(kTestIrqFlagIdEdn1CmdDone);
 
   csrng_testutils_recoverable_alerts_check(&csrng);
+  edn_errors_check();
 }
 
 void ottf_external_isr(void) {
-  top_earlgrey_plic_peripheral_t plic_peripheral;
-  dif_csrng_irq_t irq;
-  isr_testutils_csrng_isr(
-      (plic_isr_ctx_t){
-          .rv_plic = &plic,
-          .hart_id = kTopEarlgreyPlicTargetIbex0,
-      },
-      (csrng_isr_ctx_t){
-          .csrng = &csrng,
-          .plic_csrng_start_irq_id = kTopEarlgreyPlicIrqIdCsrngCsCmdReqDone,
-          .expected_irq = kDifCsrngIrqCsEntropyReq,
-          .is_only_irq = false,
-      },
-      &plic_peripheral, &irq);
-  CHECK(plic_peripheral == kTopEarlgreyPlicPeripheralCsrng,
-        "Interrupt from incorrect peripheral: (expected: %d, got: %d)",
-        kTopEarlgreyPlicPeripheralCsrng, plic_peripheral);
-  CHECK(irq == kDifCsrngIrqCsEntropyReq);
-  entropy_src_isr_csrng_req_set = true;
+  // 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) {
@@ -227,7 +388,7 @@
 
   for (size_t i = 0; i < num_iterations; ++i) {
     test_csrng_sw_entropy_req_interrupt(&csrng_seed);
-    test_csrng_edn_entropy_req_interrupt(&edn_seed);
+    test_edn_cmd_done(&edn_seed);
   }
 
   return true;