Skip to content

Zephyr fixes - #11401

Open
Frauschi wants to merge 15 commits into
wolfSSL:masterfrom
Frauschi:zephyr_rw612
Open

Zephyr fixes#11401
Frauschi wants to merge 15 commits into
wolfSSL:masterfrom
Frauschi:zephyr_rw612

Conversation

@Frauschi

@Frauschi Frauschi commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

Summary

Nine fixes to wolfSSL proper, then five commits reworking the Zephyr module's configuration.

None of the nine is Zephyr-specific: each is a place wolfSSL assumes a normal toolchain, a POSIX libc, or an OS that enables vector state, so any bare-metal or RTOS target hits the same wall.

Library fixes

Commit Problem
7f595dc UMAAL-free SP math was keyed on the Cortex-M3 alone, so every other M-profile core without the DSP extension got code its silicon cannot execute
3362737 The WOLFSSL_ZEPHYR block hands C declarations to the assembler, so no wolfSSL .S file built on Zephyr, on any architecture
4bc2165 sp_x86_64.c binds registers with the bare asm keyword, which strict ISO C rejects
d069f5d cpuid_set_flags() tested the AVX bits but never OSXSAVE/XGETBV, so wolfSSL dispatched vector code on an OS that never enabled the state and the image died on #UD
ab9a065 armv8-{aes,sha256}-asm emit aese/sha256h with no .arch_extension crypto
5ddbe55 .arch_extension sha3 was emitted only for Apple's assembler
7197713 L_mlkem_aarch64_consts sat outside its WOLFSSL_HAVE_MLKEM guard, so a -Werror build with ML-KEM off cannot compile the file
9232649 A WOLFSSL_CUSTOM_CURVES build fails at runtime on its own largest enabled curve with WC_KEY_SIZE_E
17081fd XHTONS/XNTOHS use the POSIX htons/ntohs, which Zephyr declares only under CONFIG_POSIX_API

9232649 is off by exactly one bit: under custom curves ECC_KEY_MAX_BITS() adds a bit for orders larger than the prime, the ceiling does not, and MP_BITS_CNT() rounds that into a whole extra digit. bp256 fails at a 256-bit ceiling, bp384 at 384, bp512 at 512 - always the largest curve in the set. It also makes the overridable MAX_ECC_BITS reach the runtime guard, which it never did.

Four of the nine are regenerated output. The generator changes are in https://github.com/wolfSSL/scripts/pull/675, so regeneration cannot silently drop them.

Module changes

  • 8a62e7b, bd6b6d0 - ECC curves become Kconfig options instead of a hard-coded SECP256R1. P-256 stays the default; Brainpool implies custom curves and the generic SP backend, so it is opt-in. Two Twister scenarios pin the pairings that fail at runtime rather than at build time.
  • 81b3ca4 - feature Kconfigs now depend on not having a settings file. They only write the module's user_settings.h, which such a build never reads, so they were accepted and silently ignored.
  • d893c9a - deletes Kconfig.tls-generic, Zephyr's mbedTLS file with the prefix renamed. Of its 53 symbols two reach any code, and three select symbols defined nowhere. The two live ones move into the module Kconfig; TLS 1.3 gains default y.
  • 95cdbe5 - four assembly options collapse into WOLFCRYPT_ASM, which derives the port from the CPU Zephyr reports. None of the four could express a working configuration. Each ARM port also needs two SP macros, not one: WOLFSSL_SP_<cpu>_ASM for sp_<cpu>.c, the unsuffixed one for the sp_int.c primitives the generic path uses. On frdm_rw612: ECDSA P-256 sign 16 to 136 ops/sec, verify 10 to 102, RSA-2048 public 86 to 194.

Verification

13 of 13 Twister configurations across qemu_x86, qemu_x86_64, qemu_cortex_a53, mps2/an521/cpu0 and native_sim/native/64, plus 8 of 8 wolfssl-tagged scenarios in the Zephyr fork. frdm_rw612 builds and links with CONFIG_WOLFCRYPT_ASM=y.

