Add TI C2000 AESA hardware AES port and fix CHAR_BIT!=8 AES defects

pull/11202/head
David Garske 2026-08-13 10:27:23 -07:00
parent 01426d0fdb
commit a5085bc13d
9 changed files with 860 additions and 35 deletions

View File

@ -857,6 +857,11 @@ WOLFSSL_BIGINT_TYPES
WOLFSSL_BIO_NO_FLOW_STATS
WOLFSSL_BUILD_MSG_NO_ZERO_COPY
WOLFSSL_BYTESWAP32_ASM
WOLFSSL_C2000_AES
WOLFSSL_C2000_AES_BASE
WOLFSSL_C2000_AES_NO_LOCK
WOLFSSL_C2000_AES_SS_BASE
WOLFSSL_C2000_DEVID
WOLFSSL_CAAM_BLACK_KEY_AESCCM
WOLFSSL_CAAM_BLACK_KEY_SM
WOLFSSL_CAAM_NO_BLACK_KEY

View File

@ -69,6 +69,120 @@ TI `cl2000` (C28x) miscompiles a couple of ML-DSA 32-bit reductions on this
With both, ML-DSA builds at full `-O2` on the C28x with no per-file overrides.
## Hardware AES (AESA)
The F28P55x/F28P65x carry an "AESA" accelerator (a TI EIP-120t instance) at
`0x00042000` supporting ECB/CBC/CTR/CFB/GCM/CCM with 128/192/256-bit keys.
wolfCrypt drives it through the **crypto-callback** framework rather than by
replacing `wolfcrypt/src/aes.c`:
- `wolfcrypt/src/port/ti/ti-c2000-aes.c` + `wolfssl/wolfcrypt/port/ti/ti-c2000.h`,
gated on `WOLFSSL_C2000_AES` (which also needs `WOLF_CRYPTO_CB`).
- `wc_C2000_Init(devId)` enables/resets the block and registers the callback.
It must be called **after `wolfCrypt_Init()`** -- that is what marks the
device table slots `INVALID_DEVID`, and registration claims one of those.
- A context opts in with `wc_AesInit(&aes, NULL, WOLFSSL_C2000_DEVID)`; one
initialised with `INVALID_DEVID` stays pure software. Software AES remains
compiled in, so a single image can run identical vectors through both paths
and compare -- which is how this port is validated.
- Anything the hardware cannot do returns `CRYPTOCB_UNAVAILABLE` and falls
through to software: non-block-multiple CBC (ciphertext stealing), key
lengths other than 16/24/32, and every mode outside ECB/CBC/CTR.
Phase 1 covers ECB, CBC and CTR. GCM/CCM/CMAC remain software for now.
### Measured throughput (LAUNCHXL-F28P55X at 150 MHz)
`benchmark` with `WC_USE_DEVID` pointing at the AESA device, so it emits paired
`SW`/`HW` rows:
| Operation | Software | AESA | Speedup |
|---|---|---|---|
| AES-128-ECB encrypt | 471 KiB/s | 2.37 MiB/s | 5.2x |
| AES-256-ECB encrypt | 377 KiB/s | 2.32 MiB/s | 6.3x |
| AES-128-CBC encrypt | 405 KiB/s | 2.36 MiB/s | 6.0x |
| AES-128-CBC decrypt | 388 KiB/s | 2.34 MiB/s | 6.2x |
| AES-256-CBC encrypt | 333 KiB/s | 2.31 MiB/s | 7.1x |
| AES-256-CBC decrypt | 322 KiB/s | 2.29 MiB/s | 7.3x |
| AES-128-CTR | 408 KiB/s | 1.45 MiB/s | 3.6x |
| AES-256-CTR | 335 KiB/s | 1.44 MiB/s | 4.4x |
Hardware throughput is essentially key-length independent, as expected for a
pipelined block engine -- so the speedup grows with key size, where software
pays for more rounds. CTR lands lower than ECB/CBC because the port does the
counter increment and the keystream XOR in software (see above); it is still
the mode that gains least in relative terms but it is unambiguously worth
offloading. AES-GCM moves only from ~32 to ~34 KiB/s: only its internal ECB
calls reach the accelerator, and the `GCM_SMALL` byte-wise GHASH dominates.
Doing GCM properly means using the block's own GCM mode, which is phase 2.
### Build overrides
All `#ifndef`-guarded in `wolfssl/wolfcrypt/port/ti/ti-c2000.h`:
| Macro | Default | Purpose |
|---|---|---|
| `WOLFSSL_C2000_DEVID` | `0x2000` | devId passed to `wc_AesInit()` and `wc_CryptoCb_RegisterDevice()` |
| `WOLFSSL_C2000_AES_BASE` | `0x00042000` | AESA register base (`AESA_BASE`) |
| `WOLFSSL_C2000_AES_SS_BASE` | `0x00042C00` | AESA wrapper base (`AESA_SS_BASE`) |
| `WOLFSSL_C2000_AES_NO_LOCK` | off | Assert an external lock instead of requiring `SINGLE_THREADED` |
The two base addresses are defaulted in the port rather than taken from a
device header, so the same source builds against any C2000 part that places
the block elsewhere.
### Two things the 16-bit byte forces
**Octet packing.** C2000Ware's AES API is `uint32_t*`-based. On the C28x a
`byte` buffer holds one octet per 16-bit cell and `sizeof(word32)` is 2, so the
`(uint32_t*)in` cast the TivaWare port (`ti-aes.c`) uses is wrong here. Every
transfer is staged through a local `uint32_t` block using the packing driverlib
actually expects, confirmed against its own vectors
(`driverlib/f28p55x/examples/aes/aes_ex1_ecb_encrypt.c` writes the FIPS-197 key
`2b7e1516...` as `{0x16157e2b, ...}`):
```
word[i] = b[4i] | (b[4i+1] << 8) | (b[4i+2] << 16) | (b[4i+3] << 24)
```
little-endian octets within each word, words in natural order. The
word-index reversal inside `AES_writeDataBlocking()` is internal to driverlib
and must not be compensated for. The port packs and unpacks locally
(`c2000_WordsFromOctets()` / `c2000_OctetsFromWords()`), staging through
`uint32_t` rather than wolfSSL's `word32`: `word32` is only 32-bit under
`WC_16BIT_CPU`, while driverlib writes `uint32_t` either way, so a `word32`
staging array would be half the size the hardware fills.
**The hardware CTR counter does not match wolfCrypt's.** Measured on a
LAUNCHXL-F28P55X: with `AES_OPMODE_CTR` + `AES_CTR_WIDTH_128BIT` the first
block matches NIST SP800-38A F.5.1, but later blocks diverge from software as
soon as an increment carries across an octet boundary (the F.5 counter starts
at `...fe ff`, so block 2 already does). The block's 128-bit counter increment
therefore disagrees with `IncrementAesCounter()`, which carries through all 16
octets. The port instead runs the accelerator in **ECB** mode and keeps the
counter in software: identical hardware block-operation count, correct by
construction.
Both traps are silent -- the first block is right either way, which is exactly
why the KAT harness checks multi-block, split-call and in-place cases.
### Chaining state
`aes->reg` is updated in software (last ciphertext block on encrypt, a copy of
the last input block saved *before* processing on decrypt, so in-place calls
work). `AES_readInitializationVector()` is deliberately not used: the
`IV_IN_OUT` registers only hold the saved context when `CTRL.SAVE_CONTEXT` is
set, which `AES_configureModule()` does not set, and driverlib's reader does
not poll `CTRL.SVCTXTRDY` the way `AES_readTag()` does.
### Threading
The AESA block is a single shared resource and the port reloads key, IV and
mode on every operation, so it is re-entrant across `Aes` contexts but **not**
across preemption or an ISR. `ti-c2000.h` therefore `#error`s unless
`SINGLE_THREADED` is defined, or `WOLFSSL_C2000_AES_NO_LOCK` asserts that an
external lock provides the guarantee.
## Enabling on your build
Define a user-settings header (see `IDE/C2000/user_settings.h` for a

View File

@ -30,7 +30,10 @@ fi
OUT=$(mktemp -d)
trap 'rm -rf "$OUT"' EXIT
INCS="-I$CGT_ROOT/include -I$WOLFROOT -I$SELF_DIR"
# $SELF_DIR before $WOLFROOT: wolfSSL's documented user_settings.h workflow
# puts one at the repo root, and if $WOLFROOT came first that copy would shadow
# this guard's config and silently compile a different build.
INCS="-I$CGT_ROOT/include -I$SELF_DIR -I$WOLFROOT"
CFLAGS="-v28 --abi=eabi --float_support=fpu32 --tmu_support=tmu1 -O2 \
--define=WOLFSSL_USER_SETTINGS --display_error_number --diag_warning=225"
@ -42,7 +45,7 @@ CFLAGS="-v28 --abi=eabi --float_support=fpu32 --tmu_support=tmu1 -O2 \
# covered by the on-target example build, not by this minimal guard.
SRCS="error wc_port memory logging misc coding \
sha sha256 sha512 sha3 wc_mldsa random ecc sp_int sp_c32 \
aes cmac chacha poly1305 \
aes cmac chacha poly1305 cryptocb \
curve25519 ed25519 fe_operations ge_operations \
curve448 ed448 fe_448 ge_448"
@ -60,6 +63,30 @@ for s in $SRCS; do
fi
done
# The AESA hardware-AES port needs C2000Ware driverlib headers, which CI does
# not download, so it is an opt-in extra leg: set C2000WARE to a C2000Ware
# install to include it.
if [ -n "${C2000WARE:-}" ]; then
DRV="$C2000WARE/driverlib/f28p55x/driverlib"
printf 'CC port/ti/ti-c2000-aes.c ... '
if "$CL" $CFLAGS $INCS -I"$DRV" \
-I"$C2000WARE/device_support/f28p55x/common/include" \
-I"$C2000WARE/device_support/f28p55x/headers/include" \
--define=WOLF_CRYPTO_CB --define=WOLFSSL_C2000_AES \
--compile_only --skip_assembler \
--asm_directory="$OUT" --obj_directory="$OUT" \
"$WOLFROOT/wolfcrypt/src/port/ti/ti-c2000-aes.c" \
> "$OUT/ti-c2000-aes.log" 2>&1; then
echo "ok"
else
echo "FAIL"
cat "$OUT/ti-c2000-aes.log"
rc=1
fi
else
echo "SKIP port/ti/ti-c2000-aes.c (set C2000WARE to include it)"
fi
if [ "$rc" -eq 0 ]; then
echo "TI C2000 compile-only guard: PASS"
else

View File

@ -17,6 +17,13 @@
#define WOLFSSL_NO_ASM
#define NO_INLINE
#define SINGLE_THREADED
/* C28x has a 16-bit int. Without this word32 is `unsigned int` (16 bits) and
* every 32-bit crypto value silently truncates. long is 32-bit and long long
* 64-bit on this toolchain. */
#define WC_16BIT_CPU
#define SIZEOF_LONG 4
#define SIZEOF_LONG_LONG 8
#define NO_FILESYSTEM
#define NO_WOLFSSL_DIR
#define NO_MAIN_DRIVER
@ -47,6 +54,15 @@
#define WOLFSSL_AES_SIV
#define WOLFSSL_AES_EAX
#define WOLFSSL_AES_DIRECT
#define HAVE_AES_ECB
/* Crypto callbacks, so cryptocb.c is compile-guarded here too. This is what
* the AESA hardware-AES port (WOLFSSL_C2000_AES, wolfcrypt/src/port/ti/
* ti-c2000-aes.c) plugs into. WOLFSSL_C2000_AES itself is deliberately NOT
* set: it needs C2000Ware driverlib headers, which this hardware-free guard
* does not have. compile.sh builds that file as a separate opt-in leg when
* C2000WARE is set in the environment. */
#define WOLF_CRYPTO_CB
/* ChaCha20-Poly1305 (chunk size, keystream and Poly1305 length octet I/O) */
#define HAVE_CHACHA

View File

@ -8005,7 +8005,10 @@ int wc_AesCbcEncrypt(Aes* aes, byte* out, const byte* in, word32 sz)
/* in network byte order so start at end and work back */
int i;
for (i = WC_AES_BLOCK_SIZE - 1; i >= 0; i--) {
if (++inOutCtr[i]) /* we're done unless we overflow */
/* WC_OCTET, not a bare ++: where CHAR_BIT != 8 a byte cell
* holds 0x100 and never wraps, so the carry is lost. */
inOutCtr[i] = WC_OCTET(inOutCtr[i] + 1);
if (inOutCtr[i] != 0) /* we're done unless we overflow */
return;
}
}
@ -8362,7 +8365,9 @@ static WC_INLINE void IncCtr(byte* ctr, word32 ctrSz)
{
int i;
for (i = (int)ctrSz - 1; i >= 0; i--) {
if (++ctr[i])
/* See IncrementAesCounter() on why this masks to an octet. */
ctr[i] = WC_OCTET(ctr[i] + 1);
if (ctr[i] != 0)
break;
}
}
@ -8468,7 +8473,9 @@ static WC_INLINE void IncrementGcmCounter(byte* inOutCtr)
/* in network byte order so start at end and work back */
for (i = WC_AES_BLOCK_SIZE - 1; i >= WC_AES_BLOCK_SIZE - CTR_SZ; i--) {
if (++inOutCtr[i]) /* we're done unless we overflow */
/* See IncrementAesCounter() on why this masks to an octet. */
inOutCtr[i] = WC_OCTET(inOutCtr[i] + 1);
if (inOutCtr[i] != 0) /* we're done unless we overflow */
return;
}
}
@ -8492,19 +8499,21 @@ static WC_INLINE void IncrementGcmCounter(byte* inOutCtr)
static WC_INLINE void FlattenSzInBits(byte* buf, word32 sz)
{
/* Multiply the sz by 8 */
word32 szHi = (sz >> (8*sizeof(sz) - 3));
/* Multiply the sz by 8. CHAR_BIT * sizeof, not 8 * sizeof: sizeof counts
* cells, so the latter is a 16-bit width where CHAR_BIT == 16. */
word32 szHi = (sz >> (CHAR_BIT * sizeof(sz) - 3));
sz <<= 3;
/* copy over the words of the sz into the destination buffer */
buf[0] = (byte)(szHi >> 24);
buf[1] = (byte)(szHi >> 16);
buf[2] = (byte)(szHi >> 8);
buf[3] = (byte)szHi;
buf[4] = (byte)(sz >> 24);
buf[5] = (byte)(sz >> 16);
buf[6] = (byte)(sz >> 8);
buf[7] = (byte)sz;
/* WC_OCTET, not (byte): the cast keeps the full cell where CHAR_BIT != 8,
* so a 60-octet ciphertext (480 bits) would store 0x1E0 in buf[7]. */
buf[0] = WC_OCTET(szHi >> 24);
buf[1] = WC_OCTET(szHi >> 16);
buf[2] = WC_OCTET(szHi >> 8);
buf[3] = WC_OCTET(szHi);
buf[4] = WC_OCTET(sz >> 24);
buf[5] = WC_OCTET(sz >> 16);
buf[6] = WC_OCTET(sz >> 8);
buf[7] = WC_OCTET(sz);
}
@ -10533,9 +10542,9 @@ void GHASH(Gcm* gcm, const byte* a, word32 aSz, const byte* c,
word32 len[4];
/* Lengths are in bytes. Convert to bits. */
len[0] = (aSz >> (8*sizeof(aSz) - 3));
len[0] = (aSz >> (CHAR_BIT*sizeof(aSz) - 3));
len[1] = aSz << 3;
len[2] = (cSz >> (8*sizeof(cSz) - 3));
len[2] = (cSz >> (CHAR_BIT*sizeof(cSz) - 3));
len[3] = cSz << 3;
x[0] ^= len[0];
@ -10594,9 +10603,9 @@ void GHASH(Gcm* gcm, const byte* a, word32 aSz, const byte* c,
word32 len[4]; \
word32* x = (word32*)AES_TAG(aes); \
word32* h = (word32*)aes->gcm.H; \
len[0] = (aes->aSz >> (8*sizeof(aes->aSz) - 3)); \
len[0] = (aes->aSz >> (CHAR_BIT*sizeof(aes->aSz) - 3)); \
len[1] = aes->aSz << 3; \
len[2] = (aes->cSz >> (8*sizeof(aes->cSz) - 3)); \
len[2] = (aes->cSz >> (CHAR_BIT*sizeof(aes->cSz) - 3)); \
len[3] = aes->cSz << 3; \
x[0] ^= len[0]; \
x[1] ^= len[1]; \
@ -10643,9 +10652,9 @@ void GHASH(Gcm* gcm, const byte* a, word32 aSz, const byte* c,
word32 len[4]; \
word32* x = (word32*)AES_TAG(aes); \
word32* h = (word32*)aes->gcm.H; \
len[0] = (aes->aSz >> (8*sizeof(aes->aSz) - 3)); \
len[0] = (aes->aSz >> (CHAR_BIT*sizeof(aes->aSz) - 3)); \
len[1] = aes->aSz << 3; \
len[2] = (aes->cSz >> (8*sizeof(aes->cSz) - 3)); \
len[2] = (aes->cSz >> (CHAR_BIT*sizeof(aes->cSz) - 3)); \
len[3] = aes->cSz << 3; \
x[0] ^= len[0]; \
x[1] ^= len[1]; \
@ -15497,20 +15506,22 @@ static WARN_UNUSED_RESULT int roll_auth(
word32 remainder;
int ret;
/* encode the length in */
/* encode the length in. WC_OCTET, not (byte): the cast keeps the whole
* cell where CHAR_BIT != 8, so any length above 0xFF would XOR stray bits
* into the CBC-MAC input block. */
if (inSz <= 0xFEFF) {
authLenSz = 2;
out[0] ^= (byte)(inSz >> 8);
out[1] ^= (byte)inSz;
out[0] ^= WC_OCTET(inSz >> 8);
out[1] ^= WC_OCTET(inSz);
}
else {
authLenSz = 6;
out[0] ^= 0xFF;
out[1] ^= 0xFE;
out[2] ^= (byte)(inSz >> 24);
out[3] ^= (byte)(inSz >> 16);
out[4] ^= (byte)(inSz >> 8);
out[5] ^= (byte)inSz;
out[2] ^= WC_OCTET(inSz >> 24);
out[3] ^= WC_OCTET(inSz >> 16);
out[4] ^= WC_OCTET(inSz >> 8);
out[5] ^= WC_OCTET(inSz);
}
/* Note, the protocol handles auth data up to 2^64, but we are
* using 32-bit sizes right now, so the bigger data isn't handled
@ -15549,7 +15560,11 @@ static WC_INLINE void AesCcmCtrInc(byte* B, word32 lenSz)
word32 i;
for (i = 0; i < lenSz; i++) {
if (++B[WC_AES_BLOCK_SIZE - 1 - i] != 0) return;
/* See IncrementAesCounter(): a bare ++byte leaves 0x100 in the cell
* and never carries. */
B[WC_AES_BLOCK_SIZE - 1 - i] =
WC_OCTET(B[WC_AES_BLOCK_SIZE - 1 - i] + 1);
if (B[WC_AES_BLOCK_SIZE - 1 - i] != 0) return;
}
}
@ -17104,13 +17119,15 @@ static void shiftLeftArray(byte* ary, byte shift)
ary[i] = 0;
}
else {
/* shifting over by 7 or less bits */
/* shifting over by 7 or less bits. WC_OCTET on the stores: a (byte)
* cast does not drop bits shifted past bit 7 where CHAR_BIT != 8, so
* cells would exceed 0xFF and corrupt the feedback register. */
for (i = 0; i < WC_AES_BLOCK_SIZE - 1; i++) {
byte carry = (byte)(ary[i+1] & (0XFF << (WOLFSSL_BIT_SIZE - shift)));
carry = (byte)(carry >> (WOLFSSL_BIT_SIZE - shift));
ary[i] = (byte)((ary[i] << shift) + carry);
ary[i] = WC_OCTET((ary[i] << shift) + carry);
}
ary[i] = (byte)(ary[i] << shift);
ary[i] = WC_OCTET(ary[i] << shift);
}
}
@ -17490,7 +17507,9 @@ static WC_INLINE void IncrementKeyWrapCounter(byte* inOutCtr)
/* in network byte order so start at end and work back */
for (i = KEYWRAP_BLOCK_SIZE - 1; i >= 0; i--) {
if (++inOutCtr[i]) /* we're done unless we overflow */
/* See IncrementAesCounter() on why this masks to an octet. */
inOutCtr[i] = WC_OCTET(inOutCtr[i] + 1);
if (inOutCtr[i] != 0) /* we're done unless we overflow */
return;
}
}
@ -17501,7 +17520,10 @@ static WC_INLINE void DecrementKeyWrapCounter(byte* inOutCtr)
int i;
for (i = KEYWRAP_BLOCK_SIZE - 1; i >= 0; i--) {
if (--inOutCtr[i] != 0xFF) /* we're done unless we underflow */
/* Where CHAR_BIT != 8 a bare --byte underflows 0x00 to 0xFFFF, not
* 0xFF, so the borrow is lost. */
inOutCtr[i] = WC_OCTET(inOutCtr[i] - 1);
if (inOutCtr[i] != 0xFF) /* we're done unless we underflow */
return;
}
}

