Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
129 changes: 129 additions & 0 deletions .github/workflows/ti-c2000-compile.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
name: TI C2000 (C28x) compile-only

# Compile-guard for the TI C2000 C28x port (CHAR_BIT == 16). It builds the
# wolfCrypt subset that carries the CHAR_BIT != 8 gated fixes with the TI cl2000
# code generation tools - no linking, no C2000Ware, no hardware. Purpose: catch
# compile regressions in the octet/SP/ML-DSA gated paths. On-target run-tests
# live on a hardware-in-the-loop runner (there is no public C28x simulator).

# START OF COMMON SECTION
on:
# Only build when something that can affect the C28x compile changes, so the
# job (and the CGT it pulls) does not burn runner minutes on unrelated PRs.
push:
branches: [ 'master', 'main', 'release/**' ]
paths:
- 'wolfcrypt/src/**'
- 'wolfssl/wolfcrypt/**'
- 'IDE/C2000/**'
- '.github/workflows/ti-c2000-compile.yml'
pull_request:
types: [opened, synchronize, reopened, ready_for_review]
branches: [ '**' ]
paths:
- 'wolfcrypt/src/**'
- 'wolfssl/wolfcrypt/**'
- 'IDE/C2000/**'
- '.github/workflows/ti-c2000-compile.yml'

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
# END OF COMMON SECTION

jobs:
ti_c2000_compile:
name: cl2000 compile-only
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.draft == false }}
runs-on: ubuntu-22.04
timeout-minutes: 20
env:
# TI C2000 code generation tools (cl2000). TI now gates the CGT download
# behind a login (login.ti.com), so there is no stable public direct URL.
# Mirror the installer - ti_cgt_c2000_<ver>_linux-x64_installer.bin - to a
# location CI can fetch (a wolfSSL release asset or internal server) and
# set the repo/org variable TI_C2000_CGT_URL to it (optionally
# TI_C2000_CGT_SHA256 to pin its hash). When TI_C2000_CGT_URL is unset the
# compile-only guard is skipped and the job succeeds with a notice, so the
# gate never blocks a PR on this external dependency.
CGT_VER: "22.6.2.LTS"
CGT_URL: "${{ vars.TI_C2000_CGT_URL }}"
CGT_SHA256: "${{ vars.TI_C2000_CGT_SHA256 }}"
CGT_DIR: "${{ github.workspace }}/ti-cgt-c2000"
steps:
- uses: actions/checkout@v4
name: Checkout wolfSSL

- name: Check CGT source is configured
id: gate
run: |
if [ -z "$CGT_URL" ]; then
echo "::notice::TI_C2000_CGT_URL repo variable is not set - skipping the C2000 cl2000 compile-only guard. TI gates the CGT download behind a login; mirror the installer and set TI_C2000_CGT_URL (and optionally TI_C2000_CGT_SHA256) to enable this job."
echo "skip=true" >> "$GITHUB_OUTPUT"
else
echo "skip=false" >> "$GITHUB_OUTPUT"
fi

- name: Cache TI C2000 CGT
if: steps.gate.outputs.skip != 'true'
id: cgt-cache
uses: actions/cache@v4
with:
path: ${{ env.CGT_DIR }}
key: ti-cgt-c2000-${{ env.CGT_VER }}

- name: Download + install TI C2000 CGT
if: steps.gate.outputs.skip != 'true' && steps.cgt-cache.outputs.cache-hit != 'true'
run: |
set -e
curl -fSL "$CGT_URL" -o /tmp/cgt.bin
# A gated/expired link answers with an HTML login or 404 page (often
# HTTP 200), which would be saved as a bogus "installer"; reject it.
if head -c 512 /tmp/cgt.bin | grep -qiE '<!doctype html|<html'; then
echo "::error::TI_C2000_CGT_URL returned an HTML page, not the CGT installer (the link likely needs a login or has moved). Point it at a direct mirror of ti_cgt_c2000_${CGT_VER}_linux-x64_installer.bin."
exit 1
fi
if [ -n "$CGT_SHA256" ]; then
echo "$CGT_SHA256 /tmp/cgt.bin" | sha256sum -c -
else
echo "::warning::TI_C2000_CGT_SHA256 is unset - the installer is not integrity-checked. Pin it with the hash below."
sha256sum /tmp/cgt.bin
fi
chmod +x /tmp/cgt.bin
/tmp/cgt.bin --mode unattended --prefix "$CGT_DIR"

