| // 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_rstmgr.h" |
| |
| #include <assert.h> |
| #include <stdint.h> |
| |
| #include "sw/device/lib/base/bitfield.h" |
| #include "sw/device/lib/base/macros.h" |
| #include "sw/device/lib/base/mmio.h" |
| #include "sw/device/lib/base/multibits.h" |
| #include "sw/device/lib/dif/dif_base.h" |
| |
| #include "rstmgr_regs.h" // Generated. |
| |
| // These assertions are only defined for the Earl Grey chip. |
| #if !OT_IS_ENGLISH_BREAKFAST |
| // This macro simplifies the `static_assert` check to make sure that the |
| // public reset info register bitfield matches register bits. |
| #define RSTMGR_RESET_INFO_CHECK(pub_name, priv_name) \ |
| static_assert(kDifRstmgrResetInfo##pub_name == \ |
| (0x1 << RSTMGR_RESET_##priv_name##_BIT), \ |
| "kDifRstmgrResetInfo" #pub_name \ |
| " must match the register definition!") |
| |
| RSTMGR_RESET_INFO_CHECK(Por, INFO_POR); |
| RSTMGR_RESET_INFO_CHECK(LowPowerExit, INFO_LOW_POWER_EXIT); |
| |
| static_assert(kDifRstmgrResetInfoHwReq == (RSTMGR_RESET_INFO_HW_REQ_MASK |
| << RSTMGR_RESET_INFO_HW_REQ_OFFSET), |
| "kDifRstmgrResetInfoHwReq must match the register definition!"); |
| |
| // Turn off the static_assert to enable non-earlgrey top run |
| // static_assert( |
| // RSTMGR_PARAM_NUM_SW_RESETS == 8, |
| // "Number of software resets has changed, please update this file!"); |
| |
| // The Reset Manager implementation will have to be updated if the number |
| // of software resets grows, as it would span across multiple registers, so |
| // there will be multiple of Reset Enable and Reset Control registers. The |
| // appropriate offset from the peripheral base would then have to be |
| // calculated. |
| static_assert( |
| RSTMGR_PARAM_NUM_SW_RESETS <= 32, |
| "Reset Enable and Control registers span across multiple registers!"); |
| |
| // Make sure that the public alert info crash dump size matches the HW. |
| // Note that `RSTMGR_ALERT_INFO_CTRL_INDEX_MASK` implies 16 indexes ( 0 - 15 |
| // inclusive). However, in reality it only supports 15, as |
| // `RSTMGR_ALERT_INFO_ATTR_CNT_AVAIL_MASK` is of the same size, but value of |
| // 0 indicates that there is no alert info crash dump. |
| static_assert( |
| DIF_RSTMGR_ALERT_INFO_MAX_SIZE == RSTMGR_ALERT_INFO_CTRL_INDEX_MASK, |
| "Alert info dump max size has grown, please update the public define!"); |
| #endif // !OT_IS_ENGLISH_BREAKFAST |
| |
| /** |
| * Checks whether alert_info capture is disabled. |
| */ |
| static bool alert_capture_is_locked(mmio_region_t base_addr) { |
| uint32_t bitfield = |
| mmio_region_read32(base_addr, RSTMGR_ALERT_REGWEN_REG_OFFSET); |
| |
| // When bit is cleared, alert capture is disabled. |
| return !bitfield_bit32_read(bitfield, RSTMGR_ALERT_REGWEN_EN_BIT); |
| } |
| |
| /** |
| * Checks whether CPU info capture is disabled. |
| */ |
| static bool cpu_capture_is_locked(mmio_region_t base_addr) { |
| uint32_t bitfield = |
| mmio_region_read32(base_addr, RSTMGR_CPU_REGWEN_REG_OFFSET); |
| |
| // When bit is cleared, APU capture is disabled. |
| return !bitfield_bit32_read(bitfield, RSTMGR_CPU_REGWEN_EN_BIT); |
| } |
| |
| /** |
| * Checks whether the software reset is disabled for a `peripheral`. |
| */ |
| static bool rstmgr_software_reset_is_locked( |
| mmio_region_t base_addr, dif_rstmgr_peripheral_t peripheral) { |
| return !mmio_region_read32( |
| base_addr, RSTMGR_SW_RST_REGWEN_0_REG_OFFSET + 4 * peripheral); |
| } |
| |
| /** |
| * Holds or releases a `peripheral` in/from the reset state. |
| */ |
| static void rstmgr_software_reset_hold(mmio_region_t base_addr, |
| dif_rstmgr_peripheral_t peripheral, |
| bool hold) { |
| bool value = hold ? false : true; |
| mmio_region_write32( |
| base_addr, RSTMGR_SW_RST_CTRL_N_0_REG_OFFSET + 4 * peripheral, value); |
| } |
| |
| /** |
| * Clears entire reset info register. |
| * |
| * Normal "Power On Reset" cause is also cleared. Set bit to clear. |
| */ |
| static void rstmgr_reset_info_clear(mmio_region_t base_addr) { |
| mmio_region_write32(base_addr, RSTMGR_RESET_INFO_REG_OFFSET, UINT32_MAX); |
| } |
| |
| dif_result_t dif_rstmgr_reset(const dif_rstmgr_t *handle) { |
| if (handle == NULL) { |
| return kDifBadArg; |
| } |
| |
| mmio_region_t base_addr = handle->base_addr; |
| |
| rstmgr_reset_info_clear(base_addr); |
| |
| // Set bits to stop holding all peripherals in the reset state. |
| for (uint32_t i = 0; i < RSTMGR_PARAM_NUM_SW_RESETS; i++) { |
| mmio_region_write32(base_addr, RSTMGR_SW_RST_CTRL_N_0_REG_OFFSET + i * 4, |
| UINT32_MAX); |
| } |
| |
| return kDifOk; |
| } |
| |
| dif_result_t dif_rstmgr_reset_lock(const dif_rstmgr_t *handle, |
| dif_rstmgr_peripheral_t peripheral) { |
| if (handle == NULL || peripheral >= RSTMGR_PARAM_NUM_SW_RESETS) { |
| return kDifBadArg; |
| } |
| |
| mmio_region_t base_addr = handle->base_addr; |
| |
| mmio_region_write32(base_addr, |
| RSTMGR_SW_RST_REGWEN_0_REG_OFFSET + 4 * peripheral, 0); |
| |
| return kDifOk; |
| } |
| |
| dif_result_t dif_rstmgr_reset_is_locked(const dif_rstmgr_t *handle, |
| dif_rstmgr_peripheral_t peripheral, |
| bool *is_locked) { |
| if (handle == NULL || is_locked == NULL || |
| peripheral >= RSTMGR_PARAM_NUM_SW_RESETS) { |
| return kDifBadArg; |
| } |
| |
| mmio_region_t base_addr = handle->base_addr; |
| *is_locked = rstmgr_software_reset_is_locked(base_addr, peripheral); |
| |
| return kDifOk; |
| } |
| |
| dif_result_t dif_rstmgr_reset_info_get(const dif_rstmgr_t *handle, |
| dif_rstmgr_reset_info_bitfield_t *info) { |
| if (handle == NULL || info == NULL) { |
| return kDifBadArg; |
| } |
| |
| mmio_region_t base_addr = handle->base_addr; |
| *info = mmio_region_read32(base_addr, RSTMGR_RESET_INFO_REG_OFFSET); |
| |
| return kDifOk; |
| } |
| |
| dif_result_t dif_rstmgr_reset_info_clear(const dif_rstmgr_t *handle) { |
| if (handle == NULL) { |
| return kDifBadArg; |
| } |
| |
| mmio_region_t base_addr = handle->base_addr; |
| |
| rstmgr_reset_info_clear(base_addr); |
| |
| return kDifOk; |
| } |
| |
| dif_result_t dif_rstmgr_alert_info_set_enabled(const dif_rstmgr_t *handle, |
| dif_toggle_t state) { |
| if (handle == NULL) { |
| return kDifBadArg; |
| } |
| |
| mmio_region_t base_addr = handle->base_addr; |
| |
| if (alert_capture_is_locked(base_addr)) { |
| return kDifLocked; |
| } |
| |
| uint32_t enabled = (state == kDifToggleEnabled) ? 0x1 : 0x0; |
| |
| // This will clobber the `ALERT_INFO_CTRL.INDEX` field. However, the index |
| // field is only relevant during the crash dump read operation, and is |
| // set by the caller and not the hardware, so it is safe to clobber it. |
| mmio_region_write32(base_addr, RSTMGR_ALERT_INFO_CTRL_REG_OFFSET, enabled); |
| |
| return kDifOk; |
| } |
| |
| dif_result_t dif_rstmgr_alert_info_get_enabled(const dif_rstmgr_t *handle, |
| dif_toggle_t *state) { |
| if (handle == NULL || state == NULL) { |
| return kDifBadArg; |
| } |
| |
| mmio_region_t base_addr = handle->base_addr; |
| |
| uint32_t reg = |
| mmio_region_read32(base_addr, RSTMGR_ALERT_INFO_CTRL_REG_OFFSET); |
| bool enabled = bitfield_bit32_read(reg, RSTMGR_ALERT_INFO_CTRL_EN_BIT); |
| |
| *state = enabled ? kDifToggleEnabled : kDifToggleDisabled; |
| |
| return kDifOk; |
| } |
| |
| dif_result_t dif_rstmgr_alert_info_get_size(const dif_rstmgr_t *handle, |
| size_t *size) { |
| if (handle == NULL || size == NULL) { |
| return kDifBadArg; |
| } |
| |
| mmio_region_t base_addr = handle->base_addr; |
| *size = mmio_region_read32(base_addr, RSTMGR_ALERT_INFO_ATTR_REG_OFFSET); |
| return kDifOk; |
| } |
| |
| dif_result_t dif_rstmgr_alert_info_dump_read( |
| const dif_rstmgr_t *handle, dif_rstmgr_alert_info_dump_segment_t *dump, |
| size_t dump_size, size_t *segments_read) { |
| if (handle == NULL || dump == NULL || segments_read == NULL) { |
| return kDifBadArg; |
| } |
| |
| mmio_region_t base_addr = handle->base_addr; |
| |
| // The actual crash dump size (can be smaller than `dump_size`). |
| size_t dump_size_actual = |
| mmio_region_read32(base_addr, RSTMGR_ALERT_INFO_ATTR_REG_OFFSET); |
| |
| // Partial crash dump read is not allowed. |
| if (dump_size < dump_size_actual) { |
| return kDifError; |
| } |
| |
| uint32_t control_reg = |
| mmio_region_read32(base_addr, RSTMGR_ALERT_INFO_CTRL_REG_OFFSET); |
| |
| // Read the entire alert info crash dump, one 32bit data segment at the time. |
| for (int i = 0; i < dump_size_actual; ++i) { |
| control_reg = bitfield_field32_write(control_reg, |
| RSTMGR_ALERT_INFO_CTRL_INDEX_FIELD, i); |
| |
| // Set the index of the 32bit data segment to be read at `i`. |
| mmio_region_write32(base_addr, RSTMGR_ALERT_INFO_CTRL_REG_OFFSET, |
| control_reg); |
| |
| // Read the alert info crash dump 32bit data segment. |
| dump[i] = mmio_region_read32(base_addr, RSTMGR_ALERT_INFO_REG_OFFSET); |
| } |
| |
| *segments_read = dump_size_actual; |
| |
| return kDifOk; |
| } |
| |
| dif_result_t dif_rstmgr_cpu_info_set_enabled(const dif_rstmgr_t *handle, |
| dif_toggle_t state) { |
| if (handle == NULL) { |
| return kDifBadArg; |
| } |
| |
| mmio_region_t base_addr = handle->base_addr; |
| |
| if (cpu_capture_is_locked(base_addr)) { |
| return kDifLocked; |
| } |
| |
| uint32_t enabled = (state == kDifToggleEnabled) ? 0x1 : 0x0; |
| |
| // This will clobber the `CPU_INFO_CTRL.INDEX` field. However, the index |
| // field is only relevant during the crash dump read operation, and is |
| // set by the caller and not the hardware, so it is safe to clobber it. |
| mmio_region_write32(base_addr, RSTMGR_CPU_INFO_CTRL_REG_OFFSET, enabled); |
| |
| return kDifOk; |
| } |
| |
| dif_result_t dif_rstmgr_cpu_info_get_enabled(const dif_rstmgr_t *handle, |
| dif_toggle_t *state) { |
| if (handle == NULL || state == NULL) { |
| return kDifBadArg; |
| } |
| |
| mmio_region_t base_addr = handle->base_addr; |
| |
| uint32_t reg = mmio_region_read32(base_addr, RSTMGR_CPU_INFO_CTRL_REG_OFFSET); |
| bool enabled = bitfield_bit32_read(reg, RSTMGR_CPU_INFO_CTRL_EN_BIT); |
| |
| *state = enabled ? kDifToggleEnabled : kDifToggleDisabled; |
| |
| return kDifOk; |
| } |
| |
| dif_result_t dif_rstmgr_cpu_info_get_size(const dif_rstmgr_t *handle, |
| size_t *size) { |
| if (handle == NULL || size == NULL) { |
| return kDifBadArg; |
| } |
| |
| mmio_region_t base_addr = handle->base_addr; |
| *size = mmio_region_read32(base_addr, RSTMGR_CPU_INFO_ATTR_REG_OFFSET); |
| return kDifOk; |
| } |
| |
| dif_result_t dif_rstmgr_cpu_info_dump_read( |
| const dif_rstmgr_t *handle, dif_rstmgr_cpu_info_dump_segment_t *dump, |
| size_t dump_size, size_t *segments_read) { |
| if (handle == NULL || dump == NULL || segments_read == NULL) { |
| return kDifBadArg; |
| } |
| |
| mmio_region_t base_addr = handle->base_addr; |
| |
| // The actual crash dump size (can be smaller than `dump_size`). |
| size_t dump_size_actual = |
| mmio_region_read32(base_addr, RSTMGR_CPU_INFO_ATTR_REG_OFFSET); |
| |
| // Partial crash dump read is not allowed. |
| if (dump_size < dump_size_actual) { |
| return kDifError; |
| } |
| |
| uint32_t control_reg = |
| mmio_region_read32(base_addr, RSTMGR_CPU_INFO_CTRL_REG_OFFSET); |
| |
| // Read the entire cpu info crash dump, one 32bit data segment at the time. |
| for (int i = 0; i < dump_size_actual; ++i) { |
| control_reg = bitfield_field32_write(control_reg, |
| RSTMGR_CPU_INFO_CTRL_INDEX_FIELD, i); |
| |
| // Set the index of the 32bit data segment to be read at `i`. |
| mmio_region_write32(base_addr, RSTMGR_CPU_INFO_CTRL_REG_OFFSET, |
| control_reg); |
| |
| // Read the cpu info crash dump 32bit data segment. |
| dump[i] = mmio_region_read32(base_addr, RSTMGR_CPU_INFO_REG_OFFSET); |
| } |
| |
| *segments_read = dump_size_actual; |
| |
| return kDifOk; |
| } |
| |
| dif_result_t dif_rstmgr_software_reset(const dif_rstmgr_t *handle, |
| dif_rstmgr_peripheral_t peripheral, |
| dif_rstmgr_software_reset_t reset) { |
| if (handle == NULL || peripheral >= RSTMGR_PARAM_NUM_SW_RESETS) { |
| return kDifBadArg; |
| } |
| |
| mmio_region_t base_addr = handle->base_addr; |
| if (rstmgr_software_reset_is_locked(base_addr, peripheral)) { |
| return kDifLocked; |
| } |
| |
| switch (reset) { |
| case kDifRstmgrSoftwareReset: |
| rstmgr_software_reset_hold(base_addr, peripheral, true); |
| rstmgr_software_reset_hold(base_addr, peripheral, false); |
| break; |
| case kDifRstmgrSoftwareResetHold: |
| rstmgr_software_reset_hold(base_addr, peripheral, true); |
| break; |
| case kDifRstmgrSoftwareResetRelease: |
| rstmgr_software_reset_hold(base_addr, peripheral, false); |
| break; |
| default: |
| return kDifError; |
| } |
| |
| return kDifOk; |
| } |
| |
| dif_result_t dif_rstmgr_software_reset_is_held( |
| const dif_rstmgr_t *handle, dif_rstmgr_peripheral_t peripheral, |
| bool *asserted) { |
| if (handle == NULL || asserted == NULL || |
| peripheral >= RSTMGR_PARAM_NUM_SW_RESETS) { |
| return kDifBadArg; |
| } |
| |
| // When the bit is cleared - peripheral is held in reset. |
| *asserted = !mmio_region_read32( |
| handle->base_addr, RSTMGR_SW_RST_CTRL_N_0_REG_OFFSET + 4 * peripheral); |
| |
| return kDifOk; |
| } |
| |
| dif_result_t dif_rstmgr_software_device_reset(const dif_rstmgr_t *handle) { |
| if (handle == NULL) { |
| return kDifBadArg; |
| } |
| |
| mmio_region_write32(handle->base_addr, RSTMGR_RESET_REQ_REG_OFFSET, |
| kMultiBitBool4True); |
| |
| return kDifOk; |
| } |
| |
| dif_result_t dif_rstmgr_fatal_err_code_get_codes( |
| const dif_rstmgr_t *rstmgr, dif_rstmgr_fatal_err_codes_t *codes) { |
| if (rstmgr == NULL || codes == NULL) { |
| return kDifBadArg; |
| } |
| *codes = mmio_region_read32(rstmgr->base_addr, RSTMGR_ERR_CODE_REG_OFFSET); |
| return kDifOk; |
| } |