There is now one scenario per assembly port, so a regression in the generated sources fails in CI rather than on hardware - the aarch64 one found 7197713 on its first run.

Not included

32-bit x86 (only the AES sources exist at that width); the x86_64 SP assembly (AVX throughout, called with no CPUID check and no fallback); and the AES-XTS streaming regeneration, whose C wiring is in #11386.

…x-M3

WOLFSSL_ARM_ARCH_7M selects the UMAAL-free variants in sp_cortexm.c and in the
thumb2-* assembly for Poly1305, Curve25519 and ML-KEM. It was derived from
__ARM_ARCH_7M__, which names the Cortex-M3 and nothing else.

UMAAL is not an ARMv7-M property though, it belongs to the DSP extension, which
on ARMv8-M is optional. An ARMv8-M part built without it, such as the Cortex-M33
in the NXP RW612, has no UMAAL and no __ARM_ARCH_7M__ either, so it took the
UMAAL path and would not assemble. WOLFSSL_SP_NO_UMAAL is not a way out: it is
derived from this symbol and covers only a subset of the sites.

Toolchains define __ARM_FEATURE_DSP exactly when the extension is present, so
key on its absence on an M-profile core. Verified against gcc for M0, M3, M4,
M7, M23, M33 and M33+nodsp: it is set only where UMAAL is genuinely missing.
The __ARM_ARCH_7M__ arm stays, so no configuration that worked before changes,
and the __ARM_ARCH_PROFILE test matches the one already in cpuid.c.
The WOLFSSL_ZEPHYR block includes <zephyr/kernel.h>, <zephyr/sys/printk.h>,
<zephyr/sys/util.h> and <stdlib.h>, and declares z_realloc. Assembly sources
reach settings.h through libwolfssl_sources_asm.h, so all of that was being
handed to the assembler, which stops at the first C declaration it meets:

  stddef.h:160: Error: no such instruction: `typedef long int ptrdiff_t'

That makes every wolfSSL .S file unbuildable on Zephyr. It is not specific to
one architecture or one algorithm - reproduced on x86_64 with chacha_asm.S,
sha256_asm.S, sha512_asm.S and aes_asm.S, and on a Cortex-M33 with
thumb2-aes-asm.S, which fails identically:

  stddef.h:160: Error: bad instruction `typedef int ptrdiff_t'

Zephyr ports that use the *_c.c inline-assembly variants never noticed,
because those are compiled as C.

Guard the block with __ASSEMBLER__, matching what settings.h already does for
the STM32MP13 header. Assembly still gets the feature macros from
user_settings.h; it just no longer gets the C declarations. No effect on any
C translation unit - __ASSEMBLER__ is only defined when the compiler driver
is assembling.
…s them

The nine div-word helpers bind a variable to rax with the bare asm keyword:

  register sp_digit r asm("rax");

asm is a GNU extension, not an ISO C keyword, so it is unavailable under
-std=c17 and the file will not compile:

  sp_x86_64.c:597: error: expected '=', ',', ';', 'asm' or '__attribute__'
                          before 'asm'

Zephyr compiles with -std=c17, which is how this surfaced, but any strict-ISO
build hits it. __asm__ is the alternate spelling GCC and Clang keep available
regardless of -std, and it is what the very next line of each of these
functions already uses for the statement asm - so this only makes the two
consistent.
CPUID's AVX and AVX-512 bits say the silicon has the unit. Executing one of
those instructions also requires the OS to have turned on extended state:
CR4.OSXSAVE, plus the matching XCR0 components - SSE and AVX for VEX
encodings, and opmask, ZMM_Hi256 and Hi16_ZMM on top of those for EVEX. An OS
that does not context-switch those registers leaves them clear, and the
instruction then raises #UD no matter what CPUID advertises.

