blob: 00c098b0a9cf241de1a85a12031880f03e5a0f1b [file] [log] [blame]
// 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;
}