[cryptolib] Add entropy complex init functions

Adds logic to configure the entropy_src, csrng, edn0 and edn1 blocks in
continuous mode with FIPS mode enabled. The entire initialization
sequence is provided in a single API.

This change does not integrate KAT tests to the complex initialization
sequence. These will be added in a follow up commit.

Signed-off-by: Miguel Osorio <miguelosorio@google.com>
diff --git a/sw/device/lib/crypto/drivers/BUILD b/sw/device/lib/crypto/drivers/BUILD
index 503ea01..d7600e6 100644
--- a/sw/device/lib/crypto/drivers/BUILD
+++ b/sw/device/lib/crypto/drivers/BUILD
@@ -63,6 +63,8 @@
     hdrs = ["entropy.h"],
     deps = [
         "//hw/ip/csrng/data:csrng_regs",
+        "//hw/ip/edn/data:edn_regs",
+        "//hw/ip/entropy_src/data:entropy_src_regs",
         "//hw/top_earlgrey/sw/autogen:top_earlgrey",
         "//sw/device/lib/base:abs_mmio",
         "//sw/device/lib/base:bitfield",
@@ -100,10 +102,13 @@
     deps = [
         ":entropy",
         ":entropy_kat",
+        "//hw/top_earlgrey/sw/autogen:top_earlgrey",
         "//sw/device/lib/base:macros",
         "//sw/device/lib/base:memory",
+        "//sw/device/lib/dif:otbn",
         "//sw/device/lib/testing/test_framework:check",
         "//sw/device/lib/testing/test_framework:ottf_main",
+        "//sw/device/tests:otbn_randomness_impl",
     ],
 )
 
diff --git a/sw/device/lib/crypto/drivers/entropy.c b/sw/device/lib/crypto/drivers/entropy.c
index 7f813f3..64d5259 100644
--- a/sw/device/lib/crypto/drivers/entropy.c
+++ b/sw/device/lib/crypto/drivers/entropy.c
@@ -7,12 +7,18 @@
 #include "sw/device/lib/base/abs_mmio.h"
 #include "sw/device/lib/base/bitfield.h"
 #include "sw/device/lib/base/memory.h"
+#include "sw/device/lib/base/multibits.h"
 