cpuid_set_flags() tested the feature bits alone, so on such a system wolfSSL
set CPUID_AVX1/AVX2/VAES/AVX512* and dispatched to implementations the CPU
would refuse. Zephyr is one such system - its x86 context switch is
fxsave/fxrstor and it never sets CR4.OSXSAVE - and the result is an immediate
crash the first time SHA-256 or AES-GCM picks a vector path:

  <err> os: Invalid opcode
  >>> ZEPHYR FATAL ERROR 0: CPU exception on CPU 0

Gate each family on OSXSAVE plus the XCR0 components it actually needs, the
sequence Intel documents for this. The AVX-512 dispatch sites are the reason
the masks have to differ: AesEcbEncryptBlocks(), AES_GCM_init_*,
chacha_avx512_beneficial() and poly1305_use_avx512() all branch on
IS_INTEL_AVX512 && IS_INTEL_VAES without consulting AVX1 or AVX2, so gating
only the 256-bit flags would leave the 512-bit paths reachable on exactly the
systems this is meant to protect. Keeping them on one gate would also be
wrong in the other direction, since 0x06 does not cover the ZMM state.

VAES is VEX or EVEX encoded, so it is gated with the AVX state; the AVX-512
dispatch sites test it alongside CPUID_AVX512, which carries the stricter
mask.

Where an OS does enable XSAVE, which is every mainstream one, nothing
changes. XGETBV is itself only legal once OSXSAVE is set, so the two tests
have to stay in that order.
@Frauschi Frauschi self-assigned this Sep 8, 2026

@wolfSSL-Fenrir-bot wolfSSL-Fenrir-bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fenrir Automated Review — PR #11401

Scan targets checked: wolfcrypt-bugs, wolfcrypt-port-bugs, wolfcrypt-rs-bugs, wolfcrypt-src, wolfssl-bugs, wolfssl-src

Findings: 1
1 finding(s) posted as inline comments (see file-level comments below)

This review was generated automatically by Fenrir. Reported findings require changes before merge.

Comment thread wolfcrypt/src/ecc.c Outdated
The ARMv8 AES and SHA-256 ports emit aese, aesmc and the sha256 crypto
instructions with no .arch or .arch_extension directive of their own, so they
assemble only when the command line already names a CPU that has the
extension.  Built against plain ARMv8-A, which is what -mcpu=cortex-a53 gives
you, the assembler refuses them:

  armv8-aes-asm.S: Error: selected processor does not support
                          `aese v0.16b,v1.16b'

Zephyr is one caller that lands there.  It derives -mcpu from the board's
Kconfig and has no symbol for the crypto extension, so there is nothing for a
build system layered on top to key on, and naming a CPU in the module would
retarget the library on every board that is not that CPU.  Anything else
compiling for the base architecture meets the same wall.

A .arch_extension crypto enables the mnemonics for the code that follows it.
The GNU assembler and armasm64 take one at file scope, so the .S and .asm
outputs carry a single copy - bare, and as a comment for armasm64, which
assembles these instructions unconditionally.  The C output cannot do the
same: a compiler hands each __asm__ block to the assembler separately, and
clang gives every block a fresh subtarget, so one at file scope reaches
nothing below it and the build still fails there:

  armv8-aes-asm_c.c: error: instruction requires: aes

So in the C output the directive leads every asm block instead, which is what
the SHA-3 ports already do.  All three sit inside the existing
WOLFSSL_ARMASM_NO_HW_CRYPTO guard, so a build that has opted out of the
hardware paths never asks for an extension it will not use.

Regenerated from the scripts repository, which carries the matching change.

Verified on qemu_cortex_a53 under Zephyr: wolfcrypt_test passes 43 of 43 with
2723 aese instructions in the image, where before neither file assembled.  The
generated _c.c files compile at -march=armv8-a with both GCC and clang, where
before clang rejected them.
The SHA-3, SHA-512 and FrodoKEM ARM64 ports guard their .arch_extension sha3
with __APPLE__, on the assumption that every other assembler learns about the
extension from the command line.  That does not hold.  Assembling for plain
ARMv8-A with the GNU assembler:

  armv8-sha3-asm.S: Error: selected processor does not support
                           `eor3 v31.16b,v0.16b,v5.16b,v10.16b'

