Skip to content

Commit

Permalink
[otp_ctrl] Make DAI registers software-lockable
Browse files Browse the repository at this point in the history
So far only the hardware was able to modulate the
DAI regwen. This updates the implementation so that
the DAI can be permanently locked out via SW as well.

Fixes #20348

Signed-off-by: Michael Schaffner <[email protected]>
  • Loading branch information
msfschaffner committed Jan 31, 2024
1 parent 0c864af commit 1c9adba
Show file tree
Hide file tree
Showing 14 changed files with 313 additions and 53 deletions.
27 changes: 27 additions & 0 deletions hw/ip/otp_ctrl/data/dif_otp_ctrl.c.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,33 @@ dif_result_t dif_otp_ctrl_check_consistency(const dif_otp_ctrl_t *otp) {
return kDifOk;
}

dif_result_t dif_otp_ctrl_lock_dai(const dif_otp_ctrl_t *otp) {
if (otp == NULL) {
return kDifBadArg;
}

uint32_t reg = bitfield_bit32_write(
0, OTP_CTRL_DIRECT_ACCESS_REGWEN_DIRECT_ACCESS_REGWEN_BIT, false);
mmio_region_write32(otp->base_addr, OTP_CTRL_DIRECT_ACCESS_REGWEN_REG_OFFSET,
reg);

return kDifOk;
}

dif_result_t dif_otp_ctrl_dai_is_locked(const dif_otp_ctrl_t *otp,
bool *is_locked) {
if (otp == NULL || is_locked == NULL) {
return kDifBadArg;
}

uint32_t reg = mmio_region_read32(otp->base_addr,
OTP_CTRL_DIRECT_ACCESS_REGWEN_REG_OFFSET);
*is_locked = !bitfield_bit32_read(
reg, OTP_CTRL_DIRECT_ACCESS_REGWEN_DIRECT_ACCESS_REGWEN_BIT);

return kDifOk;
}

