From a30fd54a5d1c606185bc2214fb7aeaa60d54d6f5 Mon Sep 17 00:00:00 2001 From: Guillermo Maturana Date: Sat, 2 Sep 2023 04:18:05 +0000 Subject: [PATCH] [otp_ctrl,gen] Add templates for difs This just copies the old implementation without modifications except for the header comment. Signed-off-by: Guillermo Maturana --- hw/ip/otp_ctrl/data/dif_otp_ctrl.c.tpl | 699 +++++++++++++++ hw/ip/otp_ctrl/data/dif_otp_ctrl.h.tpl | 585 +++++++++++++ .../data/dif_otp_ctrl_unittest.cc.tpl | 821 ++++++++++++++++++ sw/device/lib/dif/dif_otp_ctrl.c | 106 ++- sw/device/lib/dif/dif_otp_ctrl.h | 71 +- sw/device/lib/dif/dif_otp_ctrl_unittest.cc | 541 ++++++++++-- util/design/gen-otp-mmap.py | 10 + 7 files changed, 2681 insertions(+), 152 deletions(-) create mode 100644 hw/ip/otp_ctrl/data/dif_otp_ctrl.c.tpl create mode 100644 hw/ip/otp_ctrl/data/dif_otp_ctrl.h.tpl create mode 100644 hw/ip/otp_ctrl/data/dif_otp_ctrl_unittest.cc.tpl diff --git a/hw/ip/otp_ctrl/data/dif_otp_ctrl.c.tpl b/hw/ip/otp_ctrl/data/dif_otp_ctrl.c.tpl new file mode 100644 index 0000000000000..c55842e1ebefb --- /dev/null +++ b/hw/ip/otp_ctrl/data/dif_otp_ctrl.c.tpl @@ -0,0 +1,699 @@ +// Copyright lowRISC contributors. +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 +${gen_comment} +<% +from topgen.lib import Name + +parts = otp_mmap.config["partitions"] +digest_parts = [part for part in parts if + part["hw_digest"] or part["sw_digest"]] +read_locked_csr_parts = [part for part in parts if part["read_lock"] == "CSR"] +secret_parts = [part for part in parts if part["secret"]] +%>\ +#include "sw/device/lib/dif/dif_otp_ctrl.h" + +#include + +#include "sw/device/lib/base/bitfield.h" +#include "sw/device/lib/base/macros.h" +#include "sw/device/lib/dif/dif_base.h" + +#include "otp_ctrl_regs.h" // Generated. + +/** + * Checks if integrity/consistency-check-related operations are locked. + * + * This is a convenience function to avoid superfluous error-checking in all the + * functions that can be locked out by this register. + * + * @param check_config True to check the config regwen. False to check the + * trigger regwen. + */ +static bool checks_are_locked(const dif_otp_ctrl_t *otp, bool check_config) { + ptrdiff_t reg_offset = check_config + ? OTP_CTRL_CHECK_REGWEN_REG_OFFSET + : OTP_CTRL_CHECK_TRIGGER_REGWEN_REG_OFFSET; + size_t regwen_bit = + check_config ? OTP_CTRL_CHECK_REGWEN_CHECK_REGWEN_BIT + : OTP_CTRL_CHECK_TRIGGER_REGWEN_CHECK_TRIGGER_REGWEN_BIT; + uint32_t locked = mmio_region_read32(otp->base_addr, reg_offset); + return !bitfield_bit32_read(locked, regwen_bit); +} + +dif_result_t dif_otp_ctrl_configure(const dif_otp_ctrl_t *otp, + dif_otp_ctrl_config_t config) { + if (otp == NULL) { + return kDifBadArg; + } + if (checks_are_locked(otp, /*check_config=*/true)) { + return kDifLocked; + } + + mmio_region_write32(otp->base_addr, OTP_CTRL_CHECK_TIMEOUT_REG_OFFSET, + config.check_timeout); + mmio_region_write32(otp->base_addr, + OTP_CTRL_INTEGRITY_CHECK_PERIOD_REG_OFFSET, + config.integrity_period_mask); + mmio_region_write32(otp->base_addr, + OTP_CTRL_CONSISTENCY_CHECK_PERIOD_REG_OFFSET, + config.consistency_period_mask); + + return kDifOk; +} + +dif_result_t dif_otp_ctrl_check_integrity(const dif_otp_ctrl_t *otp) { + if (otp == NULL) { + return kDifBadArg; + } + if (checks_are_locked(otp, /*check_config=*/false)) { + return kDifLocked; + } + + uint32_t reg = + bitfield_bit32_write(0, OTP_CTRL_CHECK_TRIGGER_INTEGRITY_BIT, true); + mmio_region_write32(otp->base_addr, OTP_CTRL_CHECK_TRIGGER_REG_OFFSET, reg); + + return kDifOk; +} + +dif_result_t dif_otp_ctrl_check_consistency(const dif_otp_ctrl_t *otp) { + if (otp == NULL) { + return kDifBadArg; + } + if (checks_are_locked(otp, /*check_config=*/false)) { + return kDifLocked; + } + + uint32_t reg = + bitfield_bit32_write(0, OTP_CTRL_CHECK_TRIGGER_CONSISTENCY_BIT, true); + mmio_region_write32(otp->base_addr, OTP_CTRL_CHECK_TRIGGER_REG_OFFSET, reg); + + return kDifOk; +} + +dif_result_t dif_otp_ctrl_lock_config(const dif_otp_ctrl_t *otp) { + if (otp == NULL) { + return kDifBadArg; + } + + uint32_t reg = + bitfield_bit32_write(0, OTP_CTRL_CHECK_REGWEN_CHECK_REGWEN_BIT, false); + mmio_region_write32(otp->base_addr, OTP_CTRL_CHECK_REGWEN_REG_OFFSET, reg); + + return kDifOk; +} + +dif_result_t dif_otp_ctrl_config_is_locked(const dif_otp_ctrl_t *otp, + bool *is_locked) { + if (otp == NULL || is_locked == NULL) { + return kDifBadArg; + } + + *is_locked = checks_are_locked(otp, /*check_config=*/true); + return kDifOk; +} + +dif_result_t dif_otp_ctrl_lock_check_trigger(const dif_otp_ctrl_t *otp) { + if (otp == NULL) { + return kDifBadArg; + } + + uint32_t reg = bitfield_bit32_write( + 0, OTP_CTRL_CHECK_TRIGGER_REGWEN_CHECK_TRIGGER_REGWEN_BIT, false); + mmio_region_write32(otp->base_addr, OTP_CTRL_CHECK_TRIGGER_REGWEN_REG_OFFSET, + reg); + + return kDifOk; +} + +dif_result_t dif_otp_ctrl_check_trigger_is_locked(const dif_otp_ctrl_t *otp, + bool *is_locked) { + if (otp == NULL || is_locked == NULL) { + return kDifBadArg; + } + + *is_locked = checks_are_locked(otp, /*check_config=*/false); + return kDifOk; +} + +static bool sw_read_lock_reg_offset(dif_otp_ctrl_partition_t partition, + ptrdiff_t *reg_offset, + bitfield_bit32_index_t *index) { + switch (partition) { +% for part in read_locked_csr_parts: +<% + part_name = Name.from_snake_case(part["name"]) + part_name_define = part_name.as_c_define() + index_line = f"*index = OTP_CTRL_{part_name_define}_READ_LOCK_{part_name_define}_READ_LOCK_BIT;" +%>\ + case kDifOtpCtrlPartition${part_name.as_camel_case()}: + *reg_offset = OTP_CTRL_${part_name_define}_READ_LOCK_REG_OFFSET; + % if len(index_line) > 80 - 6: + *index = + OTP_CTRL_${part_name_define}_READ_LOCK_${part_name_define}_READ_LOCK_BIT; + % else: + ${index_line} + % endif + break; +% endfor + default: + return false; + } + return true; +} + +dif_result_t dif_otp_ctrl_lock_reading(const dif_otp_ctrl_t *otp, + dif_otp_ctrl_partition_t partition) { + if (otp == NULL) { + return kDifBadArg; + } + + ptrdiff_t offset; + bitfield_bit32_index_t index; + if (!sw_read_lock_reg_offset(partition, &offset, &index)) { + return kDifBadArg; + } + + uint32_t busy = mmio_region_read32(otp->base_addr, + OTP_CTRL_DIRECT_ACCESS_REGWEN_REG_OFFSET); + if (!bitfield_bit32_read( + busy, OTP_CTRL_DIRECT_ACCESS_REGWEN_DIRECT_ACCESS_REGWEN_BIT)) { + return kDifUnavailable; + } + + uint32_t reg = bitfield_bit32_write(0, index, false); + mmio_region_write32(otp->base_addr, offset, reg); + + return kDifOk; +} + +dif_result_t dif_otp_ctrl_reading_is_locked(const dif_otp_ctrl_t *otp, + dif_otp_ctrl_partition_t partition, + bool *is_locked) { + if (otp == NULL || is_locked == NULL) { + return kDifBadArg; + } + + ptrdiff_t offset; + bitfield_bit32_index_t index; + if (!sw_read_lock_reg_offset(partition, &offset, &index)) { + return kDifBadArg; + } + + uint32_t reg = mmio_region_read32(otp->base_addr, offset); + *is_locked = !bitfield_bit32_read(reg, index); + return kDifOk; +} + +dif_result_t dif_otp_ctrl_get_status(const dif_otp_ctrl_t *otp, + dif_otp_ctrl_status_t *status) { + if (otp == NULL || status == NULL) { + return kDifBadArg; + } + + static const bitfield_bit32_index_t kIndices[] = { +% for part in parts: +<% + part_name = Name.from_snake_case(part["name"]) + lhs = f'[kDifOtpCtrlStatusCode{part_name.as_camel_case()}Error]' + rhs = f'OTP_CTRL_STATUS_{part_name.as_c_define()}_ERROR_BIT' + line = f'{lhs} = {rhs},' +%>\ + % if len(line) > 80 - 6: + ${lhs} = + ${rhs}, + % else: + ${line} + % endif +% endfor + [kDifOtpCtrlStatusCodeDaiError] = OTP_CTRL_STATUS_DAI_ERROR_BIT, + [kDifOtpCtrlStatusCodeLciError] = OTP_CTRL_STATUS_LCI_ERROR_BIT, + [kDifOtpCtrlStatusCodeTimeoutError] = OTP_CTRL_STATUS_TIMEOUT_ERROR_BIT, + [kDifOtpCtrlStatusCodeLfsrError] = OTP_CTRL_STATUS_LFSR_FSM_ERROR_BIT, + [kDifOtpCtrlStatusCodeScramblingError] = + OTP_CTRL_STATUS_SCRAMBLING_FSM_ERROR_BIT, + [kDifOtpCtrlStatusCodeKdfError] = OTP_CTRL_STATUS_KEY_DERIV_FSM_ERROR_BIT, + [kDifOtpCtrlStatusCodeBusIntegError] = + OTP_CTRL_STATUS_BUS_INTEG_ERROR_BIT, + [kDifOtpCtrlStatusCodeDaiIdle] = OTP_CTRL_STATUS_DAI_IDLE_BIT, + [kDifOtpCtrlStatusCodeCheckPending] = OTP_CTRL_STATUS_CHECK_PENDING_BIT, + }; + + status->codes = 0; + uint32_t status_code = + mmio_region_read32(otp->base_addr, OTP_CTRL_STATUS_REG_OFFSET); + for (int i = 0; i < ARRAYSIZE(kIndices); ++i) { + // If the error is not present at all, we clear its cause bit if relevant, + // and bail immediately. + if (!bitfield_bit32_read(status_code, kIndices[i])) { + if (i <= kDifOtpCtrlStatusCodeHasCauseLast) { + status->causes[i] = kDifOtpCtrlErrorOk; + } + continue; + } + + status->codes = + bitfield_bit32_write(status->codes, (bitfield_bit32_index_t)i, true); + + if (i <= kDifOtpCtrlStatusCodeHasCauseLast) { + bitfield_field32_t field; + field = (bitfield_field32_t){ + .mask = OTP_CTRL_ERR_CODE_0_ERR_CODE_0_MASK, + .index = OTP_CTRL_ERR_CODE_0_ERR_CODE_0_OFFSET, + }; + + ptrdiff_t address = + OTP_CTRL_ERR_CODE_0_REG_OFFSET + i * (ptrdiff_t)sizeof(uint32_t); + uint32_t error_code = mmio_region_read32(otp->base_addr, address); + + dif_otp_ctrl_error_t err; + switch (bitfield_field32_read(error_code, field)) { + case OTP_CTRL_ERR_CODE_0_ERR_CODE_0_VALUE_NO_ERROR: + err = kDifOtpCtrlErrorOk; + break; + case OTP_CTRL_ERR_CODE_0_ERR_CODE_0_VALUE_MACRO_ERROR: + err = kDifOtpCtrlErrorMacroUnspecified; + break; + case OTP_CTRL_ERR_CODE_0_ERR_CODE_0_VALUE_MACRO_ECC_CORR_ERROR: + err = kDifOtpCtrlErrorMacroRecoverableRead; + break; + case OTP_CTRL_ERR_CODE_0_ERR_CODE_0_VALUE_MACRO_ECC_UNCORR_ERROR: + err = kDifOtpCtrlErrorMacroUnrecoverableRead; + break; + case OTP_CTRL_ERR_CODE_0_ERR_CODE_0_VALUE_MACRO_WRITE_BLANK_ERROR: + err = kDifOtpCtrlErrorMacroBlankCheckFailed; + break; + case OTP_CTRL_ERR_CODE_0_ERR_CODE_0_VALUE_ACCESS_ERROR: + err = kDifOtpCtrlErrorLockedAccess; + break; + case OTP_CTRL_ERR_CODE_0_ERR_CODE_0_VALUE_CHECK_FAIL_ERROR: + err = kDifOtpCtrlErrorBackgroundCheckFailed; + break; + case OTP_CTRL_ERR_CODE_0_ERR_CODE_0_VALUE_FSM_STATE_ERROR: + err = kDifOtpCtrlErrorFsmBadState; + break; + default: + return kDifError; + } + status->causes[i] = err; + } + } + + return kDifOk; +} + +typedef struct partition_info { + /** + * The absolute OTP address at which this partition starts. + */ + uint32_t start_addr; + /** + * The length of this partition, in bytes, including the digest. + * + * If the partition has a digest, it is expected to be at address + * `start_addr + len - sizeof(uint64_t)`. + */ + uint32_t len; + /** + * The alignment mask for this partition. + * + * A valid address for this partition must be such that + * `addr & align_mask == 0`. + */ + uint32_t align_mask; + + /** + * Whether this is a software-managed partition with a software-managed + * digest. + */ + bool is_software; + + /** + * Whether this partition has a digest field. + */ + bool has_digest; +} partition_info_t; + +// This is generates too many lines with different formatting variants, so +// We opt to just disable formatting. +// clang-format off +static const partition_info_t kPartitions[] = { +% for part in parts: +<% + part_name = Name.from_snake_case(part["name"]) + part_name_camel = part_name.as_camel_case() + part_name_define = part_name.as_c_define() + has_digest = part["hw_digest"] or part["sw_digest"] +%>\ + [kDifOtpCtrlPartition${part_name_camel}] = { + .start_addr = OTP_CTRL_PARAM_${part_name_define}_OFFSET, + .len = OTP_CTRL_PARAM_${part_name_define}_SIZE, + .align_mask = ${"0x7" if part in secret_parts else "0x3"}, + .is_software = ${"true" if part["sw_digest"] else "false"}, + .has_digest = ${"true" if has_digest else "false"}}, +% endfor +}; +// clang-format on + +dif_result_t dif_otp_ctrl_relative_address(dif_otp_ctrl_partition_t partition, + uint32_t abs_address, + uint32_t *relative_address) { + *relative_address = 0; + + if (partition >= ARRAYSIZE(kPartitions)) { + return kDifBadArg; + } + + if ((abs_address & kPartitions[partition].align_mask) != 0) { + return kDifUnaligned; + } + + if (abs_address < kPartitions[partition].start_addr) { + return kDifOutOfRange; + } + + *relative_address = abs_address - kPartitions[partition].start_addr; + if (*relative_address >= kPartitions[partition].len) { + *relative_address = 0; + return kDifOutOfRange; + } + + return kDifOk; +} + +dif_result_t dif_otp_ctrl_dai_read_start(const dif_otp_ctrl_t *otp, + dif_otp_ctrl_partition_t partition, + uint32_t address) { + if (otp == NULL || partition >= ARRAYSIZE(kPartitions)) { + return kDifBadArg; + } + + if ((address & kPartitions[partition].align_mask) != 0) { + return kDifUnaligned; + } + + if (address >= kPartitions[partition].len) { + return kDifOutOfRange; + } + + uint32_t busy = mmio_region_read32(otp->base_addr, + OTP_CTRL_DIRECT_ACCESS_REGWEN_REG_OFFSET); + if (!bitfield_bit32_read( + busy, OTP_CTRL_DIRECT_ACCESS_REGWEN_DIRECT_ACCESS_REGWEN_BIT)) { + return kDifUnavailable; + } + + address += kPartitions[partition].start_addr; + mmio_region_write32(otp->base_addr, OTP_CTRL_DIRECT_ACCESS_ADDRESS_REG_OFFSET, + address); + + uint32_t cmd = + bitfield_bit32_write(0, OTP_CTRL_DIRECT_ACCESS_CMD_RD_BIT, true); + mmio_region_write32(otp->base_addr, OTP_CTRL_DIRECT_ACCESS_CMD_REG_OFFSET, + cmd); + + return kDifOk; +} + +dif_result_t dif_otp_ctrl_dai_read32_end(const dif_otp_ctrl_t *otp, + uint32_t *value) { + if (otp == NULL || value == NULL) { + return kDifBadArg; + } + + uint32_t busy = mmio_region_read32(otp->base_addr, + OTP_CTRL_DIRECT_ACCESS_REGWEN_REG_OFFSET); + if (!bitfield_bit32_read( + busy, OTP_CTRL_DIRECT_ACCESS_REGWEN_DIRECT_ACCESS_REGWEN_BIT)) { + return kDifUnavailable; + } + + *value = mmio_region_read32(otp->base_addr, + OTP_CTRL_DIRECT_ACCESS_RDATA_0_REG_OFFSET); + return kDifOk; +} + +dif_result_t dif_otp_ctrl_dai_read64_end(const dif_otp_ctrl_t *otp, + uint64_t *value) { + if (otp == NULL || value == NULL) { + return kDifBadArg; + } + + uint32_t busy = mmio_region_read32(otp->base_addr, + OTP_CTRL_DIRECT_ACCESS_REGWEN_REG_OFFSET); + if (!bitfield_bit32_read( + busy, OTP_CTRL_DIRECT_ACCESS_REGWEN_DIRECT_ACCESS_REGWEN_BIT)) { + return kDifUnavailable; + } + + *value = mmio_region_read32(otp->base_addr, + OTP_CTRL_DIRECT_ACCESS_RDATA_1_REG_OFFSET); + *value <<= 32; + *value |= mmio_region_read32(otp->base_addr, + OTP_CTRL_DIRECT_ACCESS_RDATA_0_REG_OFFSET); + return kDifOk; +} + +dif_result_t dif_otp_ctrl_dai_program32(const dif_otp_ctrl_t *otp, + dif_otp_ctrl_partition_t partition, + uint32_t address, uint32_t value) { + if (otp == NULL || partition >= ARRAYSIZE(kPartitions)) { + return kDifBadArg; + } + + // Ensure that we are writing to a 32-bit-access partition by checking that + // the alignment mask is 0b11. + // + // Note furthermore that the LC partition is *not* writeable, so we eject + // here. + if (kPartitions[partition].align_mask != 0x3 || + !kPartitions[partition].has_digest) { + return kDifError; + } + + if ((address & kPartitions[partition].align_mask) != 0) { + return kDifUnaligned; + } + + // NOTE: The bounds check is tightened here, since we disallow writing the + // digest directly. If the partition does not have a digest, no tightening is + // needed. + size_t digest_size = kPartitions[partition].has_digest * sizeof(uint64_t); + if (address >= kPartitions[partition].len - digest_size) { + return kDifOutOfRange; + } + + uint32_t busy = mmio_region_read32(otp->base_addr, + OTP_CTRL_DIRECT_ACCESS_REGWEN_REG_OFFSET); + if (!bitfield_bit32_read( + busy, OTP_CTRL_DIRECT_ACCESS_REGWEN_DIRECT_ACCESS_REGWEN_BIT)) { + return kDifUnavailable; + } + + address += kPartitions[partition].start_addr; + mmio_region_write32(otp->base_addr, OTP_CTRL_DIRECT_ACCESS_ADDRESS_REG_OFFSET, + address); + + mmio_region_write32(otp->base_addr, OTP_CTRL_DIRECT_ACCESS_WDATA_0_REG_OFFSET, + value); + + uint32_t cmd = + bitfield_bit32_write(0, OTP_CTRL_DIRECT_ACCESS_CMD_WR_BIT, true); + mmio_region_write32(otp->base_addr, OTP_CTRL_DIRECT_ACCESS_CMD_REG_OFFSET, + cmd); + + return kDifOk; +} + +dif_result_t dif_otp_ctrl_dai_program64(const dif_otp_ctrl_t *otp, + dif_otp_ctrl_partition_t partition, + uint32_t address, uint64_t value) { + if (otp == NULL || partition >= ARRAYSIZE(kPartitions)) { + return kDifBadArg; + } + + // Ensure that we are writing to a 64-bit-access partition by checking that + // the alignment mask is 0b111. + if (kPartitions[partition].align_mask != 0x7) { + return kDifError; + } + + if ((address & kPartitions[partition].align_mask) != 0) { + return kDifUnaligned; + } + + // NOTE: The bounds check is tightened here, since we disallow writing the + // digest directly. + size_t digest_size = sizeof(uint64_t); + if (address >= kPartitions[partition].len - digest_size) { + return kDifOutOfRange; + } + + uint32_t busy = mmio_region_read32(otp->base_addr, + OTP_CTRL_DIRECT_ACCESS_REGWEN_REG_OFFSET); + if (!bitfield_bit32_read( + busy, OTP_CTRL_DIRECT_ACCESS_REGWEN_DIRECT_ACCESS_REGWEN_BIT)) { + return kDifUnavailable; + } + + address += kPartitions[partition].start_addr; + mmio_region_write32(otp->base_addr, OTP_CTRL_DIRECT_ACCESS_ADDRESS_REG_OFFSET, + address); + + mmio_region_write32(otp->base_addr, OTP_CTRL_DIRECT_ACCESS_WDATA_0_REG_OFFSET, + value & UINT32_MAX); + mmio_region_write32(otp->base_addr, OTP_CTRL_DIRECT_ACCESS_WDATA_1_REG_OFFSET, + value >> 32); + + uint32_t cmd = + bitfield_bit32_write(0, OTP_CTRL_DIRECT_ACCESS_CMD_WR_BIT, true); + mmio_region_write32(otp->base_addr, OTP_CTRL_DIRECT_ACCESS_CMD_REG_OFFSET, + cmd); + + return kDifOk; +} + +dif_result_t dif_otp_ctrl_dai_digest(const dif_otp_ctrl_t *otp, + dif_otp_ctrl_partition_t partition, + uint64_t digest) { + if (otp == NULL || partition >= ARRAYSIZE(kPartitions)) { + return kDifBadArg; + } + + // The LC partition does not have a digest. + if (!kPartitions[partition].has_digest) { + return kDifError; + } + + // For software partitions, the digest must be nonzero; for all other + // partitions it must be zero. + bool is_sw = kPartitions[partition].is_software; + if (is_sw == (digest == 0)) { + return kDifBadArg; + } + + uint32_t busy = mmio_region_read32(otp->base_addr, + OTP_CTRL_DIRECT_ACCESS_REGWEN_REG_OFFSET); + if (!bitfield_bit32_read( + busy, OTP_CTRL_DIRECT_ACCESS_REGWEN_DIRECT_ACCESS_REGWEN_BIT)) { + return kDifUnavailable; + } + + uint32_t address = kPartitions[partition].start_addr; + if (is_sw) { + address += kPartitions[partition].len - sizeof(digest); + } + mmio_region_write32(otp->base_addr, OTP_CTRL_DIRECT_ACCESS_ADDRESS_REG_OFFSET, + address); + + if (digest != 0) { + mmio_region_write32(otp->base_addr, + OTP_CTRL_DIRECT_ACCESS_WDATA_0_REG_OFFSET, + digest & 0xffffffff); + mmio_region_write32(otp->base_addr, + OTP_CTRL_DIRECT_ACCESS_WDATA_1_REG_OFFSET, + digest >> 32); + } + + bitfield_bit32_index_t cmd_bit = is_sw + ? OTP_CTRL_DIRECT_ACCESS_CMD_WR_BIT + : OTP_CTRL_DIRECT_ACCESS_CMD_DIGEST_BIT; + uint32_t cmd = bitfield_bit32_write(0, cmd_bit, true); + mmio_region_write32(otp->base_addr, OTP_CTRL_DIRECT_ACCESS_CMD_REG_OFFSET, + cmd); + + return kDifOk; +} + +static bool get_digest_regs(dif_otp_ctrl_partition_t partition, ptrdiff_t *reg0, + ptrdiff_t *reg1) { + switch (partition) { +% for part in digest_parts: +<% + part_name = Name.from_snake_case(part["name"]) + part_name_define = part_name.as_c_define() +%>\ + case kDifOtpCtrlPartition${part_name.as_camel_case()}: + *reg0 = OTP_CTRL_${part_name_define}_DIGEST_0_REG_OFFSET; + *reg1 = OTP_CTRL_${part_name_define}_DIGEST_1_REG_OFFSET; + break; +% endfor + 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, ®0, ®1)) { + 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) { + if (otp == NULL || digest == NULL) { + return kDifBadArg; + } + + ptrdiff_t reg0, reg1; + if (!get_digest_regs(partition, ®0, ®1)) { + return kDifBadArg; + } + + uint64_t value = mmio_region_read32(otp->base_addr, reg1); + value <<= 32; + value |= mmio_region_read32(otp->base_addr, reg0); + + if (value == 0) { + return kDifError; + } + *digest = value; + + return kDifOk; +} + +dif_result_t dif_otp_ctrl_read_blocking(const dif_otp_ctrl_t *otp, + dif_otp_ctrl_partition_t partition, + uint32_t address, uint32_t *buf, + size_t len) { + if (otp == NULL || partition >= ARRAYSIZE(kPartitions) || buf == NULL) { + return kDifBadArg; + } + + if (!kPartitions[partition].is_software) { + return kDifError; + } + + if ((address & kPartitions[partition].align_mask) != 0) { + return kDifUnaligned; + } + + if (address + len >= kPartitions[partition].len) { + return kDifOutOfRange; + } + + uint32_t reg_offset = OTP_CTRL_SW_CFG_WINDOW_REG_OFFSET + + kPartitions[partition].start_addr + address; + mmio_region_memcpy_from_mmio32(otp->base_addr, reg_offset, buf, + len * sizeof(uint32_t)); + return kDifOk; +} diff --git a/hw/ip/otp_ctrl/data/dif_otp_ctrl.h.tpl b/hw/ip/otp_ctrl/data/dif_otp_ctrl.h.tpl new file mode 100644 index 0000000000000..feca1ff8641b9 --- /dev/null +++ b/hw/ip/otp_ctrl/data/dif_otp_ctrl.h.tpl @@ -0,0 +1,585 @@ +// Copyright lowRISC contributors. +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 +${gen_comment} +<% +from topgen.lib import Name + +parts = otp_mmap.config["partitions"] +digest_parts = [part for part in parts if + part["hw_digest"] == "true" or part["sw_digest"] == "true"] +read_locked_csr_parts = [part for part in parts if part["read_lock"] == "CSR"] +secret_parts = [part for part in parts if part["secret"] == "true"] +%>\ +#ifndef OPENTITAN_SW_DEVICE_LIB_DIF_DIF_OTP_CTRL_H_ +#define OPENTITAN_SW_DEVICE_LIB_DIF_DIF_OTP_CTRL_H_ + +/** + * @file + * @brief OTP Controller Device Interface + * Functions + */ + +#include + +#include "sw/device/lib/base/macros.h" +#include "sw/device/lib/base/mmio.h" +#include "sw/device/lib/dif/dif_base.h" + +#include "sw/device/lib/dif/autogen/dif_otp_ctrl_autogen.h" + +// Header Extern Guard (so header can be used from C and C++) +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + +/** + * A partition within OTP memory. + */ +typedef enum dif_otp_ctrl_partition { +% for part in parts: +<% + part_name = Name.from_snake_case(part["name"]) + short_desc = part["desc"].split(".")[0].strip().replace("\n", " ") + long_desc_lines = part["desc"].split(".", 1)[1].strip().splitlines() + long_desc = "\n".join([" *" + (" " if line else "") + line for + line in long_desc_lines]) +%>\ + /** + * ${short_desc}. + * +${long_desc} + */ + kDifOtpCtrlPartition${part_name.as_camel_case()}, +% endfor +} dif_otp_ctrl_partition_t; + +/** + * Runtime configuration for OTP. + * + * This struct describes runtime information for one-time configuration of the + * hardware. + */ +typedef struct dif_otp_ctrl_config { + /** + * The timeout for an integrity or consistency check to succeed, in cycles. + * + * 100'000 is recommended as a minimum safe value. + */ + uint32_t check_timeout; + /** + * A mask for the pseudo-random integrity check period. + * + * The value of this mask limits the period of the integrity check; when the + * pseudo-random period is computed, this mask is applied to limit it. For + * example, a value of 0x3'ffff would correspond to a maximum period of about + * 2.8s at 24MHz. + * + * A value of zero disables the check. + */ + uint32_t integrity_period_mask; + /** + * A mask for the pseudo-random consistency check period. + * + * The value of this mask limits the period of the consistency check; when the + * pseudo-random period is computed, this mask is applied to limit it. For + * example, a value of 0x3ff'ffff would correspond to a maximum period of + * about 716s at 24MHz. + * + * A value of zero disables the check. + */ + uint32_t consistency_period_mask; +} dif_otp_ctrl_config_t; + +/** + * A hardware-level status code. + */ +typedef enum dif_otp_ctrl_status_code { + // NOTE: This enum's API *requires* that all "error"-like codes (that is, + // those which have associated cause registers) be a prefix of the enum + // values. + // + // Note furthermore that these enum variants are intended as bit indices, so + // their values should not be randomized. +% for part in parts: +<% + part_name = Name.from_snake_case(part["name"]) + part_name_camel = part_name.as_camel_case() +%>\ + /** + * Indicates an error occurred in the `${part_name_camel}` partition. + */ + kDifOtpCtrlStatusCode${part_name_camel}Error${" = 0" if loop.first else ""}, +% endfor + /** + * Indicates an error occurred in the direct access interface. + */ + kDifOtpCtrlStatusCodeDaiError, + /** + * Indicates an error occurred in the lifecycle interface. + */ + kDifOtpCtrlStatusCodeLciError, + /** + * This is not a status code; rather, it represents the last error code which + * has a corresponding "cause" register. + * + * See `dif_otp_ctrl_status_t` for information on how to use this. + */ + kDifOtpCtrlStatusCodeHasCauseLast = kDifOtpCtrlStatusCodeLciError, + /** + * Indicates that an integrity or consistency check has timed out. + * + * This error is unrecoverable. + */ + kDifOtpCtrlStatusCodeTimeoutError, + /** + * Indicates that the LFSR that generates pseudo-random integrity and + * consistency checks is in a bad state. + * + * This error is unrecoverable. + */ + kDifOtpCtrlStatusCodeLfsrError, + /** + * Indicates that the scrambling hardware is in a bad state. + * + * This error is unrecoverable. + */ + kDifOtpCtrlStatusCodeScramblingError, + /** + * Indicates that the key derivation hardware is in a bad state. + * + * This error is unrecoverable. + */ + kDifOtpCtrlStatusCodeKdfError, + /** + * Indicates a bus integrity error. + * + * This error will raise an alert. + */ + kDifOtpCtrlStatusCodeBusIntegError, + /** + * Indicates that the direct access interface is idle. + */ + kDifOtpCtrlStatusCodeDaiIdle, + /** + * Indicates that an integrity or consistency check is currently pending. + */ + kDifOtpCtrlStatusCodeCheckPending, +} dif_otp_ctrl_status_code_t; + +/** + * A hardware-level error code, associated with a particular error defined in + * `dif_otp_ctrl_status_t`. + */ +typedef enum dif_otp_ctrl_error { + /** + * Indicates no error. + */ + kDifOtpCtrlErrorOk, + /** + * Indicates that an OTP macro command was invalid or did not + * complete successfully. + * + * This error indicates non-recoverable hardware malfunction. + */ + kDifOtpCtrlErrorMacroUnspecified, + /** + * Indicates a recoverable error during a read operation. + * + * A followup read should work as expected. + */ + kDifOtpCtrlErrorMacroRecoverableRead, + /** + * Indicates an unrecoverable error during a read operation. + * + * This error indicates non-recoverable hardware malfunction. + */ + kDifOtpCtrlErrorMacroUnrecoverableRead, + /** + * Indicates that the blank write check failed during a write operation. + */ + kDifOtpCtrlErrorMacroBlankCheckFailed, + /** + * Indicates a locked memory region was accessed. + */ + kDifOtpCtrlErrorLockedAccess, + /** + * Indicates a parity, integrity or consistency check failed in the buffer + * registers. + * + * This error indicates non-recoverable hardware malfunction. + */ + kDifOtpCtrlErrorBackgroundCheckFailed, + /** + * Indicates that the FSM of the controller is in a bad state or that the + * controller's FSM has been moved into its terminal state due to escalation + * via the alert subsystem. + * + * This error indicates that the device has been glitched by an attacker. + */ + kDifOtpCtrlErrorFsmBadState, +} dif_otp_ctrl_error_t; + +/** + * The overall status of the OTP controller. + * + * See `dif_otp_ctrl_get_status()`. + */ +typedef struct dif_otp_ctrl_status { + /** + * Currently active statuses, given as a bit vector. To check whether a + * particular status code was returned, write + * + * bool has_code = (status.codes >> kMyStatusCode) & 1; + * + * Note that it is possible to quickly check that the controller is idle and + * error-free by writing + * + * bool is_ok = status.codes == (1 << kDifOtpStatusCodeDaiIdle); + */ + uint32_t codes; + /** + * A list of root causes for each error status code. + * + * If the error status code `error` is present in `codes`, and + * `error <= kDifOtpCtrlStatusCodeHasCauseLast`, then `causes[error]` + * will contain its root cause. + */ + dif_otp_ctrl_error_t causes[kDifOtpCtrlStatusCodeHasCauseLast + 1]; +} dif_otp_ctrl_status_t; + +/** + * Configures OTP with runtime information. + * + * This function should need to be called at most once for the lifetime of + * `otp`. + * + * @param otp An OTP handle. + * @param config Runtime configuration parameters. + * @return The result of the operation. + */ +OT_WARN_UNUSED_RESULT +dif_result_t dif_otp_ctrl_configure(const dif_otp_ctrl_t *otp, + dif_otp_ctrl_config_t config); + +/** + * Runs an integrity check on the OTP hardware. + * + * This function can be used to trigger an integrity check independent of the + * pseudo-random hardware-generated checks. + * + * @param otp An OTP handle. + * @return The result of the operation. + */ +OT_WARN_UNUSED_RESULT +dif_result_t dif_otp_ctrl_check_integrity(const dif_otp_ctrl_t *otp); + +/** + * Runs a consistency check on the OTP hardware. + * + * This function can be used to trigger a consistency check independent of the + * pseudo-random hardware-generated checks. + * + * @param otp An OTP handle. + * @return The result of the operation. + */ +OT_WARN_UNUSED_RESULT +dif_result_t dif_otp_ctrl_check_consistency(const dif_otp_ctrl_t *otp); + +/** + * Locks out `dif_otp_ctrl_configure()` function. + * + * This function is reentrant: calling it while functionality is locked will + * have no effect and return `kDifOtpCtrlOk`. + * + * @param otp An OTP handle. + * @return The result of the operation. + */ +OT_WARN_UNUSED_RESULT +dif_result_t dif_otp_ctrl_lock_config(const dif_otp_ctrl_t *otp); + +/** + * Checks whether `dif_otp_ctrl_configure()` function is locked-out. + * + * @param otp An OTP handle. + * @param[out] is_locked Out-param for the locked state. + * @return The result of the operation. + */ +OT_WARN_UNUSED_RESULT +dif_result_t dif_otp_ctrl_config_is_locked(const dif_otp_ctrl_t *otp, + bool *is_locked); + +/** + * Locks out `dif_otp_ctrl_check_*()` functions. + * + * This function is reentrant: calling it while functionality is locked will + * have no effect and return `kDifOtpCtrlOk`. + * + * @param otp An OTP handle. + * @return The result of the operation. + */ +OT_WARN_UNUSED_RESULT +dif_result_t dif_otp_ctrl_lock_check_trigger(const dif_otp_ctrl_t *otp); + +/** + * Checks whether the `dif_otp_ctrl_check_*()` functions are locked-out. + * + * @param otp An OTP handle. + * @param[out] is_locked Out-param for the locked state. + * @return The result of the operation. + */ +OT_WARN_UNUSED_RESULT +dif_result_t dif_otp_ctrl_check_trigger_is_locked(const dif_otp_ctrl_t *otp, + bool *is_locked); + +/** + * Locks out reads to a SW partition. + * + * This function should only be called on SW partitions; doing otherwise will + * return an error. + * + * Note that this is distinct from the write-locking performed by calling + * `dif_otp_ctrl_dai_digest()`. In particular, the effects of this function will + * not persist past a system reset. + * + * This function is reentrant: calling it while functionality is locked will + * have no effect and return `kDifOtpCtrlOk`. + * + * @param otp An OTP handle. + * @param partition The SW partition to lock. + * @return The result of the operation. + */ +OT_WARN_UNUSED_RESULT +dif_result_t dif_otp_ctrl_lock_reading(const dif_otp_ctrl_t *otp, + dif_otp_ctrl_partition_t partition); + +/** + * Checks whether reads to a SW partition are locked out. + * + * This function should only be called on SW partitions; doing otherwise will + * return an error. + * + * @param otp An OTP handle. + * @param partition the SW partition to check for locking. + * @param[out] is_locked Out-param for the locked state. + * @return The result of the operation. + */ +OT_WARN_UNUSED_RESULT +dif_result_t dif_otp_ctrl_reading_is_locked(const dif_otp_ctrl_t *otp, + dif_otp_ctrl_partition_t partition, + bool *is_locked); + +/** + * Gets the current status of the OTP controller. + * + * @param otp An OTP handle. + * @param[out] status Out-param for the controller's status. + * @return The result of the operation. + */ +OT_WARN_UNUSED_RESULT +dif_result_t dif_otp_ctrl_get_status(const dif_otp_ctrl_t *otp, + dif_otp_ctrl_status_t *status); + +/** + * Calculates a `relative_address` with respect to a `partition` start + * address. + * + * @param partition The partition to use to calculate the reference start + * address. + * @param abs_address Input address relative to the OTP memory start address. + * @param[out] relative_address The result relative address with respect to the + * `partition` start address. + * @return The result of the operation. + */ +OT_WARN_UNUSED_RESULT +dif_result_t dif_otp_ctrl_relative_address(dif_otp_ctrl_partition_t partition, + uint32_t abs_address, + uint32_t *relative_address); + +/** + * Schedules a read on the Direct Access Interface. + * + * Reads are performed relative to a partition; `address` should be given + * relative to the start of `partition`. An error is returned for out-of-bounds + * access. + * + * Furthermore, `address` must be well-aligned: it must be four-byte aligned for + * normal partitions and eight-byte-aligned for secret partitions. An error is + * returned for unaligned access. + * + * @param otp An OTP handle. + * @param partition The partition to read from. + * @param address A partition-relative address to read from. + * @return The result of the operation. + */ +OT_WARN_UNUSED_RESULT +dif_result_t dif_otp_ctrl_dai_read_start(const dif_otp_ctrl_t *otp, + dif_otp_ctrl_partition_t partition, + uint32_t address); + +/** + * Gets the result of a completed 32-bit read operation on the Direct Access + * Interface. + * + * Whether this function or its 64-bit variant should be called is dependent on + * the most recent partition read from. + * + * @param otp An OTP handle. + * @param[out] value Out-param for the read value. + * @return The result of the operation. + */ +OT_WARN_UNUSED_RESULT +dif_result_t dif_otp_ctrl_dai_read32_end(const dif_otp_ctrl_t *otp, + uint32_t *value); + +/** + * Gets the result of a completed 64-bit read operation on the Direct Access + * Interface. + * + * Whether this function or its 32-bit variant should be called is dependent on + * the most recent partition read from. + * + * @param otp An OTP handle. + * @param[out] value Out-param for the read value. + * @return The result of the operation. + */ +OT_WARN_UNUSED_RESULT +dif_result_t dif_otp_ctrl_dai_read64_end(const dif_otp_ctrl_t *otp, + uint64_t *value); + +/** + * Schedules a 32-bit write on the Direct Access Interface. + * + * Writes are performed relative to a partition; `address` should be given + * relative to the start of `partition`. An error is returned for out-of-bounds + * access. + * + * Furthermore, `address` must be four-byte-aligned, and `partition` must not be + * a secret partition. An error is returned if neither condition is met. + * + * Note that this function cannot be used to program the digest at the end of a + * `SW` partition; `dif_otp_ctrl_dai_digest()` must be used instead. + * + * @param otp An OTP handle. + * @param partition The partition to program. + * @param address A partition-relative address to program. + * @param value The value to program into the OTP. + * @return The result of the operation. + */ +OT_WARN_UNUSED_RESULT +dif_result_t dif_otp_ctrl_dai_program32(const dif_otp_ctrl_t *otp, + dif_otp_ctrl_partition_t partition, + uint32_t address, uint32_t value); + +/** + * Schedules a 64-bit write on the Direct Access Interface. + * + * Writes are performed relative to a partition; `address` should be given + * relative to the start of `partition`. An error is returned for out-of-bounds + * access. + * + * Furthermore, `address` must be eight-byte-aligned, and `partition` must be + * a secret partition. An error is returned if neither condition is met. + * + * @param otp An OTP handle. + * @param partition The partition to program. + * @param address A partition-relative address to program. + * @param value The value to program into the OTP. + * @return The result of the operation. + */ +OT_WARN_UNUSED_RESULT +dif_result_t dif_otp_ctrl_dai_program64(const dif_otp_ctrl_t *otp, + dif_otp_ctrl_partition_t partition, + uint32_t address, uint64_t value); + +/** + * Schedules a hardware digest operation on the Direct Access Interface. + * + * **This operation will also lock writes for the given partition.** + * + * If `partition` is a SW partition, `digest` must be non-zero; if it is a + * partition with a hardware-managed digest, `digest` *must* be zero (since the + * digest will be generated by the hardware). An error is returned if either + * precondition is not met. + * + * This function does not work with the lifecycle state partition, and will + * return an error in that case. + * + * @param otp An OTP handle. + * @param partition The partition to digest and lock. + * @param digest The digest to program (for SW partitions). + * @return The result of the operation. + */ +OT_WARN_UNUSED_RESULT +dif_result_t dif_otp_ctrl_dai_digest(const dif_otp_ctrl_t *otp, + dif_otp_ctrl_partition_t partition, + 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 + * has not been computed yet, or has been computed but not since device reset, + * this function will return an error. + * + * The lifecycle partition does not have a digest and will result in an error + * being returned. + * + * @param otp An OTP handle. + * @param partition The partition to get a digest for. + * @param[out] digest Out-param for the digest. + * @return The result of the operation. + */ +OT_WARN_UNUSED_RESULT +dif_result_t dif_otp_ctrl_get_digest(const dif_otp_ctrl_t *otp, + dif_otp_ctrl_partition_t partition, + uint64_t *digest); + +/** + * Performs a memory-mapped read of the given partition, if it supports them. + * + * In particular, this function will read `len` words, starting at `address`, + * relative to the start of `partition`. + * + * The same caveats for `dif_otp_ctrl_dai_read_start()` apply to `address`; in + * addition, `address + len` must also be in-range and must not overflow. + * + * This function will block until the read completes, unlike Direct Access + * Interface functions. + * + * @param otp An OTP handle. + * @param partition The partition to read from. + * @param address A partition-relative address to read from. + * @param[out] buf A buffer of words to write read values to. + * @param len The number of words to read. + * @return The result of the operation. + */ +OT_WARN_UNUSED_RESULT +dif_result_t dif_otp_ctrl_read_blocking(const dif_otp_ctrl_t *otp, + dif_otp_ctrl_partition_t partition, + uint32_t address, uint32_t *buf, + size_t len); + +#ifdef __cplusplus +} // extern "C" +#endif // __cplusplus + +#endif // OPENTITAN_SW_DEVICE_LIB_DIF_DIF_OTP_CTRL_H_ diff --git a/hw/ip/otp_ctrl/data/dif_otp_ctrl_unittest.cc.tpl b/hw/ip/otp_ctrl/data/dif_otp_ctrl_unittest.cc.tpl new file mode 100644 index 0000000000000..5290ac1b77b3d --- /dev/null +++ b/hw/ip/otp_ctrl/data/dif_otp_ctrl_unittest.cc.tpl @@ -0,0 +1,821 @@ +// Copyright lowRISC contributors. +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 +${gen_comment} +<% +from topgen.lib import Name + +parts = otp_mmap.config["partitions"] +digest_parts = [part for part in parts if + part["hw_digest"] or part["sw_digest"]] +hw_digest_parts = [part for part in parts if part["hw_digest"]] +sw_digest_parts = [part for part in parts if part["sw_digest"]] +read_locked_csr_parts = [part for part in parts if part["read_lock"] == "CSR"] +secret_parts = [part for part in parts if part["secret"]] +%>\ +#include "sw/device/lib/dif/dif_otp_ctrl.h" + +#include +#include +#include + +#include "gtest/gtest.h" +#include "sw/device/lib/base/mmio.h" +#include "sw/device/lib/base/mock_mmio.h" +#include "sw/device/lib/dif/dif_test_base.h" + +#include "otp_ctrl_regs.h" // Generated. + +namespace dif_otp_ctrl_unittest { +namespace { +using ::mock_mmio::LeInt; +using ::mock_mmio::MmioTest; +using ::mock_mmio::MockDevice; +using ::testing::Each; +using ::testing::ElementsAre; + +class OtpTest : public testing::Test, public MmioTest { + protected: + dif_otp_ctrl_t otp_ = {.base_addr = dev().region()}; +}; + +class ConfigTest : public OtpTest {}; + +TEST_F(ConfigTest, Basic) { + dif_otp_ctrl_config_t config = { + .check_timeout = 100'000, + .integrity_period_mask = 0x3'ffff, + .consistency_period_mask = 0x3ff'ffff, + }; + + EXPECT_READ32(OTP_CTRL_CHECK_REGWEN_REG_OFFSET, + {{OTP_CTRL_CHECK_REGWEN_CHECK_REGWEN_BIT, true}}); + + EXPECT_WRITE32(OTP_CTRL_CHECK_TIMEOUT_REG_OFFSET, config.check_timeout); + EXPECT_WRITE32(OTP_CTRL_INTEGRITY_CHECK_PERIOD_REG_OFFSET, + config.integrity_period_mask); + EXPECT_WRITE32(OTP_CTRL_CONSISTENCY_CHECK_PERIOD_REG_OFFSET, + config.consistency_period_mask); + + EXPECT_DIF_OK(dif_otp_ctrl_configure(&otp_, config)); +} + +TEST_F(ConfigTest, Locked) { + EXPECT_READ32(OTP_CTRL_CHECK_REGWEN_REG_OFFSET, + {{OTP_CTRL_CHECK_REGWEN_CHECK_REGWEN_BIT, false}}); + + EXPECT_EQ(dif_otp_ctrl_configure(&otp_, {}), kDifLocked); +} + +TEST_F(ConfigTest, IsConfigLocked) { + bool flag; + + EXPECT_READ32(OTP_CTRL_CHECK_REGWEN_REG_OFFSET, + {{OTP_CTRL_CHECK_REGWEN_CHECK_REGWEN_BIT, true}}); + EXPECT_DIF_OK(dif_otp_ctrl_config_is_locked(&otp_, &flag)); + EXPECT_FALSE(flag); + + EXPECT_READ32(OTP_CTRL_CHECK_REGWEN_REG_OFFSET, + {{OTP_CTRL_CHECK_REGWEN_CHECK_REGWEN_BIT, false}}); + EXPECT_DIF_OK(dif_otp_ctrl_config_is_locked(&otp_, &flag)); + EXPECT_TRUE(flag); +} + +TEST_F(ConfigTest, LockConfig) { + EXPECT_WRITE32(OTP_CTRL_CHECK_REGWEN_REG_OFFSET, + {{OTP_CTRL_CHECK_REGWEN_CHECK_REGWEN_BIT, false}}); + EXPECT_DIF_OK(dif_otp_ctrl_lock_config(&otp_)); +} + +TEST_F(ConfigTest, NullArgs) { + EXPECT_DIF_BADARG(dif_otp_ctrl_configure(nullptr, {})); + + bool flag; + EXPECT_DIF_BADARG(dif_otp_ctrl_config_is_locked(nullptr, &flag)); + EXPECT_DIF_BADARG(dif_otp_ctrl_config_is_locked(&otp_, nullptr)); + + EXPECT_DIF_BADARG(dif_otp_ctrl_lock_config(nullptr)); +} + +class CheckTest : public OtpTest {}; + +TEST_F(CheckTest, Integrity) { + EXPECT_READ32( + OTP_CTRL_CHECK_TRIGGER_REGWEN_REG_OFFSET, + {{OTP_CTRL_CHECK_TRIGGER_REGWEN_CHECK_TRIGGER_REGWEN_BIT, true}}); + EXPECT_WRITE32(OTP_CTRL_CHECK_TRIGGER_REG_OFFSET, + {{OTP_CTRL_CHECK_TRIGGER_INTEGRITY_BIT, true}}); + + EXPECT_DIF_OK(dif_otp_ctrl_check_integrity(&otp_)); +} + +TEST_F(CheckTest, Consistency) { + EXPECT_READ32( + OTP_CTRL_CHECK_TRIGGER_REGWEN_REG_OFFSET, + {{OTP_CTRL_CHECK_TRIGGER_REGWEN_CHECK_TRIGGER_REGWEN_BIT, true}}); + EXPECT_WRITE32(OTP_CTRL_CHECK_TRIGGER_REG_OFFSET, + {{OTP_CTRL_CHECK_TRIGGER_CONSISTENCY_BIT, true}}); + + EXPECT_DIF_OK(dif_otp_ctrl_check_consistency(&otp_)); +} + +TEST_F(CheckTest, LockTrigger) { + EXPECT_WRITE32( + OTP_CTRL_CHECK_TRIGGER_REGWEN_REG_OFFSET, + {{OTP_CTRL_CHECK_TRIGGER_REGWEN_CHECK_TRIGGER_REGWEN_BIT, false}}); + EXPECT_DIF_OK(dif_otp_ctrl_lock_check_trigger(&otp_)); +} + +TEST_F(CheckTest, Locked) { + EXPECT_READ32( + OTP_CTRL_CHECK_TRIGGER_REGWEN_REG_OFFSET, + {{OTP_CTRL_CHECK_TRIGGER_REGWEN_CHECK_TRIGGER_REGWEN_BIT, false}}); + EXPECT_EQ(dif_otp_ctrl_check_integrity(&otp_), kDifLocked); + + EXPECT_READ32( + OTP_CTRL_CHECK_TRIGGER_REGWEN_REG_OFFSET, + {{OTP_CTRL_CHECK_TRIGGER_REGWEN_CHECK_TRIGGER_REGWEN_BIT, false}}); + EXPECT_EQ(dif_otp_ctrl_check_consistency(&otp_), kDifLocked); +} + +TEST_F(CheckTest, NullArgs) { + EXPECT_DIF_BADARG(dif_otp_ctrl_check_integrity(nullptr)); + EXPECT_DIF_BADARG(dif_otp_ctrl_check_consistency(nullptr)); +} + +class ReadLockTest : public OtpTest {}; + +// Too many formatting variants in template code, so disabling clang-format. +// clang-format off +TEST_F(ReadLockTest, IsLocked) { + bool flag; + +% for part in read_locked_csr_parts: +<% + part_name = Name.from_snake_case(part["name"]) + part_name_camel = part_name.as_camel_case() + part_name_define = part_name.as_c_define() +%>\ + EXPECT_READ32( + OTP_CTRL_${part_name_define}_READ_LOCK_REG_OFFSET, + {{OTP_CTRL_${part_name_define}_READ_LOCK_${part_name_define}_READ_LOCK_BIT, + true}}); + EXPECT_DIF_OK(dif_otp_ctrl_reading_is_locked( + &otp_, kDifOtpCtrlPartition${part_name_camel}, &flag)); + EXPECT_FALSE(flag); + + EXPECT_READ32( + OTP_CTRL_${part_name_define}_READ_LOCK_REG_OFFSET, + {{OTP_CTRL_${part_name_define}_READ_LOCK_${part_name_define}_READ_LOCK_BIT, + false}}); + EXPECT_DIF_OK(dif_otp_ctrl_reading_is_locked( + &otp_, kDifOtpCtrlPartition${part_name_camel}, &flag)); + EXPECT_TRUE(flag); + % if not loop.last: + + %endif +% endfor +} + +TEST_F(ReadLockTest, Lock) { +% for part in read_locked_csr_parts: +<% + part_name = Name.from_snake_case(part["name"]) + part_name_camel = part_name.as_camel_case() + part_name_define = part_name.as_c_define() +%>\ + EXPECT_READ32(OTP_CTRL_DIRECT_ACCESS_REGWEN_REG_OFFSET, 1); + EXPECT_WRITE32( + OTP_CTRL_${part_name_define}_READ_LOCK_REG_OFFSET, + {{OTP_CTRL_${part_name_define}_READ_LOCK_${part_name_define}_READ_LOCK_BIT, + false}}); + EXPECT_DIF_OK(dif_otp_ctrl_lock_reading( + &otp_, kDifOtpCtrlPartition${part_name_camel})); + % if not loop.last: + + %endif +% endfor +} + +TEST_F(ReadLockTest, NotLockablePartitions) { + bool flag; +% for part in [p for p in parts if p not in read_locked_csr_parts]: +<% + part_name = Name.from_snake_case(part["name"]) + part_name_camel = part_name.as_camel_case() +%>\ + EXPECT_DIF_BADARG( + dif_otp_ctrl_lock_reading(&otp_, kDifOtpCtrlPartition${part_name_camel})); + EXPECT_DIF_BADARG(dif_otp_ctrl_reading_is_locked( + &otp_, kDifOtpCtrlPartition${part_name_camel}, &flag)); + % if not loop.last: + + %endif +% endfor +} +// clang-format on + +TEST_F(ReadLockTest, NullArgs) { + bool flag; +% for part in read_locked_csr_parts: +<% + part_name = Name.from_snake_case(part["name"]) + part_name_camel = part_name.as_camel_case() + lock_reading_line = f"dif_otp_ctrl_lock_reading(nullptr, kDifOtpCtrlPartition{part_name_camel}));" +%>\ + EXPECT_DIF_BADARG(dif_otp_ctrl_reading_is_locked( + nullptr, kDifOtpCtrlPartition${part_name_camel}, &flag)); + EXPECT_DIF_BADARG(dif_otp_ctrl_reading_is_locked( + &otp_, kDifOtpCtrlPartition${part_name_camel}, nullptr)); + % if len(lock_reading_line) > 80 - 6: + EXPECT_DIF_BADARG(dif_otp_ctrl_lock_reading( + nullptr, kDifOtpCtrlPartition${part_name_camel})); + % else: + EXPECT_DIF_BADARG( + ${lock_reading_line} + % endif + % if not loop.last: + + %endif +% endfor +} + +class StatusTest : public OtpTest {}; + +TEST_F(StatusTest, Idle) { + dif_otp_ctrl_status_t status; + + EXPECT_READ32(OTP_CTRL_STATUS_REG_OFFSET, + {{OTP_CTRL_STATUS_DAI_IDLE_BIT, true}}); + EXPECT_DIF_OK(dif_otp_ctrl_get_status(&otp_, &status)); + + EXPECT_EQ(status.codes, 1 << kDifOtpCtrlStatusCodeDaiIdle); + EXPECT_THAT(status.causes, Each(kDifOtpCtrlErrorOk)); +} + +TEST_F(StatusTest, Errors) { + dif_otp_ctrl_status_t status; + + EXPECT_READ32(OTP_CTRL_STATUS_REG_OFFSET, + { + {OTP_CTRL_STATUS_DAI_IDLE_BIT, true}, + {OTP_CTRL_STATUS_HW_CFG0_ERROR_BIT, true}, + {OTP_CTRL_STATUS_LCI_ERROR_BIT, true}, + }); + +<% + hw_cfg0_error_index = [i for i, p in enumerate(parts) + if p["name"] == "HW_CFG0"][0] + lci_error_index = len(parts) + 1 +%>\ + EXPECT_READ32(OTP_CTRL_ERR_CODE_${hw_cfg0_error_index}_REG_OFFSET, + {{OTP_CTRL_ERR_CODE_0_ERR_CODE_0_OFFSET, + OTP_CTRL_ERR_CODE_0_ERR_CODE_0_VALUE_MACRO_ECC_CORR_ERROR}}); + EXPECT_READ32(OTP_CTRL_ERR_CODE_${lci_error_index}_REG_OFFSET, + {{OTP_CTRL_ERR_CODE_0_ERR_CODE_0_OFFSET, + OTP_CTRL_ERR_CODE_0_ERR_CODE_0_VALUE_MACRO_ERROR}}); + + EXPECT_DIF_OK(dif_otp_ctrl_get_status(&otp_, &status)); + EXPECT_EQ(status.codes, (1 << kDifOtpCtrlStatusCodeDaiIdle) | + (1 << kDifOtpCtrlStatusCodeHwCfg0Error) | + (1 << kDifOtpCtrlStatusCodeLciError)); + EXPECT_EQ(status.causes[kDifOtpCtrlStatusCodeHwCfg0Error], + kDifOtpCtrlErrorMacroRecoverableRead); + EXPECT_EQ(status.causes[kDifOtpCtrlStatusCodeLciError], + kDifOtpCtrlErrorMacroUnspecified); +} + +TEST_F(StatusTest, NullArgs) { + dif_otp_ctrl_status_t status; + + EXPECT_DIF_BADARG(dif_otp_ctrl_get_status(nullptr, &status)); + EXPECT_DIF_BADARG(dif_otp_ctrl_get_status(&otp_, nullptr)); +} + +struct RelativeAddressParams { + std::string name; + dif_otp_ctrl_partition_t partition; + uint32_t abs_address; + dif_result_t expected_result; + uint32_t expected_relative_address; +}; + +class RelativeAddress + : public OtpTest, + public testing::WithParamInterface {}; + +TEST_P(RelativeAddress, RelativeAddress) { + uint32_t got_relative_address; + dif_result_t got_result = dif_otp_ctrl_relative_address( + GetParam().partition, GetParam().abs_address, &got_relative_address); + EXPECT_EQ(got_result, GetParam().expected_result); + EXPECT_EQ(got_relative_address, GetParam().expected_relative_address); +} + +INSTANTIATE_TEST_SUITE_P( + AllPartitions, RelativeAddress, + testing::Values( +% for part in parts: +<% + part_name = Name.from_snake_case(part["name"]) + part_name_camel = part_name.as_camel_case() + part_name_define = part_name.as_c_define() + step = 8 if part["secret"] else 4 +%>\ + RelativeAddressParams{ + "${part_name_camel}Okay", + kDifOtpCtrlPartition${part_name_camel}, + OTP_CTRL_PARAM_${part_name_define}_OFFSET + ${step}, + kDifOk, + ${step}, + }, + RelativeAddressParams{ + "${part_name_camel}Unaligned", + kDifOtpCtrlPartition${part_name_camel}, + OTP_CTRL_PARAM_${part_name_define}_OFFSET + 1, + kDifUnaligned, + 0, + }, +<% + ## Exclude first partition to avoid a negative offset. +%>\ + % if not loop.first: + RelativeAddressParams{ + "${part_name_camel}OutOfRangeBeforeStart", + kDifOtpCtrlPartition${part_name_camel}, + OTP_CTRL_PARAM_${part_name_define}_OFFSET - ${step}, + kDifOutOfRange, + 0, + }, + % endif + RelativeAddressParams{ + "${part_name_camel}OutOfRangePastEnd", + kDifOtpCtrlPartition${part_name_camel}, + % if len(f"OTP_CTRL_PARAM_{part_name_define}_OFFSET + OTP_CTRL_PARAM_{part_name_define}_SIZE,") <= 80 - 12: + OTP_CTRL_PARAM_${part_name_define}_OFFSET + OTP_CTRL_PARAM_${part_name_define}_SIZE, + % else: + OTP_CTRL_PARAM_${part_name_define}_OFFSET + + OTP_CTRL_PARAM_${part_name_define}_SIZE, + % endif + kDifOutOfRange, + 0, + }${")," if loop.last else ","} +% endfor + [](const testing::TestParamInfo &info) { + return info.param.name; + }); + +class DaiReadTest : public OtpTest {}; + +TEST_F(DaiReadTest, Read32) { + EXPECT_READ32( + OTP_CTRL_DIRECT_ACCESS_REGWEN_REG_OFFSET, + {{OTP_CTRL_DIRECT_ACCESS_REGWEN_DIRECT_ACCESS_REGWEN_BIT, true}}); + EXPECT_WRITE32(OTP_CTRL_DIRECT_ACCESS_ADDRESS_REG_OFFSET, + OTP_CTRL_PARAM_MANUF_STATE_OFFSET); + EXPECT_WRITE32(OTP_CTRL_DIRECT_ACCESS_CMD_REG_OFFSET, + {{OTP_CTRL_DIRECT_ACCESS_CMD_RD_BIT, true}}); + + EXPECT_DIF_OK(dif_otp_ctrl_dai_read_start(&otp_, kDifOtpCtrlPartitionHwCfg0, + /*address=*/0x20)); + + EXPECT_READ32( + OTP_CTRL_DIRECT_ACCESS_REGWEN_REG_OFFSET, + {{OTP_CTRL_DIRECT_ACCESS_REGWEN_DIRECT_ACCESS_REGWEN_BIT, true}}); + EXPECT_READ32(OTP_CTRL_DIRECT_ACCESS_RDATA_0_REG_OFFSET, 0x12345678); + + uint32_t val; + EXPECT_DIF_OK(dif_otp_ctrl_dai_read32_end(&otp_, &val)); + EXPECT_EQ(val, 0x12345678); +} + +TEST_F(DaiReadTest, Read64) { + uint64_t val; +% for part in secret_parts: +<% + part_name = Name.from_snake_case(part["name"]) + part_name_camel = part_name.as_camel_case() + part_name_define = part_name.as_c_define() +%>\ + EXPECT_READ32( + OTP_CTRL_DIRECT_ACCESS_REGWEN_REG_OFFSET, + {{OTP_CTRL_DIRECT_ACCESS_REGWEN_DIRECT_ACCESS_REGWEN_BIT, true}}); + EXPECT_WRITE32(OTP_CTRL_DIRECT_ACCESS_ADDRESS_REG_OFFSET, + OTP_CTRL_PARAM_${part_name_define}_OFFSET + 0x8); + EXPECT_WRITE32(OTP_CTRL_DIRECT_ACCESS_CMD_REG_OFFSET, + {{OTP_CTRL_DIRECT_ACCESS_CMD_RD_BIT, true}}); + + EXPECT_DIF_OK(dif_otp_ctrl_dai_read_start(&otp_, kDifOtpCtrlPartition${part_name_camel}, + /*address=*/0x8)); + + EXPECT_READ32( + OTP_CTRL_DIRECT_ACCESS_REGWEN_REG_OFFSET, + {{OTP_CTRL_DIRECT_ACCESS_REGWEN_DIRECT_ACCESS_REGWEN_BIT, true}}); + EXPECT_READ32(OTP_CTRL_DIRECT_ACCESS_RDATA_1_REG_OFFSET, 0x12345678); + EXPECT_READ32(OTP_CTRL_DIRECT_ACCESS_RDATA_0_REG_OFFSET, 0x90abcdef); + + EXPECT_DIF_OK(dif_otp_ctrl_dai_read64_end(&otp_, &val)); + EXPECT_EQ(val, 0x1234567890abcdef); + % if not loop.last: + + % endif +% endfor +} + +TEST_F(DaiReadTest, Unaligned) { + EXPECT_EQ(dif_otp_ctrl_dai_read_start(&otp_, kDifOtpCtrlPartitionHwCfg0, + /*address=*/0b01), + kDifUnaligned); + EXPECT_EQ(dif_otp_ctrl_dai_read_start(&otp_, kDifOtpCtrlPartitionSecret2, + /*address=*/0b100), + kDifUnaligned); +} + +TEST_F(DaiReadTest, OutOfRange) { + EXPECT_EQ(dif_otp_ctrl_dai_read_start(&otp_, kDifOtpCtrlPartitionHwCfg0, + /*address=*/0x100), + kDifOutOfRange); +} + +TEST_F(DaiReadTest, Busy) { + EXPECT_READ32( + OTP_CTRL_DIRECT_ACCESS_REGWEN_REG_OFFSET, + {{OTP_CTRL_DIRECT_ACCESS_REGWEN_DIRECT_ACCESS_REGWEN_BIT, false}}); + EXPECT_EQ(dif_otp_ctrl_dai_read_start(&otp_, kDifOtpCtrlPartitionHwCfg0, + /*address=*/0x0), + kDifUnavailable); + + EXPECT_READ32( + OTP_CTRL_DIRECT_ACCESS_REGWEN_REG_OFFSET, + {{OTP_CTRL_DIRECT_ACCESS_REGWEN_DIRECT_ACCESS_REGWEN_BIT, false}}); + uint32_t val32; + EXPECT_EQ(dif_otp_ctrl_dai_read32_end(&otp_, &val32), kDifUnavailable); + + EXPECT_READ32( + OTP_CTRL_DIRECT_ACCESS_REGWEN_REG_OFFSET, + {{OTP_CTRL_DIRECT_ACCESS_REGWEN_DIRECT_ACCESS_REGWEN_BIT, false}}); + uint64_t val64; + EXPECT_EQ(dif_otp_ctrl_dai_read64_end(&otp_, &val64), kDifUnavailable); +} + +TEST_F(DaiReadTest, NullArgs) { + EXPECT_DIF_BADARG(dif_otp_ctrl_dai_read_start(nullptr, + kDifOtpCtrlPartitionHwCfg0, + /*address=*/0x0)); + + uint32_t val32; + EXPECT_DIF_BADARG(dif_otp_ctrl_dai_read32_end(nullptr, &val32)); + EXPECT_DIF_BADARG(dif_otp_ctrl_dai_read32_end(&otp_, nullptr)); + + uint64_t val64; + EXPECT_DIF_BADARG(dif_otp_ctrl_dai_read64_end(nullptr, &val64)); + EXPECT_DIF_BADARG(dif_otp_ctrl_dai_read64_end(&otp_, nullptr)); +} + +class DaiProgramTest : public OtpTest {}; + +TEST_F(DaiProgramTest, Program32) { + EXPECT_READ32( + OTP_CTRL_DIRECT_ACCESS_REGWEN_REG_OFFSET, + {{OTP_CTRL_DIRECT_ACCESS_REGWEN_DIRECT_ACCESS_REGWEN_BIT, true}}); + EXPECT_WRITE32(OTP_CTRL_DIRECT_ACCESS_ADDRESS_REG_OFFSET, + OTP_CTRL_PARAM_MANUF_STATE_OFFSET); + EXPECT_WRITE32(OTP_CTRL_DIRECT_ACCESS_WDATA_0_REG_OFFSET, 0x12345678); + EXPECT_WRITE32(OTP_CTRL_DIRECT_ACCESS_CMD_REG_OFFSET, + {{OTP_CTRL_DIRECT_ACCESS_CMD_WR_BIT, true}}); + + EXPECT_DIF_OK(dif_otp_ctrl_dai_program32(&otp_, kDifOtpCtrlPartitionHwCfg0, + /*address=*/0x20, + /*value=*/0x12345678)); +} + +TEST_F(DaiProgramTest, Program64) { + EXPECT_READ32( + OTP_CTRL_DIRECT_ACCESS_REGWEN_REG_OFFSET, + {{OTP_CTRL_DIRECT_ACCESS_REGWEN_DIRECT_ACCESS_REGWEN_BIT, true}}); + EXPECT_WRITE32(OTP_CTRL_DIRECT_ACCESS_ADDRESS_REG_OFFSET, + OTP_CTRL_PARAM_SECRET2_OFFSET + 0x8); + EXPECT_WRITE32(OTP_CTRL_DIRECT_ACCESS_WDATA_0_REG_OFFSET, 0x90abcdef); + EXPECT_WRITE32(OTP_CTRL_DIRECT_ACCESS_WDATA_1_REG_OFFSET, 0x12345678); + EXPECT_WRITE32(OTP_CTRL_DIRECT_ACCESS_CMD_REG_OFFSET, + {{OTP_CTRL_DIRECT_ACCESS_CMD_WR_BIT, true}}); + + EXPECT_DIF_OK(dif_otp_ctrl_dai_program64(&otp_, kDifOtpCtrlPartitionSecret2, + /*address=*/0x8, + /*value=*/0x1234567890abcdef)); +} + +TEST_F(DaiProgramTest, BadPartition) { + EXPECT_EQ(dif_otp_ctrl_dai_program32(&otp_, kDifOtpCtrlPartitionSecret1, + /*address=*/0x0, /*value=*/42), + kDifError); + EXPECT_EQ(dif_otp_ctrl_dai_program64(&otp_, kDifOtpCtrlPartitionHwCfg0, + /*address=*/0x0, /*value=*/42), + kDifError); + + // LC is never writeable. + EXPECT_EQ(dif_otp_ctrl_dai_program32(&otp_, kDifOtpCtrlPartitionLifeCycle, + /*address=*/0x0, /*value=*/42), + kDifError); +} + +TEST_F(DaiProgramTest, Unaligned) { + EXPECT_EQ(dif_otp_ctrl_dai_program32(&otp_, kDifOtpCtrlPartitionHwCfg0, + /*address=*/0b01, /*value=*/42), + kDifUnaligned); + EXPECT_EQ(dif_otp_ctrl_dai_program64(&otp_, kDifOtpCtrlPartitionSecret2, + /*address=*/0b100, /*value=*/42), + kDifUnaligned); +} + +TEST_F(DaiProgramTest, OutOfRange) { + // Check that we can't write a digest directly. + EXPECT_EQ(dif_otp_ctrl_dai_program32( + &otp_, kDifOtpCtrlPartitionCreatorSwCfg, + /*address=*/OTP_CTRL_PARAM_CREATOR_SW_CFG_DIGEST_OFFSET, + /*value=*/42), + kDifOutOfRange); + + // Same digest check for 64-bit. + EXPECT_EQ(dif_otp_ctrl_dai_program64( + &otp_, kDifOtpCtrlPartitionSecret2, + /*address=*/OTP_CTRL_PARAM_SECRET2_DIGEST_OFFSET, /*value=*/42), + kDifOutOfRange); +} + +TEST_F(DaiProgramTest, Busy) { + EXPECT_READ32( + OTP_CTRL_DIRECT_ACCESS_REGWEN_REG_OFFSET, + {{OTP_CTRL_DIRECT_ACCESS_REGWEN_DIRECT_ACCESS_REGWEN_BIT, false}}); + EXPECT_EQ(dif_otp_ctrl_dai_program32(&otp_, kDifOtpCtrlPartitionHwCfg0, + /*address=*/0x0, /*value=*/42), + kDifUnavailable); + + EXPECT_READ32( + OTP_CTRL_DIRECT_ACCESS_REGWEN_REG_OFFSET, + {{OTP_CTRL_DIRECT_ACCESS_REGWEN_DIRECT_ACCESS_REGWEN_BIT, false}}); + EXPECT_EQ(dif_otp_ctrl_dai_program64(&otp_, kDifOtpCtrlPartitionSecret0, + /*address=*/0x0, /*value=*/42), + kDifUnavailable); +} + +TEST_F(DaiProgramTest, NullArgs) { + EXPECT_DIF_BADARG(dif_otp_ctrl_dai_program32(nullptr, + kDifOtpCtrlPartitionHwCfg0, + /*address=*/0x0, /*value=*/42)); + EXPECT_DIF_BADARG(dif_otp_ctrl_dai_program64(nullptr, + kDifOtpCtrlPartitionSecret0, + /*address=*/0x0, /*value=*/42)); +} + +class DaiDigestTest : public OtpTest {}; + +TEST_F(DaiDigestTest, DigestSw) { +% for part in sw_digest_parts: +<% + part_name = Name.from_snake_case(part["name"]) + part_name_define = part_name.as_c_define() + part_name_camel = part_name.as_camel_case() + dai_digest_line = ("EXPECT_DIF_OK(dif_otp_ctrl_dai_digest(&otp_, " + + f"kDifOtpCtrlPartition{part_name_camel},") +%>\ + EXPECT_READ32( + OTP_CTRL_DIRECT_ACCESS_REGWEN_REG_OFFSET, + {{OTP_CTRL_DIRECT_ACCESS_REGWEN_DIRECT_ACCESS_REGWEN_BIT, true}}); + EXPECT_WRITE32(OTP_CTRL_DIRECT_ACCESS_ADDRESS_REG_OFFSET, + OTP_CTRL_PARAM_${part_name.as_c_define()}_DIGEST_OFFSET); + EXPECT_WRITE32(OTP_CTRL_DIRECT_ACCESS_WDATA_0_REG_OFFSET, 0x00abcdef); + EXPECT_WRITE32(OTP_CTRL_DIRECT_ACCESS_WDATA_1_REG_OFFSET, 0xabcdef00); + EXPECT_WRITE32(OTP_CTRL_DIRECT_ACCESS_CMD_REG_OFFSET, + {{OTP_CTRL_DIRECT_ACCESS_CMD_WR_BIT, true}}); + + % if len(dai_digest_line) > 80 - 2: + EXPECT_DIF_OK(dif_otp_ctrl_dai_digest(&otp_, + kDifOtpCtrlPartition${part_name_camel}, + % else: + ${dai_digest_line} + % endif + /*digest=*/0xabcdef0000abcdef)); + % if not loop.last: + + % endif +% endfor +} + +TEST_F(DaiDigestTest, DigestHw) { +% for part in hw_digest_parts: +<% + part_name = Name.from_snake_case(part["name"]) + part_name_define = part_name.as_c_define() + part_name_camel = part_name.as_camel_case() + dai_digest_line = ("EXPECT_DIF_OK(dif_otp_ctrl_dai_digest(&otp_, " + + f"kDifOtpCtrlPartition{part_name_camel},") +%>\ + EXPECT_READ32( + OTP_CTRL_DIRECT_ACCESS_REGWEN_REG_OFFSET, + {{OTP_CTRL_DIRECT_ACCESS_REGWEN_DIRECT_ACCESS_REGWEN_BIT, true}}); + EXPECT_WRITE32(OTP_CTRL_DIRECT_ACCESS_ADDRESS_REG_OFFSET, + OTP_CTRL_PARAM_${part_name_define}_OFFSET); + EXPECT_WRITE32(OTP_CTRL_DIRECT_ACCESS_CMD_REG_OFFSET, + {{OTP_CTRL_DIRECT_ACCESS_CMD_DIGEST_BIT, true}}); + + EXPECT_DIF_OK(dif_otp_ctrl_dai_digest(&otp_, kDifOtpCtrlPartition${part_name_camel}, + /*digest=*/0)); + % if not loop.last: + + % endif +% endfor +} + +TEST_F(DaiDigestTest, BadPartition) { + EXPECT_EQ(dif_otp_ctrl_dai_digest(&otp_, kDifOtpCtrlPartitionLifeCycle, + /*digest=*/0), + kDifError); +} + +TEST_F(DaiDigestTest, Busy) { + EXPECT_READ32( + OTP_CTRL_DIRECT_ACCESS_REGWEN_REG_OFFSET, + {{OTP_CTRL_DIRECT_ACCESS_REGWEN_DIRECT_ACCESS_REGWEN_BIT, false}}); + + EXPECT_EQ( + dif_otp_ctrl_dai_digest(&otp_, kDifOtpCtrlPartitionHwCfg0, /*digest=*/0), + kDifUnavailable); +} + +TEST_F(DaiDigestTest, BadDigest) { + EXPECT_DIF_BADARG(dif_otp_ctrl_dai_digest(&otp_, kDifOtpCtrlPartitionHwCfg0, + /*digest=*/0xabcdef0000abcdef)); + EXPECT_DIF_BADARG(dif_otp_ctrl_dai_digest(&otp_, + kDifOtpCtrlPartitionCreatorSwCfg, + /*digest=*/0)); +} + +TEST_F(DaiDigestTest, NullArgs) { + EXPECT_DIF_BADARG(dif_otp_ctrl_dai_digest(nullptr, + kDifOtpCtrlPartitionCreatorSwCfg, + /*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; + bool has_digest; + ptrdiff_t reg0, reg1; +}; + +class GetDigest : public OtpTest, + public testing::WithParamInterface {}; + +TEST_P(GetDigest, GetDigest) { + if (!GetParam().has_digest) { + uint64_t digest; + EXPECT_DIF_BADARG( + dif_otp_ctrl_get_digest(&otp_, GetParam().partition, &digest)); + return; + } + + EXPECT_READ32(GetParam().reg1, 0xabcdef99); + EXPECT_READ32(GetParam().reg0, 0x99abcdef); + + uint64_t digest; + EXPECT_DIF_OK(dif_otp_ctrl_get_digest(&otp_, GetParam().partition, &digest)); + EXPECT_EQ(digest, 0xabcdef9999abcdef); +} + +TEST_P(GetDigest, BadDigest) { + if (!GetParam().has_digest) { + return; + } + + EXPECT_READ32(GetParam().reg1, 0x0); + EXPECT_READ32(GetParam().reg0, 0x0); + + uint64_t digest; + EXPECT_EQ(dif_otp_ctrl_get_digest(&otp_, GetParam().partition, &digest), + kDifError); +} + +TEST_P(GetDigest, NullArgs) { + uint64_t digest; + EXPECT_DIF_BADARG( + dif_otp_ctrl_get_digest(nullptr, GetParam().partition, &digest)); + EXPECT_DIF_BADARG( + dif_otp_ctrl_get_digest(&otp_, GetParam().partition, nullptr)); +} + +// This depends on the maximum length of partition names, which will +// be changing, so turn formatting off. +// clang-format off +INSTANTIATE_TEST_SUITE_P( + AllDigests, GetDigest, + testing::Values( +% for part in parts: +<% + part_name = Name.from_snake_case(part["name"]) + part_name_camel = part_name.as_camel_case() + part_name_define = part_name.as_c_define() +%>\ + % if part in digest_parts: + DigestParams{ + kDifOtpCtrlPartition${part_name_camel}, + true, + OTP_CTRL_${part_name_define}_DIGEST_0_REG_OFFSET, + OTP_CTRL_${part_name_define}_DIGEST_1_REG_OFFSET, + }${"" if loop.last else ","} + % else: + DigestParams{ + kDifOtpCtrlPartition${part_name_camel}, + false, + 0, + 0, + }${"));" if loop.last else ","} + % endif +% endfor +// clang-format on + +class BlockingIoTest : public OtpTest { + protected: + static constexpr size_t kWords = 4; +}; + +TEST_F(BlockingIoTest, Read) { + for (size_t i = 0; i < kWords; ++i) { + auto offset = + OTP_CTRL_PARAM_OWNER_SW_CFG_OFFSET + 0x10 + i * sizeof(uint32_t); + EXPECT_READ32(OTP_CTRL_SW_CFG_WINDOW_REG_OFFSET + offset, i + 1); + } + + std::vector buf(kWords); + EXPECT_DIF_OK(dif_otp_ctrl_read_blocking( + &otp_, kDifOtpCtrlPartitionOwnerSwCfg, 0x10, buf.data(), buf.size())); + EXPECT_THAT(buf, ElementsAre(1, 2, 3, 4)); +} + +TEST_F(BlockingIoTest, BadPartition) { + std::vector buf(kWords); + EXPECT_EQ(dif_otp_ctrl_read_blocking(&otp_, kDifOtpCtrlPartitionHwCfg0, 0x10, + buf.data(), buf.size()), + kDifError); +} + +TEST_F(BlockingIoTest, Unaligned) { + std::vector buf(kWords); + EXPECT_EQ(dif_otp_ctrl_read_blocking(&otp_, kDifOtpCtrlPartitionOwnerSwCfg, + 0x11, buf.data(), buf.size()), + kDifUnaligned); +} + +TEST_F(BlockingIoTest, OutOfRange) { + std::vector buf(0x2f0); + EXPECT_EQ(dif_otp_ctrl_read_blocking(&otp_, kDifOtpCtrlPartitionOwnerSwCfg, + 0x300, buf.data(), buf.size()), + kDifOutOfRange); + EXPECT_EQ(dif_otp_ctrl_read_blocking(&otp_, kDifOtpCtrlPartitionOwnerSwCfg, + 0x10, buf.data(), 0x330), + kDifOutOfRange); +} + +TEST_F(BlockingIoTest, NullArgs) { + std::vector buf(kWords); + EXPECT_DIF_BADARG(dif_otp_ctrl_read_blocking( + nullptr, kDifOtpCtrlPartitionOwnerSwCfg, 0x10, buf.data(), buf.size())); + EXPECT_DIF_BADARG(dif_otp_ctrl_read_blocking( + &otp_, kDifOtpCtrlPartitionOwnerSwCfg, 0x10, nullptr, buf.size())); +} + +} // namespace +} // namespace dif_otp_ctrl_unittest diff --git a/sw/device/lib/dif/dif_otp_ctrl.c b/sw/device/lib/dif/dif_otp_ctrl.c index 8c6944285adc8..cf46bd0513704 100644 --- a/sw/device/lib/dif/dif_otp_ctrl.c +++ b/sw/device/lib/dif/dif_otp_ctrl.c @@ -1,6 +1,8 @@ // Copyright lowRISC contributors. // Licensed under the Apache License, Version 2.0, see LICENSE for details. // SPDX-License-Identifier: Apache-2.0 +// DO NOT EDIT THIS FILE DIRECTLY. +// It has been generated with ./util/design/gen-otp-mmap.py #include "sw/device/lib/dif/dif_otp_ctrl.h" @@ -207,11 +209,11 @@ dif_result_t dif_otp_ctrl_get_status(const dif_otp_ctrl_t *otp, [kDifOtpCtrlStatusCodeOwnerSwCfgError] = OTP_CTRL_STATUS_OWNER_SW_CFG_ERROR_BIT, [kDifOtpCtrlStatusCodeHwCfg0Error] = OTP_CTRL_STATUS_HW_CFG0_ERROR_BIT, - [kDifOtpCtrlStatusCodeLifeCycleError] = - OTP_CTRL_STATUS_LIFE_CYCLE_ERROR_BIT, [kDifOtpCtrlStatusCodeSecret0Error] = OTP_CTRL_STATUS_SECRET0_ERROR_BIT, [kDifOtpCtrlStatusCodeSecret1Error] = OTP_CTRL_STATUS_SECRET1_ERROR_BIT, [kDifOtpCtrlStatusCodeSecret2Error] = OTP_CTRL_STATUS_SECRET2_ERROR_BIT, + [kDifOtpCtrlStatusCodeLifeCycleError] = + OTP_CTRL_STATUS_LIFE_CYCLE_ERROR_BIT, [kDifOtpCtrlStatusCodeDaiError] = OTP_CTRL_STATUS_DAI_ERROR_BIT, [kDifOtpCtrlStatusCodeLciError] = OTP_CTRL_STATUS_LCI_ERROR_BIT, [kDifOtpCtrlStatusCodeTimeoutError] = OTP_CTRL_STATUS_TIMEOUT_ERROR_BIT, @@ -320,52 +322,60 @@ typedef struct partition_info { bool has_digest; } partition_info_t; +// This is generates too many lines with different formatting variants, so +// We opt to just disable formatting. +// clang-format off static const partition_info_t kPartitions[] = { - // TODO: These should be provided by the gen'd header. - // See #3904. - [kDifOtpCtrlPartitionVendorTest] = {.start_addr = - OTP_CTRL_PARAM_VENDOR_TEST_OFFSET, - .len = OTP_CTRL_PARAM_VENDOR_TEST_SIZE, - .align_mask = 0x3, - .is_software = true, - .has_digest = true}, - [kDifOtpCtrlPartitionCreatorSwCfg] = - {.start_addr = OTP_CTRL_PARAM_CREATOR_SW_CFG_OFFSET, - .len = OTP_CTRL_PARAM_CREATOR_SW_CFG_SIZE, - .align_mask = 0x3, - .is_software = true, - .has_digest = true}, - [kDifOtpCtrlPartitionOwnerSwCfg] = {.start_addr = - OTP_CTRL_PARAM_OWNER_SW_CFG_OFFSET, - .len = OTP_CTRL_PARAM_OWNER_SW_CFG_SIZE, - .align_mask = 0x3, - .is_software = true, - .has_digest = true}, - [kDifOtpCtrlPartitionHwCfg0] = {.start_addr = OTP_CTRL_PARAM_HW_CFG0_OFFSET, - .len = OTP_CTRL_PARAM_HW_CFG0_SIZE, - .align_mask = 0x3, - .has_digest = true}, - [kDifOtpCtrlPartitionSecret0] = {.start_addr = - OTP_CTRL_PARAM_SECRET0_OFFSET, - .len = OTP_CTRL_PARAM_SECRET0_SIZE, - .align_mask = 0x7, - .has_digest = true}, - [kDifOtpCtrlPartitionSecret1] = {.start_addr = - OTP_CTRL_PARAM_SECRET1_OFFSET, - .len = OTP_CTRL_PARAM_SECRET1_SIZE, - .align_mask = 0x7, - .has_digest = true}, - [kDifOtpCtrlPartitionSecret2] = {.start_addr = - OTP_CTRL_PARAM_SECRET2_OFFSET, - .len = OTP_CTRL_PARAM_SECRET2_SIZE, - .align_mask = 0x7, - .has_digest = true}, - [kDifOtpCtrlPartitionLifeCycle] = {.start_addr = - OTP_CTRL_PARAM_LIFE_CYCLE_OFFSET, - .len = OTP_CTRL_PARAM_LIFE_CYCLE_SIZE, - .align_mask = 0x3, - .has_digest = false}, + [kDifOtpCtrlPartitionVendorTest] = { + .start_addr = OTP_CTRL_PARAM_VENDOR_TEST_OFFSET, + .len = OTP_CTRL_PARAM_VENDOR_TEST_SIZE, + .align_mask = 0x3, + .is_software = true, + .has_digest = true}, + [kDifOtpCtrlPartitionCreatorSwCfg] = { + .start_addr = OTP_CTRL_PARAM_CREATOR_SW_CFG_OFFSET, + .len = OTP_CTRL_PARAM_CREATOR_SW_CFG_SIZE, + .align_mask = 0x3, + .is_software = true, + .has_digest = true}, + [kDifOtpCtrlPartitionOwnerSwCfg] = { + .start_addr = OTP_CTRL_PARAM_OWNER_SW_CFG_OFFSET, + .len = OTP_CTRL_PARAM_OWNER_SW_CFG_SIZE, + .align_mask = 0x3, + .is_software = true, + .has_digest = true}, + [kDifOtpCtrlPartitionHwCfg0] = { + .start_addr = OTP_CTRL_PARAM_HW_CFG0_OFFSET, + .len = OTP_CTRL_PARAM_HW_CFG0_SIZE, + .align_mask = 0x3, + .is_software = false, + .has_digest = true}, + [kDifOtpCtrlPartitionSecret0] = { + .start_addr = OTP_CTRL_PARAM_SECRET0_OFFSET, + .len = OTP_CTRL_PARAM_SECRET0_SIZE, + .align_mask = 0x7, + .is_software = false, + .has_digest = true}, + [kDifOtpCtrlPartitionSecret1] = { + .start_addr = OTP_CTRL_PARAM_SECRET1_OFFSET, + .len = OTP_CTRL_PARAM_SECRET1_SIZE, + .align_mask = 0x7, + .is_software = false, + .has_digest = true}, + [kDifOtpCtrlPartitionSecret2] = { + .start_addr = OTP_CTRL_PARAM_SECRET2_OFFSET, + .len = OTP_CTRL_PARAM_SECRET2_SIZE, + .align_mask = 0x7, + .is_software = false, + .has_digest = true}, + [kDifOtpCtrlPartitionLifeCycle] = { + .start_addr = OTP_CTRL_PARAM_LIFE_CYCLE_OFFSET, + .len = OTP_CTRL_PARAM_LIFE_CYCLE_SIZE, + .align_mask = 0x3, + .is_software = false, + .has_digest = false}, }; +// clang-format on dif_result_t dif_otp_ctrl_relative_address(dif_otp_ctrl_partition_t partition, uint32_t abs_address, @@ -479,7 +489,7 @@ dif_result_t dif_otp_ctrl_dai_program32(const dif_otp_ctrl_t *otp, // Note furthermore that the LC partition is *not* writeable, so we eject // here. if (kPartitions[partition].align_mask != 0x3 || - partition == kDifOtpCtrlPartitionLifeCycle) { + !kPartitions[partition].has_digest) { return kDifError; } @@ -573,7 +583,7 @@ dif_result_t dif_otp_ctrl_dai_digest(const dif_otp_ctrl_t *otp, } // The LC partition does not have a digest. - if (partition == kDifOtpCtrlPartitionLifeCycle) { + if (!kPartitions[partition].has_digest) { return kDifError; } diff --git a/sw/device/lib/dif/dif_otp_ctrl.h b/sw/device/lib/dif/dif_otp_ctrl.h index a52e4e1c22b7f..703cdf8dddfdc 100644 --- a/sw/device/lib/dif/dif_otp_ctrl.h +++ b/sw/device/lib/dif/dif_otp_ctrl.h @@ -1,6 +1,8 @@ // Copyright lowRISC contributors. // Licensed under the Apache License, Version 2.0, see LICENSE for details. // SPDX-License-Identifier: Apache-2.0 +// DO NOT EDIT THIS FILE DIRECTLY. +// It has been generated with ./util/design/gen-otp-mmap.py #ifndef OPENTITAN_SW_DEVICE_LIB_DIF_DIF_OTP_CTRL_H_ #define OPENTITAN_SW_DEVICE_LIB_DIF_DIF_OTP_CTRL_H_ @@ -29,49 +31,76 @@ extern "C" { */ typedef enum dif_otp_ctrl_partition { /** - * The vendor test area. + * Vendor test partition. * - * This partition is reserved for macro-specific smoke tests. + * This is reserved for manufacturing smoke checks. The OTP wrapper + * control logic inside prim_otp is allowed to read/write to this + * region. ECC uncorrectable errors seen on the functional prim_otp + * interface will not lead to an alert for this partition. + * Instead, such errors will be reported as correctable ECC errors. */ kDifOtpCtrlPartitionVendorTest, /** - * The creator software configuration area. + * Software configuration partition. * - * This partition contains device-specific calibration data. + * This is for device-specific calibration data. For example, clock, + * LDO, RNG. */ kDifOtpCtrlPartitionCreatorSwCfg, /** - * The owner software configuration area. + * Software configuration partition. * - * This partition contains data to e.g. enable ROM hardening features. + * This contains data that changes software behavior in the ROM, for + * example enabling defensive features in ROM or selecting failure + * modes if verification fails. */ kDifOtpCtrlPartitionOwnerSwCfg, /** - * The hardware configuration area. + * Hardware configuration 0 partition. + * + * This contains + * - DEVICE_ID: Unique device identifier. + * - MANUF_STATE: Vector for capturing the manufacturing status. + * - EN_SRAM_IFETCH: Enable / disable execute from SRAM CSR switch. + * - EN_CSRNG_SW_APP_READ: This input efuse is used to enable access + * to the NIST internal state per instance. + * - EN_ENTROPY_SRC_FW_READ: This input efuse is used to enable access + * to the ENTROPY_DATA register directly. + * - EN_ENTROPY_SRC_FW_OVER: This input efuse is used to enable access + * to the firmware override FIFO and other related functions. */ kDifOtpCtrlPartitionHwCfg0, /** - * The device lifecycle area. - */ - kDifOtpCtrlPartitionLifeCycle, - /** - * Scrambled partition 0. + * Secret partition 0. * - * This partition contains TEST lifecycle state unlock tokens. + * This contains TEST lifecycle unlock tokens. */ kDifOtpCtrlPartitionSecret0, /** - * Scrambled partition 1. + * Secret partition 1. * - * This partition contains SRAM and flash scrambling keys. + * This contains SRAM and flash scrambling keys. */ kDifOtpCtrlPartitionSecret1, /** - * Scrambled partition 2. + * Secret partition 2. * - * This partition contains the RMA unlock token and the CreatorRootKey. + * This contains RMA unlock token, creator root key, and creator seed. */ kDifOtpCtrlPartitionSecret2, + /** + * Lifecycle partition. + * + * This contains lifecycle transition count and state. This partition + * cannot be locked since the life cycle state needs to advance to RMA + * in-field. Note that while this partition is not marked secret, it + * is not readable nor writeable via the DAI. Only the LC controller + * can access this partition, and even via the LC controller it is not + * possible to read the raw manufacturing life cycle state in encoded + * form, since that encoding is considered a netlist secret. The LC + * controller only exposes a decoded version of this state. + */ + kDifOtpCtrlPartitionLifeCycle, } dif_otp_ctrl_partition_t; /** @@ -137,10 +166,6 @@ typedef enum dif_otp_ctrl_status_code { * Indicates an error occurred in the `HwCfg0` partition. */ kDifOtpCtrlStatusCodeHwCfg0Error, - /** - * Indicates an error occurred in the `LifeCycle` partition. - */ - kDifOtpCtrlStatusCodeLifeCycleError, /** * Indicates an error occurred in the `Secret0` partition. */ @@ -153,6 +178,10 @@ typedef enum dif_otp_ctrl_status_code { * Indicates an error occurred in the `Secret2` partition. */ kDifOtpCtrlStatusCodeSecret2Error, + /** + * Indicates an error occurred in the `LifeCycle` partition. + */ + kDifOtpCtrlStatusCodeLifeCycleError, /** * Indicates an error occurred in the direct access interface. */ diff --git a/sw/device/lib/dif/dif_otp_ctrl_unittest.cc b/sw/device/lib/dif/dif_otp_ctrl_unittest.cc index cacb79d9e9504..b1ddcaa7c9f5a 100644 --- a/sw/device/lib/dif/dif_otp_ctrl_unittest.cc +++ b/sw/device/lib/dif/dif_otp_ctrl_unittest.cc @@ -1,6 +1,8 @@ // Copyright lowRISC contributors. // Licensed under the Apache License, Version 2.0, see LICENSE for details. // SPDX-License-Identifier: Apache-2.0 +// DO NOT EDIT THIS FILE DIRECTLY. +// It has been generated with ./util/design/gen-otp-mmap.py #include "sw/device/lib/dif/dif_otp_ctrl.h" @@ -134,12 +136,31 @@ TEST_F(CheckTest, NullArgs) { class ReadLockTest : public OtpTest {}; +// Too many formatting variants in template code, so disabling clang-format. +// clang-format off TEST_F(ReadLockTest, IsLocked) { bool flag; + EXPECT_READ32( + OTP_CTRL_VENDOR_TEST_READ_LOCK_REG_OFFSET, + {{OTP_CTRL_VENDOR_TEST_READ_LOCK_VENDOR_TEST_READ_LOCK_BIT, + true}}); + EXPECT_DIF_OK(dif_otp_ctrl_reading_is_locked( + &otp_, kDifOtpCtrlPartitionVendorTest, &flag)); + EXPECT_FALSE(flag); + + EXPECT_READ32( + OTP_CTRL_VENDOR_TEST_READ_LOCK_REG_OFFSET, + {{OTP_CTRL_VENDOR_TEST_READ_LOCK_VENDOR_TEST_READ_LOCK_BIT, + false}}); + EXPECT_DIF_OK(dif_otp_ctrl_reading_is_locked( + &otp_, kDifOtpCtrlPartitionVendorTest, &flag)); + EXPECT_TRUE(flag); + EXPECT_READ32( OTP_CTRL_CREATOR_SW_CFG_READ_LOCK_REG_OFFSET, - {{OTP_CTRL_CREATOR_SW_CFG_READ_LOCK_CREATOR_SW_CFG_READ_LOCK_BIT, true}}); + {{OTP_CTRL_CREATOR_SW_CFG_READ_LOCK_CREATOR_SW_CFG_READ_LOCK_BIT, + true}}); EXPECT_DIF_OK(dif_otp_ctrl_reading_is_locked( &otp_, kDifOtpCtrlPartitionCreatorSwCfg, &flag)); EXPECT_FALSE(flag); @@ -154,14 +175,15 @@ TEST_F(ReadLockTest, IsLocked) { EXPECT_READ32( OTP_CTRL_OWNER_SW_CFG_READ_LOCK_REG_OFFSET, - {{OTP_CTRL_CREATOR_SW_CFG_READ_LOCK_CREATOR_SW_CFG_READ_LOCK_BIT, true}}); + {{OTP_CTRL_OWNER_SW_CFG_READ_LOCK_OWNER_SW_CFG_READ_LOCK_BIT, + true}}); EXPECT_DIF_OK(dif_otp_ctrl_reading_is_locked( &otp_, kDifOtpCtrlPartitionOwnerSwCfg, &flag)); EXPECT_FALSE(flag); EXPECT_READ32( OTP_CTRL_OWNER_SW_CFG_READ_LOCK_REG_OFFSET, - {{OTP_CTRL_CREATOR_SW_CFG_READ_LOCK_CREATOR_SW_CFG_READ_LOCK_BIT, + {{OTP_CTRL_OWNER_SW_CFG_READ_LOCK_OWNER_SW_CFG_READ_LOCK_BIT, false}}); EXPECT_DIF_OK(dif_otp_ctrl_reading_is_locked( &otp_, kDifOtpCtrlPartitionOwnerSwCfg, &flag)); @@ -172,41 +194,77 @@ TEST_F(ReadLockTest, Lock) { EXPECT_READ32(OTP_CTRL_DIRECT_ACCESS_REGWEN_REG_OFFSET, 1); EXPECT_WRITE32( OTP_CTRL_VENDOR_TEST_READ_LOCK_REG_OFFSET, - {{OTP_CTRL_VENDOR_TEST_READ_LOCK_VENDOR_TEST_READ_LOCK_BIT, false}}); - EXPECT_DIF_OK( - dif_otp_ctrl_lock_reading(&otp_, kDifOtpCtrlPartitionVendorTest)); + {{OTP_CTRL_VENDOR_TEST_READ_LOCK_VENDOR_TEST_READ_LOCK_BIT, + false}}); + EXPECT_DIF_OK(dif_otp_ctrl_lock_reading( + &otp_, kDifOtpCtrlPartitionVendorTest)); EXPECT_READ32(OTP_CTRL_DIRECT_ACCESS_REGWEN_REG_OFFSET, 1); EXPECT_WRITE32( OTP_CTRL_CREATOR_SW_CFG_READ_LOCK_REG_OFFSET, {{OTP_CTRL_CREATOR_SW_CFG_READ_LOCK_CREATOR_SW_CFG_READ_LOCK_BIT, false}}); - EXPECT_DIF_OK( - dif_otp_ctrl_lock_reading(&otp_, kDifOtpCtrlPartitionCreatorSwCfg)); + EXPECT_DIF_OK(dif_otp_ctrl_lock_reading( + &otp_, kDifOtpCtrlPartitionCreatorSwCfg)); EXPECT_READ32(OTP_CTRL_DIRECT_ACCESS_REGWEN_REG_OFFSET, 1); EXPECT_WRITE32( OTP_CTRL_OWNER_SW_CFG_READ_LOCK_REG_OFFSET, - {{OTP_CTRL_OWNER_SW_CFG_READ_LOCK_OWNER_SW_CFG_READ_LOCK_BIT, false}}); - EXPECT_DIF_OK( - dif_otp_ctrl_lock_reading(&otp_, kDifOtpCtrlPartitionOwnerSwCfg)); + {{OTP_CTRL_OWNER_SW_CFG_READ_LOCK_OWNER_SW_CFG_READ_LOCK_BIT, + false}}); + EXPECT_DIF_OK(dif_otp_ctrl_lock_reading( + &otp_, kDifOtpCtrlPartitionOwnerSwCfg)); } -TEST_F(ReadLockTest, HwPartition) { +TEST_F(ReadLockTest, NotLockablePartitions) { bool flag; EXPECT_DIF_BADARG( dif_otp_ctrl_lock_reading(&otp_, kDifOtpCtrlPartitionHwCfg0)); + EXPECT_DIF_BADARG(dif_otp_ctrl_reading_is_locked( + &otp_, kDifOtpCtrlPartitionHwCfg0, &flag)); + + EXPECT_DIF_BADARG( + dif_otp_ctrl_lock_reading(&otp_, kDifOtpCtrlPartitionSecret0)); EXPECT_DIF_BADARG(dif_otp_ctrl_reading_is_locked( &otp_, kDifOtpCtrlPartitionSecret0, &flag)); + + EXPECT_DIF_BADARG( + dif_otp_ctrl_lock_reading(&otp_, kDifOtpCtrlPartitionSecret1)); + EXPECT_DIF_BADARG(dif_otp_ctrl_reading_is_locked( + &otp_, kDifOtpCtrlPartitionSecret1, &flag)); + + EXPECT_DIF_BADARG( + dif_otp_ctrl_lock_reading(&otp_, kDifOtpCtrlPartitionSecret2)); + EXPECT_DIF_BADARG(dif_otp_ctrl_reading_is_locked( + &otp_, kDifOtpCtrlPartitionSecret2, &flag)); + + EXPECT_DIF_BADARG( + dif_otp_ctrl_lock_reading(&otp_, kDifOtpCtrlPartitionLifeCycle)); + EXPECT_DIF_BADARG(dif_otp_ctrl_reading_is_locked( + &otp_, kDifOtpCtrlPartitionLifeCycle, &flag)); } +// clang-format on TEST_F(ReadLockTest, NullArgs) { bool flag; + EXPECT_DIF_BADARG(dif_otp_ctrl_reading_is_locked( + nullptr, kDifOtpCtrlPartitionVendorTest, &flag)); + EXPECT_DIF_BADARG(dif_otp_ctrl_reading_is_locked( + &otp_, kDifOtpCtrlPartitionVendorTest, nullptr)); + EXPECT_DIF_BADARG( + dif_otp_ctrl_lock_reading(nullptr, kDifOtpCtrlPartitionVendorTest)); + + EXPECT_DIF_BADARG(dif_otp_ctrl_reading_is_locked( + nullptr, kDifOtpCtrlPartitionCreatorSwCfg, &flag)); + EXPECT_DIF_BADARG(dif_otp_ctrl_reading_is_locked( + &otp_, kDifOtpCtrlPartitionCreatorSwCfg, nullptr)); + EXPECT_DIF_BADARG( + dif_otp_ctrl_lock_reading(nullptr, kDifOtpCtrlPartitionCreatorSwCfg)); + EXPECT_DIF_BADARG(dif_otp_ctrl_reading_is_locked( nullptr, kDifOtpCtrlPartitionOwnerSwCfg, &flag)); EXPECT_DIF_BADARG(dif_otp_ctrl_reading_is_locked( &otp_, kDifOtpCtrlPartitionOwnerSwCfg, nullptr)); - EXPECT_DIF_BADARG( dif_otp_ctrl_lock_reading(nullptr, kDifOtpCtrlPartitionOwnerSwCfg)); } @@ -259,6 +317,7 @@ TEST_F(StatusTest, NullArgs) { } struct RelativeAddressParams { + std::string name; dif_otp_ctrl_partition_t partition; uint32_t abs_address; dif_result_t expected_result; @@ -277,33 +336,231 @@ TEST_P(RelativeAddress, RelativeAddress) { EXPECT_EQ(got_relative_address, GetParam().expected_relative_address); } -INSTANTIATE_TEST_SUITE_P(AllPartitions, RelativeAddress, - testing::Values( - RelativeAddressParams{ - kDifOtpCtrlPartitionCreatorSwCfg, - OTP_CTRL_PARAM_CREATOR_SW_CFG_OFFSET + 4, - kDifOk, - 4, - }, - RelativeAddressParams{ - kDifOtpCtrlPartitionCreatorSwCfg, - OTP_CTRL_PARAM_CREATOR_SW_CFG_OFFSET + 1, - kDifUnaligned, - 0, - }, - RelativeAddressParams{ - kDifOtpCtrlPartitionCreatorSwCfg, - OTP_CTRL_PARAM_CREATOR_SW_CFG_OFFSET - 4, - kDifOutOfRange, - 0, - }, - RelativeAddressParams{ - kDifOtpCtrlPartitionCreatorSwCfg, - OTP_CTRL_PARAM_CREATOR_SW_CFG_OFFSET + - OTP_CTRL_PARAM_CREATOR_SW_CFG_SIZE, - kDifOutOfRange, - 0, - })); +INSTANTIATE_TEST_SUITE_P( + AllPartitions, RelativeAddress, + testing::Values( + RelativeAddressParams{ + "VendorTestOkay", + kDifOtpCtrlPartitionVendorTest, + OTP_CTRL_PARAM_VENDOR_TEST_OFFSET + 4, + kDifOk, + 4, + }, + RelativeAddressParams{ + "VendorTestUnaligned", + kDifOtpCtrlPartitionVendorTest, + OTP_CTRL_PARAM_VENDOR_TEST_OFFSET + 1, + kDifUnaligned, + 0, + }, + RelativeAddressParams{ + "VendorTestOutOfRangePastEnd", + kDifOtpCtrlPartitionVendorTest, + OTP_CTRL_PARAM_VENDOR_TEST_OFFSET + OTP_CTRL_PARAM_VENDOR_TEST_SIZE, + kDifOutOfRange, + 0, + }, + RelativeAddressParams{ + "CreatorSwCfgOkay", + kDifOtpCtrlPartitionCreatorSwCfg, + OTP_CTRL_PARAM_CREATOR_SW_CFG_OFFSET + 4, + kDifOk, + 4, + }, + RelativeAddressParams{ + "CreatorSwCfgUnaligned", + kDifOtpCtrlPartitionCreatorSwCfg, + OTP_CTRL_PARAM_CREATOR_SW_CFG_OFFSET + 1, + kDifUnaligned, + 0, + }, + RelativeAddressParams{ + "CreatorSwCfgOutOfRangeBeforeStart", + kDifOtpCtrlPartitionCreatorSwCfg, + OTP_CTRL_PARAM_CREATOR_SW_CFG_OFFSET - 4, + kDifOutOfRange, + 0, + }, + RelativeAddressParams{ + "CreatorSwCfgOutOfRangePastEnd", + kDifOtpCtrlPartitionCreatorSwCfg, + OTP_CTRL_PARAM_CREATOR_SW_CFG_OFFSET + + OTP_CTRL_PARAM_CREATOR_SW_CFG_SIZE, + kDifOutOfRange, + 0, + }, + RelativeAddressParams{ + "OwnerSwCfgOkay", + kDifOtpCtrlPartitionOwnerSwCfg, + OTP_CTRL_PARAM_OWNER_SW_CFG_OFFSET + 4, + kDifOk, + 4, + }, + RelativeAddressParams{ + "OwnerSwCfgUnaligned", + kDifOtpCtrlPartitionOwnerSwCfg, + OTP_CTRL_PARAM_OWNER_SW_CFG_OFFSET + 1, + kDifUnaligned, + 0, + }, + RelativeAddressParams{ + "OwnerSwCfgOutOfRangeBeforeStart", + kDifOtpCtrlPartitionOwnerSwCfg, + OTP_CTRL_PARAM_OWNER_SW_CFG_OFFSET - 4, + kDifOutOfRange, + 0, + }, + RelativeAddressParams{ + "OwnerSwCfgOutOfRangePastEnd", + kDifOtpCtrlPartitionOwnerSwCfg, + OTP_CTRL_PARAM_OWNER_SW_CFG_OFFSET + + OTP_CTRL_PARAM_OWNER_SW_CFG_SIZE, + kDifOutOfRange, + 0, + }, + RelativeAddressParams{ + "HwCfg0Okay", + kDifOtpCtrlPartitionHwCfg0, + OTP_CTRL_PARAM_HW_CFG0_OFFSET + 4, + kDifOk, + 4, + }, + RelativeAddressParams{ + "HwCfg0Unaligned", + kDifOtpCtrlPartitionHwCfg0, + OTP_CTRL_PARAM_HW_CFG0_OFFSET + 1, + kDifUnaligned, + 0, + }, + RelativeAddressParams{ + "HwCfg0OutOfRangeBeforeStart", + kDifOtpCtrlPartitionHwCfg0, + OTP_CTRL_PARAM_HW_CFG0_OFFSET - 4, + kDifOutOfRange, + 0, + }, + RelativeAddressParams{ + "HwCfg0OutOfRangePastEnd", + kDifOtpCtrlPartitionHwCfg0, + OTP_CTRL_PARAM_HW_CFG0_OFFSET + OTP_CTRL_PARAM_HW_CFG0_SIZE, + kDifOutOfRange, + 0, + }, + RelativeAddressParams{ + "Secret0Okay", + kDifOtpCtrlPartitionSecret0, + OTP_CTRL_PARAM_SECRET0_OFFSET + 8, + kDifOk, + 8, + }, + RelativeAddressParams{ + "Secret0Unaligned", + kDifOtpCtrlPartitionSecret0, + OTP_CTRL_PARAM_SECRET0_OFFSET + 1, + kDifUnaligned, + 0, + }, + RelativeAddressParams{ + "Secret0OutOfRangeBeforeStart", + kDifOtpCtrlPartitionSecret0, + OTP_CTRL_PARAM_SECRET0_OFFSET - 8, + kDifOutOfRange, + 0, + }, + RelativeAddressParams{ + "Secret0OutOfRangePastEnd", + kDifOtpCtrlPartitionSecret0, + OTP_CTRL_PARAM_SECRET0_OFFSET + OTP_CTRL_PARAM_SECRET0_SIZE, + kDifOutOfRange, + 0, + }, + RelativeAddressParams{ + "Secret1Okay", + kDifOtpCtrlPartitionSecret1, + OTP_CTRL_PARAM_SECRET1_OFFSET + 8, + kDifOk, + 8, + }, + RelativeAddressParams{ + "Secret1Unaligned", + kDifOtpCtrlPartitionSecret1, + OTP_CTRL_PARAM_SECRET1_OFFSET + 1, + kDifUnaligned, + 0, + }, + RelativeAddressParams{ + "Secret1OutOfRangeBeforeStart", + kDifOtpCtrlPartitionSecret1, + OTP_CTRL_PARAM_SECRET1_OFFSET - 8, + kDifOutOfRange, + 0, + }, + RelativeAddressParams{ + "Secret1OutOfRangePastEnd", + kDifOtpCtrlPartitionSecret1, + OTP_CTRL_PARAM_SECRET1_OFFSET + OTP_CTRL_PARAM_SECRET1_SIZE, + kDifOutOfRange, + 0, + }, + RelativeAddressParams{ + "Secret2Okay", + kDifOtpCtrlPartitionSecret2, + OTP_CTRL_PARAM_SECRET2_OFFSET + 8, + kDifOk, + 8, + }, + RelativeAddressParams{ + "Secret2Unaligned", + kDifOtpCtrlPartitionSecret2, + OTP_CTRL_PARAM_SECRET2_OFFSET + 1, + kDifUnaligned, + 0, + }, + RelativeAddressParams{ + "Secret2OutOfRangeBeforeStart", + kDifOtpCtrlPartitionSecret2, + OTP_CTRL_PARAM_SECRET2_OFFSET - 8, + kDifOutOfRange, + 0, + }, + RelativeAddressParams{ + "Secret2OutOfRangePastEnd", + kDifOtpCtrlPartitionSecret2, + OTP_CTRL_PARAM_SECRET2_OFFSET + OTP_CTRL_PARAM_SECRET2_SIZE, + kDifOutOfRange, + 0, + }, + RelativeAddressParams{ + "LifeCycleOkay", + kDifOtpCtrlPartitionLifeCycle, + OTP_CTRL_PARAM_LIFE_CYCLE_OFFSET + 4, + kDifOk, + 4, + }, + RelativeAddressParams{ + "LifeCycleUnaligned", + kDifOtpCtrlPartitionLifeCycle, + OTP_CTRL_PARAM_LIFE_CYCLE_OFFSET + 1, + kDifUnaligned, + 0, + }, + RelativeAddressParams{ + "LifeCycleOutOfRangeBeforeStart", + kDifOtpCtrlPartitionLifeCycle, + OTP_CTRL_PARAM_LIFE_CYCLE_OFFSET - 4, + kDifOutOfRange, + 0, + }, + RelativeAddressParams{ + "LifeCycleOutOfRangePastEnd", + kDifOtpCtrlPartitionLifeCycle, + OTP_CTRL_PARAM_LIFE_CYCLE_OFFSET + OTP_CTRL_PARAM_LIFE_CYCLE_SIZE, + kDifOutOfRange, + 0, + }), + [](const testing::TestParamInfo &info) { + return info.param.name; + }); class DaiReadTest : public OtpTest {}; @@ -330,6 +587,47 @@ TEST_F(DaiReadTest, Read32) { } TEST_F(DaiReadTest, Read64) { + uint64_t val; + EXPECT_READ32( + OTP_CTRL_DIRECT_ACCESS_REGWEN_REG_OFFSET, + {{OTP_CTRL_DIRECT_ACCESS_REGWEN_DIRECT_ACCESS_REGWEN_BIT, true}}); + EXPECT_WRITE32(OTP_CTRL_DIRECT_ACCESS_ADDRESS_REG_OFFSET, + OTP_CTRL_PARAM_SECRET0_OFFSET + 0x8); + EXPECT_WRITE32(OTP_CTRL_DIRECT_ACCESS_CMD_REG_OFFSET, + {{OTP_CTRL_DIRECT_ACCESS_CMD_RD_BIT, true}}); + + EXPECT_DIF_OK(dif_otp_ctrl_dai_read_start(&otp_, kDifOtpCtrlPartitionSecret0, + /*address=*/0x8)); + + EXPECT_READ32( + OTP_CTRL_DIRECT_ACCESS_REGWEN_REG_OFFSET, + {{OTP_CTRL_DIRECT_ACCESS_REGWEN_DIRECT_ACCESS_REGWEN_BIT, true}}); + EXPECT_READ32(OTP_CTRL_DIRECT_ACCESS_RDATA_1_REG_OFFSET, 0x12345678); + EXPECT_READ32(OTP_CTRL_DIRECT_ACCESS_RDATA_0_REG_OFFSET, 0x90abcdef); + + EXPECT_DIF_OK(dif_otp_ctrl_dai_read64_end(&otp_, &val)); + EXPECT_EQ(val, 0x1234567890abcdef); + + EXPECT_READ32( + OTP_CTRL_DIRECT_ACCESS_REGWEN_REG_OFFSET, + {{OTP_CTRL_DIRECT_ACCESS_REGWEN_DIRECT_ACCESS_REGWEN_BIT, true}}); + EXPECT_WRITE32(OTP_CTRL_DIRECT_ACCESS_ADDRESS_REG_OFFSET, + OTP_CTRL_PARAM_SECRET1_OFFSET + 0x8); + EXPECT_WRITE32(OTP_CTRL_DIRECT_ACCESS_CMD_REG_OFFSET, + {{OTP_CTRL_DIRECT_ACCESS_CMD_RD_BIT, true}}); + + EXPECT_DIF_OK(dif_otp_ctrl_dai_read_start(&otp_, kDifOtpCtrlPartitionSecret1, + /*address=*/0x8)); + + EXPECT_READ32( + OTP_CTRL_DIRECT_ACCESS_REGWEN_REG_OFFSET, + {{OTP_CTRL_DIRECT_ACCESS_REGWEN_DIRECT_ACCESS_REGWEN_BIT, true}}); + EXPECT_READ32(OTP_CTRL_DIRECT_ACCESS_RDATA_1_REG_OFFSET, 0x12345678); + EXPECT_READ32(OTP_CTRL_DIRECT_ACCESS_RDATA_0_REG_OFFSET, 0x90abcdef); + + EXPECT_DIF_OK(dif_otp_ctrl_dai_read64_end(&otp_, &val)); + EXPECT_EQ(val, 0x1234567890abcdef); + EXPECT_READ32( OTP_CTRL_DIRECT_ACCESS_REGWEN_REG_OFFSET, {{OTP_CTRL_DIRECT_ACCESS_REGWEN_DIRECT_ACCESS_REGWEN_BIT, true}}); @@ -347,7 +645,6 @@ TEST_F(DaiReadTest, Read64) { EXPECT_READ32(OTP_CTRL_DIRECT_ACCESS_RDATA_1_REG_OFFSET, 0x12345678); EXPECT_READ32(OTP_CTRL_DIRECT_ACCESS_RDATA_0_REG_OFFSET, 0x90abcdef); - uint64_t val; EXPECT_DIF_OK(dif_otp_ctrl_dai_read64_end(&otp_, &val)); EXPECT_EQ(val, 0x1234567890abcdef); } @@ -501,6 +798,19 @@ TEST_F(DaiProgramTest, NullArgs) { class DaiDigestTest : public OtpTest {}; TEST_F(DaiDigestTest, DigestSw) { + EXPECT_READ32( + OTP_CTRL_DIRECT_ACCESS_REGWEN_REG_OFFSET, + {{OTP_CTRL_DIRECT_ACCESS_REGWEN_DIRECT_ACCESS_REGWEN_BIT, true}}); + EXPECT_WRITE32(OTP_CTRL_DIRECT_ACCESS_ADDRESS_REG_OFFSET, + OTP_CTRL_PARAM_VENDOR_TEST_DIGEST_OFFSET); + EXPECT_WRITE32(OTP_CTRL_DIRECT_ACCESS_WDATA_0_REG_OFFSET, 0x00abcdef); + EXPECT_WRITE32(OTP_CTRL_DIRECT_ACCESS_WDATA_1_REG_OFFSET, 0xabcdef00); + EXPECT_WRITE32(OTP_CTRL_DIRECT_ACCESS_CMD_REG_OFFSET, + {{OTP_CTRL_DIRECT_ACCESS_CMD_WR_BIT, true}}); + + EXPECT_DIF_OK(dif_otp_ctrl_dai_digest(&otp_, kDifOtpCtrlPartitionVendorTest, + /*digest=*/0xabcdef0000abcdef)); + EXPECT_READ32( OTP_CTRL_DIRECT_ACCESS_REGWEN_REG_OFFSET, {{OTP_CTRL_DIRECT_ACCESS_REGWEN_DIRECT_ACCESS_REGWEN_BIT, true}}); @@ -513,6 +823,19 @@ TEST_F(DaiDigestTest, DigestSw) { EXPECT_DIF_OK(dif_otp_ctrl_dai_digest(&otp_, kDifOtpCtrlPartitionCreatorSwCfg, /*digest=*/0xabcdef0000abcdef)); + + EXPECT_READ32( + OTP_CTRL_DIRECT_ACCESS_REGWEN_REG_OFFSET, + {{OTP_CTRL_DIRECT_ACCESS_REGWEN_DIRECT_ACCESS_REGWEN_BIT, true}}); + EXPECT_WRITE32(OTP_CTRL_DIRECT_ACCESS_ADDRESS_REG_OFFSET, + OTP_CTRL_PARAM_OWNER_SW_CFG_DIGEST_OFFSET); + EXPECT_WRITE32(OTP_CTRL_DIRECT_ACCESS_WDATA_0_REG_OFFSET, 0x00abcdef); + EXPECT_WRITE32(OTP_CTRL_DIRECT_ACCESS_WDATA_1_REG_OFFSET, 0xabcdef00); + EXPECT_WRITE32(OTP_CTRL_DIRECT_ACCESS_CMD_REG_OFFSET, + {{OTP_CTRL_DIRECT_ACCESS_CMD_WR_BIT, true}}); + + EXPECT_DIF_OK(dif_otp_ctrl_dai_digest(&otp_, kDifOtpCtrlPartitionOwnerSwCfg, + /*digest=*/0xabcdef0000abcdef)); } TEST_F(DaiDigestTest, DigestHw) { @@ -520,12 +843,45 @@ TEST_F(DaiDigestTest, DigestHw) { OTP_CTRL_DIRECT_ACCESS_REGWEN_REG_OFFSET, {{OTP_CTRL_DIRECT_ACCESS_REGWEN_DIRECT_ACCESS_REGWEN_BIT, true}}); EXPECT_WRITE32(OTP_CTRL_DIRECT_ACCESS_ADDRESS_REG_OFFSET, - OTP_CTRL_PARAM_DEVICE_ID_OFFSET); + OTP_CTRL_PARAM_HW_CFG0_OFFSET); + EXPECT_WRITE32(OTP_CTRL_DIRECT_ACCESS_CMD_REG_OFFSET, + {{OTP_CTRL_DIRECT_ACCESS_CMD_DIGEST_BIT, true}}); + + EXPECT_DIF_OK(dif_otp_ctrl_dai_digest(&otp_, kDifOtpCtrlPartitionHwCfg0, + /*digest=*/0)); + + EXPECT_READ32( + OTP_CTRL_DIRECT_ACCESS_REGWEN_REG_OFFSET, + {{OTP_CTRL_DIRECT_ACCESS_REGWEN_DIRECT_ACCESS_REGWEN_BIT, true}}); + EXPECT_WRITE32(OTP_CTRL_DIRECT_ACCESS_ADDRESS_REG_OFFSET, + OTP_CTRL_PARAM_SECRET0_OFFSET); + EXPECT_WRITE32(OTP_CTRL_DIRECT_ACCESS_CMD_REG_OFFSET, + {{OTP_CTRL_DIRECT_ACCESS_CMD_DIGEST_BIT, true}}); + + EXPECT_DIF_OK(dif_otp_ctrl_dai_digest(&otp_, kDifOtpCtrlPartitionSecret0, + /*digest=*/0)); + + EXPECT_READ32( + OTP_CTRL_DIRECT_ACCESS_REGWEN_REG_OFFSET, + {{OTP_CTRL_DIRECT_ACCESS_REGWEN_DIRECT_ACCESS_REGWEN_BIT, true}}); + EXPECT_WRITE32(OTP_CTRL_DIRECT_ACCESS_ADDRESS_REG_OFFSET, + OTP_CTRL_PARAM_SECRET1_OFFSET); + EXPECT_WRITE32(OTP_CTRL_DIRECT_ACCESS_CMD_REG_OFFSET, + {{OTP_CTRL_DIRECT_ACCESS_CMD_DIGEST_BIT, true}}); + + EXPECT_DIF_OK(dif_otp_ctrl_dai_digest(&otp_, kDifOtpCtrlPartitionSecret1, + /*digest=*/0)); + + EXPECT_READ32( + OTP_CTRL_DIRECT_ACCESS_REGWEN_REG_OFFSET, + {{OTP_CTRL_DIRECT_ACCESS_REGWEN_DIRECT_ACCESS_REGWEN_BIT, true}}); + EXPECT_WRITE32(OTP_CTRL_DIRECT_ACCESS_ADDRESS_REG_OFFSET, + OTP_CTRL_PARAM_SECRET2_OFFSET); EXPECT_WRITE32(OTP_CTRL_DIRECT_ACCESS_CMD_REG_OFFSET, {{OTP_CTRL_DIRECT_ACCESS_CMD_DIGEST_BIT, true}}); - EXPECT_DIF_OK( - dif_otp_ctrl_dai_digest(&otp_, kDifOtpCtrlPartitionHwCfg0, /*digest=*/0)); + EXPECT_DIF_OK(dif_otp_ctrl_dai_digest(&otp_, kDifOtpCtrlPartitionSecret2, + /*digest=*/0)); } TEST_F(DaiDigestTest, BadPartition) { @@ -594,6 +950,7 @@ TEST_F(IsDigestComputed, Success) { struct DigestParams { dif_otp_ctrl_partition_t partition; + bool has_digest; ptrdiff_t reg0, reg1; }; @@ -601,7 +958,7 @@ class GetDigest : public OtpTest, public testing::WithParamInterface {}; TEST_P(GetDigest, GetDigest) { - if (GetParam().partition == kDifOtpCtrlPartitionLifeCycle) { + if (!GetParam().has_digest) { uint64_t digest; EXPECT_DIF_BADARG( dif_otp_ctrl_get_digest(&otp_, GetParam().partition, &digest)); @@ -617,7 +974,7 @@ TEST_P(GetDigest, GetDigest) { } TEST_P(GetDigest, BadDigest) { - if (GetParam().partition == kDifOtpCtrlPartitionLifeCycle) { + if (!GetParam().has_digest) { return; } @@ -637,43 +994,61 @@ TEST_P(GetDigest, NullArgs) { dif_otp_ctrl_get_digest(&otp_, GetParam().partition, nullptr)); } -INSTANTIATE_TEST_SUITE_P(AllDigests, GetDigest, - testing::Values( - DigestParams{ - kDifOtpCtrlPartitionCreatorSwCfg, - OTP_CTRL_CREATOR_SW_CFG_DIGEST_0_REG_OFFSET, - OTP_CTRL_CREATOR_SW_CFG_DIGEST_1_REG_OFFSET, - }, - DigestParams{ - kDifOtpCtrlPartitionOwnerSwCfg, - OTP_CTRL_OWNER_SW_CFG_DIGEST_0_REG_OFFSET, - OTP_CTRL_OWNER_SW_CFG_DIGEST_1_REG_OFFSET, - }, - DigestParams{ - kDifOtpCtrlPartitionHwCfg0, - OTP_CTRL_HW_CFG0_DIGEST_0_REG_OFFSET, - OTP_CTRL_HW_CFG0_DIGEST_1_REG_OFFSET, - }, - DigestParams{ - kDifOtpCtrlPartitionSecret0, - OTP_CTRL_SECRET0_DIGEST_0_REG_OFFSET, - OTP_CTRL_SECRET0_DIGEST_1_REG_OFFSET, - }, - DigestParams{ - kDifOtpCtrlPartitionSecret1, - OTP_CTRL_SECRET1_DIGEST_0_REG_OFFSET, - OTP_CTRL_SECRET1_DIGEST_1_REG_OFFSET, - }, - DigestParams{ - kDifOtpCtrlPartitionSecret2, - OTP_CTRL_SECRET2_DIGEST_0_REG_OFFSET, - OTP_CTRL_SECRET2_DIGEST_1_REG_OFFSET, - }, - DigestParams{ - kDifOtpCtrlPartitionLifeCycle, - 0, - 0, - })); +// This depends on the maximum length of partition names, which will +// be changing, so turn formatting off. +// clang-format off +INSTANTIATE_TEST_SUITE_P( + AllDigests, GetDigest, + testing::Values( + DigestParams{ + kDifOtpCtrlPartitionVendorTest, + true, + OTP_CTRL_VENDOR_TEST_DIGEST_0_REG_OFFSET, + OTP_CTRL_VENDOR_TEST_DIGEST_1_REG_OFFSET, + }, + DigestParams{ + kDifOtpCtrlPartitionCreatorSwCfg, + true, + OTP_CTRL_CREATOR_SW_CFG_DIGEST_0_REG_OFFSET, + OTP_CTRL_CREATOR_SW_CFG_DIGEST_1_REG_OFFSET, + }, + DigestParams{ + kDifOtpCtrlPartitionOwnerSwCfg, + true, + OTP_CTRL_OWNER_SW_CFG_DIGEST_0_REG_OFFSET, + OTP_CTRL_OWNER_SW_CFG_DIGEST_1_REG_OFFSET, + }, + DigestParams{ + kDifOtpCtrlPartitionHwCfg0, + true, + OTP_CTRL_HW_CFG0_DIGEST_0_REG_OFFSET, + OTP_CTRL_HW_CFG0_DIGEST_1_REG_OFFSET, + }, + DigestParams{ + kDifOtpCtrlPartitionSecret0, + true, + OTP_CTRL_SECRET0_DIGEST_0_REG_OFFSET, + OTP_CTRL_SECRET0_DIGEST_1_REG_OFFSET, + }, + DigestParams{ + kDifOtpCtrlPartitionSecret1, + true, + OTP_CTRL_SECRET1_DIGEST_0_REG_OFFSET, + OTP_CTRL_SECRET1_DIGEST_1_REG_OFFSET, + }, + DigestParams{ + kDifOtpCtrlPartitionSecret2, + true, + OTP_CTRL_SECRET2_DIGEST_0_REG_OFFSET, + OTP_CTRL_SECRET2_DIGEST_1_REG_OFFSET, + }, + DigestParams{ + kDifOtpCtrlPartitionLifeCycle, + false, + 0, + 0, + })); +// clang-format on class BlockingIoTest : public OtpTest { protected: diff --git a/util/design/gen-otp-mmap.py b/util/design/gen-otp-mmap.py index e45665cb23c51..a974c3bbfeee7 100755 --- a/util/design/gen-otp-mmap.py +++ b/util/design/gen-otp-mmap.py @@ -41,6 +41,11 @@ # code templates to render COV_TEMPLATES = ["hw/ip/otp_ctrl/data/otp_ctrl_cov_bind.sv.tpl"] DATA_TEMPLATES = ["hw/ip/otp_ctrl/data/otp_ctrl.hjson.tpl"] +DIF_TEMPLATES = [ + "hw/ip/otp_ctrl/data/dif_otp_ctrl.c.tpl", + "hw/ip/otp_ctrl/data/dif_otp_ctrl.h.tpl", + "hw/ip/otp_ctrl/data/dif_otp_ctrl_unittest.cc.tpl" +] ENV_TEMPLATES = [ "hw/ip/otp_ctrl/data/otp_ctrl_env_cov.sv.tpl", "hw/ip/otp_ctrl/data/otp_ctrl_env_pkg.sv.tpl", @@ -137,6 +142,11 @@ def main(): stem_path = Path(template).stem target_path = Path(template).parent / stem_path render_template(template, target_path, otp_mmap, TPL_GEN_COMMENT) + for template in DIF_TEMPLATES: + stem_path = Path(template).stem + target_path = (Path.cwd() / "sw" / "device" / "lib" / "dif" / + stem_path) + render_template(template, target_path, otp_mmap, TPL_GEN_COMMENT) for template in ENV_TEMPLATES: stem_path = Path(template).stem target_path = Path(template).parents[1] / "dv" / "env" / stem_path