ProtoCore v0.0.2
Deterministic, zero-heap network stack for embedded targets
Loading...
Searching...
No Matches
esp_crypto_hal.h
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.h
6 * @brief Single owner of the ESP32 RSA/MPI accelerator, by DIRECT register access - a self-contained HAL.
7 *
8 * A hardware-abstraction layer, deliberately outside `crypto/` (very distinct, register-level code): the
9 * library's big-field crypto (the GF(2^255-19) layer in @ref fe25519.h for X25519 / Ed25519 and the NIST P-256
10 * field/scalar layer in @ref ecdsa.cpp) drives ONE primitive on the RSA accelerator - a single-shot 256-bit
11 * modular multiply `Z = X*Y mod M`.
12 *
13 * ═══════════════════════════════════════════════════════════════════════════
14 * SELF-CONTAINED - our own register map, no vendor headers or symbols
15 * ═══════════════════════════════════════════════════════════════════════════
16 * The objective is a platform-agnostic library that reaches the silicon by DIRECT register access. So this HAL
17 * reproduces the accelerator's register map ITSELF (bases, offsets, clock/reset/power bits - values below,
18 * cross-checked against each die's TRM) and pokes it with @ref PC_HW_REG. It includes NO `soc/` header and
19 * references NO vendor symbol (no `esp_mpi_*` / `mpi_hal_*` / `mpi_ll_*`, no `RSA_*_REG`, no `SYSTEM`/
20 * `HP_SYS_CLKRST` struct). An upstream rename or header reshuffle therefore cannot touch our crypto; only a
21 * genuine silicon change would, caught by the per-die `#error`. The one build dependency is `sdkconfig.h`,
22 * solely to learn which die we are compiling for (`CONFIG_IDF_TARGET_*`) - that is the build target, not a
23 * register.
24 *
25 * Register generations: S3/S2 = "hw_ver1", ESP32-P4 and newer = "hw_ver3". The RSA block's own register
26 * offsets are identical across both; only the peripheral base and the clock/reset/power control registers
27 * differ. The classic ESP32 has no single-shot MODMULT, so it (and native builds) leave PC_RSA_MODMUL_HW
28 * undefined and callers use their software field layer.
29 *
30 * ═══════════════════════════════════════════════════════════════════════════
31 * EXCLUSIVITY
32 * ═══════════════════════════════════════════════════════════════════════════
33 * The accelerator is shared state. This HAL owns a single PC recursive mutex (see esp_crypto_hal.cpp) taken
34 * by @ref pc_rsa_hw_acquire and dropped by @ref pc_rsa_hw_release; a scalar-mult holds it across its whole
35 * run of multiplies.
36 */
37
38#ifndef PROTOCORE_ESP_CRYPTO_HAL_H
39#define PROTOCORE_ESP_CRYPTO_HAL_H
40
41#include <stdint.h>
42
43#ifdef ARDUINO
44#include "sdkconfig.h" // CONFIG_IDF_TARGET_* : which die we build for (the build target, not a register header)
45#endif
46
47// Every ESP32 die whose RSA/MPI accelerator has the single-shot MODMULT operation. HW-verified on-device
48// (main_cryptobench RFC KATs): S3, P4, C6. Register-map-complete and compile-verified pending an on-device
49// KAT on their rigs: S2, C3, C5, H2. The classic ESP32 has no single-shot MODMULT (two MULT passes), so it -
50// and native builds - leave this undefined and callers use the software field layer.
51#if defined(ARDUINO) && ((defined(CONFIG_IDF_TARGET_ESP32S3) && CONFIG_IDF_TARGET_ESP32S3) || \
52 (defined(CONFIG_IDF_TARGET_ESP32S2) && CONFIG_IDF_TARGET_ESP32S2) || \
53 (defined(CONFIG_IDF_TARGET_ESP32C3) && CONFIG_IDF_TARGET_ESP32C3) || \
54 (defined(CONFIG_IDF_TARGET_ESP32C5) && CONFIG_IDF_TARGET_ESP32C5) || \
55 (defined(CONFIG_IDF_TARGET_ESP32C6) && CONFIG_IDF_TARGET_ESP32C6) || \
56 (defined(CONFIG_IDF_TARGET_ESP32H2) && CONFIG_IDF_TARGET_ESP32H2) || \
57 (defined(CONFIG_IDF_TARGET_ESP32P4) && CONFIG_IDF_TARGET_ESP32P4))
58#define PC_RSA_MODMUL_HW 1
59#endif
60
61#ifdef PC_RSA_MODMUL_HW
62
63// Raw 32-bit memory-mapped register access - the only primitive; no vendor REG_* macro.
64#define PC_HW_REG(a) (*(volatile uint32_t *)(uintptr_t)(a))
65
66// ── Per-die peripheral base + clock/reset/power control (reproduced from each die's TRM) ──────────────────
67// Model (fits every die's quirks): a clock-enable reg+bit, an RSA-reset reg+bit, a "hold-clear" reg+mask (the
68// sibling reset bits - DS / parent-crypto / ECDSA - that would otherwise keep RSA held in reset; on some dies
69// this is a different register from the RSA reset), and optional RSA-memory power (up-clear mask + a
70// down-status bit). Clock/reset live in SYSTEM (hw_ver1), HP_SYS_CLKRST (P4), or PCR (C-series).
71#if defined(CONFIG_IDF_TARGET_ESP32S3) && CONFIG_IDF_TARGET_ESP32S3 // hw_ver1, SYSTEM clock/reset
72#define PC_RSA_BASE 0x6003C000u
73#define PC_RSA_CLK_REG 0x600C001Cu // SYSTEM_PERIP_CLK_EN1_REG
74#define PC_RSA_CLK_BIT (1u << 3) // SYSTEM_CRYPTO_RSA_CLK_EN
75#define PC_RSA_RST_REG 0x600C0024u // SYSTEM_PERIP_RST_EN1_REG
76#define PC_RSA_RST_BIT (1u << 3) // SYSTEM_CRYPTO_RSA_RST
77#define PC_RSA_HOLD_REG 0x600C0024u // SYSTEM_PERIP_RST_EN1_REG (same reg)
78#define PC_RSA_HOLD_CLEAR (1u << 4) // SYSTEM_CRYPTO_DS_RST
79#define PC_RSA_HAS_PD 1
80#define PC_RSA_PD_REG 0x600C0040u // SYSTEM_RSA_PD_CTRL_REG
81#define PC_RSA_PD_UP_CLEAR (1u << 0) // clear SYSTEM_RSA_MEM_PD to power up
82#define PC_RSA_PD_DOWN_BIT (1u << 0) // SYSTEM_RSA_MEM_PD reads 1 when powered down
83#elif defined(CONFIG_IDF_TARGET_ESP32S2) && CONFIG_IDF_TARGET_ESP32S2 // hw_ver1, DPORT clock/reset
84#define PC_RSA_BASE 0x6003C000u
85#define PC_RSA_CLK_REG 0x3F4C0044u // DPORT_PERIP_CLK_EN1_REG
86#define PC_RSA_CLK_BIT (1u << 3) // DPORT_CRYPTO_RSA_CLK_EN
87#define PC_RSA_RST_REG 0x3F4C004Cu // DPORT_PERIP_RST_EN1_REG
88#define PC_RSA_RST_BIT (1u << 3) // DPORT_CRYPTO_RSA_RST
89#define PC_RSA_HOLD_REG 0x3F4C004Cu // DPORT_PERIP_RST_EN1_REG (same reg)
90#define PC_RSA_HOLD_CLEAR (1u << 4) // DPORT_CRYPTO_DS_RST
91#define PC_RSA_HAS_PD 1
92#define PC_RSA_PD_REG 0x3F4C0068u // DPORT_RSA_PD_CTRL_REG
93#define PC_RSA_PD_UP_CLEAR (1u << 0) // clear DPORT_RSA_MEM_PD to power up
94#define PC_RSA_PD_DOWN_BIT (1u << 0) // DPORT_RSA_MEM_PD reads 1 when powered down
95#elif defined(CONFIG_IDF_TARGET_ESP32C3) && CONFIG_IDF_TARGET_ESP32C3 // hw_ver1, SYSTEM clock/reset
96#define PC_RSA_BASE 0x6003C000u
97#define PC_RSA_CLK_REG 0x600C0014u // SYSTEM_PERIP_CLK_EN1_REG
98#define PC_RSA_CLK_BIT (1u << 3) // SYSTEM_CRYPTO_RSA_CLK_EN
99#define PC_RSA_RST_REG 0x600C001Cu // SYSTEM_PERIP_RST_EN1_REG
100#define PC_RSA_RST_BIT (1u << 3) // SYSTEM_CRYPTO_RSA_RST
101#define PC_RSA_HOLD_REG 0x600C001Cu // SYSTEM_PERIP_RST_EN1_REG (same reg)
102#define PC_RSA_HOLD_CLEAR (1u << 4) // SYSTEM_CRYPTO_DS_RST
103#define PC_RSA_HAS_PD 1
104#define PC_RSA_PD_REG 0x600C0038u // SYSTEM_RSA_PD_CTRL_REG
105#define PC_RSA_PD_UP_CLEAR (1u << 0) // clear SYSTEM_RSA_MEM_PD to power up
106#define PC_RSA_PD_DOWN_BIT (1u << 0) // SYSTEM_RSA_MEM_PD reads 1 when powered down
107#elif defined(CONFIG_IDF_TARGET_ESP32P4) && CONFIG_IDF_TARGET_ESP32P4 // hw_ver3, HP_SYS_CLKRST clock/reset
108#define PC_RSA_BASE 0x50092000u
109#define PC_RSA_CLK_REG 0x500E60A8u // HP_SYS_CLKRST_PERI_CLK_CTRL25_REG
110#define PC_RSA_CLK_BIT (1u << 18) // REG_CRYPTO_RSA_CLK_EN
111#define PC_RSA_RST_REG 0x500E60C8u // HP_SYS_CLKRST_HP_RST_EN2_REG
112#define PC_RSA_RST_BIT (1u << 21) // REG_RST_EN_RSA
113#define PC_RSA_HOLD_REG 0x500E60C8u // HP_SYS_CLKRST_HP_RST_EN2_REG (same reg)
114#define PC_RSA_HOLD_CLEAR ((1u << 14) | (1u << 17) | (1u << 20)) // parent-crypto | DS | ECDSA
115#define PC_RSA_HAS_PD 0 // P4 RSA memory is always powered
116#elif defined(CONFIG_IDF_TARGET_ESP32C6) && CONFIG_IDF_TARGET_ESP32C6 // hw_ver3 regs, PCR clock/reset
117#define PC_RSA_BASE 0x6008A000u
118#define PC_RSA_CLK_REG 0x600960D0u // PCR_RSA_CONF_REG
119#define PC_RSA_CLK_BIT (1u << 0) // PCR_RSA_CLK_EN
120#define PC_RSA_RST_REG 0x600960D0u // PCR_RSA_CONF_REG (same reg)
121#define PC_RSA_RST_BIT (1u << 1) // PCR_RSA_RST_EN
122#define PC_RSA_HOLD_REG 0x600960E0u // PCR_DS_CONF_REG (a DIFFERENT register)
123#define PC_RSA_HOLD_CLEAR (1u << 1) // PCR_DS_RST_EN
124#define PC_RSA_HAS_PD 1
125#define PC_RSA_PD_REG 0x600960D4u // PCR_RSA_PD_CTRL_REG
126#define PC_RSA_PD_UP_CLEAR ((1u << 0) | (1u << 2)) // clear PCR_RSA_MEM_PD + PCR_RSA_MEM_FORCE_PD to power up
127#define PC_RSA_PD_DOWN_BIT (1u << 0) // PCR_RSA_MEM_PD reads 1 when powered down
128#elif defined(CONFIG_IDF_TARGET_ESP32C5) && CONFIG_IDF_TARGET_ESP32C5 // hw_ver3 regs, PCR (DS + ECDSA resets)
129#define PC_RSA_BASE 0x6008A000u
130#define PC_RSA_CLK_REG 0x600960D4u // PCR_RSA_CONF_REG
131#define PC_RSA_CLK_BIT (1u << 0) // PCR_RSA_CLK_EN
132#define PC_RSA_RST_REG 0x600960D4u // PCR_RSA_CONF_REG (same reg)
133#define PC_RSA_RST_BIT (1u << 1) // PCR_RSA_RST_EN
134#define PC_RSA_HOLD_REG 0x600960E4u // PCR_DS_CONF_REG
135#define PC_RSA_HOLD_CLEAR (1u << 1) // PCR_DS_RST_EN
136#define PC_RSA_HOLD2_REG 0x600960ECu // PCR_ECDSA_CONF_REG (a second, separate register)
137#define PC_RSA_HOLD2_CLEAR (1u << 1) // PCR_ECDSA_RST_EN
138#define PC_RSA_HAS_PD 1
139#define PC_RSA_PD_REG 0x600960D8u // PCR_RSA_PD_CTRL_REG
140#define PC_RSA_PD_UP_CLEAR ((1u << 0) | (1u << 2)) // clear PCR_RSA_MEM_PD + PCR_RSA_MEM_FORCE_PD to power up
141#define PC_RSA_PD_DOWN_BIT (1u << 0) // PCR_RSA_MEM_PD reads 1 when powered down
142#elif defined(CONFIG_IDF_TARGET_ESP32H2) && CONFIG_IDF_TARGET_ESP32H2 // hw_ver3 regs, PCR (DS + ECDSA resets)
143#define PC_RSA_BASE 0x6008A000u
144#define PC_RSA_CLK_REG 0x600960CCu // PCR_RSA_CONF_REG
145#define PC_RSA_CLK_BIT (1u << 0) // PCR_RSA_CLK_EN
146#define PC_RSA_RST_REG 0x600960CCu // PCR_RSA_CONF_REG (same reg)
147#define PC_RSA_RST_BIT (1u << 1) // PCR_RSA_RST_EN
148#define PC_RSA_HOLD_REG 0x600960DCu // PCR_DS_CONF_REG
149#define PC_RSA_HOLD_CLEAR (1u << 1) // PCR_DS_RST_EN
150#define PC_RSA_HOLD2_REG 0x600960E4u // PCR_ECDSA_CONF_REG (a second, separate register)
151#define PC_RSA_HOLD2_CLEAR (1u << 1) // PCR_ECDSA_RST_EN
152#define PC_RSA_HAS_PD 1
153#define PC_RSA_PD_REG 0x600960D0u // PCR_RSA_PD_CTRL_REG
154#define PC_RSA_PD_UP_CLEAR ((1u << 0) | (1u << 2)) // clear PCR_RSA_MEM_PD + PCR_RSA_MEM_FORCE_PD to power up
155#define PC_RSA_PD_DOWN_BIT (1u << 0) // PCR_RSA_MEM_PD reads 1 when powered down
156#else
157#error "esp_crypto_hal: PC_RSA_MODMUL_HW is on but no RSA register map for this die - add its bases/bits"
158#endif
159
160// ── RSA block register offsets (identical across hw_ver1 and hw_ver3) ────────────────────────────────────
161#define PC_RSA_MEM_M (PC_RSA_BASE + 0x000u) // operand M block (words)
162#define PC_RSA_MEM_Z (PC_RSA_BASE + 0x200u) // result Z block (also seeded with R^2 mod m)
163#define PC_RSA_MEM_Y (PC_RSA_BASE + 0x400u) // operand Y block
164#define PC_RSA_MEM_X (PC_RSA_BASE + 0x600u) // operand X block
165#define PC_RSA_MPRIME (PC_RSA_BASE + 0x800u) // Montgomery m' (mod 2^32)
166#define PC_RSA_MODE (PC_RSA_BASE + 0x804u) // operand length in words, minus 1
167#define PC_RSA_CLEAN (PC_RSA_BASE + 0x808u) // memory-init: reads non-zero while initializing, 0 when ready
168#define PC_RSA_START (PC_RSA_BASE + 0x810u) // write 1 to start the modular multiply
169#define PC_RSA_DONE (PC_RSA_BASE + 0x818u) // reads 1 once the op is complete (interrupt/idle bit)
170#define PC_RSA_INTCLR (PC_RSA_BASE + 0x81Cu) // write 1 to clear the completion flag
171#define PC_RSA_INTENA (PC_RSA_BASE + 0x82Cu) // completion-interrupt enable (we poll: keep 0)
172
173/**
174 * @brief Acquire the RSA accelerator for a run of modular multiplies (PC lock + direct-register bring-up).
175 * @note Bracket every batch of @ref pc_rsa_modmul with acquire/release. Implemented in esp_crypto_hal.cpp
176 * (the exclusivity mutex must be one global instance, so it cannot live in this header). Poll-only.
177 */
178void pc_rsa_hw_acquire(void);
179
180/** @brief Release the RSA accelerator (drop the PC lock). */
181void pc_rsa_hw_release(void);
182
183/**
184 * @brief `z = x*y mod m` (@p words limbs) on the RSA accelerator. Requires @ref pc_rsa_hw_acquire first.
185 * @param z result, @p words little-endian limbs (safe to alias @p x or @p y).
186 * @param x,y operands, canonical (< m).
187 * @param m the modulus (canonical @p words limbs).
188 * @param mprime Montgomery m' = -m^-1 mod 2^32.
189 * @param rinv R^2 mod m; preloaded into the result block so MODMULT returns the plain residue x*y mod m
190 * rather than a Montgomery form. Output is canonical (< m).
191 */
192static inline void pc_rsa_modmul(uint32_t *z, const uint32_t *x, const uint32_t *y, const uint32_t *m, uint32_t mprime,
193 const uint32_t *rinv, unsigned words)
194{
195 volatile uint32_t *M = (volatile uint32_t *)(uintptr_t)PC_RSA_MEM_M;
196 volatile uint32_t *X = (volatile uint32_t *)(uintptr_t)PC_RSA_MEM_X;
197 volatile uint32_t *Y = (volatile uint32_t *)(uintptr_t)PC_RSA_MEM_Y;
198 volatile uint32_t *Z = (volatile uint32_t *)(uintptr_t)PC_RSA_MEM_Z;
199 PC_HW_REG(PC_RSA_MODE) = words - 1u; // mode = words - 1
200 PC_HW_REG(PC_RSA_MPRIME) = mprime;
201 for (unsigned i = 0; i < words; i++)
202 {
203 M[i] = m[i];
204 X[i] = x[i];
205 Y[i] = y[i];
206 Z[i] = rinv[i]; // R^2 mod m in the result block -> plain (non-Montgomery) output
207 }
208 PC_HW_REG(PC_RSA_INTCLR) = 1u; // clear any stale done flag before starting
209 PC_HW_REG(PC_RSA_START) = 1u;
210 while (PC_HW_REG(PC_RSA_DONE) == 0u) // wait until the done bit reads 1
211 {
212 }
213 PC_HW_REG(PC_RSA_INTCLR) = 1u;
214 for (unsigned i = 0; i < words; i++)
215 {
216 z[i] = Z[i];
217 }
218}
219
220#endif // PC_RSA_MODMUL_HW
221#endif // PROTOCORE_ESP_CRYPTO_HAL_H