-#include "csrng_regs.h"  // Generated
+#include "csrng_regs.h"        // Generated
+#include "edn_regs.h"          // Generated
+#include "entropy_src_regs.h"  // Generated
 #include "hw/top_earlgrey/sw/autogen/top_earlgrey.h"
 
 enum {
   kBaseCsrng = TOP_EARLGREY_CSRNG_BASE_ADDR,
+  kBaseEntropySrc = TOP_EARLGREY_ENTROPY_SRC_BASE_ADDR,
+  kBaseEdn0 = TOP_EARLGREY_EDN0_BASE_ADDR,
+  kBaseEdn1 = TOP_EARLGREY_EDN1_BASE_ADDR,
 
   /**
    * CSRNG genbits buffer size in uint32_t words.
@@ -55,7 +61,161 @@
   uint32_t generate_len;
 } entropy_csrng_cmd_t;
 
-#define ENTROPY_CMD(m, i) ((bitfield_field32_t){.mask = m, .index = i})
+/**
+ * Entropy complex configuration modes.
+ *
+ * Each enum value is used a confiugration index in `kEntropyComplexConfigs`.
+ */
+typedef enum entropy_complex_config_id {
+  /**
+   * Entropy complex in continuous mode. This is the default runtime
+   * configuration.
+   */
+  kEntropyComplexConfigIdContinuous,
+  kEntropyComplexConfigIdNumEntries,
+} entropy_complex_config_id_t;
+
+/**
+ * EDN configuration settings.
+ */
+typedef struct edn_config {
+  /**
+   * Base address of the EDN block.
+   */
+  uint32_t base_address;
+  /**
+   * Number of generate calls between reseed commands.
+   */
+  uint32_t reseed_interval;
+  /**
+   * Downstream CSRNG instantiate command configuration.
+   */
+  entropy_csrng_cmd_t instantiate;
+  /**
+   * Downstream CSRNG generate command configuration.
+   */
+  entropy_csrng_cmd_t generate;
+  /**
+   * Downstream CSRNG reseed command configuration.
+   */
+  entropy_csrng_cmd_t reseed;
+} edn_config_t;
+
+/**
+ * Entropy complex configuration settings.
+ *
+ * Contains configuration paramenters for entropy_src, csrng, edn0 and edn1.
+ */
+typedef struct entropy_complex_config {
+  /**
+   * Configuration identifier.
+   */
+  entropy_complex_config_id_t id;
+  /**
+   * If set, FIPS compliant entropy will be generated by this module after being
+   * processed by an SP 800-90B compliant conditioning function.
+   */
+  multi_bit_bool_t fips_enable;
+  /**
+   * If set, entropy will be routed to a firmware-visible register instead of
+   * being distributed to other hardware IPs.
+   */
+  multi_bit_bool_t route_to_firmware;
+  /**
+   * If set, raw entropy will be sent to CSRNG, bypassing the conditioner block
+   * and disabling the FIPS hardware generated flag.
+   */
+  multi_bit_bool_t bypass_conditioner;
+  /**
+   * Enables single bit entropy mode.
+   */
+  multi_bit_bool_t single_bit_mode;
+  /**
+   * The size of the window used for health tests.
+   */
+  uint16_t fips_test_window_size;
+  /**
+   * The number of health test failures that must occur before an alert is
+   * triggered. When set to 0, alerts are disabled.
+   */
+  uint16_t alert_threshold;
+  /**
+   * EDN0 configuration.
+   */
+  edn_config_t edn0;
+  /**
+   * EDN1 configuration.
+   */
+  edn_config_t edn1;
+} entropy_complex_config_t;
+
+// Entropy complex configuration table. This is expected to be fixed at build
+// time. For this reason, it is not recommended to use this table in a ROM
+// target unless the values are known to work. In other words, only use in
+// mutable code partitions.
+static const entropy_complex_config_t
+    kEntropyComplexConfigs[kEntropyComplexConfigIdNumEntries] = {
+        [kEntropyComplexConfigIdContinuous] =
+            {
+                .fips_enable = kMultiBitBool4True,
+                .route_to_firmware = kMultiBitBool4False,
+                .bypass_conditioner = kMultiBitBool4False,
+                .single_bit_mode = kMultiBitBool4False,
+                .fips_test_window_size = 0x200,
+                .alert_threshold = 2,
+                .edn0 =
+                    {
+                        .base_address = kBaseEdn0,
+                        .reseed_interval = 32,
+                        .instantiate =
+                            {
+                                .id = kEntropyDrbgOpInstantiate,
+                                .disable_trng_input = kHardenedBoolFalse,
+                                .seed_material = NULL,
+                                .generate_len = 0,
+                            },
+                        .generate =
+                            {
+                                .id = kEntropyDrbgOpGenerate,
+                                .disable_trng_input = kHardenedBoolFalse,
+                                .seed_material = NULL,
+                                .generate_len = 8,
+                            },
+                        .reseed =
+                            {
+                                .id = kEntropyDrbgOpReseed,
+                                .disable_trng_input = kHardenedBoolFalse,
+                                .seed_material = NULL,
+                                .generate_len = 0,
+                            },
+                    },
+                .edn1 =
+                    {
+                        .base_address = kBaseEdn1,
+                        .reseed_interval = 4,
+                        .instantiate =
+                            {
+                                .id = kEntropyDrbgOpInstantiate,
+                                .disable_trng_input = kHardenedBoolFalse,
+                                .seed_material = NULL,
+                                .generate_len = 0,
+                            },
+                        .generate =
+                            {
+                                .id = kEntropyDrbgOpGenerate,
+                                .seed_material = NULL,
+                                .generate_len = 1,
+                            },
+                        .reseed =
+                            {
+                                .id = kEntropyDrbgOpReseed,
+                                .disable_trng_input = kHardenedBoolFalse,
+                                .seed_material = NULL,
+                                .generate_len = 0,
+                            },
+                    },
+            },
+};
 
 OT_WARN_UNUSED_RESULT
 static status_t csrng_send_app_cmd(uint32_t reg_address,
@@ -67,6 +227,7 @@
     cmd_ready = bitfield_bit32_read(reg, CSRNG_SW_CMD_STS_CMD_RDY_BIT);
   } while (!cmd_ready);
 
+#define ENTROPY_CMD(m, i) ((bitfield_field32_t){.mask = m, .index = i})
   // The application command header is not specified as a register in the
   // hardware specification, so the fields are mapped here by hand. The
   // command register also accepts arbitrary 32bit data.
@@ -74,6 +235,7 @@
   static const bitfield_field32_t kAppCmdFieldCmdId = ENTROPY_CMD(0xf, 0);
   static const bitfield_field32_t kAppCmdFieldCmdLen = ENTROPY_CMD(0xf, 4);
   static const bitfield_field32_t kAppCmdFieldGlen = ENTROPY_CMD(0x7ffff, 12);
+#undef ENTROPY_CMD
 
   uint32_t cmd_len = cmd.seed_material == NULL ? 0 : cmd.seed_material->len;
 
@@ -107,6 +269,202 @@
   return OK_STATUS();
 }
 
