ProtoCore v0.0.2
Deterministic, zero-heap network stack for embedded targets
Loading...
Searching...
No Matches
esp_crypto_hal.cpp
Go to the documentation of this file.
1// Copyright (C) 2026 Douglas Quigg (dstroy0) <dquigg123@gmail.com>
2// SPDX-License-Identifier: AGPL-3.0-or-later
3
4/**
5 * @file esp_crypto_hal.cpp
6 * @brief Real-HAL implementation: single-owner RSA/MPI accelerator, brought up by DIRECT register writes.
7 *
8 * Self-contained: uses only this HAL's own register map (esp_crypto_hal.h) and FreeRTOS for the one shared
9 * exclusivity mutex - no `soc/` header, no `esp_mpi_*` / `mpi_hal_*` / `mpi_ll_*` symbol. The exclusivity
10 * mutex must be ONE global instance shared by every translation unit that drives the accelerator, so
11 * acquire/release live here (a header-only `static` would give each TU its own copy - not a lock).
12 *
13 * Bring-up is ON DEMAND and the peripheral is then LEFT running: a run of MODMULTs is stateless (each reloads
14 * all operands), so re-resetting/power-cycling between ops is unnecessary - and, measured on-device, a per-op
15 * power-cycle is not even deterministic (a re-init right after a teardown can return a wrong first result). We
16 * bring up only when the peripheral is not already clocked+powered: at cold boot, or after another RSA-
17 * peripheral user (e.g. mbedTLS RSA/DH) powered it down on its own teardown. Detection reads the clock-domain
18 * registers (always accessible), never the possibly-unclocked RSA block.
19 */
20
22
23#ifdef PC_RSA_MODMUL_HW
24
25#include "freertos/FreeRTOS.h"
26#include "freertos/semphr.h"
27
28namespace
29{
30// All RSA/MPI-accelerator ownership state in one owned context (internal linkage): the PC exclusivity mutex
31// (one global instance, shared across every TU that drives the accelerator) and the spinlock guarding both its
32// lazy creation and the clock/reset register-modify-write (those clock-domain registers are shared with other
33// peripherals). One named owner, unreachable cross-TU.
34struct HalRsaCtx
35{
36 SemaphoreHandle_t lock; // PC recursive mutex; held across a whole scalar-mult run
37 portMUX_TYPE hw_mux; // guards lazy mutex creation and the shared clock/reset RMW + memory-init
38};
39HalRsaCtx s_rsa = {nullptr, portMUX_INITIALIZER_UNLOCKED};
40
41// Is the accelerator already clocked (and, where applicable, powered)? Reads clock-domain registers only,
42// which are always accessible even when the RSA block itself is unclocked.
43bool rsa_is_up()
44{
45 const bool clocked = (PC_HW_REG(PC_RSA_CLK_REG) & PC_RSA_CLK_BIT) != 0u;
46#if PC_RSA_HAS_PD
47 const bool powered = (PC_HW_REG(PC_RSA_PD_REG) & PC_RSA_PD_DOWN_BIT) == 0u;
48 return clocked && powered;
49#else
50 return clocked;
51#endif
52}
53
54// Bring the accelerator up by direct register writes: enable the bus clock, pulse the RSA reset (and release
55// the sibling resets that would otherwise hold RSA in reset), power up the RSA memory (hw_ver1 only), then
56// spin until the memory-init completes. The clock/reset registers are RMW-shared with other peripherals, so
57// the caller holds s_rsa.hw_mux. Each RMW is an explicit read-modify-write of one bit.
58void rsa_bring_up()
59{
60 uint32_t v = PC_HW_REG(PC_RSA_CLK_REG);
61 v |= PC_RSA_CLK_BIT; // bus clock on
62 PC_HW_REG(PC_RSA_CLK_REG) = v;
63
64 v = PC_HW_REG(PC_RSA_RST_REG);
65 v |= PC_RSA_RST_BIT; // assert RSA reset
66 PC_HW_REG(PC_RSA_RST_REG) = v;
67 v = PC_HW_REG(PC_RSA_RST_REG);
68 v &= ~PC_RSA_RST_BIT; // deassert RSA reset
69 PC_HW_REG(PC_RSA_RST_REG) = v;
70
71 v = PC_HW_REG(PC_RSA_HOLD_REG);
72 v &= ~PC_RSA_HOLD_CLEAR; // release the sibling resets (DS / crypto / ECDSA) that would hold RSA in reset
73 PC_HW_REG(PC_RSA_HOLD_REG) = v;
74
75#ifdef PC_RSA_HOLD2_REG
76 v = PC_HW_REG(PC_RSA_HOLD2_REG);
77 v &= ~PC_RSA_HOLD2_CLEAR; // some dies (C5/H2) hold the ECDSA reset in a second, separate register
78 PC_HW_REG(PC_RSA_HOLD2_REG) = v;
79#endif
80
81#if PC_RSA_HAS_PD
82 v = PC_HW_REG(PC_RSA_PD_REG);
83 v &= ~PC_RSA_PD_UP_CLEAR; // power up the RSA memory
84 PC_HW_REG(PC_RSA_PD_REG) = v;
85#endif
86
87 while (PC_HW_REG(PC_RSA_CLEAN) != 0u) // wait until the accelerator's memory init completes
88 {
89 }
90}
91} // namespace
92
93void pc_rsa_hw_acquire(void)
94{
95 if (s_rsa.lock == nullptr)
96 {
97 portENTER_CRITICAL(&s_rsa.hw_mux);
98 if (s_rsa.lock == nullptr)
99 {
100 s_rsa.lock = xSemaphoreCreateRecursiveMutex();
101 }
102 portEXIT_CRITICAL(&s_rsa.hw_mux);
103 }
104 xSemaphoreTakeRecursive(s_rsa.lock, portMAX_DELAY);
105
106 if (!rsa_is_up())
107 {
108 // Bring-up runs interrupts-off: the memory-init must not be preempted, or the idle task can gate the
109 // RSA clock mid-init and the clean bit never clears.
110 portENTER_CRITICAL(&s_rsa.hw_mux);
111 rsa_bring_up();
112 portEXIT_CRITICAL(&s_rsa.hw_mux);
113 }
114 PC_HW_REG(PC_RSA_INTENA) = 0u; // poll only, no completion IRQ
115}
116
117void pc_rsa_hw_release(void)
118{
119 // Leave the peripheral clocked+powered for the next op (a MODMULT run is stateless; a per-op power-cycle is
120 // both wasteful and, measured, non-deterministic). It is re-brought-up on the next acquire only if some
121 // other user powered it down meanwhile.
122 xSemaphoreGiveRecursive(s_rsa.lock);
123}
124
125#endif // PC_RSA_MODMUL_HW
Single owner of the ESP32 RSA/MPI accelerator, by DIRECT register access - a self-contained HAL.