View File

@ -81,6 +81,7 @@ EXTRA_DIST += wolfcrypt/src/port/ti/ti-aes.c \
wolfcrypt/src/port/ti/ti-des3.c \
wolfcrypt/src/port/ti/ti-hash.c \
wolfcrypt/src/port/ti/ti-ccm.c \
wolfcrypt/src/port/ti/ti-c2000-aes.c \
wolfcrypt/src/port/pic32/pic32mz-crypt.c \
wolfcrypt/src/port/nrf51.c \
wolfcrypt/src/port/aria/aria-crypt.c \

View File

@ -0,0 +1,542 @@
/* port/ti/ti-c2000-aes.c
*
* Copyright (C) 2006-2026 wolfSSL Inc.
*
* This file is part of wolfSSL.
*
* wolfSSL is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* wolfSSL is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA
*/
/* AES offload to the TI C2000 "AESA" block (EIP-120t) via crypto callbacks.
* Model and build options: wolfssl/wolfcrypt/port/ti/ti-c2000.h.
*
* Octet/word contract, confirmed against driverlib's own vectors in
* driverlib/f28p55x/examples/aes/aes_ex1_ecb_encrypt.c (FIPS-197 key
* 2b7e1516... is written {0x16157e2b, ...}): word j holds octets 4j..4j+3,
* little-endian within the word, index increasing with octet offset. The
* register-index reversal inside AES_writeDataBlocking() is internal to
* driverlib - do not compensate for it.
*
* This matters because CHAR_BIT == 16 here: a byte buffer is one octet per
* 16-bit cell and sizeof(word32) is 2, so the (uint32_t*) casts the TivaWare
* port uses are wrong. Every transfer stages through a local word32 block,
* which also makes alignment and short trailing blocks non-issues.
*
* Build options (all #ifndef-guarded in ti-c2000.h, see IDE/C2000/README.md):
* WOLFSSL_C2000_AES enable this port (needs WOLF_CRYPTO_CB)
* WOLFSSL_C2000_DEVID devId for wc_AesInit()/RegisterDevice (0x2000)
* WOLFSSL_C2000_AES_BASE AESA register base (0x00042000)
* WOLFSSL_C2000_AES_SS_BASE AESA wrapper base (0x00042C00)
* WOLFSSL_C2000_AES_NO_LOCK assert an external lock instead of requiring
* SINGLE_THREADED
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <wolfssl/wolfcrypt/settings.h>
#if defined(WOLFSSL_C2000_AES) && !defined(NO_AES)
#if !defined(WOLF_CRYPTO_CB)
#error "WOLFSSL_C2000_AES requires WOLF_CRYPTO_CB"
#endif
/* uint32_t/uint64_t are used directly below to match the driverlib API. Pull
* them in here rather than relying on the C2000Ware headers to provide them,
* so the port does not depend on include order. */
#include <stdint.h>
#include <wolfssl/wolfcrypt/aes.h>
#include <wolfssl/wolfcrypt/cryptocb.h>
#include <wolfssl/wolfcrypt/error-crypt.h>
#include <wolfssl/wolfcrypt/port/ti/ti-c2000.h>
#ifdef NO_INLINE
#include <wolfssl/wolfcrypt/misc.h>
#else
#define WOLFSSL_MISC_INCLUDED
#include <wolfcrypt/src/misc.c>
#endif
/* C2000Ware driverlib. No name collisions with wolfssl/wolfcrypt/aes.h:
* driverlib prefixes AES_DIRECTION_/AES_KEY_SIZE_/AES_OPMODE_, wolfCrypt uses
* AES_ENCRYPTION/AES_128_KEY_SIZE. */
#include "aes.h"
#include "sysctl.h"
/* Words per AES block. Not sizeof-based: sizeof(word32) is 2 here, so
* WC_AES_BLOCK_SIZE / sizeof(word32) would be 8. */
#define C2000_BLOCK_WORDS 4
/* Largest key in words (AES-256). */
#define C2000_MAX_KEY_WORDS 8
/* Pack octets into words, little-endian within each word.
*
* Deliberately uint32_t, not wolfSSL's word32: word32 is only 32-bit under
* WC_16BIT_CPU, and driverlib writes uint32_t either way - a word32 staging
* array would be half the size the hardware fills. Accumulates with <<= 8
* because cl2000 miscompiles a single (uint32_t)octet << 24 as a 16-bit
* shift (see misc.c WordsFromBytesBE32). */
static void c2000_WordsFromOctets(uint32_t* w, const byte* b, word32 wordCnt)
{
word32 i;
uint32_t r;
for (i = 0; i < wordCnt; i++) {
r = (uint32_t)(b[(i * 4) + 3] & 0xFF); r <<= 8;
r |= (uint32_t)(b[(i * 4) + 2] & 0xFF); r <<= 8;
r |= (uint32_t)(b[(i * 4) + 1] & 0xFF); r <<= 8;
r |= (uint32_t)(b[(i * 4) + 0] & 0xFF);
w[i] = r;
}
}
static void c2000_OctetsFromWords(byte* b, const uint32_t* w, word32 byteCnt)
{
word32 i;
for (i = 0; i < byteCnt; i++) {
b[i] = WC_OCTET(w[i >> 2] >> ((i & 0x3) * 8));
}
}
#define C2000_WORDS_FROM_OCTETS(w, b, n) c2000_WordsFromOctets((w), (b), (n))
#define C2000_OCTETS_FROM_WORDS(b, w, n) c2000_OctetsFromWords((b), (w), (n))
/* Stage one block, zero-padding a short tail. The zeros are what make the
* unused part of a CTR output block equal the raw keystream. */
static void c2000_LoadBlock(uint32_t blk[C2000_BLOCK_WORDS], const byte* b,
word32 nOctets)
{
byte tmp[WC_AES_BLOCK_SIZE];
if (nOctets >= WC_AES_BLOCK_SIZE) {
C2000_WORDS_FROM_OCTETS(blk, b, C2000_BLOCK_WORDS);
}
else {
XMEMSET(tmp, 0, WC_AES_BLOCK_SIZE);
XMEMCPY(tmp, b, nOctets);
C2000_WORDS_FROM_OCTETS(blk, tmp, C2000_BLOCK_WORDS);
ForceZero(tmp, WC_AES_BLOCK_SIZE);
}
}
/* The context must actually be bound to this device. Under
* WOLF_CRYPTO_CB_FIND wolfCrypt offers contexts of any devId, including ones
* whose devKey the backend never populated - encrypting with that all-zero
* key would be silent and catastrophic. Defined ahead of its callers: an
* implicit declaration here would defeat the guard's own prototype. */
static int c2000_DevIdOk(int devId, const Aes* aes)
{
if (aes == NULL) {
return 0;
}
if (aes->devId == INVALID_DEVID) {
return 0;
}
return (aes->devId == devId);
}
/* Key length (octets) -> driverlib enum; CRYPTOCB_UNAVAILABLE otherwise so
* software takes the operation. */
static int c2000_KeySize(const Aes* aes, AES_KeySize* ks)
{
switch (aes->keylen) {
case 16:
*ks = AES_KEY_SIZE_128BIT;
break;
case 24:
*ks = AES_KEY_SIZE_192BIT;
break;
case 32:
*ks = AES_KEY_SIZE_256BIT;
break;
default:
return WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE);
}
return 0;
}
#ifdef WOLFSSL_AES_COUNTER
/* Big-endian 128-bit increment, matching the static IncrementAesCounter() in
* aes.c. aes->reg stays authoritative in software so a caller can interleave
* hardware and software CTR calls on one context. */
static void c2000_IncrCounter(byte* ctr)
{
int i;
for (i = WC_AES_BLOCK_SIZE - 1; i >= 0; i--) {
/* WC_OCTET, not a bare ++: a byte cell here is 16 bits, so 0xFF + 1
* is 0x100 and the carry would never propagate. */
ctr[i] = WC_OCTET(ctr[i] + 1);
if (ctr[i] != 0) {
return;
}
}
}
#endif /* WOLFSSL_AES_COUNTER */
/* Program the block for one operation. Order is load-bearing: a soft reset
* clears CTRL, KEY1 and IV, so it must be reset -> configure -> IV -> key ->
* length, and the length write starts the engine. dataLen is padded to whole
* blocks so the hardware always emits a complete final block. */
static int c2000_AesSetup(Aes* aes, AES_Direction dir, AES_OpMode mode,
AES_CounterWidth ctrWidth, const byte* iv16, word32 dataLen)
{
AES_ConfigParams cfg;
AES_KeySize ks;
uint32_t kw[C2000_MAX_KEY_WORDS];
uint32_t ivw[C2000_BLOCK_WORDS];
int ret;
ret = c2000_KeySize(aes, &ks);
if (ret != 0) {
return ret;
}
AES_disableGlobalInterrupt(WOLFSSL_C2000_AES_SS_BASE);
AES_performSoftReset(WOLFSSL_C2000_AES_BASE);
XMEMSET(&cfg, 0, sizeof(cfg));
cfg.direction = dir;
cfg.keySize = ks;
cfg.opMode = mode;
cfg.ctrWidth = ctrWidth;
cfg.ccmLenWidth = AES_CCM_L_1;
cfg.ccmAuthLenWidth = AES_CCM_M_0;
AES_configureModule(WOLFSSL_C2000_AES_BASE, &cfg);
if (iv16 != NULL) {
C2000_WORDS_FROM_OCTETS(ivw, iv16, C2000_BLOCK_WORDS);
AES_setInitializationVector(WOLFSSL_C2000_AES_BASE,
(const uint32_t*)ivw);
ForceZero(ivw, sizeof(ivw));
}
XMEMSET(kw, 0, sizeof(kw));
C2000_WORDS_FROM_OCTETS(kw, (const byte*)aes->devKey,
(word32)aes->keylen / 4);
AES_setKey1(WOLFSSL_C2000_AES_BASE, (const uint32_t*)kw, ks);
ForceZero(kw, sizeof(kw));
AES_setDataLength(WOLFSSL_C2000_AES_BASE, (uint64_t)dataLen);
return 0;
}
/* Straight in -> out loop for the modes the hardware chains itself (ECB, CBC).
* Not AES_processData(): that redoes a 64-bit division every iteration, which
* is costly on a C28x, and we need per-block marshalling anyway. */
static int c2000_AesProcess(Aes* aes, byte* out, const byte* in, word32 sz,
AES_Direction dir, AES_OpMode mode, const byte* iv16)
{
uint32_t blk[C2000_BLOCK_WORDS];
uint32_t outw[C2000_BLOCK_WORDS];
word32 off;
word32 n;
int ret;
ret = c2000_AesSetup(aes, dir, mode, AES_CTR_WIDTH_32BIT, iv16, sz);
if (ret != 0) {
return ret;
}
for (off = 0; off < sz; off += WC_AES_BLOCK_SIZE) {
n = sz - off;
if (n > WC_AES_BLOCK_SIZE) {
n = WC_AES_BLOCK_SIZE;
}
c2000_LoadBlock(blk, in + off, n);
AES_writeDataBlocking(WOLFSSL_C2000_AES_BASE, (const uint32_t*)blk);
AES_readDataBlocking(WOLFSSL_C2000_AES_BASE, (uint32_t*)outw);
C2000_OCTETS_FROM_WORDS(out + off, outw, n);
}
ForceZero(blk, sizeof(blk));
ForceZero(outw, sizeof(outw));
return 0;
}
#if defined(HAVE_AES_ECB) || defined(WOLFSSL_AES_DIRECT) || \
defined(WOLF_CRYPTO_CB_ONLY_AES)
static int c2000_Ecb(int devId, struct wc_CryptoInfo* info)
{
Aes* aes = info->cipher.aesecb.aes;
byte* out = info->cipher.aesecb.out;
const byte* in = info->cipher.aesecb.in;
word32 sz = info->cipher.aesecb.sz;
if (aes == NULL || out == NULL || in == NULL) {
return WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE);
}
if (!c2000_DevIdOk(devId, aes)) {
return WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE);
}
if (sz == 0) {
return 0;
}
if ((sz % WC_AES_BLOCK_SIZE) != 0) {
return WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE);
}
return c2000_AesProcess(aes, out, in, sz,
info->cipher.enc ? AES_DIRECTION_ENCRYPT : AES_DIRECTION_DECRYPT,
AES_OPMODE_ECB, NULL);
}
#endif /* HAVE_AES_ECB || WOLFSSL_AES_DIRECT || WOLF_CRYPTO_CB_ONLY_AES */
#ifdef HAVE_AES_CBC
static int c2000_Cbc(int devId, struct wc_CryptoInfo* info)
{
Aes* aes = info->cipher.aescbc.aes;
byte* out = info->cipher.aescbc.out;
const byte* in = info->cipher.aescbc.in;
word32 sz = info->cipher.aescbc.sz;
byte lastIn[WC_AES_BLOCK_SIZE];
int enc = info->cipher.enc;
int ret;
if (aes == NULL || out == NULL || in == NULL) {
return WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE);
}
if (!c2000_DevIdOk(devId, aes)) {
return WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE);
}
if (sz == 0) {
return 0;
}
/* Non-block-multiple means a ciphertext-stealing caller; leave to SW. */
if ((sz % WC_AES_BLOCK_SIZE) != 0) {
return WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE);
}
/* Saved before processing so an in-place call (in == out) still has it. */
if (!enc) {
XMEMCPY(lastIn, in + sz - WC_AES_BLOCK_SIZE, WC_AES_BLOCK_SIZE);
}
ret = c2000_AesProcess(aes, out, in, sz,
enc ? AES_DIRECTION_ENCRYPT : AES_DIRECTION_DECRYPT,
AES_OPMODE_CBC, (const byte*)aes->reg);
if (ret != 0) {
ForceZero(lastIn, sizeof(lastIn));
return ret;
}
/* aes->reg must hold the last ciphertext block so successive calls chain.
* Derived in software, not via AES_readInitializationVector(): IV_IN_OUT
* only holds saved context when CTRL.SAVE_CONTEXT is set (which
* AES_configureModule() does not do), and driverlib's reader does not poll
* CTRL.SVCTXTRDY the way AES_readTag() does. */
if (enc) {
XMEMCPY(aes->reg, out + sz - WC_AES_BLOCK_SIZE, WC_AES_BLOCK_SIZE);
}
else {
XMEMCPY(aes->reg, lastIn, WC_AES_BLOCK_SIZE);
ForceZero(lastIn, WC_AES_BLOCK_SIZE);
}
return 0;
}
#endif /* HAVE_AES_CBC */
#ifdef WOLFSSL_AES_COUNTER
static int c2000_Ctr(int devId, struct wc_CryptoInfo* info)
{
Aes* aes = info->cipher.aesctr.aes;
byte* out = info->cipher.aesctr.out;
const byte* in = info->cipher.aesctr.in;
word32 sz = info->cipher.aesctr.sz;
uint32_t lastOut[C2000_BLOCK_WORDS];
uint32_t ctrw[C2000_BLOCK_WORDS];
AES_KeySize ksz;
byte ks[WC_AES_BLOCK_SIZE];
word32 blocks;
word32 used;
word32 tail;
word32 off;
word32 n;
word32 i;
int ret;
if (aes == NULL || out == NULL || in == NULL) {
return WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE);
}
if (!c2000_DevIdOk(devId, aes)) {
return WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE);
}
/* Decide before mutating state: CRYPTOCB_UNAVAILABLE makes wolfCrypt redo
* the operation in software from the original pointers, so consuming
* leftover keystream first would double-consume it. */
ret = c2000_KeySize(aes, &ksz);
if (ret != 0) {
return ret;
}
(void)ksz; /* only the accept/reject verdict is needed here */
/* Consume leftover keystream: the callback fires before wc_AesCtrEncrypt
* does this itself, so the state is ours. */
if (aes->left > 0) {
used = (aes->left < sz) ? aes->left : sz;
xorbufout(out, in,
(byte*)aes->tmp + WC_AES_BLOCK_SIZE - aes->left, used);
out += used;
in += used;
sz -= used;
aes->left -= used;
}
if (sz == 0) {
return 0;
}
/* sz is bounded by RAM on this part, so this cannot overflow in practice;
* the division is written to be safe anyway. */
blocks = (sz / WC_AES_BLOCK_SIZE) +
(((sz % WC_AES_BLOCK_SIZE) != 0) ? 1U : 0U);
/* Keystream comes from hardware ECB with the counter kept in software,
* not AES_OPMODE_CTR. Measured on a LAUNCHXL-F28P55X: with
* AES_CTR_WIDTH_128BIT the first block matches NIST SP800-38A F.5.1 but
* later blocks diverge once an increment carries across an octet boundary
* (F.5 starts at ...fe ff, so block 2 already does) - the hardware counter
* disagrees with wolfCrypt's IncrementAesCounter(). ECB costs the same
* number of hardware block operations and is correct by construction. */
ret = c2000_AesSetup(aes, AES_DIRECTION_ENCRYPT, AES_OPMODE_ECB,
AES_CTR_WIDTH_32BIT, NULL, blocks * WC_AES_BLOCK_SIZE);
if (ret != 0) {
return ret;
}
for (i = 0; i < blocks; i++) {
off = i * WC_AES_BLOCK_SIZE;
n = sz - off;
if (n > WC_AES_BLOCK_SIZE) {
n = WC_AES_BLOCK_SIZE;
}
/* Encrypt the counter block to get this block's keystream. */
C2000_WORDS_FROM_OCTETS(ctrw, (const byte*)aes->reg,
C2000_BLOCK_WORDS);
AES_writeDataBlocking(WOLFSSL_C2000_AES_BASE, (const uint32_t*)ctrw);
AES_readDataBlocking(WOLFSSL_C2000_AES_BASE, (uint32_t*)lastOut);
C2000_OCTETS_FROM_WORDS(ks, lastOut, WC_AES_BLOCK_SIZE);
xorbufout(out + off, in + off, ks, n);
c2000_IncrCounter((byte*)aes->reg);
}
tail = sz % WC_AES_BLOCK_SIZE;
if (tail != 0) {
/* Octets tail..15 are unconsumed keystream. Store the whole block;
* software reads it from the end (tmp + BLOCK - left). */
XMEMCPY(aes->tmp, ks, WC_AES_BLOCK_SIZE);
aes->left = WC_AES_BLOCK_SIZE - tail;
}
else {
aes->left = 0;
}
ForceZero(ks, sizeof(ks));
ForceZero(ctrw, sizeof(ctrw));
ForceZero(lastOut, sizeof(lastOut));
return 0;
}
#endif /* WOLFSSL_AES_COUNTER */
int wc_C2000_CryptoCb(int devId, struct wc_CryptoInfo* info, void* ctx)
{
(void)ctx;
if (info == NULL) {
return BAD_FUNC_ARG;
}
if (info->algo_type != WC_ALGO_TYPE_CIPHER) {
return WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE);
}
switch (info->cipher.type) {
#ifdef HAVE_AES_CBC
case WC_CIPHER_AES_CBC:
return c2000_Cbc(devId, info);
#endif
#ifdef WOLFSSL_AES_COUNTER
case WC_CIPHER_AES_CTR:
return c2000_Ctr(devId, info);
#endif
#if defined(HAVE_AES_ECB) || defined(WOLFSSL_AES_DIRECT) || \
defined(WOLF_CRYPTO_CB_ONLY_AES)
case WC_CIPHER_AES_ECB:
return c2000_Ecb(devId, info);
#endif
default:
break;
}
/* CFB, OFB, XTS, GCM, CCM and DES3 fall through to software. */
return WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE);
}
/* Whether this port ungated the AESA clock, so teardown knows if the
* peripheral is reachable. Mirrors c2000_clkOn in the entropy port. */
static int c2000_aesOn = 0;
int wc_C2000_Init(int devId)
{
/* Device_init() already does this on the LaunchPad BSP; repeated so the
* port works without that BSP. */
SysCtl_enablePeripheral(SYSCTL_PERIPH_CLK_AESA);
SysCtl_delay(10);
SysCtl_resetPeripheral(SYSCTL_PERIPH_RES_AESA);
AES_disableGlobalInterrupt(WOLFSSL_C2000_AES_SS_BASE);
AES_performSoftReset(WOLFSSL_C2000_AES_BASE);
c2000_aesOn = 1;
return wc_CryptoCb_RegisterDevice(devId, wc_C2000_CryptoCb, NULL);
}
int wc_C2000_Cleanup(int devId)
{
/* Only touch the peripheral if this port turned its clock on. A Cleanup()
* with no prior Init() - an application error path, say - would otherwise
* write AESA registers while the clock is gated, which raises a system
* access error on these parts rather than being a quiet no-op.
* Unregistering the device is safe either way. */
if (c2000_aesOn) {
/* A soft reset clears KEY1, so the last key used does not linger in
* the peripheral after the device is unregistered. */
AES_performSoftReset(WOLFSSL_C2000_AES_BASE);
c2000_aesOn = 0;
}
wc_CryptoCb_UnRegisterDevice(devId);
return 0;
}
#endif /* WOLFSSL_C2000_AES && !NO_AES */