+/**
+ * Enables the CSRNG block with the SW application and internal state registers
+ * enabled.
+ */
+static void csrng_configure(void) {
+  uint32_t reg =
+      bitfield_field32_write(0, CSRNG_CTRL_ENABLE_FIELD, kMultiBitBool4True);
+  reg = bitfield_field32_write(reg, CSRNG_CTRL_SW_APP_ENABLE_FIELD,
+                               kMultiBitBool4True);
+  reg = bitfield_field32_write(reg, CSRNG_CTRL_READ_INT_STATE_FIELD,
+                               kMultiBitBool4True);
+  abs_mmio_write32(kBaseCsrng + CSRNG_CTRL_REG_OFFSET, reg);
+}
+
+/**
+ * Stops a given EDN instance.
+ *
+ * It also resets the EDN CSRNG command buffer to avoid synchronization issues
+ * with the upstream CSRNG instance.
+ *
+ * @param edn_address The based address of the target EDN block.
+ */
+static void edn_stop(uint32_t edn_address) {
+  // FIFO clear is only honored if edn is enabled. This is needed to avoid
+  // synchronization issues with the upstream CSRNG instance.
+  uint32_t reg = abs_mmio_read32(edn_address + EDN_CTRL_REG_OFFSET);
+  abs_mmio_write32(edn_address + EDN_CTRL_REG_OFFSET,
+                   bitfield_field32_write(reg, EDN_CTRL_CMD_FIFO_RST_FIELD,
+                                          kMultiBitBool4True));
+
+  // Disable EDN and restore the FIFO clear at the same time so that no rogue
+  // command can get in after the clear above.
+  abs_mmio_write32(edn_address + EDN_CTRL_REG_OFFSET, EDN_CTRL_REG_RESVAL);
+}
+
+/**
+ * Blocks until EDN instance is ready to execute a new CSNRG command.
+ *
+ * @param edn_address EDN base address.
+ * @returns an error if the EDN error status bit is set.
+ */
+OT_WARN_UNUSED_RESULT
+static status_t edn_ready_block(uint32_t edn_address) {
+  uint32_t reg;
+  do {
+    reg = abs_mmio_read32(edn_address + EDN_SW_CMD_STS_REG_OFFSET);
+  } while (!bitfield_bit32_read(reg, EDN_SW_CMD_STS_CMD_RDY_BIT));
+
+  if (bitfield_bit32_read(reg, EDN_SW_CMD_STS_CMD_STS_BIT)) {
+    return INTERNAL();
+  }
+  return OK_STATUS();
+}
+
+/**
+ * Configures EDN instance based on `config` options.
+ *
+ * @param config EDN configuration options.
+ * @returns error on failure.
+ */
+OT_WARN_UNUSED_RESULT
+static status_t edn_configure(const edn_config_t *config) {
+  TRY(csrng_send_app_cmd(config->base_address + EDN_RESEED_CMD_REG_OFFSET,
+                         config->reseed));
+  TRY(csrng_send_app_cmd(config->base_address + EDN_GENERATE_CMD_REG_OFFSET,
+                         config->generate));
+  abs_mmio_write32(
+      config->base_address + EDN_MAX_NUM_REQS_BETWEEN_RESEEDS_REG_OFFSET,
+      config->reseed_interval);
+
+  uint32_t reg =
+      bitfield_field32_write(0, EDN_CTRL_EDN_ENABLE_FIELD, kMultiBitBool4True);
+  reg = bitfield_field32_write(reg, EDN_CTRL_AUTO_REQ_MODE_FIELD,
+                               kMultiBitBool4True);
+  abs_mmio_write32(config->base_address + EDN_CTRL_REG_OFFSET, reg);
+
+  TRY(edn_ready_block(config->base_address));
+  TRY(csrng_send_app_cmd(config->base_address + EDN_SW_CMD_REQ_REG_OFFSET,
+                         config->instantiate));
+  return edn_ready_block(config->base_address);
+}
+
+/**
+ * Stops the current mode of operation and disables the entropy_src module.
+ *
+ * All configuration registers are set to their reset values to avoid
+ * synchronization issues with internal FIFOs.
+ */
+static void entropy_src_stop(void) {
+  abs_mmio_write32(kBaseEntropySrc + ENTROPY_SRC_MODULE_ENABLE_REG_OFFSET,
+                   ENTROPY_SRC_MODULE_ENABLE_REG_RESVAL);
+
+  // Set default values for other critical registers to avoid synchronization
+  // issues.
+  abs_mmio_write32(kBaseEntropySrc + ENTROPY_SRC_ENTROPY_CONTROL_REG_OFFSET,
+                   ENTROPY_SRC_ENTROPY_CONTROL_REG_RESVAL);
+  abs_mmio_write32(kBaseEntropySrc + ENTROPY_SRC_CONF_REG_OFFSET,
+                   ENTROPY_SRC_CONF_REG_RESVAL);
+  abs_mmio_write32(kBaseEntropySrc + ENTROPY_SRC_HEALTH_TEST_WINDOWS_REG_OFFSET,
+                   ENTROPY_SRC_HEALTH_TEST_WINDOWS_REG_RESVAL);
+  abs_mmio_write32(kBaseEntropySrc + ENTROPY_SRC_ALERT_THRESHOLD_REG_OFFSET,
+                   ENTROPY_SRC_ALERT_THRESHOLD_REG_RESVAL);
+}
+
+/**
+ * Disables the entropy complex.
+ *
+ * The order of operations is important to avoid synchronization issues across
+ * blocks. For Example, EDN has FIFOs used to send commands to the downstream
+ * CSRNG instances. Such FIFOs are not cleared when EDN is reconfigured, and an
+ * explicit clear FIFO command needs to be set by software (see #14506). There
+ * may be additional race conditions for downstream blocks that are
+ * processing requests from an upstream endpoint (e.g. entropy_src processing a
+ * request from CSRNG, or CSRNG processing a request from EDN). To avoid these
+ * issues, it is recommended to first disable EDN, then CSRNG and entropy_src
+ * last.
+ *
+ * See hw/ip/csrng/doc/_index.md#module-enable-and-disable for more details.
+ */
+static void entropy_complex_stop_all(void) {
+  edn_stop(kBaseEdn0);
+  edn_stop(kBaseEdn1);
+  abs_mmio_write32(kBaseCsrng + CSRNG_CTRL_REG_OFFSET, CSRNG_CTRL_REG_RESVAL);
+  entropy_src_stop();
+}
+
+/**
+ * Configures the entropy_src with based on `config` options.
+ *
+ * @param config Entropy Source configuration options.
+ * @return error on failure.
+ */
+OT_WARN_UNUSED_RESULT
+static status_t entropy_src_configure(const entropy_complex_config_t *config) {
+  // Control register configuration.
+  uint32_t reg = bitfield_field32_write(
+      0, ENTROPY_SRC_ENTROPY_CONTROL_ES_ROUTE_FIELD, config->route_to_firmware);
+  reg = bitfield_field32_write(reg, ENTROPY_SRC_ENTROPY_CONTROL_ES_TYPE_FIELD,
+                               config->bypass_conditioner);
+  abs_mmio_write32(kBaseEntropySrc + ENTROPY_SRC_ENTROPY_CONTROL_REG_OFFSET,
+                   reg);
+
+  // Config register configuration
+  reg = bitfield_field32_write(0, ENTROPY_SRC_CONF_FIPS_ENABLE_FIELD,
+                               config->fips_enable);
+  reg = bitfield_field32_write(reg,
+                               ENTROPY_SRC_CONF_ENTROPY_DATA_REG_ENABLE_FIELD,
+                               config->route_to_firmware);
+  reg = bitfield_field32_write(reg, ENTROPY_SRC_CONF_THRESHOLD_SCOPE_FIELD,
+                               kMultiBitBool4False);
+  reg = bitfield_field32_write(reg, ENTROPY_SRC_CONF_RNG_BIT_ENABLE_FIELD,
+                               config->single_bit_mode);
+  reg = bitfield_field32_write(reg, ENTROPY_SRC_CONF_RNG_BIT_SEL_FIELD, 0);
+  abs_mmio_write32(kBaseEntropySrc + ENTROPY_SRC_CONF_REG_OFFSET, reg);
+
+  // Configure health test windw. Conditioning bypass is not supported.
+  abs_mmio_write32(
+      kBaseEntropySrc + ENTROPY_SRC_HEALTH_TEST_WINDOWS_REG_OFFSET,
+      bitfield_field32_write(ENTROPY_SRC_HEALTH_TEST_WINDOWS_REG_RESVAL,
+                             ENTROPY_SRC_HEALTH_TEST_WINDOWS_FIPS_WINDOW_FIELD,
+                             config->fips_test_window_size));
+
+  // Configure alert threshold
+  reg = bitfield_field32_write(
+      0, ENTROPY_SRC_ALERT_THRESHOLD_ALERT_THRESHOLD_FIELD,
+      config->alert_threshold);
+  reg = bitfield_field32_write(
+      reg, ENTROPY_SRC_ALERT_THRESHOLD_ALERT_THRESHOLD_INV_FIELD,
+      ~config->alert_threshold);
+  abs_mmio_write32(kBaseEntropySrc + ENTROPY_SRC_ALERT_THRESHOLD_REG_OFFSET,
+                   reg);
+
+  abs_mmio_write32(kBaseEntropySrc + ENTROPY_SRC_MODULE_ENABLE_REG_OFFSET,
+                   kMultiBitBool4True);
+
+  // TODO: Add FI checks.
+  return OK_STATUS();
+}
+
+status_t entropy_complex_init(void) {
+  entropy_complex_stop_all();
+
+  const entropy_complex_config_t *config =
+      &kEntropyComplexConfigs[kEntropyComplexConfigIdContinuous];
+  if (launder32(config->id) != kEntropyComplexConfigIdContinuous) {
+    return INTERNAL();
+  }
+
+  // TODO: Add health check configuration.
+
+  TRY(entropy_src_configure(config));
+  csrng_configure();
+  TRY(edn_configure(&config->edn0));
+  return edn_configure(&config->edn1);
+}
+
 status_t entropy_csrng_instantiate(
     hardened_bool_t disable_trng_input,
     const entropy_seed_material_t *seed_material) {
diff --git a/sw/device/lib/crypto/drivers/entropy.h b/sw/device/lib/crypto/drivers/entropy.h
index bf3d158..b3ee698 100644
--- a/sw/device/lib/crypto/drivers/entropy.h
+++ b/sw/device/lib/crypto/drivers/entropy.h
@@ -33,6 +33,17 @@
 } entropy_seed_material_t;
 
 /**
+ * Configures the entropy complex in continuous mode.
+ *
+ * The complex is configured in continuous mode with FIPS mode enabled. This is
+ * the default operational mode of the entropy_src, csrng, edn0 and edn1 blocks.
+ *
+ * @return Operation status in `status_t` format.
+ */
+OT_WARN_UNUSED_RESULT
+status_t entropy_complex_init(void);
+
+/**
  * Instantiate the SW CSRNG with a new seed value.
  *
  * SW CSRNG refers to the CSRNG hardware instance available for software use.
diff --git a/sw/device/lib/crypto/drivers/entropy_test.c b/sw/device/lib/crypto/drivers/entropy_test.c
index 28c3980..1372c42 100644
--- a/sw/device/lib/crypto/drivers/entropy_test.c
+++ b/sw/device/lib/crypto/drivers/entropy_test.c
@@ -1,18 +1,38 @@
 // 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/crypto/drivers/entropy.h"
+
 #include "sw/device/lib/base/memory.h"
 #include "sw/device/lib/base/status.h"
 #include "sw/device/lib/crypto/drivers/entropy_kat.h"
+#include "sw/device/lib/dif/dif_otbn.h"
 #include "sw/device/lib/runtime/log.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"
 
 OTTF_DEFINE_TEST_CONFIG();
+
+static void entropy_complex_init_test(void) {
+  CHECK_STATUS_OK(entropy_complex_init());
+
+  // The following test requests entropy from both EDN0 and EDN1.
+  dif_otbn_t otbn;
+  CHECK_DIF_OK(
+      dif_otbn_init(mmio_region_from_addr(TOP_EARLGREY_OTBN_BASE_ADDR), &otbn));
+  otbn_randomness_test_start(&otbn);
+  CHECK(otbn_randomness_test_end(&otbn, /*skip_otbn_don_check=*/false));
+}
+
 bool test_main(void) {
   status_t result = entropy_csrng_kat();
   if (!status_ok(result)) {
     LOG_ERROR("entropy_csrng_kat: %r\n", result);
     return false;
   }
+  entropy_complex_init_test();
   return true;
 }