Emit the directive unconditionally.  It is a no-op where the extension is
already enabled, so the Apple path is unchanged.

The AES port needs it too and had no directive for it at all.  Its AES-GCM
EOR3 variants, behind WOLFSSL_ARMASM_CRYPTO_SHA3, emit eor3 while the only
directive in the file is the crypto one added earlier in this branch - that
covers aes, sha2 and pmull, but not sha3.

Regenerated from the scripts repository, which carries the matching change.
The ARM64 ML-KEM port emits eor3 from the SHA-3 extension and sqrdmlsh from the
ARMv8.1 RDMA extension.  Neither is in the base ARMv8-A a toolchain may be
invoked with, and the file carries no directive of its own:

  armv8-mlkem-asm.S: Error: selected processor does not support
                            `eor3 v31.16b,v0.16b,v5.16b,v10.16b'
  armv8-mlkem-asm.S: Error: selected processor does not support
                            `sqrdmlsh v21.8h,v29.8h,v4.h[0]'

Each directive sits inside the guard that selects the variant needing it -
WOLFSSL_ARMASM_CRYPTO_SHA3 for eor3, !WOLFSSL_AARCH64_NO_SQRDMLSH for sqrdmlsh -
so a build that has opted out never asks for an extension it will not use.

The RDMA one is spelled rdm rather than the rdma alias.  LLVM has accepted rdm
since long before it grew that alias, and Apple's assembler - which is what the
aarch64 CI runner uses - rejects rdma outright.  Both GNU binutils versions to
hand take either spelling, so rdm is the portable one.

Found by building the Zephyr wolfcrypt test for qemu_cortex_a53 with the
assembly and ML-KEM both enabled.  That failed to assemble before this; it now
links, with 299 sqrdmlsh instructions in the image.
L_mlkem_aarch64_consts sat outside the WOLFSSL_HAVE_MLKEM conditional while
every other constant in the file - zetas, zetas_inv, q - sat inside it. With
ML-KEM off the C output then declares a static const nothing references, and a
-Werror build stops:

  armv8-mlkem-asm_c.c:37: error: 'L_mlkem_aarch64_consts' defined but not used
    [-Werror=unused-const-variable=]

Zephyr compiles wolfSSL with -Werror, so this made armv8-mlkem-asm_c.c
unbuildable on any aarch64 target that does not enable ML-KEM - which is every
one of them by default.

The cause is in the generator: Kyber_ARM64_Neon_ASM#initialize emitted the
constant, but write() is what opens the guard, and it runs later. Fixed there by
moving the emission into a define_consts() called from write(), mirroring the
existing define_q(); these files are the regenerated result. All three outputs
change by the same three-line move and nothing else.
A WOLFSSL_CUSTOM_CURVES build fails at runtime on its own largest enabled
curve. Enabling Brainpool with sizes 256, 384 and 512 gives

  ecc_test_curve_size 64 failed!  (WC_KEY_SIZE_E, -234)

and it is not a bp512 quirk: a bp256-only build fails on bp256, a
bp256+bp384 build on bp384. Every time it is the largest curve in the set.

The ceiling is off by exactly one bit. ECC_KEY_MAX_BITS() takes a
"dp->size * 8 + 1" variant, for orders a bit larger than their prime, but
MAX_ECC_BITS_NEEDED is the plain curve size. MP_BITS_CNT() rounds up to whole
digits, so that single bit puts the key one digit past the ceiling and
MP_BITS_OVER_MAX() rejects it.