- name: Locate cl2000
if: steps.gate.outputs.skip != 'true'
id: find-cl
run: |
CL=$(find "$CGT_DIR" -type f -name cl2000 | head -1)
test -n "$CL" || { echo "cl2000 not found under $CGT_DIR"; exit 1; }
echo "cgt_root=$(dirname "$(dirname "$CL")")" >> "$GITHUB_OUTPUT"

- name: Compile-only guard
if: steps.gate.outputs.skip != 'true'
run: |
CGT_ROOT="${{ steps.find-cl.outputs.cgt_root }}" \
IDE/C2000/compile.sh

# Portable regression guard for the WC_SHA3_SPLIT64 Keccak permutation added
# for CHAR_BIT != 8 targets. It is pure C and runs on any host, so force it
# on a normal x86-64 build and run the crypto KATs (SHA-3/SHAKE plus the
# SHAKE-heavy ML-DSA/ML-KEM) that exercise it. No hardware or TI CGT needed,
# so unlike the compile-only guard above this always runs.
sha3_split64_host:
name: SHA-3 split-64 host test
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.draft == false }}
runs-on: ubuntu-22.04
timeout-minutes: 20
steps:
- uses: actions/checkout@v4
name: Checkout wolfSSL

- name: Build + run crypto KATs with WC_SHA3_SPLIT64 forced
run: |
./autogen.sh
./configure --enable-dilithium --enable-mlkem --enable-experimental \
--enable-shake128 --enable-shake256 CPPFLAGS="-DWC_SHA3_SPLIT64"
make
./wolfcrypt/test/testwolfcrypt
7 changes: 7 additions & 0 deletions .wolfssl_known_macro_extras
Original file line number Diff line number Diff line change
Expand Up @@ -758,6 +758,7 @@ WOLFSSL_CLANG_TIDY
WOLFSSL_CLIENT_EXAMPLE
WOLFSSL_CONTIKI
WOLFSSL_CRL_ALLOW_MISSING_CDP
WOLFSSL_DILITHIUM_VERIFY_SMALLEST_MEM
WOLFSSL_DISABLE_EARLY_SANITY_CHECKS
WOLFSSL_DRBG_SHA256
WOLFSSL_DTLS13_ECHO_LEGACY_SESSION_ID
Expand Down Expand Up @@ -1131,6 +1132,12 @@ __SUNPRO_CC
__SVR4
__TASKING__
__TI_COMPILER_VERSION__
__TMS320C2000__
__TMS320C2800__
__TMS320C28XX__
__TMS320C54X__
__TMS320C5500__
__TMS320C55X__
__TURBOC__
__UNIX__
__USE_GNU
Expand Down
100 changes: 100 additions & 0 deletions IDE/C2000/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
# TI C2000 C28x (CHAR_BIT == 16) support

wolfCrypt builds and runs on the TI C2000 C28x DSP family, a word-addressed
architecture where `CHAR_BIT == 16` (a C `char`/`unsigned char` is 16 bits and
is the smallest addressable unit). Support is gated behind `WOLFSSL_WIDE_BYTE`,
which `wolfssl/wolfcrypt/types.h` auto-enables when `CHAR_BIT != 8` or a known
16-bit-char TI toolchain macro is seen (`__TMS320C28XX__`, `__TMS320C2000__`,
etc.). On normal 8-bit-byte targets none of this code changes behavior.

## Validated on hardware (LAUNCHXL-F28P55X, TMS320F28P550SJ, cl2000)

- SHA-1; SHA-224/256, SHA-384/512, SHA-512/224, SHA-512/256
- SHA3-224/256/384/512, SHAKE128/256 (split-64 Keccak permutation auto-enabled
for `WOLFSSL_WIDE_BYTE`, ~53% faster than the generic C path)
- ML-DSA-44/65/87 (Dilithium) verify and full keygen/sign/verify;
ML-KEM-512/768/1024 (FIPS 203)
- AES-128/192/256 CBC/CTR/CFB/OFB/GCM/XTS; AES-CMAC, AES-CCM, AES-GMAC,
AES-SIV, AES-EAX
- HMAC + HKDF; ChaCha20-Poly1305; Poly1305
- X25519 + Ed25519; X448 + Ed448 (CURVE448_SMALL/ED448_SMALL byte backend)
- ECDSA + ECDH (SECP256R1, SP math)
- RSA-2048 PKCS#1 v1.5 sign and verify; DH FFDHE-2048 (SP math)

