| // 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_otbn.h" |
| |
| #include <assert.h> |
| |
| #include "sw/device/lib/base/bitfield.h" |
| |
| #include "otbn_regs.h" // Generated. |
| |
| static_assert(kDifOtbnErrBitsBadDataAddr == |
| (1 << OTBN_ERR_BITS_BAD_DATA_ADDR_BIT), |
| "Layout of error bits changed."); |
| static_assert(kDifOtbnErrBitsBadInsnAddr == |
| (1 << OTBN_ERR_BITS_BAD_INSN_ADDR_BIT), |
| "Layout of error bits changed."); |
| static_assert(kDifOtbnErrBitsCallStack == (1 << OTBN_ERR_BITS_CALL_STACK_BIT), |
| "Layout of error bits changed."); |
| static_assert(kDifOtbnErrBitsIllegalInsn == |
| (1 << OTBN_ERR_BITS_ILLEGAL_INSN_BIT), |
| "Layout of error bits changed."); |
| static_assert(kDifOtbnErrBitsLoop == (1 << OTBN_ERR_BITS_LOOP_BIT), |
| "Layout of error bits changed."); |
| static_assert(kDifOtbnErrBitsFatalImem == (1 << OTBN_ERR_BITS_FATAL_IMEM_BIT), |
| "Layout of error bits changed."); |
| static_assert(kDifOtbnErrBitsFatalDmem == (1 << OTBN_ERR_BITS_FATAL_DMEM_BIT), |
| "Layout of error bits changed."); |
| static_assert(kDifOtbnErrBitsFatalReg == (1 << OTBN_ERR_BITS_FATAL_REG_BIT), |
| "Layout of error bits changed."); |
| static_assert(kDifOtbnErrBitsFatalIllegalBusAccess == |
| (1 << OTBN_ERR_BITS_FATAL_ILLEGAL_BUS_ACCESS_BIT), |
| "Layout of error bits changed."); |
| static_assert(kDifOtbnErrBitsFatalLifecycleEscalation == |
| (1 << OTBN_ERR_BITS_FATAL_LIFECYCLE_ESCALATION_BIT), |
| "Layout of error bits changed."); |
| |
| /** |
| * Data width of big number subset, in bytes. |
| */ |
| const int kDifOtbnWlenBytes = 256 / 8; |
| |
| /** |
| * Convert from a `dif_otbn_interrupt_t` to the appropriate bit index. |
| * |
| * INTR_STATE, INTR_ENABLE, and INTR_TEST registers have the same bit offset. |
| */ |
| static bool irq_bit_index_get(dif_otbn_interrupt_t irq_type, |
| uint8_t *offset_out) { |
| ptrdiff_t offset; |
| switch (irq_type) { |
| case kDifOtbnInterruptDone: |
| offset = OTBN_INTR_COMMON_DONE_BIT; |
| break; |
| default: |
| return false; |
| } |
| |
| *offset_out = offset; |
| |
| return true; |
| } |
| |
| /** |
| * Ensures that `offset` and `size` are valid for a given `mem_size`. |
| * |
| * Valid are 32b word accesses to 32b-aligned memory locations within |
| * `mem_size`. |
| */ |
| static bool check_offset_len(uint32_t offset_bytes, size_t len_bytes, |
| size_t mem_size) { |
| // The overflow check below assumes/requires two unsigned inputs. |
| return (len_bytes % sizeof(uint32_t) == 0 && |
| offset_bytes % sizeof(uint32_t) == 0 && |
| offset_bytes + len_bytes >= len_bytes && |
| offset_bytes + len_bytes <= mem_size); |
| } |
| |
| dif_otbn_result_t dif_otbn_init(const dif_otbn_config_t *config, |
| dif_otbn_t *otbn) { |
| if (config == NULL || otbn == NULL) { |
| return kDifOtbnBadArg; |
| } |
| |
| otbn->base_addr = config->base_addr; |
| dif_otbn_reset(otbn); |
| |
| return kDifOtbnOk; |
| } |
| |
| dif_otbn_result_t dif_otbn_reset(const dif_otbn_t *otbn) { |
| if (otbn == NULL) { |
| return kDifOtbnBadArg; |
| } |
| |
| mmio_region_write32(otbn->base_addr, OTBN_INTR_ENABLE_REG_OFFSET, 0); |
| |
| // Clear all pending interrupts. |
| mmio_region_write32(otbn->base_addr, OTBN_INTR_STATE_REG_OFFSET, 0xFFFFFFFF); |
| |
| return kDifOtbnOk; |
| } |
| |
| dif_otbn_result_t dif_otbn_irq_state_get(const dif_otbn_t *otbn, |
| dif_otbn_interrupt_t irq_type, |
| dif_otbn_enable_t *state) { |
| if (otbn == NULL || state == NULL) { |
| return kDifOtbnBadArg; |
| } |
| |
| uint8_t bit_index; |
| if (!irq_bit_index_get(irq_type, &bit_index)) { |
| return kDifOtbnError; |
| } |
| |
| bool enabled = bitfield_bit32_read( |
| mmio_region_read32(otbn->base_addr, OTBN_INTR_STATE_REG_OFFSET), |
| bit_index); |
| *state = (enabled ? kDifOtbnEnable : kDifOtbnDisable); |
| |
| return kDifOtbnOk; |
| } |
| |
| dif_otbn_result_t dif_otbn_irq_state_clear(const dif_otbn_t *otbn, |
| dif_otbn_interrupt_t irq_type) { |
| if (otbn == NULL) { |
| return kDifOtbnBadArg; |
| } |
| |
| uint8_t bit_index; |
| if (!irq_bit_index_get(irq_type, &bit_index)) { |
| return kDifOtbnError; |
| } |
| |
| uint32_t register_value = 0x0u; |
| register_value = bitfield_bit32_write(register_value, bit_index, true); |
| mmio_region_write32(otbn->base_addr, OTBN_INTR_STATE_REG_OFFSET, |
| register_value); |
| |
| return kDifOtbnOk; |
| } |
| |
| dif_otbn_result_t dif_otbn_irqs_disable(const dif_otbn_t *otbn, |
| uint32_t *state) { |
| if (otbn == NULL) { |
| return kDifOtbnBadArg; |
| } |
| |
| // Pass the interrupt state back to the caller. |
| if (state != NULL) { |
| *state = mmio_region_read32(otbn->base_addr, OTBN_INTR_ENABLE_REG_OFFSET); |
| } |
| |
| // Disable all interrupts. |
| mmio_region_write32(otbn->base_addr, OTBN_INTR_ENABLE_REG_OFFSET, 0u); |
| |
| return kDifOtbnOk; |
| } |
| |
| dif_otbn_result_t dif_otbn_irqs_restore(const dif_otbn_t *otbn, |
| uint32_t state) { |
| if (otbn == NULL) { |
| return kDifOtbnBadArg; |
| } |
| |
| // Restore interrupt state. |
| mmio_region_write32(otbn->base_addr, OTBN_INTR_ENABLE_REG_OFFSET, state); |
| |
| return kDifOtbnOk; |
| } |
| |
| dif_otbn_result_t dif_otbn_irq_control(const dif_otbn_t *otbn, |
| dif_otbn_interrupt_t irq_type, |
| dif_otbn_enable_t enable) { |
| if (otbn == NULL) { |
| return kDifOtbnBadArg; |
| } |
| |
| uint8_t bit_index; |
| if (!irq_bit_index_get(irq_type, &bit_index)) { |
| return kDifOtbnError; |
| } |
| |
| // Enable/Disable interrupt. |
| uint32_t register_value = |
| mmio_region_read32(otbn->base_addr, OTBN_INTR_ENABLE_REG_OFFSET); |
| register_value = bitfield_bit32_write(register_value, bit_index, |
| (enable == kDifOtbnEnable)); |
| mmio_region_write32(otbn->base_addr, OTBN_INTR_ENABLE_REG_OFFSET, |
| register_value); |
| |
| return kDifOtbnOk; |
| } |
| |
| dif_otbn_result_t dif_otbn_irq_force(const dif_otbn_t *otbn, |
| dif_otbn_interrupt_t irq_type) { |
| if (otbn == NULL) { |
| return kDifOtbnBadArg; |
| } |
| |
| uint8_t bit_index; |
| if (!irq_bit_index_get(irq_type, &bit_index)) { |
| return kDifOtbnError; |
| } |
| |
| // Force the requested interrupt. |
| uint32_t register_value = |
| mmio_region_read32(otbn->base_addr, OTBN_INTR_TEST_REG_OFFSET); |
| register_value = bitfield_bit32_write(register_value, bit_index, true); |
| mmio_region_write32(otbn->base_addr, OTBN_INTR_TEST_REG_OFFSET, |
| register_value); |
| |
| return kDifOtbnOk; |
| } |
| |
| dif_otbn_result_t dif_otbn_start(const dif_otbn_t *otbn, |
| unsigned int start_addr) { |
| if (otbn == NULL || start_addr % sizeof(uint32_t) != 0 || |
| start_addr >= OTBN_IMEM_SIZE_BYTES) { |
| return kDifOtbnBadArg; |
| } |
| |
| mmio_region_write32(otbn->base_addr, OTBN_START_ADDR_REG_OFFSET, start_addr); |
| |
| uint32_t cmd_reg_val = 0x0u; |
| cmd_reg_val = bitfield_bit32_write(cmd_reg_val, OTBN_CMD_START_BIT, true); |
| mmio_region_write32(otbn->base_addr, OTBN_CMD_REG_OFFSET, cmd_reg_val); |
| |
| return kDifOtbnOk; |
| } |
| |
| dif_otbn_result_t dif_otbn_is_busy(const dif_otbn_t *otbn, bool *busy) { |
| if (otbn == NULL || busy == NULL) { |
| return kDifOtbnBadArg; |
| } |
| |
| uint32_t status = mmio_region_read32(otbn->base_addr, OTBN_STATUS_REG_OFFSET); |
| *busy = bitfield_bit32_read(status, OTBN_STATUS_BUSY_BIT); |
| |
| return kDifOtbnOk; |
| } |
| |
| dif_otbn_result_t dif_otbn_get_err_bits(const dif_otbn_t *otbn, |
| dif_otbn_err_bits_t *err_bits) { |
| if (otbn == NULL || err_bits == NULL) { |
| return kDifOtbnBadArg; |
| } |
| |
| uint32_t err_bits_raw = |
| mmio_region_read32(otbn->base_addr, OTBN_ERR_BITS_REG_OFFSET); |
| |
| *err_bits = err_bits_raw; |
| return kDifOtbnOk; |
| } |
| |
| dif_otbn_result_t dif_otbn_get_insn_cnt(const dif_otbn_t *otbn, |
| uint32_t *insn_cnt) { |
| if (otbn == NULL || insn_cnt == NULL) { |
| return kDifOtbnBadArg; |
| } |
| |
| *insn_cnt = mmio_region_read32(otbn->base_addr, OTBN_INSN_CNT_REG_OFFSET); |
| return kDifOtbnOk; |
| } |
| |
| dif_otbn_result_t dif_otbn_imem_write(const dif_otbn_t *otbn, |
| uint32_t offset_bytes, const void *src, |
| size_t len_bytes) { |
| if (otbn == NULL || src == NULL || |
| !check_offset_len(offset_bytes, len_bytes, OTBN_IMEM_SIZE_BYTES)) { |
| return kDifOtbnBadArg; |
| } |
| |
| mmio_region_memcpy_to_mmio32( |
| otbn->base_addr, OTBN_IMEM_REG_OFFSET + offset_bytes, src, len_bytes); |
| |
| return kDifOtbnOk; |
| } |
| |
| dif_otbn_result_t dif_otbn_imem_read(const dif_otbn_t *otbn, |
| uint32_t offset_bytes, void *dest, |
| size_t len_bytes) { |
| if (otbn == NULL || dest == NULL || |
| !check_offset_len(offset_bytes, len_bytes, OTBN_IMEM_SIZE_BYTES)) { |
| return kDifOtbnBadArg; |
| } |
| |
| mmio_region_memcpy_from_mmio32( |
| otbn->base_addr, OTBN_IMEM_REG_OFFSET + offset_bytes, dest, len_bytes); |
| |
| return kDifOtbnOk; |
| } |
| |
| dif_otbn_result_t dif_otbn_dmem_write(const dif_otbn_t *otbn, |
| uint32_t offset_bytes, const void *src, |
| size_t len_bytes) { |
| if (otbn == NULL || src == NULL || |
| !check_offset_len(offset_bytes, len_bytes, OTBN_DMEM_SIZE_BYTES)) { |
| return kDifOtbnBadArg; |
| } |
| |
| mmio_region_memcpy_to_mmio32( |
| otbn->base_addr, OTBN_DMEM_REG_OFFSET + offset_bytes, src, len_bytes); |
| |
| return kDifOtbnOk; |
| } |
| |
| dif_otbn_result_t dif_otbn_dmem_read(const dif_otbn_t *otbn, |
| uint32_t offset_bytes, void *dest, |
| size_t len_bytes) { |
| if (otbn == NULL || dest == NULL || |
| !check_offset_len(offset_bytes, len_bytes, OTBN_IMEM_SIZE_BYTES)) { |
| return kDifOtbnBadArg; |
| } |
| |
| mmio_region_memcpy_from_mmio32( |
| otbn->base_addr, OTBN_DMEM_REG_OFFSET + offset_bytes, dest, len_bytes); |
| |
| return kDifOtbnOk; |
| } |
| |
| size_t dif_otbn_get_dmem_size_bytes(const dif_otbn_t *otbn) { |
| return OTBN_DMEM_SIZE_BYTES; |
| } |
| |
| size_t dif_otbn_get_imem_size_bytes(const dif_otbn_t *otbn) { |
| return OTBN_IMEM_SIZE_BYTES; |
| } |