Name that bit MAX_ECC_BITS_EXTRA and give the working values room for it.
Custom curves are only one of the three ways the variant gets selected:
ECC_MIN_KEY_SZ at or below 160 takes it, and so does HAVE_ECC_KOBLITZ with
ECC_MIN_KEY_SZ at or below 224 - which is the default for any Koblitz build,
and fails there in exactly the same way. Both sides are built from the one
macro: ECC_KEY_MAX_BITS() adds MAX_ECC_BITS_EXTRA rather than a literal 1,
which collapses its two variants into one, and MAX_ECC_BITS_USE adds the same
macro so the two cannot drift apart.

MAX_ECC_BITS itself keeps meaning the plain size of the largest curve. The
extra bit is an internal sizing detail, so folding it in there instead would
make a build that pins MAX_ECC_BITS to exactly what its curves need fail the
guard that rejects a too-small value - which is what --with-max-ecc-bits=1024
alongside --enable-all-crypto does.

Second, MAX_ECC_BITS_USE derived from MAX_ECC_BITS_NEEDED, so the overridable
MAX_ECC_BITS never reached the runtime guard - defining it larger changed the
key struct but not the check, and there was no way to size the working values
for a curve that is not compiled in. Derive it from MAX_ECC_BITS instead; ecc.h
already refuses a value below what the enabled curves need, so this only ever
widens, and it is what an arbitrary curve passed to wc_ecc_set_custom_curve()
needs. The SP_INT_BITS clamp above it is unchanged and still applies: sp_int
cannot hold more than that many bits whatever the ceiling asks for.

Both are no-ops for a build that takes neither variant.
XHTONS and XNTOHS resolve to htons() and ntohs() whenever the socket I/O layer
is compiled in.  Zephyr 4.4 renamed those to net_htons() and net_ntohs() in
<zephyr/net/net_ip.h> and brings the unprefixed spellings back only under
CONFIG_NET_NAMESPACE_COMPAT_MODE, so a build without that option fails the
moment anything reaches them:

  wolfio.h:1060: error: implicit declaration of function 'htons'
    [-Wimplicit-function-declaration]

On a host-libc target such as native_sim the same thing surfaces at link time
instead, as an undefined reference out of DefTicketEncCb().

That call site uses XHTONS on a session-ticket length field rather than on a
socket port, so the macro is doing plain byte-order work.  Nothing had reached
it before because session tickets require TLS 1.3.

Zephyr 4.3 and older define htons() and ntohs() themselves, in that same
header and with no POSIX involvement, so neither spelling covers the whole
supported range.  Gate on KERNEL_VERSION_NUMBER, the way the 4.1 socket
changes already are a few hundred lines above.

Reproduced on Zephyr's own net.http.server.tls.wolfssl, which enables
networking but not CONFIG_POSIX_API; it and the other seven wolfssl-tagged
scenarios pass with this.  The module's own suite passes on 4.4, and the
repository's Zephyr workflows cover 2.7.4 through 4.3.
The module hard-coded ECC_USER_CURVES with SECP256R1 and nothing else, so an
application needing P-384, P-521 or a Brainpool curve had to patch
user_settings.h. Each curve is now its own Kconfig option under WOLFSSL_ECC,
with P-256 the default so existing configurations are unaffected.

Two dependencies are encoded here rather than left for the next person to
hit, because both fail in a way that does not point at the cause.

Brainpool needs custom-curve support. Its curves are not prime-field NIST
curves, and ecc.c refuses to build them without WOLFSSL_CUSTOM_CURVES.

Custom curves in turn cannot coexist with the per-curve SP math this module
selects, because SP's fast paths are written per curve and cannot represent
an arbitrary one. Asking for Brainpool therefore moves the whole build onto
the generic SP variant, which costs size and costs speed on the common
curves. That is a real trade-off, and it is why Brainpool is opt-in.

