[dif/otp_ctrl] add DIF to check if a partition digest is computed

This adds a DIF to check if the partition's digest has been computed,
and therefore, determine if a partition has been write-locked.

This fixes #12588.

Signed-off-by: Timothy Trippel <ttrippel@google.com>
diff --git a/sw/device/lib/dif/dif_otp_ctrl.c b/sw/device/lib/dif/dif_otp_ctrl.c
index 7497fca..7ef720b 100644
--- a/sw/device/lib/dif/dif_otp_ctrl.c
+++ b/sw/device/lib/dif/dif_otp_ctrl.c
@@ -612,6 +612,65 @@
   return kDifOk;
 }
 
+static bool get_digest_regs(dif_otp_ctrl_partition_t partition, ptrdiff_t *reg0,
+                            ptrdiff_t *reg1) {
+  switch (partition) {
+    case kDifOtpCtrlPartitionVendorTest:
+      *reg0 = OTP_CTRL_VENDOR_TEST_DIGEST_0_REG_OFFSET;
+      *reg1 = OTP_CTRL_VENDOR_TEST_DIGEST_1_REG_OFFSET;
+      break;
+    case kDifOtpCtrlPartitionCreatorSwCfg:
+      *reg0 = OTP_CTRL_CREATOR_SW_CFG_DIGEST_0_REG_OFFSET;
+      *reg1 = OTP_CTRL_CREATOR_SW_CFG_DIGEST_1_REG_OFFSET;
+      break;
+    case kDifOtpCtrlPartitionOwnerSwCfg:
+      *reg0 = OTP_CTRL_OWNER_SW_CFG_DIGEST_0_REG_OFFSET;
+      *reg1 = OTP_CTRL_OWNER_SW_CFG_DIGEST_1_REG_OFFSET;
+      break;
+    case kDifOtpCtrlPartitionHwCfg:
+      *reg0 = OTP_CTRL_HW_CFG_DIGEST_0_REG_OFFSET;
+      *reg1 = OTP_CTRL_HW_CFG_DIGEST_1_REG_OFFSET;
+      break;
+    case kDifOtpCtrlPartitionSecret0:
+      *reg0 = OTP_CTRL_SECRET0_DIGEST_0_REG_OFFSET;
+      *reg1 = OTP_CTRL_SECRET0_DIGEST_1_REG_OFFSET;
+      break;
+    case kDifOtpCtrlPartitionSecret1:
+      *reg0 = OTP_CTRL_SECRET1_DIGEST_0_REG_OFFSET;
+      *reg1 = OTP_CTRL_SECRET1_DIGEST_1_REG_OFFSET;
+      break;
+    case kDifOtpCtrlPartitionSecret2:
+      *reg0 = OTP_CTRL_SECRET2_DIGEST_0_REG_OFFSET;
+      *reg1 = OTP_CTRL_SECRET2_DIGEST_1_REG_OFFSET;
+      break;
+    default:
+      return false;
+  }
+
+  return true;
+}
+
+dif_result_t dif_otp_ctrl_is_digest_computed(const dif_otp_ctrl_t *otp,
+                                             dif_otp_ctrl_partition_t partition,
+                                             bool *is_computed) {
+  if (otp == NULL || is_computed == NULL) {
+    return kDifBadArg;
+  }
+
+  ptrdiff_t reg0, reg1;
+  if (!get_digest_regs(partition, &reg0, &reg1)) {
+    return kDifBadArg;
+  }
+
+  uint64_t value = mmio_region_read32(otp->base_addr, reg1);
+  value <<= 32;
+  value |= mmio_region_read32(otp->base_addr, reg0);
+
+  *is_computed = value != 0;
+
+  return kDifOk;
+}
+
 dif_result_t dif_otp_ctrl_get_digest(const dif_otp_ctrl_t *otp,
                                      dif_otp_ctrl_partition_t partition,
                                      uint64_t *digest) {
@@ -619,43 +678,9 @@
     return kDifBadArg;
   }
 
-  // The LC partition does not have a digest.
-  if (partition == kDifOtpCtrlPartitionLifeCycle) {
-    return kDifBadArg;
-  }
-
   ptrdiff_t reg0, reg1;
-  switch (partition) {
-    case kDifOtpCtrlPartitionVendorTest:
-      reg0 = OTP_CTRL_VENDOR_TEST_DIGEST_0_REG_OFFSET;
-      reg1 = OTP_CTRL_VENDOR_TEST_DIGEST_1_REG_OFFSET;
-      break;
-    case kDifOtpCtrlPartitionCreatorSwCfg:
-      reg0 = OTP_CTRL_CREATOR_SW_CFG_DIGEST_0_REG_OFFSET;
-      reg1 = OTP_CTRL_CREATOR_SW_CFG_DIGEST_1_REG_OFFSET;
-      break;
-    case kDifOtpCtrlPartitionOwnerSwCfg:
-      reg0 = OTP_CTRL_OWNER_SW_CFG_DIGEST_0_REG_OFFSET;
-      reg1 = OTP_CTRL_OWNER_SW_CFG_DIGEST_1_REG_OFFSET;
-      break;
-    case kDifOtpCtrlPartitionHwCfg:
-      reg0 = OTP_CTRL_HW_CFG_DIGEST_0_REG_OFFSET;
-      reg1 = OTP_CTRL_HW_CFG_DIGEST_1_REG_OFFSET;
-      break;
-    case kDifOtpCtrlPartitionSecret0:
-      reg0 = OTP_CTRL_SECRET0_DIGEST_0_REG_OFFSET;
-      reg1 = OTP_CTRL_SECRET0_DIGEST_1_REG_OFFSET;
-      break;
-    case kDifOtpCtrlPartitionSecret1:
-      reg0 = OTP_CTRL_SECRET1_DIGEST_0_REG_OFFSET;
-      reg1 = OTP_CTRL_SECRET1_DIGEST_1_REG_OFFSET;
-      break;
-    case kDifOtpCtrlPartitionSecret2:
-      reg0 = OTP_CTRL_SECRET2_DIGEST_0_REG_OFFSET;
-      reg1 = OTP_CTRL_SECRET2_DIGEST_1_REG_OFFSET;
-      break;
-    default:
-      return kDifBadArg;
+  if (!get_digest_regs(partition, &reg0, &reg1)) {
+    return kDifBadArg;
   }
 
   uint64_t value = mmio_region_read32(otp->base_addr, reg1);
diff --git a/sw/device/lib/dif/dif_otp_ctrl.h b/sw/device/lib/dif/dif_otp_ctrl.h
index 2676913..4558b24 100644
--- a/sw/device/lib/dif/dif_otp_ctrl.h
+++ b/sw/device/lib/dif/dif_otp_ctrl.h
@@ -521,6 +521,24 @@
                                      uint64_t digest);
 
 /**
+ * Checks if the digest value for the given partition has been computed. Once a
+ * digest has been computed for a partition, the partition is write-locked
+ * (additionally, read-locked if the partition is secret).
+ *
+ * The lifecycle partition does not have a digest, and checking if this region
+ * has a computed digest will return an error.
+ *
+ * @param otp An OTP handle.
+ * @param partition The partition to check the digest of.
+ * @param[out] is_computed Indicates if the digest has been computed.
+ * @return The result of the operation.
+ */
+OT_WARN_UNUSED_RESULT
+dif_result_t dif_otp_ctrl_is_digest_computed(const dif_otp_ctrl_t *otp,
+                                             dif_otp_ctrl_partition_t partition,
+                                             bool *is_computed);
+
+/**
  * Gets the buffered digest value for the given partition.
  *
  * Note that this value is only updated when the device is reset; if the digest
diff --git a/sw/device/lib/dif/dif_otp_ctrl_unittest.cc b/sw/device/lib/dif/dif_otp_ctrl_unittest.cc
index d87884c..f8a4821 100644
--- a/sw/device/lib/dif/dif_otp_ctrl_unittest.cc
+++ b/sw/device/lib/dif/dif_otp_ctrl_unittest.cc
@@ -496,6 +496,40 @@
                                             /*digest=*/0xabcdef0000abcdef));
 }
 
+class IsDigestComputed : public OtpTest {};
+
+TEST_F(IsDigestComputed, NullArgs) {
+  bool is_computed;
+  EXPECT_DIF_BADARG(dif_otp_ctrl_is_digest_computed(
+      nullptr, kDifOtpCtrlPartitionSecret2, &is_computed));
+  EXPECT_DIF_BADARG(dif_otp_ctrl_is_digest_computed(
+      &otp_, kDifOtpCtrlPartitionSecret2, nullptr));
+  EXPECT_DIF_BADARG(dif_otp_ctrl_is_digest_computed(
+      nullptr, kDifOtpCtrlPartitionSecret2, nullptr));
+}
+
+TEST_F(IsDigestComputed, BadPartition) {
+  bool is_computed;
+  EXPECT_DIF_BADARG(dif_otp_ctrl_is_digest_computed(
+      &otp_, kDifOtpCtrlPartitionLifeCycle, &is_computed));
+}
+
+TEST_F(IsDigestComputed, Success) {
+  bool is_computed;
+
+  EXPECT_READ32(OTP_CTRL_SECRET2_DIGEST_1_REG_OFFSET, 0x98abcdef);
+  EXPECT_READ32(OTP_CTRL_SECRET2_DIGEST_0_REG_OFFSET, 0xabcdef01);
+  EXPECT_DIF_OK(dif_otp_ctrl_is_digest_computed(
+      &otp_, kDifOtpCtrlPartitionSecret2, &is_computed));
+  EXPECT_TRUE(is_computed);
+
+  EXPECT_READ32(OTP_CTRL_SECRET2_DIGEST_1_REG_OFFSET, 0);
+  EXPECT_READ32(OTP_CTRL_SECRET2_DIGEST_0_REG_OFFSET, 0);
+  EXPECT_DIF_OK(dif_otp_ctrl_is_digest_computed(
+      &otp_, kDifOtpCtrlPartitionSecret2, &is_computed));
+  EXPECT_FALSE(is_computed);
+}
+
 struct DigestParams {
   dif_otp_ctrl_partition_t partition;
   ptrdiff_t reg0, reg1;