| // Copyright lowRISC contributors. |
| // Licensed under the Apache License, Version 2.0, see LICENSE for details. |
| // SPDX-License-Identifier: Apache-2.0 |
| |
| #include "sw/device/lib/dif/dif_edn.h" |
| |
| #include "sw/device/lib/base/bitfield.h" |
| #include "sw/device/lib/base/multibits.h" |
| #include "sw/device/lib/dif/dif_csrng_shared.h" |
| |
| #include "edn_regs.h" // Generated |
| |
| static dif_result_t check_locked(const dif_edn_t *edn) { |
| if (mmio_region_read32(edn->base_addr, EDN_REGWEN_REG_OFFSET) == 0) { |
| return kDifLocked; |
| } |
| return kDifOk; |
| } |
| |
| dif_result_t dif_edn_configure(const dif_edn_t *edn) { |
| if (edn == NULL) { |
| return kDifBadArg; |
| } |
| DIF_RETURN_IF_ERROR(check_locked(edn)); |
| |
| uint32_t reg = mmio_region_read32(edn->base_addr, EDN_CTRL_REG_OFFSET); |
| reg = bitfield_field32_write(reg, EDN_CTRL_EDN_ENABLE_FIELD, |
| kMultiBitBool4True); |
| mmio_region_write32(edn->base_addr, EDN_CTRL_REG_OFFSET, reg); |
| |
| return kDifOk; |
| } |
| |
| dif_result_t dif_edn_lock(const dif_edn_t *edn) { |
| if (edn == NULL) { |
| return kDifBadArg; |
| } |
| mmio_region_write32(edn->base_addr, EDN_REGWEN_REG_OFFSET, 0); |
| return kDifOk; |
| } |
| |
| dif_result_t dif_edn_is_locked(const dif_edn_t *edn, bool *is_locked) { |
| if (edn == NULL || is_locked == NULL) { |
| return kDifBadArg; |
| } |
| *is_locked = check_locked(edn) != kDifOk; |
| return kDifOk; |
| } |
| |
| dif_result_t dif_edn_set_boot_mode(const dif_edn_t *edn) { |
| if (edn == NULL) { |
| return kDifBadArg; |
| } |
| DIF_RETURN_IF_ERROR(check_locked(edn)); |
| |
| uint32_t reg = mmio_region_read32(edn->base_addr, EDN_CTRL_REG_OFFSET); |
| reg = bitfield_field32_write(reg, EDN_CTRL_BOOT_REQ_MODE_FIELD, |
| kMultiBitBool4True); |
| mmio_region_write32(edn->base_addr, EDN_CTRL_REG_OFFSET, reg); |
| |
| return kDifOk; |
| } |
| |
| dif_result_t dif_edn_set_auto_mode(const dif_edn_t *edn, |
| dif_edn_auto_params_t config) { |
| if (edn == NULL) { |
| return kDifBadArg; |
| } |
| DIF_RETURN_IF_ERROR(check_locked(edn)); |
| |
| // Check that EDN is disabled. If it is not disabled (e.g., through a call |
| // to `dif_edn_stop()`), we are not ready to change mode. |
| uint32_t ctrl_reg = mmio_region_read32(edn->base_addr, EDN_CTRL_REG_OFFSET); |
| const uint32_t edn_en = |
| bitfield_field32_read(ctrl_reg, EDN_CTRL_EDN_ENABLE_FIELD); |
| if (dif_multi_bit_bool_to_toggle(edn_en) != kDifToggleDisabled) { |
| return kDifError; |
| } |
| |
| // Ensure neither automatic nor boot request mode is set. |
| ctrl_reg = bitfield_field32_write(ctrl_reg, EDN_CTRL_AUTO_REQ_MODE_FIELD, |
| kMultiBitBool4False); |
| ctrl_reg = bitfield_field32_write(ctrl_reg, EDN_CTRL_BOOT_REQ_MODE_FIELD, |
| kMultiBitBool4False); |
| mmio_region_write32(edn->base_addr, EDN_CTRL_REG_OFFSET, ctrl_reg); |
| |
| // Clear the reseed command FIFO and the generate command FIFO. |
| ctrl_reg = bitfield_field32_write(ctrl_reg, EDN_CTRL_CMD_FIFO_RST_FIELD, |
| kMultiBitBool4True); |
| mmio_region_write32(edn->base_addr, EDN_CTRL_REG_OFFSET, ctrl_reg); |
| |
| // Restore command FIFOs to normal operation mode. This is a prerequisite |
| // before any further commands can be issued to these FIFOs. |
| ctrl_reg = bitfield_field32_write(ctrl_reg, EDN_CTRL_CMD_FIFO_RST_FIELD, |
| kMultiBitBool4False); |
| mmio_region_write32(edn->base_addr, EDN_CTRL_REG_OFFSET, ctrl_reg); |
| |
| // Fill the reseed command FIFO. |
| mmio_region_write32(edn->base_addr, EDN_RESEED_CMD_REG_OFFSET, |
| config.reseed_cmd.cmd); |
| for (size_t i = 0; i < config.reseed_cmd.seed_material.len; ++i) { |
| mmio_region_write32(edn->base_addr, EDN_RESEED_CMD_REG_OFFSET, |
| config.reseed_cmd.seed_material.data[i]); |
| } |
| |
| // Fill the generate command FIFO. |
| mmio_region_write32(edn->base_addr, EDN_GENERATE_CMD_REG_OFFSET, |
| config.generate_cmd.cmd); |
| for (size_t i = 0; i < config.generate_cmd.seed_material.len; ++i) { |
| mmio_region_write32(edn->base_addr, EDN_GENERATE_CMD_REG_OFFSET, |
| config.generate_cmd.seed_material.data[i]); |
| } |
| |
| // Set the maximum number of requests between reseeds. |
| mmio_region_write32(edn->base_addr, |
| EDN_MAX_NUM_REQS_BETWEEN_RESEEDS_REG_OFFSET, |
| config.reseed_interval); |
| |
| // Re-enable EDN in automatic request mode. |
| ctrl_reg = bitfield_field32_write(ctrl_reg, EDN_CTRL_EDN_ENABLE_FIELD, |
| kMultiBitBool4True); |
| ctrl_reg = bitfield_field32_write(ctrl_reg, EDN_CTRL_AUTO_REQ_MODE_FIELD, |
| kMultiBitBool4True); |
| mmio_region_write32(edn->base_addr, EDN_CTRL_REG_OFFSET, ctrl_reg); |
| |
| // Wait until EDN is ready to accept commands. |
| bool ready = false; |
| while (!ready) { |
| DIF_RETURN_IF_ERROR(dif_edn_get_status(edn, kDifEdnStatusReady, &ready)); |
| } |
| |
| // Command CSRNG Instantiate. As soon as CSRNG acknowledges this command, |
| // EDN will start automatically sending reseed and generate commands. |
| const dif_edn_entropy_src_toggle_t entropy_src_enable = |
| ((config.instantiate_cmd.cmd & 0xF00) >> 8) == kMultiBitBool4True |
| ? kDifEdnEntropySrcToggleDisable |
| : kDifEdnEntropySrcToggleEnable; |
| DIF_RETURN_IF_ERROR(dif_edn_instantiate( |
| edn, entropy_src_enable, &config.instantiate_cmd.seed_material)); |
| |
| // Wait until CSRNG acknowledges command. |
| ready = false; |
| while (!ready) { |
| DIF_RETURN_IF_ERROR(dif_edn_get_status(edn, kDifEdnStatusReady, &ready)); |
| } |
| |
| // Read request acknowledge error and return accordingly. |
| bool ack_err; |
| DIF_RETURN_IF_ERROR(dif_edn_get_status(edn, kDifEdnStatusCsrngAck, &ack_err)); |
| return ack_err ? kDifError : kDifOk; |
| } |
| |
| dif_result_t dif_edn_get_status(const dif_edn_t *edn, dif_edn_status_t flag, |
| bool *set) { |
| if (edn == NULL || set == NULL) { |
| return kDifBadArg; |
| } |
| |
| uint32_t bit; |
| switch (flag) { |
| case kDifEdnStatusReady: |
| bit = EDN_SW_CMD_STS_CMD_RDY_BIT; |
| break; |
| case kDifEdnStatusCsrngAck: |
| bit = EDN_SW_CMD_STS_CMD_STS_BIT; |
| break; |
| default: |
| return kDifBadArg; |
| } |
| |
| uint32_t reg = mmio_region_read32(edn->base_addr, EDN_SW_CMD_STS_REG_OFFSET); |
| *set = bitfield_bit32_read(reg, bit); |
| return kDifOk; |
| } |
| |
| dif_result_t dif_edn_get_errors(const dif_edn_t *edn, uint32_t *unhealthy_fifos, |
| uint32_t *errors) { |
| if (edn == NULL || unhealthy_fifos == NULL || errors == NULL) { |
| return kDifBadArg; |
| } |
| *unhealthy_fifos = 0; |
| *errors = 0; |
| |
| uint32_t reg = mmio_region_read32(edn->base_addr, EDN_ERR_CODE_REG_OFFSET); |
| *unhealthy_fifos = |
| bitfield_bit32_copy(*unhealthy_fifos, kDifEdnFifoReseedCmd, reg, |
| EDN_ERR_CODE_SFIFO_RESCMD_ERR_BIT); |
| *unhealthy_fifos = |
| bitfield_bit32_copy(*unhealthy_fifos, kDifEdnFifoGenerateCmd, reg, |
| EDN_ERR_CODE_SFIFO_GENCMD_ERR_BIT); |
| |
| *errors = bitfield_bit32_copy(*errors, kDifEdnErrorAckSm, reg, |
| EDN_ERR_CODE_EDN_ACK_SM_ERR_BIT); |
| *errors = bitfield_bit32_copy(*errors, kDifEdnErrorMainSm, reg, |
| EDN_ERR_CODE_EDN_MAIN_SM_ERR_BIT); |
| *errors = bitfield_bit32_copy(*errors, kDifEdnErrorCounterFault, reg, |
| EDN_ERR_CODE_EDN_CNTR_ERR_BIT); |
| *errors = bitfield_bit32_copy(*errors, kDifEdnErrorFifoWrite, reg, |
| EDN_ERR_CODE_FIFO_WRITE_ERR_BIT); |
| *errors = bitfield_bit32_copy(*errors, kDifEdnErrorFifoRead, reg, |
| EDN_ERR_CODE_FIFO_READ_ERR_BIT); |
| *errors = bitfield_bit32_copy(*errors, kDifEdnErrorFifoFullAndEmpty, reg, |
| EDN_ERR_CODE_FIFO_STATE_ERR_BIT); |
| |
| return kDifOk; |
| } |
| |
| dif_result_t dif_edn_get_cmd_unhealthy_fifo_force(const dif_edn_t *edn, |
| dif_edn_fifo_t fifo) { |
| if (edn == NULL) { |
| return kDifBadArg; |
| } |
| |
| uint32_t fifo_bit; |
| switch (fifo) { |
| case kDifEdnFifoReseedCmd: |
| fifo_bit = EDN_ERR_CODE_SFIFO_RESCMD_ERR_BIT; |
| break; |
| case kDifEdnFifoGenerateCmd: |
| fifo_bit = EDN_ERR_CODE_SFIFO_GENCMD_ERR_BIT; |
| break; |
| default: |
| return kDifBadArg; |
| } |
| |
| DIF_RETURN_IF_ERROR(check_locked(edn)); |
| mmio_region_write32(edn->base_addr, EDN_ERR_CODE_TEST_REG_OFFSET, fifo_bit); |
| return kDifOk; |
| } |
| |
| dif_result_t dif_edn_get_cmd_error_force(const dif_edn_t *edn, |
| dif_edn_error_t error) { |
| if (edn == NULL) { |
| return kDifBadArg; |
| } |
| |
| uint32_t error_bit; |
| switch (error) { |
| case kDifEdnErrorAckSm: |
| error_bit = EDN_ERR_CODE_EDN_ACK_SM_ERR_BIT; |
| break; |
| case kDifEdnErrorMainSm: |
| error_bit = EDN_ERR_CODE_EDN_MAIN_SM_ERR_BIT; |
| break; |
| case kDifEdnErrorCounterFault: |
| error_bit = EDN_ERR_CODE_EDN_CNTR_ERR_BIT; |
| break; |
| case kDifEdnErrorFifoWrite: |
| error_bit = EDN_ERR_CODE_FIFO_WRITE_ERR_BIT; |
| break; |
| case kDifEdnErrorFifoRead: |
| error_bit = EDN_ERR_CODE_FIFO_READ_ERR_BIT; |
| break; |
| case kDifEdnErrorFifoFullAndEmpty: |
| error_bit = EDN_ERR_CODE_FIFO_STATE_ERR_BIT; |
| break; |
| default: |
| return kDifBadArg; |
| } |
| |
| DIF_RETURN_IF_ERROR(check_locked(edn)); |
| mmio_region_write32(edn->base_addr, EDN_ERR_CODE_TEST_REG_OFFSET, error_bit); |
| return kDifOk; |
| } |
| |
| dif_result_t dif_edn_get_main_state_machine(const dif_edn_t *edn, |
| uint32_t *state) { |
| if (edn == NULL || state == NULL) { |
| return kDifBadArg; |
| } |
| |
| *state = mmio_region_read32(edn->base_addr, EDN_MAIN_SM_STATE_REG_OFFSET); |
| return kDifOk; |
| } |
| |
| dif_result_t dif_edn_instantiate( |
| const dif_edn_t *edn, dif_edn_entropy_src_toggle_t entropy_src_enable, |
| const dif_edn_seed_material_t *seed_material) { |
| if (edn == NULL) { |
| return kDifBadArg; |
| } |
| return csrng_send_app_cmd( |
| edn->base_addr, EDN_SW_CMD_REQ_REG_OFFSET, |
| (csrng_app_cmd_t){ |
| .id = kCsrngAppCmdInstantiate, |
| .entropy_src_enable = |
| (dif_csrng_entropy_src_toggle_t)entropy_src_enable, |
| .seed_material = (const dif_csrng_seed_material_t *)seed_material, |
| }); |
| } |
| |
| dif_result_t dif_edn_reseed(const dif_edn_t *edn, |
| const dif_edn_seed_material_t *seed_material) { |
| if (edn == NULL || seed_material == NULL) { |
| return kDifBadArg; |
| } |
| dif_csrng_seed_material_t seed_material2; |
| memcpy(&seed_material2, seed_material, sizeof(seed_material2)); |
| return csrng_send_app_cmd(edn->base_addr, EDN_SW_CMD_REQ_REG_OFFSET, |
| (csrng_app_cmd_t){ |
| .id = kCsrngAppCmdReseed, |
| .seed_material = &seed_material2, |
| }); |
| } |
| |
| dif_result_t dif_edn_update(const dif_edn_t *edn, |
| const dif_edn_seed_material_t *seed_material) { |
| if (edn == NULL || seed_material == NULL) { |
| return kDifBadArg; |
| } |
| dif_csrng_seed_material_t seed_material2; |
| memcpy(&seed_material2, seed_material, sizeof(seed_material2)); |
| return csrng_send_app_cmd(edn->base_addr, EDN_SW_CMD_REQ_REG_OFFSET, |
| (csrng_app_cmd_t){ |
| .id = kCsrngAppCmdUpdate, |
| .seed_material = &seed_material2, |
| }); |
| } |
| |
| dif_result_t dif_edn_generate_start(const dif_edn_t *edn, size_t len) { |
| if (edn == NULL || len == 0) { |
| return kDifBadArg; |
| } |
| |
| // Round up the number of 128bit blocks. Aligning with respect to uint32_t. |
| // TODO(#6112): Consider using a canonical reference for alignment operations. |
| const uint32_t num_128bit_blocks = (len + 3) / 4; |
| return csrng_send_app_cmd(edn->base_addr, EDN_SW_CMD_REQ_REG_OFFSET, |
| (csrng_app_cmd_t){ |
| .id = kCsrngAppCmdGenerate, |
| .generate_len = num_128bit_blocks, |
| }); |
| } |
| |
| dif_result_t dif_edn_uninstantiate(const dif_edn_t *edn) { |
| if (edn == NULL) { |
| return kDifBadArg; |
| } |
| return csrng_send_app_cmd(edn->base_addr, EDN_SW_CMD_REQ_REG_OFFSET, |
| (csrng_app_cmd_t){ |
| .id = kCsrngAppCmdUnisntantiate, |
| }); |
| } |
| |
| dif_result_t dif_edn_stop(const dif_edn_t *edn) { |
| if (edn == NULL) { |
| return kDifBadArg; |
| } |
| DIF_RETURN_IF_ERROR(check_locked(edn)); |
| |
| // Fifo clear is only honored if edn is enabled. |
| uint32_t reg = mmio_region_read32(edn->base_addr, EDN_CTRL_REG_OFFSET); |
| reg = bitfield_field32_write(reg, EDN_CTRL_CMD_FIFO_RST_FIELD, |
| kMultiBitBool4True); |
| mmio_region_write32(edn->base_addr, EDN_CTRL_REG_OFFSET, reg); |
| |
| // Disable edn and restore Fifo clear at the same time so that no rogue |
| // command can get in after the clear above. |
| mmio_region_write32(edn->base_addr, EDN_CTRL_REG_OFFSET, EDN_CTRL_REG_RESVAL); |
| |
| return kDifOk; |
| } |
| |
| dif_result_t dif_edn_get_recoverable_alerts(const dif_edn_t *edn, |
| uint32_t *alerts) { |
| if (edn == NULL || alerts == NULL) { |
| return kDifBadArg; |
| } |
| |
| *alerts = 0; |
| uint32_t reg = |
| mmio_region_read32(edn->base_addr, EDN_RECOV_ALERT_STS_REG_OFFSET); |
| *alerts = bitfield_bit32_copy(*alerts, kDifEdnRecoverableAlertBadEnable, reg, |
| EDN_RECOV_ALERT_STS_EDN_ENABLE_FIELD_ALERT_BIT); |
| *alerts = |
| bitfield_bit32_copy(*alerts, kDifEdnRecoverableAlertBadBootReqMode, reg, |
| EDN_RECOV_ALERT_STS_BOOT_REQ_MODE_FIELD_ALERT_BIT); |
| *alerts = |
| bitfield_bit32_copy(*alerts, kDifEdnRecoverableAlertBadAutoReqMode, reg, |
| EDN_RECOV_ALERT_STS_AUTO_REQ_MODE_FIELD_ALERT_BIT); |
| *alerts = |
| bitfield_bit32_copy(*alerts, kDifEdnRecoverableAlertBadFifoClear, reg, |
| EDN_RECOV_ALERT_STS_CMD_FIFO_RST_FIELD_ALERT_BIT); |
| *alerts = bitfield_bit32_copy(*alerts, kDifEdnRecoverableAlertRepeatedGenBits, |
| reg, EDN_RECOV_ALERT_STS_EDN_BUS_CMP_ALERT_BIT); |
| return kDifOk; |
| } |
| |
| dif_result_t dif_edn_clear_recoverable_alerts(const dif_edn_t *edn) { |
| if (edn == NULL) { |
| return kDifBadArg; |
| } |
| mmio_region_write32(edn->base_addr, EDN_RECOV_ALERT_STS_REG_OFFSET, 0); |
| return kDifOk; |
| } |