[dif, kmac] Add function to read the kmac error

This fixes lowRISC/OpenTitan#6248

Signed-off-by: Douglas Reis <doreis@lowrisc.org>
diff --git a/sw/device/lib/dif/dif_kmac.c b/sw/device/lib/dif/dif_kmac.c
index 180f965..0f2a31c 100644
--- a/sw/device/lib/dif/dif_kmac.c
+++ b/sw/device/lib/dif/dif_kmac.c
@@ -105,7 +105,7 @@
  *
  * If the hardware is not idle then the `CFG` register is locked.
  *
- * @param params Hardware parameters.
+ * @param kmac Handle.
  * @returns Whether the hardware is currently idle or not.
  */
 static bool is_state_idle(const dif_kmac_t *kmac) {
@@ -119,7 +119,7 @@
  *
  * Note that writes to the message FIFO may still block if it is full.
  *
- * @param params Hardware parameters.
+ * @param kmac Handle.
  * @returns Whether the hardware is currently absorbing or not.
  */
 static bool is_state_absorb(const dif_kmac_t *kmac) {
@@ -131,7 +131,7 @@
  * Report whether the hardware is currently in the squeeze state which means
  * that the output state is valid and may be read by software.
  *
- * @param params Hardware parameters.
+ * @param kmac Handle.
  * @returns Whether the hardware is currently in the squeeze state or not.
  */
 static bool is_state_squeeze(const dif_kmac_t *kmac) {
@@ -139,6 +139,38 @@
   return bitfield_bit32_read(reg, KMAC_STATUS_SHA3_SQUEEZE_BIT);
 }
 
+/**
+ * Report whether the hardware has indicated a error.
+ *
+ * @param kmac Handle.
+ * @returns True if an error occurred, False otherwise.
+ */
+static bool has_error_occurred(const dif_kmac_t *kmac) {
+  uint32_t reg =
+      mmio_region_read32(kmac->base_addr, KMAC_INTR_STATE_REG_OFFSET);
+  return bitfield_bit32_read(reg, KMAC_INTR_STATE_KMAC_ERR_BIT);
+}
+
+/**
+ * Poll until the status register is in the 'absorb' state or int state register
+ * has indicated an error.
+ *
+ * @param kmac
+ * @return dif_result
+ */
+static dif_result_t poll_state(const dif_kmac_t *kmac, uint32_t flag) {
+  while (true) {
+    uint32_t reg = mmio_region_read32(kmac->base_addr, KMAC_STATUS_REG_OFFSET);
+    if (bitfield_bit32_read(reg, flag)) {
+      break;
+    }
+    if (has_error_occurred(kmac)) {
+      return kDifError;
+    }
+  }
+  return kDifOk;
+}
+
 dif_result_t dif_kmac_configure(dif_kmac_t *kmac, dif_kmac_config_t config) {
   if (kmac == NULL) {
     return kDifBadArg;
@@ -287,14 +319,7 @@
   mmio_region_write32(kmac->base_addr, KMAC_CMD_REG_OFFSET, cmd_reg);
 
   // Poll until the status register is in the 'absorb' state.
-  while (true) {
-    if (is_state_absorb(kmac)) {
-      break;
-    }
-    // TODO(#6248): check for error.
-  }
-
-  return kDifOk;
+  return poll_state(kmac, KMAC_STATUS_SHA3_ABSORB_BIT);
 }
 
 dif_result_t dif_kmac_mode_shake_start(
@@ -343,15 +368,7 @@
       bitfield_field32_write(0, KMAC_CMD_CMD_FIELD, KMAC_CMD_CMD_VALUE_START);
   mmio_region_write32(kmac->base_addr, KMAC_CMD_REG_OFFSET, cmd_reg);
 
-  // Poll until the status register is in the 'absorb' state.
-  while (true) {
-    if (is_state_absorb(kmac)) {
-      break;
-    }
-    // TODO(#6248): check for error.
-  }
-
-  return kDifOk;
+  return poll_state(kmac, KMAC_STATUS_SHA3_ABSORB_BIT);
 }
 
 dif_result_t dif_kmac_mode_cshake_start(
@@ -451,15 +468,7 @@
       bitfield_field32_write(0, KMAC_CMD_CMD_FIELD, KMAC_CMD_CMD_VALUE_START);
   mmio_region_write32(kmac->base_addr, KMAC_CMD_REG_OFFSET, cmd_reg);
 
-  // Poll until the status register is in the 'absorb' state.
-  while (true) {
-    if (is_state_absorb(kmac)) {
-      break;
-    }
-    // TODO(#6248): check for error.
-  }
-
-  return kDifOk;
+  return poll_state(kmac, KMAC_STATUS_SHA3_ABSORB_BIT);
 }
 
 dif_result_t dif_kmac_mode_kmac_start(
@@ -574,13 +583,7 @@
       bitfield_field32_write(0, KMAC_CMD_CMD_FIELD, KMAC_CMD_CMD_VALUE_START);
   mmio_region_write32(kmac->base_addr, KMAC_CMD_REG_OFFSET, cmd_reg);
 
-  // Poll until the status register is in the 'absorb' state.
-  while (true) {
-    if (is_state_absorb(kmac)) {
-      break;
-    }
-    // TODO(#6248): check for error.
-  }
+  return poll_state(kmac, KMAC_STATUS_SHA3_ABSORB_BIT);
 
   return kDifOk;
 }
@@ -805,3 +808,13 @@
 
   return kDifOk;
 }
+
+dif_result_t dif_kmac_get_error(const dif_kmac_t *kmac,
+                                dif_kmac_error_t *error) {
+  if (kmac == NULL || error == NULL) {
+    return kDifBadArg;
+  }
+
+  *error = mmio_region_read32(kmac->base_addr, KMAC_ERR_CODE_REG_OFFSET);
+  return kDifOk;
+}
diff --git a/sw/device/lib/dif/dif_kmac.h b/sw/device/lib/dif/dif_kmac.h
index 89a96f8..12c9c18 100644
--- a/sw/device/lib/dif/dif_kmac.h
+++ b/sw/device/lib/dif/dif_kmac.h
@@ -666,7 +666,11 @@
                           dif_kmac_operation_state_t *operation_state);
 
 /**
- * Get the current error code.
+ * Read the kmac error register to get the error code indicated the interrupt
+ * state.
+ *
+ * This function should be called in case of any of the `start` functions
+ * returns `kDifError`.
  *
  * @param kmac A KMAC handle.
  * @param[out] error The current error code.
diff --git a/sw/device/lib/dif/dif_kmac_unittest.cc b/sw/device/lib/dif/dif_kmac_unittest.cc
index 3c77793..f56f6b5 100644
--- a/sw/device/lib/dif/dif_kmac_unittest.cc
+++ b/sw/device/lib/dif/dif_kmac_unittest.cc
@@ -717,4 +717,33 @@
   EXPECT_DIF_BADARG(dif_kmac_get_status(&kmac_, nullptr));
 }
 
+class KmacGetErrorTest : public KmacTest {
+ protected:
+  static constexpr std::array<dif_kmac_error_t, 7> kErrors = {
+      kDifErrorNone,
+      kDifErrorKeyNotValid,
+      kDifErrorSoftwarePushedMessageFifo,
+      kDifErrorSoftwarePushedWrongCommand,
+      kDifErrorEntropyWaitTimerExpired,
+      kDifErrorEntropyModeIncorrect,
+      kDifErrorUnknownError};
+  dif_kmac_error_t error_;
+  KmacGetErrorTest() { op_state_.squeezing = true; }
+};
+constexpr std::array<dif_kmac_error_t, 7> KmacGetErrorTest::kErrors;
+
+TEST_F(KmacGetErrorTest, Success) {
+  for (auto err : kErrors) {
+    EXPECT_READ32(KMAC_ERR_CODE_REG_OFFSET, err);
+    EXPECT_DIF_OK(dif_kmac_get_error(&kmac_, &error_));
+    EXPECT_EQ(error_, err);
+  }
+}
+
+TEST_F(KmacGetErrorTest, BadArg) {
+  EXPECT_DIF_BADARG(dif_kmac_get_error(nullptr, &error_));
+
+  EXPECT_DIF_BADARG(dif_kmac_get_error(&kmac_, nullptr));
+}
+
 }  // namespace dif_kmac_unittest