Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflow_metadata/pr_hash
Original file line number Diff line number Diff line change
@@ -1 +1 @@
2578805cc6716c99d7f0c49eb750cbc5eb2bfacc7a6f88d21390b09984a6b0f63cacfe194aa644cc532f2f8db0e54306
02bc991df25f3f59250f38bf089cc10e6a3b9a53e9742b26504f10740f855a6812bd8b3b06fd06e1fd0d7880f08ead3a
2 changes: 1 addition & 1 deletion .github/workflow_metadata/pr_timestamp
Original file line number Diff line number Diff line change
@@ -1 +1 @@
1757015124
1757020066
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ int part_read_compare(
// depend on the partition.
for (uint32_t addr = part->address;
addr < part->digest_address;
addr += part->granularity) {
addr += part->granularity / 8) {
dai_rd(addr, &act_data[0], &act_data[1], part->granularity, exp_status);
mismatches += compare(act_data[0], exp_data[0], addr);
// Check second 32 bit only if the partition has a granularity
Expand All @@ -75,7 +75,11 @@ int part_read_compare(
return mismatches;
}

int part_zeroize(const partition_t* part, uint32_t exp_status) {
int part_zeroize(
const partition_t* part,
uint8_t only_marker,
uint8_t only_until_half_data,
uint32_t exp_status) {
uint32_t rdata[2];
int mismatches = 0;

Expand All @@ -85,11 +89,20 @@ int part_zeroize(const partition_t* part, uint32_t exp_status) {
mismatches += compare(rdata[0], 0xFFFFFFFF, part->zer_address);
mismatches += compare(rdata[1], 0xFFFFFFFF, part->zer_address + 4);

if (only_marker) return mismatches;

// Secondly, zeroize the data, for which the granularity and size
// depend on the partition.
uint32_t addr_bound = part->digest_address;
if (only_until_half_data) {
// In this case, set the address bound half way between the
// digest address and the base address.
addr_bound -= (part->digest_address - part->address) / 2;
}

for (uint32_t addr = part->address;
addr < part->digest_address;
addr += part->granularity) {
addr < addr_bound;
addr += part->granularity / 8) {
dai_zer(addr, &rdata[0], &rdata[1], part->granularity, exp_status);
mismatches += compare(rdata[0], 0xFFFFFFFF, addr);
// Check second 32 bit only if the partition has a granularity
Expand All @@ -99,11 +112,13 @@ int part_zeroize(const partition_t* part, uint32_t exp_status) {
}
}

// Finally, zeroize the digest, which always has a granularity and
// size of 64 bit.
dai_zer(part->digest_address, &rdata[0], &rdata[1], 64, exp_status);
mismatches += compare(rdata[0], 0xFFFFFFFF, part->digest_address);
mismatches += compare(rdata[1], 0xFFFFFFFF, part->digest_address + 4);
if (!only_until_half_data) {
// Finally, zeroize the digest, which always has a granularity and
// size of 64 bit.
dai_zer(part->digest_address, &rdata[0], &rdata[1], 64, exp_status);
mismatches += compare(rdata[0], 0xFFFFFFFF, part->digest_address);
mismatches += compare(rdata[1], 0xFFFFFFFF, part->digest_address + 4);
}

return mismatches;
}
Expand All @@ -130,25 +145,20 @@ int check_part_zeroized(
return mismatches;
}

void test_main (void) {
int prepare_test(const partition_t* part, uint32_t rd_lock_csr_addr) {
// Initialize test constants.
const uint32_t exp_data[] = {0xA5A5A5A5, 0x96969696};

// Choose one of the zeroizable SW partitions with a CSR read lock.
const partition_t part = partitions[CPTRA_SS_LOCK_HEK_PROD_3];
const uint32_t rd_lock_csr_addr =
SOC_OTP_CTRL_CPTRA_SS_LOCK_HEK_PROD_3_READ_LOCK;

// Step 1: Write the partition.
for (uint32_t addr = part.address;
addr < part.digest_address;
addr += part.granularity / 8) {
dai_wr(addr, exp_data[0], exp_data[1], part.granularity, 0);
for (uint32_t addr = part->address;
addr < part->digest_address;
addr += part->granularity / 8) {
dai_wr(addr, exp_data[0], exp_data[1], part->granularity, 0);
}

// Step 2: Write the digest (doesn't have to be and isn't a real
// digest value).
dai_wr(part.digest_address, exp_data[0], exp_data[1], 64, 0);
dai_wr(part->digest_address, exp_data[0], exp_data[1], 64, 0);

// Step 3: Reset.
reset_fc_lcc_rtl();
Expand All @@ -159,12 +169,12 @@ void test_main (void) {
uint32_t csr = lsu_read_32(rd_lock_csr_addr);
if (csr != 1) {
VPRINTF(LOW, "TEST BUG: Partition is read-locked!\n");
goto epilogue;
return 2;
}
// Then read the value back and compare it.
if (part_read_compare(&part, exp_data, 0) != 0) {
if (part_read_compare(part, exp_data, 0) != 0) {
VPRINTF(LOW, "ERROR: Step 4 failed!\n");
goto epilogue;
return 1;
}

// Step 5: Activate the read lock CSR.
Expand All @@ -173,20 +183,56 @@ void test_main (void) {
// Step 6: Verify that reading the partition now gives an access
// error and returns zeros.
uint32_t exp_zeros[] = {0, 0};
if (part_read_compare(&part, exp_zeros, OTP_CTRL_STATUS_DAI_ERROR_MASK)
if (part_read_compare(part, exp_zeros, OTP_CTRL_STATUS_DAI_ERROR_MASK)
!= 0) {
VPRINTF(LOW, "ERROR: Step 6 failed!\n");
goto epilogue;
return 1;
}

// Step 7: Reset again.
reset_fc_lcc_rtl();
wait_dai_op_idle(0);

return 0;
}

int end_test(const partition_t* part) {
// Note that the data and digest of the zeroized partition cannot be
// read without a reset, as this would result in ECC errors. This is
// not a problem because in Step 8, SW checked the result of
// zeroization and ensured that all fuses are now set to `1` also
// for the data and digest part of the partition.

// Step 10: Reset.
reset_fc_lcc_rtl();
wait_dai_op_idle(0);

// Step 11: Read and check that everything in the partition is all
// ones.
if (check_part_zeroized(part, /*only_marker=*/0, 0) != 0) {
VPRINTF(LOW, "ERROR: Final step failed!\n");
return 1;
}

return 0;
}

int test_normal_zeroization (void) {
// Choose one of the zeroizable SW partitions with a CSR read lock.
const partition_t part = partitions[CPTRA_SS_LOCK_HEK_PROD_3];
const uint32_t rd_lock_csr_addr =
SOC_OTP_CTRL_CPTRA_SS_LOCK_HEK_PROD_3_READ_LOCK;

int retval = prepare_test(&part, rd_lock_csr_addr);
if (retval != 0) {
return retval;
}

// Step 8: Zeroize the partition.
if (part_zeroize(&part, 0) != 0) {
if (part_zeroize(&part, /*only_marker=*/0, /*only_until_half_data=*/0, 0)
!= 0) {
VPRINTF(LOW, "ERROR: Step 8 failed!\n");
goto epilogue;
return 1;
}

// Step 9: Read the zeroization marker through the DAI. In the
Expand All @@ -197,28 +243,131 @@ void test_main (void) {
// step emulates.
if (check_part_zeroized(&part, /*only_marker=*/1, 0) != 0) {
VPRINTF(LOW, "ERROR: Step 9 failed!\n");
goto epilogue;
return 1;
}

// Note that the data and digest of the zeroized partition cannot be
// read without a reset, as this would result in ECC errors. This is
// not a problem because in Step 8, SW checked the result of
// zeroization and ensured that all fuses are now set to `1` also
// for the data and digest part of the partition.
return end_test(&part);
}

// Step 10: Reset.
int test_half_zeroization (void) {
// Choose another zeroizable SW partition with a CSR read lock.
const partition_t part = partitions[CPTRA_SS_LOCK_HEK_PROD_4];
const uint32_t rd_lock_csr_addr =
SOC_OTP_CTRL_CPTRA_SS_LOCK_HEK_PROD_4_READ_LOCK;

int retval = prepare_test(&part, rd_lock_csr_addr);
if (retval != 0) {
return retval;
}

// Step 8: Zeroize the partition, but only until half the data.
if (part_zeroize(&part, /*only_marker=*/0, /*only_until_half_data=*/1, 0)
!= 0) {
VPRINTF(LOW, "ERROR: Step 8 failed!\n");
return 1;
}

// Step 9: Reset. This is to emulate that a reset happened while
// SW was zeroizing the partition.
reset_fc_lcc_rtl();
wait_dai_op_idle(0);

// Step 11: Read and check that everything in the partition is all
// ones.
if (check_part_zeroized(&part, /*only_marker=*/0, 0) != 0) {
// Step 10: Read the zeroization marker, which should indicate that
// zeroization has been started on the partition.
if (check_part_zeroized(&part, /*only_marker=*/1, 0) != 0) {
VPRINTF(LOW, "ERROR: Step 10 failed!\n");
return 1;
}

// Step 11: Zeroize the entire partition again, this time entirely.
if (part_zeroize(&part, /*only_marker=*/0, /*only_until_half_data=*/0, 0)
!= 0) {
VPRINTF(LOW, "ERROR: Step 11 failed!\n");
return 1;
}

return end_test(&part);
}

int test_marker_interrupted_zeroization (void) {
// Choose another zeroizable SW partition with a CSR read lock.
const partition_t part = partitions[CPTRA_SS_LOCK_HEK_PROD_5];
const uint32_t rd_lock_csr_addr =
SOC_OTP_CTRL_CPTRA_SS_LOCK_HEK_PROD_5_READ_LOCK;

int retval = prepare_test(&part, rd_lock_csr_addr);
if (retval != 0) {
return retval;
}

// Step 8: Arm the trigger that will reset the fuse controller
// during the next zeroization.
lsu_write_32(SOC_MCI_TOP_MCI_REG_DEBUG_OUT, CMD_FC_LCC_EN_RESET_WHILE_0ING);

// Step 9: Zeroize the marker, which will only partially succeed.
uint32_t zer_data[2];
dai_zer(part.zer_address, &zer_data[0], &zer_data[1], 64, 0);
// Due to the interruption of the zeroization, it does not complete the read-back.
// A separate read is necessary to get the partially zeroized marker.
dai_rd(part.zer_address, &zer_data[0], &zer_data[1], 64, 0);
VPRINTF(LOW, "DEBUG: Step 9 zeroization marker [0] = 0x%08x, [1] = 0x%08x\n",
zer_data[0], zer_data[1]);

// Step 10: Disable the reset trigger from Step 8.
lsu_write_32(SOC_MCI_TOP_MCI_REG_DEBUG_OUT, CMD_FC_LCC_DIS_RESET_WHILE_0ING);

//Step 11: Just zeroize everything again.
if (part_zeroize(&part, /*only_marker=*/0, /*only_until_half_data=*/0, 0)
!= 0) {
VPRINTF(LOW, "ERROR: Step 11 failed!\n");
goto epilogue;
return 1;
}

return end_test(&part);
}

int test_data_interrupted_zeroization (void) {
// Choose another zeroizable SW partition with a CSR read lock.
const partition_t part = partitions[CPTRA_SS_LOCK_HEK_PROD_6];
const uint32_t rd_lock_csr_addr =
SOC_OTP_CTRL_CPTRA_SS_LOCK_HEK_PROD_6_READ_LOCK;

int retval = prepare_test(&part, rd_lock_csr_addr);
if (retval != 0) {
return retval;
}

// Step 8: Zeroize the marker.
if (part_zeroize(&part, /*only_marker=*/1, /*only_until_half_data=*/0, 0)
!= 0) {
VPRINTF(LOW, "ERROR: Step 8 failed!\n");
return 1;
}

// Step 9: Arm the trigger that will reset the fuse controller
// during the next zeroization.
lsu_write_32(SOC_MCI_TOP_MCI_REG_DEBUG_OUT, CMD_FC_LCC_EN_RESET_WHILE_0ING);

// Step 10: Zeroize the first data fuses, which will only partially
// succeed.
uint32_t zer_data[2];
dai_zer(part.address, &zer_data[0], &zer_data[1], part.granularity, 0);
// Due to the interruption of the zeroization, it does not complete the read-back.
// A separate read is necessary to get the partially zeroized data.
dai_rd(part.address, &zer_data[0], &zer_data[1], part.granularity, 0);
VPRINTF(LOW, "DEBUG: Step 10 fuses [0] = 0x%08x\n", zer_data[0]);

// Step 11: Disable the reset trigger from Step 9.
lsu_write_32(SOC_MCI_TOP_MCI_REG_DEBUG_OUT, CMD_FC_LCC_DIS_RESET_WHILE_0ING);

// Step 12: Just zeroize everything again.
if (part_zeroize(&part, /*only_marker=*/0, /*only_until_half_data=*/0, 0)
!= 0) {
VPRINTF(LOW, "ERROR: Step 12 failed!\n");
return 1;
}

epilogue:
VPRINTF(LOW, "caliptra_ss_fuse_ctrl_zeroization_reset test finished\n");
return end_test(&part);
}

void main (void) {
Expand All @@ -230,7 +379,39 @@ void main (void) {
lcc_initialization();
grant_mcu_for_fc_writes();

test_main();
VPRINTF(LOW, "INFO: caliptra_ss_fuse_ctrl_zeroization_reset code.\n");

VPRINTF(LOW, "INFO: Starting normal zeroization test.\n");
int result = test_normal_zeroization();
if (result == 0) {
VPRINTF(LOW, "INFO: Test PASSED\n");
} else {
VPRINTF(LOW, "ERROR: Test FAILED\n")
}

VPRINTF(LOW, "INFO: Starting half-partition zeroization test.\n");
result = test_half_zeroization();
if (result == 0) {
VPRINTF(LOW, "INFO: Test PASSED\n");
} else {
VPRINTF(LOW, "ERROR: Test FAILED\n")
}

VPRINTF(LOW, "INFO: Starting test with interrupted marker zeroization.\n");
result = test_marker_interrupted_zeroization();
if (result == 0) {
VPRINTF(LOW, "INFO: Test PASSED\n");
} else {
VPRINTF(LOW, "ERROR: Test FAILED\n")
}

VPRINTF(LOW, "INFO: Starting test with interrupted data zeroization.\n");
int result = test_data_interrupted_zeroization();
if (result == 0) {
VPRINTF(LOW, "INFO: Test PASSED\n");
} else {
VPRINTF(LOW, "ERROR: Test FAILED\n")
}

for (uint8_t ii = 0; ii < 160; ii++) {
__asm__ volatile ("nop"); // Sleep loop as "nop"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@
#define CMD_DISABLE_CLK_BYP_ACK FC_LCC_CMD_OFFSET + 0x1A
#define CMD_LC_TRIGGER_ESCALATION0_DIS FC_LCC_CMD_OFFSET + 0x1B
#define CMD_LC_TRIGGER_ESCALATION1_DIS FC_LCC_CMD_OFFSET + 0x1C
#define CMD_FC_LCC_EN_RESET_WHILE_0ING FC_LCC_CMD_OFFSET + 0x1D
#define CMD_FC_LCC_DIS_RESET_WHILE_0ING FC_LCC_CMD_OFFSET + 0x1E


#define TB_CMD_DISABLE_MCU_SRAM_PROT_ASSERTS 0xC0
Expand Down
2 changes: 2 additions & 0 deletions src/integration/testbench/caliptra_ss_tb_cmd_list.svh
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,8 @@ localparam CMD_LC_FAULT_CNTR = FC_LCC_CMD_OFFSET + 8'h19;
localparam CMD_DISABLE_CLK_BYP_ACK = FC_LCC_CMD_OFFSET + 8'h1A;
localparam CMD_LC_TRIGGER_ESCALATION0_DIS = FC_LCC_CMD_OFFSET + 8'h1B;
localparam CMD_LC_TRIGGER_ESCALATION1_DIS = FC_LCC_CMD_OFFSET + 8'h1C;
localparam CMD_FC_LCC_EN_RESET_WHILE_0ING = FC_LCC_CMD_OFFSET + 8'h1D;
localparam CMD_FC_LCC_DIS_RESET_WHILE_0ING = FC_LCC_CMD_OFFSET + 8'h1E;


localparam TB_DISABLE_MCU_SRAM_PROT_ASSERTS = 8'hc0;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,11 @@
`ifndef FC_PATH
`define FC_PATH `CPTRA_SS_TOP_PATH.u_otp_ctrl
`endif
`ifndef FC_OTP_PRIM
`define FC_OTP_PRIM `CPTRA_SS_TB_TOP_NAME.u_otp
`endif
`ifndef FC_MEM
`define FC_MEM `CPTRA_SS_TB_TOP_NAME.u_otp.u_prim_ram_1p_adv.u_mem.mem
`define FC_MEM `FC_OTP_PRIM.u_prim_ram_1p_adv.u_mem.mem
`endif
`ifndef CPTRA_CORE_TOP_PATH
`define CPTRA_CORE_TOP_PATH `CPTRA_SS_TOP_PATH.caliptra_top_dut
Expand Down
Loading