dif_result_t dif_otp_ctrl_lock_config(const dif_otp_ctrl_t *otp) {
if (otp == NULL) {
return kDifBadArg;
Expand Down
40 changes: 34 additions & 6 deletions hw/ip/otp_ctrl/data/dif_otp_ctrl.h.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -288,11 +288,39 @@ dif_result_t dif_otp_ctrl_check_integrity(const dif_otp_ctrl_t *otp);
OT_WARN_UNUSED_RESULT
dif_result_t dif_otp_ctrl_check_consistency(const dif_otp_ctrl_t *otp);
/**
* Locks out access to the direct access interface registers.
*
* This function is idempotent: calling it while functionality is locked will
* have no effect and return `kDifOk`.
*
* @param otp An OTP handle.
* @return The result of the operation.
*/
OT_WARN_UNUSED_RESULT
dif_result_t dif_otp_ctrl_lock_dai(const dif_otp_ctrl_t *otp);
/**
* Checks whether access to the direct access interface is locked.
*
* Note that besides locking the DAI out until the next reset using the
* dif_otp_ctrl_lock_dai function, the DAI is also temporarily locked by the
* HW itself when it is busy processing a DAI command. In such a case, the
* kDifOtpCtrlStatusCodeDaiIdle status bit will be set to 0 as well.
*
* @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_dai_is_locked(const dif_otp_ctrl_t *otp,
bool *is_locked);
/**
* Locks out `dif_otp_ctrl_configure()` function.
*
* This function is reentrant: calling it while functionality is locked will
* have no effect and return `kDifOtpCtrlOk`.
* This function is idempotent: calling it while functionality is locked will
* have no effect and return `kDifOk`.
*
* @param otp An OTP handle.
* @return The result of the operation.
Expand All @@ -314,8 +342,8 @@ dif_result_t dif_otp_ctrl_config_is_locked(const dif_otp_ctrl_t *otp,
/**
* Locks out `dif_otp_ctrl_check_*()` functions.
*
* This function is reentrant: calling it while functionality is locked will
* have no effect and return `kDifOtpCtrlOk`.
* This function is idempotent: calling it while functionality is locked will
* have no effect and return `kDifOk`.
*
* @param otp An OTP handle.
* @return The result of the operation.
Expand Down Expand Up @@ -344,8 +372,8 @@ dif_result_t dif_otp_ctrl_check_trigger_is_locked(const dif_otp_ctrl_t *otp,
* `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`.
* This function is idempotent: calling it while functionality is locked will
* have no effect and return `kDifOk`.
*
* @param otp An OTP handle.
* @param partition The SW partition to lock.
Expand Down
33 changes: 33 additions & 0 deletions hw/ip/otp_ctrl/data/dif_otp_ctrl_unittest.cc.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,39 @@ class OtpTest : public testing::Test, public MmioTest {
dif_otp_ctrl_t otp_ = {.base_addr = dev().region()};
};

class DaiRegwenTest : public OtpTest {};

TEST_F(DaiRegwenTest, LockDai) {
EXPECT_WRITE32(
OTP_CTRL_DIRECT_ACCESS_REGWEN_REG_OFFSET,
{{OTP_CTRL_DIRECT_ACCESS_REGWEN_DIRECT_ACCESS_REGWEN_BIT, false}});
EXPECT_DIF_OK(dif_otp_ctrl_lock_dai(&otp_));
}

TEST_F(DaiRegwenTest, IsDaiLocked) {
bool flag;
EXPECT_READ32(
OTP_CTRL_DIRECT_ACCESS_REGWEN_REG_OFFSET,
{{OTP_CTRL_DIRECT_ACCESS_REGWEN_DIRECT_ACCESS_REGWEN_BIT, true}});
EXPECT_DIF_OK(dif_otp_ctrl_dai_is_locked(&otp_, &flag));
EXPECT_FALSE(flag);

EXPECT_READ32(
OTP_CTRL_DIRECT_ACCESS_REGWEN_REG_OFFSET,
{{OTP_CTRL_DIRECT_ACCESS_REGWEN_DIRECT_ACCESS_REGWEN_BIT, false}});
EXPECT_DIF_OK(dif_otp_ctrl_dai_is_locked(&otp_, &flag));
EXPECT_TRUE(flag);
}

TEST_F(DaiRegwenTest, NullArgs) {
EXPECT_DIF_BADARG(dif_otp_ctrl_lock_dai(nullptr));
bool flag;
EXPECT_DIF_BADARG(dif_otp_ctrl_dai_is_locked(nullptr, &flag));
EXPECT_DIF_BADARG(dif_otp_ctrl_dai_is_locked(&otp_, nullptr));
}

class ConfigTest : public OtpTest {};

TEST_F(ConfigTest, Basic) {
Expand Down
17 changes: 11 additions & 6 deletions hw/ip/otp_ctrl/data/otp_ctrl.hjson
Original file line number Diff line number Diff line change
Expand Up @@ -1870,19 +1870,24 @@
desc: '''
Register write enable for all direct access interface registers.
''',
swaccess: "ro",
hwaccess: "hwo",
swaccess: "rw0c",
hwaccess: "hrw",
hwext: "true",
hwqe: "true",
tags: [ // OTP internal HW will set this enable register to 0 when OTP is not under IDLE
// state, so could not auto-predict its value
"excl:CsrNonInitTests:CsrExclCheck"],
fields: [
{
bits: "0",
desc: ''' This bit is hardware-managed and only readable by software.
The DAI sets this bit temporarily to 0 during an OTP operation such that
the corresponding address and data registers cannot be modified while
the operation is pending.
desc: '''
This bit controls whether the DAI registers can be written.
Write 0 to it in order to clear the bit.

Note that the hardware also modulates this bit and sets it to 0 temporarily
during an OTP operation such that the corresponding address and data registers
cannot be modified while an operation is pending. The !!DAI_IDLE status bit
will also be set to 0 in such a case.
'''
resval: 1,
},
Expand Down
17 changes: 11 additions & 6 deletions hw/ip/otp_ctrl/data/otp_ctrl.hjson.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -873,19 +873,24 @@ otp_size_as_uint32 = otp_size_as_bytes // 4
desc: '''
Register write enable for all direct access interface registers.
''',
swaccess: "ro",
hwaccess: "hwo",
swaccess: "rw0c",
hwaccess: "hrw",
hwext: "true",
hwqe: "true",
tags: [ // OTP internal HW will set this enable register to 0 when OTP is not under IDLE
// state, so could not auto-predict its value
"excl:CsrNonInitTests:CsrExclCheck"],
fields: [
{
bits: "0",
desc: ''' This bit is hardware-managed and only readable by software.
The DAI sets this bit temporarily to 0 during an OTP operation such that
the corresponding address and data registers cannot be modified while
the operation is pending.
desc: '''
This bit controls whether the DAI registers can be written.
Write 0 to it in order to clear the bit.
Note that the hardware also modulates this bit and sets it to 0 temporarily
during an OTP operation such that the corresponding address and data registers
cannot be modified while an operation is pending. The !!DAI_IDLE status bit
will also be set to 0 in such a case.
'''
resval: 1,
},
Expand Down
33 changes: 27 additions & 6 deletions hw/ip/otp_ctrl/data/otp_ctrl_scoreboard.sv.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,10 @@ class otp_ctrl_scoreboard #(type CFG_T = otp_ctrl_env_cfg)
// This bit is used for DAI interface to mark if the read access is valid.
bit dai_read_valid;

// This captures the regwen state as configured by the SW side (i.e. without HW modulation
// with the idle signal overlaid).
bit direct_access_regwen_state = 1;

// ICEBOX(#17798): currently scb will skip checking the readout value if the ECC error is
// uncorrectable. Because if the error is uncorrectable, current scb does not track all the
// backdoor injected values.
Expand Down Expand Up @@ -151,7 +155,7 @@ class otp_ctrl_scoreboard #(type CFG_T = otp_ctrl_env_cfg)

if (cfg.otp_ctrl_vif.under_error_states() == 0) begin
// Dai access is unlocked because the power init is done
void'(ral.direct_access_regwen.predict(1));
void'(ral.direct_access_regwen.predict(direct_access_regwen_state));

// Dai idle is set because the otp init is done
exp_status[OtpDaiIdleIdx] = 1;
Expand Down Expand Up @@ -557,6 +561,10 @@ class otp_ctrl_scoreboard #(type CFG_T = otp_ctrl_env_cfg)
bit do_read_check = 1;
uvm_reg csr;
dv_base_reg dv_reg;
string csr_name;

`uvm_info(`gfn, $sformatf("sw state %d, reg state %d", direct_access_regwen_state,
`gmv(ral.direct_access_regwen)), UVM_LOW);

// if access was to a valid csr, get the csr handle
if (csr_addr inside {cfg.ral_models[ral_name].csr_addrs}) begin
Expand Down Expand Up @@ -624,22 +632,26 @@ class otp_ctrl_scoreboard #(type CFG_T = otp_ctrl_env_cfg)
`uvm_fatal(`gfn, $sformatf("Access unexpected addr 0x%0h", csr_addr))
end

csr_name = csr.get_name();

if (addr_phase_write) begin
if (cfg.en_cov && cfg.otp_ctrl_vif.alert_reqs && csr.get_name == "direct_access_cmd") begin
if (cfg.en_cov && cfg.otp_ctrl_vif.alert_reqs && csr_name == "direct_access_cmd") begin
cov.req_dai_access_after_alert_cg.sample(item.a_data);
end

// Skip predict if the register is locked by `direct_access_regwen`.
// An exception is the direct_access_regwen which may always be written.
if (ral.direct_access_regwen.locks_reg_or_fld(dv_reg) &&
`gmv(ral.direct_access_regwen) == 0) return;
`gmv(ral.direct_access_regwen) == 0 &&
csr_name != "direct_access_regwen") return;

void'(csr.predict(.value(item.a_data), .kind(UVM_PREDICT_WRITE), .be(item.a_mask)));
end

// process the csr req
// for write, update local variable and fifo at address phase
// for read, update predication at address phase and compare at data phase
case (csr.get_name())
case (csr_name)
// add individual case item for each csr
"intr_state": begin
if (data_phase_read) begin
Expand Down Expand Up @@ -904,7 +916,7 @@ class otp_ctrl_scoreboard #(type CFG_T = otp_ctrl_env_cfg)
if (under_dai_access && !cfg.otp_ctrl_vif.under_error_states()) begin
if (item.d_data[OtpDaiIdleIdx]) begin
under_dai_access = 0;
void'(ral.direct_access_regwen.predict(1));
void'(ral.direct_access_regwen.predict(direct_access_regwen_state));
void'(ral.intr_state.otp_operation_done.predict(1));
end
end
Expand Down Expand Up @@ -940,6 +952,15 @@ class otp_ctrl_scoreboard #(type CFG_T = otp_ctrl_env_cfg)
end
end
end
"direct_access_regwen": begin
if (addr_phase_write) begin
// This locks the DAI until the next reset.
if (!item.a_data[0]) begin
direct_access_regwen_state = 0;
void'(ral.direct_access_regwen.predict(0));
end
end
end
// For error codes, if lc_prog in progress, err_code might update anytime in DUT. Ignore
// checking until req is acknowledged.

Expand Down Expand Up @@ -967,7 +988,6 @@ class otp_ctrl_scoreboard #(type CFG_T = otp_ctrl_env_cfg)
<% part_name_snake = Name.from_snake_case(part["name"]).as_snake_case() %>\
"${part_name_snake}_read_lock",
% endfor
"direct_access_regwen",
"direct_access_wdata_0",
"direct_access_wdata_1",
"direct_access_address",
Expand Down Expand Up @@ -1071,6 +1091,7 @@ class otp_ctrl_scoreboard #(type CFG_T = otp_ctrl_env_cfg)
sram_fifos[i].flush();
end

direct_access_regwen_state = 1;
under_chk = 0;
under_dai_access = 0;
ignore_digest_chk = 0;
Expand Down
19 changes: 14 additions & 5 deletions hw/ip/otp_ctrl/doc/registers.md
Original file line number Diff line number Diff line change
Expand Up @@ -220,13 +220,22 @@ Register write enable for all direct access interface registers.
### Fields

```wavejson
{"reg": [{"name": "DIRECT_ACCESS_REGWEN", "bits": 1, "attr": ["ro"], "rotate": -90}, {"bits": 31}], "config": {"lanes": 1, "fontsize": 10, "vspace": 220}}
{"reg": [{"name": "DIRECT_ACCESS_REGWEN", "bits": 1, "attr": ["rw0c"], "rotate": -90}, {"bits": 31}], "config": {"lanes": 1, "fontsize": 10, "vspace": 220}}
```

| Bits | Type | Reset | Name | Description |
|:------:|:------:|:-------:|:---------------------|:-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| 31:1 | | | | Reserved |
| 0 | ro | 0x1 | DIRECT_ACCESS_REGWEN | This bit is hardware-managed and only readable by software. The DAI sets this bit temporarily to 0 during an OTP operation such that the corresponding address and data registers cannot be modified while the operation is pending. |
| Bits | Type | Reset | Name |
|:------:|:------:|:-------:|:--------------------------------------------------------------------|
| 31:1 | | | Reserved |
| 0 | rw0c | 0x1 | [DIRECT_ACCESS_REGWEN](#direct_access_regwen--direct_access_regwen) |

### DIRECT_ACCESS_REGWEN . DIRECT_ACCESS_REGWEN
This bit controls whether the DAI registers can be written.
Write 0 to it in order to clear the bit.

Note that the hardware also modulates this bit and sets it to 0 temporarily
during an OTP operation such that the corresponding address and data registers
cannot be modified while an operation is pending. The [`DAI_IDLE`](#dai_idle) status bit
will also be set to 0 in such a case.

## DIRECT_ACCESS_CMD
Command register for direct accesses.
Expand Down
Loading

0 comments on commit 1c9adba

Please sign in to comment.