| // 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/crypto/drivers/entropy.h" | |
| #include "sw/device/lib/base/abs_mmio.h" | |
| #include "sw/device/lib/base/bitfield.h" | |
| #include "sw/device/lib/base/memory.h" | |
| #include "csrng_regs.h" // Generated | |
| #include "hw/top_earlgrey/sw/autogen/top_earlgrey.h" | |
| enum { | |
| kBaseCsrng = TOP_EARLGREY_CSRNG_BASE_ADDR, | |
| /** | |
| * CSRNG genbits buffer size in uint32_t words. | |
| */ | |
| kEntropyCsrngBitsBufferNumWords = 4, | |
| }; | |
| /** | |
| * Supported CSRNG application commands. | |
| * See https://docs.opentitan.org/hw/ip/csrng/doc/#command-header for | |
| * details. | |
| */ | |
| // TODO(#14542): Harden csrng/edn command fields. | |
| typedef enum entropy_csrng_op { | |
| kEntropyDrbgOpInstantiate = 1, | |
| kEntropyDrbgOpReseed = 2, | |
| kEntropyDrbgOpGenerate = 3, | |
| kEntropyDrbgOpUpdate = 4, | |
| kEntropyDrbgOpUnisntantiate = 5, | |
| } entropy_csrng_op_t; | |
| /** | |
| * CSRNG application interface command header parameters. | |
| */ | |
| typedef struct entropy_csrng_cmd { | |
| /** | |
| * Application command ID. | |
| */ | |
| entropy_csrng_op_t id; | |
| /** | |
| * Entropy source enable. | |
| * | |
| * Mapped to flag0 in the hardware command interface. | |
| */ | |
| hardened_bool_t disable_trng_input; | |
| const entropy_seed_material_t *seed_material; | |
| /** | |
| * Generate length. Specified as number of 128bit blocks. | |
| */ | |
| uint32_t generate_len; | |
| } entropy_csrng_cmd_t; | |
| #define ENTROPY_CMD(m, i) ((bitfield_field32_t){.mask = m, .index = i}) | |
| OT_WARN_UNUSED_RESULT | |
| static status_t csrng_send_app_cmd(uint32_t reg_address, | |
| entropy_csrng_cmd_t cmd) { | |
| uint32_t reg; | |
| bool cmd_ready; | |
| do { | |
| reg = abs_mmio_read32(kBaseCsrng + CSRNG_SW_CMD_STS_REG_OFFSET); | |
| cmd_ready = bitfield_bit32_read(reg, CSRNG_SW_CMD_STS_CMD_RDY_BIT); | |
| } while (!cmd_ready); | |
| // The application command header is not specified as a register in the | |
| // hardware specification, so the fields are mapped here by hand. The | |
| // command register also accepts arbitrary 32bit data. | |
| static const bitfield_field32_t kAppCmdFieldFlag0 = ENTROPY_CMD(0xf, 8); | |
| static const bitfield_field32_t kAppCmdFieldCmdId = ENTROPY_CMD(0xf, 0); | |
| static const bitfield_field32_t kAppCmdFieldCmdLen = ENTROPY_CMD(0xf, 4); | |
| static const bitfield_field32_t kAppCmdFieldGlen = ENTROPY_CMD(0x7ffff, 12); | |
| uint32_t cmd_len = cmd.seed_material == NULL ? 0 : cmd.seed_material->len; | |
| if (cmd_len & ~kAppCmdFieldCmdLen.mask) { | |
| return INTERNAL(); | |
| } | |
| // TODO: Consider removing this since the driver will be constructing these | |
| // commands internally. | |
| // Ensure the `seed_material` array is word-aligned, so it can be loaded to a | |
| // CPU register with natively aligned loads. | |
| if (cmd.seed_material != NULL && | |
| misalignment32_of((uintptr_t)cmd.seed_material->data) != 0) { | |
| return INTERNAL(); | |
| } | |
| // Build and write application command header. | |
| reg = bitfield_field32_write(0, kAppCmdFieldCmdId, cmd.id); | |
| reg = bitfield_field32_write(reg, kAppCmdFieldCmdLen, cmd_len); | |
| reg = bitfield_field32_write(reg, kAppCmdFieldGlen, cmd.generate_len); | |
| if (launder32(cmd.disable_trng_input) == kHardenedBoolTrue) { | |
| reg = bitfield_field32_write(reg, kAppCmdFieldFlag0, kMultiBitBool4True); | |
| } | |
| abs_mmio_write32(reg_address, reg); | |
| for (size_t i = 0; i < cmd_len; ++i) { | |
| abs_mmio_write32(reg_address, cmd.seed_material->data[i]); | |
| } | |
| return OK_STATUS(); | |
| } | |
| status_t entropy_csrng_instantiate( | |
| hardened_bool_t disable_trng_input, | |
| const entropy_seed_material_t *seed_material) { | |
| return csrng_send_app_cmd(kBaseCsrng + CSRNG_CMD_REQ_REG_OFFSET, | |
| (entropy_csrng_cmd_t){ | |
| .id = kEntropyDrbgOpInstantiate, | |
| .disable_trng_input = disable_trng_input, | |
| .seed_material = seed_material, | |
| .generate_len = 0, | |
| }); | |
| } | |
| status_t entropy_csrng_reseed(hardened_bool_t disable_trng_input, | |
| const entropy_seed_material_t *seed_material) { | |
| return csrng_send_app_cmd(kBaseCsrng + CSRNG_CMD_REQ_REG_OFFSET, | |
| (entropy_csrng_cmd_t){ | |
| .id = kEntropyDrbgOpReseed, | |
| .disable_trng_input = disable_trng_input, | |
| .seed_material = seed_material, | |
| .generate_len = 0, | |
| }); | |
| } | |
| status_t entropy_csrng_update(const entropy_seed_material_t *seed_material) { | |
| return csrng_send_app_cmd(kBaseCsrng + CSRNG_CMD_REQ_REG_OFFSET, | |
| (entropy_csrng_cmd_t){ | |
| .id = kEntropyDrbgOpUpdate, | |
| .seed_material = seed_material, | |
| .generate_len = 0, | |
| }); | |
| } | |
| status_t entropy_csrng_generate_start( | |
| const entropy_seed_material_t *seed_material, size_t len) { | |
| // 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(kBaseCsrng + CSRNG_CMD_REQ_REG_OFFSET, | |
| (entropy_csrng_cmd_t){ | |
| .id = kEntropyDrbgOpGenerate, | |
| .seed_material = seed_material, | |
| .generate_len = num_128bit_blocks, | |
| }); | |
| } | |
| status_t entropy_csrng_generate_data_get(uint32_t *buf, size_t len) { | |
| for (size_t i = 0; i < len; ++i) { | |
| // Block until there is more data available in the genbits buffer. CSRNG | |
| // generates data in 128bit chunks (i.e. 4 words). | |
| static_assert(kEntropyCsrngBitsBufferNumWords == 4, | |
| "kEntropyCsrngBitsBufferNumWords must be a power of 2."); | |
| if (i & (kEntropyCsrngBitsBufferNumWords - 1)) { | |
| uint32_t reg; | |
| do { | |
| reg = abs_mmio_read32(kBaseCsrng + CSRNG_GENBITS_VLD_REG_OFFSET); | |
| } while (!bitfield_bit32_read(reg, CSRNG_GENBITS_VLD_GENBITS_VLD_BIT)); | |
| } | |
| buf[i] = abs_mmio_read32(kBaseCsrng + CSRNG_GENBITS_REG_OFFSET); | |
| } | |
| return OK_STATUS(); | |
| } | |
| status_t entropy_csrng_generate(const entropy_seed_material_t *seed_material, | |
| uint32_t *buf, size_t len) { | |
| TRY(entropy_csrng_generate_start(seed_material, len)); | |
| return entropy_csrng_generate_data_get(buf, len); | |
| } | |
| status_t entropy_csrng_uninstantiate(void) { | |
| return csrng_send_app_cmd(kBaseCsrng + CSRNG_CMD_REQ_REG_OFFSET, | |
| (entropy_csrng_cmd_t){ | |
| .id = kEntropyDrbgOpUpdate, | |
| .seed_material = NULL, | |
| .generate_len = 0, | |
| }); | |
| } |