The on-target acceptance gate is the per-algorithm KAT set the reference example
prints over JTAG (e.g. `ML-DSA-87 verify KAT: PASS`, `X448 a*Bpub: PASS`); the
split-64 Keccak path is additionally validated on a host build with
`-DWC_SHA3_SPLIT64` forced, and the compile-only CI below guards every
`WOLFSSL_WIDE_BYTE` source against build breakage.

## What `WOLFSSL_WIDE_BYTE` fixes

The `CHAR_BIT != 8` work falls into a few recurring classes, each a no-op on
8-bit targets:

- Byte/word aliasing. Serializing a `word32`/`word64` via a `byte*` cast moves
cells, not octets. Replaced with shift-based octet I/O. Shared helpers live in
`wolfcrypt/src/misc.c`: `WordsFromBytesBE32`/`BytesFromWordsBE32`,
`BytesFromWordsLE32`, the 64-bit variants, and octet-correct
`readUnalignedWord32`/`readUnalignedWord64`. `sp_int.c sp_read_unsigned_bin`
uses the endian-/`CHAR_BIT`-agnostic shift loop for its leftover bytes.
- `(byte)x` not truncating to an octet (it keeps 16 bits). Masked with
`WC_OCTET(x)` = `(byte)((x) & 0xFF)` (types.h). Used across the ML-KEM/ML-DSA
encoders, the SP `*_to_bin` serializers, AES `GETBYTE`, base64, and DRBG.
- Integer-promotion bugs. `1U << n` is 16-bit on C28x (use `1UL`); a bit width
written `sizeof(t) * 8` is wrong when `CHAR_BIT != 8` (use `CHAR_BIT *
sizeof(t)`); a `byte` operand promotes to a 16-bit `int`.
- `sizeof` counting cells, not octets. e.g. `CHACHA_CHUNK_BYTES` is `16 * 4`,
not `16 * sizeof(word32)` (= 32 on C28x, which halves the ChaCha block).

The SP backend file `wolfcrypt/src/sp_c32.c` is generated; the `& 0xFF` octet
masks added to its `sp_*_to_bin_*` serializers are also applied in the SP
generator templates so a regeneration preserves them (tracked separately).

## cl2000 compiler workarounds

TI `cl2000` (C28x) miscompiles a couple of ML-DSA 32-bit reductions on this
16-bit target; both are worked around in `dilithium.h`, defaulted on under
`WC_16BIT_CPU` (correct on every target, no-ops off `WC_16BIT_CPU`):

- `MLDSA_MUL_QINV_WIDE64` -- `mldsa_mont_red()`'s q^-1 step uses a 32x64->64
widening multiply instead of 32x32->32.
- `MLDSA_MUL_Q_SLOW` -- `mldsa_red()`/`mldsa_mont_red()`'s `* q` step uses shifts
(`q = 2^23 - 2^13 + 1`) instead of a multiply. At `-O2` cl2000 mis-generates
`mldsa_red()`'s multiply-based Barrett reduction: the reduced value stays
correct (the verify KAT passes) but the emitted code adds a stray store that
corrupts memory a later allocation trips on; the shift form avoids it (a
widening multiply does not). Isolated by pinning single functions to `-O1`
with `#pragma FUNCTION_OPTIONS`.

With both, ML-DSA builds at full `-O2` on the C28x with no per-file overrides.

## Enabling on your build

Define a user-settings header (see `IDE/C2000/user_settings.h` for a
minimal CHAR_BIT!=8 config) and build with `WOLFSSL_USER_SETTINGS`. For the SP
math backend on a 16-bit-int target also set `WOLFSSL_SP_MATH`,
`SP_WORD_SIZE 32`, and `WOLFSSL_SP_ALLOW_16BIT_CPU`.

## Reference example