Brainpool does not drag another curve in with it, though it looks as though
it must. On the generic variant the enabled-curve ceiling has to clear the
largest Brainpool curve compiled in, and when it does not that curve fails at
runtime with WC_KEY_SIZE_E. Measured on native_sim: brainpoolP256r1 failed
with a 256-bit ceiling, brainpoolP384r1 with a 384-bit one and
brainpoolP512r1 with a 512-bit one - each time the largest curve in the set.
That ceiling was short by exactly one bit, fixed earlier in this branch, so a
Brainpool build compiles only the sizes it asks for.

Koblitz keeps its commented-out HAVE_ECC_KOBLITZ line rather than gaining an
option. secp256k1 and secp224k1 have no TLS use and no wolfPSA one, so the
hand-edit hint is the right level of support for them.

P-256 keeps its inverted sense: it is the one curve wolfCrypt enables by
default, so it is turned off by defining NO_ECC256 rather than by omission.

Verified on frdm_rw612. With P-384 and P-521 selected, all three curves
generate a key and round-trip a signature on device; with the defaults the
image is unchanged at P-256 only and 20 KB smaller, since neither the extra
curves nor the generic math are pulled in.
The curve Kconfigs decide which curves wolfCrypt compiles and which SP
implementation backs them, and getting that pairing wrong fails at runtime
with WC_KEY_SIZE_E rather than at build time. Two wolfssl_test scenarios pin
the combinations that matter: the NIST curves on their per-curve SP paths
with no custom curve in sight, and the Brainpool set that forces the generic
backend, with no P-521 present to lend it a curve ceiling.
…s used

The build-profile options only write the module's user_settings.h, which is
never read when the application supplies its own settings file. So
CONFIG_WOLFSSL_ECC_384=y alongside a settings file was accepted and silently
ignored, and the same went for every other feature option.

They now depend on not having one, and disappear from the menu instead. This
needs a tracking bool because WOLFSSL_SETTINGS_FILE is a string symbol and
Kconfig evaluates a string in a logical context as always-false, which would
have made every one of these dependencies quietly unsatisfiable.

FIPS is gated on the master switch rather than on each version. WOLFCRYPT_FIPS
writes HAVE_FIPS into user_settings.h, so it belongs under the rule; putting
the dependency on the choice members alone would leave the version prompt
visible with nothing selectable and its default unreachable, while CMake still
compiled the FIPS bundle.

Kconfig.tls-generic is left alone here. Only two of its 53 symbols reach any
code, so gating the rest would make dead options look conditional; the commit
after this one deletes the file and rehomes those two with the dependency.

wolfssl_tls_sock's no-malloc scenario was setting five of those options next
to a settings file, so they now name symbols that are not visible. Drop them
and say why they are gone.

Options that drive the build rather than the wolfCrypt configuration, meaning
the implementation choice, the assembly port and the install path, are
untouched and stay selectable either way.
Kconfig.tls-generic is a copy of Zephyr's mbedTLS Kconfig.tls-generic with the
symbol prefix renamed, which is why a wolfSSL module carries options named
after mbedTLS internals - ECP is their elliptic-curve-over-prime-field module
and DP their domain parameters, neither of which means anything here.

Renaming the prefix was all that happened: nothing was wired up behind it. Of
the 53 config symbols in the file, exactly two reach any code, WOLFSSL_TLS_VERSION_1_2
and _1_3. Checked every symbol against the whole workspace - all .c, .h, .cmake,
CMakeLists.txt, Kconfig and .conf files under zephyr/ and modules/crypto/wolfssl.
The other 51 appear only in that file. Setting
CONFIG_WOLFSSL_ECP_DP_SECP384R1_ENABLED=y has always done precisely nothing,
and unlike the options the previous commit gated, these do nothing whether or
not a settings file is present.

Three of them are worse than inert. WOLFSSL_TLS_VERSION_1_0, _1_1 and _1_3
select WOLFSSL_ALLOW_TLSV10_ENABLED, WOLFSSL_NO_OLD_TLS_DISABLED and
WOLFSSL_TLS13_ENABLED, none of which is defined anywhere. Kconfig accepts a
select of an undefined symbol silently, so the reader sees a mechanism that
does not exist. user_settings.h even tested CONFIG_WOLFSSL_TLS13_ENABLED
alongside the real symbol; that half of the condition could never be true.

