[hmac_enc, tests] Introduce hmac_enc chip level IRQ test

https://docs.opentitan.org/sw/device/tests/#ip-integration
chip_sw_hmac_enc

This test simply ensures that "empty" and "done" interrupts fire
up when appropriate conditions are met. The test only partially
satisfies the chip_sw_hmac_enc requirements, and requires a
separate test to verify the digest.

Signed-off-by: Silvestrs Timofejevs <silvestrst@lowrisc.org>
diff --git a/hw/top_earlgrey/data/chip_testplan.hjson b/hw/top_earlgrey/data/chip_testplan.hjson
index 28f93ec..3ddc228 100644
--- a/hw/top_earlgrey/data/chip_testplan.hjson
+++ b/hw/top_earlgrey/data/chip_testplan.hjson
@@ -1622,7 +1622,7 @@
             the HMAC done and FIFO empty interrupts as a part of this test.
             '''
       milestone: V2
-      tests: []
+      tests: ["chip_sw_hmac_enc_irq"]
     }
     {
       name: chip_sw_hmac_idle
diff --git a/hw/top_earlgrey/dv/chip_sim_cfg.hjson b/hw/top_earlgrey/dv/chip_sim_cfg.hjson
index 938e5d8..ce42fe5 100644
--- a/hw/top_earlgrey/dv/chip_sim_cfg.hjson
+++ b/hw/top_earlgrey/dv/chip_sim_cfg.hjson
@@ -395,6 +395,12 @@
       run_opts: ["+sw_test_timeout_ns=22000000"]
     }
     {
+      name: chip_sw_hmac_enc_irq_test
+      uvm_test_seq: chip_sw_base_vseq
+      sw_images: ["sw/device/tests/hmac_enc_irq_test:1"]
+      en_run_modes: ["sw_test_mode_test_rom"]
+    }
+    {
       name: chip_sw_kmac_mode_cshake_test
       uvm_test_seq: chip_sw_base_vseq
       sw_images: ["sw/device/tests/kmac_mode_cshake_test:1"]
diff --git a/sw/device/tests/hmac_enc_irq_test.c b/sw/device/tests/hmac_enc_irq_test.c
new file mode 100644
index 0000000..a40864e
--- /dev/null
+++ b/sw/device/tests/hmac_enc_irq_test.c
@@ -0,0 +1,146 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+
+#include <assert.h>
+
+#include "sw/device/lib/base/mmio.h"
+#include "sw/device/lib/dif/dif_hmac.h"
+#include "sw/device/lib/dif/dif_rv_plic.h"
+#include "sw/device/lib/irq.h"
+#include "sw/device/lib/runtime/ibex.h"
+#include "sw/device/lib/runtime/log.h"
+#include "sw/device/lib/testing/check.h"
+#include "sw/device/lib/testing/rv_plic_testutils.h"
+#include "sw/device/lib/testing/test_framework/ottf.h"
+
+#include "hw/top_earlgrey/sw/autogen/top_earlgrey.h"
+#include "sw/device/lib/testing/autogen/isr_testutils.h"
+
+/**
+ * HMAC done 50uS timeout, which should be enough even for a 100kHz device.
+ *
+ * https://docs.opentitan.org/hw/ip/hmac/doc/
+ * Final hash calculation takes 360 cycles, which consists of one
+ * block compression and extra HMAC computation.
+ */
+const uint32_t kHmacEncFinishTimeoutUsec = 50;
+
+/**
+ * FIFO empty 10uS timeout, which should be enough even for a 100kHz device.
+ *
+ * https://docs.opentitan.org/hw/ip/hmac/doc/
+ * Single HMAC block compression takes 80 cycles.
+ */
+const uint32_t kHmacEncEmptyTimeoutUsec = 10;
+
+static plic_isr_ctx_t plic_ctx = {
+    .hart_id = kTopEarlgreyPlicTargetIbex0,
+};
+
+static dif_hmac_t hmac;
+static top_earlgrey_plic_peripheral_t peripheral_serviced;
+static dif_hmac_irq_t irq_serviced;
+static hmac_isr_ctx_t hmac_ctx = {
+    .hmac = &hmac,
+    .plic_hmac_start_irq_id = kTopEarlgreyPlicIrqIdHmacHmacDone,
+    .is_only_irq = false,
+};
+
+const test_config_t kTestConfig;
+
+static const dif_hmac_transaction_t kHmacTransactionConfig = {
+    .digest_endianness = kDifHmacEndiannessLittle,
+    .message_endianness = kDifHmacEndiannessLittle,
+};
+
+/**
+ * External ISR.
+ *
+ * Handles all peripheral interrupts on Ibex. PLIC asserts an external interrupt
+ * line to the CPU, which results in a call to this OTTF ISR. This ISR
+ * overrides the default OTTF implementation.
+ */
+void ottf_external_isr(void) {
+  isr_testutils_hmac_isr(plic_ctx, hmac_ctx, &peripheral_serviced,
+                         &irq_serviced);
+}
+
+/**
+ * Enables interrupts required by this test.
+ */
+static void enable_irqs(void) {
+  mmio_region_t base_addr =
+      mmio_region_from_addr(TOP_EARLGREY_RV_PLIC_BASE_ADDR);
+  CHECK_DIF_OK(dif_rv_plic_init(base_addr, plic_ctx.rv_plic));
+
+  // Enable interrupts in HMAC IP.
+  CHECK_DIF_OK(dif_hmac_irq_set_enabled(hmac_ctx.hmac, kDifHmacIrqHmacDone,
+                                        kDifToggleEnabled));
+  CHECK_DIF_OK(dif_hmac_irq_set_enabled(hmac_ctx.hmac, kDifHmacIrqFifoEmpty,
+                                        kDifToggleEnabled));
+
+  // Enable interrupts in PLIC.
+  rv_plic_testutils_irq_range_enable(plic_ctx.rv_plic, plic_ctx.hart_id,
+                                     kTopEarlgreyPlicIrqIdHmacHmacDone,
+                                     kTopEarlgreyPlicIrqIdHmacFifoEmpty);
+
+  // Enable interrupts in Ibex.
+  irq_global_ctrl(true);
+  irq_external_ctrl(true);
+}
+
+/**
+ * Read and compare the length of the message in the HMAC engine to the length
+ * of the message sent in bits.
+ */
+static void check_message_length(uint64_t expected_sent_bits) {
+  uint64_t sent_bits;
+  CHECK_DIF_OK(dif_hmac_get_message_length(&hmac, &sent_bits));
+
+  // 64bit formatting is not supported, so split into hi and lo hex 32bit
+  // values. These should appear as 64bit hex values in the debug output.
+  CHECK(expected_sent_bits == sent_bits,
+        "Message length mismatch. "
+        "Expected 0x%08x%08x bits but got 0x%08x%08x bits.",
+        (uint32_t)(expected_sent_bits >> 32), (uint32_t)expected_sent_bits,
+        (uint32_t)(sent_bits >> 32), (uint32_t)sent_bits);
+}
+
+bool test_main() {
+  mmio_region_t base_addr = mmio_region_from_addr(TOP_EARLGREY_HMAC_BASE_ADDR);
+  CHECK_DIF_OK(dif_hmac_init(base_addr, &hmac));
+
+  enable_irqs();
+
+  // The purpose of this test is to ensure that HMAC empty and done interrupts
+  // assert when the conditions are met. Digest is not verified by this
+  // test, which means that a "dummy" data will suffice.
+  size_t sent_bytes;
+  char data[4] = {0xaa, 0xbb, 0xcc, 0xdd};
+  CHECK_DIF_OK(dif_hmac_mode_sha256_start(&hmac, kHmacTransactionConfig));
+  hmac_ctx.expected_irq = kDifHmacIrqFifoEmpty;
+  CHECK_DIF_OK(
+      dif_hmac_fifo_push(&hmac, &data[0], ARRAYSIZE(data), &sent_bytes));
+  check_message_length(32);
+
+  // Spin waiting for the "empty" interrupt.
+  IBEX_SPIN_FOR(irq_serviced == hmac_ctx.expected_irq,
+                kHmacEncEmptyTimeoutUsec);
+
+  // Race conditions could result in a stale value due to `hmac_empty_irq`
+  // being set in the ISR, however, in practice that does not matter.
+  hmac_ctx.expected_irq = kDifHmacIrqHmacDone;
+  CHECK_DIF_OK(dif_hmac_process(&hmac));
+  LOG_INFO("Waiting for HMAC to finish");
+
+  // Spin waiting for the "done" interrupt.
+  IBEX_SPIN_FOR(irq_serviced == hmac_ctx.expected_irq,
+                kHmacEncFinishTimeoutUsec);
+
+  // Finish the HMAC operation.
+  dif_hmac_digest_t dummy_digest;
+  CHECK_DIF_OK(dif_hmac_finish(&hmac, &dummy_digest));
+
+  return true;
+}
diff --git a/sw/device/tests/meson.build b/sw/device/tests/meson.build
index f730c75..fc665c4 100644
--- a/sw/device/tests/meson.build
+++ b/sw/device/tests/meson.build
@@ -907,6 +907,29 @@
   }
 }
 
+hmac_enc_irq_test_lib = declare_dependency(
+  link_with: static_library(
+    'hmac_enc_irq_test_lib',
+    sources: ['hmac_enc_irq_test.c'],
+    dependencies: [
+      sw_lib_dif_hmac,
+      sw_lib_dif_rv_plic,
+      sw_lib_irq,
+      sw_lib_mmio,
+      sw_lib_runtime_ibex,
+      sw_lib_runtime_log,
+      sw_lib_testing_isr_testutils,
+      sw_lib_testing_rv_plic_testutils,
+      top_earlgrey,
+    ],
+  ),
+)
+sw_tests += {
+  'hmac_enc_irq_test': {
+    'library': hmac_enc_irq_test_lib,
+  }
+}
+
 ###############################################################################
 # Auto-generated tests
 ###############################################################################