A complete bare-metal example with KATs, benchmark, linker scripts, and per-
algorithm build toggles is in wolfSSL Examples:
`embedded/ti-c2000-f28p55x/` (see its `README.md` for the `make` options:
`ECC`, `MLKEM`, `AES`, `AESEXTRA`, `X25519`, `HKDF`, `CHACHA`, `RSA`, `SIGN`,
`BENCH`).

Representative throughput on the F28P55X at 150 MHz: SHA-256 ~284 KiB/s; SHA3-256
~264 KiB/s; SHAKE128 ~319 KiB/s; RNG Hash-DRBG ~122 KiB/s. ML-DSA-87 verify
~225 ms/op in ~10.7 KB RAM (zero heap, with `WOLFSSL_MLDSA_VERIFY_SMALLEST_MEM`
+ `WOLFSSL_MLDSA_ASSIGN_KEY`).

## Continuous integration

`IDE/C2000/compile.sh` runs `cl2000 --compile_only` over the
`CHAR_BIT != 8` wolfCrypt subset to guard these paths without hardware;
`.github/workflows/ti-c2000-compile.yml` runs it in CI. TI gates the C2000
code generation tools behind a login, so the workflow fetches the installer
from the `TI_C2000_CGT_URL` repo/org variable (mirror it to a wolfSSL release
asset or internal server; optionally pin `TI_C2000_CGT_SHA256`). When that
variable is unset the job is skipped with a notice rather than failing.
68 changes: 68 additions & 0 deletions IDE/C2000/compile.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
#!/bin/sh
# compile.sh - compile-only guard for the TI C2000 (C28x, CHAR_BIT==16) port.
#
# Builds the wolfCrypt subset that compiles under CHAR_BIT==16 with the TI cl2000
# code generation tools, using IDE/C2000/user_settings.h. No linking, no
# C2000Ware, no hardware: this only catches compile regressions in the
# CHAR_BIT != 8 gated code paths (SHA-2/3/SHAKE, ML-DSA-87 verify, SP-ECC).
#
# Usage:
# CGT_ROOT=/path/to/ti-cgt-c2000_xx.y.z IDE/C2000/compile.sh
#
# CGT_ROOT must point at a TI C2000 codegen install (the dir containing
# bin/cl2000). The CGT is a free download from TI; in CI it is fetched/cached
# by .github/workflows/ti-c2000-compile.yml.

set -e

: "${CGT_ROOT:?set CGT_ROOT to the ti-cgt-c2000 install (dir with bin/cl2000)}"

# Repo root = two levels up from this script.
SELF_DIR=$(cd "$(dirname "$0")" && pwd)
WOLFROOT=$(cd "$SELF_DIR/../.." && pwd)
CL="$CGT_ROOT/bin/cl2000"

if [ ! -x "$CL" ]; then
echo "ERROR: cl2000 not found/executable at $CL" >&2
exit 2
fi

OUT=$(mktemp -d)
trap 'rm -rf "$OUT"' EXIT

INCS="-I$CGT_ROOT/include -I$WOLFROOT -I$SELF_DIR"
CFLAGS="-v28 --abi=eabi --float_support=fpu32 --tmu_support=tmu1 -O2 \
--define=WOLFSSL_USER_SETTINGS --display_error_number --diag_warning=225"

# wolfCrypt sources to compile-guard under CHAR_BIT==16. This is the set that
# carries the CHAR_BIT != 8 gated fixes (plus their direct deps) - the
# regression surface for this port. hash.c (an unmodified dispatch wrapper) is
# intentionally omitted: its wc_OidGetHash() OID switch needs the fuller ASN/OID
# config of a real build to avoid a 16-bit-int case-label fold, and it is
# 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 \
curve25519 ed25519 fe_operations ge_operations \
curve448 ed448 fe_448 ge_448"

rc=0
for s in $SRCS; do
printf 'CC %s.c ... ' "$s"
if "$CL" $CFLAGS $INCS --compile_only --skip_assembler \
--asm_directory="$OUT" --obj_directory="$OUT" \
"$WOLFROOT/wolfcrypt/src/$s.c" > "$OUT/$s.log" 2>&1; then
echo "ok"
else
echo "FAIL"
cat "$OUT/$s.log"
rc=1
fi
done

if [ "$rc" -eq 0 ]; then
echo "TI C2000 compile-only guard: PASS"
else
echo "TI C2000 compile-only guard: FAIL" >&2
fi
exit "$rc"
Loading
Loading