Delete the file and move the two live symbols into the module's own Kconfig,
next to WOLFSSL_DTLS where the other protocol options are, keeping the
settings-file dependency. The dead selects go with it, as do the four dead
options the two TLS samples were setting and the stale entry in
.wolfssl_known_macro_extras.

TLS 1.3 gains a default of y on the way across. The old file had 1.2 on and
1.3 off, which was Zephyr's mbedTLS default in 2018; wolfSSL's own configure
enables both, and a module that ships TLS 1.3 off by default is not a sensible
starting point now. Enabling both rather than 1.3 alone keeps interop with the
TLS 1.2-only peers embedded deployments still meet, and does not quietly drop
1.2 from an existing configuration. A 1.3-only build works by clearing the 1.2
option: user_settings.h already defines WOLFSSL_NO_TLS12 when it is off.

That default is also what first reached DefTicketEncCb(), since session
tickets need TLS 1.3 - hence the wolfio byte-order fix earlier in this branch,
without which eight of the module's own scenarios stop compiling.

The ECC curve options that replaced the WOLFSSL_ECP_DP_* ones - and that do
reach user_settings.h - went in earlier in this branch.
Four options guarded the assembly - WOLFCRYPT_ARMASM, its THUMB2 companion,
WOLFCRYPT_INTELASM, and nothing at all for the single-precision math - and
between them they could not express a working configuration on any target.

WOLFCRYPT_ARMASM_THUMB2 selected the thumb2-* sources but nothing anywhere
defined WOLFSSL_ARMASM_THUMB2, so every caller still took the ARMv8 path and
the link failed on AES_*_AARCH32. It also needs WOLFSSL_ARMASM_NO_HW_CRYPTO,
since Cortex-M has no AES or SHA extension and the Thumb2 code is the whole
of it. The single-precision assembly had no option at all:
WOLFSSL_SP_ARM_CORTEX_M_ASM sat commented out inside an #if 0 while
sp_cortexm.c was already in the source list.

WOLFCRYPT_INTELASM was worse: it named the 32-bit source pair while
user_settings.h declared WOLFSSL_X86_64_BUILD, so aes_asm.S compiled to an
empty object behind its WOLFSSL_X86_BUILD guard and aes_gcm_x86_asm.S emitted
32-bit code into a 64-bit build. It could not have produced a working image.

Only one combination is ever right for a given board, so the four collapse
into WOLFCRYPT_ASM and the module derives the rest from the CPU Zephyr
reports: ARMv7-M and ARMv8-M mainline take Thumb2 plus the Cortex-M math,
AArch64 the ARMv8 and ARM64 ones, other 32-bit ARM the ARMv8-32 and ARM32
ones, and x86_64 the Intel symmetric set with its correct 64-bit filenames.

ARMv6-M and ARMv8-M baseline deliberately get no symmetric assembly. The
Thumb2 port uses UBFX and LDRD, neither of which those cores have, so keying
it on CONFIG_CPU_CORTEX_M would hand an M0 or M23 board code that cannot
assemble. They still get the Thumb math, which is the only thing wolfCrypt
ships for that profile - the same split the SP half already made.

Each ARM port sets two single-precision macros, not one. WOLFSSL_SP_<cpu>_ASM
compiles sp_<cpu>.c, the per-size backend for the RSA, DH and ECC sizes it
covers; the unsuffixed WOLFSSL_SP_<cpu> enables the inline-asm word primitives
inside sp_int.c, which is what the generic WOLFSSL_SP_MATH_ALL path uses for
every other size and curve. sp_int.c's own header block names them as separate
features and configure.ac sets them from separate options, neither implying
the other. Defining only the _ASM half - which is what this module did - left
Brainpool, custom curves and any uncovered RSA or DH size running with no
assembly at all.