View File

@ -99,6 +99,7 @@ noinst_HEADERS+= \
wolfssl/wolfcrypt/port/pic32/pic32mz-crypt.h \
wolfssl/wolfcrypt/port/ti/ti-hash.h \
wolfssl/wolfcrypt/port/ti/ti-ccm.h \
wolfssl/wolfcrypt/port/ti/ti-c2000.h \
wolfssl/wolfcrypt/port/nrf51.h \
wolfssl/wolfcrypt/port/nxp/ksdk_port.h \
wolfssl/wolfcrypt/port/nxp/dcp_port.h \

View File

@ -0,0 +1,97 @@
/* ti-c2000.h
*
* Copyright (C) 2006-2026 wolfSSL Inc.
*
* This file is part of wolfSSL.
*
* wolfSSL is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* wolfSSL is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA
*/
/* TI C2000 (C28x) on-chip crypto support.
*
* The F28P55x/F28P65x carry an "AESA" accelerator (a TI EIP-120t instance)
* offering ECB/CBC/CTR/CFB/GCM/CCM with 128/192/256-bit keys. wolfCrypt
* reaches it through the crypto-callback framework rather than by replacing
* wolfcrypt/src/aes.c, so software AES stays available: a given Aes context
* opts in by passing WOLFSSL_C2000_DEVID to wc_AesInit(), and a context
* initialised with INVALID_DEVID runs pure software. Anything the hardware
* cannot do returns CRYPTOCB_UNAVAILABLE and falls through to software.
*
* This is a different device from the TivaWare/TM4C block behind
* WOLFSSL_TI_CRYPT (wolfcrypt/src/port/ti/ti-aes.c); the two are not
* interchangeable and must not both be enabled.
*/
#ifndef WOLF_CRYPT_PORT_TI_C2000_H
#define WOLF_CRYPT_PORT_TI_C2000_H
#include <wolfssl/wolfcrypt/types.h>
#ifdef WOLFSSL_C2000_AES
#if defined(WOLFSSL_TI_CRYPT)
#error "WOLFSSL_C2000_AES and WOLFSSL_TI_CRYPT are different devices"
#endif
/* The AESA block is a single shared resource with no per-context state (key,
* IV and mode are reloaded on every operation), so the port is re-entrant
* across Aes contexts but not across preemption or an ISR. Define
* WOLFSSL_C2000_AES_NO_LOCK to assert that an external lock provides that
* guarantee. */
#if !defined(SINGLE_THREADED) && !defined(WOLFSSL_C2000_AES_NO_LOCK)
#error "WOLFSSL_C2000_AES needs SINGLE_THREADED or WOLFSSL_C2000_AES_NO_LOCK"
#endif
/* devId handed to wc_AesInit() and wc_CryptoCb_RegisterDevice(). */
#ifndef WOLFSSL_C2000_DEVID
#define WOLFSSL_C2000_DEVID 0x2000
#endif
/* AESA_BASE / AESA_SS_BASE from C2000Ware inc/hw_memmap.h. Defaulted here so
* the port does not depend on which device header happens to be on the
* include path. */
#ifndef WOLFSSL_C2000_AES_BASE
#define WOLFSSL_C2000_AES_BASE 0x00042000U
#endif
#ifndef WOLFSSL_C2000_AES_SS_BASE
#define WOLFSSL_C2000_AES_SS_BASE 0x00042C00U
#endif
#ifdef __cplusplus
extern "C" {
#endif
struct wc_CryptoInfo;
/* Enable and reset the AESA block, then register the callback for devId.
* Must be called after wolfCrypt_Init(): wc_CryptoCb_RegisterDevice() looks
* for a slot whose devId is INVALID_DEVID, and the device table is only
* initialised to that value by wolfCrypt_Init(). */
WOLFSSL_API int wc_C2000_Init(int devId);
/* Unregister the callback. */
WOLFSSL_API int wc_C2000_Cleanup(int devId);
/* The callback itself, exposed so an application can register it by hand. */
WOLFSSL_API int wc_C2000_CryptoCb(int devId, struct wc_CryptoInfo* info,
void* ctx);
#ifdef __cplusplus
}
#endif
#endif /* WOLFSSL_C2000_AES */
#endif /* WOLF_CRYPT_PORT_TI_C2000_H */