The x86_64 set carries fe_x25519_asm.S, wc_mlkem_asm.S and wc_mldsa_asm.S but
not the AES pair. curve25519.c, wc_mlkem_poly.c and wc_mldsa.c each take the
Intel path whenever USE_INTEL_SPEEDUP is defined, so omitting any of them is a
link error the moment an application enables that algorithm; the AES sources
define nothing but _aesni entry points whose callers sit behind WOLFSSL_AESNI,
which this module does not set, so they were bytes with no reachable caller.

The 32-bit A and R profiles need WOLFSSL_ARMASM_NO_HW_CRYPTO unless the
toolchain says otherwise. Their ARMv8-32 sources carry hardware AES and SHA
blocks behind that guard and no .arch_extension of their own, so on a plain
ARMv7 part such as qemu_cortex_a9 the assembler rejects aese.8 and its
neighbours outright. Key it on __ARM_FEATURE_CRYPTO, which the compiler
defines only when the -mcpu Zephyr derived actually has the extension; that
covers Cortex-M by the same test.

ML-KEM's small-memory key generation and encapsulation are dropped where the
assembly is on for x86_64 or AArch64. wc_mlkem.c rejects that combination with
an #error rather than falling back, so CONFIG_WOLFSSL_MLKEM together with this
option did not compile at all - and wolfssl_tls_sock already sets it.

32-bit x86 is not offered. Only the AES sources have an implementation at
that width; sha256_asm.S, chacha_asm.S and poly1305_asm.S are all guarded to
WOLFSSL_X86_64_BUILD and would contribute nothing.

The x86_64 single-precision assembly is not offered either, and that one is
worth recording. sp_x86_64_asm.S is AVX throughout - over fifteen hundred VEX
instructions - and sp_x86_64.c calls into it unconditionally, with no CPUID
dispatch and no scalar counterpart. Zephyr's x86 context switch is
fxsave/fxrstor and it never sets CR4.OSXSAVE, so YMM state is neither saved
nor enabled and those instructions fault. The symmetric sources are fine
because they pick their implementation from CPUID at run time and now fall
back correctly.

Both set(TOOLCHAIN_C_FLAGS ...) calls go as well. They could never have had
an effect: Zephyr applies that variable globally at zephyr/CMakeLists.txt:425
and only add_subdirectory's modules at line 788, so the assignment ran after
the flags were consumed, and a plain set() in a subdirectory scope never
reached the parent either. Confirmed by forcing the assignment to run
unconditionally and finding neither -mcpu=cortex-a53+crypto nor
-mstrict-align anywhere in the resulting compile database.

HAVE___UINT128_T is defined wherever the compiler has the type. Every 64-bit
single-precision backend works in 128-bit intermediates; an autoconf build
learns the type is available from a configure probe, but with user settings
nobody sets it and sp_int.c fails on an undeclared sp_int_word. AArch64 needs
this as much as x86_64 does - qemu_cortex_a53 stops there before it ever
reaches the assembler.

One wolfssl_test scenario per port now covers the option rather than testing
it by hand: qemu_x86_64 for Intel, qemu_cortex_a53 for AArch64, and
mps2/an521/cpu0, a Cortex-M33, for Thumb2. They do not run in this
repository's own Zephyr workflow, which builds against 2.7.4, 3.4.0 and 3.5.0
- those predate hierarchical board names, and the job installs only the
x86_64 toolchain - but they run against any modern Zephyr. The aarch64 one
earned its keep immediately by catching an unused-constant error in the
generated ML-KEM assembly, fixed earlier in this branch.

Measured on frdm_rw612 with the benchmark, ops/sec: ECDSA P-256 sign 16 to
136, verify 10 to 102, RSA-2048 public 86 to 194. Thumb2 is worth 1.5x on
AES-CBC and 1.3x on ChaCha20, and costs about 5% on SHA-256. wolfcrypt_test
passes on frdm_rw612 and on all three QEMU targets with the option set.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants