diff --git a/.github/workflows/README.md b/.github/workflows/README.md index 368cdf15..7a6f5b07 100644 --- a/.github/workflows/README.md +++ b/.github/workflows/README.md @@ -1,7 +1,8 @@ # wolfTrust CI -CI has a fast per-PR host lane, the tiered M33MU emulator matrix, and static -analysis. M33MU runs a per-port smoke set on every pull request and the full +Three lanes, modeled on wolfProvider's CI: a fast per-PR host lane, the M33MU +emulator matrix, and the AArch64 QEMU matrix, plus static analysis. Both +emulator lanes are tiered: a smoke set on every pull request, and the full matrix on labels, main pushes, and nightly. `codeql.yml` runs C security queries. `coverity.yml` runs on Sundays at @@ -22,7 +23,9 @@ An accepted upload queues analysis; results appear after Coverity processes it. |------|---------|---------| | **Fast (per-PR)** | every PR; push to main | host unit suites, ISO C99, house style, bare-scope scan, Arm PSA-FF conformance, cross-compile, compiler matrix, sanitizers, valgrind, integrations, core/port split guard | | **M33MU smoke** | every PR | per port, on both crypto engines: STM32H563 `positive gtzcneg crossdomain bothpsa confboot devcrypto`; MIMXRT700 `positive ahbscneg crossdomain bothpsa confboot devcrypto` | -| **M33MU full** | PR labels `ci:all`, `ci:h5`, `ci:rt700`; push to main; `cron: 0 8 * * *`; `workflow_dispatch` (port input) | every scenario of that port on both engines (see below) | +| **M33MU full** | PR labels `ci:all`, `ci:stm32h563`, `ci:imxrt700`; push to main; `cron: 0 8 * * *`; `workflow_dispatch` (port input) | every scenario of that port on both engines (see below) | +| **AArch64 smoke** | every PR | a representative subset: `virt-gicv3-a72` `smoke boot positive crossdomain ffa-direct confboot devcrypto` on both crypto engines, and `versal-virt` the same on the native engine | +| **AArch64 full** | PR labels `ci:all`, `ci:aarch64`, `ci:qemu-virt`, `ci:qemu-versal`; push to main; nightly; `workflow_dispatch` (cell input) | every AArch64 scenario and the Arm FF-A ACS groups on three QEMU cells under both crypto engines (see below) | The M33MU workflow (`m33mu.yml`) is label-selected the way wolfProvider's `pr-osp-select.yml` is: a `select` job checks the routing table @@ -33,8 +36,8 @@ groups it picked, so a job that is not selected never appears as skipped. | Label | Effect | |-------|--------| | (no label) | each port's smoke tier on both engines | -| `ci:h5` | the STM32H563 full matrix (the change touched only that port) | -| `ci:rt700` | the MIMXRT700 full matrix (the change touched only that port) | +| `ci:stm32h563` | the STM32H563 full matrix (the change touched only that port) | +| `ci:imxrt700` | the MIMXRT700 full matrix (the change touched only that port) | | `ci:all` / `ci:m33mu` | every scenario of every port (a core change) | A label keeps applying on later pushes to the PR. Pushes to @@ -86,6 +89,37 @@ full matrix on a PR, add its `ci:` label; off-PR against a branch, The local box gate `run_m33mu.sh` (a Zephyr+FreeRTOS lifecycle) and the `make test-target` loop remain the pre-push mirror of the M33MU jobs. +## AArch64 QEMU + +`aarch64-cross-compile.yml` (workflow name **AArch64 cross compilation**) is +label-selected the same way `m33mu.yml` is: a `select` job self-tests the +matrix, reads the PR's `ci:*` labels, the event, and the dispatch input, and +one matrix job runs exactly the cells it picked. It runs in +`ghcr.io/wolfssl/wolfboot-ci-aarch64` on three cells, `virt-gicv2-a35`, +`virt-gicv3-a72`, and `versal-virt`. + +| Label | Effect | +|-------|--------| +| (no label) | the smoke tier: `virt-gicv3-a72` (both engines) and `versal-virt` (native) run the catch-most subset | +| `ci:qemu-virt` | the two `virt` cells' full suite and FF-A ACS (the change touched only that family) | +| `ci:qemu-versal` | the `versal-virt` full suite and FF-A ACS | +| `ci:aarch64` | every cell's full suite and FF-A ACS (a core AArch64 change) | +| `ci:all` | this plus the full M33MU matrix | + +A label keeps applying on later pushes to the PR. Pushes to `main`, the nightly +schedule (via `nightly.yml`), and manual dispatch (with a `cell` input) run the +full matrix. The cells and the smoke subset live in +`tests/target/lib/scenario_matrix.py`, which `make test-target-a` also reads +(`WT_TIER=full` for the whole suite). + +| Check name | What it proves | +|------------|----------------| +| `el3__` | the EL3 image links under the symbol guard, then the tier's QEMU AArch64 scenarios run through `tests/target/run_suite.sh qemu-a` | +| `ffa_acs__` | the Arm FF-A ACS groups (discovery, direct and indirect messaging, memory, notifications, interrupts) at their asserted floors (full tier only) | + +To run a scenario locally, use `tests/target/run_qemu_a_scenario.sh ` +with `MACHINE`, `GIC`, `CPU`, and `WT_ENGINE` set as in the workflow. + ## Host unit suites (per-suite checks) `unit-tests.yml` reads `UNIT_SUITES` from `tests/host/Makefile` (via diff --git a/.github/workflows/_resolve-wolfssl.yml b/.github/workflows/_resolve-wolfssl.yml index e0e8e978..0953c6c3 100644 --- a/.github/workflows/_resolve-wolfssl.yml +++ b/.github/workflows/_resolve-wolfssl.yml @@ -15,7 +15,7 @@ permissions: jobs: resolve: - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 timeout-minutes: 5 outputs: matrix: ${{ steps.refs.outputs.matrix }} diff --git a/.github/workflows/aarch64-cross-compile.yml b/.github/workflows/aarch64-cross-compile.yml new file mode 100644 index 00000000..3c1a79fe --- /dev/null +++ b/.github/workflows/aarch64-cross-compile.yml @@ -0,0 +1,113 @@ +name: AArch64 cross compilation + +on: + # Pull requests run the AArch64 smoke tier; the ci:all, ci:aarch64, + # ci:qemu-virt, and ci:qemu-versal labels, pushes to main, the nightly + # schedule (workflow_call from nightly.yml), and manual dispatch run the full + # cell matrix and the FF-A ACS. + pull_request: + types: [opened, synchronize, reopened, labeled] + push: + branches: [main] + workflow_dispatch: + inputs: + cell: + description: AArch64 cell or family whose full matrix to run + type: choice + options: [all, qemu-virt, qemu-versal, virt-gicv2-a35, virt-gicv3-a72, versal-virt] + default: all + workflow_call: + +permissions: + contents: read + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + # Label-selected the same way m33mu.yml is: one select job reads the ci:* + # labels (ci:qemu-virt or ci:qemu-versal for a family's full matrix, ci:all + # or ci:aarch64 for every cell, no label for the smoke tier), the event, and + # the dispatch input, then one matrix job runs exactly those cells. + select: + name: qemu_a_select + runs-on: ubuntu-latest + outputs: + matrix: ${{ steps.p.outputs.matrix }} + steps: + - uses: actions/checkout@v4 + + - id: p + env: + PR_LABELS: ${{ join(github.event.pull_request.labels.*.name, ' ') }} + CELL_INPUT: ${{ inputs.cell }} + run: | + python3 tests/target/lib/scenario_matrix.py --selftest + matrix=$(python3 tests/target/lib/scenario_matrix.py --aarch64-plan) + echo "matrix=$matrix" >> "$GITHUB_OUTPUT" + printf '%s' "$matrix" | python3 -c 'import json, sys + for e in json.load(sys.stdin): + print(e["job"], "(" + e["engine"] + ")")' + + qemu-a: + name: ${{ matrix.job }} + needs: select + runs-on: ubuntu-latest + # The serial suite takes 15-18 min per cell on the reference box; the + # per-scenario QEMU budgets are hang detectors, not expected runtimes. + timeout-minutes: 75 + container: + image: ${{ matrix.image }} + strategy: + fail-fast: false + matrix: + include: ${{ fromJSON(needs.select.outputs.matrix) }} + env: + WT_ENGINE: ${{ matrix.engine }} + MACHINE: ${{ matrix.machine }} + GIC: ${{ matrix.gic }} + CPU: ${{ matrix.cpu }} + SMP: ${{ matrix.smp }} + steps: + - uses: actions/checkout@v4 + + - name: Trust the checked-out workspace + run: git config --global --add safe.directory "$GITHUB_WORKSPACE" + + - name: Initialize wolfTrust dependencies + run: | + git config --global url."https://github.com/".insteadOf "git@github.com:" + git submodule update --init --single-branch lib/wolfSSL lib/wolfHSM lib/wolfCOSE lib/wolfPSA + + - name: Toolchain and emulator versions + run: | + aarch64-none-elf-gcc --version | head -1 + qemu-system-aarch64 --version | head -1 + + - name: Self-test the EL3 symbol guard and the expect library + if: matrix.kind == 'suite' + run: | + tools/check-el3-symbols.sh --selftest + tests/target/lib/expect.sh --selftest + + - name: Cross-build the EL3 monitor image (the symbol guard runs at link) + if: matrix.kind == 'suite' + run: make ARCH=aarch64 TARGET=${{ matrix.target }} WT_GIC_VERSION="$GIC" WT_CPU="$CPU" BUILD_DIR=build-aarch64-guard el3-image + + - name: Fetch the pinned Arm FF-A ACS once for the six groups + if: matrix.kind == 'acs' + run: | + tests/upstream/fetch_ffa_acs.sh "$GITHUB_WORKSPACE/.acs-cache" + echo "WT_FFA_ACS_CACHE=$GITHUB_WORKSPACE/.acs-cache" >> "$GITHUB_ENV" + + - name: Run the AArch64 scenarios + run: tests/target/run_suite.sh qemu-a ${{ matrix.scenarios }} + + - name: Show the boot logs + if: always() + run: | + for f in logs/target-*.log; do + echo "== $f" + cat "$f" || true + done diff --git a/.github/workflows/auto-pin-dependencies.yml b/.github/workflows/auto-pin-dependencies.yml index b2c82f98..db0ce123 100644 --- a/.github/workflows/auto-pin-dependencies.yml +++ b/.github/workflows/auto-pin-dependencies.yml @@ -11,7 +11,7 @@ permissions: jobs: pin: - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 timeout-minutes: 30 steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/core-port-split.yml b/.github/workflows/core-port-split.yml index 9ff7e4e6..539fa2b1 100644 --- a/.github/workflows/core-port-split.yml +++ b/.github/workflows/core-port-split.yml @@ -25,11 +25,13 @@ jobs: git config --global --add safe.directory "$GITHUB_WORKSPACE" git config --global url."https://github.com/".insteadOf git@github.com: - - name: Self-test the split and port-only-diff guards + - name: Self-test the guards and the scenario assertion library run: | tools/check-core-port-split.sh --selftest tools/check-port-only-diff.sh --selftest tools/check-docs-no-internal-links.sh --selftest + tools/check-el3-symbols.sh --selftest + tests/target/lib/expect.sh --selftest - name: Enforce the core/port split (strict) run: WT_SPLIT_STRICT=1 tools/check-core-port-split.sh diff --git a/.github/workflows/fuzz.yml b/.github/workflows/fuzz.yml index 305d93cb..4b84a6f6 100644 --- a/.github/workflows/fuzz.yml +++ b/.github/workflows/fuzz.yml @@ -17,7 +17,7 @@ jobs: # type here: schedule / manual dispatch get the full 600s soak; PR pushes and # merges get the 60s smoke that keeps the required checks fast. select: - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 outputs: matrix: ${{ steps.pick.outputs.matrix }} steps: @@ -39,7 +39,7 @@ jobs: fuzz: name: fuzz_${{ matrix.name }} needs: select - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 timeout-minutes: 20 strategy: fail-fast: false diff --git a/.github/workflows/integration-tests.yml b/.github/workflows/integration-tests.yml index 1bc1180f..b762e903 100644 --- a/.github/workflows/integration-tests.yml +++ b/.github/workflows/integration-tests.yml @@ -22,7 +22,7 @@ jobs: wolfcose: name: wolfcose_integration_${{ matrix.name }} needs: resolve - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 container: image: ghcr.io/wolfssl/wolftrust-ci:latest-beb806af7628e02acee729632a74a7a3630f21fd timeout-minutes: 30 @@ -69,7 +69,7 @@ jobs: wolfpsa: name: wolfpsa_integration_${{ matrix.name }} needs: resolve - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 container: image: ghcr.io/wolfssl/wolftrust-ci:latest-beb806af7628e02acee729632a74a7a3630f21fd timeout-minutes: 30 diff --git a/.github/workflows/m33mu.yml b/.github/workflows/m33mu.yml index 6b1d822b..91bdd238 100644 --- a/.github/workflows/m33mu.yml +++ b/.github/workflows/m33mu.yml @@ -1,7 +1,7 @@ name: M33MU on: - # Pull requests run each port's smoke tier; the ci:all, ci:h5, and ci:rt700 + # Pull requests run each port's smoke tier; the ci:all, ci:stm32h563, and ci:imxrt700 # labels, pushes to main, the nightly schedule (workflow_call from # nightly.yml), and manual dispatch run a port's full matrix. pull_request: @@ -280,7 +280,7 @@ jobs: fi # Label-selected, the wolfProvider way: one select job reads the ci:* labels - # (ci:h5 or ci:rt700 for that port's full matrix when only that port + # (ci:stm32h563 or ci:imxrt700 for that port's full matrix when only that port # changed, ci:all or ci:m33mu for everything after a core change, no label # for each port's smoke tier), the event, and the dispatch input, then one # matrix job runs exactly those scenario groups, so nothing shows up skipped. diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index 9d9f3965..6099421f 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -37,6 +37,9 @@ jobs: cross_compile: uses: ./.github/workflows/cross-compile.yml + aarch64_cross_compile: + uses: ./.github/workflows/aarch64-cross-compile.yml + core_port_split: uses: ./.github/workflows/core-port-split.yml diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml index 32cc1858..83d788a8 100644 --- a/.github/workflows/unit-tests.yml +++ b/.github/workflows/unit-tests.yml @@ -31,7 +31,7 @@ jobs: host-unit-tests: name: ${{ matrix.suite }} needs: discover - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 container: image: ghcr.io/wolfssl/wolftrust-ci:latest-beb806af7628e02acee729632a74a7a3630f21fd timeout-minutes: 20 @@ -52,7 +52,7 @@ jobs: name: arm_psa_ff_conformance # Bare runner, not the CI image: the image inherits m33mu, and # make test-conformance takes the emulator path when m33mu is on PATH. - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 timeout-minutes: 20 steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/wiki-sync.yml b/.github/workflows/wiki-sync.yml index 9938adbc..3da0b42d 100644 --- a/.github/workflows/wiki-sync.yml +++ b/.github/workflows/wiki-sync.yml @@ -17,7 +17,7 @@ concurrency: jobs: sync: - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - uses: actions/checkout@v4 - name: Publish docs/ to wiki diff --git a/.gitignore b/.gitignore index fe2d02fa..52d5d089 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,11 @@ # Build outputs /build/ +/build-aarch64-*/ tests/firmware/stm32h563/build/ tests/firmware/stm32h563-vnet/build/ tests/firmware/mimxrt700-baremetal/build/ +tests/firmware/aarch64-smoke/build/ +tests/firmware/aarch64-ns-smoke/build/ tests/firmware/stm32h563-vnet/stm32h563_OTP.bin *.o /wolftrust @@ -22,6 +25,11 @@ tests/fuzz/crash-* tests/fuzz/oom-* tests/fuzz/timeout-* +# Target runner logs and the CI's FF-A ACS clone cache +/ci-*.log +/h5-uart-capture.log +/.acs-cache/ + # Stray duplicate at repo root /stm32h563_OTP.bin diff --git a/Makefile b/Makefile index 2853fc46..8d048c23 100644 --- a/Makefile +++ b/Makefile @@ -21,7 +21,7 @@ include mk/common.mk .DEFAULT_GOAL := all -.PHONY: all secure-image size-report test c99-check test-conformance test-target test-hardware fetch-psa-ff-tests \ +.PHONY: all secure-image size-report test c99-check test-conformance test-target test-target-a test-hardware fetch-psa-ff-tests \ clean firmware-stm32h563 run-stm32h563 run-stm32h563-tui run-stm32h563-uarts \ test-domain-host test-domain-compilers test-domain-sanitize \ test-domain-valgrind test-manifest-host test-manifest-compilers \ @@ -83,6 +83,20 @@ else fi endif +# AArch64 twin of test-target on QEMU (virt GICv2/GICv3, xlnx-versal-virt); +# auto-detect qemu-system-aarch64 + aarch64-none-elf (or WT_TARGET_SCENARIOS=1), +# skip explicitly otherwise. MACHINE/GIC/CPU/SMP pass through to the runner. +# WT_TIER=smoke (default) runs the per-PR subset, WT_TIER=full the whole suite, +# both from tests/target/lib/scenario_matrix.py (the FF-A ACS stays in CI). +WT_QEMU_A_SCENARIOS ?= $(shell python3 tests/target/lib/scenario_matrix.py --aarch64-flat --tier $(WT_TIER)) +test-target-a: + @if ! tests/target/detect_qemu_a.sh >/dev/null 2>&1; then \ + echo "SKIP: AArch64 QEMU scenarios ($$(tests/target/detect_qemu_a.sh 2>&1))"; \ + else \ + MACHINE="$(MACHINE)" GIC="$(GIC)" CPU="$(CPU)" SMP="$(SMP)" \ + tests/target/run_suite.sh qemu-a $(WT_QEMU_A_SCENARIOS); \ + fi + # Real STM32H563 hardware equivalence suite: positive lifecycle + restart # recovery + cross-domain isolation on a Nucleo-H563ZI, the on-silicon # counterpart of test-target. Needs the ST-Link + board (detect_h5.sh) and a @@ -147,6 +161,8 @@ clean: rm -rf $(BUILD_DIR) $(MAKE) -C tests/firmware/stm32h563 clean $(MAKE) -C tests/firmware/stm32h563-vnet clean + $(MAKE) -C tests/firmware/aarch64-smoke clean + $(MAKE) -C tests/firmware/aarch64-ns-smoke clean $(MAKE) -C tests/host/domain clean $(MAKE) -C tests/host/manifest clean $(MAKE) -C tests/host/lifecycle clean diff --git a/README.md b/README.md index 4800307e..f2aae065 100644 --- a/README.md +++ b/README.md @@ -1,35 +1,44 @@ # wolfTrust -wolfTrust is a Secure Partition Manager (SPM) and secure-services runtime -designed for Cortex-M targets. It separates reusable policy and service code -from architecture- and target-specific execution and protection code. The +wolfTrust is a Secure Partition Manager (SPM) and secure-services runtime for +Arm Cortex-M and Cortex-A targets. It separates reusable policy and service +code from architecture- and target-specific execution and protection code. The common runtime provides interprocess communication (IPC) through the Arm Platform Security Architecture (PSA) Firmware Framework for M (FF-M), manifest policy, lifecycle management, scheduling, fault recovery, and PSA services. -The only currently supported and validated reference implementation combines -the Armv8-M adapter with the STM32H563 Cortex-M33 port. Support for additional -Cortex-M ports is an intended extension point. Such ports may reuse the common -runtime and, where applicable, the Armv8-M layer. Cortex-A support is an -architectural goal, not a current capability. It will require a new adapter and -changes to current internal execution and protection contracts; the design goal -is to preserve the public manifest, service, IPC, and PSA API contracts. +Two architecture ports share that runtime: + +- **Armv8-M** with the STM32H563 Cortex-M33 port, the reference + implementation validated on hardware. +- **AArch64** (Cortex-A), a Trusted Firmware-A replacement: an EL3 monitor + that is the Arm Firmware Framework for A-profile (FF-A) Secure Partition + Manager Dispatcher (SPMD), a Secure EL1 Secure Partition Manager Core (SPMC) + that runs the same common runtime, and Secure Partitions at Secure EL0. + Normal-world clients reach the services over FF-A v1.2. This port is + validated under QEMU on `virt` (GICv2 and GICv3) and `xlnx-versal-virt`; no + Cortex-A silicon port is validated yet. See + [FF-A Compatibility](docs/FF-A-Compatibility.md). + +Both ports keep the public manifest, service, IPC, and PSA API contracts; +AArch64 manifests add an optional FF-A section. Additional Cortex-M and +Cortex-A ports are an intended extension point. ## Architecture ```mermaid flowchart TB - BOOT[Trusted first-stage loader
current reference: wolfBoot] + BOOT[Trusted first-stage loader
Armv8-M reference: wolfBoot] subgraph APP[Application domains] - GA[Cortex-M client A
Zephyr, FreeRTOS, or bare metal] - GB[Cortex-M client B
Zephyr, FreeRTOS, or bare metal] + GA[Client A
Cortex-M guest or Cortex-A Normal world] + GB[Client B
Cortex-M guest or Cortex-A Normal world] end subgraph PORT[Architecture and target ports] - GW[Client gateway
current: five Armv8-M CMSE veneers] - ARCH[Architecture adapter
current: Armv8-M] - TARGET[Target and board port
current: STM32H563] + GW[Client gateway
Armv8-M: five CMSE veneers
AArch64: FF-A through the EL3 SPMD] + ARCH[Architecture adapter
Armv8-M, or AArch64 EL3 SPMD and Secure EL1 SPMC] + TARGET[Target and board port
STM32H563; QEMU virt and Versal] GW --- ARCH ARCH --- TARGET end @@ -74,10 +83,10 @@ flowchart TB ST --> WC AT --> COSE AT --> WC - TARGET -->|current register and RNG access| HAL + TARGET -->|STM32H563 register and RNG access| HAL ``` -### Current reference port +### Current reference ports On the STM32H563 reference chain, wolfBoot authenticates wolfTrust, Armv8-M TrustZone isolates the Secure runtime from Non-secure guests, and STM32 Global @@ -88,16 +97,26 @@ current reference port, not a requirement imposed on every intended port. ## Ports -wolfTrust currently supports two ports; each guide covers the board, the -first-stage loader contract, and the runners: +wolfTrust currently supports two Armv8-M ports and an AArch64 (Cortex-A) port; +each guide covers the board or machine, the first-stage loader contract, and +the runners: - STM32H563 (NUCLEO-H563ZI): [STM32H5 Guide](docs/STM32H5-Guide.md) - NXP MIMXRT700 (MIMXRT700-EVK): [MIMXRT700 Guide](docs/MIMXRT700-Guide.md) +- AArch64 (QEMU virt and xlnx-versal-virt): [Porting Guide](docs/Porting.md) -Both build with `TARGET=` (`stm32h563` is the default) and run the -same scenario set under the M33MU emulator with `make test-target +The Armv8-M ports build with `TARGET=` (`stm32h563` is the default) and +run the same scenario set under the M33MU emulator with `make test-target TARGET=`. +On the QEMU AArch64 cells, the EL3 monitor boots first, programs the GIC, and +enters the Secure EL1 SPMC, which runs each Secure Partition at Secure EL0 +under its own stage-1 translation table. A Normal-world payload at NS-EL1 +reaches the services through FF-A direct requests that carry the PSA client +calls. No wolfBoot AArch64 port exists yet, so these targets have no +authenticated boot or boot handoff record; the handoff region is empty and +the services that depend on it fail closed. + ## Quick start For the initial Secure build and host tests, install GNU Make, Python 3, Git, a @@ -123,6 +142,18 @@ Target runners assemble the wolfBoot-to-wolfTrust chain, patch signature-covered guest measurements into wolfTrust, and then sign the wolfTrust image. +The AArch64 build needs the `aarch64-none-elf-` toolchain, and its scenarios +need `qemu-system-aarch64`; the `ghcr.io/wolfssl/wolfboot-ci-aarch64` +container carries both: + +```sh +make ARCH=aarch64 TARGET=qemuvirt +make test-target-a +``` + +The AArch64 build produces the EL3 monitor (`build/wolftrust_el3.elf`) and +the Secure EL1 SPMC (`build/wolftrust.elf`). + Build both PSA reference guests with: ```sh @@ -137,6 +168,7 @@ Common validation entry points: make test make test-target make test-target TARGET=mimxrt700 +make test-target-a make test-conformance WT_H5_DOCKER_IMAGE=ghcr.io/wolfssl/wolfboot-ci-m33mu:v1.15 make test-hardware ``` @@ -144,7 +176,8 @@ WT_H5_DOCKER_IMAGE=ghcr.io/wolfssl/wolfboot-ci-m33mu:v1.15 make test-hardware `make test-target` runs the port's smoke tier under M33MU (`WT_TIER=full` for every scenario): the STM32H563 default skips explicitly when M33MU is unavailable, and `TARGET=mimxrt700` builds its pinned emulator and wolfBoot -first stage itself. +first stage itself. `make test-target-a` runs the AArch64 scenarios under QEMU +and skips explicitly when QEMU or the AArch64 toolchain is unavailable. `make test-conformance` instead runs its 20-test host subset and warns that it is not full emulator or hardware evidence. `make test-hardware` skips when board detection fails; on hosts without `lsusb`, a missing ST-Link can instead @@ -160,11 +193,13 @@ documentation source: - [Getting Started](docs/Getting-Started.md) - [Architecture](docs/Architecture.md) +- [Crypto Engines](docs/Crypto-Engines.md) - [Security Model](docs/Security-Model.md) - [Threat Model](docs/Threat-Model.md) - [API Reference](docs/API-Reference.md) - [Services](docs/Services.md) - [TF-M Compatibility](docs/TF-M-Compatibility.md) +- [FF-A Compatibility](docs/FF-A-Compatibility.md) - [Macros](docs/Macros.md) - [Porting](docs/Porting.md) - [Building](docs/Building.md) diff --git a/docs/Architecture.md b/docs/Architecture.md index 697ace94..39880f1c 100644 --- a/docs/Architecture.md +++ b/docs/Architecture.md @@ -7,12 +7,13 @@ Secure service dispatch. Architecture and target ports provide the execution, memory-protection, interrupt, storage, entropy, and boot mechanisms needed to enforce that policy. -The only currently supported and validated reference implementation combines -the Armv8-M adapter with the STM32H563 port. Its configuration runs two -Non-secure guests on one Cortex-M33 and exposes Secure services only through -FF-M IPC. These are current reference-port choices. The common policy and -service design is intended for reuse, but current internal contracts still -contain Armv8-M-specific types and assumptions. +Two architecture ports run the same policy and service code. The Armv8-M +adapter with the STM32H563 port is the reference implementation validated on +hardware; it runs two Non-secure guests on one Cortex-M33 and exposes Secure +services only through FF-M IPC. The AArch64 adapter replaces Trusted +Firmware-A on Cortex-A and is validated under QEMU; see +[AArch64 architecture](#aarch64-architecture). The boot flow, isolation, and +scheduling sections below describe the Armv8-M reference port. ## Software stack @@ -28,20 +29,22 @@ contain Armv8-M-specific types and assumptions. ## Portability boundary -Most policy and service code lives under `src/` outside `src/arch/`, but the -current internal contracts still expose Armv8-M exception-frame and -MPU-oriented types. `src/arch/armv8m/` supplies the CMSE gateway and pointer -checks, Secure Partition coroutine switching, and the Secure SVC transport. -The companion `port/stm32h563/` supplies device startup and guest exception -paths, context handling, GTZC attribution, guest and Secure MPU programming, -interrupt routing, flash, entropy, timers, and boot handoff. - -Additional Cortex-M ports may reuse the existing interfaces when their -execution and protection models match. Cortex-A support is an architectural -goal, not a current capability. It will require a new adapter and changes to -current internal execution and protection contracts; the design goal is to -preserve the public manifest, service, IPC, and PSA API contracts. See -[Porting](Porting.md) for the current boundary. +Policy and service code lives under `src/` outside `src/arch/` and names no +architecture or SoC. It reaches the hardware through two contracts: +`wolftrust/arch.h` (`wt_arch_*`: execution, opaque trap frames, protection +domains, interrupts) and `wolftrust/platform.h` (`wt_platform_*`: device, +console, flash, entropy, and boot handoff). `src/arch/common/` holds the +Secure Partition gate, scheduler, and FF-M gateway bodies every architecture +links. + +`src/arch/armv8m/` supplies the CMSE gateway and pointer checks, Secure +Partition coroutine switching, and the Secure SVC transport; the companion +`port/stm32h563/` supplies device startup and guest exception paths, GTZC +attribution, guest and Secure MPU programming, interrupt routing, flash, +entropy, timers, and boot handoff. `src/arch/aarch64/` supplies the EL3 +monitor, the FF-A layer, the Secure EL1 SPMC, and the GIC drivers; the QEMU +ports are `port/qemuvirt/`, `port/versal/`, and the shared +`port/common/aarch64/`. See [Porting](Porting.md) for the boundary. ## Boot flow @@ -162,3 +165,39 @@ lifecycle value fails closed. See [Security Model](Security-Model.md), [Services](Services.md), and [Threat Model](Threat-Model.md) for the security properties built on this design. + +## AArch64 architecture + +The AArch64 port replaces Trusted Firmware-A with the FF-A configuration of +an SPMD at EL3, an SPMC at Secure EL1, and Secure Partitions at Secure EL0, +with no Normal-world Hypervisor. [FF-A Compatibility](FF-A-Compatibility.md) +lists the implemented interfaces and the intentional differences. + +| Level | Component | Role | +| --- | --- | --- | +| EL3 | Monitor and SPMD (`src/arch/aarch64/el3/`, `ffa/ffa_spmd.c`) | Initializes the GIC and the secure timer, switches worlds, answers PSCI, and relays FF-A calls between the Normal world and the SPMC. Its archive holds no SPM, service, or crypto code. | +| Secure EL1 | SPMC (`src/arch/aarch64/spm/`) | Runs the common runtime (manifest, services, scheduler, FF-M gateway), builds each partition's stage-1 translation table, and implements FF-A messaging, memory sharing, notifications, and interrupt handling. | +| Secure EL0 | Secure Partitions | Each runs under its own stage-1 table and ASID, enters by exception return, and calls the SPMC by SVC. | +| NS-EL1 | Normal world | Calls FF-A by SMC; PSA client calls ride FF-A direct requests to the SPMC's framework endpoint. | + +On the QEMU targets, the monitor starts from the reset vector, builds the FF-A +boot-information blob, places the SPMC image in its band, and enters Secure +EL1. The SPMC turns on its MMU, validates the manifest, starts the services, +runs each Secure Partition's initialization at Secure EL0 until it calls +`FFA_MSG_WAIT`, and then completes its own initialization with +`FFA_MSG_WAIT`, after which the monitor launches the Normal world. + +A Normal-world PSA call is an FF-A direct request that the SPMD relays to the +SPMC. The SPMC's FF-M gateway validates the handle, copies the caller's +vectors through the Normal-world window, and dispatches the request to the +owning partition; the reply returns as a direct response. Partition stage-1 +tables are 4 KB-granule, refuse writable and executable mappings, and map +data execute-never. A partition that touches memory outside its domain takes +a data abort at Secure EL0 and is restarted or quarantined by its manifest +policy. + +The secure timer and Secure interrupts are Group 0 and reach the SPMC as FIQs +while Secure code runs. The SPMC signals a Secure interrupt to a waiting +owner and queues it for a running one. A Non-secure interrupt either preempts +the Secure world back to the Normal world or stays pending, per the running +partition's manifest action. diff --git a/docs/Building.md b/docs/Building.md index 65c7dfd2..984ba88e 100644 --- a/docs/Building.md +++ b/docs/Building.md @@ -1,9 +1,11 @@ # Building -The supported Secure build tuple is Armv8-M on STM32H563. The root Makefile -includes `mk/target-stm32h563.mk`, `mk/arch-armv8m.mk`, and `mk/common.mk` -(target facts, architecture facts, and the shared build in that order) and -cross-compiles a freestanding Cortex-M33 image. +The default Secure build tuple is Armv8-M on STM32H563; `ARCH=aarch64` builds +the Cortex-A monitor and SPMC instead (see +[AArch64 monitor and SPMC images](#aarch64-monitor-and-spmc-images)). The root +Makefile includes `mk/target-.mk`, `mk/arch-.mk`, and +`mk/common.mk` (target facts, architecture facts, and the shared build in that +order) and cross-compiles a freestanding image. ## Prerequisites @@ -12,6 +14,9 @@ cross-compiles a freestanding Cortex-M33 image. - Git and initialized submodules - GNU Arm Embedded tools with the `arm-none-eabi-` prefix - a native C compiler for host tests +- for the AArch64 QEMU scenarios, `qemu-system-aarch64` and a toolchain with + the `aarch64-none-elf-` prefix; CI uses `ghcr.io/wolfssl/wolfboot-ci-aarch64`, + which also runs locally through Docker Some submodule URLs use GitHub SSH. Configure GitHub SSH access or an equivalent Git URL rewrite before initializing them. @@ -76,9 +81,35 @@ tuning variables (`WT_VNET_POOL_SLOTS`, `WT_VNET_FRAME_MAX`, `WT_CONF_DIAG_TRAP` variables. Use a fresh `BUILD_DIR` or clean the active output directory before changing an option that the recipe does not record. +## AArch64 monitor and SPMC images + +```sh +make ARCH=aarch64 TARGET=qemuvirt # virt, GICv3, cortex-a72 +make ARCH=aarch64 TARGET=qemuvirt WT_GIC_VERSION=2 WT_CPU=cortex-a35 +make ARCH=aarch64 TARGET=versal # WT_VERSAL_VIRT=1 today +``` + +The AArch64 build produces two images. `el3-image` is the monitor: the +archive `build/libwt_el3.a` (audited by `tools/check-el3-symbols.sh` at link +time) linked whole into `build/wolftrust_el3.elf` and `build/wolftrust_el3.bin`. +`secure-image` is the Secure EL1 SPMC, `build/wolftrust.elf` and +`build/wolftrust.bin`: the neutral core, the services, wolfCrypt and the selected +[crypto engine](Crypto-Engines.md), +the AArch64 Secure EL1 layer, and the port, linked by +`src/arch/aarch64/spm/wolftrust.ld` into the SPM image, RAM, and keystore +bands the shared layout `port/common/aarch64/l3_layout.h` places. On QEMU virt the runner places the +SPMC image behind the monitor in the pflash image (`WT_SPM_FLASH_OFFSET`) +and the monitor copies it to its band; on versal-virt the QEMU loader places +the ELF. The QEMU ports share `port/common/aarch64/` (platform operations, +partition tables, a RAM-backed wolfHSM NVM, and a test entropy source that +silicon ports must replace). The `ghcr.io/wolfssl/wolfboot-ci-aarch64` +container carries the toolchain and QEMU; `make test-target-a` boots the +result. + ## Manifest generation -The default input is `port/stm32h563/manifest.json`. +The default input is `port/stm32h563/manifest.json`; AArch64 targets use +`port/qemuvirt/manifest.json` and `port/versal/manifest.json`. `CONFIG_VNET=y` selects `manifest-vnet.json`, and `WT_CONFORMANCE=1` selects `manifest-conformance.json`. @@ -88,9 +119,29 @@ make WT_CONFORMANCE=1 ``` The generator is constrained to FF-M framework version `0x0100`, -feature mask `0x1` (connection-based IPC), and 32-bit addresses. +feature mask `0x1` (connection-based IPC), and 32-bit addresses on Armv8-M. Unsupported capabilities or an invalid resource layout stop the build. +AArch64 manifests (`--address-bits 64`) may add an optional top-level +`ffa` section with one entry per Secure Partition domain: the FF-A +partition properties of DEN0077A Table 5.1 (`ffa_version`, `uuids`, +`execution_contexts`, `runtime_el`, `messaging`, `ns_interrupt_action`, +`boot_info_register`). +The generator accepts only what the SPMC implements for these partitions: FF-A +`1.2`, one execution context, `S-EL0`, messaging `none` (their services are reached +through the SPMC's PSA endpoint, not by FF-A messages to the partition), +`signaled`, and boot information register `none` (the SPMC hands these +partitions no FF-A boot information blob); any other value stops the build. +The generator emits them as a separate `wt_generated_ffa_partitions` table +declared by `wolftrust/arch/aarch64/ffa_manifest.h`; a 64-bit manifest +without the section gets an empty table (count 0), 32-bit output is +unchanged, and a 32-bit target rejects the section. 64-bit targets also get +`WT_GENERATED_TABLE_POOL_PAGES` in the generated header: the 4 KB pages the +stage-1 tables need (one table per partition, sized from its memory resources +and stack, plus the SPMC's own table pages from `--spm-table-pages` and a spare +set). The count is a lower bound, and the generated source fails the build when +the target's `WT_SPM_TABLE_POOL_PAGES` is smaller. + ## Build controls Examples: @@ -125,6 +176,10 @@ make BUILD_DIR=build-no-lto WT_LTO=0 size-report Record the LTO setting with published size results. The locally measured TF-M v2.1.1 comparison builds did not use LTO. +The AArch64 build keeps `WT_LTO=0` and refuses `1`: its linker script places +the isolation bands, and the EL3 symbol guard audits the monitor archive, by +object name. + Changing guest count, addresses, or sizes also requires matching manifest, guest linker, emulator-load, flash, and measurement-record settings. See [Macros](Macros.md) for the supported values and constraints. diff --git a/docs/Crypto-Engines.md b/docs/Crypto-Engines.md index c677aa3c..954e1f41 100644 --- a/docs/Crypto-Engines.md +++ b/docs/Crypto-Engines.md @@ -14,6 +14,11 @@ recovery path. The STM32H563 manifest requests isolation profile 3 in both builds. That value is wolfTrust's validated policy profile, not proof of independent TF-M Level 3 code and data isolation. +On AArch64 the same selector builds the Secure EL1 SPMC and the Normal-world +payload, and the boundary is FF-A rather than CMSE veneers. Both engines run +the QEMU AArch64 scenarios except `hsmattackneg`, which drives the wolfHSM +wire and is skipped under the native engine. + ## At a glance | | Native crypto engine | wolfHSM engine | diff --git a/docs/FF-A-Compatibility.md b/docs/FF-A-Compatibility.md new file mode 100644 index 00000000..1f11bde3 --- /dev/null +++ b/docs/FF-A-Compatibility.md @@ -0,0 +1,112 @@ +# FF-A Compatibility + +wolfTrust implements the Arm Firmware Framework for A-profile (FF-A, DEN0077A +v1.2) needed to run secure partitions on its AArch64 targets, without linking +Trusted Firmware-A, Hafnium, or any other FF-A implementation. The EL3 monitor +is the SPMD, an S-EL1 component is the SPMC, and the secure partitions run at +S-EL0. The Normal world runs without a Hypervisor. + +This register describes the code in the repository. It is not a certification +statement. Conformance is measured against the Arm FF-A Architecture Compliance +Suite (ACS), pinned at `tests/upstream/ffa-acs.rev`; the results and the +by-design deviations are below. + +## Compatibility register + +| Interface or behavior | Version | Status | Repository evidence | +| --- | --- | --- | --- | +| Version negotiation | FF-A 1.2 | `FFA_VERSION` answers a compatible or later version with 1.2 and refuses one below major 1, and locks the version the caller settles on after its first other call; an ABI introduced after the caller's negotiated version (notifications, `FFA_MSG_SEND2`, `FFA_SPM_ID_GET` from 1.1; `FFA_MSG_SEND_DIRECT_REQ2`/`RESP2`, `FFA_PARTITION_INFO_GET_REGS`, `FFA_CONSOLE_LOG` from 1.2) is `NOT_SUPPORTED` for that caller at every instance, in `FFA_FEATURES` and when invoked (13.2.2); a caller that never invokes `FFA_VERSION` is held to 1.2 from its first other call, since 13.2 names no default (interpretation) | `wt_ffa_version_negotiate`, `wt_ffa_fid_available` in `include/wolftrust/arch/aarch64/ffa_abi.h` | +| Feature and id discovery | 1.2 | `FFA_FEATURES`, `FFA_ID_GET`, `FFA_SPM_ID_GET` supported; `FFA_SPM_ID_GET` returns the SPMC id `0x8000` to the Normal world and to partitions, and the SPMD id `0x8001` at the Secure physical instance (13.11.2); both ids are IMPLEMENTATION DEFINED and unique (6.3), and partitions take `0x8002` up; `FFA_FEATURES` reports the schedule-receiver interrupt, the memory-retrieve NS-bit property, and a partition's one-page RX/TX buffer limit | `src/arch/aarch64/ffa/ffa_spmd.c`, `src/arch/aarch64/spm/spm_svc_glue.c` | +| Partition discovery | 1.2 | `FFA_PARTITION_INFO_GET` (buffer form) and `FFA_PARTITION_INFO_GET_REGS` (register form), Nil-UUID and by-UUID; each partition is listed under its live endpoint id with the properties it really has, so a PSA partition, whose services are reached through the PSA framework endpoint and the FF-M gate, advertises only its AArch64 execution state | `src/arch/aarch64/ffa/ffa_partinfo.c`, `wt_spm_partition_info` | +| RX/TX buffers | 1.2 | `FFA_RXTX_MAP`, `FFA_RXTX_UNMAP`, `FFA_RX_RELEASE`, with per-endpoint RX ownership; a partition maps its pair over Secure Normal memory it owns and may write, including memory donated to it, that no transaction covers and no manifest shares with another partition | mailbox helpers in `src/arch/aarch64/ffa/ffa_mem.c`, `wt_spm_mem_rxtx_ok` | +| Direct messaging | 1.2 | `FFA_MSG_SEND_DIRECT_REQ`/`RESP` (32 and 64), `FFA_MSG_SEND_DIRECT_REQ2`/`RESP2`, partition to partition and Normal world to partition; a request to an endpoint that does not take that kind of request, or from a partition that discovery does not list advertising sending it, is `DENIED`, one to an id that names no endpoint `INVALID_PARAMETERS`; a callee may complete a request with `FFA_SUCCESS` in place of a response; SBZ flag bits are ignored and cleared on relay, MBZ bits refused | `src/arch/aarch64/ffa/ffa_msg.c`, `src/arch/aarch64/spm/coroutine_aarch64.c` | +| Runtime model | 1.2 | `FFA_MSG_WAIT`, `FFA_RUN`, `FFA_YIELD`, `FFA_NORMAL_WORLD_RESUME`, `FFA_INTERRUPT`; the SP initialization model of 8.5: a partition initializes until `FFA_MSG_WAIT` (a PSA partition until it blocks in the FF-M gate), may meanwhile send a direct request to a partition that has initialized, is refused `FFA_YIELD`, `FFA_RUN`, and responses, and reports failure with `FFA_ERROR`, after which it waits (8.5 rule 3) and is never run again, a direct request or `FFA_RUN` to it `DENIED`, not in a state to handle one (Tables 14.14, 15.8, and 15.16, which keep `BUSY` for a receiver that is running, blocked, or preempted); it, and a partition out of service for good after a fault no restart policy covers, keep their ids and discovery entries, since ids are never reused (6.1 item 2), and give up their Secure interrupts, RX/TX pair, memory transactions, and notification bindings; a memory transaction or notification call naming the first is `DENIED`, and a direct request, `FFA_RUN`, memory transaction, or notification call naming the second `ABORTED` (Tables 14.14, 15.8, 16.12, 16.16, 16.20; DEN0140 Table 2.5), an indirect message to either `DENIED` (Table 15.4); `FFA_MSG_WAIT` hands the caller's RX buffer back unless a v1.2 caller sets the Retain RX Buffer Ownership flag (DEN0077A v1.2 REL0 Table 14.3; SBZ, and ignored, for an earlier caller), and a partition's `FFA_YIELD` naming an endpoint or a timeout, which Table 14.9 leaves to the partition managers, is `INVALID_PARAMETERS` | `src/arch/aarch64/ffa/ffa_runtime.c`, `src/arch/aarch64/spm/coroutine_aarch64.c` | +| Console log | 1.2 | `FFA_CONSOLE_LOG` (32 and 64) | `wt_ffa_spmd_console_call`, `ffa_console_log` | +| Memory management | DEN0140 1.2 | Share, lend, and donate; retrieve, relinquish, reclaim; several borrowers, the 1.2 32-byte access descriptor with implementation-defined bytes, permission and type rules, the zero and alignment-hint flags, the multi-borrower bypass flag (whose IMPLEMENTATION DEFINED action is that the retrieving borrower names itself alone), and a borrower naming the address ranges it maps the memory at; every descriptor is read at the access descriptor size it states and written in the one of the reader's negotiated version (16 bytes through 1.1, 32 from 1.2), a caller that negotiated v1.0 is read and answered in the v1.0 descriptor layout, and told the NS bit only if its `FFA_FEATURES` asked for it; SBZ fields and flag bits are ignored, MBZ ones refused; a PSA call's vectors reach only Normal-world memory the caller still has, never a page it lent or donated or that a partition now owns, and a page it shares only as the share left it | `src/arch/aarch64/ffa/ffa_mem.c`, `src/arch/aarch64/spm/spm_mem.c`, `wt_arch_ns_check_*` in `src/arch/aarch64/spm/platform_arch.c` | +| Fragmented memory transmission | DEN0140 4.1.2 | `FFA_MEM_FRAG_TX`/`FRAG_RX` for share, lend, donate, and retrieve requests from partitions and the Normal world; the handle is reserved with the first fragment and names the region once the descriptor is whole; every later fragment comes through the TX buffer the first one used, so a sender that unmaps its RX/TX pair mid-transfer has it aborted (`ABORTED` on its next `FFA_MEM_FRAG_TX`, DEN0140 4.1.2 rules 6 and 8), and a first fragment from a sender with a transfer still in progress is `BUSY` while that transfer goes on (rules 6 and 9) | `wt_ffa_mem_frag_*` in `src/arch/aarch64/ffa/ffa_mem.c`, `wt_spm_mem_frag_*` in `src/arch/aarch64/spm/spm_mem.c` | +| Notifications | 1.2 Ch.10 | Bitmap create and destroy, bind and unbind, set and get for partition, VM, and framework sources, `FFA_NOTIFICATION_INFO_GET`, and the schedule-receiver interrupt (SGI 8); `FFA_NOTIFICATION_GET`'s VM and Hypervisor flags, SBZ at the Non-secure physical instance (DEN0077A v1.2 REL0 Table 16.23), are ignored there; a sender out of service takes what it pended with the bindings that name it; a VM has its framework bitmap only from its create to its destroy (10.3), so an indirect message to it outside that is `DENIED` before its RX buffer is touched (Table 15.4). PSA partitions do not take FF-A notifications: discovery leaves their property bit 3 clear, `FFA_FEATURES` of a notification ABI and their own notification calls are `NOT_SUPPORTED` (10.7 rules 5 and 6), `FFA_NOTIFICATION_SET` naming one the receiver is `DENIED` (Table 16.20), and the other notification calls, which name only notification endpoints, refuse its id as unrecognized (`INVALID_PARAMETERS`) | `src/arch/aarch64/ffa/ffa_notif.c`, `src/arch/aarch64/spm/spm_main.c`, `wt_spm_notif_set` | +| Indirect messaging | 1.2 | `FFA_MSG_SEND2` from either world into the receiver's RX buffer, with the RX-buffer-full framework notification and per-partition send and receive properties; a message to a partition that does not take them, a PSA partition included, is `DENIED` and one to an id that names no endpoint `INVALID_PARAMETERS` (Table 15.4); the delivered header is written from the fields the SPMC validated, with the SPMC-identified sender and its SBZ words cleared; each header is read and written in the layout of its endpoint's negotiated version (7.1): 20 bytes through v1.1, and from v1.2 the 40 bytes of DEN0077A v1.2 REL0 Table 7.2, a reserved word and the receiver's UUID following the v1.1 fields, a payload moving behind a longer receiver header; the receiver owns the RX buffer only once an `FFA_NOTIFICATION_GET` has returned the RX-full notification (7.2.2.4.2 rule 2.1.1), so an `FFA_RX_RELEASE` before that is `DENIED` (Table 13.22) and an `FFA_MSG_WAIT` releases nothing | `src/arch/aarch64/ffa/ffa_msg.c`, `wt_spm_msg2_deliver` | +| Interrupts | 1.2 Ch.9 | Secure interrupts signaled to a waiting owner and queued for a running or blocked one (delivered as `FFA_INTERRUPT`); Non-secure interrupts preempt a partition whose manifest signals them and stay pending for one that queues them, and for the PSA partitions an FF-M call runs, since the PSA framework endpoint answers only with a response, never an `FFA_INTERRUPT` for `FFA_RUN` to resume (9.3.1.3); GICv2 and GICv3 | `src/arch/aarch64/spm/spm_irq.c`, `src/arch/aarch64/spm/coroutine_aarch64.c` | +| Memory permissions | DEN0140 2.8, 2.9 | `FFA_MEM_PERM_GET` during a partition's initialization over memory its manifest names or that was donated to it, and `FFA_MEM_PERM_SET` over the part of it that is its own Normal and Device memory: read-write, read-only, executable, and no access, a Device page or donated Non-secure memory never executable | `wt_spm_mem_perm_get`/`set` in `src/arch/aarch64/spm/spm_mem.c` | +| Boot information | 5.4 | Boot-info blob with an IMPDEF descriptor carrying the wolfBoot handoff | `src/arch/aarch64/ffa/ffa_boot_info.c` | +| Power management | PSCI 1.1 (DEN0022D.b) | The mandatory set for a Normal world on the boot core: `CPU_SUSPEND` (core standby), `CPU_OFF` (`DENIED`: the uniprocessor SPMC is resident), `CPU_ON`/`AFFINITY_INFO` (the Normal world's machine view, DEN0022 4.4, is the boot core alone: it is `ON`, and every other MPIDR, including a secondary the monitor keeps parked, is `INVALID_PARAMETERS`), `MIGRATE`/`MIGRATE_INFO_TYPE`/`MIGRATE_INFO_UP_CPU` (uniprocessor, not migrate capable: `MIGRATE` is `DENIED` for the boot core and `INVALID_PARAMETERS` for any other MPIDR), `SYSTEM_OFF`, `SYSTEM_RESET` (a machine cold reset through the port's reset hook, which does not return; `virt` drives its Secure PL061 restart line; `xlnx-versal-virt`, whose model leaves its CRP, CRF, APU, and PSM reset blocks unimplemented, is powered off with the reset exit code and powered on again by the runner, the power cycle DEN0022 5.11.1 describes), `PSCI_FEATURES`; an SMC64 call's `int32` result is sign-extended across `x0` (DEN0028 2.8, 5.1) and an SMC32 call's fills `w0`. A valid `CPU_SUSPEND`, `CPU_OFF`, `SYSTEM_OFF`, or `SYSTEM_RESET` is first sent to the SPMC as the DEN0077A 18.2.4 framework message (Table 18.6) and completes only on its Table 18.8 `SUCCESS`, any other answer making it `DENIED` with `x4`-`x17` preserved; the SPMC denies `CPU_OFF`, and while the message is outstanding the SPMD refuses the SPMC's `FFA_MSG_WAIT`, `FFA_YIELD`, and `FFA_NORMAL_WORLD_RESUME` with `DENIED` | `src/arch/aarch64/el3/psci.c`, `src/arch/aarch64/ffa/ffa_spmd.c` | +| SMC calling convention | SMCCC 1.2 (DEN0028) | `SMCCC_VERSION` reports 1.2 (discoverable through `PSCI_FEATURES`) and `SMCCC_ARCH_FEATURES` answers for itself and `SMCCC_VERSION` only, to a caller in either world; calls that return only `x0` preserve `x4`-`x17`; an SMC32 call is read as `w1`-`w7`; an AArch32 Normal-world EL1 beneath an NS-EL2 payload makes SMC32 calls as `R0`-`R7`, and an SMC64 id from it is unknown; unknown function ids return `-1`, sign-extended; wolfTrust's private monitor and SVC calls sit in the OEM range with the MBZ bits clear | `src/arch/aarch64/el3/monitor_calls.c`, `include/wolftrust/arch/aarch64/monitor_abi.h` | + +## Intentional differences + +Each row mirrors the deviation grammar of the internal FF-A alignment register. + +| Difference | Classification | Reason and impact | +| --- | --- | --- | +| The Normal world is one FF-A endpoint, id `0`, with no Hypervisor: the SPMD answers `FFA_ID_GET` with `0` and the SPMC refuses a direct request, an RX/TX unmap, or an RX release naming any other Normal-world id. | Scoped isolation model | The SPMD plays the Hypervisor's id-allocation role of section 6.1 for that one endpoint, which is the spec's no-Hypervisor configuration and how the ACS runs. Several managed Normal-world guests are a Cortex-M port feature; on AArch64 they would need a Hypervisor at NS-EL2. | +| Secure partitions are S-EL0 physical partitions only; there are no S-EL1 or logical partitions, and a single PE (secondaries parked). | Scoped isolation model | `FFA_PARTITION_INFO_GET` reports one execution context per partition; uni-processor migration semantics hold trivially, so the ACS `up_migrate_capable` test skips. | +| `FFA_PARTITION_INFO_GET` does not report a TF-A-style EL3 logical partition. | Scoped configuration | wolfTrust has no EL3 logical partition; the ACS `ffa_partition_info_get_lsp` test looks for one and is a recorded deviation. | +| A secure partition that donates memory loses its own EL0 access to it when it sends the donate, and may reclaim it until the receiver retrieves it; the retrieve completes the transfer and frees the handle. | Implementation detail | The donor's page entries are held EL1-only rather than unmapped, so a reclaim before retrieval restores each one exactly; DEN0140 1.9.2 frees a donate's handle at the end of a successful retrieval. | +| Memory the Normal world lends or donates to a partition stays reachable by a privileged Normal world for as long as the partition holds it; the SPMC records the transfer but cannot take the sender's own mapping away. | Trust assumption | With no Hypervisor the Normal-world kernel is the relayer for its own mappings and removes its own access itself (DEN0140 1.4.1). wolfTrust has no EL2 stage-2 translation, and no supported target programs a memory firewall over Non-secure RAM, so a Normal world that keeps or recreates the mapping can read and write the memory while a partition uses it, and can race the zero-before-retrieve flag (1.11.4.1). A partition must treat memory the Normal world lent, donated, or shared as untrusted input: copy it into its own memory before validating it, and keep no secret in it. | +| A partition cannot donate, lend, or share memory to the Normal world, not even Non-secure memory the Normal world donated to it; the send is `DENIED`. | Unsupported | DEN0140 Table 1.7 lists no SP-to-NS-Endpoint combination for these transactions, and rule 4 of 2.1.1.2, 2.2.1.2, and 2.3.1.2 answers one of Secure memory with `DENIED`, the code every such send gets. A partition gives memory back to the Normal world only by relinquishing what it was lent or shared (Table 1.8). | +| A partition bound to the memory relayer that faults is terminated, not restarted: what it borrowed is unmapped, what it lent or shared ends with its borrowers and never comes back to it, and pages it never sent stay in its table, which never runs again. | Implementation detail | DEN0140 1.3.1 rule 9 passes access to a terminated SP's memory to the SPM; the SPMC keeps its own S-EL1 entries for every page. | +| A partition the SPMC runs that is not bound to the memory relayer (a PSA partition) may be named a borrower, but takes no part in memory management, so it never retrieves and the owner reclaims. | Scoped product surface | Each such partition is an endpoint the SPMC manages (DEN0140 1.11.3.3); its memory-management calls are `DENIED`. | +| A borrower that names the address ranges to map the memory at (DEN0140 1.11.3.2) is mapped only when they are the region's own pages in order; any other ranges, a size other than the sender's, or an alignment hint with them is `INVALID_PARAMETERS`. The response then has no composite and a zero offset (1.11.3.3). | Scoped isolation model | S-EL0 partitions see memory at its physical address, so the relayer never maps a region elsewhere. | +| A retrieve's alignment hint n (bits[8:5] with bit[9] set) asks for a 2^n x 4 KB boundary, so hint 0 is 4 KB and hint 15 is 128 MB; a region not on that boundary is `DENIED`. | Interpretation | DEN0140 Table 1.22 prints the boundary as "2*n x 4KB", unchanged through 1.3 ALP5, which would make hint 0 no boundary at all; a power of two is the only reading under which every hint names one. Hints 1 and 2 give the same boundary under either reading, and the ACS asks only for hint 1. S-EL0 partitions see memory at its physical address, so the relayer never picks another one. | +| The receiver of a donate, and the one borrower of a lend, state the instruction access they want in their retrieve request: leaving it unspecified is `INVALID_PARAMETERS`, executable is `DENIED`, and the response reports not-executable. A share's borrowers, and a lend's several, leave it unspecified. | Interpretation | DEN0140 1.10.3 item 2 has the receiver specify its instruction access in `FFA_MEM_RETRIEVE_REQ`, answers an error with `INVALID_PARAMETERS`, and has the relayer deny an access its IMPLEMENTATION DEFINED mechanism does not allow, which here never maps a borrower executable; item 1 keeps b'00 for a share and a lend to several. Seven retrieves in six ACS single-borrower lend and donate servers leave it unspecified; patch 0015 has them state not-executable. | +| `FFA_MEM_PERM_SET` is `INVALID_PARAMETERS` on memory a transaction covers (DEN0140 1.3.1 rule 7), on code every partition runs, on memory a manifest shares with another partition, on a mapped RX/TX pair, and, read-only, on the writable manifest memory of a PSA partition (one not bound to the relayer); no access leaves the page the SPMC's to read and write, so it reads back execute-never. Non-secure memory donated to a partition is never made executable, since the Normal world can still write it. `DENIED` is kept for a call outside the partition's initialization, the only case Tables 2.37 and 2.41 give it. | Stronger isolation policy | The SPMC writes a partition's RX buffer, and the FF-M gate the buffers a PSA partition names anywhere in its writable manifest memory, at S-EL1 through the partition's own table, where an EL0 read-only page is read-only too; a partition bound to the relayer has only its RX buffer written that way. Shared memory is no one partition's to re-permission; Table 2.41 answers a region the caller may not re-permission with `INVALID_PARAMETERS`. SCTLR_EL1.WXN makes an S-EL1-writable page execute-never, as 2.8.0.0.1 allows GET to report. | +| Memory sharing runs on a fixed, build-sized page pool and handle table with a bounded borrower count, and a fragmented descriptor reassembles into one page with one transfer in flight per sender, in one slot kept for the Normal world and two the partitions share. | Stronger resource policy | The zero-allocation SPM rejects excess work rather than expanding at runtime; exhaustion returns `NO_MEMORY`. The Normal world can rewrite its TX buffer at any time, so its descriptor, or first fragment, is parsed only from a copy in one Secure page; one larger than a page is `NO_MEMORY`. A retrieve only flips entries the borrower's table already holds for the SPMC, so no table ever grows after boot, and a page no fill entry names is `NO_MEMORY` to retrieve. A declared total that the first fragment's own headers contradict is `INVALID_PARAMETERS` before any handle is reserved; headers the full parse refuses (an access descriptor size or offset it does not accept) are not walked, and that parse answers the whole request. | +| Borrowers map shared and lent memory only as Normal write-back inner-shareable memory. A lend or share naming Device, non-cacheable, or non-shareable memory is `INVALID_PARAMETERS`, as is a retrieve request asking for non-cacheable or non-shareable memory; outer-shareable memory, a retrieve request for Device memory, and a send of the sender's own Device pages, is `DENIED`. | Implementation detail | DEN0140 1.10.4.2 item 5 lets the relayer refuse less permissive attributes it will not map, and items 1 and 2 deny more permissive ones. A lend to one borrower or a donate leaves the attributes to the relayer, and every retrieve response reports the ones the borrower was mapped with. | +| A retrieve response is never sent in fragments, so `FFA_MEM_FRAG_RX` from a borrower is `INVALID_PARAMETERS`. | Implementation detail | The largest descriptor the relayer holds fits every RX buffer, so no response fragment is ever outstanding. | +| The SPMC sends partitions no power management messages (DEN0077A 18.2.4), and a manifest cannot register for them (the optional Table 5.1 field). | Unsupported | Manifest partitions take no FF-A messages, so none could receive one; the SPMC answers the SPMD's message itself. The SPMC is uniprocessor and resident on the boot core, so no power operation loses Secure state: `CPU_SUSPEND` enters core standby only (a power-down state is `INVALID_PARAMETERS`) and returns with the SPMC's and every partition's context intact, `CPU_OFF` is `DENIED`, `SYSTEM_OFF` stops the machine, and `SYSTEM_RESET` cold-boots it through the Chapter 5 setup again; `SYSTEM_RESET2` and `SYSTEM_SUSPEND` are `NOT_SUPPORTED`, so no warm boot (18.2.3) and no Table 18.7 message occurs. A partition gets no notice before the machine powers off or resets. | +| Managed exit is not offered; a partition's Non-secure interrupt action is either signaled or queued. | Scoped isolation model | Managed exit applies to S-EL1 partitions; every wolfTrust partition runs at S-EL0. A queued partition runs with the GIC priority mask at the top of the Non-secure range, so Secure interrupts still reach it. | +| The RX-buffer-full framework notification is set in the SPM half for a Secure sender and in the Hypervisor half for a Normal-world sender, and `FFA_NOTIFICATION_GET` returns and clears each half only when its own flag asks for it. | Interpretation | Section 10.8.1 pends a VM sender's RX-buffer-full in the Hypervisor framework bitmap, and section 16.6 says a caller ignores w7 unless it set the Hypervisor flag. Three ACS receivers asked for the SPM half only and then read w7; patch 0011 corrects them. | +| The PSA and wolfTrust service protocol rides on direct messaging plus shared regions, and wolfTrust-private partition hypercalls use the SMCCC OEM range. | Scoped product surface | Partition-message payloads are wolfTrust-defined (the spec leaves the payload to the sender and receiver); the private hypercalls are IMPDEF interfaces outside the FF-A function-id ranges. | +| `FFA_NOTIFICATION_GET` refuses flag bits 31:4 with `INVALID_PARAMETERS`, although Table 16.23 marks them SBZ. | ACS-driven | Every other SBZ field is ignored; the ACS `notification_get` test requires this one refused. | +| An S-EL0 partition reports failed initialization with `FFA_ERROR`, and may complete a direct request with `FFA_SUCCESS`, through the SVC conduit. | Interpretation | Tables 12.3 and 12.6 list SMC and ERET at the Secure virtual instance, but section 4.4 makes SVC the S-EL0 partition's only conduit, mirroring SMC, and sections 8.5 and 15.2 require both transitions. | +| Manifests use the wolfTrust JSON generator and boot information uses an IMPDEF descriptor type. | Integration difference | Allowed by sections 5.2.1 and 5.4; the mandatory partition properties are all present. | +| A target with no hand-off item still hands the SPMC a boot-information blob, one with a zero descriptor count. | Interpretation | 5.4 says one or more instances of boot information could be passed and names no other way to say there is none; the SPMC's parser takes the empty blob as "nothing to hand off". | +| Memory donated to a partition stays that partition's across its fault restart, and a partition retired for good keeps holding it, so it never returns to the Normal world. | Interpretation | DEN0140 1.3.1 rule 9 leaves a terminated endpoint's memory to the SPM: the binding stays live, so `wt_spm_mem_ns_owns` keeps the pages out of the Normal world's window; the restarted endpoint is the same one, so its own donated pages are not scrubbed with its private bands. | +| The SPMD keeps no RX/TX pair of its own at the Secure physical instance, so it never issues `FFA_RX_RELEASE` to the SPMC over ERET, and an `FFA_RX_RELEASE` the SPMC sends it over SMC is `NOT_SUPPORTED`. | Scoped design | Table 13.20 lists ERET as the only conduit at that instance; the SPMC writes Normal-world descriptors straight into the Normal world's own RX buffer, so no SPMD-owned buffer exists to release. | +| The `xlnx-versal-virt` manifests declare `isolation_profile` 0 and claim no isolation level; the `qemuvirt` manifests declare Level 3. | Known gap | The Versal model has no XMPU, RISAF, or XPPU to fence the Secure bands and the SPM's peripherals from the Normal world; a Versal silicon port claims Level 3 once it locks them. | + +## ACS conformance results + +The Arm FF-A ACS runs against the SPMC as a `run_qemu_a_scenario.sh` scenario +per implemented test group, on the three QEMU cells (`virt` GICv2 Cortex-A35, +`virt` GICv3 Cortex-A72, and `versal-virt`), in the `qemu-a-ffa-acs` CI job. + +| Group | Result | +| --- | --- | +| `setup_discovery` | 14 passed, 1 skipped (single PE), 1 by-design deviation (`ffa_partition_info_get_lsp`) | +| `direct_messaging` | 5 passed, 1 skipped | +| `memory_manage` | 70 passed, 0 failed | +| `notifications` | 10 passed | +| `indirect_messaging` | 2 passed | +| `interrupts` | 6 passed (every test that applies to S-EL0 partitions) | + +The ACS is a conformance oracle only. It is never a source for the wolfTrust +implementation. A defect in an ACS test itself is corrected by a recorded patch +under `tests/conformance/ffa-acs/patches/`, applied by `build_acs.sh`, and never +by changing wolfTrust to match a wrong expectation. The tests the ACS marks +unverified upstream (`ACS_FFA_UNVERIFIED`) and the S-EL1-partition tests do not +run in this configuration. + +The pinned ACS has no fragmented-transmission tests, so `FFA_MEM_FRAG_TX`/ +`FRAG_RX` are proven by the host suite (`tests/host/ffa_mem`, every split point) +and by the `ffa-memneg` scenario, where the Normal world sends a share in two +fragments (a second first fragment in between is `BUSY`) and has a second +share aborted by unmapping its RX/TX pair between them. + +| Patch | Why | +| --- | --- | +| 0001 | `up_migrate_capable` is not applicable on a single-PE platform. | +| 0002 | A memory test read its handle after `FFA_FEATURES` had overwritten it. | +| 0003 | Memory-region requests were filled without first clearing them. The three `*_retrieve_with_address_range` servers cleared theirs only after setting the alignment-hint flag, erasing it, so the flag is now set after the clear. | +| 0004 | The platform describes its own endpoint properties. | +| 0005 | The build forwards the partition-message UUID field setting. | +| 0006 | An indirect-messaging test packed physical endpoint ids into logical-id fields. | +| 0007 | GICv2 initialization never probed the CPU interface id (GICv3 only), so every interrupt test asserted during setup. | +| 0008 | `sp_el0_blocked` packed physical endpoint ids into logical-id fields. | +| 0009 | A platform may time S-EL0 partition waits on the virtual counter instead of a loop calibrated for another platform. | +| 0010 | `sp_preempted_el0` set its keep-the-RX-buffer flag after `FFA_MSG_WAIT` instead of before. | +| 0011 | `direct_msg_sp_to_vm`, `ffa_msg_send2` and `ffa_msg_send2_uuid_check` read a VM sender's RX-buffer-full from w7 after an `FFA_NOTIFICATION_GET` that asked only for the SPM framework bitmap. Section 10.8.1 pends that notification in the Hypervisor framework bitmap, and section 16.6 says w7 is ignored unless the Hypervisor flag is set, so they now ask with the Hypervisor flag. | +| 0012 | `ffa_direct_message_error` and `ffa_direct_message_error1` passed the sender's logical id OR-ed with the receiver's id shifted left into the logical-id lookup, reading far past the endpoint table, instead of packing the sender's endpoint id over the receiver's. They now pack the ids as `ffa_msg_send_error` does. | +| 0013 | Thirteen multi-borrower servers named every borrower in their retrieve request with the Non-retrieval Borrower flag clear, asking the relayer to retrieve on the other borrower's behalf. DEN0140 Table 1.17 sets the flag for each other borrower and 1.10.1 makes a wrong encoding INVALID_PARAMETERS, so each server now sets it on every entry but its own. | +| 0014 | `ffa_version` expected `NOT_SUPPORTED` for a caller asking a later minor (1.4) or major (2.2). Section 13.2.2 requires a callee at a lesser version than the caller to return its highest version, so both now expect 1.2. | +| 0015 | Six single-borrower lend and donate servers retrieved with the instruction access left unspecified, in seven places. DEN0140 1.10.3 item 2 has a donate's receiver and a lend's one borrower specify it in `FFA_MEM_RETRIEVE_REQ`, so each now states not-executable. | diff --git a/docs/Getting-Started.md b/docs/Getting-Started.md index d9521a5e..50a72fc6 100644 --- a/docs/Getting-Started.md +++ b/docs/Getting-Started.md @@ -3,7 +3,8 @@ This guide builds the currently supported STM32H563 Secure image, the Zephyr and FreeRTOS reference guests, and the host tests. See [Building](Building.md) for build controls, and read [STM32H5 Guide](STM32H5-Guide.md) before flashing a -board. +board. The AArch64 (Cortex-A) images and their QEMU scenarios are covered in +[Build and run the AArch64 images](#build-and-run-the-aarch64-images). ## Prerequisites @@ -18,6 +19,9 @@ board. - Docker for the supplied CI-container workflows - M33MU for emulated Cortex-M33 execution, or a NUCLEO-H563ZI with ST-Link for hardware execution +- For the AArch64 images, an `aarch64-none-elf-` toolchain and + `qemu-system-aarch64`; the `ghcr.io/wolfssl/wolfboot-ci-aarch64` container + carries both Guest setup downloads the Zephyr v4.2.0 tag and the FreeRTOS `main` branch by default, so it requires network access on its first run and the FreeRTOS @@ -122,6 +126,22 @@ bytes first as described in [STM32H5 Guide](STM32H5-Guide.md). The current build, so its shorter `make test-hardware` form must not be used for this hardened build until the runner is fixed. +## Build and run the AArch64 images + +```sh +make ARCH=aarch64 TARGET=qemuvirt +make test-target-a +make test-target-a MACHINE=versal-virt +tests/target/run_suite.sh qemu-a positive confboot ffaacs-memory +``` + +The build produces the EL3 monitor (`build/wolftrust_el3.elf`) and the Secure +EL1 SPMC (`build/wolftrust.elf`) for QEMU `virt`. `make test-target-a` builds +and boots a quick subset of the QEMU scenarios (`WT_QEMU_A_SCENARIOS`); +`tests/target/run_suite.sh qemu-a` runs any of them. See +[Testing](Testing.md#qemu-aarch64-scenarios) for the full list. These targets +have no wolfBoot port and no authenticated boot yet. + ## Next steps - Read [Architecture](Architecture.md) for the request path and isolation model. diff --git a/docs/Home.md b/docs/Home.md index 6916ccc9..fa6de397 100644 --- a/docs/Home.md +++ b/docs/Home.md @@ -1,35 +1,44 @@ # wolfTrust -wolfTrust is a Secure Partition Manager (SPM) and secure-services runtime -designed for Cortex-M targets. It separates reusable policy and service code -from architecture- and target-specific execution and protection code. The +wolfTrust is a Secure Partition Manager (SPM) and secure-services runtime for +Arm Cortex-M and Cortex-A targets. It separates reusable policy and service +code from architecture- and target-specific execution and protection code. The common runtime implements Arm Platform Security Architecture (PSA) Firmware Framework for M (FF-M) interprocess communication (IPC), manifest policy, scheduling, lifecycle management, fault recovery, and services. -The only currently supported and validated reference implementation combines -the Armv8-M adapter with the STM32H563 Cortex-M33 port. Support for additional -Cortex-M ports is an intended extension point. Such ports may reuse the common -runtime and, where applicable, the Armv8-M layer. Cortex-A support is an -architectural goal, not a current capability. It will require a new adapter and -changes to current internal execution and protection contracts; the design goal -is to preserve the public manifest, service, IPC, and PSA API contracts. +Two architecture ports share that runtime: + +- **Armv8-M** with the STM32H563 Cortex-M33 port, the reference + implementation validated on hardware. +- **AArch64** (Cortex-A), a Trusted Firmware-A replacement: an EL3 monitor + that is the Arm Firmware Framework for A-profile (FF-A) Secure Partition + Manager Dispatcher (SPMD), a Secure EL1 Secure Partition Manager Core (SPMC) + that runs the same common runtime, and Secure Partitions at Secure EL0. + Normal-world clients reach the services over FF-A v1.2. This port is + validated under QEMU on `virt` (GICv2 and GICv3) and `xlnx-versal-virt`; no + Cortex-A silicon port is validated yet. See + [FF-A Compatibility](FF-A-Compatibility.md). + +Both ports keep the public manifest, service, IPC, and PSA API contracts; +AArch64 manifests add an optional FF-A section. Additional Cortex-M and +Cortex-A ports are an intended extension point. ## Architecture ```mermaid flowchart TB - BOOT[Trusted first-stage loader
current reference: wolfBoot] + BOOT[Trusted first-stage loader
Armv8-M reference: wolfBoot] subgraph APP[Application domains] - GA[Cortex-M client A
Zephyr, FreeRTOS, or bare metal] - GB[Cortex-M client B
Zephyr, FreeRTOS, or bare metal] + GA[Client A
Cortex-M guest or Cortex-A Normal world] + GB[Client B
Cortex-M guest or Cortex-A Normal world] end subgraph PORT[Architecture and target ports] - GW[Client gateway
current: five Armv8-M CMSE veneers] - ARCH[Architecture adapter
current: Armv8-M] - TARGET[Target and board port
current: STM32H563] + GW[Client gateway
Armv8-M: five CMSE veneers
AArch64: FF-A through the EL3 SPMD] + ARCH[Architecture adapter
Armv8-M, or AArch64 EL3 SPMD and Secure EL1 SPMC] + TARGET[Target and board port
STM32H563; QEMU virt and Versal] GW --- ARCH ARCH --- TARGET end @@ -74,10 +83,10 @@ flowchart TB ST --> WC AT --> COSE AT --> WC - TARGET -->|current register and RNG access| HAL + TARGET -->|STM32H563 register and RNG access| HAL ``` -### Current reference port +### Current reference ports In the STM32H563 reference chain, wolfBoot authenticates wolfTrust, TrustZone isolates the Secure runtime from Non-secure guests, and STM32 Global TrustZone @@ -85,14 +94,22 @@ Controller (GTZC) memory attribution isolates guest RAM. The Zephyr and FreeRTOS reference guests use wolfPSA's PSA Crypto API through the Armv8-M port's five CMSE gateway veneers. +On the QEMU AArch64 cells, the EL3 monitor boots first, programs the GIC, and +enters the Secure EL1 SPMC, which runs each Secure Partition at Secure EL0 +under its own stage-1 translation table. A Normal-world payload at NS-EL1 +reaches the services through FF-A direct requests that carry the PSA client +calls. No wolfBoot AArch64 port exists yet, so these targets have no +authenticated boot or boot handoff record; the handoff region is empty and +the services that depend on it fail closed. + ## Key Features | Feature | Description | | --- | --- | | Authenticated chain | The boot port supplies authenticated measurement, lifecycle, and version data. The reference integration uses wolfBoot and signature-covered guest records. | -| One mediated client boundary | Application domains reach services only through the client gateway supplied by the architecture port. The current Armv8-M image exports exactly five `WolfTrust_FFM_*` veneers. | +| One mediated client boundary | Application domains reach services only through the client gateway supplied by the architecture port. The current Armv8-M image exports exactly five `WolfTrust_FFM_*` veneers; on AArch64 the Normal world reaches services only through FF-A calls that the EL3 SPMD relays to the SPMC. | | Caller-bound IPC | wolfTrust derives the PSA client identity from the active application domain, copies vector descriptors, checks every range, and enforces manifest access policy. | -| Port-defined isolation | Each port declares and enforces the protection capabilities required by its manifest. The STM32H563 reference uses TrustZone, the Secure MPU, and GTZC MPCBB attribution; its exact limits are documented in [Security Model](Security-Model.md). | +| Port-defined isolation | Each port declares and enforces the protection capabilities required by its manifest. The STM32H563 reference uses TrustZone, the Secure MPU, and GTZC MPCBB attribution; its exact limits are documented in [Security Model](Security-Model.md). The AArch64 port runs each Secure Partition at Secure EL0 under its own stage-1 translation table, refusing writable and executable mappings. | | PSA cryptography | Zephyr and FreeRTOS reference guests call wolfPSA's PSA Crypto API. The default native engine runs wolfCrypt in each guest and obtains DRBG seeds from the Secure vault; the optional wolfHSM engine routes supported operations to per-guest Secure server namespaces. | | Secure services | Connection-based services provide the selected crypto engine, attestation, Internal Trusted Storage (ITS), Protected Storage, firmware update, and an optional Secure virtual Ethernet switch. The optional bare-metal networking guests run wolfIP outside the Secure image. | | Fault containment | A guest fault either restarts the guest within policy limits or leaves it quarantined. A Secure Partition fault releases synchronization state before failing affected calls. Restart paths scrub declared private writable memory before rearming; forbidden, exhausted, or failed recovery escalates to the port's fail-closed path. | @@ -110,9 +127,10 @@ port's five CMSE gateway veneers. | [API Reference](API-Reference.md) | PSA client, service, storage, update, lifecycle, attestation, and gateway APIs | | [Services](Services.md) | Behavior and access policy for each Secure service | | [TF-M Compatibility](TF-M-Compatibility.md) | Supported interfaces, intentional differences, and migration guidance | +| [FF-A Compatibility](FF-A-Compatibility.md) | The AArch64 FF-A interface register, intentional differences, and Arm FF-A ACS results | | [Macros](Macros.md) | Supported build and manifest configuration | | [Porting](Porting.md) | Architecture and target port contracts | | [Building](Building.md) | Build targets, outputs, and cross-build options | -| [Testing](Testing.md) | Host, M33MU, and STM32H563 validation | +| [Testing](Testing.md) | Host, M33MU, QEMU AArch64, and STM32H563 validation | | [Project Structure](Project-Structure.md) | Repository layout | | [STM32H5 Guide](STM32H5-Guide.md) | STM32H563 provisioning, flashing, WRP, and recovery safety | diff --git a/docs/Macros.md b/docs/Macros.md index 9a8eeb6f..36a8ca95 100644 --- a/docs/Macros.md +++ b/docs/Macros.md @@ -1,19 +1,21 @@ # Macros -The STM32H563 build is configured through GNU Make variables. The build turns -selected values into C preprocessor defines. Defaults below come from -`mk/target-stm32h563.mk`, `mk/arch-armv8m.mk`, and `mk/common.mk`. +The build is configured through GNU Make variables. The build turns selected +values into C preprocessor defines. Defaults below come from +`mk/target-stm32h563.mk`, `mk/arch-armv8m.mk`, and `mk/common.mk` unless a row +says otherwise; the AArch64 variables are under +[AArch64 build options](#aarch64-build-options). ## Build selection | Define | Description | Requirement | | --- | --- | --- | -| `ARCH` | Architecture build selector; default `armv8m`. | Must match an `mk/arch-.mk` fragment; `armv8m` is the only architecture today. | -| `TARGET` | Target build selector; default `stm32h563`. | Must match an `mk/target-.mk` fragment; the root Makefile includes it, the architecture fragment, and `mk/common.mk`. | -| `TOOLPREFIX` | Cross-tool prefix; default `arm-none-eabi-`. | The prefixed GCC, objcopy, nm, and size tools must be available. | +| `ARCH` | Architecture build selector; default `armv8m`, or `aarch64`. | Must match an `mk/arch-.mk` fragment. | +| `TARGET` | Target build selector; default `stm32h563`; `qemuvirt` or `versal` with `ARCH=aarch64`. | Must match an `mk/target-.mk` fragment; the root Makefile includes it, the architecture fragment, and `mk/common.mk`. | +| `TOOLPREFIX` | Cross-tool prefix; default `arm-none-eabi-`, or `aarch64-none-elf-` with `ARCH=aarch64`. | The prefixed GCC, objcopy, nm, and size tools must be available. | | `BUILD_DIR` | Secure build output directory; default `build`. | Must be writable. | -| `WT_LTO` | Enable Secure-image link-time optimization; default `1`. | Set to `0` for diagnostics or a non-LTO size comparison. The GNU Arm compiler must support `-flto=auto`. | -| `WT_ENGINE` | Secure crypto engine: `native` (default) dispatches wolfCrypt directly behind the SERVICE_HSM door with explicitly vault-backed keys stored as `SENSITIVE` and `NONEXPORTABLE` NVM objects; `hsm` links the wolfHSM server as a key-management add-on (server-keystore semantics and an external-HSM offload path). Legacy `WT_ENGINE_HSM=0/1` maps onto the selector. | Both engines share the identical FF-M surface (5 veneers, SIDs, manifest, and L3 bands) and run every applicable CI scenario. Guest builds must use the same engine as the Secure image. See [Crypto Engines](Crypto-Engines.md). | +| `WT_LTO` | Enable Secure-image link-time optimization; default `1` (`0` on AArch64). | Set to `0` for diagnostics or a non-LTO size comparison. The GNU Arm compiler must support `-flto=auto`. AArch64 builds refuse `1`: their linker script places isolation bands, and the EL3 symbol guard audits the monitor archive, by object name. | +| `WT_ENGINE` | Secure crypto engine: `native` (default) dispatches wolfCrypt directly behind the SERVICE_HSM door with explicitly vault-backed keys stored as `SENSITIVE` and `NONEXPORTABLE` NVM objects; `hsm` links the wolfHSM server as a key-management add-on (server-keystore semantics and an external-HSM offload path). Legacy `WT_ENGINE_HSM=0/1` maps onto the selector. | Both engines share the identical FF-M surface (5 veneers, SIDs, manifest, and L3 bands) and run every applicable CI scenario. Guest builds must use the same engine as the Secure image. On AArch64 both engines run every QEMU scenario except `hsmattackneg`, which needs the wolfHSM wire. See [Crypto Engines](Crypto-Engines.md). | ## Core target configuration @@ -38,6 +40,25 @@ selected values into C preprocessor defines. Defaults below come from | `WT_WOLFCRYPT_STM32_HASH` | STM32 HASH acceleration selector; default `0`. | Must remain `0`: the Makefile rejects other values because wolfHSM SHA state is not compatible with the peripheral representation. | | `WT_CONFORMANCE` | When `1`, select the manifest and sources used by Arm PSA API validation. | Use only for conformance builds; the default production manifest is selected at `0`. | +## AArch64 build options + +Defaults come from `mk/arch-aarch64.mk` and `mk/target-qemuvirt.mk` or +`mk/target-versal.mk`; the Secure band placement comes from the shared layout +in `port/common/aarch64/l3_layout.h`. + +| Define | Description | Requirement | +| --- | --- | --- | +| `WT_CPU` | `-mcpu` for the EL3 and Secure EL1 images; default `cortex-a72`. | The QEMU cells use `cortex-a72` and, on `virt` with GICv2, `cortex-a35`. | +| `WT_GIC_VERSION` | GIC driver, `2` or `3`; default `3`. | Must match the interrupt controller; `versal` is GICv3. | +| `WT_PORT_BOOT_CPUS` | Cores the monitor expects at reset; default `2` on `qemuvirt`, `1` on `versal` (the model keeps APU core 1 powered off). The boot core runs the monitor and the rest park at EL3; a declared core that does not park stops the boot. | Must match the cores the loader starts. | +| `WT_ISOLATION_LEVEL` | The isolation level the build implements; default `3`. It gates the shared level 3 layer in `port/common/aarch64/`. | Must be `3`: no other level is implemented, so any other value stops the build. | +| `WT_VERSAL_VIRT` | Selects the QEMU `xlnx-versal-virt` model of the `versal` target; default `1`. | Versal silicon needs `0` and a silicon port. | +| `WT_SPM_IMAGE_PA`, `WT_SPM_RAM_PA`, `WT_SPM_KEYSTORE_PA`, `WT_SPM_RXTX_PA`, `WT_SPM_SHARE_PA` and their `_SIZE` values; `WT_SPM_BOOT_INFO_PA`, `WT_SPM_TABLE_POOL_PA`, `WT_SPM_TABLE_POOL_PAGES` | Secure EL1 band placement: the SPMC image, its RAM, the keystore band, the partition RX/TX pages, the shared page, the boot-information page, and the stage-1 table pool. Placed by `port/common/aarch64/l3_layout.h` from the port's `WT_L3_BAND_BASE` and `WT_RAM_S_BASE`; `WT_SPM_TABLE_POOL_PA` and `WT_SPM_TABLE_POOL_PAGES` may be overridden on the command line. | Must lie in Secure memory that the platform fences from the Normal world, must not overlap, and must hold the linked sections; the linker and the table builder refuse overflow. | +| `WT_EL3_RESET_LIMIT` | System resets a `virt` run may make before it ends: `1` by default, `256` for conformance images. `xlnx-versal-virt` does not use it: its reset powers the model off, and the runner bounds the power cycles the same way. | Test only. Production builds leave it unset: every reset goes through the port's `wt_platform_board_system_reset`, and a hook that returns panics the monitor. A build that sets it without `WT_PORT_EMULATED=1` fails, since a real machine's `SYSTEM_RESET` is always a cold reset (DEN0022 5.11). | +| `WT_PORT_EMULATED` | Marks an emulated machine, whose runner stands in for its power cycles; `1` on `qemuvirt`. | A silicon port never sets it. | +| `WT_QEMU_TEST_ENTROPY` | Seeds the DRBG from a test source; default `1` on the QEMU targets. | Test only. A silicon port must provide a real entropy source and build with `0`. | +| `WT_EL3_TEST_DRIVER`, `WT_EL3_TEST_HANDOFF`, `WT_FFA_ACS`, `WT_EL3_NS_EL2`, `WT_EL3_EL2_DIRTY_PROBE`, `WT_GIC_SPI_ROUTE_PROBE`, `WT_EL3_BOOT_NEG_PROBE` | Test builds: the monitor's direct-request driver, a synthetic boot handoff record, the Arm FF-A ACS partitions, a Normal world entered at NS-EL2 for the ACS, EL2 state and GICv3 SPI routes left dirty as an earlier boot stage might leave them, and a redistributor read asleep (`1`), a secure tick that never arrives (`2`), or an SPMC boot self-test that fails (`3`). | Never set in production builds; the QEMU runner sets them per scenario. | + ## Image layout | Define | Description | Requirement | diff --git a/docs/Porting.md b/docs/Porting.md index 789fe0a4..732a97f8 100644 --- a/docs/Porting.md +++ b/docs/Porting.md @@ -5,16 +5,15 @@ and board-specific execution. The fully silicon-validated build tuple is `armv8m-stm32h563`. A second Armv8-M tuple, `armv8m-mimxrt700` (external octal-NOR execute-in-place), is in hardware bring-up and reuses the architecture adapter unchanged; see the [STM32H5 Guide](STM32H5-Guide.md) and -[MIMXRT700 Guide](MIMXRT700-Guide.md) for the two worked examples. Support for -additional Cortex-M ports is an intended extension point. Such ports may reuse -common policy and service code and an existing architecture adapter when their +[MIMXRT700 Guide](MIMXRT700-Guide.md) for the two worked examples. A third +tuple, `aarch64` with the `qemuvirt` and `versal` targets, is validated under +QEMU (`versal` builds the `xlnx-versal-virt` model today). Support for +additional ports is an intended extension point. Such ports may reuse common +policy and service code and an existing architecture adapter when their execution and protection models match. -Cortex-A support is an architectural goal, not a current capability. It will -require a new adapter and changes to current internal execution and protection -contracts. The design goal is to preserve the public manifest, service, IPC, -and PSA API contracts. Every new port must report its actual capabilities and -must not claim security properties until they are tested on that target. +Every new port must report its actual capabilities and must not claim +security properties until they are tested on that target. ## Port layers @@ -184,17 +183,107 @@ worked examples above give a concrete map for each board. 3. Add `mk/arch-.mk` (if new) and `mk/target-.mk`; the root Makefile selects them from `ARCH` and `TARGET`, and `mk/common.mk` needs no change. -4. Supply startup/vector and linker handling appropriate to the target, and - give every object the port links an owner in `tools/secure_owners.txt`. -5. Generate the manifest at build time and include its digest in the signed - Secure image. +4. Supply startup/vector and linker handling appropriate to the target. On + Armv8-M, give every object the port links an owner in + `tools/secure_owners.txt`, which the post-link layout check reads. +5. Generate the manifest at build time; the generated source embeds its + digest in the Secure image, which a target with an authenticated first + stage signs. 6. Integrate application domains with the architecture's client boundary and matching generated service IDs. Armv8-M targets link Non-secure guests against the CMSE import library. -7. Add image assembly that patches guest ID, version, size, and digest records - before signing wolfTrust. +7. On a target with separately built Normal-world guest images (the + Armv8-M ports today), add image assembly that patches guest ID, version, + size, and digest records before signing wolfTrust. 8. Add safe provisioning tooling for the target's security attribution, application-image write protection, debug policy, and product lifecycle. +9. Implement isolation level 3. Level 3 is required for a new port unless + the port specifically targets level 1 or 2, which would first need those + levels implemented. On AArch64, reuse the shared + layer in `port/common/aarch64/` (below): `mk/arch-aarch64.mk` gates it with + `WT_ISOLATION_LEVEL` (default `3`), and since only level 3 exists today any + other value stops the build. The manifest's `isolation_profile` states the + claim separately: `3`, or `0` (no claim) on a target that cannot yet fence + the Secure bands. + +## AArch64 targets + +An AArch64 SoC port adds: + +- `mk/target-.mk`: the EL3 text and RAM bands, the boot CPU count, and + whether the loader already configured the UART and the counter frequency; +- `port//memory_map.h`, `el3_board.c`, and `uart.c` for the monitor; +- `port//l3_port.h` for the level 3 layer (below); +- `port//manifest.json`, whose optional `ffa` section gives each Secure + Partition's FF-A properties (see [Building](Building.md)). + +The QEMU targets share their Secure EL1 platform code in +`port/common/aarch64/`: the `wolftrust/platform.h` operations, the partition +entry table, a RAM-backed NVM, and a test entropy source that a silicon port +must replace. The EL3 monitor archive `libwt_el3.a` may reference only the +port hooks listed in `tools/el3-symbols.allow` (`wt_platform_board_init`, +`wt_platform_board_system_reset`, the console pair), may define globally only +the monitor symbols `tools/el3-defines.allow` names, and must define no SPM, +service, or crypto code; the link rule runs `tools/check-el3-symbols.sh` on +every build and again whenever either list changes. `wt_platform_board_system_reset` +performs the machine cold reset of PSCI `SYSTEM_RESET` and does not return: +`virt` drives the restart line of its Secure PL061, and `xlnx-versal-virt`, +whose model leaves its reset blocks unimplemented, powers the model off with +the reset exit code for the runner to power it on again. A hook that returns +panics the monitor. A silicon port must also +fence the Secure bands from the Normal world in hardware (a TZASC, XMPU, or +RISAF): QEMU `virt` models the fence with its secure memory, and +`xlnx-versal-virt` does not model one. The port's `memory_map.h` states which +through `WT_PORT_NS_MEMORY_FENCE`, and only a port that sets it to `1` claims +security-state isolation. Every isolation level needs that capability, so the +core refuses a Level 1, 2, or 3 manifest on an unfenced port. The `qemuvirt` +manifests declare Level 3; the `xlnx-versal-virt` manifests declare +`isolation_profile` 0 (service only) and claim no isolation level. A Versal +silicon port sets the flag only once it programs +and locks the XMPU over the Secure bands before the Normal world runs. + +### Isolation level 3 on AArch64 + +Every AArch64 port reuses the level 3 layer in `port/common/aarch64/` +instead of writing it again; whether the port claims level 3 is its +manifests' choice (below): + +- `l3_layout.h` places the Secure EL1 bands at default offsets from the + port's 1 MiB aligned `WT_L3_BAND_BASE`: the SPMC image, its RAM, the + conformance data window, the keystore window, the RX/TX and shared pages, + and the FF-A ACS window. The vault, attestation and crypto bands tile the + keystore window exactly. The boot-information page and the default stage-1 + table pool open `WT_RAM_S_BASE`; a band macro defined before the include + (the runner moves the table pool for the ACS) takes precedence. +- `platform_l3.c` holds the level 3 platform operations: the code every + partition maps, the empty partition peripheral table, the SPM-private RAM, + the privileged-stack check, the conformance data window and the test-build + probe addresses. `conf_backend.c`, built only with `WT_CONFORMANCE=1`, holds + the per-partition conformance grants. +- `tools/aarch64_l3_layout.py` reads the layout from the port's + `memory_map.h` for the compiler, the linker and the target runner, and + after the link checks that the port's manifest grants exactly the keystore + bands those same values place. An unreadable layout stops the build. + +A port using the shared layer supplies: + +- in `memory_map.h`, a literal `WT_L3_BAND_BASE` and `WT_RAM_S_BASE`, then + `#include "../common/aarch64/l3_layout.h"`; a port that claims level 3 + places them in Secure memory the board fences from the Normal world; +- `l3_port.h`, naming a Secure peripheral only the SPM drives as + `WT_L3_SPM_PERIPHERAL_BASE`; +- manifests whose vault, attestation and crypto partitions are granted the + three keystore bands. + +No band address is written twice. A port's manifests state its claim: the +`qemuvirt` manifests declare `isolation_profile` 3, and the `versal` +manifests declare profile 0 because the model cannot fence the Secure bands. +A port claiming level 3 runs every scenario in `QEMU_A_L3_REQUIRED` +(`tests/target/lib/scenario_matrix.py`) in its full QEMU tier. +`scenario_matrix.py --selftest`, run in CI, fails when the suite drops a +required scenario or a cell's `l3_exempt` entry names no required scenario or +gives no reason; `versal-virt` lists `secramneg` and `periphneg` there, citing +#45. ## Validation checklist @@ -209,6 +298,11 @@ worked examples above give a concrete map for each board. two build fragments, tests, docs, and workflows. - Run `tools/check-docs-no-internal-links.sh`; `docs/` is published to the wiki and must not reference internal ledgers or developer paths. +- On an AArch64 port, run `tools/check-el3-symbols.sh `: the + EL3 monitor archive may leave unresolved only the hooks listed in + `tools/el3-symbols.allow`, may define globally only the symbols + `tools/el3-defines.allow` names, and must define no SPM, service, or crypto + code. - Cross-build the Secure image with warnings enabled. - On the current Armv8-M port, inspect `nm` output and confirm only the five FF-M veneers are Non-secure-callable. diff --git a/docs/Project-Structure.md b/docs/Project-Structure.md index dd149382..5fcbd7e9 100644 --- a/docs/Project-Structure.md +++ b/docs/Project-Structure.md @@ -9,20 +9,25 @@ | `src/` | Architecture-neutral boot sequence, monitor, FF-M runtime, domains, manifests, verification, rollback, recovery, and the Secure Partition entry bodies | | `src/arch/common/` | Architecture-neutral code every architecture links as is: the Secure Partition gate dispatch, fault recovery, scheduler, the SP-side PSA API, and the NS FF-M gateway bodies, all written over the `wolftrust/arch.h` primitives | | `src/arch/armv8m/` | Armv8-M mechanisms behind `wolftrust/arch.h`: reset entry, guest context switching, exception handlers, virtual SysTick, NVIC routing, table-driven SAU and MPU programming, the SVC trap decoder, the CMSE range checks, and the five NS veneers | +| `src/arch/aarch64/` | AArch64 mechanisms behind `wolftrust/arch.h`: the EL3 monitor (`el3/`: entry, vectors, world switch, PSCI, console, ESR decoder), the FF-A layer (`ffa/`: the SPMD relay, boot information, messaging, memory transactions, notifications, partition information, runtime model), the Secure EL1 SPMC (`spm/`: MMU and stage-1 tables, partition domains, S-EL0 coroutines, the SVC gate, interrupts, memory relayer), the GICv2 and GICv3 drivers (`gic/`), the PL011 driver, and the freestanding `memset`/`memcpy` | | `src/client/` | OS-neutral FF-M, storage, firmware-update, HSM, and VNET client transports | | `src/sched/` | Static coroutine and tasklet scheduling | | `src/services/` | HSM relay, vault, storage, attestation, firmware update, and VNET service code | | `src/sync/` | Synchronization primitives used by Secure services | | `src/vnet/` | Secure virtual Ethernet data plane | | `port/stm32h563/` | STM32H563 SoC facts and `wolftrust/platform.h` operations: registers, board and memory maps, the SAU and MPU region tables, GTZC windows, clocks, UART, flash, entropy, partition tables, manifest, and the boot-time probes | +| `port/qemuvirt/` | QEMU `virt` (`secure=on`) SoC facts: memory map, GIC and PL011 bases, EL3 board hooks | +| `port/versal/` | AMD Versal SoC facts (the QEMU `xlnx-versal-virt` variant today): OCM and DDR bands, GIC-500 and PS UART bases, EL3 board hooks | +| `port/common/aarch64/` | Secure EL1 platform code the QEMU AArch64 targets share: `wolftrust/platform.h` operations, partition entry table, RAM-backed NVM, test entropy, and the conformance backend | | `mk/` | Build fragments: `common.mk` (every rule shared by all targets), `arch-.mk` (toolchain and architecture sources), `target-.mk` (SoC sources, placement, and image checks) | | `tools/manifest/` | Manifest validation and C/header generation | | `tools/measure/` | Guest-measurement record patching before image signing | | `tools/handoff/` | Boot-handoff record generation and validation for emulator runs and host tests | | `tests/host/` | Native unit and integration suites | -| `tests/target/` | M33MU and STM32H563 build, flash, provisioning, and scenario runners | -| `tests/firmware/` | Bare-metal, Zephyr, FreeRTOS, conformance, and VNET guest images | +| `tests/target/` | M33MU, QEMU AArch64, and STM32H563 build, flash, provisioning, and scenario runners, with the shared assertion library and suite driver | +| `tests/firmware/` | Bare-metal, Zephyr, FreeRTOS, conformance, VNET, AArch64 EL3 smoke, and AArch64 Normal-world payload images | | `tests/upstream/` | Fetch and integration helpers for pinned external validation suites | +| `tests/conformance/ffa-acs/` | The Arm FF-A ACS platform target for wolfTrust and its recorded patches | | `lib/` | Git submodules for wolfSSL, wolfPSA, wolfHSM, wolfCOSE, wolfHAL, and wolfIP | | `.github/workflows/` | Build, test, dependency, fuzz, and wiki synchronization workflows | diff --git a/docs/Security-Model.md b/docs/Security-Model.md index 66103458..2a7e598f 100644 --- a/docs/Security-Model.md +++ b/docs/Security-Model.md @@ -358,6 +358,50 @@ contexts, and cryptographic scratch space use fixed storage. Oversized requests fail instead of allocating. The link also rejects allocator symbols in both engine images. +## Cortex-A isolation level 3 + +On QEMU `virt`, the AArch64 port implements isolation level 3 of the PSA +Firmware Framework for M 1.0 (FF-M, Arm DEN 0063) with either crypto engine, +`native` or `hsm`. The Secure Partition Manager Core (SPMC) of the Firmware +Framework for Arm A-profile (FF-A) runs at Secure EL1, and every Secure +Partition runs at Secure EL0 under its own stage 1 translation table. The rules are cited by number; their text +is in the specification. + +| Requirement | wolfTrust on AArch64 | +| --- | --- | +| I1, only Code is executable (section 3.1.2) | Partition tables map data execute-never, the SPM maps its writable Secure RAM execute-never, and the table builder refuses a writable and executable region. | +| I2, only Private data is writable (section 3.1.2) | Code and constant data are read-only in every partition table; a partition can write only its own stack and data band, plus memory another endpoint shares, lends, or donates to it through FF-A. | +| I3, NSPE to SPE (sections 3.1.3 and 3.1.4) | The Secure bands and the SPM's devices sit where the `virt` bus refuses Normal-world access. The Normal world reaches services only through FF-A calls; for a PSA call the SPMC validates and copies the vectors it names, and a Secure interrupt is claimed as Group 0, disabled, cleared, and read back before its partition runs. | +| I3, Secure Partition to Secure Partition and to the SPM (sections 3.1.3 and 3.1.4) | Each partition has its own data band, and every boot refuses a composed table that can write another partition's band or reach SPM-private RAM. | +| I3, indirect access (section 3.1.4) | No partition is assigned a device, and the SPM's devices are never mapped into a partition table. | +| Private runtime state (section 4.2.1) | A partition's writable state is its own stack and, where it has one, its own data band. On restart the SPMC zeroes the stack and resets the band to its link image. The image has no heap. | +| Violation handling (section 3.1.6) | A partition access that breaks a rule takes an abort at Secure EL0 and ends that run of the partition, which its manifest restart policy restarts within a bounded budget or escalates to a fail-closed platform halt. A fault in the SPMC halts the platform through the EL3 monitor. | + +### Deviations + +- Level 3 is the only isolation level implemented. A manifest that declares + level 1 or 2 is refused; level 0 makes no isolation claim. +- A faulted partition is restarted under its manifest restart policy, with a + bounded budget, instead of staying terminated. +- The Normal world is one FF-A endpoint, not a set of managed guests. +- The claim covers QEMU `virt`. The `xlnx-versal-virt` manifests declare no + isolation level, because that model has no XMPU or XPPU to fence the + Secure bands and the SPM's devices from the Normal world. +- The evidence is emulator evidence; no Cortex-A silicon run is recorded yet. + +### Evidence + +- The Arm psa-arch-tests FF-M IPC suite passes 85 tests with 4 heap tests + skipped on `virt` with both engines. +- The six Arm FF-A ACS groups meet their recorded floors on `virt` with both + engines, with the by-design deviations listed in + [ACS conformance results](FF-A-Compatibility.md#acs-conformance-results). +- The isolation negatives in + [QEMU AArch64 scenarios](Testing.md#qemu-aarch64-scenarios) run on both + engines. +- All of the above passed at commit `b3587f01` in the + [AArch64 CI run](https://github.com/wolfSSL/wolfTrust/actions/runs/37090043983). + ## Source anchors - [FF-M gateway](../src/arch/armv8m/ffm_nsc.c) diff --git a/docs/Testing.md b/docs/Testing.md index c35de758..15905d69 100644 --- a/docs/Testing.md +++ b/docs/Testing.md @@ -10,6 +10,7 @@ as a result from another. | --- | --- | --- | | Native host | State machines, manifests, IPC ownership, copied transfers, services, storage, crypto integration, recovery decisions, and negative inputs | Cortex-M exception return, CMSE, SAU, MPU, GTZC, or physical flash behavior | | M33MU | Cortex-M33 instruction flow, TrustZone transitions, CMSE gateway calls, MPU faults, guest scheduling, authenticated boot, and target service interactions | STM32H563 peripherals, real option bytes, WRP, ST-Link, or silicon timing | +| QEMU AArch64 | EL3 monitor, Secure EL1 SPMC, S-EL0 partition isolation under stage-1 tables, GICv2 and GICv3 interrupt routing, FF-A calls from both worlds, and the Arm FF-M, dev_apis, and FF-A ACS suites | Cortex-A silicon, TZASC, XMPU or RISAF fencing, cache and TLB behavior that QEMU does not model, or wolfBoot on AArch64 | | STM32H563 | The actual NUCLEO-H563ZI boot chain, Secure/Non-secure attribution, faults, flash, UART, and selected end-to-end behavior | Other devices, other provisioning states, peer-flash confidentiality, or adversarial peripheral and Non-secure NVIC ownership | ## Host tests @@ -31,7 +32,13 @@ verification, rollback decisions, IPC and FF-M behavior, SPM policy, gateway vectors, Secure Partition layout and recovery, crypto-engine relay and key isolation, vault and storage services, attestation and COSE integration, firmware update, runtime remeasurement, linked Secure layout, VNET, public PSA -headers, boot-handoff record consumption, and negative paths. +headers, boot-handoff record consumption, and negative paths. The AArch64 +suites cover the exception-syndrome decoder, the stage-1 table builder and +domain switches, the FF-A function-id and version rules, the boot-information +blob, the partition runtime state machine, memory transaction descriptors and +fragments, notifications, the SPMD's Secure physical instance, the PSA FF-A +transport, and each AArch64 port's isolation claim against its own manifests +(an unfenced port claims no isolation level and refuses one). The attestation IAK suite runs wolfHSM NVM with both the default 8-byte and STM32H5 16-byte flash programming units. @@ -69,7 +76,7 @@ are deleted instead of being reused by the next Make invocation. The per-PR core/port split workflow builds the `CONFIG_VNET=y` Secure image. The M33MU smoke tier builds and runs the `WT_CONFORMANCE=1` `confboot` layout -on every PR; the VNET layout runs with the full matrix (`ci:h5` label, push +on every PR; the VNET layout runs with the full matrix (`ci:stm32h563` label, push to main, nightly), so both optional isolation-band configurations stay under the linked-image check in CI. @@ -348,6 +355,86 @@ the run. These are emulator results. They prove the SAU attribution and the monitor's containment on a faithful core model; the silicon `ahbscneg` run on the EVK is recorded separately. +## QEMU AArch64 scenarios + +The AArch64 twin of the M33MU runner boots the EL3 monitor, the Secure EL1 +SPMC, and a Normal-world payload under `qemu-system-aarch64` on three cells: +`virt` (`secure=on`) with GICv3 and a Cortex-A72, `virt` with GICv2 and a +Cortex-A35, and `xlnx-versal-virt`: + +```sh +make test-target-a # MACHINE=virt GIC=3 CPU=cortex-a72 +make test-target-a MACHINE=versal-virt +tests/target/run_suite.sh qemu-a positive ffa-memneg +WT_ENGINE=hsm tests/target/run_qemu_a_scenario.sh hsmattackneg +``` + +`make test-target-a` runs a quick subset (`WT_QEMU_A_SCENARIOS`); CI runs +every scenario below on the three cells under both engines. + +The runner auto-detects `qemu-system-aarch64` and the `aarch64-none-elf` +toolchain and skips explicitly otherwise; CI runs it inside +`ghcr.io/wolfssl/wolfboot-ci-aarch64`. `WT_ENGINE` (default `native`) +selects the crypto engine of the SPMC image and the Normal-world client, as +on M33MU. Both emulator runners share `tests/target/run_suite.sh` (the +per-scenario report, `SKIP` lines, and `logs/target-.log`) and +assert through `tests/target/lib/expect.sh`. + +| Scenario | What it proves | +| --- | --- | +| `smoke` | A standalone EL3 image runs wolfTrust-built code at EL3, prints on the secure console, and parks the secondary cores | +| `boot` | The monitor initializes the GIC, takes the secure tick as a Group 0 FIQ, builds the FF-A boot-information blob, and enters the SPMC, which reads `SMCCC_VERSION` and `SMCCC_ARCH_FEATURES` from the monitor, turns on its stage-1 MMU, runs S-EL0 partitions through the SVC gate, and completes initialization with `FFA_MSG_WAIT` | +| `boot-smp2` | The same image on two `virt` cores: the secondary parks at EL3 and exactly one core runs the monitor | +| `parkneg`, `rdistneg`, `tickneg` | The monitor stops the boot with a panic, before it enters Secure EL1, when a declared secondary never parks, the GICv3 redistributor reads asleep (SKIP on GICv2), or the secure tick never reaches EL3 | +| `positive-secure` | The neutral core boots at Secure EL1 and every Secure Partition initializes at Secure EL0 under its own translation table | +| `positive` | The Normal-world guest discovers the partitions, reads framework and service versions, is refused an unknown service, and completes a data-carrying `psa_call` through the FF-M gateway: a wolfHSM echo under `hsm`, two random draws over the native wire under `native` | +| `guest1` | SKIP: the AArch64 ports run a single Normal-world endpoint (`0x0000`), so the second-guest identity path has no AArch64 counterpart; M33MU covers it | +| `crossdomain`, `keystoreneg`, `periphspneg` | A partition reading outside its domain, a non-keystore partition reading the keystore band, or a partition reading the SPM's secure UART takes a data abort at S-EL0, spends its restart budget, and escalates to fail-closed recovery | +| `bandneg1` to `bandneg6` | Each of the vault, attestation, and crypto partitions reads and then writes another one's private band at boot; both accesses take a data abort at S-EL0 on that band, and every partition still initializes | +| `restartneg1` to `restartneg3` | The crypto, attestation, or vault partition plants state in its private band and faults once; the restarted instance finds the band back at its link-time image, and every partition still initializes | +| `spfaultneg`, `panicneg` | A partition that faults once, or is panicked for a programmer error, is restarted by manifest policy and every partition still initializes | +| `svcneg` | A service partition that issues the SPMC's own yield SVC is a programmer error: it is panicked and restarted, and every partition still initializes | +| `fpneg` | A partition's FP instruction traps at S-EL0 (FP is disabled for partitions), the fault is contained, and the partition restarts | +| `mspovfneg` | The SPMC overflows its own stack into the unmapped guard page below it; the abort at S-EL1 panics fail-closed before any partition starts | +| `xnneg` | From a partition's SVC the SPMC branches into code it wrote into SPM RAM; the execute-never fetch aborts at S-EL1 and panics fail-closed | +| `spbudgetneg` | A partition that faults on every entry exhausts its restart budget and escalates to fail-closed recovery | +| `tablesneg` | The stage-1 table builder refuses a writable and executable region and the SPMC panics before its MMU is on | +| `proofneg` | A boot self-test forced to fail stops the SPMC with its panic before any partition starts | +| `manifestneg`, `manifestneg2`, `manifestneg3` | A corrupted manifest, a manifest that declares isolation level 2, or a vault table granted part of the crypto partition's band stops the boot with the manifest-validation panic code | +| `ffa-direct` | A direct request from the monitor's test driver reaches an S-EL0 echo partition and returns complemented | +| `ffa-sint` | A Secure interrupt is signaled to its owning partition while it waits and queued while it runs | +| `ns-smoke`, `ffa-discovery` | The Normal-world payload runs at NS-EL1, negotiates FF-A 1.2 with the SPMD, and discovers the partitions through the SPMC | +| `ffa-guest-direct` | A Normal-world direct request reaches a Secure partition and echoes back, a refused `FFA_MSG_SEND_DIRECT_REQ2` returns `x8`-`x17` zero, and a request a Normal-world interrupt preempts is resumed by `FFA_RUN` with the id `FFA_INTERRUPT` named | +| `psci` | The Normal world reads `ICC_SRE_EL1` with SRE set under a GICv3, checks the mandatory PSCI 1.1 calls as a boot-core-only system sees them (`CPU_ON`, `CPU_OFF`, `AFFINITY_INFO`, `CPU_SUSPEND`, `MIGRATE` and the migrate queries, `PSCI_FEATURES`; each SMC64 error compared across all of `x0`, so it must be sign-extended; on `virt` a second core parks beside it and is not a valid `CPU_ON`, `AFFINITY_INFO`, or `MIGRATE` target), `SMCCC_VERSION` 1.2 with `SMCCC_ARCH_FEATURES` and `x4`-`x7` preserved across a PSCI call answered alone and one sent to the SPMC, and powers off through the SPMD; the SPMC is told of `CPU_OFF` (it denies) and `SYSTEM_OFF` (it grants), and never of the invalid `CPU_SUSPEND` | +| `ffa-preempt` | A core-standby `CPU_SUSPEND`, granted by the SPMC's answer to its power message, wakes on the Secure tick, the tick preempts the Normal world at EL3, the SPMC services it, and the Normal world resumes | +| `resetneg` | A Normal-world `SYSTEM_RESET`, granted by the SPMC's answer to its power message, reboots the chain once and the second reset ends the run; both boots count every parked secondary (two cores on `virt`). The reset is a cold one on both machines: a UART register the first boot marked reads its reset value again. `virt` resets through its Secure GPIO and ends at the monitor's reset limit; `xlnx-versal-virt`, which models no reset controller, is powered off by the monitor and on again by the runner once, and ends at the runner's power-cycle limit | +| `secramneg`, `periphneg` | A Normal-world read of Secure RAM, or of the SPM's secure UART, is refused (SKIP on `xlnx-versal-virt`, whose model has no XMPU, RISAF, or XPPU, so that port claims no isolation level) | +| `psci-el2` | The `psci` checks from a Normal world entered at NS-EL2, which then clears `HCR_EL2.RW` and runs an AArch32 EL1 caller: its SMC32 `PSCI_VERSION` and a count-only `FFA_PARTITION_INFO_GET` forwarded to the SPMC return to AArch32, and an SMC64 id answers `-1` | +| `el2dirtyneg` | The `psci` checks on a monitor that starts on EL2 state an earlier stage left dirty (SMC trapped, a foreign virtual MPIDR, `ICC_SRE_EL2` clear), and under a GICv3 with every SPI's `GICD_IROUTER` naming a PE that does not exist: the Secure SPI the SPMC enables still reaches it; `virt` turns EL2 on for it | +| `smcfuzz` | Every unimplemented SMC function id from the Normal world is refused cleanly (`-1` sign-extended through all of `x0` for an SMC64 id), and an SMC32 call with junk in its upper register halves is read as `w1`-`w7` | +| `ffa-memneg` | Malformed memory transactions from the Normal world are refused, as is a share sent before an RX/TX pair is mapped or naming a dynamically allocated buffer; a reclaimed handle is dead, and a share sent in two fragments completes under the handle its first fragment reserved | +| `hsmattackneg` | Under `hsm`, a forged wolfHSM client id cannot reach the attestation key and an NVM-group request never reaches the server; SKIP under `native`, which links no wolfHSM wire | +| `hsmpinneg` | Under `hsm`, the relay forges its wolfHSM server pointers into SPM RAM before every pin and the `positive` flow still completes, so the relay re-pins them; SKIP under `native` | +| `attestneg` | Oversized challenges, empty token buffers, misattributed lifecycles, and tampered tokens are rejected; an untampered token verifies in the guest | +| `vaultrecover`, `vaultrecoversec` | A foreign vault self-heals under an unlocked lifecycle and is refused, failing closed, under a locked one, where the guest's attestation key query is refused as a reformatted vault would not refuse it | +| `storage` | The Normal world round-trips Internal Trusted Storage through the vault partition | +| `confboot` | The Arm FF-M IPC suite from the Normal world: 85 passed and 4 skipped on the `virt` cells; 78 and 4 on `xlnx-versal-virt`, where the seven Normal-world fence tests cannot fault without an XMPU model | +| `devstorage`, `devattest`, `devcrypto` | The Arm dev_apis storage, attestation, and crypto suites from the Normal world over FF-A; the crypto suite (and `vaultrecover`) must total exactly 64 passed and 13 skipped, so a newly skipped test fails the run | +| `ffaacs-discovery`, `ffaacs-direct`, `ffaacs-memory`, `ffaacs-notify`, `ffaacs-indirect`, `ffaacs-interrupts` | One Arm FF-A ACS test group each, asserted at a floor with no SIM ERROR; see [FF-A Compatibility](FF-A-Compatibility.md) for the counts and by-design deviations | + +Beyond the board-specific (`gtzcneg`, `revneg`, `ahbscneg`), VNET (`vnet`, +`vnetneg`), and wolfBoot update (`bootupdate`, `fwustage`) scenarios, these +M33MU scenarios have no AArch64 twin: + +- `restart`, `hsmfaultneg`, `authneg`, `remeasureneg`, `bothiso`, and + `bothpsa` need managed Normal-world guests; the AArch64 ports run one + unmanaged Normal-world endpoint, and its HSM tasklet lives in the SPMC. +- `rollbackneg` needs a version floor that survives a reset; the QEMU ports + keep NVM in RAM. +- `deputyneg` has no privileged flash deputy to test; the vault's NVM sits in + its own band. +- `sealneg`, `sealbootneg`, `sealhaltneg`, and `sealpivotneg` test the + Armv8-M stack seals. ## STM32H563 hardware @@ -441,6 +528,9 @@ The workflows under `.github/workflows/` separately run: - host unit tests; - compiler variants, sanitizers, and Valgrind; - Cortex-M33 cross-compilation of both crypto engines; +- AArch64 cross-compilation with the EL3 symbol guard at link, every QEMU + AArch64 scenario, and the FF-A ACS groups on the three QEMU cells (`virt` + GICv2 and GICv3, `xlnx-versal-virt`); - dependency integration; - the core/port split guard and the docs guard (no internal-ledger or home-directory references in the published docs); @@ -450,8 +540,9 @@ The workflows under `.github/workflows/` separately run: ### Trigger routing -The host, compiler, sanitizer, Valgrind, cross-compilation, integration, and -core/port split checks run on every pull request, including drafts. The fuzz +The host, compiler, sanitizer, Valgrind, cross-compilation (Cortex-M33 and +AArch64), integration, and core/port split checks run on every pull request, +including drafts. The fuzz target also runs on pull requests as a 60-second libFuzzer smoke pass; the nightly schedule and manual dispatch run the 600-second soak instead. @@ -464,7 +555,7 @@ each port's smoke tier on both crypto engines (STM32H563: `positive`, The full matrix runs on a push to `main`, on the nightly schedule, on manual dispatch (with a `port` input), and on a pull request that carries the -`ci:h5`, `ci:rt700`, or `ci:all` label. The scenario groups per port and tier +`ci:stm32h563`, `ci:imxrt700`, or `ci:all` label. The scenario groups per port and tier are in `tests/target/lib/scenario_matrix.py`; `make test-target TARGET=` runs the same smoke tier locally and `WT_TIER=full` every scenario. diff --git a/docs/Threat-Model.md b/docs/Threat-Model.md index d5ed3192..28618f0e 100644 --- a/docs/Threat-Model.md +++ b/docs/Threat-Model.md @@ -125,6 +125,16 @@ Unprivileged service threads request flash, entropy, locking, and reset through SVC gates. The handler validates the originating partition, but a defect in that privileged dispatcher is within the trusted computing base. +### Normal-world memory lent to an AArch64 partition stays Normal-world memory + +On the AArch64 ports the Normal-world kernel removes its own mapping when it +lends or donates memory to a Secure Partition (FF-A memory management, DEN0140 +1.4.1); there is no Hypervisor stage-2 or memory firewall to enforce that. A +privileged Normal world can therefore keep reading and writing memory it lent, +donated, or shared while a partition holds it. Partitions must copy such memory +into their own memory before validating it and must keep no secret in it. See +[FF-A Compatibility](FF-A-Compatibility.md). + ### Availability is bounded, not guaranteed A hostile guest can spend its own execution time on rejected requests and can diff --git a/docs/_Sidebar.md b/docs/_Sidebar.md index eaf8a7e2..51a7f5e2 100644 --- a/docs/_Sidebar.md +++ b/docs/_Sidebar.md @@ -9,6 +9,7 @@ - [[API Reference]] - [[Services]] - [[TF-M Compatibility]] +- [[FF-A Compatibility]] - [[Macros]] - [[Porting]] - [[Building]] diff --git a/include/wolftrust/arch/aarch64/context.h b/include/wolftrust/arch/aarch64/context.h new file mode 100644 index 00000000..abf8a81d --- /dev/null +++ b/include/wolftrust/arch/aarch64/context.h @@ -0,0 +1,55 @@ +/* context.h + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfTrust. + * + * wolfTrust 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. + * + * wolfTrust 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, see . + */ + +/* AArch64 bodies of the core's forward-declared context and trap frame. */ + +#ifndef WOLFTRUST_ARCH_AARCH64_CONTEXT_H +#define WOLFTRUST_ARCH_AARCH64_CONTEXT_H + +#include +#include + +#include "wolftrust/platform.h" + +/* Lower-EL exception frame the S-EL1 vectors build: x0-x30, then the + * banked state. Offsets are fixed for the assembly entry paths. */ +struct wt_trap_frame { + uint64_t x[31]; + uint64_t sp_el0; + uint64_t elr; + uint64_t spsr; + uint64_t esr; + uint64_t far; +}; + +/* Non-secure endpoint context held by the neutral runtime; the NS gateway + * fills it in when the Normal world arrives. */ +struct wt_guest_context { + uint64_t x[31]; + uint64_t sp_el0; + uint64_t elr; + uint64_t spsr; + uintptr_t pc; + bool frame_stacked; +}; + +#define WT_TRAP_FRAME_SIZE 288u + +#endif /* WOLFTRUST_ARCH_AARCH64_CONTEXT_H */ diff --git a/include/wolftrust/arch/aarch64/domain.h b/include/wolftrust/arch/aarch64/domain.h new file mode 100644 index 00000000..e89ca5e6 --- /dev/null +++ b/include/wolftrust/arch/aarch64/domain.h @@ -0,0 +1,136 @@ +/* domain.h + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfTrust. + * + * wolfTrust 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. + * + * wolfTrust 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, see . + */ + +#ifndef WOLFTRUST_ARCH_AARCH64_DOMAIN_H +#define WOLFTRUST_ARCH_AARCH64_DOMAIN_H + +#include "wolftrust/types.h" +#include "wolftrust/arch/aarch64/tables.h" + +#include +#include + +/* Partition domains at S-EL1: one prebuilt stage-1 table per region set, + * keyed by the stable regions pointer the core hands to the wt_arch_* + * domain operations; ASID 0 is the SPM-only table, partitions get 1.. */ + +/* Every partition (WT_CO_MAX) plus each boot self-test domain, which stays + * built (spm_main.c: prove_el0, the echo, spin, discover, and borrow + * partitions), and the extra table a memory-sharing borrower rebuilds while + * it holds a retrieved region; coroutine_aarch64.c checks the sum. */ +#define WT_DOMAIN_PROOF_TABLES 5u +#define WT_DOMAIN_MAX_TABLES 20u +/* Fill entries a partition table keeps EL1-only unless the partition's own + * regions cover one flagged WT_DOMAIN_FILL_SHARED entirely, in which case + * the partition's mapping (EL0 + EL1) replaces it. Partial cover still + * fails. A shareable range is mapped non-global in every table (it is the + * builder's WT_TABLES_ATTR_NG hint) so no ASID inherits another's entry. One + * also flagged WT_DOMAIN_FILL_OWNED is a single endpoint's own memory: only a + * region exactly equal to it takes it over, and one that covers more fails. */ +#define WT_DOMAIN_MAX_FILL 32u +#define WT_DOMAIN_FILL_SHARED WT_TABLES_ATTR_NG +#define WT_DOMAIN_FILL_OWNED 0x10000000u + +#define WT_DOMAIN_FAIL_INIT 1 +#define WT_DOMAIN_FAIL_BUILD 2 +#define WT_DOMAIN_FAIL_SLOTS 3 + +/* The memory resource a manifest partition's stack runs in, exactly as the + * scheduler picks it (the last private writable Normal resource holding the + * declared stack), so the SPMC maps all of it. Returns 0, or -1 for none. */ +int wt_domain_stack_band(const wt_domain_descriptor_t* d, + wt_memory_region_t* band); + +/* Resource i of a manifest partition when the SPMC itself writes it from EL1 + * (its stack band, or a private band the fault scrub clears), so every table + * maps it EL1-only. Returns 0 with *band set, or -1. */ +int wt_domain_spm_band(const wt_domain_descriptor_t* d, size_t i, + wt_memory_region_t* band); + +/* Non-zero when [base, base + size) reaches an owned fill entry that owners + * (one per fill entry) gives to anyone but owner: memory a partition's + * manifest may not name, since its table would take that entry over. */ +int wt_domain_fill_foreign(const wt_memory_region_t* fill, + const uint32_t* owners, size_t fill_count, + uint32_t owner, uintptr_t base, size_t size); + +/* Builds the SPM-only table over the pool; returns its TTBR0 or 0. The + * fill list is kept and mapped EL1-only into every partition table. */ +uint64_t wt_domain_init(const wt_memory_region_t* fill, size_t fill_count, + uint8_t* pool, uint64_t pool_pa, size_t pool_size); + +uint64_t wt_domain_current_ttbr0(void); +size_t wt_domain_tables_built(void); +size_t wt_domain_pool_pages_used(void); + +/* FFA_MEM_PERM_SET/GET on an already-built domain, over pages the caller has + * found to be the partition's own (wt_tables_set_el0_attributes: its EL0 + * pages, Normal or Device); the range starts on a page; WT_TABLES_* result + * codes. A Device page keeps its memory type and is never made executable. GET + * reports no WT_MEM_ATTR_READ for a page the partition cannot reach at EL0 + * (one a transaction holds or it made no-access). */ +int wt_domain_set_permissions(const wt_memory_region_t* regions, size_t count, + uintptr_t va, size_t pages, uint32_t attributes); +int wt_domain_get_permissions(const wt_memory_region_t* regions, size_t count, + uintptr_t va, uint32_t* attributes); + +/* The EL0 access the domain's table gives va as Normal write-back memory + * (whatever its region list says; a Device page is none): memory the partition + * reaches so at EL0 is memory it may itself send. */ +#define WT_DOMAIN_ACCESS_NONE 0 +#define WT_DOMAIN_ACCESS_RO 1 +#define WT_DOMAIN_ACCESS_RW 2 +int wt_domain_page_access(const wt_memory_region_t* regions, size_t count, + uintptr_t va); +/* Non-zero when the domain's table holds va as the partition's in any form: + * reachable at EL0, made no-access by the partition, or held while a + * transaction it sent covers it; never an SPMC EL1-only entry. */ +int wt_domain_page_claimed(const wt_memory_region_t* regions, size_t count, + uintptr_t va); +/* Non-zero when the domain's table gives va to the partition at EL0, or keeps + * it as a page the partition made no-access, and no transaction holds it. */ +int wt_domain_page_owned(const wt_memory_region_t* regions, size_t count, + uintptr_t va); +/* Non-zero when the domain's table maps va as Non-secure memory. */ +int wt_domain_page_ns(const wt_memory_region_t* regions, size_t count, + uintptr_t va); + +/* Lend a built domain a window onto memory outside its own regions, and take + * it back (memory sharing); WT_TABLES_* result codes. */ +int wt_domain_grant(const wt_memory_region_t* regions, size_t count, + uintptr_t va, size_t pages, uint32_t attributes, + int* was_mapped); +int wt_domain_revoke(const wt_memory_region_t* regions, size_t count, + uintptr_t va, size_t pages, int was_mapped); + +/* An owner's own pages while a transaction holds them, with no EL0 access + * at all, and back exactly as they were (wt_tables_hold_el0/withdraw_el0/ + * release_el0); WT_TABLES_* result codes. */ +int wt_domain_owner_hold(const wt_memory_region_t* regions, size_t count, + uintptr_t va, size_t pages, int keep_read); +int wt_domain_owner_withdraw(const wt_memory_region_t* regions, size_t count, + uintptr_t va, size_t pages); +int wt_domain_owner_release(const wt_memory_region_t* regions, size_t count, + uintptr_t va, size_t pages); + +/* Fail-closed hook: the SPMC image panics, the host suite records it. */ +void wt_domain_fail(int code); + +#endif /* WOLFTRUST_ARCH_AARCH64_DOMAIN_H */ diff --git a/include/wolftrust/arch/aarch64/el3.h b/include/wolftrust/arch/aarch64/el3.h new file mode 100644 index 00000000..67439522 --- /dev/null +++ b/include/wolftrust/arch/aarch64/el3.h @@ -0,0 +1,158 @@ +/* el3.h + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfTrust. + * + * wolfTrust 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. + * + * wolfTrust 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, see . + */ + +#ifndef WOLFTRUST_ARCH_AARCH64_EL3_H +#define WOLFTRUST_ARCH_AARCH64_EL3_H + +#include + +/* EL3 monitor internals shared between its objects and the port's EL3 hooks. */ + +#define WT_EL3_MAX_CPUS 4u + +/* Vector slot indexes handed to wt_el3_exception. */ +#define WT_EL3_VEC_CUR_SP0_SYNC 0u +#define WT_EL3_VEC_CUR_SP0_IRQ 1u +#define WT_EL3_VEC_CUR_SP0_FIQ 2u +#define WT_EL3_VEC_CUR_SP0_SERROR 3u +#define WT_EL3_VEC_CUR_SPX_SYNC 4u +#define WT_EL3_VEC_CUR_SPX_IRQ 5u +#define WT_EL3_VEC_CUR_SPX_FIQ 6u +#define WT_EL3_VEC_CUR_SPX_SERROR 7u +#define WT_EL3_VEC_LOWER64_SYNC 8u +#define WT_EL3_VEC_LOWER64_IRQ 9u +#define WT_EL3_VEC_LOWER64_FIQ 10u +#define WT_EL3_VEC_LOWER64_SERROR 11u +#define WT_EL3_VEC_LOWER32_SYNC 12u + +/* Register frame the vector table saves; layout is shared with vectors.S. The + * full x0-x30 is captured so a world switch can resume a lower EL exactly (the + * callee-saved x19-x28 carry the SPMC's neutral-core state across an NS + * excursion). */ +typedef struct wt_el3_frame { + uint64_t x[31]; + uint64_t elr; + uint64_t spsr; + uint64_t pad; +} wt_el3_frame_t; + +/* A saved world (Secure SPMC or Normal-world guest). EL1 system registers are + * not banked by security state on these cores, so a world switch saves and + * restores the running world's full register file (frame) and its EL1 context + * around every SPMD entry. */ +typedef struct wt_el3_world { + wt_el3_frame_t frame; + uint64_t scr_el3; + uint64_t sp_el0; + uint64_t sp_el1; + uint64_t sctlr_el1; + uint64_t ttbr0_el1; + uint64_t ttbr1_el1; + uint64_t tcr_el1; + uint64_t mair_el1; + uint64_t amair_el1; + uint64_t vbar_el1; + uint64_t tpidr_el0; + uint64_t tpidrro_el0; + uint64_t tpidr_el1; + uint64_t contextidr_el1; + uint64_t cpacr_el1; + uint64_t elr_el1; + uint64_t spsr_el1; + uint64_t esr_el1; + uint64_t far_el1; + uint64_t par_el1; + uint64_t mdscr_el1; + uint64_t cntkctl_el1; +} wt_el3_world_t; + +extern volatile uint8_t g_wt_el3_parked[WT_EL3_MAX_CPUS]; +extern volatile uint32_t g_wt_el3_ready; +extern volatile uint32_t g_wt_el3_tick_intid; + +void wt_el3_puts(const char* text); +void wt_el3_puthex(uint64_t value, unsigned int digits); +void wt_el3_putdec(uint64_t value); + +void wt_el3_semihost_exit(uint64_t code) __attribute__((noreturn)); +void wt_el3_enter_secure_el1(void (*entry)(void), uintptr_t stack_top, + uint64_t x0_arg) __attribute__((noreturn)); +/* Drop to NS-EL1 to run the Normal world; x0_arg reaches it in x0. */ +void wt_el3_enter_ns(void (*entry)(void), uintptr_t sp, + uint64_t x0_arg) __attribute__((noreturn)); +/* Why the Normal world is currently paused in the Secure world. */ +#define WT_NS_PENDING_NONE 0u +#define WT_NS_PENDING_REPLY 1u /* an SMC it made is being served; deliver x0-x7 */ +#define WT_NS_PENDING_RESUME 2u /* a Secure interrupt preempted it; resume as-is */ +#define WT_NS_PENDING_PM 3u /* its PSCI call awaits the SPMC's power answer */ + +/* The SPMC signalled initialization complete with FFA_MSG_WAIT: launch the + * Normal world (saving the SPMC so it can be resumed), or exit when there is no + * Normal-world payload. ERETs into the launched world; never returns. */ +void wt_el3_world_launch_ns(wt_el3_frame_t* frame) __attribute__((noreturn)); +/* Forward the NS call in `frame` to the SPMC as the return of its blocked + * FFA_MSG_WAIT, and switch to the Secure world to run it. Never returns. */ +void wt_el3_world_forward_to_secure(wt_el3_frame_t* frame) + __attribute__((noreturn)); +/* Hand the SPMC the power management request msg (x0-x17) for the NS PSCI + * call in `frame`, which stays as it is. Never returns. */ +void wt_el3_world_pm_to_secure(wt_el3_frame_t* frame, const uint64_t* msg) + __attribute__((noreturn)); +/* End that PSCI call with x0, x1-x3 zero and x4-x17 as the caller left them + * (SMCCC 1.1 and later), and switch to the Normal world. Never returns. */ +void wt_el3_world_pm_return_to_ns(wt_el3_frame_t* frame, uint64_t x0) + __attribute__((noreturn)); +/* A Secure interrupt preempted the Normal world: deliver FFA_INTERRUPT to the + * SPMC, which takes the interrupt from the GIC itself, and switch to it, + * keeping the NS context to resume. Never returns. */ +void wt_el3_world_preempt_to_secure(wt_el3_frame_t* frame) + __attribute__((noreturn)); +/* Deliver the SPMC's reply in `frame` back to the Normal world and switch to + * it. Never returns. */ +void wt_el3_world_return_to_ns(wt_el3_frame_t* frame) __attribute__((noreturn)); +/* Resume the preempted Normal world where it left off (no reply). Never returns. */ +void wt_el3_world_resume_ns(wt_el3_frame_t* frame) __attribute__((noreturn)); +/* Why the Normal world is paused in the Secure world (WT_NS_PENDING_*). */ +unsigned int wt_el3_world_ns_pending(void); +/* 0 on the cold boot, non-zero after a warm reset (the .noinit boot counter). */ +unsigned int wt_el3_reset_count(void); +/* Enter a saved world: program SCR_EL3, restore its register file, and ERET. */ +void wt_el3_world_eret(const wt_el3_frame_t* frame, uint64_t scr_el3) + __attribute__((noreturn)); +void wt_el3_fault(uint64_t kind, uint64_t esr, uint64_t far, uint64_t elr) + __attribute__((noreturn)); +uint64_t wt_el3_monitor_call(uint32_t fid, uint64_t arg); +void wt_el3_exception(uint64_t kind, wt_el3_frame_t* frame); +void wt_el3_main(void) __attribute__((noreturn)); + +void wt_el3_timer_arm_ms(uint32_t ms); +void wt_el3_timer_disable(void); + +/* Port hooks the EL3 image needs (the tools/el3-symbols.allow set). */ +void wt_platform_board_init(void); +/* Cold-reset the machine; returns only where the platform has no reset. */ +void wt_platform_board_system_reset(void); +void wt_platform_console_putc(char c); +void wt_platform_console_flush(void); + +/* The S-EL1 entry the monitor drops into; the SPM side owns it. */ +void wt_spm_entry(void) __attribute__((noreturn)); + +#endif /* WOLFTRUST_ARCH_AARCH64_EL3_H */ diff --git a/include/wolftrust/arch/aarch64/esr.h b/include/wolftrust/arch/aarch64/esr.h new file mode 100644 index 00000000..943942b2 --- /dev/null +++ b/include/wolftrust/arch/aarch64/esr.h @@ -0,0 +1,39 @@ +/* esr.h + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfTrust. + * + * wolfTrust 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. + * + * wolfTrust 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, see . + */ + +#ifndef WOLFTRUST_ARCH_AARCH64_ESR_H +#define WOLFTRUST_ARCH_AARCH64_ESR_H + +#include "wolftrust/types.h" + +#include +#include + +/* Pure decode of an exception syndrome; no system register access, so the + * host suite can drive every row. */ +wt_fault_reason_t wt_esr_classify(uint64_t esr, uint64_t far, int from_ns, + uint64_t guard_base, uint64_t guard_size); + +/* Writes "[SYNC EL= EC=0x.. ISS=0x....... FAR=0x................]" (all + * 25 ISS bits) and returns the length written (always NUL-terminated). */ +size_t wt_esr_format(char* out, size_t out_size, uint32_t el, uint64_t esr, + uint64_t far); + +#endif /* WOLFTRUST_ARCH_AARCH64_ESR_H */ diff --git a/include/wolftrust/arch/aarch64/ffa.h b/include/wolftrust/arch/aarch64/ffa.h new file mode 100644 index 00000000..12115ab4 --- /dev/null +++ b/include/wolftrust/arch/aarch64/ffa.h @@ -0,0 +1,117 @@ +/* ffa.h + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfTrust. + * + * wolfTrust 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. + * + * wolfTrust 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, see . + */ + +#ifndef WOLFTRUST_ARCH_AARCH64_FFA_H +#define WOLFTRUST_ARCH_AARCH64_FFA_H + +#include + +/* One FF-A call: x0 = function id, x1-x7 parameters in, x0-x7 results out. */ +typedef struct wt_ffa_regs { + uint64_t x[8]; +} wt_ffa_regs_t; + +/* SMC conduit (S-EL1 -> EL3, NS EL1 -> EL3). x8-x17 are caller-saved. Only + * the target has the conduit; host suites drive the dispatchers directly. */ +#if defined(__aarch64__) +static inline void wt_ffa_smc(wt_ffa_regs_t* r) +{ + register uint64_t x0 __asm__("x0") = r->x[0]; + register uint64_t x1 __asm__("x1") = r->x[1]; + register uint64_t x2 __asm__("x2") = r->x[2]; + register uint64_t x3 __asm__("x3") = r->x[3]; + register uint64_t x4 __asm__("x4") = r->x[4]; + register uint64_t x5 __asm__("x5") = r->x[5]; + register uint64_t x6 __asm__("x6") = r->x[6]; + register uint64_t x7 __asm__("x7") = r->x[7]; + + __asm__ volatile("smc #0" + : "+r"(x0), "+r"(x1), "+r"(x2), "+r"(x3), + "+r"(x4), "+r"(x5), "+r"(x6), "+r"(x7) + : + : "x8", "x9", "x10", "x11", "x12", "x13", "x14", + "x15", "x16", "x17", "memory"); + r->x[0] = x0; + r->x[1] = x1; + r->x[2] = x2; + r->x[3] = x3; + r->x[4] = x4; + r->x[5] = x5; + r->x[6] = x6; + r->x[7] = x7; +} +#endif /* __aarch64__ */ + +/* The extended form FFA_MSG_SEND_DIRECT_REQ2/RESP2 use: x8-x17 carry payload + * too, so the conduit loads and captures all eighteen registers. */ +typedef struct wt_ffa_regs_ext { + wt_ffa_regs_t base; + uint64_t ext[10]; +} wt_ffa_regs_ext_t; + +/* spm_switch.S; eighteen operands exceed what an inline asm may name. */ +void wt_ffa_smc_ext(wt_ffa_regs_ext_t* r); + +/* wt_ffa_spmd_secure_call outcomes. */ +#define WT_SPMD_ACTION_REPLY 0 /* r holds the reply; return to the SPMC */ +#define WT_SPMD_ACTION_LAUNCH 1 /* SPMC init done; launch the Normal world */ + +/* EL3 (SPMD) handling of one FF-A call taken at the Secure physical instance. + * Fills r with the FFA_SUCCESS/FFA_ERROR reply and returns WT_SPMD_ACTION_REPLY, + * or returns WT_SPMD_ACTION_LAUNCH when the SPMC has finished initializing. */ +int wt_ffa_spmd_secure_call(wt_ffa_regs_t* r); +/* EL3 (SPMD) handling of one FF-A call taken at the NS physical instance (from + * the Normal world once launched); fills r with the reply. */ +void wt_ffa_spmd_ns_call(wt_ffa_regs_t* r); +/* Called for every NS-instance call before it is answered or forwarded. */ +void wt_ffa_spmd_ns_note(uint32_t fid); +/* Called for every Secure physical instance call from the SPMC. */ +void wt_ffa_spmd_secure_note(uint32_t fid); +int wt_ffa_spmd_secure_available(uint32_t fid); +/* Non-zero when an NS-instance FID must be forwarded to the SPMC rather than + * answered by the SPMD (partition discovery, guest-to-SP messaging). */ +int wt_ffa_spmd_ns_forwards(uint32_t fid); +/* A call wt_ffa_spmd_ns_forwards selected, in the saved frame x[0..17]: 1 to + * forward x to the SPMC, or 0 when the SPMD has answered it in x instead. */ +int wt_ffa_spmd_ns_forward(uint64_t* x); +/* The SPMC's reply to a forwarded call, in its saved frame, before it is + * returned: the Table 13.8 answer to a forwarded FFA_VERSION becomes that + * call's result in w0 (13.2.3.2); any other reply is left as it is. */ +void wt_ffa_spmd_ns_reply(uint64_t* x); +/* Non-zero when an SMC from the SPMC is the reply to a forwarded NS call. */ +int wt_ffa_spmd_is_ns_reply(uint32_t fid); +/* Non-zero when an SMC from the SPMC yields the CPU back to the Normal world. */ +int wt_ffa_spmd_is_ns_resume(uint32_t fid); +/* 18.2.4: what an SMC from the SPMC is while a power management request is + * outstanding. Its answer is GRANTED only for a Table 18.8 SUCCESS and DENIED + * for any other reply; a bare switch to the Normal world (FFA_MSG_WAIT, + * FFA_YIELD, FFA_NORMAL_WORLD_RESUME) is REFUSED, x then holding the + * FFA_ERROR(DENIED) the SPMC gets back. Any other call is NONE. */ +#define WT_SPMD_PM_NONE 0 +#define WT_SPMD_PM_GRANTED 1 +#define WT_SPMD_PM_DENIED 2 +#define WT_SPMD_PM_REFUSED 3 +int wt_ffa_spmd_pm_answer(uint64_t* x); +unsigned int wt_ffa_spmd_spmc_ready(void); +/* FFA_CONSOLE_LOG over x[0..7] (SMC32) or x[0..17] (SMC64); the reply lands + * in x[0..7]. The caller hands the saved register frame directly. */ +void wt_ffa_spmd_console_call(uint64_t* x, unsigned int is64); + +#endif /* WOLFTRUST_ARCH_AARCH64_FFA_H */ diff --git a/include/wolftrust/arch/aarch64/ffa_abi.h b/include/wolftrust/arch/aarch64/ffa_abi.h new file mode 100644 index 00000000..f828c71f --- /dev/null +++ b/include/wolftrust/arch/aarch64/ffa_abi.h @@ -0,0 +1,362 @@ +/* ffa_abi.h + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfTrust. + * + * wolfTrust 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. + * + * wolfTrust 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, see . + */ + +#ifndef WOLFTRUST_ARCH_AARCH64_FFA_ABI_H +#define WOLFTRUST_ARCH_AARCH64_FFA_ABI_H + +#include + +/* Arm FF-A v1.2 function ids, status codes, and ids (DEN0077A). This is the + * only file that may spell an FF-A function id. */ + +#define WT_FFA_VERSION_MAJOR 1u +#define WT_FFA_VERSION_MINOR 2u +#define WT_FFA_VERSION_1_2 0x00010002u +#define WT_FFA_VERSION_MAKE(maj, min) ((uint32_t)(((maj) << 16) | (min))) +#define WT_FFA_VERSION_MAJOR_OF(v) (((v) >> 16) & 0x7FFFu) +#define WT_FFA_VERSION_MINOR_OF(v) ((v) & 0xFFFFu) + +#define WT_FFA_FID32_FIRST 0x84000060u +#define WT_FFA_FID32_LAST 0x840000FFu +#define WT_FFA_FID64_FIRST 0xC4000060u +#define WT_FFA_FID64_LAST 0xC40000FFu + +#define WT_FFA_ERROR 0x84000060u +#define WT_FFA_SUCCESS32 0x84000061u +#define WT_FFA_SUCCESS64 0xC4000061u +#define WT_FFA_INTERRUPT 0x84000062u +#define WT_FFA_VERSION 0x84000063u +#define WT_FFA_FEATURES 0x84000064u +#define WT_FFA_RX_RELEASE 0x84000065u +#define WT_FFA_RXTX_MAP32 0x84000066u +#define WT_FFA_RXTX_MAP64 0xC4000066u +#define WT_FFA_RXTX_UNMAP 0x84000067u +#define WT_FFA_PARTITION_INFO_GET 0x84000068u +#define WT_FFA_ID_GET 0x84000069u +#define WT_FFA_MSG_WAIT 0x8400006Bu +#define WT_FFA_YIELD 0x8400006Cu +#define WT_FFA_RUN 0x8400006Du +#define WT_FFA_MSG_SEND_DIRECT_REQ32 0x8400006Fu +#define WT_FFA_MSG_SEND_DIRECT_REQ64 0xC400006Fu +#define WT_FFA_MSG_SEND_DIRECT_RESP32 0x84000070u +#define WT_FFA_MSG_SEND_DIRECT_RESP64 0xC4000070u +/* DEN0140 memory management. DONATE/LEND/SHARE/RETRIEVE_REQ have SMC32 and + * SMC64 conventions; RETRIEVE_RESP/RELINQUISH/RECLAIM/FRAG_* are SMC32 only. */ +#define WT_FFA_MEM_DONATE32 0x84000071u +#define WT_FFA_MEM_DONATE64 0xC4000071u +#define WT_FFA_MEM_LEND32 0x84000072u +#define WT_FFA_MEM_LEND64 0xC4000072u +#define WT_FFA_MEM_SHARE32 0x84000073u +#define WT_FFA_MEM_SHARE64 0xC4000073u +#define WT_FFA_MEM_RETRIEVE_REQ32 0x84000074u +#define WT_FFA_MEM_RETRIEVE_REQ64 0xC4000074u +#define WT_FFA_MEM_RETRIEVE_RESP 0x84000075u +#define WT_FFA_MEM_RELINQUISH 0x84000076u +#define WT_FFA_MEM_RECLAIM 0x84000077u +#define WT_FFA_MEM_FRAG_RX 0x8400007Au +#define WT_FFA_MEM_FRAG_TX 0x8400007Bu +#define WT_FFA_NORMAL_WORLD_RESUME 0x8400007Cu +#define WT_FFA_NOTIFICATION_BITMAP_CREATE 0x8400007Du +#define WT_FFA_NOTIFICATION_BITMAP_DESTROY 0x8400007Eu +#define WT_FFA_NOTIFICATION_BIND 0x8400007Fu +#define WT_FFA_NOTIFICATION_UNBIND 0x84000080u +#define WT_FFA_NOTIFICATION_SET 0x84000081u +#define WT_FFA_NOTIFICATION_GET 0x84000082u +#define WT_FFA_NOTIFICATION_INFO_GET32 0x84000083u +#define WT_FFA_NOTIFICATION_INFO_GET64 0xC4000083u +#define WT_FFA_RX_ACQUIRE 0x84000084u +#define WT_FFA_SPM_ID_GET 0x84000085u +#define WT_FFA_MSG_SEND2 0x84000086u +#define WT_FFA_MEM_PERM_GET32 0x84000088u +#define WT_FFA_MEM_PERM_GET64 0xC4000088u +#define WT_FFA_MEM_PERM_SET32 0x84000089u +#define WT_FFA_MEM_PERM_SET64 0xC4000089u +#define WT_FFA_CONSOLE_LOG32 0x8400008Au +#define WT_FFA_CONSOLE_LOG64 0xC400008Au +#define WT_FFA_PARTITION_INFO_GET_REGS 0xC400008Bu +#define WT_FFA_MSG_SEND_DIRECT_REQ2 0xC400008Du +#define WT_FFA_MSG_SEND_DIRECT_RESP2 0xC400008Eu + +#define WT_FFA_NOT_SUPPORTED (-1) +#define WT_FFA_INVALID_PARAMETERS (-2) +#define WT_FFA_NO_MEMORY (-3) +#define WT_FFA_BUSY (-4) +#define WT_FFA_INTERRUPTED (-5) +#define WT_FFA_DENIED (-6) +#define WT_FFA_RETRY (-7) +#define WT_FFA_ABORTED (-8) +#define WT_FFA_NO_DATA (-9) +#define WT_FFA_NOT_READY (-10) + +/* Partition ids: bit 15 set = allocated by the SPM (SPMC, SPMD, then SPs); + * bit 15 clear = Normal-world endpoints, id 0 = the primary NS endpoint. + * SPMC and SPMD ids are IMPLEMENTATION DEFINED, only unique (DEN0077A 6.3). */ +#define WT_FFA_ID_NS_PRIMARY 0x0000u +#define WT_FFA_ID_SPMC 0x8000u +#define WT_FFA_ID_SPMD 0x8001u +#define WT_FFA_ID_SP_FIRST 0x8002u +/* The PSA framework endpoint at the NS physical instance: the SPMC answers a + * Normal-world client's register-only FrameworkVersion/ServiceVersion/Connect/ + * Close as this receiver, without a backing partition. */ +#define WT_FFA_ID_PSA 0x80FDu +/* The memory-sharing boot self-test borrower: the SPMC shares a page to this + * endpoint and an S-EL0 partition retrieves it through the SVC gate. */ +#define WT_FFA_ID_MEM_BORROWER 0x80FBu + +/* FFA_PARTITION_INFO_GET w5 flags: bit 0 set returns only the partition count + * in w2, so the caller needs no RX buffer. */ +#define WT_FFA_PARTINFO_FLAG_COUNT (1u << 0) +/* wolfTrust test echo partition and its request payload; both exist only in + * WT_EL3_TEST_DRIVER=1 builds and never in a production image. */ +#define WT_FFA_ID_ECHO 0x80FEu +#define WT_FFA_TEST_PAYLOAD 0x1234ABCDu + +/* FFA_FEATURES: w1 bit 31 set = function id queried, clear = feature id. */ +#define WT_FFA_FEATURES_IS_FID(w1) (((w1) & 0x80000000u) != 0u) + +static inline int wt_ffa_fid_in_range(uint32_t fid) +{ + return ((fid >= WT_FFA_FID32_FIRST) && (fid <= WT_FFA_FID32_LAST)) || + ((fid >= WT_FFA_FID64_FIRST) && (fid <= WT_FFA_FID64_LAST)); +} + +/* Compatibility of caller x.y with callee a.b (13.2.1): same major and a + * caller minor no greater than the callee's. */ +static inline int wt_ffa_version_compatible(uint32_t caller, uint32_t callee) +{ + return (WT_FFA_VERSION_MAJOR_OF(caller) == WT_FFA_VERSION_MAJOR_OF(callee)) && + (WT_FFA_VERSION_MINOR_OF(caller) <= WT_FFA_VERSION_MINOR_OF(callee)); +} + +/* Version a is less than version b (13.2.1). */ +static inline int wt_ffa_version_less(uint32_t a, uint32_t b) +{ + return (WT_FFA_VERSION_MAJOR_OF(a) < WT_FFA_VERSION_MAJOR_OF(b)) || + ((WT_FFA_VERSION_MAJOR_OF(a) == WT_FFA_VERSION_MAJOR_OF(b)) && + (WT_FFA_VERSION_MINOR_OF(a) < WT_FFA_VERSION_MINOR_OF(b))); +} + +/* 13.2.2: a compatible caller, or one asking for a later version than ours, + * is told ours; one below our major (the callee may pick either answer) and a + * malformed (bit 31) request are NOT_SUPPORTED. */ +static inline int32_t wt_ffa_version_reply(uint32_t input, uint32_t ours) +{ + if (((input & 0x80000000u) != 0u) || + (!wt_ffa_version_compatible(input, ours) && + !wt_ffa_version_less(ours, input))) { + return (int32_t)WT_FFA_NOT_SUPPORTED; + } + return (int32_t)ours; +} + +/* The version a caller that asked for input and was told reply goes on to + * use (13.2.1): its own when compatible, else the one it must downgrade to. */ +static inline uint32_t wt_ffa_version_settle(uint32_t input, uint32_t reply) +{ + return wt_ffa_version_compatible(input, reply) ? input : reply; +} + +/* One caller's negotiated version (13.2): it may renegotiate until it makes + * any other FF-A call, after which only the version it settled on is accepted. + * A caller that never negotiated is held to ours. */ +typedef struct wt_ffa_version_state { + uint32_t version; + uint8_t locked; +} wt_ffa_version_state_t; + +static inline void wt_ffa_version_lock(wt_ffa_version_state_t* st, uint32_t ours) +{ + if (st->locked == 0u) { + if (st->version == 0u) { + st->version = ours; + } + st->locked = 1u; + } +} + +/* Once locked, the settled version is the only one the callee supports: a + * later version is told it, any other change is NOT_SUPPORTED (13.2.2). */ +static inline int32_t wt_ffa_version_negotiate(wt_ffa_version_state_t* st, + uint32_t input, uint32_t ours) +{ + int32_t reply = wt_ffa_version_reply(input, ours); + + if (reply == (int32_t)WT_FFA_NOT_SUPPORTED) { + return reply; + } + if (st->locked != 0u) { + if (input == st->version) { + return (int32_t)st->version; + } + return wt_ffa_version_less(st->version, input) + ? (int32_t)st->version : (int32_t)WT_FFA_NOT_SUPPORTED; + } + st->version = wt_ffa_version_settle(input, (uint32_t)reply); + return reply; +} + +/* The Framework version an ABI first appeared in (DEN0077A revision + * history: notifications, indirect messaging, RX_ACQUIRE, and SPM_ID_GET in + * v1.1; CONSOLE_LOG, PARTITION_INFO_GET_REGS, and DIRECT_REQ2/RESP2 in v1.2). */ +static inline uint32_t wt_ffa_fid_min_version(uint32_t fid) +{ + uint32_t version; + + switch (fid) { + case WT_FFA_NOTIFICATION_BITMAP_CREATE: + case WT_FFA_NOTIFICATION_BITMAP_DESTROY: + case WT_FFA_NOTIFICATION_BIND: + case WT_FFA_NOTIFICATION_UNBIND: + case WT_FFA_NOTIFICATION_SET: + case WT_FFA_NOTIFICATION_GET: + case WT_FFA_NOTIFICATION_INFO_GET32: + case WT_FFA_NOTIFICATION_INFO_GET64: + case WT_FFA_RX_ACQUIRE: + case WT_FFA_SPM_ID_GET: + case WT_FFA_MSG_SEND2: + version = WT_FFA_VERSION_MAKE(1u, 1u); + break; + case WT_FFA_CONSOLE_LOG32: + case WT_FFA_CONSOLE_LOG64: + case WT_FFA_PARTITION_INFO_GET_REGS: + case WT_FFA_MSG_SEND_DIRECT_REQ2: + case WT_FFA_MSG_SEND_DIRECT_RESP2: + version = WT_FFA_VERSION_MAKE(1u, 2u); + break; + default: + version = WT_FFA_VERSION_MAKE(1u, 0u); + break; + } + return version; +} + +/* 13.2.2: the negotiated version is the only one the callee supports for the + * caller, so an ABI introduced after it is not implemented for that caller. */ +static inline int wt_ffa_fid_available(uint32_t fid, uint32_t negotiated) +{ + return !wt_ffa_version_less(negotiated, wt_ffa_fid_min_version(fid)); +} + +/* The version a caller's data structures are encoded at (18.5.3): the one it + * negotiated, or ours when it never asked. */ +static inline uint32_t wt_ffa_version_of(const wt_ffa_version_state_t* st, + uint32_t ours) +{ + return (st->version != 0u) ? st->version : ours; +} + +/* FFA_FEATURES feature ids (13.3, Table 13.14) and the properties this + * implementation reports. The schedule receiver interrupt is the SGI of + * 9.4.1, raised for the Normal world when notification work pends; S-EL0 + * partitions have no NPI and are scheduled to their pending notifications. */ +#define WT_FFA_FEATURE_NPI 0x1u +#define WT_FFA_FEATURE_SRI 0x2u +#define WT_FFA_FEATURE_MEI 0x3u +#define WT_FFA_SRI_INTID 8u +/* FFA_MEM_RETRIEVE_REQ: bit 1 in (caller) and out (SPMC) = NS bit is used. */ +#define WT_FFA_FEATURES_RETRIEVE_NS_BIT 0x2u + +/* DEN0140 1.10.4.1.1: a v1.1+ partition must set the NS-bit request in its + * FFA_FEATURES(FFA_MEM_RETRIEVE_REQ) input properties; a v1.0 one may leave it + * clear (Table 1.19). An invalid query is NOT_SUPPORTED, the only error 13.3 + * gives FFA_FEATURES. 0, or NOT_SUPPORTED. */ +static inline int32_t wt_ffa_features_retrieve_check(uint32_t caller_version, + uint32_t input) +{ + if ((caller_version >= WT_FFA_VERSION_MAKE(1u, 1u)) && + ((input & WT_FFA_FEATURES_RETRIEVE_NS_BIT) == 0u)) { + return (int32_t)WT_FFA_NOT_SUPPORTED; + } + return 0; +} +/* DEN0140 Table 1.19: a retrieve response carries the NS bit to a v1.1+ + * partition, and to a v1.0 one only if its FFA_FEATURES query asked for it. */ +static inline int wt_ffa_ns_bit_used(uint32_t caller_version, int requested) +{ + return (caller_version >= WT_FFA_VERSION_MAKE(1u, 1u)) || (requested != 0); +} +/* FFA_RXTX_MAP: w2 bits[1:0] = 0 for a 4K minimum and alignment, bits[31:16] + * = the most pages per buffer (0 = no limit). */ +#define WT_FFA_FEATURES_RXTX_MAX_PAGES(n) (((uint32_t)(n) & 0xFFFFu) << 16) + +/* FFA_CONSOLE_LOG (13.12): the character count is w1 bits 7:0 (bits 31:8 + * SBZ), 1..24 over w2-w7 for SMC32 and 1..128 over x2-x17 for SMC64; 0 when + * the count is out of range. */ +static inline uint32_t wt_ffa_console_count(uint64_t w1, unsigned int is64) +{ + uint32_t count = (uint32_t)w1 & 0xFFu; + uint32_t max = (is64 != 0u) ? 128u : 24u; + + return ((count >= 1u) && (count <= max)) ? count : 0u; +} + +/* FFA_PARTITION_INFO_GET_REGS answers in x0-x17 although it is asked in + * x0-x3, so a relayer decides a reply's width from what it forwarded too. */ +static inline int wt_ffa_reply_is_ext(uint32_t forwarded, uint32_t reply) +{ + return (reply == WT_FFA_MSG_SEND_DIRECT_RESP2) || + ((forwarded == WT_FFA_PARTITION_INFO_GET_REGS) && + (reply == WT_FFA_SUCCESS64)); +} + +/* Registers a relayed message occupies: x0-x7, or x0-x17 for REQ2/RESP2; an + * SMC64 REQ/RESP's x8-x17 are Reserved (SBZ) (Tables 15.7 and 15.11). */ +#define WT_FFA_MSG_REGS 8u +#define WT_FFA_MSG_REGS_EXT 18u + +static inline unsigned int wt_ffa_msg_reg_count(uint64_t x0) +{ + uint32_t fid = (uint32_t)x0; + + return ((fid == WT_FFA_MSG_SEND_DIRECT_REQ2) || + (fid == WT_FFA_MSG_SEND_DIRECT_RESP2)) ? WT_FFA_MSG_REGS_EXT + : WT_FFA_MSG_REGS; +} + +/* x8-x17 of the reply in x to a call: an SMC64 caller gets them as Reserved + * (MBZ) results unless the reply extends into them (11.2); an SMC32 caller's + * are preserved across the call (SMCCC 2.6), so they are left alone. */ +static inline void wt_ffa_reply_clear_ext(uint32_t call, uint64_t* x) +{ + unsigned int i; + + if (((call & 0x40000000u) == 0u) || + (wt_ffa_reply_is_ext(call, (uint32_t)x[0]) != 0)) { + return; + } + for (i = WT_FFA_MSG_REGS; i < WT_FFA_MSG_REGS_EXT; i++) { + x[i] = 0u; + } +} + +/* A 32-bit function id carries w1-w7 only (SMCCC): a relayer hands the + * receiver the low halves and never leaks the sender's upper register bits. */ +static inline void wt_ffa_regs_normalize(uint64_t* x) +{ + unsigned int i; + + if (((uint32_t)x[0] & 0x40000000u) == 0u) { + for (i = 1u; i < 8u; i++) { + x[i] &= 0xFFFFFFFFull; + } + } +} + +#endif /* WOLFTRUST_ARCH_AARCH64_FFA_ABI_H */ diff --git a/include/wolftrust/arch/aarch64/ffa_boot_info.h b/include/wolftrust/arch/aarch64/ffa_boot_info.h new file mode 100644 index 00000000..29f4bd36 --- /dev/null +++ b/include/wolftrust/arch/aarch64/ffa_boot_info.h @@ -0,0 +1,105 @@ +/* ffa_boot_info.h + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfTrust. + * + * wolfTrust 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. + * + * wolfTrust 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, see . + */ + +#ifndef WOLFTRUST_ARCH_AARCH64_FFA_BOOT_INFO_H +#define WOLFTRUST_ARCH_AARCH64_FFA_BOOT_INFO_H + +#include +#include + +/* FF-A boot information protocol (DEN0077A 1.2, 5.4, Tables 5.8 and 5.9): + * the blob the SPMD hands the SPMC in x0. Byte-wise little-endian access so + * the code runs with the MMU off on either side. */ + +#define WT_FFA_BOOT_INFO_SIGNATURE 0x0FFAu +#define WT_FFA_BOOT_INFO_HEADER_SIZE 32u +#define WT_FFA_BOOT_INFO_DESC_SIZE 32u +#define WT_FFA_BOOT_INFO_NAME_SIZE 16u + +#define WT_FFA_BOOT_INFO_TYPE_FDT 0x00u +#define WT_FFA_BOOT_INFO_TYPE_HOB 0x01u +#define WT_FFA_BOOT_INFO_TYPE_IMPDEF 0x80u +/* wolfTrust boot handoff record (WT-PORT-0020) rides an IMPDEF descriptor. */ +#define WT_FFA_BOOT_INFO_TYPE_WT_HANDOFF (WT_FFA_BOOT_INFO_TYPE_IMPDEF | 0x01u) +#define WT_FFA_BOOT_INFO_NAME_WT_HANDOFF "wt.handoff" + +#define WT_FFA_BOOT_INFO_NAME_STRING 0u +#define WT_FFA_BOOT_INFO_NAME_UUID 1u +#define WT_FFA_BOOT_INFO_CONTENTS_ADDRESS 0u +#define WT_FFA_BOOT_INFO_CONTENTS_VALUE 1u + +#define WT_FFA_BOOT_INFO_OK 0 +#define WT_FFA_BOOT_INFO_ERROR_ARGUMENT (-1) +#define WT_FFA_BOOT_INFO_ERROR_SIGNATURE (-2) +#define WT_FFA_BOOT_INFO_ERROR_VERSION (-3) +#define WT_FFA_BOOT_INFO_ERROR_LAYOUT (-4) +#define WT_FFA_BOOT_INFO_ERROR_RESERVED (-5) +#define WT_FFA_BOOT_INFO_ERROR_DESC (-6) +#define WT_FFA_BOOT_INFO_ERROR_SPACE (-7) +#define WT_FFA_BOOT_INFO_ERROR_NOT_FOUND (-8) + +/* One item the producer wants in the blob. Address-form items are copied + * into the blob after the descriptor array so the blob stays self-contained; + * value-form items carry `value` in the Contents field. */ +typedef struct wt_ffa_boot_info_item { + const void* source; + uint64_t value; + const char* name; + uint32_t size; + uint8_t type; + uint8_t name_format; + uint8_t contents_format; +} wt_ffa_boot_info_item_t; + +/* One descriptor as the consumer reads it back. */ +typedef struct wt_ffa_boot_info_desc { + uint64_t contents; + uint32_t size; + uint16_t flags; + uint8_t type; + char name[WT_FFA_BOOT_INFO_NAME_SIZE]; +} wt_ffa_boot_info_desc_t; + +typedef struct wt_ffa_boot_info { + uint64_t blob_pa; + uint32_t version; + uint32_t blob_size; + uint32_t desc_count; + uint32_t desc_offset; +} wt_ffa_boot_info_t; + +size_t wt_ffa_boot_info_array_end(uint32_t desc_count); + +int wt_ffa_boot_info_build(uint8_t* blob, uint64_t blob_pa, size_t blob_limit, + uint32_t version, + const wt_ffa_boot_info_item_t* items, + uint32_t count, uint32_t* blob_size); + +/* Validates the header and every descriptor before reporting success. */ +int wt_ffa_boot_info_parse(const uint8_t* blob, uint64_t blob_pa, + size_t blob_limit, wt_ffa_boot_info_t* out); + +int wt_ffa_boot_info_desc(const uint8_t* blob, const wt_ffa_boot_info_t* info, + uint32_t index, wt_ffa_boot_info_desc_t* out); + +int wt_ffa_boot_info_find(const uint8_t* blob, const wt_ffa_boot_info_t* info, + uint8_t type, wt_ffa_boot_info_desc_t* out); + +#endif /* WOLFTRUST_ARCH_AARCH64_FFA_BOOT_INFO_H */ diff --git a/include/wolftrust/arch/aarch64/ffa_manifest.h b/include/wolftrust/arch/aarch64/ffa_manifest.h new file mode 100644 index 00000000..c8d50f50 --- /dev/null +++ b/include/wolftrust/arch/aarch64/ffa_manifest.h @@ -0,0 +1,63 @@ +/* ffa_manifest.h + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfTrust. + * + * wolfTrust 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. + * + * wolfTrust 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, see . + */ + +#ifndef WOLFTRUST_ARCH_AARCH64_FFA_MANIFEST_H +#define WOLFTRUST_ARCH_AARCH64_FFA_MANIFEST_H + +#include +#include + +/* FF-A partition properties (DEN0077A 1.2 Table 5.1) the manifest tool + * emits from the optional "ffa" section, one entry per Secure Partition + * domain; the wolfTrust domain and partition tables stay as they are. */ + +#define WT_FFA_MANIFEST_MAX_UUIDS 4u + +#define WT_FFA_RUNTIME_EL_SEL0 0u +#define WT_FFA_RUNTIME_EL_SEL1 1u +#define WT_FFA_MESSAGING_NONE 0u +#define WT_FFA_MESSAGING_DIRECT 1u +#define WT_FFA_MESSAGING_INDIRECT 2u +#define WT_FFA_NS_INTERRUPT_SIGNALED 0u +#define WT_FFA_NS_INTERRUPT_QUEUED 1u +/* Table 5.10: no register carries an FF-A boot information blob address. */ +#define WT_FFA_BOOT_INFO_NONE 0xFFFFFFFFu + +/* Bytes in the textual order of the canonical UUID string. */ +typedef struct wt_ffa_uuid { + uint8_t bytes[16]; +} wt_ffa_uuid_t; + +typedef struct wt_ffa_partition_manifest { + const wt_ffa_uuid_t* uuids; + uint32_t domain_id; + uint32_t uuid_count; + uint32_t execution_contexts; + uint32_t runtime_el; + uint32_t messaging; + uint32_t ns_interrupt_action; + uint32_t boot_info_register; + /* The FF-A version the partition expects (WT_FFA_VERSION_MAKE form). */ + uint32_t ffa_version; +} wt_ffa_partition_manifest_t; + +const wt_ffa_partition_manifest_t* wt_generated_ffa_partitions_get(size_t* count); + +#endif /* WOLFTRUST_ARCH_AARCH64_FFA_MANIFEST_H */ diff --git a/include/wolftrust/arch/aarch64/ffa_mem.h b/include/wolftrust/arch/aarch64/ffa_mem.h new file mode 100644 index 00000000..1ef5fbe0 --- /dev/null +++ b/include/wolftrust/arch/aarch64/ffa_mem.h @@ -0,0 +1,621 @@ +/* ffa_mem.h + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfTrust. + * + * wolfTrust 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. + * + * wolfTrust 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, see . + */ + +#ifndef WOLFTRUST_ARCH_AARCH64_FFA_MEM_H +#define WOLFTRUST_ARCH_AARCH64_FFA_MEM_H + +#include +#include + +/* Arm FF-A Memory Management (DEN0140) v1.0-v1.2 transaction descriptors: the + * lend/donate/share memory transaction descriptor and its endpoint access, + * composite, and constituent sub-descriptors, plus the relayer validation a + * 1.2 SPMC runs before it acts on a transaction. Pure functions over + * caller-provided buffers; the FF-A function ids live in ffa_abi.h. */ + +#define WT_FFA_MEM_PAGE_SIZE 0x1000u + +/* Fixed byte sizes of the v1.1 descriptor layout. */ +#define WT_FFA_MEM_TXN_HDR_SIZE 48u /* transaction descriptor header */ +/* The FF-A v1.0 header (DEN0140 Table 4.17) has no access descriptor size or + * offset: its 16-byte access descriptors follow at 32, and bytes [24, 28) are + * reserved. A v1.0 caller's descriptors use it (DEN0077A 18.5.3). */ +#define WT_FFA_MEM_TXN_HDR_SIZE_V10 32u +#define WT_FFA_MEM_ACCESS_SIZE 16u /* endpoint memory access descriptor */ +/* FF-A 1.2 grew it by 16 implementation-defined bytes ahead of the reserved + * tail; a descriptor names its own size, so both layouts are accepted. */ +#define WT_FFA_MEM_ACCESS_SIZE_V12 32u +#define WT_FFA_MEM_ACC_OFF_IMPDEF 8u /* v1.2 only: 16 bytes */ +#define WT_FFA_MEM_IMPDEF_SIZE 16u +#define WT_FFA_MEM_COMPOSITE_HDR_SIZE 16u /* composite memory region header */ +#define WT_FFA_MEM_CONSTITUENT_SIZE 16u /* constituent memory region descriptor */ + +/* Transaction descriptor field offsets (Table 5.19). */ +#define WT_FFA_MEM_TXN_OFF_SENDER 0u /* u16 sender endpoint id */ +#define WT_FFA_MEM_TXN_OFF_ATTRS 2u /* u16 memory region attributes */ +#define WT_FFA_MEM_TXN_OFF_FLAGS 4u /* u32 flags */ +#define WT_FFA_MEM_TXN_OFF_HANDLE 8u /* u64 handle */ +#define WT_FFA_MEM_TXN_OFF_TAG 16u /* u64 tag */ +#define WT_FFA_MEM_TXN_OFF_ACC_SIZE 24u /* u32 size of each access descriptor */ +#define WT_FFA_MEM_TXN_OFF_ACC_COUNT 28u /* u32 access descriptor count */ +#define WT_FFA_MEM_TXN_OFF_ACC_OFFSET 32u /* u32 offset to the access array */ +/* [36, 48) reserved (SBZ). */ +#define WT_FFA_MEM_ACC_OFFSET_ALIGN 16u /* the access array offset's alignment */ + +/* Endpoint memory access descriptor field offsets (Table 5.16). */ +#define WT_FFA_MEM_ACC_OFF_RECEIVER 0u /* u16 receiver endpoint id */ +#define WT_FFA_MEM_ACC_OFF_PERMS 2u /* u8 access permissions */ +#define WT_FFA_MEM_ACC_OFF_FLAGS 3u /* u8 access descriptor flags */ +#define WT_FFA_MEM_ACC_OFF_COMP_OFF 4u /* u32 offset to the composite descriptor */ +/* [8, 16) reserved (SBZ). */ + +/* Access descriptor flags byte (DEN0140 1.10.1): MBZ in a lend/donate/share; + * in a retrieve request bit 0 marks an entry that names another borrower. */ +#define WT_FFA_MEM_ACC_FLAG_NON_RETRIEVAL (1u << 0) + +/* Composite memory region header field offsets (Table 5.13). */ +#define WT_FFA_MEM_COMP_OFF_PAGES 0u /* u32 total page count */ +#define WT_FFA_MEM_COMP_OFF_COUNT 4u /* u32 constituent count */ +/* [8, 16) reserved (SBZ). */ + +/* Constituent memory region descriptor field offsets (Table 5.11). */ +#define WT_FFA_MEM_CONS_OFF_ADDR 0u /* u64 page-aligned base address */ +#define WT_FFA_MEM_CONS_OFF_PAGES 8u /* u32 page count */ +/* [12, 16) reserved (SBZ). */ + +/* Memory access permissions byte (Table 5.14). */ +#define WT_FFA_MEM_PERM_DATA_MASK 0x3u +#define WT_FFA_MEM_PERM_DATA_NOT_SPEC 0x0u +#define WT_FFA_MEM_PERM_DATA_RO 0x1u +#define WT_FFA_MEM_PERM_DATA_RW 0x2u +#define WT_FFA_MEM_PERM_DATA_RSVD 0x3u +#define WT_FFA_MEM_PERM_INSTR_SHIFT 2u +#define WT_FFA_MEM_PERM_INSTR_MASK 0xCu +#define WT_FFA_MEM_PERM_INSTR_NOT_SPEC 0x0u +#define WT_FFA_MEM_PERM_INSTR_NX (0x1u << WT_FFA_MEM_PERM_INSTR_SHIFT) +#define WT_FFA_MEM_PERM_INSTR_X (0x2u << WT_FFA_MEM_PERM_INSTR_SHIFT) +#define WT_FFA_MEM_PERM_RSVD_MASK 0xF0u /* bits[7:4] SBZ */ + +/* Transaction descriptor flags (Table 5.20/5.21). Bits[4:3] carry the + * transaction type only in a retrieve response; they are zero in a send. */ +#define WT_FFA_MEM_FLAG_ZERO (1u << 0) +#define WT_FFA_MEM_FLAG_TIME_SLICE (1u << 1) +/* Retrieve request only: zero the memory after the borrower relinquishes. */ +#define WT_FFA_MEM_FLAG_ZERO_AFTER (1u << 2) +/* Retrieve request only (FF-A 1.2): the caller names just itself although the + * transaction has several borrowers. */ +#define WT_FFA_MEM_FLAG_BYPASS_BORROWERS (1u << 10) +/* Every retrieve request flag Table 1.22 defines; bits[31:11] are SBZ. */ +#define WT_FFA_MEM_FLAG_RETRIEVE_MASK 0x7FFu +#define WT_FFA_MEM_FLAG_TYPE_SHIFT 3u +#define WT_FFA_MEM_FLAG_TYPE_MASK (0x3u << WT_FFA_MEM_FLAG_TYPE_SHIFT) +#define WT_FFA_MEM_FLAG_TYPE_SHARE (0x1u << WT_FFA_MEM_FLAG_TYPE_SHIFT) +#define WT_FFA_MEM_FLAG_TYPE_LEND (0x2u << WT_FFA_MEM_FLAG_TYPE_SHIFT) +#define WT_FFA_MEM_FLAG_TYPE_DONATE (0x3u << WT_FFA_MEM_FLAG_TYPE_SHIFT) +#define WT_FFA_MEM_FLAG_ALIGN_MASK (0x1Fu << 5) /* bits[9:5] alignment hint */ +/* Flag bits a retrieve request may set besides its type, zero-after, and + * bypass flags. Time slicing (DEN0140 4.1.3) is not implemented, so its flag + * is refused in every call. */ +#define WT_FFA_MEM_FLAG_SEND_MASK (WT_FFA_MEM_FLAG_ZERO | \ + WT_FFA_MEM_FLAG_ALIGN_MASK) + +/* Memory region attributes (Table 5.18). */ +#define WT_FFA_MEM_ATTR_SHARE_MASK 0x3u +#define WT_FFA_MEM_ATTR_SHARE_NON 0x0u +#define WT_FFA_MEM_ATTR_SHARE_RSVD 0x1u +#define WT_FFA_MEM_ATTR_SHARE_OUTER 0x2u +#define WT_FFA_MEM_ATTR_SHARE_INNER 0x3u +#define WT_FFA_MEM_ATTR_CACHE_SHIFT 2u +#define WT_FFA_MEM_ATTR_CACHE_MASK (0x3u << WT_FFA_MEM_ATTR_CACHE_SHIFT) +#define WT_FFA_MEM_ATTR_CACHE_NC (0x1u << WT_FFA_MEM_ATTR_CACHE_SHIFT) +#define WT_FFA_MEM_ATTR_CACHE_WB (0x3u << WT_FFA_MEM_ATTR_CACHE_SHIFT) +#define WT_FFA_MEM_ATTR_TYPE_SHIFT 4u +#define WT_FFA_MEM_ATTR_TYPE_MASK (0x3u << WT_FFA_MEM_ATTR_TYPE_SHIFT) +#define WT_FFA_MEM_ATTR_TYPE_DEVICE (0x1u << WT_FFA_MEM_ATTR_TYPE_SHIFT) +#define WT_FFA_MEM_ATTR_TYPE_NORMAL (0x2u << WT_FFA_MEM_ATTR_TYPE_SHIFT) +#define WT_FFA_MEM_ATTR_NS (1u << 6) /* non-secure memory */ +#define WT_FFA_MEM_ATTR_RSVD_MASK 0xFF80u /* bits[15:7] SBZ */ +/* What the relayer's stage 1 tables map every borrower with. */ +#define WT_FFA_MEM_ATTR_RELAYER (WT_FFA_MEM_ATTR_TYPE_NORMAL | \ + WT_FFA_MEM_ATTR_CACHE_WB | \ + WT_FFA_MEM_ATTR_SHARE_INNER) + +/* The memory management operation, derived from the FF-A function id by the + * caller. Ordered to match the transaction-type field encoding (Table 5.21). */ +typedef enum wt_ffa_mem_op { + WT_FFA_MEM_OP_SHARE = 1, + WT_FFA_MEM_OP_LEND = 2, + WT_FFA_MEM_OP_DONATE = 3 +} wt_ffa_mem_op_t; + +/* One address range contributed to a transaction. */ +typedef struct wt_ffa_mem_constituent { + uint64_t address; + uint32_t page_count; +} wt_ffa_mem_constituent_t; + +/* A constituent captured for a live handle, with the borrower's permissions + * and the security state, so the relayer can map it into the borrower and + * unmap it on relinquish/reclaim. B4.3 shares a single region; multi-borrower + * and fragmented sharing are deferred. */ +#define WT_FFA_MEM_MAX_REGIONS 4u + +typedef struct wt_ffa_mem_region { + uint64_t base; + uint32_t page_count; + uint8_t permissions; /* FF-A access permissions byte (Table 5.14) */ + uint8_t ns; /* 1 if the shared memory is Non-secure */ +} wt_ffa_mem_region_t; + +/* Parsed and validated header of a memory transaction descriptor. */ +typedef struct wt_ffa_mem_txn { + uint64_t handle; + uint64_t tag; + uint32_t flags; + uint32_t receiver_count; + uint32_t access_desc_size; + uint32_t access_offset; + uint32_t composite_offset; + uint32_t total_page_count; + uint32_t constituent_count; + uint16_t sender; + uint16_t attributes; +} wt_ffa_mem_txn_t; + +/* Relinquish descriptor (Table 5.24): handle, flags, endpoint count, then the + * endpoint id array. */ +#define WT_FFA_MEM_RELINQ_OFF_HANDLE 0u /* u64 */ +#define WT_FFA_MEM_RELINQ_OFF_FLAGS 8u /* u32 */ +#define WT_FFA_MEM_RELINQ_OFF_COUNT 12u /* u32 endpoint count */ +#define WT_FFA_MEM_RELINQ_OFF_ENDPOINTS 16u /* u16 each */ +#define WT_FFA_MEM_RELINQ_HDR_SIZE 16u +/* The zero-memory bit, the one flag FFA_MEM_RELINQUISH and FFA_MEM_RECLAIM + * act on: bit[1] (time slicing) is refused, bits[31:2] are SBZ. */ +#define WT_FFA_MEM_RELINQ_FLAG_MASK 0x1u +#define WT_FFA_MEM_RELINQ_FLAG_ZERO 0x1u + +/* Inputs to build a single-receiver lend/donate/share descriptor; handle is + * zero in a request and the allocated handle in a retrieve response. */ +typedef struct wt_ffa_mem_build { + const wt_ffa_mem_constituent_t* constituents; + uint64_t tag; + uint64_t handle; + uint32_t flags; + uint32_t constituent_count; + wt_ffa_mem_op_t op; + uint16_t sender; + uint16_t receiver; + uint16_t attributes; + uint8_t permissions; + /* 0 selects the size of the reader's version: 16 bytes through FF-A 1.1, + * 32 from 1.2 (DEN0077A 18.5.3); appended so older initializers hold. */ + uint8_t access_desc_size; + /* 16 implementation-defined bytes for a v1.2 descriptor, or NULL. */ + const uint8_t* impdef; + /* The reader's FF-A version: 1.0 selects the v1.0 layout, 0 or any later + * version Table 1.20. */ + uint32_t version; +} wt_ffa_mem_build_t; + +/* Memory region attributes bits[5:0] (DEN0140 Table 1.18): the type is not + * the reserved b'11, a Normal cacheability and shareability are not marked + * must-not-be-used, and the bits a Device or unspecified type leaves reserved + * are zero. Returns 0 or WT_FFA_INVALID_PARAMETERS (1.10.4.2 item 5). */ +int wt_ffa_mem_attributes_check(uint16_t attributes); + +/* The attributes the borrowers of a lend or share map with, from the valid + * attributes its sender stated (DEN0140 1.10.4.2): an unspecified type leaves + * them to the relayer, which maps only WT_FFA_MEM_ATTR_RELAYER. Returns 0 with + * *out set, WT_FFA_DENIED for more permissive attributes than that (item 1), + * or WT_FFA_INVALID_PARAMETERS for less permissive ones it cannot map, such + * as Device, non-cacheable, or non-shareable memory (item 5). */ +int wt_ffa_mem_send_attributes(uint16_t attributes, uint16_t* out); + +/* Lay out a single-receiver memory transaction descriptor for op into buf + * (header, one endpoint access descriptor, composite header, constituents). + * The handle is left zero (the relayer allocates it). Returns 0 with *out_len + * set to the encoded length, or WT_FFA_INVALID_PARAMETERS on a bad request, + * or WT_FFA_NO_MEMORY if buf cannot hold the descriptor. */ +int wt_ffa_mem_txn_build(uint8_t* buf, size_t len, + const wt_ffa_mem_build_t* in, size_t* out_len); + +/* Relayer validation of a lend/donate/share transaction descriptor for op + * (DEN0140 Ch.2): the header is well formed and inside buf, the flags ask for + * no time slicing (the parsed flags keep only the zero-memory bit, the rest + * being SBZ), the sender equals + * expect_sender, every receiver descriptor references one composite whose + * constituents are page-aligned, non-zero, non-overlapping, in range, and sum + * to the declared page count, and the permissions and attributes are legal. + * On success 0 is returned and *out holds the parsed header. Otherwise a + * WT_FFA_* negative: DENIED for a wrong sender, NOT_SUPPORTED for an access + * descriptor size this SPMC cannot parse, NO_MEMORY for more constituents than + * WT_FFA_MEM_MAX_REGIONS, INVALID_PARAMETERS for any malformed field. */ +int wt_ffa_mem_txn_validate(const uint8_t* buf, size_t len, wt_ffa_mem_op_t op, + uint16_t expect_sender, wt_ffa_mem_txn_t* out); + +/* wt_ffa_mem_txn_validate for a descriptor laid out for FF-A version: a 1.0 + * caller's in the v1.0 layout, any later one's in Table 1.20. */ +int wt_ffa_mem_txn_validate_at(const uint8_t* buf, size_t len, + wt_ffa_mem_op_t op, uint16_t expect_sender, + uint32_t version, wt_ffa_mem_txn_t* out); + +/* wt_ffa_mem_txn_validate for a lend/donate/share the relayer is asked to + * start: its Handle field is zero as well (DEN0140 1.11.1), since this SPMC + * allocates every handle and takes none from a Hypervisor. A descriptor sent + * in fragments carries its reserved handle in registers, not here. */ +int wt_ffa_mem_send_validate(const uint8_t* buf, size_t len, wt_ffa_mem_op_t op, + uint16_t expect_sender, wt_ffa_mem_txn_t* out); +int wt_ffa_mem_send_validate_at(const uint8_t* buf, size_t len, + wt_ffa_mem_op_t op, uint16_t expect_sender, + uint32_t version, wt_ffa_mem_txn_t* out); + +/* Read receiver index's endpoint id and permissions from a descriptor that + * wt_ffa_mem_txn_validate has accepted. */ +int wt_ffa_mem_receiver(const uint8_t* buf, size_t len, + const wt_ffa_mem_txn_t* txn, uint32_t index, + uint16_t* out_id, uint8_t* out_perms); + +/* Read constituent index from a descriptor that wt_ffa_mem_txn_validate has + * accepted. */ +int wt_ffa_mem_constituent(const uint8_t* buf, size_t len, + const wt_ffa_mem_txn_t* txn, uint32_t index, + wt_ffa_mem_constituent_t* out); + +/* Collect the constituents of receiver_index from an accepted descriptor into + * a mapping list: each region's base, page count, the receiver's permissions, + * and the security state from the transaction attributes. Returns 0 with + * *out_n set, WT_FFA_INVALID_PARAMETERS for a bad argument, or WT_FFA_NO_MEMORY + * if the descriptor has more constituents than max. */ +int wt_ffa_mem_regions_from_txn(const uint8_t* buf, size_t len, + const wt_ffa_mem_txn_t* txn, + uint32_t receiver_index, + wt_ffa_mem_region_t* out, uint32_t max, + uint32_t* out_n); + +/* Lay out a single-receiver memory retrieve request for handle: a transaction + * descriptor header plus one endpoint access descriptor and no composite. + * Returns 0 with *out_len set, or WT_FFA_NO_MEMORY. The plain form writes the + * 16-byte FF-A 1.1 access descriptor, _at the header and access descriptor of + * version (the 32-byte v1.0 header for a v1.0 reader). */ +int wt_ffa_mem_retrieve_req_build(uint8_t* buf, size_t len, uint64_t handle, + uint16_t sender, uint16_t receiver, + uint8_t permissions, size_t* out_len); +int wt_ffa_mem_retrieve_req_build_at(uint8_t* buf, size_t len, uint64_t handle, + uint16_t sender, uint16_t receiver, + uint8_t permissions, uint32_t version, + size_t* out_len); + +/* Parse a memory retrieve request: the handle, the sender (owner), and the + * single receiver. Returns 0, WT_FFA_NOT_SUPPORTED for an access descriptor + * size this SPMC cannot parse or more than one receiver, or + * WT_FFA_INVALID_PARAMETERS for a malformed descriptor. */ +int wt_ffa_mem_retrieve_req_parse(const uint8_t* buf, size_t len, + uint64_t* out_handle, uint16_t* out_sender, + uint16_t* out_receiver); + +/* Lay out a relinquish descriptor naming one endpoint. Returns 0 with *out_len + * set, WT_FFA_INVALID_PARAMETERS for a reserved flag, or WT_FFA_NO_MEMORY. */ +int wt_ffa_mem_relinquish_build(uint8_t* buf, size_t len, uint64_t handle, + uint32_t flags, uint16_t endpoint, + size_t* out_len); + +/* wt_ffa_mem_relinquish_parse that also returns the descriptor's flags. */ +int wt_ffa_mem_relinquish_parse_ex(const uint8_t* buf, size_t len, + uint64_t* out_handle, uint16_t* out_endpoint, + uint32_t* out_flags); + +/* Everything a retrieve request states, for the relayer to hold against the + * transaction it names (11.4.2), including the ranges one receiver may name + * in a composite of its own. */ +typedef struct wt_ffa_mem_retrieve_req { + uint64_t handle; + uint64_t tag; + uint32_t flags; + uint32_t receiver_count; + uint32_t access_desc_size; + uint16_t sender; + uint16_t attributes; + uint16_t receivers[3]; + uint8_t permissions[3]; + uint8_t impdef[3][16]; + uint8_t access_flags[3]; + /* The address ranges entry range_index names for its own mapping + * (DEN0140 1.11.3.2); range_count is 0 when every entry leaves them to + * the relayer. */ + uint32_t range_count; + uint32_t range_index; + wt_ffa_mem_constituent_t ranges[WT_FFA_MEM_MAX_REGIONS]; +} wt_ffa_mem_retrieve_req_t; + +int wt_ffa_mem_retrieve_req_parse_ex(const uint8_t* buf, size_t len, + wt_ffa_mem_retrieve_req_t* out); +/* wt_ffa_mem_retrieve_req_parse_ex for a request laid out for FF-A version. */ +int wt_ffa_mem_retrieve_req_parse_at(const uint8_t* buf, size_t len, + uint32_t version, + wt_ffa_mem_retrieve_req_t* out); + +/* FFA_MEM_RECLAIM flags (Table 2.31): 0, or WT_FFA_INVALID_PARAMETERS for the + * time-slicing bit; bits[31:2] are SBZ and ignored. */ +int wt_ffa_mem_reclaim_flags_check(uint32_t flags); + +/* Parse a relinquish descriptor with exactly one endpoint. Returns 0, + * WT_FFA_NOT_SUPPORTED for more than one endpoint, or + * WT_FFA_INVALID_PARAMETERS for a malformed descriptor. */ +int wt_ffa_mem_relinquish_parse(const uint8_t* buf, size_t len, + uint64_t* out_handle, uint16_t* out_endpoint); + +/* FFA_RXTX_MAP buffer geometry (7.2.1): each of TX and RX is pages 4 KB pages, + * page-aligned, distinct, and non-overlapping. Returns 0 or + * WT_FFA_INVALID_PARAMETERS. */ +#define WT_FFA_RXTX_MIN_PAGES 1u +/* w3[5:0] carries the count (Table 13.25), so 63 is the most it can name. */ +#define WT_FFA_RXTX_MAX_PAGES 63u +int wt_ffa_rxtx_validate(uint64_t tx, uint64_t rx, uint32_t pages); + +/* One endpoint's RX/TX pair and who owns its RX buffer (7.2.2): the producer + * acquires RX before it writes, the endpoint hands it back with RX_RELEASE. + * rx_full is one of the WT_FFA_RX_* states below. */ +#define WT_FFA_RX_EMPTY 0u +#define WT_FFA_RX_OWNED 1u +/* Full with a partition message, still the producer's until the endpoint + * retrieves its RX-full notification (7.2.2.4.2 rule 2.1.1). */ +#define WT_FFA_RX_POSTED 2u +typedef struct wt_ffa_mailbox { + uint64_t tx; + uint64_t rx; + uint32_t pages; + uint8_t mapped; + uint8_t rx_full; +} wt_ffa_mailbox_t; + +/* The page count in an FFA_RXTX_MAP w3; bits[31:6] are SBZ, which the callee + * ignores (Table 13.25, 11.2). */ +#define WT_FFA_RXTX_PAGE_COUNT(w3) ((uint32_t)(w3) & 0x3Fu) + +/* w3 is the raw FFA_RXTX_MAP page-count word. DENIED when a pair is already + * mapped, INVALID_PARAMETERS for bad geometry. */ +int wt_ffa_mailbox_map(wt_ffa_mailbox_t* mb, uint64_t tx, uint64_t rx, + uint32_t w3); +/* INVALID_PARAMETERS when no pair is mapped. */ +int wt_ffa_mailbox_unmap(wt_ffa_mailbox_t* mb); +/* Non-zero when [base, base + size) holds a page of mb's mapped pair. */ +int wt_ffa_mailbox_overlaps(const wt_ffa_mailbox_t* mb, uint64_t base, + uint64_t size); +/* Where a memory management call's descriptor of len bytes sits: the caller's + * TX buffer (DEN0140 2.1.1.2 items 1-2), as no dynamically allocated buffer is + * supported (4.1.1.3), so addr and pages (w3/x3, w4) are zero. Returns 0 with + * *out_tx set, or INVALID_PARAMETERS for a buffer address or size, no mapped + * pair, or a descriptor longer than the TX buffer. */ +int wt_ffa_mem_tx_buffer(const wt_ffa_mailbox_t* mb, uint64_t addr, + uint32_t pages, uint32_t len, uint64_t* out_tx); +/* DENIED when no pair is mapped, BUSY while RX is full. The endpoint owns + * the framework message written next (7.2.2.4.2 rule 2.2). */ +int wt_ffa_mailbox_rx_acquire(wt_ffa_mailbox_t* mb); +/* As rx_acquire, for an FFA_MSG_SEND2 partition message: RX is posted. */ +int wt_ffa_mailbox_rx_post(wt_ffa_mailbox_t* mb); +/* A completed FFA_NOTIFICATION_GET whose framework bitmap carried an RX-full + * notification hands a posted RX buffer to the endpoint. */ +void wt_ffa_mailbox_rx_claim(wt_ffa_mailbox_t* mb, uint64_t framework); +/* DENIED unless the endpoint owns RX (Table 13.22). */ +int wt_ffa_mailbox_rx_release(wt_ffa_mailbox_t* mb); + +/* Memory transaction handle registry (handle lifetime state). A handle names a + * transaction from allocation until reclaim; DEN0140 5.10.2 sets bit[63] of a + * handle allocated by the SPMC to zero. */ +#define WT_FFA_MEM_MAX_HANDLES 8u +#define WT_FFA_MEM_HANDLE_INVALID 0xFFFFFFFFFFFFFFFFull + +typedef enum wt_ffa_mem_state { + WT_FFA_MEM_STATE_FREE = 0, + WT_FFA_MEM_STATE_SHARED, + WT_FFA_MEM_STATE_LENT, + WT_FFA_MEM_STATE_DONATED +} wt_ffa_mem_state_t; + +/* A transaction names up to this many borrowers (10.2: memory may be shared + * with or lent to several endpoints at once). */ +#define WT_FFA_MEM_MAX_BORROWERS 3u + +typedef struct wt_ffa_mem_borrower { + uint16_t id; + uint8_t permissions; /* what the owner granted this borrower */ + uint8_t retrieved; + uint8_t mapping; /* relayer cookie: how the retrieve mapped it */ + /* What the owner attached for this borrower (FF-A 1.2); its retrieve + * request must repeat it. Zero for a 16-byte access descriptor. */ + uint8_t impdef[16]; + uint8_t ever_retrieved; /* set by the first retrieve, kept on relinquish */ +} wt_ffa_mem_borrower_t; + +typedef struct wt_ffa_mem_handle_entry { + uint64_t handle; + uint64_t tag; + wt_ffa_mem_region_t regions[WT_FFA_MEM_MAX_REGIONS]; + wt_ffa_mem_borrower_t borrowers[WT_FFA_MEM_MAX_BORROWERS]; + uint32_t owner_cookie; /* relayer cookie: the owner's own mapping */ + uint16_t owner; + uint16_t borrower; /* the first borrower */ + uint16_t attributes; /* Table 1.18 bits[5:0] every borrower maps with */ + uint8_t state; + uint8_t retrieved; /* borrowers currently holding the region */ + uint8_t region_count; + uint8_t borrower_count; +} wt_ffa_mem_handle_entry_t; + +typedef struct wt_ffa_mem_registry { + wt_ffa_mem_handle_entry_t entries[WT_FFA_MEM_MAX_HANDLES]; + uint64_t next_handle; +} wt_ffa_mem_registry_t; + +void wt_ffa_mem_registry_init(wt_ffa_mem_registry_t* reg); + +/* Take the next handle for a transaction whose descriptor is still arriving + * in fragments (DEN0140 4.1.2): the same handle names the region once + * wt_ffa_mem_share_register_as binds it. */ +uint64_t wt_ffa_mem_handle_reserve(wt_ffa_mem_registry_t* reg); + +/* wt_ffa_mem_share_register with a handle reserved earlier. */ +int wt_ffa_mem_share_register_as(wt_ffa_mem_registry_t* reg, + wt_ffa_mem_op_t op, uint16_t owner, + uint16_t borrower, + const wt_ffa_mem_region_t* regions, + uint32_t n, uint64_t handle); + +/* Reassembly of a transaction descriptor sent in fragments (DEN0140 4.1.2), + * bounded by the largest descriptor the relayer takes in one piece. */ +#define WT_FFA_MEM_FRAG_MAX WT_FFA_MEM_PAGE_SIZE + +typedef struct wt_ffa_mem_frag { + uint8_t buf[WT_FFA_MEM_FRAG_MAX]; + uint64_t handle; + uint32_t total; + uint32_t received; + uint16_t sender; + uint8_t op; + uint8_t active; + uint8_t aborted; +} wt_ffa_mem_frag_t; + +/* Start reassembly with the first fragment of a descriptor of total bytes. + * Returns 0, WT_FFA_INVALID_PARAMETERS for a fragment that is empty or not + * shorter than total, or WT_FFA_NO_MEMORY past WT_FFA_MEM_FRAG_MAX. */ +int wt_ffa_mem_frag_begin(wt_ffa_mem_frag_t* f, uint64_t handle, + uint16_t sender, uint8_t op, const uint8_t* frag, + uint32_t frag_len, uint32_t total); + +/* Append the next fragment; *done is set once the descriptor is whole. + * Returns 0, or WT_FFA_INVALID_PARAMETERS for another handle or sender, an + * empty fragment, or one past the declared total. */ +int wt_ffa_mem_frag_add(wt_ffa_mem_frag_t* f, uint64_t handle, + uint16_t sender, const uint8_t* frag, + uint32_t frag_len, int* done); + +void wt_ffa_mem_frag_reset(wt_ffa_mem_frag_t* f); + +/* The descriptor length the first fragment's own headers describe (a retrieve + * request when retrieve is non-zero, else a lend/share/donate), so a declared + * total that disagrees is refused before any fragment is taken. Returns 1 with + * *size set, or 0 when the fragment is too short to tell or its access array + * is one the full parse refuses (that parse then answers the request). */ +int wt_ffa_mem_frag_expected(const uint8_t* frag, uint32_t frag_len, + int retrieve, uint64_t* size); +/* wt_ffa_mem_frag_expected for a descriptor laid out for FF-A version. */ +int wt_ffa_mem_frag_expected_at(const uint8_t* frag, uint32_t frag_len, + int retrieve, uint32_t version, uint64_t* size); + +/* Allocate a unique handle for a validated transaction, with no captured + * region set. Returns 0 with *out_handle set, or WT_FFA_NO_MEMORY when the + * registry is full. */ +int wt_ffa_mem_handle_alloc(wt_ffa_mem_registry_t* reg, wt_ffa_mem_op_t op, + uint16_t owner, uint16_t borrower, + uint64_t* out_handle); + +/* Allocate a handle and capture the region set to map into the borrower on + * retrieve. Returns 0 with *out_handle set, WT_FFA_INVALID_PARAMETERS for a bad + * argument or a region count over WT_FFA_MEM_MAX_REGIONS, or WT_FFA_NO_MEMORY + * when the registry is full. */ +int wt_ffa_mem_share_register(wt_ffa_mem_registry_t* reg, wt_ffa_mem_op_t op, + uint16_t owner, uint16_t borrower, + const wt_ffa_mem_region_t* regions, uint32_t n, + uint64_t* out_handle); + +/* The implementation-defined bytes of receiver index (zeros for a 16-byte + * access descriptor). */ +int wt_ffa_mem_receiver_impdef(const uint8_t* buf, size_t len, + const wt_ffa_mem_txn_t* txn, uint32_t index, + uint8_t* out16); + +/* Record the tag the owner attached (a retrieve request must repeat it) and + * the relayer's own cookie for the transaction. */ +void wt_ffa_mem_handle_set_meta(wt_ffa_mem_registry_t* reg, uint64_t handle, + uint64_t tag, uint32_t owner_cookie); + +/* Record the attributes (wt_ffa_mem_send_attributes) the borrowers of a live + * handle map with; a retrieve request is held against them. */ +void wt_ffa_mem_handle_set_attributes(wt_ffa_mem_registry_t* reg, + uint64_t handle, uint16_t attributes); + +/* Name a further borrower of a live handle nobody has retrieved yet. Returns 0, + * WT_FFA_INVALID_PARAMETERS for an unknown handle, the owner, or a repeat, or + * WT_FFA_NO_MEMORY past WT_FFA_MEM_MAX_BORROWERS. */ +int wt_ffa_mem_handle_add_borrower(wt_ffa_mem_registry_t* reg, uint64_t handle, + uint16_t borrower, uint8_t permissions); + +/* The borrower record of a live handle, or NULL. */ +wt_ffa_mem_borrower_t* wt_ffa_mem_handle_borrower(wt_ffa_mem_registry_t* reg, + uint64_t handle, + uint16_t borrower); + +/* Non-zero when [base, base + pages) overlaps memory a live handle holds. */ +int wt_ffa_mem_registry_overlaps(const wt_ffa_mem_registry_t* reg, + uint64_t base, uint32_t pages); + +/* Copy the region set captured for a live handle into out (up to max). Returns + * 0 with *out_n set, WT_FFA_INVALID_PARAMETERS for an unknown handle or bad + * argument, or WT_FFA_NO_MEMORY if the handle has more regions than max. */ +int wt_ffa_mem_handle_regions(const wt_ffa_mem_registry_t* reg, uint64_t handle, + wt_ffa_mem_region_t* out, uint32_t max, + uint32_t* out_n); + +/* Look up a live handle. Returns 0 with *out set, or WT_FFA_INVALID_PARAMETERS + * for an unknown handle. */ +int wt_ffa_mem_handle_lookup(const wt_ffa_mem_registry_t* reg, uint64_t handle, + const wt_ffa_mem_handle_entry_t** out); + +/* Free a handle unconditionally (a donate consumes it once retrieved). */ +int wt_ffa_mem_handle_free(wt_ffa_mem_registry_t* reg, uint64_t handle); + +/* A borrower retrieves a handle once. Returns 0, WT_FFA_INVALID_PARAMETERS for + * an unknown handle, or WT_FFA_DENIED for the wrong borrower or a second + * retrieve. */ +int wt_ffa_mem_handle_retrieve(wt_ffa_mem_registry_t* reg, uint64_t handle, + uint16_t borrower); + +/* A borrower relinquishes a retrieved handle. Returns 0, or a WT_FFA_* negative + * for an unknown handle, the wrong borrower, or a handle not retrieved. */ +int wt_ffa_mem_handle_relinquish(wt_ffa_mem_registry_t* reg, uint64_t handle, + uint16_t borrower); + +/* The owner reclaims a relinquished handle, freeing it for reuse refusal. + * Returns 0, WT_FFA_INVALID_PARAMETERS for an unknown handle, or WT_FFA_DENIED + * for the wrong owner or a still-retrieved handle. */ +int wt_ffa_mem_handle_reclaim(wt_ffa_mem_registry_t* reg, uint64_t handle, + uint16_t owner); + +/* The transaction-type flag (Table 1.23 bits[4:3]) of a live handle's state. */ +uint32_t wt_ffa_mem_type_flag(uint8_t state); + +/* Hold receiver's parsed retrieve request against the transaction its handle + * names (DEN0140 2.4.1.2): every named endpoint is a borrower whose + * implementation-defined bytes it repeats, no borrower is named twice and + * each of them is named (1.11.3.3), or with the bypass flag the receiver + * alone, the Non-retrieval Borrower flag is clear in the receiver's own entry + * and set in every other (Table 1.17), the tag, flags, and transaction type + * agree, attributes it states are the transaction's own (1.10.4.2), and every + * other borrower named carries the data access the lender gave it. Returns 0, + * WT_FFA_INVALID_PARAMETERS for a field the request got wrong or attributes + * less permissive than the transaction's (item 5), or WT_FFA_DENIED for Device + * memory (only Normal memory is ever sent), more permissive attributes, or + * another borrower's data access that is not the lender's. */ +int wt_ffa_mem_retrieve_req_check(const wt_ffa_mem_handle_entry_t* e, + const wt_ffa_mem_retrieve_req_t* rq, + uint16_t receiver); + +#endif /* WOLFTRUST_ARCH_AARCH64_FFA_MEM_H */ diff --git a/include/wolftrust/arch/aarch64/ffa_msg.h b/include/wolftrust/arch/aarch64/ffa_msg.h new file mode 100644 index 00000000..cdcb3ac0 --- /dev/null +++ b/include/wolftrust/arch/aarch64/ffa_msg.h @@ -0,0 +1,174 @@ +/* ffa_msg.h + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfTrust. + * + * wolfTrust 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. + * + * wolfTrust 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, see . + */ + +#ifndef WOLFTRUST_ARCH_AARCH64_FFA_MSG_H +#define WOLFTRUST_ARCH_AARCH64_FFA_MSG_H + +#include + +/* FF-A direct messaging (DEN0077A 15.2/15.3) and the relayer checks of 7.4.2. + * A partition-message direct request/response carries its endpoint ids in w1 + * (sender in bits 31:16, receiver in 15:0), w2 SBZ (the framework bit in 31 is + * clear for a partition message), and up to five implementation-defined + * payload words in w3-w7 (SMC32) or x3-x7 (SMC64). */ + +#define WT_FFA_DIRECT_FRAMEWORK_BIT 0x80000000u +#define WT_FFA_DIRECT_PAYLOAD_WORDS 5u +/* Tables 15.7/15.11: a partition message clears the framework bit and the MBZ + * bits 7:0 of w2; bits 30:8 are SBZ, ignored by the relayer. */ +#define WT_FFA_DIRECT_FLAGS_MBZ (WT_FFA_DIRECT_FRAMEWORK_BIT | 0xFFu) + +/* The instance a message is relayed at decides which world each endpoint is: + * the SPMD relays Normal world to Secure, the SPMC relays partition to + * partition. */ +typedef enum wt_ffa_instance { + WT_FFA_INSTANCE_NS_PHYSICAL = 0, + WT_FFA_INSTANCE_SECURE_VIRTUAL +} wt_ffa_instance_t; + +static inline uint16_t wt_ffa_direct_sender(uint64_t w1) +{ + return (uint16_t)(w1 >> 16); +} + +static inline uint16_t wt_ffa_direct_receiver(uint64_t w1) +{ + return (uint16_t)(w1 & 0xFFFFu); +} + +static inline int wt_ffa_id_is_secure(uint16_t id) +{ + return (id & 0x8000u) != 0u; +} + +/* Build a 32-bit direct request or response into x[0..7]: x[0] = fid, w1 packs + * the ids, w2 = 0, x[3..7] carry the five payload words (NULL clears them). */ +void wt_ffa_direct_build(uint64_t* x, uint32_t fid, uint16_t sender, + uint16_t receiver, const uint32_t* payload); + +/* Relayer validation (7.4.2): 0 if the message may be forwarded, else a + * INVALID_PARAMETERS (15.2.1: a malformed frame or an endpoint id the instance + * does not relay for). */ +int wt_ffa_direct_req_check(const uint64_t* x, wt_ffa_instance_t inst); +int wt_ffa_direct_resp_check(const uint64_t* x, wt_ffa_instance_t inst); + +/* Clear the SBZ fields of a checked partition message before it is handed to + * its receiver: w2 of a REQ/RESP, x2/x3 of a RESP2 (a REQ2's are its UUID). */ +void wt_ffa_direct_clear_sbz(uint64_t* x); + +/* FFA_RUN target (14.3, Table 14.13): w1 bits 31:16 name the endpoint and bits + * 15:0 its vCPU. Every endpoint here is UP with the single execution context 0 + * (4.7), so another vCPU id is INVALID_PARAMETERS (Table 14.14). */ +int wt_ffa_run_target(uint32_t w1, uint16_t* id); + +/* FFA_RUN of an endpoint busy with a direct request from requester: only that + * requester may resume it, once it yielded (8.2) or a Non-secure interrupt + * preempted it (9.3.1.1). Returns 0, else DENIED. */ +int wt_ffa_run_busy_check(uint16_t requester, uint16_t caller, + unsigned int yielded, unsigned int preempted); + +/* Write msg (x0-x7, or x0-x17 for REQ2/RESP2) into the saved registers x of + * the call it answers, x[0] naming that call; an SMC64 caller's x8-x17 the + * message does not fill come back zero (11.2). */ +void wt_ffa_msg_deliver(uint64_t* x, const uint64_t* msg); + +/* 13.2.3.2: the SPMD hands a Normal-world FFA_VERSION to an S-EL1 SPMC as a + * framework direct request from the SPMD (Table 13.7, w3 = the version asked) + * and the SPMC answers with a framework direct response (Table 13.8, w3 = the + * FFA_VERSION result the Normal world is given). */ +#define WT_FFA_FWK_VERSION_REQ (WT_FFA_DIRECT_FRAMEWORK_BIT | 0x08u) +#define WT_FFA_FWK_VERSION_RESP (WT_FFA_DIRECT_FRAMEWORK_BIT | 0x09u) + +void wt_ffa_fwk_version_req(uint64_t* x, uint32_t version); +int wt_ffa_fwk_version_is_req(const uint64_t* x); +void wt_ffa_fwk_version_resp(uint64_t* x, int32_t result); +/* The result a Table 13.8 response carries, or NOT_SUPPORTED for any other + * message. */ +int32_t wt_ffa_fwk_version_result(const uint64_t* x); + +/* 18.2.4: the SPMD tells the SPMC of a PSCI power operation with a framework + * direct request (Table 18.6: w3 the PSCI function id, x4-x6 its x1-x3) and + * the SPMC answers with a framework direct response (Table 18.8: w3 SUCCESS or + * DENIED). */ +#define WT_FFA_FWK_PM_PSCI_REQ (WT_FFA_DIRECT_FRAMEWORK_BIT | 0x00u) +#define WT_FFA_FWK_PM_RESP (WT_FFA_DIRECT_FRAMEWORK_BIT | 0x02u) + +/* An SMC64 PSCI id travels in FFA_MSG_SEND_DIRECT_REQ64, an SMC32 one in + * REQ32 with its parameters cut to 32 bits; x7-x17 are zero. */ +void wt_ffa_fwk_pm_req(uint64_t* x, uint32_t psci_fid, uint64_t a1, + uint64_t a2, uint64_t a3); +int wt_ffa_fwk_pm_is_req(const uint64_t* x); +void wt_ffa_fwk_pm_resp(uint64_t* x, int32_t status); +/* 1 only for a Table 18.8 response carrying SUCCESS with w4-w7 zero; any + * other message, or a response carrying anything else, is 0. */ +int wt_ffa_fwk_pm_granted(const uint64_t* x); + +/* FFA_MSG_SEND2 (15.1): the partition message header at the start of the + * sender's TX buffer. Table 7.2 lays out flags, a reserved word, the payload + * offset, sender and receiver ids (sender bits 31:16), and the payload size; + * that 20-byte form is the header of an endpoint that negotiated v1.0 or v1.1, + * and a v1.2 endpoint's 40-byte header adds a reserved word and the + * receiver's UUID. The flags and reserved words are SBZ: ignored here and + * cleared in the receiver's copy. w1 bits 15:0 are SBZ. At the NS physical + * instance w1 bits 31:16 name the sender and the delay-SRI hint in w2 bit 1 + * is MBZ (Secure virtual only, 16.5.1); at the secure virtual instance (the + * SVC conduit) w1 bits 31:16 are MBZ and w2 is ignored (Table 15.3). */ +#define WT_FFA_MSG2_HEADER_SIZE 40u +#define WT_FFA_MSG2_HEADER_SIZE_V1_1 20u +#define WT_FFA_MSG2_FLAG_DELAY_SRI (1u << 1) + +/* The header as parsed: each field is read from the TX buffer once, so what + * is validated is what is delivered even if the sender rewrites its TX. A + * header without a UUID field parses as the Nil UUID. */ +typedef struct wt_ffa_msg2 { + uint32_t offset; + uint32_t size; + uint16_t sender; + uint16_t receiver; + uint8_t uuid[16]; +} wt_ffa_msg2_t; + +/* The header size of an endpoint at a negotiated FF-A version. */ +uint32_t wt_ffa_msg2_header_size(uint32_t version); + +/* Validate the header, in the layout of the version the caller negotiated, + * against the caller and the TX bounds; the receiver's UUID is the caller's + * to compare once the receiver is known. */ +int wt_ffa_msg2_parse(const uint8_t* tx, uint32_t tx_size, uint16_t caller, + uint32_t version, wt_ffa_instance_t inst, uint32_t w1, + uint32_t w2, wt_ffa_msg2_t* out); + +/* A header either names the receiver's UUID or leaves it Nil. */ +int wt_ffa_msg2_uuid_ok(const uint8_t* header_uuid, const uint8_t* ep_uuid); + +/* Where the payload lands in an RX whose owner negotiated version: the + * sender's offset, moved past the receiver's header when it is shorter. The + * relayer checks the result plus the size against the RX before the copy. */ +uint32_t wt_ffa_msg2_rx_offset(const wt_ffa_msg2_t* msg, uint32_t version); + +/* Produce a parsed message in the receiver's RX of rx_size bytes, in the + * header layout of the version the receiver negotiated: the header is + * written from msg (the sender being the caller the SPMC identified), the + * payload is copied from tx to wt_ffa_msg2_rx_offset, every other byte is + * cleared (7.2.2.3.2). */ +void wt_ffa_msg2_copy(uint8_t* rx, uint32_t rx_size, uint32_t version, + const uint8_t* tx, const wt_ffa_msg2_t* msg); + +#endif /* WOLFTRUST_ARCH_AARCH64_FFA_MSG_H */ diff --git a/include/wolftrust/arch/aarch64/ffa_notif.h b/include/wolftrust/arch/aarch64/ffa_notif.h new file mode 100644 index 00000000..04b98958 --- /dev/null +++ b/include/wolftrust/arch/aarch64/ffa_notif.h @@ -0,0 +1,123 @@ +/* ffa_notif.h + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfTrust. + * + * wolfTrust 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. + * + * wolfTrust 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, see . + */ + +#ifndef WOLFTRUST_ARCH_AARCH64_FFA_NOTIF_H +#define WOLFTRUST_ARCH_AARCH64_FFA_NOTIF_H + +#include + +/* FF-A v1.2 notifications (DEN0077A Ch.10, ABIs 17.5-17.12): one state + * machine shared by the SVC gate (partitions) and the forwarded Normal-world + * instance, host-testable with no hardware. Notification ids are bits of a + * 64-bit space per receiver; a receiver keeps one pending bitmap per signal + * source class (partitions, VMs, framework). */ + +#define WT_FFA_NOTIF_MAX_EP 12u +#define WT_FFA_NOTIF_COUNT 64u +/* Per-vCPU notification state exists for one execution context only. */ +#define WT_FFA_NOTIF_MAX_VCPUS 1u + +/* FFA_NOTIFICATION_BIND / FFA_NOTIFICATION_SET w2 flags. */ +#define WT_FFA_NOTIF_FLAG_PER_VCPU (1u << 0) +#define WT_FFA_NOTIF_FLAG_DELAY_SRI (1u << 1) +#define WT_FFA_NOTIF_SET_VCPU(w2) (((w2) >> 16) & 0xFFFFu) +#define WT_FFA_NOTIF_SET_MBZ 0x0000FFFCu + +/* FFA_NOTIFICATION_GET w2 flags: which pending bitmaps to return and clear. */ +#define WT_FFA_NOTIF_GET_FLAG_SP (1u << 0) +#define WT_FFA_NOTIF_GET_FLAG_VM (1u << 1) +#define WT_FFA_NOTIF_GET_FLAG_SPM (1u << 2) +#define WT_FFA_NOTIF_GET_FLAG_HYP (1u << 3) +#define WT_FFA_NOTIF_GET_FLAG_ALL 0xFu + +/* The 64-bit framework bitmap: the SPM's half [31:0] (w6 of a GET) and the + * Hypervisor's [63:32] (w7), each drained only by its own GET flag (16.6). + * RX-full is bit 0 for a Secure sender's message and bit 32 for a Normal-world + * sender's (10.8.1). */ +#define WT_FFA_NOTIF_FW_SPM_MASK 0x00000000FFFFFFFFull +#define WT_FFA_NOTIF_FW_HYP_MASK 0xFFFFFFFF00000000ull +#define WT_FFA_NOTIF_FW_SPM_RX_FULL (1ull << 0) +#define WT_FFA_NOTIF_FW_NS_RX_FULL (1ull << 32) + +/* w1 of BIND/UNBIND/SET carries sender [31:16] and receiver [15:0]; w1 of + * GET carries the receiver's vCPU id [31:16] and the receiver [15:0]. */ +#define WT_FFA_NOTIF_W1_HIGH(w1) ((uint16_t)(((w1) >> 16) & 0xFFFFu)) +#define WT_FFA_NOTIF_W1_LOW(w1) ((uint16_t)((w1) & 0xFFFFu)) + +/* FFA_NOTIFICATION_INFO_GET w2: bit 0 = more lists pending than returned, + * bits [11:7] = count of lists, two size bits per list from bit 12 up. Each + * list is a 16-bit endpoint id followed by that many 16-bit vCPU ids, packed + * from x3 (or w3) upward. */ +#define WT_FFA_NOTIF_INFO_MORE (1u << 0) +#define WT_FFA_NOTIF_INFO_COUNT(n) (((uint32_t)(n) & 0x1Fu) << 7) +#define WT_FFA_NOTIF_INFO_MAX_REGS 5u + +typedef struct wt_ffa_notif_get_result { + uint64_t from_sp; + uint64_t from_vm; + uint64_t framework; +} wt_ffa_notif_get_result_t; + +typedef struct wt_ffa_notif_info_result { + uint64_t regs[WT_FFA_NOTIF_INFO_MAX_REGS]; + uint64_t w2; +} wt_ffa_notif_info_result_t; + +void wt_ffa_notif_reset(void); +int wt_ffa_notif_register(uint16_t id, int secure); +/* An endpoint out of service: its bindings and pending notifications go, as + * does every binding naming it the sender, with whatever it pended through + * that binding; its id stays recognized, and a BIND/UNBIND naming it the + * sender or a SET to it answers code (ABORTED for one that aborted, Tables + * 16.12, 16.16, 16.20). */ +void wt_ffa_notif_retire(uint16_t id, int32_t code); + +int32_t wt_ffa_notif_bitmap_create(uint16_t caller, uint32_t vm_id, + uint32_t vcpu_count); +int32_t wt_ffa_notif_bitmap_destroy(uint16_t caller, uint32_t vm_id); +int32_t wt_ffa_notif_bind(uint16_t caller, uint32_t w1, uint32_t flags, + uint64_t bitmap); +int32_t wt_ffa_notif_unbind(uint16_t caller, uint32_t w1, uint32_t w2, + uint64_t bitmap); +int32_t wt_ffa_notif_set(uint16_t caller, uint32_t w1, uint32_t flags, + uint64_t bitmap); +int32_t wt_ffa_notif_get(uint16_t caller, uint32_t w1, uint32_t flags, + wt_ffa_notif_get_result_t* out); +int32_t wt_ffa_notif_info_get(uint16_t caller, int is64, + wt_ffa_notif_info_result_t* out); + +/* The framework message-pending notification a message send pends for its + * receiver, on the half of the bitmap the sender's world owns; consumed + * through GET like any other class. */ +int32_t wt_ffa_notif_frame_rx_full(uint16_t receiver, int sender_secure); +/* 0 when receiver has a framework bitmap to pend RX-full in; DENIED for a VM + * that has none (never created, or destroyed: 10.3 rule 7) or an endpoint out + * of service, so a message send is refused before it takes the RX buffer. */ +int32_t wt_ffa_notif_frame_ready(uint16_t receiver); + +/* Schedule-receiver interrupt latch: set when a signal leaves work for the + * Normal-world scheduler, cleared when it asks. */ +int wt_ffa_notif_sri_take(void); +int wt_ffa_notif_sri_pending(void); +/* Set instead when a partition signals without the delay hint: the SRI is + * asserted as its call completes rather than at the next Normal-world entry. */ +int wt_ffa_notif_sri_take_now(void); + +#endif /* WOLFTRUST_ARCH_AARCH64_FFA_NOTIF_H */ diff --git a/include/wolftrust/arch/aarch64/ffa_partinfo.h b/include/wolftrust/arch/aarch64/ffa_partinfo.h new file mode 100644 index 00000000..e84d1fd0 --- /dev/null +++ b/include/wolftrust/arch/aarch64/ffa_partinfo.h @@ -0,0 +1,145 @@ +/* ffa_partinfo.h + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfTrust. + * + * wolfTrust 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. + * + * wolfTrust 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, see . + */ + +#ifndef WOLFTRUST_ARCH_AARCH64_FFA_PARTINFO_H +#define WOLFTRUST_ARCH_AARCH64_FFA_PARTINFO_H + +#include +#include + +#include "wolftrust/arch/aarch64/ffa_manifest.h" + +/* FFA_PARTITION_INFO_GET partition information descriptors (DEN0077A 1.2 6.1, + * Table 6.1) written into a caller's RX buffer (7.2). A descriptor is the + * partition id, its execution-context count, and a properties word; from FF-A + * 1.1 it also carries the partition UUID. */ + +#define WT_FFA_PARTINFO_DESC_V10 8u /* id + context count + props */ +#define WT_FFA_PARTINFO_DESC_V11 24u /* the above plus the 16-byte UUID */ + +/* WT_FFA_PARTINFO_FLAG_COUNT (w5 bit 0, count-only) lives in ffa_abi.h. */ + +/* Partition properties (Table 6.2): FFA_MSG_SEND_DIRECT_REQ receipt/sending, + * indirect messaging, notification receipt, AArch64 execution state, and + * FFA_MSG_SEND_DIRECT_REQ2 receipt/sending. */ +#define WT_FFA_PARTINFO_PROP_DIRECT_RECV 0x1u +#define WT_FFA_PARTINFO_PROP_DIRECT_SEND 0x2u +#define WT_FFA_PARTINFO_PROP_INDIRECT 0x4u +#define WT_FFA_PARTINFO_PROP_NOTIF 0x8u +#define WT_FFA_PARTINFO_PROP_AARCH64 0x100u +#define WT_FFA_PARTINFO_PROP_REQ2_RECV 0x200u +#define WT_FFA_PARTINFO_PROP_REQ2_SEND 0x400u +/* The v1.0 descriptor defines only bits 2:0 of the properties (Table 18.22). */ +#define WT_FFA_PARTINFO_PROP_V10_MASK 0x7u + +typedef struct wt_ffa_partinfo_entry { + uint16_t id; + uint16_t exec_contexts; + uint32_t properties; + uint8_t uuid[16]; +} wt_ffa_partinfo_entry_t; + +/* Descriptor size for a caller at the given negotiated FF-A version: 8 bytes + * before 1.1, 24 from 1.1 (the UUID was added to the descriptor). */ +uint32_t wt_ffa_partinfo_desc_size(uint32_t caller_version); + +/* Discovery records for one manifest partition under the live endpoint id of + * the partition running in its domain; none when id is 0 (nothing runs there). + * One record per exported UUID, all with that id (6.2.2). Its services are + * reached through the PSA framework endpoint and the FF-M gate, never by FF-A + * messaging to its own id, so a record advertises only the AArch64 execution + * state it runs in at S-EL0 (Table 6.2 bit 8). Returns 0 with + * *out_n records written to out, WT_FFA_NO_MEMORY when out cannot hold them, + * or WT_FFA_INVALID_PARAMETERS for a partition exporting no UUID or too many. */ +int wt_ffa_partinfo_from_manifest(const wt_ffa_partition_manifest_t* part, + uint16_t id, wt_ffa_partinfo_entry_t* out, + size_t cap, size_t* out_n); + +/* The properties listed for id, OR-ed over its records (one per UUID): 0 with + * *props set, or INVALID_PARAMETERS when no record has that id. */ +int wt_ffa_partinfo_props_of(const wt_ffa_partinfo_entry_t* parts, size_t n, + uint16_t id, uint32_t* props); + +/* Whether an endpoint with these properties takes (receive != 0) or may send + * a direct request of kind fid: bits 0/1 for FFA_MSG_SEND_DIRECT_REQ32/64, + * bits 9/10 for FFA_MSG_SEND_DIRECT_REQ2 (Table 6.2). 0, or DENIED (Tables + * 15.8 and 15.16). */ +int wt_ffa_direct_req_allowed(uint32_t props, uint32_t fid, int receive); + +/* FFA_MSG_SEND2 from sender (Table 5.1: indirect messaging support covers + * sending as well as receiving): the Normal world always, a partition only if + * parts lists it with the indirect-messaging property. 0, or DENIED (Table + * 15.4). */ +int wt_ffa_msg2_sender_allowed(const wt_ffa_partinfo_entry_t* parts, size_t n, + uint16_t sender); + +/* A partition-to-partition direct request of kind fid (7.4.2 rule 2): the + * sender must be listed in parts and advertise sending it (DENIED otherwise), + * the receiver listed (INVALID_PARAMETERS otherwise) and advertise taking it + * (DENIED otherwise). 0 when both hold. */ +int wt_ffa_direct_req_authorize(const wt_ffa_partinfo_entry_t* parts, size_t n, + uint16_t sender, uint16_t receiver, + uint32_t fid); + +/* Write the descriptors matching uuid16 into rx (7.2/6.1). A Nil UUID (all + * zero) matches every partition; otherwise only those whose UUID equals it, + * and the descriptors' UUID field is then zero (Table 6.1). flags bit 0 + * returns only the count (no descriptors written); the SBZ bits 31:1 are + * ignored. On success 0 is returned with *out_count set and *out_desc_size + * set to the per-descriptor size (0 for a count-only request). The producer + * zeroes every descriptor byte it does not fill (7.2.2); a v1.0 caller's + * descriptor carries only the property bits Table 18.22 defines. + * WT_FFA_NO_MEMORY if rx cannot hold the matching descriptors. */ +int wt_ffa_partinfo_write(uint8_t* rx, size_t rx_size, uint32_t caller_version, + const wt_ffa_partinfo_entry_t* parts, size_t n, + const uint8_t* uuid16, uint32_t flags, + uint32_t* out_count, uint32_t* out_desc_size); + +/* FFA_PARTITION_INFO_GET (13.8) over parts: x = the call's registers (UUID in + * w1-w4, flags in w5), caller_version = the FF-A version the caller negotiated + * (the descriptor layout follows it, 18.5.3). A count needs no buffer; + * descriptors go to the RX buffer of the caller's mailbox mb and take its + * ownership. BUSY when that RX buffer is not mapped or not free (Table 13.36); + * INVALID_PARAMETERS for a UUID nothing matches, found before the RX buffer + * changes hands. */ +struct wt_ffa_mailbox; +int wt_ffa_partinfo_get(const uint64_t* x, uint32_t caller_version, + struct wt_ffa_mailbox* mb, + const wt_ffa_partinfo_entry_t* parts, size_t n, + uint32_t* count, uint32_t* size); + +/* FFA_PARTITION_INFO_GET_REGS (13.9): up to five matching descriptors per + * call in out[3..17], three registers each (id, contexts and properties; then + * the UUID, zero for a specific-UUID query), from the start index on. out[2] + * packs the last index, the index of the last descriptor returned, the tag and + * the descriptor size. The list never changes, so the tag is zero. + * INVALID_PARAMETERS for a UUID nothing matches, a start index past the end, + * or a nonzero tag at start 0 (MBZ); RETRY for a nonzero tag after it. */ +#define WT_FFA_PARTINFO_REGS_PER_CALL 5u +int wt_ffa_partinfo_regs(const wt_ffa_partinfo_entry_t* parts, size_t n, + const uint8_t* uuid16, uint16_t start, uint16_t tag, + uint64_t* out18); + +/* The same call decoded from its registers x (UUID in x1/x2, start index and + * tag in x3 bits 15:0 and 31:16, bits 63:32 SBZ), for either instance. */ +int wt_ffa_partinfo_regs_call(const wt_ffa_partinfo_entry_t* parts, size_t n, + const uint64_t* x, uint64_t* out18); + +#endif /* WOLFTRUST_ARCH_AARCH64_FFA_PARTINFO_H */ diff --git a/include/wolftrust/arch/aarch64/ffa_runtime.h b/include/wolftrust/arch/aarch64/ffa_runtime.h new file mode 100644 index 00000000..ccbb5111 --- /dev/null +++ b/include/wolftrust/arch/aarch64/ffa_runtime.h @@ -0,0 +1,86 @@ +/* ffa_runtime.h + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfTrust. + * + * wolfTrust 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. + * + * wolfTrust 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, see . + */ + +#ifndef WOLFTRUST_ARCH_AARCH64_FFA_RUNTIME_H +#define WOLFTRUST_ARCH_AARCH64_FFA_RUNTIME_H + +#include + +/* FF-A partition runtime state machine (DEN0077A Ch.8). Each partition's + * execution context is in exactly one of these states; the SPMC drives the + * transitions and rejects illegal ones so a partition can never be entered + * from a state the framework does not allow. */ + +typedef enum wt_ffa_rt_state { + WT_FFA_RT_WAITING = 0, /* idle, ready to receive a message or FFA_RUN */ + WT_FFA_RT_RUNNING, /* executing on this PE */ + WT_FFA_RT_PREEMPTED, /* was running, preempted by a physical interrupt */ + WT_FFA_RT_BLOCKED /* yielded or blocked on an outbound call */ +} wt_ffa_rt_state_t; + +typedef enum wt_ffa_rt_event { + WT_FFA_RT_EV_RUN = 0, /* FFA_RUN allocates cycles to this partition */ + WT_FFA_RT_EV_DIRECT_REQ, /* a direct request is delivered to it */ + WT_FFA_RT_EV_MSG_WAIT, /* it calls FFA_MSG_WAIT, returning to waiting */ + WT_FFA_RT_EV_DIRECT_RESP, /* it sends a direct response, completing a req */ + WT_FFA_RT_EV_YIELD, /* it calls FFA_YIELD or blocks on a call */ + WT_FFA_RT_EV_INTERRUPT /* a physical interrupt preempts it */ +} wt_ffa_rt_event_t; + +/* Apply an FF-A runtime-model event to a partition state (Ch.8, Table 8.1). + * On a legal transition updates *state and returns 0. On an illegal one the + * state is left unchanged and a negative FF-A status is returned: BUSY for a + * direct request to a partition that is not waiting (§7.4), DENIED for every + * other illegal transition (§8.2), INVALID_PARAMETERS for a bad argument. */ +int wt_ffa_rt_transition(wt_ffa_rt_state_t *state, wt_ffa_rt_event_t event); + +/* The runtime model for SP initialization (8.5): whether an execution context + * still initializing may make call fid. A direct request only to an SP that + * has initialized (rule 1); FFA_YIELD, FFA_RUN and the direct responses never + * (rules 4-6, DENIED per 8.1 rule 4); any other call is served. 0 or DENIED. */ +int wt_ffa_rt_init_call(uint32_t fid, int target_initialized); + +/* FFA_SUCCESS completing a direct request in place of a response (15.2, + * 15.4) carries nothing: w1-w7 of FFA_SUCCESS32, x1-x17 of FFA_SUCCESS64 MBZ. + * x = x0-x17. 0, or INVALID_PARAMETERS. */ +int wt_ffa_rt_success_check(const uint64_t* x); + +/* FFA_ERROR from a partition at the Secure virtual instance (Table 12.4): + * w1 MBZ, w2 an error code (negative). 0, or INVALID_PARAMETERS. */ +int wt_ffa_rt_error_check(const uint64_t* x); + +/* FFA_YIELD from a partition (Table 14.9): the w1 endpoint/vCPU ids and the + * w2/w3 timeout are the partition managers' to use and MBZ from an endpoint, + * so a partition cannot ask for a timed yield; w4-w7 are SBZ and ignored. + * x = x0-x7. 0, or INVALID_PARAMETERS. */ +int wt_ffa_rt_yield_check(const uint64_t* x); + +/* FFA_MSG_WAIT flags (w2, Table 14.3): bit 0 keeps the caller's RX buffer. */ +#define WT_FFA_MSG_WAIT_RETAIN_RX 0x1u + +/* FFA_MSG_WAIT from a partition that negotiated version (14.1): 1 when the + * call hands its RX buffer back, 0 when a v1.2 caller keeps it with the + * Retain RX Buffer Ownership flag; bits[31:1] and an earlier caller's w2 are + * SBZ and ignored. x = x0-x7. */ +int wt_ffa_rt_msg_wait_releases_rx(uint32_t version, const uint64_t* x); + +const char *wt_ffa_rt_state_name(wt_ffa_rt_state_t state); + +#endif /* WOLFTRUST_ARCH_AARCH64_FFA_RUNTIME_H */ diff --git a/include/wolftrust/arch/aarch64/gic.h b/include/wolftrust/arch/aarch64/gic.h new file mode 100644 index 00000000..b4dbe447 --- /dev/null +++ b/include/wolftrust/arch/aarch64/gic.h @@ -0,0 +1,63 @@ +/* gic.h + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfTrust. + * + * wolfTrust 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. + * + * wolfTrust 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, see . + */ + +#ifndef WOLFTRUST_ARCH_AARCH64_GIC_H +#define WOLFTRUST_ARCH_AARCH64_GIC_H + +#include + +/* One driver per GIC architecture version, bound at build by WT_GIC_VERSION. + * Secure interrupts are Group 0 (FIQ), Non-secure ones Group 1. */ + +#define WT_GIC_INTID_SPURIOUS 1023u +#define WT_GIC_INTID_SECURE_TIMER 29u +/* INTIDs from 1020 are special: no register lies past the last SPI. */ +#define WT_GIC_INTID_LIMIT 1020u +/* A priority mask at the top of the Non-secure range: Secure priorities pass + * it and a Normal-world interrupt stays pending behind it (9.3.1.3). */ +#define WT_GIC_PMR_MASK_NS 0x80u + +struct wt_gic_ops { + void (*init_secure)(void); + void (*set_group0)(uint32_t intid); + void (*enable)(uint32_t intid); + void (*disable)(uint32_t intid); + void (*set_priority)(uint32_t intid, uint8_t priority); + uint32_t (*ack_group0)(void); + void (*eoi_group0)(uint32_t intid); + void (*set_pending)(uint32_t intid); + void (*raise_ns_sgi)(uint32_t intid); + /* Set the CPU interface priority mask, returning the previous one. */ + uint32_t (*swap_pmr)(uint32_t pmr); + void (*clear_pending)(uint32_t intid); + /* Readback of one 32-line word: enabled lines, and lines not in Group 0. */ + uint32_t (*enabled_word)(uint32_t word); + uint32_t (*not_group0_word)(uint32_t word); + /* Lines the distributor implements (GICD_TYPER.ITLinesNumber). */ + uint32_t (*line_count)(void); + unsigned int version; +}; + +extern const struct wt_gic_ops* const wt_gic; + +/* 1 once a GICv3 redistributor reports its children awake; always 1 on GICv2. */ +unsigned int wt_gic_rdist_woken(void); + +#endif /* WOLFTRUST_ARCH_AARCH64_GIC_H */ diff --git a/include/wolftrust/arch/aarch64/monitor_abi.h b/include/wolftrust/arch/aarch64/monitor_abi.h new file mode 100644 index 00000000..d9fdb864 --- /dev/null +++ b/include/wolftrust/arch/aarch64/monitor_abi.h @@ -0,0 +1,59 @@ +/* monitor_abi.h + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfTrust. + * + * wolfTrust 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. + * + * wolfTrust 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, see . + */ + +#ifndef WOLFTRUST_ARCH_AARCH64_MONITOR_ABI_H +#define WOLFTRUST_ARCH_AARCH64_MONITOR_ABI_H + +#include + +/* S-EL1 -> EL3 monitor calls: SMC64 OEM function ids, accepted only from + * the Secure world; a Non-secure caller gets WT_MON_NOT_SUPPORTED. */ +#define WT_MON_FID_RESUME_NS 0xC3000000u +#define WT_MON_FID_LAUNCH_NS 0xC3000001u +#define WT_MON_FID_PANIC 0xC3000002u +#define WT_MON_FID_SYSTEM_RESET 0xC3000003u +#define WT_MON_FID_EXIT 0xC3000004u +#define WT_MON_FID_SET_TICK_HZ 0xC3000005u +/* Test driver only: x1 != 0 lets the CPU interface signal Group 1 Non-secure + * interrupts with no Normal world to enable them, x1 == 0 stops it. */ +#define WT_MON_FID_TEST_NS_GROUP 0xC3000006u + +#define WT_MON_NOT_SUPPORTED 0xFFFFFFFFFFFFFFFFull + +/* Exit immediates, kept from the Armv8-M BKPT convention the runners expect. */ +#define WT_MON_EXIT_SUCCESS 0x7Fu +#define WT_MON_EXIT_PANIC 0x7Eu +#define WT_MON_EXIT_RESET 0x7Du + +static inline uint64_t wt_mon_call(uint64_t fid, uint64_t arg) +{ + register uint64_t x0 __asm__("x0") = fid; + register uint64_t x1 __asm__("x1") = arg; + + __asm__ volatile("smc #0" + : "+r"(x0) + : "r"(x1) + : "x2", "x3", "x4", "x5", "x6", "x7", "x8", "x9", + "x10", "x11", "x12", "x13", "x14", "x15", "x16", + "x17", "memory"); + return x0; +} + +#endif /* WOLFTRUST_ARCH_AARCH64_MONITOR_ABI_H */ diff --git a/include/wolftrust/arch/aarch64/pl011.h b/include/wolftrust/arch/aarch64/pl011.h new file mode 100644 index 00000000..474fa96f --- /dev/null +++ b/include/wolftrust/arch/aarch64/pl011.h @@ -0,0 +1,31 @@ +/* pl011.h + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfTrust. + * + * wolfTrust 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. + * + * wolfTrust 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, see . + */ + +#ifndef WOLFTRUST_ARCH_AARCH64_PL011_H +#define WOLFTRUST_ARCH_AARCH64_PL011_H + +#include + +/* Polled PL011 console used by QEMU virt, versal-virt, and Versal silicon. */ +void wt_pl011_init(uintptr_t base, uint32_t clock_hz, uint32_t baud); +void wt_pl011_putc(uintptr_t base, char c); +void wt_pl011_flush(uintptr_t base); + +#endif /* WOLFTRUST_ARCH_AARCH64_PL011_H */ diff --git a/include/wolftrust/arch/aarch64/psa_ffa.h b/include/wolftrust/arch/aarch64/psa_ffa.h new file mode 100644 index 00000000..b105941c --- /dev/null +++ b/include/wolftrust/arch/aarch64/psa_ffa.h @@ -0,0 +1,56 @@ +/* psa_ffa.h + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfTrust. + * + * wolfTrust 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. + * + * wolfTrust 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, see . + */ + +#ifndef WOLFTRUST_ARCH_AARCH64_PSA_FFA_H +#define WOLFTRUST_ARCH_AARCH64_PSA_FFA_H + +#include +#include + +#include "wolftrust/arch/aarch64/ffa.h" +#include "wolftrust/arch/aarch64/psa_ffa_transport.h" + +/* AArch64 binding of the PSA client transport: the client op rides the payload + * words of an FFA_MSG_SEND_DIRECT_REQ64 to WT_FFA_ID_PSA (op in w3, arguments + * in x4/x5), and the SPMC answers with the result in w3 of the RESP64. + * The SPMC side is the AArch64 twin of the Armv8-M CMSE veneers: it hands each + * operation to the neutral FF-M gateway (src/arch/common/ffm_gateway.c), whose + * core copies a call's vectors itself (psa_read/psa_write) after the NS-window + * checks below. */ + +/* SPMC-side handler: r holds the client's direct request on entry and the + * direct response (or FFA_ERROR) on return. Returns 0 when it answered, -1 on + * a malformed request. */ +int wt_spm_psa_framework(wt_ffa_regs_t* r); + +/* Record the Non-secure window [ns_lo, ns_hi) the SPMC may read a guest's + * vectors from. Nothing is inside an unset window (fail closed). */ +void wt_spm_psa_init(uint64_t ns_lo, uint64_t ns_hi); + +/* 1 when [base, base+len) lies entirely inside the Non-secure window (an empty + * span always does), else 0. The wt_arch_ns_check_* operations use it. */ +int wt_spm_ns_window_ok(uintptr_t base, size_t len); + +#if !defined(__aarch64__) +/* Host-test SMC seam: the fixture drives one client transaction to the SPMC. */ +void wt_ffa_transport_smc(wt_ffa_regs_t* r); +#endif + +#endif /* WOLFTRUST_ARCH_AARCH64_PSA_FFA_H */ diff --git a/include/wolftrust/arch/aarch64/psa_ffa_transport.h b/include/wolftrust/arch/aarch64/psa_ffa_transport.h new file mode 100644 index 00000000..110d7f99 --- /dev/null +++ b/include/wolftrust/arch/aarch64/psa_ffa_transport.h @@ -0,0 +1,49 @@ +/* psa_ffa_transport.h + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfTrust. + * + * wolfTrust 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. + * + * wolfTrust 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, see . + */ + +#ifndef WOLFTRUST_PSA_FFA_TRANSPORT_H +#define WOLFTRUST_PSA_FFA_TRANSPORT_H + +#include + +/* Architecture-neutral wire contract of the PSA client transport over FF-A. + * The operating-system-neutral client is src/client/psa_ffm_client.c (the same + * one every Armv8-M guest links); the FF-A/SMC binding in src/arch// + * provides its WolfTrust_FFM_* entry points by marshalling each operation into + * (op, arg0, arg1) for wt_psa_ffa_op, and the SPMC front-ends the neutral FF-M + * gateway with them. */ + +#define WT_PSA_FFA_OP_FRAMEWORK_VERSION 1u +#define WT_PSA_FFA_OP_SERVICE_VERSION 2u +#define WT_PSA_FFA_OP_CONNECT 3u +#define WT_PSA_FFA_OP_CLOSE 4u +/* Call: arg0 = the Non-secure address of the client's wt_ffm_veneer_iovec_t, + * arg1 = the handle in bits 31:0 and the call type in bits 63:32. */ +#define WT_PSA_FFA_OP_CALL 5u + +/* Bound on the preemption resume loop the transport hides. */ +#define WT_PSA_FFA_MAX_RESUME 16u + +/* Carry one PSA framework operation to the SPMC and return its result. The + * arguments are 64-bit so a Call can pass the address of its vector block. + * Returns 0 with *result set, or -1 on a transport error. */ +int wt_psa_ffa_op(uint32_t op, uint64_t a0, uint64_t a1, uint32_t* result); + +#endif /* WOLFTRUST_PSA_FFA_TRANSPORT_H */ diff --git a/include/wolftrust/arch/aarch64/psci.h b/include/wolftrust/arch/aarch64/psci.h new file mode 100644 index 00000000..144b5196 --- /dev/null +++ b/include/wolftrust/arch/aarch64/psci.h @@ -0,0 +1,104 @@ +/* psci.h + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfTrust. + * + * wolfTrust 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. + * + * wolfTrust 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, see . + */ + +#ifndef WOLFTRUST_ARCH_AARCH64_PSCI_H +#define WOLFTRUST_ARCH_AARCH64_PSCI_H + +#include + +/* PSCI (Arm DEN0022) function ids: the SMCCC Standard Secure Service range + * 0x84000000-0x8400001F (SMC32) and 0xC4000000-0xC400001F (SMC64), below the + * FF-A range so the SPMD routes by id. The Normal world runs on the boot core + * only, next to the uniprocessor SPMC (WT-FFM-0067). */ +#define WT_PSCI_FID32_FIRST 0x84000000u +#define WT_PSCI_FID32_LAST 0x8400001Fu +#define WT_PSCI_FID64_FIRST 0xC4000000u +#define WT_PSCI_FID64_LAST 0xC400001Fu + +#define WT_PSCI_VERSION 0x84000000u +#define WT_PSCI_CPU_SUSPEND32 0x84000001u +#define WT_PSCI_CPU_SUSPEND64 0xC4000001u +#define WT_PSCI_CPU_OFF 0x84000002u +#define WT_PSCI_CPU_ON32 0x84000003u +#define WT_PSCI_CPU_ON64 0xC4000003u +#define WT_PSCI_AFFINITY_INFO32 0x84000004u +#define WT_PSCI_AFFINITY_INFO64 0xC4000004u +#define WT_PSCI_MIGRATE32 0x84000005u +#define WT_PSCI_MIGRATE64 0xC4000005u +#define WT_PSCI_MIGRATE_INFO_TYPE 0x84000006u +#define WT_PSCI_MIGRATE_INFO_UP_CPU32 0x84000007u +#define WT_PSCI_MIGRATE_INFO_UP_CPU64 0xC4000007u +#define WT_PSCI_SYSTEM_OFF 0x84000008u +#define WT_PSCI_SYSTEM_RESET 0x84000009u +#define WT_PSCI_FEATURES 0x8400000Au +#define WT_PSCI_CPU_FREEZE 0x8400000Bu +#define WT_PSCI_SYSTEM_SUSPEND64 0xC400000Eu + +/* PSCI 1.1 (major 1, minor 1). */ +#define WT_PSCI_VERSION_1_1 0x00010001u + +/* Return codes (5.2.2). */ +#define WT_PSCI_SUCCESS 0 +#define WT_PSCI_NOT_SUPPORTED (-1) +#define WT_PSCI_INVALID_PARAMS (-2) +#define WT_PSCI_DENIED (-3) +#define WT_PSCI_ALREADY_ON (-4) +#define WT_PSCI_INTERNAL_FAILURE (-6) +#define WT_PSCI_DISABLED (-8) + +/* AFFINITY_INFO states: the running core is ON. */ +#define WT_PSCI_AFFINITY_ON 0 + +/* MIGRATE_INFO_TYPE 1: a uniprocessor Trusted OS that cannot migrate. */ +#define WT_PSCI_TOS_UP_NOT_MIGRATABLE 1u + +/* The one power_state offered (original format): core standby, StateID 0. */ +#define WT_PSCI_STATE_CORE_STANDBY 0u + +/* SMCCC Arm Architecture Calls served beside PSCI (DEN0028 7.2, 7.3). */ +#define WT_SMCCC_VERSION 0x80000000u +#define WT_SMCCC_ARCH_FEATURES 0x80000001u +#define WT_SMCCC_VERSION_1_2 0x00010002u +#define WT_SMCCC_NOT_SUPPORTED (-1) + +static inline int wt_psci_fid_in_range(uint32_t fid) +{ + return ((fid >= WT_PSCI_FID32_FIRST) && (fid <= WT_PSCI_FID32_LAST)) || + ((fid >= WT_PSCI_FID64_FIRST) && (fid <= WT_PSCI_FID64_LAST)); +} + +struct wt_ffa_regs; +/* wt_psci_ns_call outcomes: r holds the reply, or a valid power operation + * waits on the SPMC's answer to its power management message (FF-A 18.2.4). */ +#define WT_PSCI_ACTION_REPLY 0 +#define WT_PSCI_ACTION_MESSAGE 1 + +/* EL3 handling of a PSCI call taken at the NS physical instance. */ +int wt_psci_ns_call(struct wt_ffa_regs* r); +/* Finish the operation the message announced: granted runs it (SYSTEM_OFF and + * SYSTEM_RESET do not return), otherwise DENIED. Returns the caller's x0. */ +uint64_t wt_psci_pm_complete(int granted); + +/* EL3 system reset shared by the NS PSCI SYSTEM_RESET and the Secure world's + * WT_MON_FID_SYSTEM_RESET: the port resets the machine, and a port hook that + * returns panics the monitor. tag names the requester. */ +void wt_el3_system_reset(const char* tag); + +#endif /* WOLFTRUST_ARCH_AARCH64_PSCI_H */ diff --git a/include/wolftrust/arch/aarch64/spm_mem.h b/include/wolftrust/arch/aarch64/spm_mem.h new file mode 100644 index 00000000..c162efd0 --- /dev/null +++ b/include/wolftrust/arch/aarch64/spm_mem.h @@ -0,0 +1,174 @@ +/* spm_mem.h + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfTrust. + * + * wolfTrust 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. + * + * wolfTrust 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, see . + */ + +#ifndef WOLFTRUST_ARCH_AARCH64_SPM_MEM_H +#define WOLFTRUST_ARCH_AARCH64_SPM_MEM_H + +#include "wolftrust/arch/aarch64/ffa_mem.h" +#include "wolftrust/ffm_domain.h" + +#include +#include + +/* The SPMC's FF-A memory-sharing relayer (DEN0140): validates a lend or share, + * allocates the handle, and opens a window onto the region in the borrowing + * partition's stage-1 table; relinquish closes it, reclaim frees the handle. + * A partition's table changes only through these transactions. */ + +struct wt_co; + +/* A partition that may take part in memory transactions: its endpoint id, its + * coroutine (the caller identity at the SVC gate), and its mutable domain. */ +typedef struct wt_spm_mem_binding { + struct wt_co* co; + wt_secure_domain_t* dom; + uint16_t id; + uint8_t live; +} wt_spm_mem_binding_t; + +void wt_spm_mem_init(void); +/* The Non-secure memory a Normal-world sender may name. */ +void wt_spm_mem_ns_window(uint64_t base, uint64_t size); + +int wt_spm_mem_bind(uint16_t id, struct wt_co* co, wt_secure_domain_t* dom); + +/* The binding of the calling partition, or NULL if it has none. */ +const wt_spm_mem_binding_t* wt_spm_mem_binding(const struct wt_co* co); + +/* Release everything the partition holds (wt_spm_mem_endpoint_teardown) and + * forget its binding, so a coroutine reused later starts unbound. */ +void wt_spm_mem_unbind(const struct wt_co* co); + +/* Owner side: validate desc as a lend or share from sender and register it. + * Returns 0 with *out_handle set, or a WT_FFA_* negative. */ +int wt_spm_mem_share(const uint8_t* desc, size_t len, wt_ffa_mem_op_t op, + uint16_t sender, uint64_t* out_handle); + +/* A descriptor sent in fragments (DEN0140 4.1.2). begin takes the first + * fragment of a lend, share, donate (op = wt_ffa_mem_op_t) or retrieve request + * (WT_SPM_MEM_FRAG_OP_RETRIEVE) and returns the handle that ties the rest to + * it; next appends one, returning the bytes held in *offset and *done once the + * descriptor is whole. frag_share completes a whole lend/share/donate under + * that handle; a whole retrieve request is read with frag_desc and handed to + * wt_spm_mem_retrieve by the caller, which then releases it. */ +#define WT_SPM_MEM_FRAG_OP_RETRIEVE 0xFFu +int wt_spm_mem_frag_begin(uint8_t op, uint16_t sender, const uint8_t* frag, + uint32_t frag_len, uint32_t total, uint64_t* handle); +int wt_spm_mem_frag_next(uint64_t handle, uint16_t sender, const uint8_t* frag, + uint32_t frag_len, uint32_t* offset, int* done); +const uint8_t* wt_spm_mem_frag_desc(uint64_t handle, uint16_t sender, + uint32_t* len, uint8_t* op); +void wt_spm_mem_frag_release(uint64_t handle, uint16_t sender); +int wt_spm_mem_frag_share(uint64_t handle, uint16_t sender); +/* A lend, share, or donate from the Normal world's TX buffer: its whole + * descriptor, or first fragment when frag_len < total, is copied once into + * Secure memory and then sent as wt_spm_mem_share or begun as + * wt_spm_mem_frag_begin would. WT_FFA_NO_MEMORY past WT_FFA_MEM_FRAG_MAX. */ +int wt_spm_mem_ns_send(wt_ffa_mem_op_t op, const uint8_t* tx, + uint32_t frag_len, uint32_t total, uint64_t* handle); +/* The sender unmapped the TX buffer its fragments come through: its next + * FFA_MEM_FRAG_TX is answered WT_FFA_ABORTED and the transfer ends. */ +void wt_spm_mem_frag_abort(uint16_t sender); + +/* Borrower side: parse the retrieve request in req, check receiver is the + * declared borrower and the request names the owner, write the retrieve + * response descriptor into resp, map the region into the borrower's table, and + * mark the handle retrieved. Returns 0 with *out_resp_len set, or a WT_FFA_* + * negative with nothing changed. */ +int wt_spm_mem_retrieve(const uint8_t* req, size_t len, uint16_t receiver, + uint8_t* resp, size_t resp_cap, size_t* out_resp_len); + +/* Borrower side: parse the relinquish descriptor in rel, check it names the + * caller, unmap the region, and mark the handle relinquished. */ +int wt_spm_mem_relinquish(const uint8_t* rel, size_t len, uint16_t endpoint); + +/* Owner side: free a handle no borrower holds; flags is the FFA_MEM_RECLAIM + * flags word (bit 0 zeroes the memory first). */ +int wt_spm_mem_reclaim(uint64_t handle, uint16_t owner, uint32_t flags); + +/* Non-zero when [base, base + size) holds a page some memory transaction still + * covers: no RX/TX pair may be mapped over it. */ +int wt_spm_mem_in_transaction(uint64_t base, uint64_t size); + +/* Non-zero when the Normal world still owns all of [base, base + size): it lies + * in the window and no partition holds a page of it (one donated or lent). */ +int wt_spm_mem_ns_owns(uint64_t base, uint64_t size); + +/* Non-zero when the Normal world may still read (write = 0) or write + * [base, base + size) of its window, the check every copy the SPMC makes on + * its behalf passes: never a page it lent or donated, or one a partition now + * owns; a page it shares only as the share left it. An empty span passes. */ +int wt_spm_mem_ns_access(uint64_t base, uint64_t size, int write); + +/* Provided by the SVC glue and the Normal-world dispatcher: non-zero when + * [base, base + size) holds a page of an RX/TX pair a partition, or the Normal + * world, has mapped with the SPMC. */ +int wt_spm_mailbox_overlaps(uint64_t base, uint64_t size); +int wt_spm_ns_mailbox_overlaps(uint64_t base, uint64_t size); + +/* FFA_MEM_PERM_GET/SET permission word (DEN0140 Tables 2.36 and 2.40): + * bits[1:0] data access, bit[2] set = not executable. */ +#define WT_FFA_PERM_DATA_MASK 0x3u +#define WT_FFA_PERM_DATA_NONE 0x0u +#define WT_FFA_PERM_DATA_RW 0x1u +#define WT_FFA_PERM_DATA_RO 0x3u +#define WT_FFA_PERM_XN 0x4u + +/* Memory a partition may access, for the calls below: pages its manifest + * names, and pages a donate made its own (DEN0140 2.4.1.2 item 12) that its + * table still gives it and no live transaction covers. Its own memory is + * those pages less the image every partition runs and memory a manifest + * shares with another partition. */ + +/* FFA_MEM_PERM_GET (DEN0140 2.8) for a partition confined to dom: its + * permissions on the page at va in *perm. Returns 0 or + * WT_FFA_INVALID_PARAMETERS for an unaligned address or a page it may not + * access (Table 2.37). */ +int wt_spm_mem_perm_get(const wt_secure_domain_t* dom, uint64_t va, + uint32_t* perm); + +/* FFA_MEM_PERM_SET (DEN0140 2.9) for a partition confined to dom whose RX/TX + * pair is mb: re-permission pages pages at va. Returns 0, or + * WT_FFA_INVALID_PARAMETERS (Table 2.41) for a bad encoding, alignment, or + * count, a page that is not its own, or memory whose permissions are not the + * partition's to change: memory a transaction covers, code every partition + * runs, its mapped RX/TX pair, and, for read-only, the writable manifest + * memory of a partition the relayer does not bind (an FF-M partition), which + * the FF-M gate writes at S-EL1 through the partition's own table. The caller + * answers DENIED outside the partition's initialization. */ +int wt_spm_mem_perm_set(const wt_secure_domain_t* dom, + const wt_ffa_mailbox_t* mb, uint64_t va, + uint32_t pages, uint32_t perm); + +/* Non-zero when the page at va may be one of an FFA_RXTX_MAP pair for the + * partition confined to dom: its own Secure Normal memory, shared with no other + * partition, that it may write and no memory transaction covers (DEN0077A + * 7.2.2.2 rule 3: a pair shared with the SPMC is never visible to the Normal + * world). */ +int wt_spm_mem_rxtx_ok(const wt_secure_domain_t* dom, uint64_t va); + +/* A bound partition faulted and is terminated: unmap everything it retrieved + * (zeroing what its retrieve asked to be zeroed), end what it owns and no + * borrower holds, leave what a borrower still holds to end with that borrower, + * never give the partition access back (DEN0140 1.3.1 rule 9), and drop any + * descriptor it was still sending in fragments. */ +void wt_spm_mem_endpoint_teardown(const struct wt_co* co); + +#endif /* WOLFTRUST_ARCH_AARCH64_SPM_MEM_H */ diff --git a/include/wolftrust/arch/aarch64/spm_svc.h b/include/wolftrust/arch/aarch64/spm_svc.h new file mode 100644 index 00000000..53e27dff --- /dev/null +++ b/include/wolftrust/arch/aarch64/spm_svc.h @@ -0,0 +1,327 @@ +/* spm_svc.h + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfTrust. + * + * wolfTrust 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. + * + * wolfTrust 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, see . + */ + +/* Secure virtual instance (SVC) conventions between S-EL0 partitions and + * the S-EL1 SPMC. FF-A function ids ride x0 as at any instance; the + * wolfTrust partition-message hypercall (DEV-05) uses the OEM range. */ + +#ifndef WOLFTRUST_ARCH_AARCH64_SPM_SVC_H +#define WOLFTRUST_ARCH_AARCH64_SPM_SVC_H + +#include "wolftrust/arch/aarch64/context.h" +#include "wolftrust/arch/aarch64/ffa_msg.h" +#include "wolftrust/types.h" + +#include +#include + +/* The FF-A echo partition is built when the EL3 test driver drives it + * (WT_EL3_TEST_DRIVER) or when a Normal-world guest does (WT_NS_GUEST_ECHO): it + * is the direct-message target in both proofs. */ +#if (defined(WT_EL3_TEST_DRIVER) && (WT_EL3_TEST_DRIVER == 1)) || \ + (defined(WT_NS_GUEST_ECHO) && (WT_NS_GUEST_ECHO == 1)) +#define WT_SPM_ECHO_SP 1 +#endif + +/* x0 = this id, x1 = wt_spm_call_t*, x8 = call->op; x0 = gate status out. */ +#define WT_SPM_SVC_FID_CALL 0xC3000100u +/* Scheduler yield from a partition; x1 carries a token the SPMC records. */ +#define WT_SPM_SVC_FID_YIELD 0xC3000101u +/* Test-timer service for the ACS platform layer: arm a Secure interrupt + * (x1 = intid, x2 = deadline in milliseconds) or stop the caller's own. */ +#define WT_SPM_SVC_FID_TIMER_ARM 0xC3000102u +#define WT_SPM_SVC_FID_TIMER_STOP 0xC3000103u + +/* The para-virtual interrupt controls of the ACS partition support layer + * (Hafnium's values), taken at the SVC gate: enable claims an interrupt for + * the caller, get returns the id the last FFA_INTERRUPT delivered. */ +#define WT_SPM_HVC_INTERRUPT_ENABLE 0xFF03u +#define WT_SPM_HVC_INTERRUPT_GET 0xFF04u +#define WT_SPM_HVC_INTERRUPT_DEACTIVATE 0xFF08u + +/* Saved S-EL0 register state of one partition, indexed by coroutine id. */ +typedef struct wt_sp_arch { + wt_trap_frame_t frame; + uint64_t tpidr_el0; +} wt_sp_arch_t; + +/* Set while an S-EL1 exception handler runs on a partition's behalf. */ +extern volatile uint32_t g_wt_spm_handler_depth; +extern volatile uint64_t g_wt_spm_trap_spsr; +/* The frame of the exception being handled (valid while depth != 0). */ +extern wt_trap_frame_t* volatile g_wt_spm_live_frame; +/* The partition whose exception is being handled (valid while depth != 0); + * a partition it runs on its behalf (an SP-to-SP message) nests under it. */ +struct wt_co; +extern struct wt_co* volatile g_wt_spm_handler_co; + +void wt_spm_sp_panic_trap(void); +void wt_spm_idle(void) __attribute__((noreturn)); + +/* Load frame into the S-EL0 state and ERET; returns when the partition's + * exception handler unwinds back through wt_sp_el0_leave. */ +void wt_sp_el0_enter(wt_trap_frame_t* frame); +void wt_sp_el0_leave(void) __attribute__((noreturn)); +void wt_spm_lower_sync(wt_trap_frame_t* frame); +uint64_t wt_spm_yield_token(void); +/* What wt_arch_thread_unprivileged said inside the last yield's handler. */ +uint32_t wt_spm_yield_unprivileged(void); + +/* Set once the core owns the partitions: each S-EL0 partition's successful + * initialization is then counted and reported. */ +extern volatile uint32_t g_wt_spm_partitions_live; +uint32_t wt_spm_sp_init_count(void); + +/* FF-A endpoint identity of the S-EL0 partitions, and whether one is still in + * its initialization (8.5): until it calls FFA_MSG_WAIT, or for an FF-M + * partition until it blocks in the FF-M gate (bracketed by wt_spm_sp_in_gate); + * wt_spm_sp_init_complete records that. */ +uint16_t wt_spm_sp_ffa_id(const struct wt_co* co); +struct wt_co* wt_spm_sp_by_ffa_id(uint16_t id); +uint16_t wt_spm_sp_ffa_id_of_domain(uint32_t domain_id); +int wt_spm_sp_initializing(const struct wt_co* co); +void wt_spm_sp_init_complete(const struct wt_co* co); +void wt_spm_sp_in_gate(const struct wt_co* co, unsigned int inside); +/* A partition that reported failed initialization (8.5 rule 3, FFA_ERROR with + * code) waits, never counted as initialized and never run again: a request or + * FFA_RUN to it is DENIED, as it is not in a state to handle one. */ +void wt_spm_sp_init_failed(struct wt_co* co, int32_t code); +int wt_spm_sp_failed_init(const struct wt_co* co); +/* Out of service for good after a fault no restart policy covers: what it + * held is released, its id stays listed (6.1 item 2: ids are never reused), + * and an ABI naming it is ABORTED where the ABI's error table has that code. + * A partition that failed initialization gives up the same resources and is + * DENIED instead. wt_spm_sp_unavailable is that code, or 0 for a live one. */ +void wt_spm_sp_retire(struct wt_co* co); +int32_t wt_spm_sp_unavailable(const struct wt_co* co); + +/* The boot handoff record the FF-A boot information named, if any. */ +extern uintptr_t g_wt_spm_handoff_pa; +extern size_t g_wt_spm_handoff_size; +void wt_spm_init_partitions(void); + +/* FF-A direct messaging at the Secure virtual instance. A partition that has + * blocked in FFA_MSG_WAIT is delivered a request by loading it into the saved + * x0-x7 of its frame and resuming it; its FFA_MSG_SEND_DIRECT_RESP is captured + * here by the gate before the partition blocks again. */ +extern uint64_t g_wt_ffa_direct_resp[18]; +extern volatile uint32_t g_wt_ffa_direct_resp_ready; + +/* S-EL0 echo partition (sp_entry.S): replies to each direct request with the + * ids swapped and the first payload word complemented. */ +void wt_sp_ffa_echo(void); + +/* S-EL0 yielding partition (sp_entry.S): FFA_YIELD on each direct request, + * and the echo partition's reply once FFA_RUN resumes it. */ +void wt_sp_ffa_yield(void); + +/* S-EL0 discovery partition (sp_entry.S): calls FFA_PARTITION_INFO_GET with a + * Nil UUID (RX base in x0) and yields the match count and first id. */ +void wt_sp_ffa_discover(void); + +/* S-EL0 memory-sharing borrower (sp_entry.S): x0 = argument block {handle, + * shared page base, TX base, retrieve request length, own id}. Retrieves the + * shared page, reads its seeded bytes and writes a reply byte at S-EL0, yields + * the bytes, relinquishes the page, and yields the status. */ +void wt_sp_ffa_borrow(void); + +/* Preemption of a running S-EL0 partition by the scheduling tick: the lower-EL + * FIQ handler saves the partition's frame, marks it runnable, and unwinds to + * the scheduler. wt_sp_spin is an S-EL0 partition that never blocks, used by + * the boot self-test to prove a spinning partition is preempted. */ +void wt_spm_preempt_from_fiq(wt_trap_frame_t* frame); +void wt_spm_preempt_timer_arm(void); +void wt_spm_preempt_timer_stop(void); +void wt_sp_spin(void); + +/* Deliver req (x0..x17 as at FFA_MSG_WAIT's return) to a waiting partition, + * run it until it responds or yields, and copy the response (or FFA_YIELD) + * into resp. 0 on success; BUSY if it is not waiting, ABORTED if it faulted, + * DENIED if it blocked without responding. */ +struct wt_co; +int wt_spm_ffa_direct_deliver(struct wt_co* co, const uint64_t* req, + uint64_t* resp); + +/* How a running endpoint handed the CPU back; the gate sets it before it + * blocks the partition, the invoker consumes it. */ +#define WT_FFA_SP_EXIT_NONE 0u +#define WT_FFA_SP_EXIT_RESP 1u +#define WT_FFA_SP_EXIT_WAIT 2u +#define WT_FFA_SP_EXIT_YIELD 3u +#define WT_FFA_SP_EXIT_CALL 4u +/* A Normal-world interrupt preempted the partition (Ch.9 NS-Int signaled): + * the invoker sees FFA_INTERRUPT and resumes it later with FFA_RUN. */ +#define WT_FFA_SP_EXIT_NSINT 5u +/* The run loop stopped a preempted partition so a waiting one could be + * signaled first; the stopped one resumes afterwards. */ +#define WT_FFA_SP_EXIT_SIGNAL 6u +extern volatile uint32_t g_wt_ffa_sp_exit; + +/* FFA_RUN on behalf of caller; out is what the caller's FFA_RUN returns. */ +int wt_spm_ffa_run(struct wt_co* co, uint16_t caller, uint64_t* out); +/* Arm target for caller's direct request (req) or FFA_RUN (req == NULL); on 0 + * the gate blocks the caller with WT_FFA_SP_EXIT_CALL. */ +int wt_spm_ffa_sp_call(const struct wt_co* caller, struct wt_co* target, + const uint64_t* req); +/* Non-zero while co processes a direct request, with the request's ids. */ +int wt_spm_ffa_sp_requester(const struct wt_co* co, uint16_t* requester, + uint16_t* self); +/* Non-zero if the request co processes arrived as FFA_MSG_SEND_DIRECT_REQ2. */ +int wt_spm_ffa_sp_req2(const struct wt_co* co); +/* Non-zero if co yielded inside a direct request caller sent it. */ +int wt_spm_ffa_sp_yielded_to(const struct wt_co* co, uint16_t caller); + +/* Secure interrupt routing to a partition (Ch.9, Table 9.1): a declared Secure + * interrupt is signalled to its owner with FFA_INTERRUPT while the owner waits, + * or queued while it runs and delivered on its next FFA_MSG_WAIT. */ +int wt_spm_ffa_signal_deliver(struct wt_co* co, uint32_t intid); + +/* The Secure interrupts queued for one partition (9.2.1): each id at most once, + * delivered oldest first, so none overwrites another. */ +#define WT_SPM_SINT_QUEUE_MAX 8u +typedef struct wt_spm_sint_fifo { + uint32_t intid[WT_SPM_SINT_QUEUE_MAX]; + uint32_t count; +} wt_spm_sint_fifo_t; +/* 0 once intid is queued (an id already queued stays queued once), -1 for + * id 0 or a full queue. */ +int wt_spm_sint_fifo_push(wt_spm_sint_fifo_t* q, uint32_t intid); +/* The oldest queued id, removed; 0 when the queue is empty. */ +uint32_t wt_spm_sint_fifo_pop(wt_spm_sint_fifo_t* q); + +void wt_spm_sint_queue(uint32_t intid); +void wt_spm_sint_queue_for(struct wt_co* co, uint32_t intid); +/* The oldest interrupt queued for co, removed and recorded as the id its + * FFA_INTERRUPT carries (what the get answers); 0 when none is queued. */ +uint32_t wt_spm_sint_take_pending(const struct wt_co* co); +extern volatile uint32_t g_wt_spm_sint_queued; +void wt_spm_prove_sint_route(struct wt_co* co); + +/* Dynamic Secure-interrupt ownership, claimed through the para-virtual + * enable; the id the last FFA_INTERRUPT delivered answers the get. */ +int wt_spm_sint_own(struct wt_co* co, uint32_t intid, unsigned int enable); +struct wt_co* wt_spm_sint_owner(uint32_t intid); +void wt_spm_sint_set_delivered(const struct wt_co* co, uint32_t intid); +uint32_t wt_spm_sint_delivered(const struct wt_co* co); +/* Non-zero when owner waits while another partition runs, so the SPMC must + * preempt that partition to signal the owner (Table 9.1). */ +int wt_spm_sint_signal_needed(struct wt_co* owner); + +/* A Normal-world Group 1 interrupt asserted while a partition ran. */ +void wt_spm_preempt_from_irq(wt_trap_frame_t* frame); +/* Acknowledge the Secure interrupt that preempted the Normal world. */ +uint32_t wt_spm_ns_sint_take(void); + +/* The test-timer service: arm makes the interrupt pending at its deadline + * (see spm_irq.c for when a Normal-world one lands); stop clears the timers + * the caller armed (owner NULL for the Normal world's). A partition arms only + * an interrupt it owns, and its timer dies if it stops owning it; the Normal + * world arms only an SPI no partition may claim. */ +int wt_spm_twdog_arm(const struct wt_co* caller, uint32_t intid, uint32_t ms); +void wt_spm_twdog_stop(const struct wt_co* owner); +void wt_spm_twdog_tick(void); +int wt_spm_current_is_partition(void); + +/* FFA_PARTITION_INFO_GET for either instance, at the caller's negotiated + * version; see spm_svc_glue.c. */ +struct wt_ffa_mailbox; +int wt_spm_partition_info(const uint64_t* x, uint32_t caller_version, + struct wt_ffa_mailbox* mb, uint32_t* count, + uint32_t* size); +int wt_spm_partition_info_regs(const uint64_t* x, uint64_t* out18); +/* The Table 6.2 properties discovery lists for id, or INVALID_PARAMETERS for + * an id it does not list. */ +int wt_spm_partition_props(uint16_t id, uint32_t* props); +/* 0 when id may send FFA_MSG_SEND2, else DENIED (wt_ffa_msg2_sender_allowed). */ +int wt_spm_msg2_sender_allowed(uint16_t id); +/* FFA_NOTIFICATION_SET from either conduit: a receiver discovery lists as not + * taking notifications is DENIED (Table 16.20), the rest wt_ffa_notif_set. */ +int32_t wt_spm_notif_set(uint16_t caller, uint32_t w1, uint32_t w2, + uint64_t bitmap); + +/* FF-A native partitions: separately built S-EL0 images that speak FF-A + * directly rather than hosting an FF-M service (the FF-A ACS endpoints). The + * port lists them; the SPMC maps each one's regions, enters it at its entry, + * and reports it through FFA_PARTITION_INFO_GET. Conformance builds only. */ +#define WT_FFA_NATIVE_SP_MAX 4u +#define WT_FFA_NATIVE_SP_REGIONS 4u +#define WT_FFA_NATIVE_SP_INTIDS 2u + +typedef struct wt_ffa_native_sp { + uintptr_t entry; + uintptr_t stack_base; + size_t stack_size; + wt_memory_region_t regions[WT_FFA_NATIVE_SP_REGIONS]; + size_t region_count; + uint8_t uuid[16]; + uint32_t properties; + /* FF-A "Action in response to a Non-secure interrupt": 0 queued (masked + * while the partition runs), 2 signaled (preempts to the Normal world). */ + uint8_t ns_int_action; + /* The Secure interrupts the partition may claim, all others refused. */ + uint32_t intids[WT_FFA_NATIVE_SP_INTIDS]; + uint32_t intid_count; +} wt_ffa_native_sp_t; + +/* Non-zero when sp declares intid as one it may claim. */ +int wt_spm_native_declares(const wt_ffa_native_sp_t* sp, uint32_t intid); +/* Non-zero when some native partition declares intid. */ +int wt_spm_sint_declared_any(uint32_t intid); + +const wt_ffa_native_sp_t* wt_platform_ffa_native_partitions(size_t* count); +const wt_ffa_native_sp_t* wt_spm_ffa_native_list(size_t* count); +struct wt_co* wt_spm_ffa_native_by_id(uint16_t id); +uint16_t wt_spm_ffa_native_id(size_t index); + +/* A partition's RX/TX pair as the SVC gate registered it, for a producer + * delivering into its RX; NULL when the slot has none. */ +struct wt_ffa_mailbox* wt_spm_sp_mailbox_of(const struct wt_co* co); + +/* Forget a slot's negotiated FF-A version and RX/TX pair, so a partition + * started in it negotiates and maps its own. */ +void wt_spm_sp_ffa_reset(const struct wt_co* co); + +/* The FF-A version a partition, or the Normal world, negotiated (13.2), which + * the data structures exchanged with it follow (DEN0077A 18.5.3). */ +uint32_t wt_spm_sp_ffa_version(const struct wt_co* co); +uint32_t wt_spm_ns_ffa_version(void); +/* Non-zero when a partition is told the security state of memory it retrieves: + * from v1.1 always, at v1.0 only once its FFA_FEATURES(FFA_MEM_RETRIEVE_REQ) + * asked for it (DEN0140 1.10.4.1.1, Table 1.19). */ +int wt_spm_sp_ffa_ns_bit(const struct wt_co* co); + +/* FFA_MSG_SEND2 delivery from either conduit: validate the partition message + * in the caller's TX, in the header layout of the version the caller + * negotiated, against the rules of the instance it was invoked at and copy it + * into the receiver's RX in the layout of the receiver's version. */ +int wt_spm_msg2_deliver(uint16_t caller, uint32_t version, const uint8_t* tx, + uint32_t tx_size, wt_ffa_instance_t inst, uint32_t w1, + uint32_t w2); + +/* The test echo partition (WT_FFA_ID_ECHO), NULL unless WT_EL3_TEST_DRIVER=1. + * enable_mmu publishes its stack band (the slot after the last manifest + * partition stack) and the init pass builds the partition on it. */ +struct wt_co* wt_spm_ffa_echo_partition(void); +/* The runnable endpoint a Normal-world FFA_RUN names: the echo or a native. */ +struct wt_co* wt_spm_ffa_endpoint_by_id(uint16_t id); +extern uintptr_t g_wt_spm_echo_stack_base; +extern uintptr_t g_wt_spm_echo_stack_size; + +#endif /* WOLFTRUST_ARCH_AARCH64_SPM_SVC_H */ diff --git a/include/wolftrust/arch/aarch64/sysreg.h b/include/wolftrust/arch/aarch64/sysreg.h new file mode 100644 index 00000000..041f41bc --- /dev/null +++ b/include/wolftrust/arch/aarch64/sysreg.h @@ -0,0 +1,189 @@ +/* sysreg.h + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfTrust. + * + * wolfTrust 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. + * + * wolfTrust 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, see . + */ + +#ifndef WOLFTRUST_ARCH_AARCH64_SYSREG_H +#define WOLFTRUST_ARCH_AARCH64_SYSREG_H + +#include + +#define WT_SYSREG_READ(name, reg) \ + static inline uint64_t wt_read_##name(void) \ + { \ + uint64_t value; \ + __asm__ volatile("mrs %0, " reg : "=r"(value)); \ + return value; \ + } +#define WT_SYSREG_WRITE(name, reg) \ + static inline void wt_write_##name(uint64_t value) \ + { \ + __asm__ volatile("msr " reg ", %0" : : "r"(value) : "memory"); \ + } + +WT_SYSREG_READ(currentel, "CurrentEL") +WT_SYSREG_READ(mpidr_el1, "MPIDR_EL1") +WT_SYSREG_READ(cntfrq_el0, "CNTFRQ_EL0") +WT_SYSREG_READ(cntpct_el0, "CNTPCT_EL0") +WT_SYSREG_READ(esr_el3, "ESR_EL3") +WT_SYSREG_READ(far_el3, "FAR_EL3") +WT_SYSREG_READ(elr_el3, "ELR_EL3") +WT_SYSREG_READ(spsr_el3, "SPSR_EL3") +WT_SYSREG_READ(scr_el3, "SCR_EL3") +WT_SYSREG_READ(mdcr_el3, "MDCR_EL3") +WT_SYSREG_READ(id_aa64dfr0_el1, "ID_AA64DFR0_EL1") +WT_SYSREG_WRITE(scr_el3, "SCR_EL3") +WT_SYSREG_WRITE(elr_el3, "ELR_EL3") +WT_SYSREG_WRITE(spsr_el3, "SPSR_EL3") +WT_SYSREG_WRITE(sp_el1, "SP_EL1") +WT_SYSREG_WRITE(sctlr_el1, "SCTLR_EL1") + +/* EL1 context that is not banked by security state on these cores: saved and + * restored around a world switch (wt_el3_world_switch). */ +WT_SYSREG_READ(hcr_el2, "HCR_EL2") +WT_SYSREG_WRITE(hcr_el2, "HCR_EL2") +WT_SYSREG_READ(sp_el0, "SP_EL0") +WT_SYSREG_WRITE(sp_el0, "SP_EL0") +WT_SYSREG_READ(sp_el1, "SP_EL1") +WT_SYSREG_READ(sctlr_el1, "SCTLR_EL1") +WT_SYSREG_READ(ttbr0_el1, "TTBR0_EL1") +WT_SYSREG_WRITE(ttbr0_el1, "TTBR0_EL1") +WT_SYSREG_READ(ttbr1_el1, "TTBR1_EL1") +WT_SYSREG_WRITE(ttbr1_el1, "TTBR1_EL1") +WT_SYSREG_READ(tcr_el1, "TCR_EL1") +WT_SYSREG_WRITE(tcr_el1, "TCR_EL1") +WT_SYSREG_READ(mair_el1, "MAIR_EL1") +WT_SYSREG_WRITE(mair_el1, "MAIR_EL1") +WT_SYSREG_READ(amair_el1, "AMAIR_EL1") +WT_SYSREG_WRITE(amair_el1, "AMAIR_EL1") +WT_SYSREG_READ(vbar_el1, "VBAR_EL1") +WT_SYSREG_WRITE(vbar_el1, "VBAR_EL1") +WT_SYSREG_READ(tpidr_el0, "TPIDR_EL0") +WT_SYSREG_WRITE(tpidr_el0, "TPIDR_EL0") +WT_SYSREG_READ(tpidrro_el0, "TPIDRRO_EL0") +WT_SYSREG_WRITE(tpidrro_el0, "TPIDRRO_EL0") +WT_SYSREG_READ(tpidr_el1, "TPIDR_EL1") +WT_SYSREG_WRITE(tpidr_el1, "TPIDR_EL1") +WT_SYSREG_READ(contextidr_el1, "CONTEXTIDR_EL1") +WT_SYSREG_WRITE(contextidr_el1, "CONTEXTIDR_EL1") +WT_SYSREG_READ(cpacr_el1, "CPACR_EL1") +WT_SYSREG_WRITE(cpacr_el1, "CPACR_EL1") +WT_SYSREG_READ(elr_el1, "ELR_EL1") +WT_SYSREG_WRITE(elr_el1, "ELR_EL1") +WT_SYSREG_READ(spsr_el1, "SPSR_EL1") +WT_SYSREG_WRITE(spsr_el1, "SPSR_EL1") +WT_SYSREG_READ(esr_el1, "ESR_EL1") +WT_SYSREG_WRITE(esr_el1, "ESR_EL1") +WT_SYSREG_READ(far_el1, "FAR_EL1") +WT_SYSREG_WRITE(far_el1, "FAR_EL1") +WT_SYSREG_READ(par_el1, "PAR_EL1") +WT_SYSREG_WRITE(par_el1, "PAR_EL1") +WT_SYSREG_READ(mdscr_el1, "MDSCR_EL1") +WT_SYSREG_WRITE(mdscr_el1, "MDSCR_EL1") +WT_SYSREG_READ(cntkctl_el1, "CNTKCTL_EL1") +WT_SYSREG_WRITE(cntkctl_el1, "CNTKCTL_EL1") + +static inline void wt_isb(void) +{ + __asm__ volatile("isb" : : : "memory"); +} + +static inline void wt_dsb_sy(void) +{ + __asm__ volatile("dsb sy" : : : "memory"); +} + +static inline void wt_daif_clear_fiq(void) +{ + __asm__ volatile("msr DAIFClr, #1" : : : "memory"); +} + +static inline void wt_daif_set_fiq(void) +{ + __asm__ volatile("msr DAIFSet, #1" : : : "memory"); +} + +static inline uint64_t wt_current_el(void) +{ + return (wt_read_currentel() >> 2) & 0x3u; +} + +/* SCR_EL3 */ +#define WT_SCR_NS (1u << 0) +#define WT_SCR_IRQ (1u << 1) +#define WT_SCR_FIQ (1u << 2) +#define WT_SCR_EA (1u << 3) +#define WT_SCR_SMD (1u << 7) +#define WT_SCR_HCE (1u << 8) +#define WT_SCR_SIF (1u << 9) +#define WT_SCR_RW (1u << 10) +#define WT_SCR_ST (1u << 11) +/* Secure world running: EA and the secure timer at S-EL1, FIQ left to S-EL1. */ +#define WT_SCR_EL3_SECURE (WT_SCR_RW | WT_SCR_ST | WT_SCR_EA) +/* Normal world running: NS, plus FIQ trapped to EL3 so a Secure interrupt can + * preempt it (Ch.9). Matches wt_el3_enter_ns. */ +#define WT_SCR_EL3_NS (WT_SCR_NS | WT_SCR_FIQ | WT_SCR_EA | WT_SCR_RW | WT_SCR_ST) + +/* HCR_EL2.RW: EL1 is AArch64. Required before an ERET to NS-EL1 AArch64 while + * EL2 is implemented, or the state change is illegal (EC 0x0e). */ +#define WT_HCR_EL2_RW (1ull << 31) + +/* SCTLR_EL3 and SCTLR_EL1 */ +#define WT_SCTLR_EL3_RES1 0x30C50830u +#define WT_SCTLR_EL1_RES1 0x30D00800u +#define WT_SCTLR_M (1u << 0) +#define WT_SCTLR_C (1u << 2) +#define WT_SCTLR_SA (1u << 3) +#define WT_SCTLR_I (1u << 12) + +/* SPSR: EL1h with D, A, I, F masked. */ +#define WT_SPSR_EL1H_DAIF 0x3C5u +#define WT_SPSR_EL2H_DAIF 0x3C9u +#define WT_SPSR_M_EL(spsr) ((uint32_t)(((spsr) >> 2) & 0x3u)) + +/* ESR */ +#define WT_ESR_EC(esr) ((uint32_t)(((esr) >> 26) & 0x3Fu)) +#define WT_ESR_ISS(esr) ((uint32_t)((esr) & 0x1FFFFFFu)) +#define WT_ESR_FSC(esr) ((uint32_t)((esr) & 0x3Fu)) +#define WT_ESR_EC_UNKNOWN 0x00u +#define WT_ESR_EC_FP_ACCESS 0x07u +#define WT_ESR_EC_ILLEGAL_STATE 0x0Eu +#define WT_ESR_EC_SMC32 0x13u +#define WT_ESR_EC_SVC64 0x15u +#define WT_ESR_EC_SMC64 0x17u +#define WT_ESR_EC_SYSREG 0x18u +#define WT_ESR_EC_IABT_LOWER 0x20u +#define WT_ESR_EC_IABT_SAME 0x21u +#define WT_ESR_EC_PC_ALIGN 0x22u +#define WT_ESR_EC_DABT_LOWER 0x24u +#define WT_ESR_EC_DABT_SAME 0x25u +#define WT_ESR_EC_SP_ALIGN 0x26u +#define WT_ESR_EC_SERROR 0x2Fu +#define WT_ESR_EC_BRK 0x3Cu +#define WT_ESR_FSC_EXTERNAL 0x10u +/* Synchronous external aborts and parity/ECC errors, on the access itself + * or on a translation-table walk at level 0-3. */ +#define WT_ESR_FSC_EXTERNAL_WALK 0x14u +#define WT_ESR_FSC_PARITY 0x18u +#define WT_ESR_FSC_PARITY_WALK 0x1Cu +#define WT_ESR_FSC_IS_EXTERNAL(fsc) \ + (((fsc) == WT_ESR_FSC_EXTERNAL) || ((fsc) == WT_ESR_FSC_PARITY) || \ + (((fsc) & 0x3Cu) == WT_ESR_FSC_EXTERNAL_WALK) || \ + (((fsc) & 0x3Cu) == WT_ESR_FSC_PARITY_WALK)) + +#endif /* WOLFTRUST_ARCH_AARCH64_SYSREG_H */ diff --git a/include/wolftrust/arch/aarch64/tables.h b/include/wolftrust/arch/aarch64/tables.h new file mode 100644 index 00000000..e3ac82ed --- /dev/null +++ b/include/wolftrust/arch/aarch64/tables.h @@ -0,0 +1,173 @@ +/* tables.h + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfTrust. + * + * wolfTrust 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. + * + * wolfTrust 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, see . + */ + +#ifndef WOLFTRUST_ARCH_AARCH64_TABLES_H +#define WOLFTRUST_ARCH_AARCH64_TABLES_H + +#include "wolftrust/types.h" + +#include +#include + +/* Stage-1 translation tables for one Secure Partition domain at S-EL0/S-EL1: + * 4 KB granule, 39-bit VA (T0SZ = 25, three levels, pages only), one table + * set per partition, built from the manifest regions over a byte pool. Pure + * C with no system-register access so the host suite can walk every page. */ + +#define WT_TABLES_PAGE_SIZE 4096u +#define WT_TABLES_ENTRIES 512u +#define WT_TABLES_VA_BITS 39u +#define WT_TABLES_VA_LIMIT (1ull << WT_TABLES_VA_BITS) + +#define WT_TABLES_OK 0 +#define WT_TABLES_ERROR_ARGUMENT (-1) +#define WT_TABLES_ERROR_WX (-2) +#define WT_TABLES_ERROR_POOL (-3) +#define WT_TABLES_ERROR_ALIGN (-4) +#define WT_TABLES_ERROR_RANGE (-5) +#define WT_TABLES_ERROR_OVERLAP (-6) +#define WT_TABLES_ERROR_UNMAPPED (-7) + +/* MAIR_EL1: idx0 Device-nGnRnE, idx1 Device-nGnRE, idx2 Normal WBWA, + * idx3 Normal non-cacheable. */ +#define WT_TABLES_ATTR_DEVICE_NGNRNE 0u +#define WT_TABLES_ATTR_DEVICE_NGNRE 1u +#define WT_TABLES_ATTR_NORMAL_WBWA 2u +#define WT_TABLES_ATTR_NORMAL_NC 3u +#define WT_TABLES_MAIR_EL1 \ + (0x00ull | (0x04ull << 8) | (0xFFull << 16) | (0x44ull << 24)) + +/* TCR_EL1: T0SZ 25, IRGN0/ORGN0 WBWA, SH0 inner, TG0 4K, T1SZ 25 with EPD1 + * (no TTBR1 walks until the NS window lands), IPS 40-bit, 8-bit ASIDs. */ +#define WT_TABLES_TCR_EL1 \ + (25ull | (1ull << 8) | (1ull << 10) | (3ull << 12) | (0ull << 14) | \ + (25ull << 16) | (1ull << 23) | (2ull << 32)) + +/* Access permission field values (AP[2:1]). */ +/* Region attribute hint above the access bits: map an EL1-only region + * non-global because another table maps the same range at EL0. */ +#define WT_TABLES_ATTR_NG 0x40000000u +/* Map the output as Non-secure (PTE_NS): a Secure-EL1 access through the entry + * reaches Non-secure physical memory, so the SPMC can read a guest's buffers. */ +#define WT_TABLES_ATTR_NS 0x20000000u + +#define WT_TABLES_AP_EL1_RW 0u +#define WT_TABLES_AP_ALL_RW 1u +#define WT_TABLES_AP_EL1_RO 2u +#define WT_TABLES_AP_ALL_RO 3u + +/* base_pa is what the descriptors carry; on the target it equals base. */ +typedef struct wt_tables_pool { + uint8_t* base; + uint64_t base_pa; + size_t size; + size_t used; +} wt_tables_pool_t; + +typedef struct wt_tables { + uint64_t* l1; + uint64_t l1_pa; + uint16_t asid; +} wt_tables_t; + +typedef struct wt_tables_walk { + uint64_t pa; + uint32_t attr_index; + uint32_t ap; + uint32_t uxn; + uint32_t pxn; + uint32_t ng; + uint32_t ns; + uint32_t held; /* an owner page a memory transaction holds */ + uint32_t hidden; /* an EL0 page its owner made no-access */ +} wt_tables_walk_t; + +void wt_tables_pool_init(wt_tables_pool_t* pool, uint8_t* base, uint64_t base_pa, + size_t size); +uint64_t wt_tables_pool_pa(const wt_tables_pool_t* pool, const void* page); +size_t wt_tables_pool_pages_used(const wt_tables_pool_t* pool); + +/* el0_regions: the partition's manifest regions (EL0 + EL1 access, nG). + * el1_regions: the SPM image, pool, and stacks (EL1 only, global). The two + * sets must not overlap; a region that is writable and executable fails. */ +int wt_tables_build(wt_tables_t* t, uint16_t asid, + const wt_memory_region_t* el0_regions, size_t el0_count, + const wt_memory_region_t* el1_regions, size_t el1_count, + wt_tables_pool_t* pool); + +/* Software walk: WT_TABLES_OK with the page's fields, or ERROR_UNMAPPED. */ +int wt_tables_walk(const wt_tables_t* t, const wt_tables_pool_t* pool, + uint64_t va, wt_tables_walk_t* out); + +uint64_t wt_tables_ttbr0(const wt_tables_t* t); + +/* Re-permission pages the partition already owns at EL0 (FFA_MEM_PERM_SET): + * every page must be a mapped, Secure EL0 page of Normal or Device memory, or + * one made no-access here, else nothing changes. Each keeps its memory type, + * and an executable Device page is refused (ERROR_WX). Attributes 0 is no access: the page + * leaves EL0 but stays the partition's to re-permission, and S-EL1 keeps + * read-write access to it. The caller invalidates the table's ASID. */ +int wt_tables_set_el0_attributes(wt_tables_t* t, const wt_tables_pool_t* pool, + uint64_t va, size_t pages, + uint32_t attributes); + +/* mmu.S: stage 1 on at S-EL1 (M|C|I|SA|SA0|WXN, EL0 wfi/wfe trapping), the + * per-domain TTBR0 switch (distinct ASIDs, no TLBI), and the per-ASID + * invalidation a permission change needs. */ +/* A window onto memory the table does not give EL0: grant rewrites pages it + * maps EL1-only and non-global as EL0 data pages, revoke puts back what was + * there (*was_mapped from the grant). A page the table does not map is never + * granted (ERROR_UNMAPPED), so no grant takes a pool page and every revoke + * leaves the SPMC's own entry. Never executable; an EL0 or global page is + * never granted over. The caller invalidates the table's ASID. */ +int wt_tables_grant_el0(wt_tables_t* t, const wt_tables_pool_t* pool, + uint64_t va, size_t pages, uint32_t attributes, + int* was_mapped); +int wt_tables_revoke_el0(wt_tables_t* t, const wt_tables_pool_t* pool, + uint64_t va, size_t pages, int was_mapped); + +/* An owner's own EL0 pages while a transaction holds them: hold takes EL0 + * access and execution away (keep_read: only write access), keeping each + * entry's permissions in its software bits; release puts back exactly what + * hold kept. Every page must be an EL0 page (hold) or a held one (release), + * else nothing changes; a held page is never re-permissioned. The caller + * invalidates the table's ASID. */ +int wt_tables_hold_el0(wt_tables_t* t, const wt_tables_pool_t* pool, + uint64_t va, size_t pages, int keep_read); +int wt_tables_release_el0(wt_tables_t* t, const wt_tables_pool_t* pool, + uint64_t va, size_t pages); +/* Held pages lose any EL0 access keep_read left them, as a hold without it + * would have, and keep what release puts back. */ +int wt_tables_withdraw_el0(wt_tables_t* t, const wt_tables_pool_t* pool, + uint64_t va, size_t pages); + +void wt_mmu_enable(uint64_t ttbr0, uint64_t mair, uint64_t tcr); +void wt_mmu_switch_ttbr0(uint64_t ttbr0); +void wt_mmu_tlbi_asid(uint64_t asid); +/* Clean and invalidate [va, va + size) to the point of coherency. */ +void wt_mmu_dcache_clean_inval(uint64_t va, uint64_t size); +/* Make instructions written as data in [va, va + size), mapped by the current + * TTBR0, visible to instruction fetch. */ +void wt_mmu_sync_icache(uint64_t va, uint64_t size); + +/* Port hook: device pages the SPM itself needs mapped (the secure console). */ +const wt_memory_region_t* wt_platform_board_device_regions(size_t* count); + +#endif /* WOLFTRUST_ARCH_AARCH64_TABLES_H */ diff --git a/include/wolftrust/spm_sched.h b/include/wolftrust/spm_sched.h index 995b0494..e6d54e39 100644 --- a/include/wolftrust/spm_sched.h +++ b/include/wolftrust/spm_sched.h @@ -53,6 +53,8 @@ int wt_spm_sched_set_restore(int32_t partition_id, * reach, or any access to SPM-private RAM. WT_FFM_ERROR_ISOLATION refuses the * boot. */ int wt_spm_sched_validate(void); +/* Nonzero while the running coroutine is a scheduled Secure Partition. */ +int wt_spm_sched_current_is_partition(void); #if (defined(WT_BAND_NEG_PROBE) && (WT_BAND_NEG_PROBE != 0)) || \ (defined(WT_RESTART_NEG_PROBE) && (WT_RESTART_NEG_PROBE != 0)) diff --git a/mk/arch-aarch64.mk b/mk/arch-aarch64.mk new file mode 100644 index 00000000..1a45eb84 --- /dev/null +++ b/mk/arch-aarch64.mk @@ -0,0 +1,213 @@ +# AArch64 architecture inputs. Included after mk/target-.mk and before +# mk/common.mk. Two products: the EL3 monitor (libwt_el3.a linked whole into +# wolftrust_el3.elf) and the S-EL1 SPMC image (wolftrust.elf: the neutral core, +# the services, wolfCrypt/wolfHSM, the S-EL1 arch layer, and the port). +TOOLPREFIX ?= aarch64-none-elf- +WT_CPU ?= cortex-a72 +WT_GIC_VERSION ?= 3 +AR := $(TOOLPREFIX)ar +# wolftrust.ld and the EL3 guard select objects by name, which LTO renames. +WT_LTO ?= 0 +ifneq ($(WT_LTO),0) +$(error WT_LTO=$(WT_LTO) is unsupported on AArch64 (want 0)) +endif +CPU_FLAGS := -mcpu=$(WT_CPU) -mgeneral-regs-only -mstrict-align +ARCH_CFLAGS := -DWT_TARGET_BUILD=1 -DWT_GIC_VERSION=$(WT_GIC_VERSION) +# Enter the Normal world at EL2 instead of EL1 (a boot loader or an EL2 payload). +WT_EL3_NS_EL2 ?= 0 +ifeq ($(WT_EL3_NS_EL2),1) +ARCH_CFLAGS += -DWT_EL3_NS_EL2=1 +endif +# Test only: the monitor starts on EL2 state an earlier stage left dirty. +# Isolation level gate: only level 3 is implemented, so any other level stops +# the build rather than linking an image without the level 3 layer. +WT_ISOLATION_LEVEL ?= 3 +ifneq ($(WT_ISOLATION_LEVEL),3) +$(error only isolation level 3 is implemented (WT_ISOLATION_LEVEL=$(WT_ISOLATION_LEVEL))) +endif +ARCH_CFLAGS += -DWT_ISOLATION_LEVEL=$(WT_ISOLATION_LEVEL) + +# Level 3 layer shared by every AArch64 port: the board's memory_map.h names +# WT_L3_BAND_BASE and port/common/aarch64/l3_layout.h places every Secure band +# from it; the build reads them back, and an unreadable layout stops it. +PORT_COMMON_DIR := $(ROOT)/port/common/aarch64 +PORT_HEADERS += $(wildcard $(PORT_COMMON_DIR)/*.h) +# A band overridden on the command line reaches the tool too, so the compiler, +# the linker and the manifest check all see the same layout. +WT_L3_OVERRIDABLE := WT_SPM_BOOT_INFO_PA WT_SPM_TABLE_POOL_PA WT_SPM_IMAGE_PA \ + WT_SPM_IMAGE_SIZE WT_SPM_RAM_PA WT_SPM_RAM_SIZE WT_SPM_CONFDATA_PA \ + WT_SPM_CONFDATA_SIZE WT_SPM_KEYSTORE_PA WT_SPM_KEYSTORE_SIZE \ + WT_SPM_RXTX_PA WT_SPM_RXTX_SIZE WT_SPM_SHARE_PA WT_SPM_SHARE_SIZE +WT_L3_TOOL := python3 $(ROOT)/tools/aarch64_l3_layout.py \ + --cc $(TOOLPREFIX)gcc -I$(PORT_COMMON_DIR) \ + -DWT_ISOLATION_LEVEL=$(WT_ISOLATION_LEVEL) \ + $(foreach v,$(WT_L3_OVERRIDABLE),$(if $(filter command \ + line,$(origin $(v))),-D$(v)=$($(v))u)) +# Every target fragment names its port; only the EL3 audit self-test loads +# this fragment alone, with no port and no Secure image to place. +ifneq ($(PORT_DIR),) +WT_L3_LAYOUT := $(shell $(WT_L3_TOOL) --shell $(PORT_DIR)/memory_map.h) +ifeq ($(strip $(WT_L3_LAYOUT)),) +$(error cannot read the level 3 layout from $(PORT_DIR)/memory_map.h) +endif +$(foreach kv,$(WT_L3_LAYOUT),$(eval $(kv))) +WT_SPM_TABLE_POOL_PAGES ?= 128 +ifeq ($(WT_EL3_TEST_HANDOFF),1) +TARGET_CFLAGS += -DWT_PORT_HANDOFF_PA=$(WT_L3_HANDOFF_PA)u +endif +ifeq ($(WT_FFA_ACS),1) +TARGET_CFLAGS += -DWT_FFA_ACS_BASE=$(WT_L3_FFA_ACS_PA)u +endif +WT_L3_CFLAGS := $(foreach v,WT_SPM_BOOT_INFO_PA WT_SPM_TABLE_POOL_PA \ + WT_SPM_IMAGE_PA WT_SPM_IMAGE_SIZE WT_SPM_RAM_PA WT_SPM_RAM_SIZE \ + WT_SPM_KEYSTORE_PA WT_SPM_KEYSTORE_SIZE WT_SPM_RXTX_PA WT_SPM_RXTX_SIZE \ + WT_SPM_SHARE_PA WT_SPM_SHARE_SIZE WT_SPM_CONFDATA_PA \ + WT_SPM_CONFDATA_SIZE,-D$(v)=$($(v))u) \ + -DWT_SPM_TABLE_POOL_PAGES=$(WT_SPM_TABLE_POOL_PAGES)u -I$(PORT_COMMON_DIR) +WT_L3_LDFLAGS := $(foreach v,WT_SPM_IMAGE_PA WT_SPM_IMAGE_SIZE \ + WT_SPM_RAM_PA WT_SPM_RAM_SIZE WT_SPM_KEYSTORE_PA WT_SPM_KEYSTORE_SIZE \ + WT_SPM_VAULT_PA WT_SPM_VAULT_SIZE WT_SPM_ATTEST_PA WT_SPM_ATTEST_SIZE \ + WT_SPM_HSMDATA_PA WT_SPM_HSMDATA_SIZE WT_SPM_CONFDATA_PA \ + WT_SPM_CONFDATA_SIZE,-Wl,--defsym=$(v)=$($(v))) +TARGET_CFLAGS += $(WT_L3_CFLAGS) +TARGET_LDFLAGS += $(WT_L3_LDFLAGS) +TARGET_EXTRA_SRCS += $(PORT_COMMON_DIR)/platform_l3.c +endif + +WT_FP_NEG_PROBE ?= 0 +ifeq ($(WT_FP_NEG_PROBE),1) +ARCH_CFLAGS += -DWT_FP_NEG_PROBE=1 +endif + +WT_MSP_OVF_PROBE ?= 0 +ifeq ($(WT_MSP_OVF_PROBE),1) +ARCH_CFLAGS += -DWT_MSP_OVF_PROBE=1 +endif + +WT_XN_NEG_PROBE ?= 0 +ifeq ($(WT_XN_NEG_PROBE),1) +ARCH_CFLAGS += -DWT_XN_NEG_PROBE=1 +endif + +WT_EL3_EL2_DIRTY_PROBE ?= 0 +ifeq ($(WT_EL3_EL2_DIRTY_PROBE),1) +ARCH_CFLAGS += -DWT_EL3_EL2_DIRTY_PROBE=1 +endif +# Test only: an earlier stage left every GICv3 SPI routed to an absent PE. +WT_GIC_SPI_ROUTE_PROBE ?= 0 +ifeq ($(WT_GIC_SPI_ROUTE_PROBE),1) +ARCH_CFLAGS += -DWT_GIC_SPI_ROUTE_PROBE=1 +endif +# Test only: the monitor sees its redistributor asleep (1) or never gets its +# secure tick (2), or an SPMC boot self-test fails (3), and the boot must stop. +WT_EL3_BOOT_NEG_PROBE ?= 0 +ifneq ($(WT_EL3_BOOT_NEG_PROBE),0) +ARCH_CFLAGS += -DWT_EL3_BOOT_NEG_PROBE=$(WT_EL3_BOOT_NEG_PROBE) +endif +WT_WOLFCRYPT_SP_ASM := 0 +WT_WOLFCRYPT_ARMASM := 0 +# 64-bit SP math words, C implementation (no WOLFSSL_SP_ARM64_ASM). +ARCH_HSM_DEFS := -DWOLFSSL_SP_ARM64 -DHAVE___UINT128_T=1 +ARCH_WOLFCRYPT_SP_SRCS := $(WOLFSSL_DIR)/wolfcrypt/src/sp_c64.c +ARCH_WOLFCRYPT_ASM_SRCS := +# RAM-backed NVM until the QEMU ports have a flash controller model. +ARCH_WOLFHSM_SRCS := $(WOLFHSM_DIR)/src/wh_flash_ramsim.c +ARCH_START_SRCS := +ARCH_SRCS := +WT_SPM_TABLE_PAGES ?= 8 +MANIFEST_ARCH_OPTS := --address-bits 64 --mpu-granule 4096 \ + --spm-table-pages $(WT_SPM_TABLE_PAGES) + +ARCH_DIR := $(ROOT)/src/arch/aarch64 +ARCH_SHARED_C_SRCS := \ + $(ARCH_DIR)/el3/console.c \ + $(ARCH_DIR)/el3/timer.c \ + $(ARCH_DIR)/ffa/ffa_boot_info.c \ + $(ARCH_DIR)/gic/gicv$(WT_GIC_VERSION).c \ + $(ARCH_DIR)/drivers/pl011.c \ + $(EL3_PORT_SRCS) +EL3_C_SRCS := \ + $(ARCH_SHARED_C_SRCS) \ + $(ARCH_DIR)/el3/esr.c \ + $(ARCH_DIR)/el3/monitor_calls.c \ + $(ARCH_DIR)/el3/el3_main.c \ + $(ARCH_DIR)/el3/world.c \ + $(ARCH_DIR)/el3/psci.c \ + $(ARCH_DIR)/ffa/ffa_spmd.c \ + $(ARCH_DIR)/ffa/ffa_mem.c \ + $(ARCH_DIR)/ffa/ffa_msg.c \ + $(ARCH_DIR)/common/libc_min.c +EL3_ASM_SRCS := \ + $(ARCH_DIR)/el3/start.S \ + $(ARCH_DIR)/el3/vectors.S +SPM_C_SRCS := \ + $(ARCH_SHARED_C_SRCS) \ + $(ARCH_DIR)/spm/spm_main.c \ + $(ARCH_DIR)/spm/tables.c \ + $(ARCH_DIR)/spm/domain.c \ + $(ARCH_DIR)/spm/spm_irq.c \ + $(ARCH_DIR)/spm/platform_arch.c \ + $(ARCH_DIR)/spm/coroutine_aarch64.c \ + $(ARCH_DIR)/spm/sp_trap.c \ + $(ARCH_DIR)/spm/spm_svc_glue.c \ + $(ARCH_DIR)/spm/psa_service.c \ + $(ARCH_DIR)/spm/spm_mem.c \ + $(ARCH_DIR)/ffa/ffa_msg.c \ + $(ARCH_DIR)/ffa/ffa_mem.c \ + $(ARCH_DIR)/ffa/ffa_notif.c \ + $(ARCH_DIR)/ffa/ffa_partinfo.c \ + $(ARCH_DIR)/ffa/ffa_runtime.c \ + $(ARCH_DIR)/el3/esr.c +# The conformance image adds the privileged NVM/interrupt backend the SVC gate +# calls for the unprivileged DRIVER partition (WT_CONFORMANCE is a command-line +# override, so it is already set here before mk/common.mk seats its default). +ifeq ($(WT_CONFORMANCE),1) +SPM_C_SRCS += $(ROOT)/port/common/aarch64/conf_backend.c +endif +SPM_ASM_SRCS := $(ARCH_DIR)/spm/spm_entry.S $(ARCH_DIR)/spm/mmu.S \ + $(ARCH_DIR)/spm/spm_switch.S $(ARCH_DIR)/spm/sp_entry.S +ARCH_TREE_SRCS := $(sort $(EL3_C_SRCS) $(SPM_C_SRCS)) +ARCH_ASM_SRCS := $(EL3_ASM_SRCS) $(SPM_ASM_SRCS) + +wt_arch_objs = $(foreach s,$(1),$(BUILD_DIR)/wt_sec_$(notdir $(basename $(s))).o) +EL3_ARCHIVE_OBJS := $(call wt_arch_objs,$(EL3_C_SRCS) $(EL3_ASM_SRCS)) +ARCH_SECURE_OBJS := $(call wt_arch_objs,$(SPM_C_SRCS) $(SPM_ASM_SRCS)) +EL3_LIB := $(BUILD_DIR)/libwt_el3.a +EL3_ELF := $(BUILD_DIR)/wolftrust_el3.elf +EL3_BIN := $(BUILD_DIR)/wolftrust_el3.bin +EL3_LD := $(ARCH_DIR)/el3/el3.ld + +SECURE_CMSE_IMPLIB := +ARCH_LINK_OUTPUTS := +# The core is not entered yet; keep it linked so the closure is proven. +ARCH_LDFLAGS := -Wl,--undefined=wt_boot_run +# The manifest must grant exactly the keystore bands the shared layout places. +define arch_image_checks + $(WT_L3_TOOL) --check-manifest $(MANIFEST_INPUT) $(PORT_DIR)/memory_map.h \ + || { rm -f $(SECURE_ELF); exit 1; } +endef +$(BUILD_DIR)/wolftrust.elf: $(ROOT)/tools/aarch64_l3_layout.py \ + $(PORT_COMMON_DIR)/l3_layout.h $(MANIFEST_INPUT) +ARCH_DEFAULT_GOALS := el3-image secure-image + +.PHONY: el3-image +el3-image: $(EL3_BIN) $(EL3_ELF) + @$(SIZE) $(EL3_ELF) + +$(EL3_LIB): $(EL3_ARCHIVE_OBJS) | $(BUILD_DIR) + rm -f $@ + $(AR) rcs $@ $(EL3_ARCHIVE_OBJS) + +# WT-PORT-0012: the archive is audited, then linked whole. A policy change +# re-audits, and a failed audit leaves no image, stale or new, behind. +EL3_AUDIT := $(ROOT)/tools/check-el3-symbols.sh $(ROOT)/tools/el3-symbols.allow \ + $(ROOT)/tools/el3-defines.allow +$(EL3_ELF): $(EL3_LIB) $(EL3_LD) $(EL3_AUDIT) + rm -f $@ $(EL3_BIN) + $(ROOT)/tools/check-el3-symbols.sh $(EL3_LIB) --nm $(TOOLPREFIX)nm + $(CC) $(SECURE_CFLAGS) -nostartfiles -Wl,--build-id=none \ + $(TARGET_LDFLAGS) -Wl,-T$(EL3_LD) -Wl,--gc-sections \ + -o $@ -Wl,--whole-archive $(EL3_LIB) -Wl,--no-whole-archive -lgcc + +$(EL3_BIN): $(EL3_ELF) + $(OBJCOPY) -O binary $< $@ diff --git a/mk/common.mk b/mk/common.mk index 874c665c..3ee39a6d 100644 --- a/mk/common.mk +++ b/mk/common.mk @@ -94,6 +94,7 @@ WT_VNET_NEG_PROBE ?= 0 WT_MANIFEST_NEG_PROBE ?= 0 WT_REMEASURE_PROBE ?= 0 WT_BOOTUPDATE_PROBE ?= 0 +WT_TABLES_NEGATIVE ?= 0 WT_CONFORMANCE ?= 0 # Virtual-Ethernet (VNET) subsystem. Off until Wave 2 lands a working @@ -222,6 +223,28 @@ endif ifeq ($(WT_BOOTUPDATE_PROBE),1) SECURE_CFLAGS += -DWT_BOOTUPDATE_PROBE=1 endif +ifeq ($(WT_TABLES_NEGATIVE),1) +SECURE_CFLAGS += -DWT_TABLES_NEGATIVE=1 +endif +WT_EL3_TEST_DRIVER ?= 0 +ifeq ($(WT_EL3_TEST_DRIVER),1) +SECURE_CFLAGS += -DWT_EL3_TEST_DRIVER=1 +endif +WT_EL3_NS_SMOKE ?= 0 +ifeq ($(WT_EL3_NS_SMOKE),1) +SECURE_CFLAGS += -DWT_EL3_NS_SMOKE=1 +endif +# Build the FF-A echo partition for a Normal-world guest to exchange a direct +# message with (the ffa-guest-direct proof), without the EL3 test driver. +WT_NS_GUEST_ECHO ?= 0 +ifeq ($(WT_NS_GUEST_ECHO),1) +SECURE_CFLAGS += -DWT_NS_GUEST_ECHO=1 +endif +# Arm a Secure tick that preempts the Normal world (the ffa-preempt proof). +WT_NS_PREEMPT ?= 0 +ifeq ($(WT_NS_PREEMPT),1) +SECURE_CFLAGS += -DWT_NS_PREEMPT=1 +endif # Hardware guest-flash write protection: refuse to launch a guest whose image # sectors are not WRP-protected, so a peer Non-secure guest cannot reprogram a # suspended guest's flash. Silicon only (the M33MU model has no flash WRP). @@ -301,6 +324,7 @@ ifeq ($(WT_ENGINE),native) WOLFHSM_SECURE_SRCS := $(filter %/wh_nvm.c %/wh_nvm_flash.c %/wh_flash_unit.c \ %/wh_lock.c %/wh_utils.c %/wh_keyid.c,$(WOLFHSM_SECURE_SRCS)) endif +WOLFHSM_SECURE_SRCS += $(ARCH_WOLFHSM_SRCS) WOLFCRYPT_SECURE_SRCS := \ $(WOLFSSL_DIR)/wolfcrypt/src/aes.c \ @@ -395,13 +419,14 @@ MANIFEST_OBJ := $(BUILD_DIR)/wt_sec_wolftrust_manifest_generated.o ARCH_TREE_SRCS ?= ARCH_ASM_SRCS ?= ARCH_TREE_OBJS := $(foreach s,$(ARCH_TREE_SRCS) $(ARCH_ASM_SRCS),$(BUILD_DIR)/wt_sec_$(notdir $(basename $(s))).o) +ARCH_SECURE_OBJS ?= $(ARCH_TREE_OBJS) ALL_SECURE_OBJS := $(strip \ $(HSM_SECURE_BASE_OBJS) \ $(HSM_WOLFHSM_SEC_OBJS) \ $(HSM_WOLFCRYPT_SEC_OBJS) \ $(HSM_WT_EXTRA_OBJS) \ - $(ARCH_TREE_OBJS) \ + $(ARCH_SECURE_OBJS) \ $(MANIFEST_OBJ)) # LTO cannot safely rewrite objects whose symbols are consumed by inline @@ -843,7 +868,10 @@ CONF_UPSTREAM_SRCS := \ $(CONF_UPSTREAM_SRCS): $(UPSTREAM_STAMP) ; -$(UPSTREAM_STAMP): | $(BUILD_DIR) +# lp64-addr: i072/i084 write sizeof(pointer) bytes into a 4-byte addr_t +# out-vector, a PROGRAMMER ERROR on LP64 targets; identical bytes on 32-bit. +$(UPSTREAM_STAMP): $(ROOT)/tests/upstream/psa-arch-tests-ec-overflow.patch \ + $(ROOT)/tests/upstream/psa-arch-tests-lp64-addr.patch | $(BUILD_DIR) $(ROOT)/tests/upstream/fetch_psa_arch_tests.sh \ $(BUILD_DIR)/upstream/psa-arch-tests git -C $(BUILD_DIR)/upstream/psa-arch-tests apply --reverse --check \ @@ -851,6 +879,11 @@ $(UPSTREAM_STAMP): | $(BUILD_DIR) 2>/dev/null || \ git -C $(BUILD_DIR)/upstream/psa-arch-tests apply \ $(abspath $(ROOT)/tests/upstream/psa-arch-tests-ec-overflow.patch) + git -C $(BUILD_DIR)/upstream/psa-arch-tests apply --reverse --check \ + $(abspath $(ROOT)/tests/upstream/psa-arch-tests-lp64-addr.patch) \ + 2>/dev/null || \ + git -C $(BUILD_DIR)/upstream/psa-arch-tests apply \ + $(abspath $(ROOT)/tests/upstream/psa-arch-tests-lp64-addr.patch) touch $@ # Derived schedule, not a suite edit: skipped tests need a runtime capability @@ -1393,7 +1426,8 @@ $(MANIFEST_DIR): # The stamp records the selected variant; a mismatch regenerates even when # mtimes tie within one second, so a stale variant can never be linked. -MANIFEST_MODE := MANIFEST_INPUT=$(MANIFEST_INPUT) CONFIG_VNET=$(CONFIG_VNET) WT_CONFORMANCE=$(WT_CONFORMANCE) GEN_OPTS=--supported-features 0x1 --supported-framework-version 0x100 --address-bits 32 +MANIFEST_ARCH_OPTS ?= --address-bits 32 +MANIFEST_MODE := MANIFEST_INPUT=$(MANIFEST_INPUT) CONFIG_VNET=$(CONFIG_VNET) WT_CONFORMANCE=$(WT_CONFORMANCE) GEN_OPTS=--supported-features 0x1 --supported-framework-version 0x100 $(MANIFEST_ARCH_OPTS) $(MANIFEST_STAMP): $(ROOT)/tools/manifest/generate.py $(MANIFEST_INPUT) \ FORCE | $(MANIFEST_DIR) @@ -1404,7 +1438,7 @@ $(MANIFEST_STAMP): $(ROOT)/tools/manifest/generate.py $(MANIFEST_INPUT) \ else \ python3 $(ROOT)/tools/manifest/generate.py $(MANIFEST_INPUT) \ $(MANIFEST_DIR) --supported-features 0x1 \ - --supported-framework-version 0x100 --address-bits 32 \ + --supported-framework-version 0x100 $(MANIFEST_ARCH_OPTS) \ && printf '%s\n' '$(MANIFEST_MODE)' > "$@"; \ fi @@ -1460,6 +1494,19 @@ $(BUILD_MODE_STAMP): FORCE | $(BUILD_DIR) 'WT_SVC_NEG_PROBE=$(WT_SVC_NEG_PROBE)' \ 'WT_REMEASURE_PROBE=$(WT_REMEASURE_PROBE)' \ 'WT_BOOTUPDATE_PROBE=$(WT_BOOTUPDATE_PROBE)' \ + 'WT_ENGINE=$(WT_ENGINE)' \ + 'WT_TABLES_NEGATIVE=$(WT_TABLES_NEGATIVE)' \ + 'WT_EL3_TEST_DRIVER=$(WT_EL3_TEST_DRIVER)' \ + 'WT_EL3_NS_SMOKE=$(WT_EL3_NS_SMOKE)' \ + 'WT_NS_GUEST_ECHO=$(WT_NS_GUEST_ECHO)' \ + 'WT_NS_PREEMPT=$(WT_NS_PREEMPT)' \ + 'WT_GUEST_FLASH_WRP=$(WT_GUEST_FLASH_WRP)' \ + 'WT_VAULT_FOREIGN_PROBE=$(WT_VAULT_FOREIGN_PROBE)' \ + 'WT_VAULT_PROBE_SECURED=$(WT_VAULT_PROBE_SECURED)' \ + 'CPU_FLAGS=$(CPU_FLAGS)' \ + 'ARCH_CFLAGS=$(ARCH_CFLAGS)' \ + 'TARGET_CFLAGS=$(TARGET_CFLAGS)' \ + 'TARGET_LDFLAGS=$(TARGET_LDFLAGS)' \ 'WT_MAX_GUESTS=$(WT_MAX_GUESTS)' \ 'WT_CO_STACK_SIZE=$(WT_CO_STACK_SIZE)' \ 'WT_SPM_STACK_SIZE=$(WT_SPM_STACK_SIZE)' \ @@ -1505,6 +1552,9 @@ $(BUILD_DIR)/wt_sec_%.o: $(ROOT)/src/sync/%.c $(WOLFHSM_CFG_H) $(BUILD_MODE_STAM $(BUILD_DIR)/wt_sec_%.o: $(WOLFHSM_RUNNER_DIR)/%.c $(WOLFHSM_CFG_H) $(BUILD_MODE_STAMP) | $(BUILD_DIR) $(CC) $(SECURE_CFLAGS) -c -o $@ $< +$(BUILD_DIR)/wt_sec_%.o: $(ROOT)/port/common/$(ARCH)/%.c $(PORT_HEADERS) $(WOLFHSM_CFG_H) $(BUILD_MODE_STAMP) | $(BUILD_DIR) + $(CC) $(SECURE_CFLAGS) -c -o $@ $< + $(BUILD_DIR)/wt_sec_%.o: $(PORT_DIR)/%.c $(PORT_HEADERS) $(WOLFHSM_CFG_H) $(BUILD_MODE_STAMP) | $(BUILD_DIR) $(CC) $(SECURE_CFLAGS) -c -o $@ $< @@ -1555,6 +1605,9 @@ $(BUILD_DIR)/sec_$(notdir $(TARGET_PLATFORM_SRC:.c=.o)): $(TARGET_PLATFORM_SRC) $(BUILD_DIR)/sec_%.o: $(WOLFHSM_RUNNER_DIR)/%.c $(WOLFHSM_CFG_H) $(BUILD_MODE_STAMP) | $(BUILD_DIR) $(CC) $(SECURE_CFLAGS) -c -o $@ $< +$(BUILD_DIR)/sec_%.o: $(ROOT)/port/common/$(ARCH)/%.c $(PORT_HEADERS) $(WOLFHSM_CFG_H) $(BUILD_MODE_STAMP) | $(BUILD_DIR) + $(CC) $(SECURE_CFLAGS) -c -o $@ $< + $(BUILD_DIR)/sec_%.o: $(PORT_DIR)/%.c $(PORT_HEADERS) $(WOLFHSM_CFG_H) $(BUILD_MODE_STAMP) | $(BUILD_DIR) $(CC) $(SECURE_CFLAGS) -c -o $@ $< diff --git a/mk/target-qemuvirt.mk b/mk/target-qemuvirt.mk new file mode 100644 index 00000000..5a0eb4d5 --- /dev/null +++ b/mk/target-qemuvirt.mk @@ -0,0 +1,83 @@ +# QEMU virt (secure=on) target inputs for the AArch64 build. Included first, +# before mk/arch-.mk and mk/common.mk; repository paths come from the +# Makefile. GICv2 or GICv3 and the CPU model are runner-selected. +WT_CPU ?= cortex-a72 +WT_GIC_VERSION ?= 3 +WT_PORT_BOOT_CPUS ?= 2 +PORT_DIR := $(ROOT)/port/qemuvirt +PORT_HEADERS := $(wildcard $(PORT_DIR)/*.h) +# The conformance PAL is shared by the AArch64 targets; WT_CONFORMANCE=1 swaps +# in the manifest that also hosts Arm's test partitions. +TARGET_CONF_DIR := $(ROOT)/port/common/aarch64/conformance +ifeq ($(WT_CONFORMANCE),1) +MANIFEST_INPUT := $(PORT_DIR)/manifest-conformance.json +else +MANIFEST_INPUT := $(PORT_DIR)/manifest.json +endif + +WT_EL3_TEXT_BASE ?= 0x00000000 +WT_EL3_RAM_BASE ?= 0x0E000000 +WT_EL3_RAM_SIZE ?= 0x00040000 +WT_NS_IMAGE_PA ?= 0x44000000 +WT_PSA_NS_WINDOW_SIZE ?= 0x00100000 +WT_SPM_FLASH_OFFSET ?= 0x00100000 +WT_QEMU_TEST_ENTROPY ?= 1 +# A system reset reboots the machine through its Secure GPIO, bounded so a test +# run ends (the conformance suite resets on every panic test). +ifeq ($(WT_CONFORMANCE),1) +WT_EL3_RESET_LIMIT ?= 256 +else +WT_EL3_RESET_LIMIT ?= 1 +endif +WT_UART_SKIP_INIT ?= 0 +# QEMU presets CNTFRQ_EL0; leave it alone like a boot ROM would. +WT_PORT_CNTFRQ_KEEP ?= 1 + +TARGET_CFLAGS := \ + -DWT_EL3_TEXT_BASE=$(WT_EL3_TEXT_BASE)u \ + -DWT_EL3_RAM_BASE=$(WT_EL3_RAM_BASE)u \ + -DWT_EL3_RAM_SIZE=$(WT_EL3_RAM_SIZE)u \ + -DWT_PORT_BOOT_CPUS=$(WT_PORT_BOOT_CPUS)u \ + -DWT_UART_SKIP_INIT=$(WT_UART_SKIP_INIT) \ + -DWT_PORT_CNTFRQ_KEEP=$(WT_PORT_CNTFRQ_KEEP) \ + -DWT_NS_IMAGE_PA=$(WT_NS_IMAGE_PA)u \ + -DWT_PSA_NS_WINDOW_SIZE=$(WT_PSA_NS_WINDOW_SIZE)u \ + -DWT_SPM_FLASH_OFFSET=$(WT_SPM_FLASH_OFFSET)u \ + -DWT_QEMU_TEST_ENTROPY=$(WT_QEMU_TEST_ENTROPY) \ + -DWT_EL3_RESET_LIMIT=$(WT_EL3_RESET_LIMIT)u \ + -DWT_PORT_EMULATED=1 +# No boot loader runs ahead of the monitor under QEMU: synthesize the boot +# handoff record it would leave (emulator tests only, never production). +WT_EL3_TEST_HANDOFF ?= 0 +ifeq ($(WT_EL3_TEST_HANDOFF),1) +TARGET_CFLAGS += -DWT_EL3_TEST_HANDOFF=1 -DWT_PORT_HANDOFF_SIZE=64u +endif +# Arm FF-A ACS conformance image (its band base comes from the shared layout). +WT_FFA_ACS ?= 0 +# The runner places the partition images and the test NVM here in the pflash +# image; the monitor copies them into Secure RAM. +WT_FFA_ACS_FLASH_OFFSET ?= 0x00200000 +WT_FFA_ACS_FLASH_SIZE ?= 0x00410000 +# The test NVM rides at this offset within the ACS band, behind the four 1 MB +# SP images; it must survive a reset (the suite records progress in it). +WT_FFA_ACS_NVM_OFFSET ?= 0x00400000 +ifeq ($(WT_FFA_ACS),1) +TARGET_CFLAGS += -DWT_FFA_ACS=1 \ + -DWT_FFA_ACS_FLASH_OFFSET=$(WT_FFA_ACS_FLASH_OFFSET)u \ + -DWT_FFA_ACS_FLASH_SIZE=$(WT_FFA_ACS_FLASH_SIZE)u \ + -DWT_FFA_ACS_NVM_OFFSET=$(WT_FFA_ACS_NVM_OFFSET)u +endif +# The monitor's load image must end before the SPMC image packed behind it in +# the one pflash file (run_qemu_a_scenario.sh truncates to this offset). +TARGET_LDFLAGS := \ + -Wl,--defsym=WT_EL3_LOAD_LIMIT=$(WT_EL3_TEXT_BASE)+$(WT_SPM_FLASH_OFFSET) \ + -Wl,--defsym=WT_EL3_TEXT_BASE=$(WT_EL3_TEXT_BASE) \ + -Wl,--defsym=WT_EL3_RAM_BASE=$(WT_EL3_RAM_BASE) \ + -Wl,--defsym=WT_EL3_RAM_SIZE=$(WT_EL3_RAM_SIZE) +SECURE_LD := $(ROOT)/src/arch/aarch64/spm/wolftrust.ld + +PORT_COMMON_DIR := $(ROOT)/port/common/aarch64 +TARGET_PLATFORM_SRC := $(PORT_COMMON_DIR)/platform_qemu.c +TARGET_PARTITIONS_SRC := $(PORT_COMMON_DIR)/partitions.c +TARGET_EXTRA_SRCS := $(PORT_COMMON_DIR)/hsm_nvm.c $(PORT_COMMON_DIR)/rng_entropy.c +EL3_PORT_SRCS := $(PORT_DIR)/el3_board.c $(PORT_DIR)/uart.c diff --git a/mk/target-versal.mk b/mk/target-versal.mk new file mode 100644 index 00000000..c3e57f9d --- /dev/null +++ b/mk/target-versal.mk @@ -0,0 +1,66 @@ +# AMD Versal target inputs for the AArch64 build. Included first, before +# mk/arch-.mk and mk/common.mk; repository paths come from the Makefile. +# WT_VERSAL_VIRT=1 selects the QEMU xlnx-versal-virt variant of the port. +WT_CPU ?= cortex-a72 +WT_GIC_VERSION := 3 +# xlnx-versal-virt keeps APU core 1 powered off: only the boot core comes up. +WT_PORT_BOOT_CPUS ?= 1 +WT_VERSAL_VIRT ?= 1 +ifneq ($(WT_VERSAL_VIRT),1) +$(error the Versal silicon paths land with the port bring-up; build with WT_VERSAL_VIRT=1) +endif +PORT_DIR := $(ROOT)/port/versal +PORT_HEADERS := $(wildcard $(PORT_DIR)/*.h) +# The conformance PAL is shared by the AArch64 targets; WT_CONFORMANCE=1 swaps +# in the manifest that also hosts Arm's test partitions. +TARGET_CONF_DIR := $(ROOT)/port/common/aarch64/conformance +ifeq ($(WT_CONFORMANCE),1) +MANIFEST_INPUT := $(PORT_DIR)/manifest-conformance.json +else +MANIFEST_INPUT := $(PORT_DIR)/manifest.json +endif + +WT_EL3_TEXT_BASE ?= 0xFFFC0000 +WT_EL3_RAM_BASE ?= 0xFFFE0000 +WT_EL3_RAM_SIZE ?= 0x00020000 +WT_NS_IMAGE_PA ?= 0x44000000 +WT_PSA_NS_WINDOW_SIZE ?= 0x00100000 +WT_QEMU_TEST_ENTROPY ?= $(WT_VERSAL_VIRT) +# The PLM configures the PS UARTs and CNTFRQ_EL0 before EL3 runs. +WT_UART_SKIP_INIT ?= 1 +WT_PORT_CNTFRQ_KEEP ?= 1 + +TARGET_CFLAGS := \ + -DWT_EL3_TEXT_BASE=$(WT_EL3_TEXT_BASE)u \ + -DWT_EL3_RAM_BASE=$(WT_EL3_RAM_BASE)u \ + -DWT_EL3_RAM_SIZE=$(WT_EL3_RAM_SIZE)u \ + -DWT_PORT_BOOT_CPUS=$(WT_PORT_BOOT_CPUS)u \ + -DWT_UART_SKIP_INIT=$(WT_UART_SKIP_INIT) \ + -DWT_PORT_CNTFRQ_KEEP=$(WT_PORT_CNTFRQ_KEEP) \ + -DWT_VERSAL_VIRT=$(WT_VERSAL_VIRT) \ + -DWT_NS_IMAGE_PA=$(WT_NS_IMAGE_PA)u \ + -DWT_PSA_NS_WINDOW_SIZE=$(WT_PSA_NS_WINDOW_SIZE)u \ + -DWT_QEMU_TEST_ENTROPY=$(WT_QEMU_TEST_ENTROPY) +# No boot loader runs ahead of the monitor under QEMU: synthesize the boot +# handoff record it would leave (emulator tests only, never production). +WT_EL3_TEST_HANDOFF ?= 0 +ifeq ($(WT_EL3_TEST_HANDOFF),1) +TARGET_CFLAGS += -DWT_EL3_TEST_HANDOFF=1 -DWT_PORT_HANDOFF_SIZE=64u +endif +# Arm FF-A ACS conformance image (its band base comes from the shared layout). +WT_FFA_ACS ?= 0 +ifeq ($(WT_FFA_ACS),1) +TARGET_CFLAGS += -DWT_FFA_ACS=1 +endif +TARGET_LDFLAGS := \ + -Wl,--defsym=WT_EL3_LOAD_LIMIT=$(WT_EL3_RAM_BASE) \ + -Wl,--defsym=WT_EL3_TEXT_BASE=$(WT_EL3_TEXT_BASE) \ + -Wl,--defsym=WT_EL3_RAM_BASE=$(WT_EL3_RAM_BASE) \ + -Wl,--defsym=WT_EL3_RAM_SIZE=$(WT_EL3_RAM_SIZE) +SECURE_LD := $(ROOT)/src/arch/aarch64/spm/wolftrust.ld + +PORT_COMMON_DIR := $(ROOT)/port/common/aarch64 +TARGET_PLATFORM_SRC := $(PORT_COMMON_DIR)/platform_qemu.c +TARGET_PARTITIONS_SRC := $(PORT_COMMON_DIR)/partitions.c +TARGET_EXTRA_SRCS := $(PORT_COMMON_DIR)/hsm_nvm.c $(PORT_COMMON_DIR)/rng_entropy.c +EL3_PORT_SRCS := $(PORT_DIR)/el3_board.c $(PORT_DIR)/uart.c diff --git a/port/common/aarch64/conf_backend.c b/port/common/aarch64/conf_backend.c new file mode 100644 index 00000000..e642f3ed --- /dev/null +++ b/port/common/aarch64/conf_backend.c @@ -0,0 +1,142 @@ +/* conf_backend.c + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfTrust. + * + * wolfTrust 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. + * + * wolfTrust 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, see . + */ + +/* Privileged backend for the AArch64 conformance image, called from the SVC + * gate (spm_gate_core.c) on behalf of the unprivileged DRIVER partition. The + * NVM store lives in a .noinit band so val's boot flag survives the system + * reset the panic tests trigger: QEMU keeps RAM across virt's machine reset and + * the runner keeps versal-virt's DDR across its power cycle, and neither the + * EL3 image copy, the loaders, nor the SPMC bss clear touches this band. The + * interrupt hook raises the DRIVER partition's Secure SPI through the GIC. */ + +#include "wolftrust/arch/aarch64/gic.h" +#include "wolftrust/platform.h" +#include "wolftrust/spm_transport.h" +#include "wolftrust/types.h" + +#include "psa_manifest/pid.h" +#include "conformance/conf_nvm.h" + +#include +#include +#include + +extern uint8_t _s_conf_server_data[]; +extern uint8_t _e_conf_server_data[]; +extern uint8_t _s_conf_client_data[]; +extern uint8_t _e_conf_client_data[]; +extern uint8_t _s_conf_driver_data[]; +extern uint8_t _e_conf_driver_data[]; + +#define WT_CONF_NVM_MAGIC 0x774E564Du /* "wNVM" */ +#define WT_CONF_UART_INTID 63u + +/* .noinit: not in the image (NOLOAD) and outside the SPMC bss-clear range, so + * its contents persist across a reset. */ +static struct { + uint32_t magic; + uint8_t store[WT_CONF_NVM_SIZE]; +} g_conf_nvm __attribute__((section(".noinit"))); + +static void wt_conf_nvm_prime(void) +{ + /* First power-on (cold boot) leaves the band cleared; present the + * flash-blank 0xFF state val expects until something is written. */ + if (g_conf_nvm.magic != WT_CONF_NVM_MAGIC) { + (void)memset(g_conf_nvm.store, 0xFF, sizeof(g_conf_nvm.store)); + g_conf_nvm.magic = WT_CONF_NVM_MAGIC; + } +} + +int wt_conf_nvm_flash_sync(uint8_t *buf, uint32_t len, int store) +{ + if (buf == NULL || len == 0u || len > sizeof(g_conf_nvm.store)) { + return -1; + } + wt_conf_nvm_prime(); + if (store == 0) { + (void)memcpy(buf, g_conf_nvm.store, (size_t)len); + } + else { + (void)memcpy(g_conf_nvm.store, buf, (size_t)len); + } + return 0; +} + +void wt_conf_uart_irq_set(int on) +{ + if (on != 0) { + wt_gic->set_pending(WT_CONF_UART_INTID); + } + else { + wt_gic->disable(WT_CONF_UART_INTID); + } +} + +/* Append one read-write segment to a test partition's grants; an empty + * segment or a full table grants nothing. */ +static size_t conf_grant(wt_memory_region_t* regions, size_t count, + size_t max, uintptr_t from, uintptr_t to) +{ + if (regions != NULL && count < max && to > from) { + regions[count].base = from; + regions[count].size = (uint32_t)(to - from); + regions[count].attributes = WT_MEM_ATTR_READ | WT_MEM_ATTR_WRITE; + count++; + } + return count; +} + +/* The band holds one page-aligned writable segment per test partition + * (wolftrust.ld) with the two pseudo-MMIO pages above them; each partition is + * granted its own segment and MMIO page alone, so none reaches another's + * state and the L3 isolation tests fault where the suite expects. The + * production services never touch this band. */ +size_t wt_platform_conf_sp_grants(int32_t partition_id, + wt_memory_region_t* regions, + size_t count, size_t max) +{ + uintptr_t base = (uintptr_t)WT_SPM_CONFDATA_PA; + + if (partition_id == SERVER_PARTITION_ID) { + count = conf_grant(regions, count, max, + (uintptr_t)_s_conf_server_data, + (uintptr_t)_e_conf_server_data); + count = conf_grant(regions, count, max, + base + WT_CONF_SERVER_MMIO_OFFSET, + base + WT_CONF_SERVER_MMIO_OFFSET + + WT_CONF_MMIO_HOLE_SIZE); + } + else if (partition_id == CLIENT_PARTITION_ID) { + count = conf_grant(regions, count, max, + (uintptr_t)_s_conf_client_data, + (uintptr_t)_e_conf_client_data); + } + else if (partition_id == DRIVER_PARTITION_ID) { + count = conf_grant(regions, count, max, + (uintptr_t)_s_conf_driver_data, + (uintptr_t)_e_conf_driver_data); + count = conf_grant(regions, count, max, + base + WT_CONF_DRIVER_MMIO_OFFSET, + base + WT_CONF_DRIVER_MMIO_OFFSET + + WT_CONF_MMIO_HOLE_SIZE); + } + return count; +} diff --git a/port/common/aarch64/conformance/conf_nvm.h b/port/common/aarch64/conformance/conf_nvm.h new file mode 100644 index 00000000..306f5766 --- /dev/null +++ b/port/common/aarch64/conformance/conf_nvm.h @@ -0,0 +1,50 @@ +/* conf_nvm.h + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfTrust. + * + * wolfTrust 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. + * + * wolfTrust 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, see . + */ + +#ifndef WOLFTRUST_STM32H563_CONF_NVM_H +#define WOLFTRUST_STM32H563_CONF_NVM_H + +#include + +/* Survive-reset NVM seam for the conformance DRIVER partition (P5 K2). The + * unprivileged PAL calls this to persist its shadow: store==0 loads the flash + * sector into buf, store!=0 writes buf back. Returns 0 on success. The target + * build issues an SVC to the privileged flash driver; a host test links its + * own flash-simulator implementation of this symbol instead. */ +int wt_conf_nvm_sync(uint8_t *buf, uint32_t len, int store); + +/* PAL interrupt source control (P4.2c): on!=0 drives the platform UART so its + * NVIC line fires into the DRIVER partition's manifest interrupt signal; on==0 + * quiesces the source. The unprivileged PAL traps to the privileged device + * poke via SVC; the interrupt itself is delivered through the real vector. */ +int wt_conf_irq_set(int on); + +/* Bytes of the DRIVER partition's NVMEM: the PAL shadow, the privileged + * store, and the NVMEM_0 range pal_config.h advertises. */ +#define WT_CONF_NVM_SIZE 0x100u + +/* Pseudo-MMIO pages the isolation tests probe, one per owning partition, + * carved out of the conformance data band above its data (pal_config.h and + * wt_platform_conf_sp_grants agree through these). */ +#define WT_CONF_SERVER_MMIO_OFFSET 0x1C000u +#define WT_CONF_DRIVER_MMIO_OFFSET 0x1D000u +#define WT_CONF_MMIO_HOLE_SIZE 0x1000u + +#endif /* WOLFTRUST_STM32H563_CONF_NVM_H */ diff --git a/port/common/aarch64/conformance/conf_nvm_sync.c b/port/common/aarch64/conformance/conf_nvm_sync.c new file mode 100644 index 00000000..da2dc493 --- /dev/null +++ b/port/common/aarch64/conformance/conf_nvm_sync.c @@ -0,0 +1,62 @@ +/* conf_nvm_sync.c + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfTrust. + * + * wolfTrust 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. + * + * wolfTrust 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, see . + */ + +/* Target side of the survive-reset NVM seam for the AArch64 conformance + * image: the unprivileged DRIVER partition traps its shadow buffer through the + * SVC gate so the privileged SPMC drives the persistent store. The privileged + * backend (wt_conf_nvm_flash_sync) keeps the shadow in a .noinit band that + * survives the EL3 warm reset the panic tests use. */ + +#include "conf_nvm.h" + +#include "wolftrust/spm_transport.h" +#include "wolftrust/spm_gate.h" + +#include + +int wt_conf_nvm_sync(uint8_t *buf, uint32_t len, int store) +{ + wt_spm_call_t call; + + (void)memset(&call, 0, sizeof(call)); + call.op = WT_SPM_OP_CONF_NVM_SYNC; + call.buffer = buf; + call.num_bytes = (size_t)len; + call.call_type = store; + + if (wt_spm_sp_call(&call) != WT_FFM_SUCCESS) { + return -1; + } + return call.ret_int; +} + +int wt_conf_irq_set(int on) +{ + wt_spm_call_t call; + + (void)memset(&call, 0, sizeof(call)); + call.op = WT_SPM_OP_CONF_IRQ_SET; + call.call_type = on; + + if (wt_spm_sp_call(&call) != WT_FFM_SUCCESS) { + return -1; + } + return call.ret_int; +} diff --git a/port/common/aarch64/conformance/pal_attestation_config.h b/port/common/aarch64/conformance/pal_attestation_config.h new file mode 100644 index 00000000..7c147f75 --- /dev/null +++ b/port/common/aarch64/conformance/pal_attestation_config.h @@ -0,0 +1,58 @@ +/* pal_attestation_config.h + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfTrust. + * + * wolfTrust 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. + * + * wolfTrust 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, see . + */ + +/* Attestation PAL configuration for the unmodified Arm psa-arch-tests + * dev_apis/initial_attestation suite. PLATFORM_OVERRIDE_ATTEST_PK stays + * undefined: the wolfTrust IAK is generated inside the wolfHSM vault per + * device, so the verify key must be fetched at runtime through + * tfm_initial_attest_get_public_key (conformance_pal.c) rather than + * baked in as upstream's well-known TF-M test key. */ + +#ifndef _PAL_ATTESTATION_CONFIG_H_ +#define _PAL_ATTESTATION_CONFIG_H_ + +#include +#include + +#define CRYPTO_VERSION_BETA3 + +#define COSE_ALGORITHM_ES256 -7 +#define COSE_ALG_SHA256_PROPRIETARY -72000 + +#define USEFUL_BUF_MAKE_STACK_UB UsefulBuf_MAKE_STACK_UB + +#define COSE_SIG_CONTEXT_STRING_SIGNATURE1 "Signature1" + +#define T_COSE_SIGN1_MAX_PROT_HEADER (1 + 1 + 5 + 9) + +#define T_COSE_SIZE_OF_TBS \ + (1 + sizeof(COSE_SIG_CONTEXT_STRING_SIGNATURE1) + 2 + \ + T_COSE_SIGN1_MAX_PROT_HEADER + 3) + +#define NULL_USEFUL_BUF_C NULLUsefulBufC + +#define ECC_CURVE_SECP256R1_PULBIC_KEY_LENGTH (1 + 2 * 32) + +int32_t tfm_initial_attest_get_public_key(uint8_t *public_key_buff, + size_t public_key_buf_size, + size_t *public_key_len, + psa_ecc_family_t *elliptic_family_type); + +#endif /* _PAL_ATTESTATION_CONFIG_H_ */ diff --git a/port/common/aarch64/conformance/pal_config.h b/port/common/aarch64/conformance/pal_config.h new file mode 100644 index 00000000..35ef5a6e --- /dev/null +++ b/port/common/aarch64/conformance/pal_config.h @@ -0,0 +1,122 @@ +/* pal_config.h + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfTrust. + * + * wolfTrust 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. + * + * wolfTrust 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, see . + */ + +/* Arm psa-arch-tests PAL target configuration for wolfTrust on the AArch64 + * QEMU virt / Versal targets. The DRIVER partition's NVMEM is a RAM shadow + * written through the SVC gate into a .noinit band (survives the warm reset the + * panic tests use); the UART/watchdog values name devices the driver plane + * uses once VERBOSITY routing lands. The per-partition MMIO holes are unmapped + * secure addresses the isolation tests probe expecting a fault. */ + +#ifndef _PAL_CONFIG_H_ +#define _PAL_CONFIG_H_ + +#include "conf_nvm.h" + +/* The val suite runs the same tests at every level; xlnx-versal-virt fences + * no Secure band, so its manifests declare profile 0 (docs/Porting.md) and + * the runner records the seven NS-fence tests it cannot pass. */ +#define PLATFORM_PSA_ISOLATION_LEVEL 3 + +#define UART_NUM 1 +#define UART_0_BASE 0x09000000 +#define UART_0_SIZE 0xFFF +#define UART_0_INTR_ID 0xFF +#define UART_0_PERMISSION TYPE_READ_WRITE + +#define WATCHDOG_NUM 1 +#define WATCHDOG_0_BASE 0x0E3F0000 +#define WATCHDOG_0_SIZE 0x3FF +#define WATCHDOG_0_INTR_ID 0xFF +#define WATCHDOG_0_PERMISSION TYPE_READ_WRITE +#define WATCHDOG_0_NUM_OF_TICK_PER_MICRO_SEC 0x3 +#define WATCHDOG_0_TIMEOUT_IN_MICRO_SEC_LOW 0xF4240 +#define WATCHDOG_0_TIMEOUT_IN_MICRO_SEC_MEDIUM 0x1E8480 +#define WATCHDOG_0_TIMEOUT_IN_MICRO_SEC_HIGH 0x4C4B40 +#define WATCHDOG_0_TIMEOUT_IN_MICRO_SEC_CRYPTO 0x1312D00 + +#define NVMEM_NUM 1 +#define NVMEM_0_START 0x0E3F1000 +#define NVMEM_0_END (NVMEM_0_START + WT_CONF_NVM_SIZE - 1u) +#define NVMEM_0_PERMISSION TYPE_READ_WRITE + +#define NSPE_MMIO_NUM 1 +#define NSPE_MMIO_0_START 0x44080000 +#define NSPE_MMIO_0_END 0x4408001F +#define NSPE_MMIO_0_PERMISSION TYPE_READ_WRITE + +/* Per-partition pseudo-MMIO holes near the top of the shared conformance data + * band, above its .data/.bss fill. The band is granted to the test partitions, + * so a partition reaches its own hole; the L3 isolation tests carve each hole + * out of every other partition's grant (a later slice) so a cross-partition + * poke faults. WT_SPM_CONFDATA_PA is a -D on the secure conformance build. */ +#define SERVER_PARTITION_MMIO_NUM 1 +#define SERVER_PARTITION_MMIO_0_START (WT_SPM_CONFDATA_PA + WT_CONF_SERVER_MMIO_OFFSET) +#define SERVER_PARTITION_MMIO_0_END (SERVER_PARTITION_MMIO_0_START + 0x100u) +#define SERVER_PARTITION_MMIO_0_PERMISSION TYPE_READ_WRITE + +#define DRIVER_PARTITION_MMIO_NUM 1 +#define DRIVER_PARTITION_MMIO_0_START (WT_SPM_CONFDATA_PA + WT_CONF_DRIVER_MMIO_OFFSET) +#define DRIVER_PARTITION_MMIO_0_END (DRIVER_PARTITION_MMIO_0_START + 0x100u) +#define DRIVER_PARTITION_MMIO_0_PERMISSION TYPE_READ_WRITE + +#define PLATFORM_WD_BASE WATCHDOG_0_BASE +#define PLATFORM_WD_NUM_OF_TICK_PER_MICRO_SEC WATCHDOG_0_NUM_OF_TICK_PER_MICRO_SEC +#define PLATFORM_WD_TIMEOUT_IN_MICRO_SEC_LOW WATCHDOG_0_TIMEOUT_IN_MICRO_SEC_LOW +#define PLATFORM_WD_TIMEOUT_IN_MICRO_SEC_MEDIUM WATCHDOG_0_TIMEOUT_IN_MICRO_SEC_MEDIUM +#define PLATFORM_WD_TIMEOUT_IN_MICRO_SEC_HIGH WATCHDOG_0_TIMEOUT_IN_MICRO_SEC_HIGH +#define PLATFORM_WD_TIMEOUT_IN_MICRO_SEC_CRYPTO WATCHDOG_0_TIMEOUT_IN_MICRO_SEC_CRYPTO + +#define PLATFORM_NVM_BASE NVMEM_0_START + +#define PLATFORM_NSPE_MMIO_START NSPE_MMIO_0_START +#define PLATFORM_SERVER_PARTITION_MMIO_START SERVER_PARTITION_MMIO_0_START +#define PLATFORM_DRIVER_PARTITION_MMIO_START DRIVER_PARTITION_MMIO_0_START +#define PLATFORM_DRIVER_PARTITION_MMIO_END DRIVER_PARTITION_MMIO_0_END + +#ifdef IPC +#include "psa/client.h" +#include "psa_manifest/sid.h" +#include "psa_manifest/pid.h" +#endif + +/* dev_apis Storage: the val NSPE and test TUs reach the PSA storage types and + * API version macros through this per-target config. */ +#if defined(STORAGE) || defined(INTERNAL_TRUSTED_STORAGE) || \ + defined(PROTECTED_STORAGE) +#include "psa/internal_trusted_storage.h" +#include "psa/protected_storage.h" +#define ARCH_TEST_STORAGE_UID_MAX_SIZE 512 +#endif + +/* dev_apis Crypto: wolfPSA is the guest's psa_* provider; the algorithm + * surface the suite may exercise lives in pal_crypto_config.h. */ +#if defined(CRYPTO) +#include "psa/crypto.h" +#include "pal_crypto_config.h" +#endif + +#if defined(INITIAL_ATTESTATION) +#include "psa/crypto.h" +#include "psa/initial_attestation.h" +#include "pal_attestation_config.h" +#endif + +#endif /* _PAL_CONFIG_H_ */ diff --git a/port/common/aarch64/conformance/pal_crypto_config.h b/port/common/aarch64/conformance/pal_crypto_config.h new file mode 100644 index 00000000..4ee59155 --- /dev/null +++ b/port/common/aarch64/conformance/pal_crypto_config.h @@ -0,0 +1,70 @@ +/** @file + * Copyright (c) 2019-2025, Arm Limited or its affiliates. All rights reserved. + * SPDX-License-Identifier : Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +**/ + +/* Crypto test configuration for the wolfTrust STM32H563 guest, derived from + * the Arm pal_crypto_config.h template. The enabled set mirrors exactly what + * the guest wolfPSA/wolfCrypt build compiles in (module/wolfhsm-client + * user_settings.h): SHA-256, HMAC, HKDF, PBKDF2, TLS-1.2 PRF, AES + * CBC/CTR/GCM/CCM, ECC P-256 ECDSA/ECDH incl. RFC 6979. Everything absent + * from that build (RSA, DES, ChaCha20, CMAC, SHA-1/224/384/512, PAKE) stays + * off so the suite skips those vectors instead of failing on honest + * NOT_SUPPORTED. */ + +#ifndef _PAL_CRYPTO_CONFIG_H_ +#define _PAL_CRYPTO_CONFIG_H_ + +#define ARCH_TEST_RAW + +#define ARCH_TEST_AES +#define ARCH_TEST_AES_128 +#define ARCH_TEST_AES_192 +#define ARCH_TEST_AES_256 +#define ARCH_TEST_AES_512 + +#define ARCH_TEST_CIPHER +#define ARCH_TEST_CIPHER_MODE_CTR +#define ARCH_TEST_CIPHER_MODE_CBC +#define ARCH_TEST_CTR_AES +#define ARCH_TEST_CBC_AES +#define ARCH_TEST_CBC_AES_NO_PADDING +#define ARCH_TEST_CBC_NO_PADDING +#define ARCH_TEST_CBC_PKCS7 + +#define ARCH_TEST_HASH +#define ARCH_TEST_SHA256 + +#define ARCH_TEST_CCM +#define ARCH_TEST_GCM + +#define ARCH_TEST_HMAC +#define ARCH_TEST_HKDF +#define ARCH_TEST_HKDF_EXTRACT +#define ARCH_TEST_HKDF_EXPAND +#define ARCH_TEST_PBKDF2 +#define ARCH_TEST_TLS12_PRF +#define ARCH_TEST_TLS12_PSK_TO_MS +#define ARCH_TEST_TRUNCATED_MAC + +#define ARCH_TEST_ECC +#define ARCH_TEST_ECC_CURVE_SECP256R1 +#define ARCH_TEST_ECDH +#define ARCH_TEST_ECDSA +#define ARCH_TEST_DETERMINISTIC_ECDSA + +#include "pal_crypto_config_check.h" + +#endif /* _PAL_CRYPTO_CONFIG_H_ */ diff --git a/port/common/aarch64/conformance/pal_driver_intf.c b/port/common/aarch64/conformance/pal_driver_intf.c new file mode 100644 index 00000000..d0457b29 --- /dev/null +++ b/port/common/aarch64/conformance/pal_driver_intf.c @@ -0,0 +1,159 @@ +/* pal_driver_intf.c + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfTrust. + * + * wolfTrust 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. + * + * wolfTrust 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, see . + */ + +/* SPE PAL for the Arm psa-arch-tests DRIVER partition on wolfTrust (P3a). + * NVMEM is a flash-backed store surviving an AIRCR reset (P5 K2): a RAM shadow + * loaded from the reserved flash sector at first use and written through on + * every write via wt_conf_nvm_sync (SVC to the privileged flash driver). The + * watchdog and interrupt hooks are no-ops until P4/P6 provide the real + * devices; prints are swallowed until the secure UART routing lands in P3b. + * All state lives in the driver partition's own CONFDATA/.bss window. */ + +#include "conf_nvm.h" + +#include +#include +#include + +typedef uintptr_t addr_t; + +static uint8_t g_drv_nvm[WT_CONF_NVM_SIZE]; +static uint8_t g_drv_stage[WT_CONF_NVM_SIZE]; +static uint8_t g_drv_nvm_ready; +static uint8_t g_drv_wd_enabled; + +/* A load that fails leaves the shadow unready: a read then fails instead of + * returning a fabricated blank sector, and the next access reloads. */ +static int wt_conf_drv_nvm_init(void) +{ + if (g_drv_nvm_ready == 0u) { + if (wt_conf_nvm_sync(g_drv_nvm, sizeof(g_drv_nvm), 0) != 0) { + return -1; + } + g_drv_nvm_ready = 1u; + } + return 0; +} + +#if defined(WT_CONF_NVM_HOST_TEST) +/* Host-test seam: force the next access to reload from the flash simulator, + * modelling what a post-reset boot does. Never compiled into the target. */ +void wt_conf_drv_nvm_test_reset(void) +{ + g_drv_nvm_ready = 0u; +} +#endif + +void pal_uart_init(uint32_t uart_base_addr) +{ + (void)uart_base_addr; +} + +void pal_print_s(const char* str, int32_t data) +{ + (void)str; + (void)data; +} + +int pal_print(uint8_t c) +{ + (void)c; + return 0; +} + +int pal_nvmem_write(addr_t base, uint32_t offset, void* buffer, int size) +{ + (void)base; + if (wt_conf_drv_nvm_init() != 0) { + return 0; + } + /* Wrap-safe: offset + size overflows size_t on a 32-bit target, so + * compare each side against the array bound without adding them. */ + if (buffer == NULL || size < 0 || + (size_t)offset > sizeof(g_drv_nvm) || + (size_t)size > sizeof(g_drv_nvm) - (size_t)offset) { + return 0; + } + /* Write through to flash first; the shadow takes the bytes only once + * they are persisted, so a failed store never reads back as committed. */ + (void)memcpy(g_drv_stage, g_drv_nvm, sizeof(g_drv_stage)); + (void)memcpy(&g_drv_stage[offset], buffer, (size_t)size); + if (wt_conf_nvm_sync(g_drv_stage, sizeof(g_drv_stage), 1) != 0) { + return 0; + } + (void)memcpy(g_drv_nvm, g_drv_stage, sizeof(g_drv_nvm)); + return 1; +} + +int pal_nvmem_read(addr_t base, uint32_t offset, void* buffer, int size) +{ + (void)base; + if (wt_conf_drv_nvm_init() != 0) { + return 0; + } + /* Wrap-safe: offset + size overflows size_t on a 32-bit target, so + * compare each side against the array bound without adding them. */ + if (buffer == NULL || size < 0 || + (size_t)offset > sizeof(g_drv_nvm) || + (size_t)size > sizeof(g_drv_nvm) - (size_t)offset) { + return 0; + } + (void)memcpy(buffer, &g_drv_nvm[offset], (size_t)size); + return 1; +} + +int pal_wd_timer_init(addr_t base_addr, uint32_t time_us, + uint32_t timer_tick_us) +{ + (void)base_addr; + (void)time_us; + (void)timer_tick_us; + return 0; +} + +int pal_wd_timer_enable(addr_t base_addr) +{ + (void)base_addr; + g_drv_wd_enabled = 1u; + return 0; +} + +int pal_wd_timer_disable(addr_t base_addr) +{ + (void)base_addr; + g_drv_wd_enabled = 0u; + return 0; +} + +int pal_wd_timer_is_enabled(addr_t base_addr) +{ + (void)base_addr; + return (int)g_drv_wd_enabled; +} + +void pal_generate_interrupt(void) +{ + (void)wt_conf_irq_set(1); +} + +void pal_disable_interrupt(void) +{ + (void)wt_conf_irq_set(0); +} diff --git a/port/common/aarch64/hsm_nvm.c b/port/common/aarch64/hsm_nvm.c new file mode 100644 index 00000000..b69daa8c --- /dev/null +++ b/port/common/aarch64/hsm_nvm.c @@ -0,0 +1,126 @@ +/* hsm_nvm.c + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfTrust. + * + * wolfTrust 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. + * + * wolfTrust 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, see . + */ + +/* wolfHSM NVM provider (WT-PORT-0003) and firmware-update staging for the + * QEMU machines: RAM-backed until a flash controller model exists. The + * vault therefore does not survive a reset on these targets. */ + +#include "wolftrust/port_nvm.h" +#include "wolftrust/services/fwu_service.h" +#include "wolfhsm/wh_flash_ramsim.h" + +#include +#include +#include + +#define WT_NVM_SIZE 0x00010000u +#define WT_NVM_SECTOR_SIZE 0x00001000u +#define WT_NVM_PAGE_SIZE 16u +#define WT_FWU_STAGE_SIZE 0x00010000u + +static uint8_t g_nvm_memory[WT_NVM_SIZE] __attribute__((aligned(16))); +static whFlashRamsimCtx g_nvm_ctx; +static const whFlashRamsimCfg g_nvm_cfg = { + .memory = g_nvm_memory, + .size = WT_NVM_SIZE, + .sectorSize = WT_NVM_SECTOR_SIZE, + .pageSize = WT_NVM_PAGE_SIZE, + .erasedByte = 0xFFu, + .initData = NULL, +}; + +const whFlashCb g_wt_hsm_flash_cb = WH_FLASH_RAMSIM_CB; + +void *wt_hsm_flash_context(void) +{ + return &g_nvm_ctx; +} + +const void *wt_hsm_flash_config(void) +{ + return &g_nvm_cfg; +} + +int wt_hsm_flash_format(void) +{ + (void)memset(g_nvm_memory, 0xFF, sizeof(g_nvm_memory)); + return 0; +} + +static uint8_t g_fwu_stage[WT_FWU_STAGE_SIZE] __attribute__((aligned(16))); +static uint32_t g_fwu_staged; + +static int wt_fwu_backend_begin(void* ctx) +{ + (void)ctx; + (void)memset(g_fwu_stage, 0xFF, sizeof(g_fwu_stage)); + g_fwu_staged = 0u; + return 0; +} + +static int wt_fwu_backend_write(void* ctx, uint32_t offset, + const uint8_t* data, uint32_t size) +{ + (void)ctx; + if (data == NULL || size == 0u || offset > WT_FWU_STAGE_SIZE || + size > WT_FWU_STAGE_SIZE - offset) { + return -1; + } + (void)memcpy(&g_fwu_stage[offset], data, size); + if (offset + size > g_fwu_staged) { + g_fwu_staged = offset + size; + } + return 0; +} + +static int wt_fwu_backend_arm(void* ctx, uint32_t image_size, + uint32_t version) +{ + (void)ctx; + (void)image_size; + (void)version; + return -1; +} + +static int wt_fwu_backend_disarm(void* ctx) +{ + (void)ctx; + return 0; +} + +/* No boot loader consumes the staged image on these machines yet. */ +static int wt_fwu_backend_verify(void* ctx, uint32_t staged_size, + uint32_t* header_version) +{ + (void)ctx; + (void)staged_size; + (void)header_version; + return -1; +} + +const wt_fwu_backend_t wt_fwu_flash_backend = { + .begin = wt_fwu_backend_begin, + .write = wt_fwu_backend_write, + .arm = wt_fwu_backend_arm, + .disarm = wt_fwu_backend_disarm, + .capacity = WT_FWU_STAGE_SIZE, + .align = 16u, + .verify = wt_fwu_backend_verify, +}; diff --git a/port/common/aarch64/l3_layout.h b/port/common/aarch64/l3_layout.h new file mode 100644 index 00000000..a90f8519 --- /dev/null +++ b/port/common/aarch64/l3_layout.h @@ -0,0 +1,104 @@ +/* l3_layout.h + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfTrust. + * + * wolfTrust 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. + * + * wolfTrust 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, see . + */ + +/* Isolation level 3 Secure band layout shared by every AArch64 port. A port's + * memory_map.h defines WT_L3_BAND_BASE (1 MiB aligned) and WT_RAM_S_BASE, + * then includes this; tools/aarch64_l3_layout.py hands the build the same + * values. */ + +#ifndef WOLFTRUST_PORT_AARCH64_L3_LAYOUT_H +#define WOLFTRUST_PORT_AARCH64_L3_LAYOUT_H + +/* Guests and host tests include the port map without the build's level. */ +#if defined(WT_ISOLATION_LEVEL) && (WT_ISOLATION_LEVEL != 3) +#error "the shared AArch64 layout implements isolation level 3 only" +#endif +#if !defined(WT_L3_BAND_BASE) || !defined(WT_RAM_S_BASE) +#error "define WT_L3_BAND_BASE and WT_RAM_S_BASE before including l3_layout.h" +#endif + +/* The FF-A boot information page opens Secure RAM; the table pool follows. */ +#ifndef WT_SPM_BOOT_INFO_PA +#define WT_SPM_BOOT_INFO_PA WT_RAM_S_BASE +#endif +#ifndef WT_SPM_TABLE_POOL_PA +#define WT_SPM_TABLE_POOL_PA (WT_RAM_S_BASE + 0x00001000u) +#endif +#ifndef WT_SPM_IMAGE_PA +#define WT_SPM_IMAGE_PA (WT_L3_BAND_BASE + 0x00100000u) +#endif +#ifndef WT_SPM_IMAGE_SIZE +#define WT_SPM_IMAGE_SIZE 0x00100000u +#endif +#ifndef WT_SPM_RAM_PA +#define WT_SPM_RAM_PA (WT_L3_BAND_BASE + 0x00200000u) +#endif +#ifndef WT_SPM_RAM_SIZE +#define WT_SPM_RAM_SIZE 0x00040000u +#endif +/* Where a boot loader leaves its handoff record (the emulator synthesizes it). */ +#define WT_L3_HANDOFF_PA (WT_L3_BAND_BASE + 0x00240000u) +#ifndef WT_SPM_CONFDATA_PA +#define WT_SPM_CONFDATA_PA (WT_L3_BAND_BASE + 0x002C0000u) +#endif +#ifndef WT_SPM_CONFDATA_SIZE +#define WT_SPM_CONFDATA_SIZE 0x00020000u +#endif + +/* Keystore window: the vault, attestation and crypto partitions each own one + * private band in it, and the port manifests grant exactly these. */ +#ifndef WT_SPM_KEYSTORE_PA +#define WT_SPM_KEYSTORE_PA (WT_L3_BAND_BASE + 0x00300000u) +#endif +#ifndef WT_SPM_KEYSTORE_SIZE +#define WT_SPM_KEYSTORE_SIZE 0x00040000u +#endif +#define WT_SPM_VAULT_PA WT_SPM_KEYSTORE_PA +#define WT_SPM_VAULT_SIZE 0x00024000u +#define WT_SPM_ATTEST_PA (WT_SPM_VAULT_PA + WT_SPM_VAULT_SIZE) +#define WT_SPM_ATTEST_SIZE 0x00001000u +#define WT_SPM_HSMDATA_PA (WT_SPM_ATTEST_PA + WT_SPM_ATTEST_SIZE) +#define WT_SPM_HSMDATA_SIZE 0x0001B000u + +#ifndef WT_SPM_RXTX_PA +#define WT_SPM_RXTX_PA (WT_L3_BAND_BASE + 0x00340000u) +#endif +#ifndef WT_SPM_RXTX_SIZE +#define WT_SPM_RXTX_SIZE 0x00002000u +#endif +/* One page the SPMC shares to a partition through FFA_MEM_SHARE at boot. */ +#ifndef WT_SPM_SHARE_PA +#define WT_SPM_SHARE_PA (WT_L3_BAND_BASE + 0x00342000u) +#endif +#ifndef WT_SPM_SHARE_SIZE +#define WT_SPM_SHARE_SIZE 0x00001000u +#endif +/* Arm FF-A ACS conformance images: SP1..SP4 load into 1 MB bands from here. */ +#define WT_L3_FFA_ACS_PA (WT_L3_BAND_BASE + 0x00400000u) + +#if (WT_SPM_HSMDATA_PA + WT_SPM_HSMDATA_SIZE) != \ + (WT_SPM_KEYSTORE_PA + WT_SPM_KEYSTORE_SIZE) +#error "the keystore bands must tile the keystore window exactly" +#endif +#if (WT_SPM_KEYSTORE_PA + WT_SPM_KEYSTORE_SIZE) > WT_SPM_RXTX_PA +#error "the keystore window overlaps the RX/TX band" +#endif + +#endif /* WOLFTRUST_PORT_AARCH64_L3_LAYOUT_H */ diff --git a/port/common/aarch64/partitions.c b/port/common/aarch64/partitions.c new file mode 100644 index 00000000..10655e53 --- /dev/null +++ b/port/common/aarch64/partitions.c @@ -0,0 +1,253 @@ +/* partitions.c + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfTrust. + * + * wolfTrust 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. + * + * wolfTrust 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, see . + */ + +/* Non-secure endpoint tables for the QEMU-hosted AArch64 ports. The guest + * domains come from the generated manifest; nothing launches them until the + * NS gateway lands, and launch verification fails closed meanwhile. */ + +#include "wolftrust/arch/aarch64/context.h" +#include "wolftrust/arch/aarch64/el3.h" +#include "wolftrust/guest_verify.h" +#include "wolftrust/partition.h" +#include "memory_map.h" + +#include + +#ifndef WT_TIMESLICE_MS +#define WT_TIMESLICE_MS 2U +#endif + +/* No Normal world yet: the NS gateway (B3) enforces guest domains and + * turns these endpoints on; until then the port offers none, so the core + * schedules only Secure Partitions and idles on FF-A. */ +#define WT_PORT_NS_GUESTS 0u +/* An object, not the macro: gcc -Wtype-limits rejects the loop test i < 0u. */ +static const size_t g_port_ns_guests = WT_PORT_NS_GUESTS; + +static wt_guest_config_t g_partition_configs[WT_MAX_GUESTS]; +static wt_guest_runtime_t g_partition_runtime[WT_MAX_GUESTS]; +static wt_guest_context_t g_partition_contexts[WT_MAX_GUESTS]; +static uintptr_t g_bound_exec_bases[WT_MAX_GUESTS]; +static size_t g_bound_exec_sizes[WT_MAX_GUESTS]; + +/* Security-state isolation, and with it every isolation level, needs a bus + * fence between the Secure bands and the Normal world. */ +#if defined(WT_PORT_NS_MEMORY_FENCE) && (WT_PORT_NS_MEMORY_FENCE == 1) +#define WT_PORT_SECURITY_STATE_CAPABILITY WT_CAPABILITY_SECURITY_STATE +#else +#define WT_PORT_SECURITY_STATE_CAPABILITY 0U +#endif + +static const wt_profile_capabilities_t g_profile_capabilities = { + .capabilities = WT_PORT_SECURITY_STATE_CAPABILITY | + WT_CAPABILITY_PRIVILEGE_STATE | + WT_CAPABILITY_ROT_ISOLATION | + WT_CAPABILITY_DOMAIN_ISOLATION | + WT_CAPABILITY_MEMORY_PROTECTION | + WT_CAPABILITY_INTERRUPT_ISOLATION | + WT_CAPABILITY_RESTART, + .max_domains = 11U, + .max_memory_resources_per_domain = 3U, + .max_interrupts_per_domain = 1U, +}; + +const wt_guest_measurement_t* wt_platform_guest_measurements(size_t* count) +{ + if (count != NULL) { + *count = 0u; + } + return NULL; +} + +static void wt_partitions_wire(void) +{ + size_t i; + + for (i = 0u; i < WT_MAX_GUESTS; ++i) { + g_partition_configs[i].guest_id = (wt_guest_id_t)i; + g_partition_configs[i].name[0] = 'g'; + g_partition_configs[i].name[1] = (char)('0' + i); + g_partition_configs[i].name[2] = '\0'; + g_partition_configs[i].timeslice_ms = WT_TIMESLICE_MS; + g_partition_configs[i].initial_state = WT_GUEST_STOPPED; + g_partition_runtime[i].context = &g_partition_contexts[i]; + } +} + +const wt_guest_config_t* wt_partitions_config_table(size_t* count) +{ + wt_partitions_wire(); + if (count != NULL) { + *count = WT_PORT_NS_GUESTS; + } + return g_partition_configs; +} + +wt_guest_runtime_t* wt_partitions_runtime_table(size_t* count) +{ + wt_partitions_wire(); + if (count != NULL) { + *count = WT_PORT_NS_GUESTS; + } + return g_partition_runtime; +} + +const wt_profile_capabilities_t* wt_partitions_profile_capabilities(void) +{ + return &g_profile_capabilities; +} + +static const wt_domain_descriptor_t* wt_partition_manifest_domain( + const wt_system_manifest_t* manifest, wt_domain_id_t id) +{ + size_t i; + + for (i = 0U; i < manifest->domain_count; ++i) { + if (manifest->domains[i].id == id) { + return &manifest->domains[i]; + } + } + return NULL; +} + +static int bind_fail(const char* why) +{ + wt_el3_puts("[SPM] guest bind failed: "); + wt_el3_puts(why); + wt_el3_puts("\r\n"); + return -1; +} + +int wt_partitions_bind_manifest(const wt_system_manifest_t* manifest) +{ + size_t i; + size_t resource; + size_t window_count; + size_t region_count; + + if (manifest == NULL || manifest->domains == NULL) { + return bind_fail("manifest"); + } + wt_partitions_wire(); + + for (i = 0U; i < g_port_ns_guests; ++i) { + wt_guest_config_t* config = &g_partition_configs[i]; + const wt_domain_descriptor_t* domain; + + /* Domain zero is the SPM; guest n is domain n + 1. */ + domain = wt_partition_manifest_domain(manifest, + (wt_domain_id_t)config->guest_id + 1U); + if (domain == NULL || + domain->domain_class != WT_DOMAIN_CLASS_NONSECURE_APPLICATION || + domain->security_state != WT_SECURITY_STATE_NONSECURE || + domain->privilege_state != WT_PRIVILEGE_STATE_PRIVILEGED || + domain->restart_policy.action != WT_RESTART_ACTION_DOMAIN || + domain->entry_point == 0U || + domain->memory_resource_count > WT_MAX_MEMORY_REGIONS) { + return bind_fail("guest domain shape"); + } + if (wt_partition_validate_port_binding(config, domain) != + WT_PORT_VALID) { + return bind_fail("port binding"); + } + + config->restart_policy.restart_limit = + domain->restart_policy.restart_limit; + config->restart_policy.restart_window_ticks = + domain->restart_policy.restart_window_ticks; + config->restart_policy.initial_delay_ticks = + domain->restart_policy.initial_delay_ticks; + config->initial_state = (wt_guest_state_t)domain->initial_lifecycle; + config->launch_required = domain->launch_required; + config->launch_min_version = domain->launch_min_version; + config->vector_table = domain->entry_point; + + window_count = 0U; + region_count = 0U; + g_bound_exec_bases[i] = 0U; + g_bound_exec_sizes[i] = 0U; + for (resource = 0U; resource < domain->memory_resource_count; + ++resource) { + const wt_memory_resource_t* r = &domain->memory_resources[resource]; + + if ((r->attributes & WT_MEM_ATTR_DEVICE) == 0U) { + if (window_count >= WT_MAX_MEMORY_WINDOWS) { + return -1; + } + config->memory_windows[window_count] = *r; + window_count++; + } + config->memory_regions[region_count].base = r->base; + config->memory_regions[region_count].size = r->size; + config->memory_regions[region_count].attributes = r->attributes & + (WT_MEM_ATTR_READ | WT_MEM_ATTR_WRITE | WT_MEM_ATTR_EXEC | + WT_MEM_ATTR_DEVICE); + region_count++; + if ((r->attributes & WT_MEM_ATTR_EXEC) != 0U && + g_bound_exec_sizes[i] == 0U) { + g_bound_exec_bases[i] = r->base; + g_bound_exec_sizes[i] = r->size; + } + } + if (g_bound_exec_sizes[i] == 0U) { + return -1; + } + config->memory_window_count = window_count; + config->memory_region_count = region_count; + (void)memset(&config->irq_mask, 0, sizeof(config->irq_mask)); + if (domain->stack_base > (uintptr_t)-1 - domain->stack_size) { + return -1; + } + config->initial_msp_ns = domain->stack_base + domain->stack_size; + } + return 0; +} + +void wt_partition_reset_runtime(const wt_guest_config_t* config, + wt_guest_runtime_t* runtime) +{ + uint32_t restart_count; + uint32_t first_restart_tick; + + if (config == NULL || runtime == NULL) { + return; + } + restart_count = runtime->restart_count; + first_restart_tick = runtime->first_restart_tick; + (void)memset(runtime, 0, sizeof(*runtime)); + runtime->context = + &g_partition_contexts[(size_t)(runtime - g_partition_runtime)]; + (void)memset(runtime->context, 0, sizeof(*runtime->context)); + runtime->restart_count = restart_count; + runtime->first_restart_tick = first_restart_tick; + runtime->state = config->initial_state; + runtime->context->sp_el0 = config->initial_msp_ns; + runtime->context->pc = config->vector_table; + if (config->guest_id >= WT_MAX_GUESTS || + g_bound_exec_sizes[config->guest_id] == 0U || + runtime->context->pc < g_bound_exec_bases[config->guest_id] || + runtime->context->pc >= g_bound_exec_bases[config->guest_id] + + g_bound_exec_sizes[config->guest_id]) { + runtime->context->pc = 0U; + return; + } + runtime->context->elr = runtime->context->pc; + runtime->context->frame_stacked = false; +} diff --git a/port/common/aarch64/platform_l3.c b/port/common/aarch64/platform_l3.c new file mode 100644 index 00000000..b0603e6d --- /dev/null +++ b/port/common/aarch64/platform_l3.c @@ -0,0 +1,124 @@ +/* platform_l3.c + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfTrust. + * + * wolfTrust 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. + * + * wolfTrust 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, see . + */ + +/* Isolation level 3 platform hooks shared by every AArch64 port. The bands + * come from l3_layout.h through the port's memory_map.h; the port's l3_port.h + * names the one board input, a Secure peripheral only the SPM drives. */ + +#include "wolftrust/platform.h" +#include "wolftrust/priv_stack.h" +#include "memory_map.h" +#include "l3_port.h" + +#include +#include + +extern uint8_t _e_secure_text[]; +extern uint8_t _e_secure_rodata[]; + +/* The code every partition executes: the SPMC image text (RX) and its + * constant data (RO), shaped exactly like the SPMC's shareable fill entries + * so the partition mapping replaces them (the manifest's executable + * resource is policy only; the scheduler maps code from here). The load + * images of initialized data that follow the constant data stay EL1-only. */ +size_t wt_platform_sp_shared_regions(wt_memory_region_t* regions, size_t max) +{ + uintptr_t text_end = (uintptr_t)_e_secure_text; + uintptr_t rodata_end = (uintptr_t)_e_secure_rodata; + + if (regions == NULL || max < 2u) { + return 0u; + } + regions[0].base = (uintptr_t)WT_SPM_IMAGE_PA; + regions[0].size = text_end - (uintptr_t)WT_SPM_IMAGE_PA; + regions[0].attributes = WT_MEM_ATTR_READ | WT_MEM_ATTR_EXEC; + regions[1].base = text_end; + regions[1].size = rodata_end - text_end; + regions[1].attributes = WT_MEM_ATTR_READ; + return 2u; +} + +/* No peripheral is a partition's to own yet: every DEVICE resource is refused. */ +const struct wt_periph* wt_platform_sp_peripherals(size_t* count) +{ + if (count != NULL) { + *count = 0U; + } + return NULL; +} + +/* SPM RAM (the SPMC's data, bss and stacks) stays EL1-only. */ +size_t wt_platform_spm_private_regions(wt_memory_region_t* regions, + size_t max) +{ + if (regions == NULL || max < 1u) { + return 0u; + } + regions[0].base = (uintptr_t)WT_SPM_RAM_PA; + regions[0].size = WT_SPM_RAM_SIZE; + regions[0].attributes = WT_MEM_ATTR_READ | WT_MEM_ATTR_WRITE; + return 1u; +} + +int wt_platform_priv_stack_ok(const void *stack, size_t size) +{ + if (stack == NULL) { + return 0; + } + return wt_priv_stack_ok((uintptr_t)stack, size, (uintptr_t)WT_SPM_RAM_PA, + (uintptr_t)WT_SPM_RAM_PA + WT_SPM_RAM_SIZE, + NULL, 0u); +} + +#if defined(WT_CONFORMANCE) && (WT_CONFORMANCE == 1) +size_t wt_platform_conf_shared_regions(wt_memory_region_t* regions, + size_t max) +{ + if (regions == NULL || max < 1u) { + return 0u; + } + regions[0].base = (uintptr_t)WT_SPM_CONFDATA_PA; + regions[0].size = WT_SPM_CONFDATA_SIZE; + regions[0].attributes = WT_MEM_ATTR_READ | WT_MEM_ATTR_WRITE; + return 1u; +} +#endif + +#if (defined(WT_FFM_NEGATIVE_PROBE) && (WT_FFM_NEGATIVE_PROBE == 1)) || \ + (defined(WT_KEYSTORE_NEG_PROBE) && (WT_KEYSTORE_NEG_PROBE == 1)) || \ + (defined(WT_BAND_NEG_PROBE) && (WT_BAND_NEG_PROBE != 0)) || \ + (defined(WT_PERIPH_SP_NEG_PROBE) && (WT_PERIPH_SP_NEG_PROBE == 1)) || \ + (defined(WT_MANIFEST_NEG_PROBE) && (WT_MANIFEST_NEG_PROBE == 3)) +uintptr_t wt_platform_probe_address(unsigned int target) +{ + switch (target) { + case WT_PROBE_VAULT_DATA_BAND: + return (uintptr_t)WT_SPM_VAULT_PA; + case WT_PROBE_ATTEST_DATA_BAND: + return (uintptr_t)WT_SPM_ATTEST_PA; + case WT_PROBE_HSM_DATA_BAND: + return (uintptr_t)WT_SPM_HSMDATA_PA; + case WT_PROBE_SPM_PERIPHERAL: + return (uintptr_t)WT_L3_SPM_PERIPHERAL_BASE; + default: + return (uintptr_t)WT_SPM_RAM_PA; + } +} +#endif diff --git a/port/common/aarch64/platform_qemu.c b/port/common/aarch64/platform_qemu.c new file mode 100644 index 00000000..e3f59689 --- /dev/null +++ b/port/common/aarch64/platform_qemu.c @@ -0,0 +1,219 @@ +/* platform_qemu.c + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfTrust. + * + * wolfTrust 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. + * + * wolfTrust 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, see . + */ + +/* wt_platform_* operations shared by the QEMU-hosted AArch64 ports (virt + * and versal-virt): the board is already initialized by the EL3 monitor, + * so the SPMC side only reports, resets, and describes its image. */ + +#include "wolftrust/arch/aarch64/el3.h" +#include "wolftrust/arch/aarch64/ffa.h" +#include "wolftrust/arch/aarch64/ffa_abi.h" +#include "wolftrust/arch/aarch64/monitor_abi.h" +#include "wolftrust/arch/aarch64/spm_svc.h" +#include "wolftrust/arch/aarch64/tables.h" +#include "wolftrust/ffm_gateway.h" +#include "wolftrust/platform.h" +#include "memory_map.h" + +#include +#include + +/* Install the NS memory checks the FF-M gateway consults for every + * Normal-world vector (the port seam the Armv8-M port installs the same way). */ +void wt_platform_init(void) +{ + wt_ffm_gateway_install(); +} + +/* No TrustZone address-space filter on these machines (WT-PORT-0008: the + * port does not claim the capability, so nothing consults this). */ +void wt_platform_program_memory_windows(const wt_memory_window_t* windows, + size_t count) +{ + (void)windows; + (void)count; +} + +void wt_platform_log_fault(wt_guest_id_t guest_id, wt_fault_reason_t reason, + uintptr_t fault_address, uintptr_t pc) +{ + wt_el3_puts("[SPM] fault guest="); + wt_el3_putdec(guest_id); + wt_el3_puts(" reason="); + wt_el3_putdec((uint64_t)reason); + wt_el3_puts(" addr=0x"); + wt_el3_puthex(fault_address, 16u); + wt_el3_puts(" pc=0x"); + wt_el3_puthex(pc, 16u); + wt_el3_puts("\r\n"); +} + +int wt_platform_guest_flash_wrp_ok(uintptr_t window_base, size_t window_size) +{ + (void)window_base; + (void)window_size; + return 0; +} + +/* No Normal world to run: the SPMC waits for FF-A events instead. */ +void wt_platform_all_guests_faulted(void) +{ + static uint32_t entered; + + /* The first call never returns, so a re-entry is a recovery escalation. */ + if (entered != 0u) { + wt_el3_puts("[SPM] restart budget exhausted, failing closed\r\n"); + wt_platform_console_flush(); + (void)wt_mon_call(WT_MON_FID_PANIC, 0x7Du); + for (;;) { + __asm__ volatile("wfi"); + } + } + entered = 1u; + wt_spm_init_partitions(); + wt_el3_puts("[SPM] partitions ready n="); + wt_el3_putdec(wt_spm_sp_init_count()); + wt_el3_puts("\r\n[SPM] no runnable guest, waiting for FF-A events\r\n"); + wt_spm_idle(); +} + +void wt_platform_panic(void) +{ + wt_el3_puts("[SPM] panic from 0x"); + wt_el3_puthex((uint64_t)(uintptr_t)__builtin_return_address(0), 16u); + wt_el3_puts("\r\n"); + wt_platform_console_flush(); + (void)wt_mon_call(WT_MON_FID_PANIC, 0xF2u); + for (;;) { + __asm__ volatile("wfi"); + } +} + +void wt_platform_system_reset(void) +{ + wt_platform_console_flush(); + (void)wt_mon_call(WT_MON_FID_SYSTEM_RESET, 0u); + for (;;) { + __asm__ volatile("wfi"); + } +} + +#ifdef WT_ENGINE_HSM +bool wt_platform_secure_service_active(void) +{ + return false; +} + +void wt_platform_note_hsm_wait_skip(wt_guest_id_t guest_id) +{ + (void)guest_id; +} +#endif + +/* The wolfBoot handoff record arrives through the FF-A boot information + * blob; none is placed yet, so the region is empty and fails closed. */ +volatile void* wt_platform_boot_handoff_region(size_t* size) +{ + if (size != NULL) { + *size = (g_wt_spm_handoff_pa != 0u) ? g_wt_spm_handoff_size : 0u; + } + return (volatile void*)g_wt_spm_handoff_pa; +} + +#if defined(WT_FFA_ACS) && (WT_FFA_ACS == 1) +/* The Arm FF-A ACS endpoints SP1..SP4 (conformance images only): each owns a + * 1 MB band the runner loads its image into, entered 0x4000 in; SP1 also owns + * the suite's 64 KB test NVM and its read-only test page. The UUIDs are the + * pinned suite's, stored as the little-endian bytes of its four words. */ +#define WT_ACS_BAND_SIZE 0x00100000u +#define WT_ACS_ENTRY_OFFSET 0x00004000u +#define WT_ACS_STACK_SIZE 0x00001000u +#define WT_ACS_NVM_OFFSET 0x00400000u +#define WT_ACS_NVM_SIZE 0x00010000u +#define WT_ACS_RO_OFFSET 0x00410000u +/* The first two test partitions receive indirect messages, the last two do + * not: the suite expects DENIED when one is sent to a non-receiver. */ +#define WT_ACS_PROPERTIES 0x0000070Fu +#define WT_ACS_PROPERTIES_NO_INDIRECT 0x0000070Bu +/* The suite's trusted watchdog (its PLATFORM_TWDOG_INTID): any test partition + * may be the one to claim it, and none may claim another interrupt. */ +#define WT_ACS_TWDOG_INTID 56u +#define WT_ACS_INTIDS { WT_ACS_TWDOG_INTID }, 1u + +#define WT_ACS_BAND(n) ((uintptr_t)WT_FFA_ACS_BASE + ((n) * WT_ACS_BAND_SIZE)) +/* The image marks its own data and stack RW with FFA_MEM_PERM_SET at init. */ +#define WT_ACS_IMAGE(n) { \ + WT_ACS_BAND(n), WT_ACS_BAND_SIZE, WT_MEM_ATTR_READ | WT_MEM_ATTR_EXEC } +#define WT_ACS_UUID(a, b, c, d) { \ + (uint8_t)(a), (uint8_t)((a) >> 8), (uint8_t)((a) >> 16), (uint8_t)((a) >> 24), \ + (uint8_t)(b), (uint8_t)((b) >> 8), (uint8_t)((b) >> 16), (uint8_t)((b) >> 24), \ + (uint8_t)(c), (uint8_t)((c) >> 8), (uint8_t)((c) >> 16), (uint8_t)((c) >> 24), \ + (uint8_t)(d), (uint8_t)((d) >> 8), (uint8_t)((d) >> 16), (uint8_t)((d) >> 24) } +#define WT_ACS_ENTRY(n) (WT_ACS_BAND(n) + WT_ACS_ENTRY_OFFSET) +#define WT_ACS_STACK(n) (WT_ACS_BAND(n) + WT_ACS_BAND_SIZE - WT_ACS_STACK_SIZE) + +static const wt_ffa_native_sp_t g_acs_partitions[] = { + { + WT_ACS_ENTRY(0u), WT_ACS_STACK(0u), WT_ACS_STACK_SIZE, + { + WT_ACS_IMAGE(0u), + { (uintptr_t)WT_FFA_ACS_BASE + WT_ACS_NVM_OFFSET, WT_ACS_NVM_SIZE, + WT_MEM_ATTR_READ | WT_MEM_ATTR_WRITE }, + { (uintptr_t)WT_FFA_ACS_BASE + WT_ACS_RO_OFFSET, WT_TABLES_PAGE_SIZE, + WT_MEM_ATTR_READ } + }, + 3u, + WT_ACS_UUID(0x1e67b5b4u, 0xe14f904au, 0x13fb1fb8u, 0xcbdae1dau), + WT_ACS_PROPERTIES, + 2u, /* SP1: Non-secure interrupts are signaled (preempt). */ + WT_ACS_INTIDS + }, + { + WT_ACS_ENTRY(1u), WT_ACS_STACK(1u), WT_ACS_STACK_SIZE, + { WT_ACS_IMAGE(1u) }, 1u, + WT_ACS_UUID(0x092358d1u, 0xb94723f0u, 0x64447c82u, 0xc88f57f5u), + WT_ACS_PROPERTIES, + 2u, /* SP2: signaled. */ + WT_ACS_INTIDS + }, + { + WT_ACS_ENTRY(2u), WT_ACS_STACK(2u), WT_ACS_STACK_SIZE, + { WT_ACS_IMAGE(2u) }, 1u, + WT_ACS_UUID(0x735cb579u, 0xb9448c1du, 0xe1619385u, 0xd2d80a77u), + WT_ACS_PROPERTIES_NO_INDIRECT, + 0u, /* SP3: Non-secure interrupts are queued. */ + WT_ACS_INTIDS + }, + { + WT_ACS_ENTRY(3u), WT_ACS_STACK(3u), WT_ACS_STACK_SIZE, + { WT_ACS_IMAGE(3u) }, 1u, + WT_ACS_UUID(0x2658cda4u, 0xcf6713e1u, 0x49cd10f9u, 0x31ef6813u), + WT_ACS_PROPERTIES_NO_INDIRECT, + 0u, /* SP4: Non-secure interrupts are queued. */ + WT_ACS_INTIDS + } +}; + +const wt_ffa_native_sp_t* wt_platform_ffa_native_partitions(size_t* count) +{ + *count = sizeof(g_acs_partitions) / sizeof(g_acs_partitions[0]); + return g_acs_partitions; +} +#endif diff --git a/port/common/aarch64/rng_entropy.c b/port/common/aarch64/rng_entropy.c new file mode 100644 index 00000000..2872dec7 --- /dev/null +++ b/port/common/aarch64/rng_entropy.c @@ -0,0 +1,97 @@ +/* rng_entropy.c + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfTrust. + * + * wolfTrust 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. + * + * wolfTrust 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, see . + */ + +/* Entropy for the wolfCrypt DRBG on the QEMU machines, which model no + * TRNG the Secure world can reach: a counter-seeded generator that is + * explicitly a test source. Silicon ports must provide a real one. */ + +#if !defined(WT_QEMU_TEST_ENTROPY) || (WT_QEMU_TEST_ENTROPY != 1) +#error "the QEMU test entropy source is only for emulated targets" +#endif + +#include "wolftrust/arch.h" +#include "wolftrust/spm_gate.h" +#include "wolftrust/spm_transport.h" + +#include +#include +#include + +int wolftrust_rng_generate_block(unsigned char *output, unsigned int sz); +int wolftrust_rng_generate_block_direct(unsigned char *output, + unsigned int sz); + +static uint64_t s_state; + +static uint64_t read_cntpct(void) +{ + uint64_t v; + + __asm__ volatile("isb\n\tmrs %0, CNTPCT_EL0" : "=r"(v)); + return v; +} + +static uint64_t next_word(void) +{ + uint64_t x = s_state; + + x ^= x << 13; + x ^= x >> 7; + x ^= x << 17; + s_state = x; + return x * 0x2545F4914F6CDD1Dull; +} + +int wolftrust_rng_generate_block_direct(unsigned char *output, unsigned int sz) +{ + unsigned int i; + uint64_t word = 0u; + + if (output == NULL && sz != 0u) { + return -1; + } + if (s_state == 0u) { + s_state = read_cntpct() | 1u; + } + for (i = 0u; i < sz; i++) { + if ((i % sizeof(word)) == 0u) { + word = next_word() ^ read_cntpct(); + } + output[i] = (unsigned char)(word >> (8u * (i % sizeof(word)))); + } + return 0; +} + +int wolftrust_rng_generate_block(unsigned char *output, unsigned int sz) +{ + if (wt_arch_thread_unprivileged()) { + wt_spm_call_t call; + + (void)memset(&call, 0, sizeof(call)); + call.op = WT_SPM_OP_KEYSTORE_ENTROPY; + call.buffer = output; + call.num_bytes = sz; + if (wt_spm_sp_call(&call) != WT_FFM_SUCCESS) { + return -1; + } + return call.ret_int; + } + return wolftrust_rng_generate_block_direct(output, sz); +} diff --git a/port/qemuvirt/el3_board.c b/port/qemuvirt/el3_board.c new file mode 100644 index 00000000..0895d73c --- /dev/null +++ b/port/qemuvirt/el3_board.c @@ -0,0 +1,73 @@ +/* el3_board.c + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfTrust. + * + * wolfTrust 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. + * + * wolfTrust 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, see . + */ + +/* Board bring-up the EL3 monitor asks of the QEMU virt port. */ + +#include "memory_map.h" +#include "wolftrust/arch/aarch64/el3.h" +#include "wolftrust/arch/aarch64/pl011.h" +#include "wolftrust/arch/aarch64/tables.h" + +/* The machine's Secure PL061 (secure=on): its line 1 is wired to the board's + * gpio-restart, a whole-machine reset. GPIODATA writes only the bits the + * address selects (offset bits [9:2]). */ +#define WT_SECURE_GPIO_BASE 0x090B0000u +#define WT_PL061_DIR 0x400u +#define WT_GPIO_RESET_LINE (1u << 1) + +/* The SPMC owns the GIC: distributor, the GICv2 CPU interface, and the + * GICv3 redistributor frames (up to eight cores) beside the console. */ +static const wt_memory_region_t g_device_regions[] = { + { WT_UART_S_BASE, 0x1000u, + WT_MEM_ATTR_READ | WT_MEM_ATTR_WRITE | WT_MEM_ATTR_DEVICE }, + { WT_GICD_BASE, 0x10000u, + WT_MEM_ATTR_READ | WT_MEM_ATTR_WRITE | WT_MEM_ATTR_DEVICE }, + { WT_GICC_BASE, 0x10000u, + WT_MEM_ATTR_READ | WT_MEM_ATTR_WRITE | WT_MEM_ATTR_DEVICE }, + { WT_GICR_BASE, 0x100000u, + WT_MEM_ATTR_READ | WT_MEM_ATTR_WRITE | WT_MEM_ATTR_DEVICE } +}; + +void wt_platform_board_init(void) +{ +#if !defined(WT_UART_SKIP_INIT) || (WT_UART_SKIP_INIT == 0) + wt_pl011_init(WT_UART_S_BASE, WT_UART_CLOCK_HZ, WT_UART_BAUD); +#endif +} + +void wt_platform_board_system_reset(void) +{ + volatile uint32_t* dir = + (volatile uint32_t*)(uintptr_t)(WT_SECURE_GPIO_BASE + WT_PL061_DIR); + volatile uint32_t* data = (volatile uint32_t*)(uintptr_t) + (WT_SECURE_GPIO_BASE + (WT_GPIO_RESET_LINE << 2)); + + *dir |= WT_GPIO_RESET_LINE; + *data = WT_GPIO_RESET_LINE; + for (;;) { + __asm__ volatile("wfi" ::: "memory"); + } +} + +const wt_memory_region_t* wt_platform_board_device_regions(size_t* count) +{ + *count = sizeof(g_device_regions) / sizeof(g_device_regions[0]); + return g_device_regions; +} diff --git a/port/qemuvirt/l3_port.h b/port/qemuvirt/l3_port.h new file mode 100644 index 00000000..1db64ed5 --- /dev/null +++ b/port/qemuvirt/l3_port.h @@ -0,0 +1,31 @@ +/* l3_port.h + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfTrust. + * + * wolfTrust 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. + * + * wolfTrust 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, see . + */ + +/* QEMU virt inputs to the shared AArch64 isolation level 3 layer. */ + +#ifndef WOLFTRUST_QEMUVIRT_L3_PORT_H +#define WOLFTRUST_QEMUVIRT_L3_PORT_H + +#include "memory_map.h" + +/* A Secure peripheral only the SPM drives, probed by the periphsp negative. */ +#define WT_L3_SPM_PERIPHERAL_BASE WT_UART_S_BASE + +#endif /* WOLFTRUST_QEMUVIRT_L3_PORT_H */ diff --git a/port/qemuvirt/manifest-conformance.json b/port/qemuvirt/manifest-conformance.json new file mode 100644 index 00000000..75ac984c --- /dev/null +++ b/port/qemuvirt/manifest-conformance.json @@ -0,0 +1,828 @@ +{ + "format_version": 1, + "generator_version": "WOLFTRUST_GEN_1", + "features": 1, + "isolation_profile": 3, + "profile_capabilities": { + "capabilities": 127, + "max_domains": 11, + "max_memory_resources_per_domain": 3, + "max_interrupts_per_domain": 1 + }, + "domains": [ + { + "id": 0, + "domain_class": 0, + "rot_role": 1, + "security_state": 0, + "privilege_state": 0, + "initial_lifecycle": 1, + "entry_point": 235929600, + "stack_base": 236978176, + "stack_size": 4096, + "memory_resources": [ + { + "base": 235929600, + "size": 1048576, + "attributes": 37, + "share_id": 3 + }, + { + "base": 236978176, + "size": 4096, + "attributes": 19, + "share_id": 0 + } + ], + "interrupt_resources": [], + "restart_policy": { + "action": 2, + "restart_limit": 0, + "restart_window_ticks": 0, + "initial_delay_ticks": 0 + }, + "required_capabilities": 127, + "launch_required": 0, + "launch_min_version": 0 + }, + { + "id": 1, + "domain_class": 2, + "rot_role": 0, + "security_state": 1, + "privilege_state": 0, + "initial_lifecycle": 1, + "entry_point": 1073741824, + "stack_base": 1090519040, + "stack_size": 65536, + "memory_resources": [ + { + "base": 1073741824, + "size": 1048576, + "attributes": 5, + "share_id": 0 + }, + { + "base": 1090519040, + "size": 1048576, + "attributes": 19, + "share_id": 0 + }, + { + "base": 150994944, + "size": 4096, + "attributes": 43, + "share_id": 2 + } + ], + "interrupt_resources": [], + "restart_policy": { + "action": 1, + "restart_limit": 3, + "restart_window_ticks": 8000, + "initial_delay_ticks": 1 + }, + "required_capabilities": 80, + "launch_required": 1, + "launch_min_version": 1 + }, + { + "id": 2, + "domain_class": 2, + "rot_role": 0, + "security_state": 1, + "privilege_state": 0, + "initial_lifecycle": 1, + "entry_point": 1107296256, + "stack_base": 1124073472, + "stack_size": 65536, + "memory_resources": [ + { + "base": 1107296256, + "size": 1048576, + "attributes": 5, + "share_id": 0 + }, + { + "base": 1124073472, + "size": 1048576, + "attributes": 19, + "share_id": 0 + }, + { + "base": 150994944, + "size": 4096, + "attributes": 43, + "share_id": 2 + } + ], + "interrupt_resources": [], + "restart_policy": { + "action": 1, + "restart_limit": 3, + "restart_window_ticks": 8000, + "initial_delay_ticks": 1 + }, + "required_capabilities": 80, + "launch_required": 1, + "launch_min_version": 1 + }, + { + "id": 3, + "domain_class": 1, + "rot_role": 2, + "security_state": 0, + "privilege_state": 1, + "initial_lifecycle": 0, + "entry_point": 235929600, + "stack_base": 237240320, + "stack_size": 16384, + "memory_resources": [ + { + "base": 235929600, + "size": 1048576, + "attributes": 37, + "share_id": 3 + }, + { + "base": 237240320, + "size": 16384, + "attributes": 19, + "share_id": 0 + }, + { + "base": 238174208, + "size": 4096, + "attributes": 19, + "share_id": 0 + } + ], + "interrupt_resources": [], + "restart_policy": { + "action": 1, + "restart_limit": 3, + "restart_window_ticks": 8000, + "initial_delay_ticks": 1 + }, + "required_capabilities": 80, + "launch_required": 0, + "launch_min_version": 0 + }, + { + "id": 4, + "domain_class": 1, + "rot_role": 2, + "security_state": 0, + "privilege_state": 1, + "initial_lifecycle": 0, + "entry_point": 235929600, + "stack_base": 237305856, + "stack_size": 16384, + "memory_resources": [ + { + "base": 235929600, + "size": 1048576, + "attributes": 37, + "share_id": 3 + }, + { + "base": 237305856, + "size": 16384, + "attributes": 19, + "share_id": 0 + }, + { + "base": 238178304, + "size": 110592, + "attributes": 19, + "share_id": 0 + } + ], + "interrupt_resources": [], + "restart_policy": { + "action": 1, + "restart_limit": 3, + "restart_window_ticks": 8000, + "initial_delay_ticks": 1 + }, + "required_capabilities": 80, + "launch_required": 0, + "launch_min_version": 0 + }, + { + "id": 5, + "domain_class": 1, + "rot_role": 2, + "security_state": 0, + "privilege_state": 1, + "initial_lifecycle": 0, + "entry_point": 235929600, + "stack_base": 237371392, + "stack_size": 16384, + "memory_resources": [ + { + "base": 235929600, + "size": 1048576, + "attributes": 37, + "share_id": 3 + }, + { + "base": 237371392, + "size": 16384, + "attributes": 19, + "share_id": 0 + }, + { + "base": 238026752, + "size": 147456, + "attributes": 19, + "share_id": 0 + } + ], + "interrupt_resources": [], + "restart_policy": { + "action": 1, + "restart_limit": 3, + "restart_window_ticks": 8000, + "initial_delay_ticks": 1 + }, + "required_capabilities": 80, + "launch_required": 0, + "launch_min_version": 0 + }, + { + "id": 6, + "domain_class": 1, + "rot_role": 2, + "security_state": 0, + "privilege_state": 1, + "initial_lifecycle": 0, + "entry_point": 235929600, + "stack_base": 237436928, + "stack_size": 16384, + "memory_resources": [ + { + "base": 235929600, + "size": 1048576, + "attributes": 37, + "share_id": 3 + }, + { + "base": 237436928, + "size": 16384, + "attributes": 19, + "share_id": 0 + } + ], + "interrupt_resources": [], + "restart_policy": { + "action": 1, + "restart_limit": 3, + "restart_window_ticks": 8000, + "initial_delay_ticks": 1 + }, + "required_capabilities": 80, + "launch_required": 0, + "launch_min_version": 0 + }, + { + "id": 7, + "domain_class": 1, + "rot_role": 2, + "security_state": 0, + "privilege_state": 1, + "initial_lifecycle": 0, + "entry_point": 235929600, + "stack_base": 237502464, + "stack_size": 16384, + "memory_resources": [ + { + "base": 235929600, + "size": 1048576, + "attributes": 37, + "share_id": 3 + }, + { + "base": 237502464, + "size": 16384, + "attributes": 19, + "share_id": 0 + } + ], + "interrupt_resources": [], + "restart_policy": { + "action": 1, + "restart_limit": 3, + "restart_window_ticks": 8000, + "initial_delay_ticks": 1 + }, + "required_capabilities": 80, + "launch_required": 0, + "launch_min_version": 0 + }, + { + "id": 8, + "domain_class": 1, + "rot_role": 3, + "security_state": 0, + "privilege_state": 1, + "initial_lifecycle": 0, + "entry_point": 235929600, + "stack_base": 237568000, + "stack_size": 16384, + "memory_resources": [ + { + "base": 235929600, + "size": 1048576, + "attributes": 37, + "share_id": 3 + }, + { + "base": 237568000, + "size": 16384, + "attributes": 19, + "share_id": 0 + } + ], + "interrupt_resources": [], + "restart_policy": { + "action": 1, + "restart_limit": 3, + "restart_window_ticks": 8000, + "initial_delay_ticks": 1 + }, + "required_capabilities": 80, + "launch_required": 0, + "launch_min_version": 0 + }, + { + "id": 9, + "domain_class": 1, + "rot_role": 2, + "security_state": 0, + "privilege_state": 1, + "initial_lifecycle": 0, + "entry_point": 235929600, + "stack_base": 237633536, + "stack_size": 16384, + "memory_resources": [ + { + "base": 235929600, + "size": 1048576, + "attributes": 37, + "share_id": 3 + }, + { + "base": 237633536, + "size": 16384, + "attributes": 19, + "share_id": 0 + } + ], + "interrupt_resources": [ + { + "interrupt": 63, + "attributes": 0, + "share_id": 0 + } + ], + "restart_policy": { + "action": 1, + "restart_limit": 3, + "restart_window_ticks": 8000, + "initial_delay_ticks": 1 + }, + "required_capabilities": 80, + "launch_required": 0, + "launch_min_version": 0 + }, + { + "id": 10, + "domain_class": 1, + "rot_role": 3, + "security_state": 0, + "privilege_state": 1, + "initial_lifecycle": 0, + "entry_point": 235929600, + "stack_base": 237699072, + "stack_size": 16384, + "memory_resources": [ + { + "base": 235929600, + "size": 1048576, + "attributes": 37, + "share_id": 3 + }, + { + "base": 237699072, + "size": 16384, + "attributes": 19, + "share_id": 0 + } + ], + "interrupt_resources": [], + "restart_policy": { + "action": 1, + "restart_limit": 3, + "restart_window_ticks": 8000, + "initial_delay_ticks": 1 + }, + "required_capabilities": 80, + "launch_required": 0, + "launch_min_version": 0 + } + ], + "partitions": [ + { + "name": "PARTITION_ATTEST", + "domain_id": 3, + "framework_version": 256, + "model": 0, + "priority": 2, + "services": [ + { + "name": "SERVICE_ATTEST", + "sid": 4096, + "version": 1, + "version_policy": 0, + "signal": 16, + "stateless_handle_index": 0, + "nonsecure_clients": true, + "connection_based": true + } + ], + "dependencies": [ + 4102 + ], + "interrupts": [] + }, + { + "name": "PARTITION_HSM", + "domain_id": 4, + "framework_version": 256, + "model": 0, + "priority": 1, + "services": [ + { + "name": "SERVICE_HSM", + "sid": 4102, + "version": 1, + "version_policy": 0, + "signal": 32, + "stateless_handle_index": 0, + "nonsecure_clients": true, + "connection_based": true + } + ], + "dependencies": [ + 4098 + ], + "interrupts": [] + }, + { + "name": "PARTITION_VAULT", + "domain_id": 5, + "framework_version": 256, + "model": 0, + "priority": 1, + "services": [ + { + "name": "SERVICE_VAULT", + "sid": 4098, + "version": 1, + "version_policy": 0, + "signal": 16, + "stateless_handle_index": 0, + "nonsecure_clients": false, + "connection_based": true + } + ], + "dependencies": [], + "interrupts": [] + }, + { + "name": "PARTITION_ITS", + "domain_id": 6, + "framework_version": 256, + "model": 0, + "priority": 1, + "services": [ + { + "name": "SERVICE_ITS", + "sid": 4099, + "version": 1, + "version_policy": 0, + "signal": 16, + "stateless_handle_index": 0, + "nonsecure_clients": true, + "connection_based": true + } + ], + "dependencies": [ + 4098 + ], + "interrupts": [] + }, + { + "name": "PARTITION_PS", + "domain_id": 7, + "framework_version": 256, + "model": 0, + "priority": 1, + "services": [ + { + "name": "SERVICE_PS", + "sid": 4100, + "version": 1, + "version_policy": 0, + "signal": 16, + "stateless_handle_index": 0, + "nonsecure_clients": true, + "connection_based": true + } + ], + "dependencies": [ + 4098 + ], + "interrupts": [] + }, + { + "name": "SERVER_PARTITION", + "domain_id": 8, + "framework_version": 256, + "model": 0, + "priority": 1, + "services": [ + { + "name": "SERVER_TEST_DISPATCHER", + "sid": 64257, + "version": 1, + "version_policy": 1, + "signal": 16, + "stateless_handle_index": 0, + "nonsecure_clients": true, + "connection_based": true + }, + { + "name": "SERVER_SECURE_CONNECT_ONLY", + "sid": 64258, + "version": 2, + "version_policy": 1, + "signal": 32, + "stateless_handle_index": 0, + "nonsecure_clients": false, + "connection_based": true + }, + { + "name": "SERVER_STRICT_VERSION", + "sid": 64259, + "version": 2, + "version_policy": 0, + "signal": 64, + "stateless_handle_index": 0, + "nonsecure_clients": true, + "connection_based": true + }, + { + "name": "SERVER_UNSPECIFIED_VERSION", + "sid": 64260, + "version": 1, + "version_policy": 0, + "signal": 128, + "stateless_handle_index": 0, + "nonsecure_clients": true, + "connection_based": true + }, + { + "name": "SERVER_RELAX_VERSION", + "sid": 64261, + "version": 2, + "version_policy": 1, + "signal": 256, + "stateless_handle_index": 0, + "nonsecure_clients": true, + "connection_based": true + }, + { + "name": "SERVER_UNEXTERN", + "sid": 64262, + "version": 2, + "version_policy": 1, + "signal": 512, + "stateless_handle_index": 0, + "nonsecure_clients": true, + "connection_based": true + }, + { + "name": "SERVER_CONNECTION_DROP", + "sid": 64263, + "version": 2, + "version_policy": 1, + "signal": 1024, + "stateless_handle_index": 0, + "nonsecure_clients": true, + "connection_based": true + } + ], + "dependencies": [ + 64513, + 64515 + ], + "interrupts": [] + }, + { + "name": "DRIVER_PARTITION", + "domain_id": 9, + "framework_version": 256, + "model": 0, + "priority": 1, + "services": [ + { + "name": "DRIVER_UART", + "sid": 64513, + "version": 1, + "version_policy": 1, + "signal": 16, + "stateless_handle_index": 0, + "nonsecure_clients": true, + "connection_based": true + }, + { + "name": "DRIVER_WATCHDOG", + "sid": 64514, + "version": 1, + "version_policy": 1, + "signal": 32, + "stateless_handle_index": 0, + "nonsecure_clients": true, + "connection_based": true + }, + { + "name": "DRIVER_NVMEM", + "sid": 64515, + "version": 1, + "version_policy": 1, + "signal": 64, + "stateless_handle_index": 0, + "nonsecure_clients": true, + "connection_based": true + }, + { + "name": "DRIVER_TEST", + "sid": 64516, + "version": 1, + "version_policy": 1, + "signal": 128, + "stateless_handle_index": 0, + "nonsecure_clients": true, + "connection_based": true + } + ], + "dependencies": [], + "interrupts": [ + { + "signal_name": "DRIVER_UART_INTR_SIG", + "interrupt": 63, + "signal": 256 + } + ] + }, + { + "name": "CLIENT_PARTITION", + "domain_id": 10, + "framework_version": 256, + "model": 0, + "priority": 1, + "services": [ + { + "name": "CLIENT_TEST_DISPATCHER", + "sid": 64001, + "version": 1, + "version_policy": 1, + "signal": 16, + "stateless_handle_index": 0, + "nonsecure_clients": true, + "connection_based": true + } + ], + "dependencies": [ + 64513, + 64515, + 64516, + 64257, + 64260, + 64259, + 64261, + 64258, + 64263 + ], + "interrupts": [] + } + ], + "limits": { + "max_partitions": 8, + "max_services_per_partition": 7, + "max_dependencies_per_partition": 9, + "max_stateless_handles": 32 + }, + "ffa": { + "partitions": [ + { + "domain_id": 3, + "ffa_version": "1.2", + "uuids": [ + "b606f19b-be86-561c-b866-4178d43941c1" + ], + "execution_contexts": 1, + "runtime_el": "S-EL0", + "messaging": "none", + "ns_interrupt_action": "signaled", + "boot_info_register": "none" + }, + { + "domain_id": 4, + "ffa_version": "1.2", + "uuids": [ + "4fd3da63-102c-5ef8-9ead-376bd722c337" + ], + "execution_contexts": 1, + "runtime_el": "S-EL0", + "messaging": "none", + "ns_interrupt_action": "signaled", + "boot_info_register": "none" + }, + { + "domain_id": 5, + "ffa_version": "1.2", + "uuids": [ + "bbc056fc-6657-5d2b-84c3-6fa550da0a93" + ], + "execution_contexts": 1, + "runtime_el": "S-EL0", + "messaging": "none", + "ns_interrupt_action": "signaled", + "boot_info_register": "none" + }, + { + "domain_id": 6, + "ffa_version": "1.2", + "uuids": [ + "83237edb-31a7-589e-bc5d-0db08b5130d9" + ], + "execution_contexts": 1, + "runtime_el": "S-EL0", + "messaging": "none", + "ns_interrupt_action": "signaled", + "boot_info_register": "none" + }, + { + "domain_id": 7, + "ffa_version": "1.2", + "uuids": [ + "1bed069d-b139-53b9-94e4-d331a18571ae" + ], + "execution_contexts": 1, + "runtime_el": "S-EL0", + "messaging": "none", + "ns_interrupt_action": "signaled", + "boot_info_register": "none" + }, + { + "domain_id": 8, + "ffa_version": "1.2", + "uuids": [ + "5e5b8e6e-5f5c-5a5d-9e01-000000000008" + ], + "execution_contexts": 1, + "runtime_el": "S-EL0", + "messaging": "none", + "ns_interrupt_action": "signaled", + "boot_info_register": "none" + }, + { + "domain_id": 9, + "ffa_version": "1.2", + "uuids": [ + "5e5b8e6e-5f5c-5a5d-9e01-000000000009" + ], + "execution_contexts": 1, + "runtime_el": "S-EL0", + "messaging": "none", + "ns_interrupt_action": "signaled", + "boot_info_register": "none" + }, + { + "domain_id": 10, + "ffa_version": "1.2", + "uuids": [ + "5e5b8e6e-5f5c-5a5d-9e01-00000000000a" + ], + "execution_contexts": 1, + "runtime_el": "S-EL0", + "messaging": "none", + "ns_interrupt_action": "signaled", + "boot_info_register": "none" + } + ] + } +} diff --git a/port/qemuvirt/manifest.json b/port/qemuvirt/manifest.json new file mode 100644 index 00000000..1030eedd --- /dev/null +++ b/port/qemuvirt/manifest.json @@ -0,0 +1,577 @@ +{ + "format_version": 1, + "generator_version": "WOLFTRUST_GEN_1", + "features": 1, + "isolation_profile": 3, + "profile_capabilities": { + "capabilities": 127, + "max_domains": 9, + "max_memory_resources_per_domain": 3, + "max_interrupts_per_domain": 0 + }, + "domains": [ + { + "id": 0, + "domain_class": 0, + "rot_role": 1, + "security_state": 0, + "privilege_state": 0, + "initial_lifecycle": 1, + "entry_point": 235929600, + "stack_base": 236978176, + "stack_size": 4096, + "memory_resources": [ + { + "base": 235929600, + "size": 1048576, + "attributes": 37, + "share_id": 3 + }, + { + "base": 236978176, + "size": 4096, + "attributes": 19, + "share_id": 0 + } + ], + "interrupt_resources": [], + "restart_policy": { + "action": 2, + "restart_limit": 0, + "restart_window_ticks": 0, + "initial_delay_ticks": 0 + }, + "required_capabilities": 127, + "launch_required": 0, + "launch_min_version": 0 + }, + { + "id": 1, + "domain_class": 2, + "rot_role": 0, + "security_state": 1, + "privilege_state": 0, + "initial_lifecycle": 1, + "entry_point": 1073741824, + "stack_base": 1090519040, + "stack_size": 65536, + "memory_resources": [ + { + "base": 1073741824, + "size": 1048576, + "attributes": 5, + "share_id": 0 + }, + { + "base": 1090519040, + "size": 1048576, + "attributes": 19, + "share_id": 0 + }, + { + "base": 150994944, + "size": 4096, + "attributes": 43, + "share_id": 2 + } + ], + "interrupt_resources": [], + "restart_policy": { + "action": 1, + "restart_limit": 3, + "restart_window_ticks": 8000, + "initial_delay_ticks": 1 + }, + "required_capabilities": 80, + "launch_required": 1, + "launch_min_version": 1 + }, + { + "id": 2, + "domain_class": 2, + "rot_role": 0, + "security_state": 1, + "privilege_state": 0, + "initial_lifecycle": 1, + "entry_point": 1107296256, + "stack_base": 1124073472, + "stack_size": 65536, + "memory_resources": [ + { + "base": 1107296256, + "size": 1048576, + "attributes": 5, + "share_id": 0 + }, + { + "base": 1124073472, + "size": 1048576, + "attributes": 19, + "share_id": 0 + }, + { + "base": 150994944, + "size": 4096, + "attributes": 43, + "share_id": 2 + } + ], + "interrupt_resources": [], + "restart_policy": { + "action": 1, + "restart_limit": 3, + "restart_window_ticks": 8000, + "initial_delay_ticks": 1 + }, + "required_capabilities": 80, + "launch_required": 1, + "launch_min_version": 1 + }, + { + "id": 3, + "domain_class": 1, + "rot_role": 2, + "security_state": 0, + "privilege_state": 1, + "initial_lifecycle": 0, + "entry_point": 235929600, + "stack_base": 237240320, + "stack_size": 16384, + "memory_resources": [ + { + "base": 235929600, + "size": 1048576, + "attributes": 37, + "share_id": 3 + }, + { + "base": 237240320, + "size": 16384, + "attributes": 19, + "share_id": 0 + }, + { + "base": 238174208, + "size": 4096, + "attributes": 19, + "share_id": 0 + } + ], + "interrupt_resources": [], + "restart_policy": { + "action": 1, + "restart_limit": 3, + "restart_window_ticks": 8000, + "initial_delay_ticks": 1 + }, + "required_capabilities": 80, + "launch_required": 0, + "launch_min_version": 0 + }, + { + "id": 4, + "domain_class": 1, + "rot_role": 2, + "security_state": 0, + "privilege_state": 1, + "initial_lifecycle": 0, + "entry_point": 235929600, + "stack_base": 237305856, + "stack_size": 16384, + "memory_resources": [ + { + "base": 235929600, + "size": 1048576, + "attributes": 37, + "share_id": 3 + }, + { + "base": 237305856, + "size": 16384, + "attributes": 19, + "share_id": 0 + }, + { + "base": 238178304, + "size": 110592, + "attributes": 19, + "share_id": 0 + } + ], + "interrupt_resources": [], + "restart_policy": { + "action": 1, + "restart_limit": 3, + "restart_window_ticks": 8000, + "initial_delay_ticks": 1 + }, + "required_capabilities": 80, + "launch_required": 0, + "launch_min_version": 0 + }, + { + "id": 5, + "domain_class": 1, + "rot_role": 2, + "security_state": 0, + "privilege_state": 1, + "initial_lifecycle": 0, + "entry_point": 235929600, + "stack_base": 237371392, + "stack_size": 16384, + "memory_resources": [ + { + "base": 235929600, + "size": 1048576, + "attributes": 37, + "share_id": 3 + }, + { + "base": 237371392, + "size": 16384, + "attributes": 19, + "share_id": 0 + }, + { + "base": 238026752, + "size": 147456, + "attributes": 19, + "share_id": 0 + } + ], + "interrupt_resources": [], + "restart_policy": { + "action": 1, + "restart_limit": 3, + "restart_window_ticks": 8000, + "initial_delay_ticks": 1 + }, + "required_capabilities": 80, + "launch_required": 0, + "launch_min_version": 0 + }, + { + "id": 6, + "domain_class": 1, + "rot_role": 2, + "security_state": 0, + "privilege_state": 1, + "initial_lifecycle": 0, + "entry_point": 235929600, + "stack_base": 237436928, + "stack_size": 16384, + "memory_resources": [ + { + "base": 235929600, + "size": 1048576, + "attributes": 37, + "share_id": 3 + }, + { + "base": 237436928, + "size": 16384, + "attributes": 19, + "share_id": 0 + } + ], + "interrupt_resources": [], + "restart_policy": { + "action": 1, + "restart_limit": 3, + "restart_window_ticks": 8000, + "initial_delay_ticks": 1 + }, + "required_capabilities": 80, + "launch_required": 0, + "launch_min_version": 0 + }, + { + "id": 7, + "domain_class": 1, + "rot_role": 2, + "security_state": 0, + "privilege_state": 1, + "initial_lifecycle": 0, + "entry_point": 235929600, + "stack_base": 237502464, + "stack_size": 16384, + "memory_resources": [ + { + "base": 235929600, + "size": 1048576, + "attributes": 37, + "share_id": 3 + }, + { + "base": 237502464, + "size": 16384, + "attributes": 19, + "share_id": 0 + } + ], + "interrupt_resources": [], + "restart_policy": { + "action": 1, + "restart_limit": 3, + "restart_window_ticks": 8000, + "initial_delay_ticks": 1 + }, + "required_capabilities": 80, + "launch_required": 0, + "launch_min_version": 0 + }, + { + "id": 8, + "domain_class": 1, + "rot_role": 2, + "security_state": 0, + "privilege_state": 1, + "initial_lifecycle": 0, + "entry_point": 235929600, + "stack_base": 237568000, + "stack_size": 16384, + "memory_resources": [ + { + "base": 235929600, + "size": 1048576, + "attributes": 37, + "share_id": 3 + }, + { + "base": 237568000, + "size": 16384, + "attributes": 19, + "share_id": 0 + } + ], + "interrupt_resources": [], + "restart_policy": { + "action": 1, + "restart_limit": 3, + "restart_window_ticks": 8000, + "initial_delay_ticks": 1 + }, + "required_capabilities": 80, + "launch_required": 0, + "launch_min_version": 0 + } + ], + "partitions": [ + { + "name": "PARTITION_ATTEST", + "domain_id": 3, + "framework_version": 256, + "model": 0, + "priority": 2, + "services": [ + { + "name": "SERVICE_ATTEST", + "sid": 4096, + "version": 1, + "version_policy": 0, + "signal": 16, + "stateless_handle_index": 0, + "nonsecure_clients": true, + "connection_based": true + } + ], + "dependencies": [ + 4102 + ], + "interrupts": [] + }, + { + "name": "PARTITION_HSM", + "domain_id": 4, + "framework_version": 256, + "model": 0, + "priority": 1, + "services": [ + { + "name": "SERVICE_HSM", + "sid": 4102, + "version": 1, + "version_policy": 0, + "signal": 32, + "stateless_handle_index": 0, + "nonsecure_clients": true, + "connection_based": true + } + ], + "dependencies": [ + 4098 + ], + "interrupts": [] + }, + { + "name": "PARTITION_VAULT", + "domain_id": 5, + "framework_version": 256, + "model": 0, + "priority": 1, + "services": [ + { + "name": "SERVICE_VAULT", + "sid": 4098, + "version": 1, + "version_policy": 0, + "signal": 16, + "stateless_handle_index": 0, + "nonsecure_clients": false, + "connection_based": true + } + ], + "dependencies": [], + "interrupts": [] + }, + { + "name": "PARTITION_ITS", + "domain_id": 6, + "framework_version": 256, + "model": 0, + "priority": 1, + "services": [ + { + "name": "SERVICE_ITS", + "sid": 4099, + "version": 1, + "version_policy": 0, + "signal": 16, + "stateless_handle_index": 0, + "nonsecure_clients": true, + "connection_based": true + } + ], + "dependencies": [ + 4098 + ], + "interrupts": [] + }, + { + "name": "PARTITION_PS", + "domain_id": 7, + "framework_version": 256, + "model": 0, + "priority": 1, + "services": [ + { + "name": "SERVICE_PS", + "sid": 4100, + "version": 1, + "version_policy": 0, + "signal": 16, + "stateless_handle_index": 0, + "nonsecure_clients": true, + "connection_based": true + } + ], + "dependencies": [ + 4098 + ], + "interrupts": [] + }, + { + "name": "PARTITION_FWU", + "domain_id": 8, + "framework_version": 256, + "model": 0, + "priority": 1, + "services": [ + { + "name": "SERVICE_FWU", + "sid": 4101, + "version": 1, + "version_policy": 0, + "signal": 16, + "stateless_handle_index": 0, + "nonsecure_clients": true, + "connection_based": true + } + ], + "dependencies": [], + "interrupts": [] + } + ], + "limits": { + "max_partitions": 6, + "max_services_per_partition": 1, + "max_dependencies_per_partition": 1, + "max_stateless_handles": 32 + }, + "ffa": { + "partitions": [ + { + "domain_id": 3, + "ffa_version": "1.2", + "uuids": [ + "b606f19b-be86-561c-b866-4178d43941c1" + ], + "execution_contexts": 1, + "runtime_el": "S-EL0", + "messaging": "none", + "ns_interrupt_action": "signaled", + "boot_info_register": "none" + }, + { + "domain_id": 4, + "ffa_version": "1.2", + "uuids": [ + "4fd3da63-102c-5ef8-9ead-376bd722c337" + ], + "execution_contexts": 1, + "runtime_el": "S-EL0", + "messaging": "none", + "ns_interrupt_action": "signaled", + "boot_info_register": "none" + }, + { + "domain_id": 5, + "ffa_version": "1.2", + "uuids": [ + "bbc056fc-6657-5d2b-84c3-6fa550da0a93" + ], + "execution_contexts": 1, + "runtime_el": "S-EL0", + "messaging": "none", + "ns_interrupt_action": "signaled", + "boot_info_register": "none" + }, + { + "domain_id": 6, + "ffa_version": "1.2", + "uuids": [ + "83237edb-31a7-589e-bc5d-0db08b5130d9" + ], + "execution_contexts": 1, + "runtime_el": "S-EL0", + "messaging": "none", + "ns_interrupt_action": "signaled", + "boot_info_register": "none" + }, + { + "domain_id": 7, + "ffa_version": "1.2", + "uuids": [ + "1bed069d-b139-53b9-94e4-d331a18571ae" + ], + "execution_contexts": 1, + "runtime_el": "S-EL0", + "messaging": "none", + "ns_interrupt_action": "signaled", + "boot_info_register": "none" + }, + { + "domain_id": 8, + "ffa_version": "1.2", + "uuids": [ + "181c2d9a-3595-5761-93d7-ab39cea21360" + ], + "execution_contexts": 1, + "runtime_el": "S-EL0", + "messaging": "none", + "ns_interrupt_action": "signaled", + "boot_info_register": "none" + } + ] + } +} diff --git a/port/qemuvirt/memory_map.h b/port/qemuvirt/memory_map.h new file mode 100644 index 00000000..d7a1cfc1 --- /dev/null +++ b/port/qemuvirt/memory_map.h @@ -0,0 +1,63 @@ +/* memory_map.h + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfTrust. + * + * wolfTrust 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. + * + * wolfTrust 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, see . + */ + +#ifndef WOLFTRUST_QEMUVIRT_MEMORY_MAP_H +#define WOLFTRUST_QEMUVIRT_MEMORY_MAP_H + +/* QEMU virt with secure=on: flash0 at 0 is secure-only, the 16 MiB secure + * SRAM sits at 0x0E000000, and the second PL011 is the secure console. */ + +#ifndef WT_EL3_TEXT_BASE +#define WT_EL3_TEXT_BASE 0x00000000u +#endif +#ifndef WT_EL3_RAM_BASE +#define WT_EL3_RAM_BASE 0x0E000000u +#endif +#ifndef WT_EL3_RAM_SIZE +#define WT_EL3_RAM_SIZE 0x00040000u +#endif +#define WT_RAM_S_BASE 0x0E040000u +#define WT_RAM_S_SIZE 0x00FC0000u +/* The secure SRAM and flash0 are Secure-only on the bus (secramneg). */ +#define WT_PORT_NS_MEMORY_FENCE 1 +/* The Secure bands, laid out from here by port/common/aarch64/l3_layout.h. */ +#define WT_L3_BAND_BASE 0x0E000000u +/* Normal-world payload load/run address: NS DRAM, well clear of the secure + * SRAM window (virt DRAM starts at 0x40000000). */ +#ifndef WT_NS_IMAGE_PA +#define WT_NS_IMAGE_PA 0x44000000u +#endif + +#define WT_GICD_BASE 0x08000000u +#define WT_GICC_BASE 0x08010000u +#define WT_GICR_BASE 0x080A0000u + +#define WT_UART_NS_BASE 0x09000000u +#define WT_UART_S_BASE 0x09040000u +#define WT_UART_CLOCK_HZ 24000000u +#define WT_UART_BAUD 115200u + +/* Device space: the virt peripherals below Secure RAM, then PCIe. */ +#define WT_PORT_MMIO_WINDOWS { { 0x08000000u, 0x0E000000u }, \ + { 0x10000000u, 0x40000000u } } + +#include "../common/aarch64/l3_layout.h" + +#endif /* WOLFTRUST_QEMUVIRT_MEMORY_MAP_H */ diff --git a/port/qemuvirt/uart.c b/port/qemuvirt/uart.c new file mode 100644 index 00000000..64ce29fb --- /dev/null +++ b/port/qemuvirt/uart.c @@ -0,0 +1,35 @@ +/* uart.c + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfTrust. + * + * wolfTrust 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. + * + * wolfTrust 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, see . + */ + +/* Secure console of the QEMU virt port: the secure PL011. */ + +#include "memory_map.h" +#include "wolftrust/arch/aarch64/el3.h" +#include "wolftrust/arch/aarch64/pl011.h" + +void wt_platform_console_putc(char c) +{ + wt_pl011_putc(WT_UART_S_BASE, c); +} + +void wt_platform_console_flush(void) +{ + wt_pl011_flush(WT_UART_S_BASE); +} diff --git a/port/stm32h563/conformance/conf_nvm.h b/port/stm32h563/conformance/conf_nvm.h index 16383eea..05295436 100644 --- a/port/stm32h563/conformance/conf_nvm.h +++ b/port/stm32h563/conformance/conf_nvm.h @@ -36,4 +36,8 @@ int wt_conf_nvm_sync(uint8_t *buf, uint32_t len, int store); * poke via SVC; the interrupt itself is delivered through the real vector. */ int wt_conf_irq_set(int on); +/* Bytes of the DRIVER partition's NVMEM: the PAL shadow and the NVMEM_0 + * range pal_config.h advertises. */ +#define WT_CONF_NVM_SIZE 0x100u + #endif /* WOLFTRUST_STM32H563_CONF_NVM_H */ diff --git a/port/stm32h563/conformance/conf_nvm_sync.c b/port/stm32h563/conformance/conf_nvm_sync.c index 815efc64..6fea754b 100644 --- a/port/stm32h563/conformance/conf_nvm_sync.c +++ b/port/stm32h563/conformance/conf_nvm_sync.c @@ -57,7 +57,9 @@ int wt_conf_nvm_sync(uint8_t *buf, uint32_t len, int store) call.num_bytes = (size_t)len; call.call_type = store; - (void)wt_spm_sp_call(&call); + if (wt_spm_sp_call(&call) != WT_FFM_SUCCESS) { + return -1; + } return call.ret_int; } @@ -69,6 +71,8 @@ int wt_conf_irq_set(int on) call.op = WT_SPM_OP_CONF_IRQ_SET; call.call_type = on; - (void)wt_spm_sp_call(&call); + if (wt_spm_sp_call(&call) != WT_FFM_SUCCESS) { + return -1; + } return call.ret_int; } diff --git a/port/stm32h563/conformance/pal_config.h b/port/stm32h563/conformance/pal_config.h index 567aab3b..4643cd86 100644 --- a/port/stm32h563/conformance/pal_config.h +++ b/port/stm32h563/conformance/pal_config.h @@ -26,6 +26,8 @@ #ifndef _PAL_CONFIG_H_ #define _PAL_CONFIG_H_ +#include "conf_nvm.h" + #define PLATFORM_PSA_ISOLATION_LEVEL 3 #define UART_NUM 1 @@ -47,7 +49,7 @@ #define NVMEM_NUM 1 #define NVMEM_0_START 0x0C07FC00 -#define NVMEM_0_END 0x0C07FFFF +#define NVMEM_0_END (NVMEM_0_START + WT_CONF_NVM_SIZE - 1u) #define NVMEM_0_PERMISSION TYPE_READ_WRITE #define NSPE_MMIO_NUM 1 diff --git a/port/stm32h563/conformance/pal_driver_intf.c b/port/stm32h563/conformance/pal_driver_intf.c index 72557160..d0457b29 100644 --- a/port/stm32h563/conformance/pal_driver_intf.c +++ b/port/stm32h563/conformance/pal_driver_intf.c @@ -34,18 +34,18 @@ typedef uintptr_t addr_t; -#define WT_CONF_DRV_NVM_SIZE 0x100u -static uint8_t g_drv_nvm[WT_CONF_DRV_NVM_SIZE]; +static uint8_t g_drv_nvm[WT_CONF_NVM_SIZE]; +static uint8_t g_drv_stage[WT_CONF_NVM_SIZE]; static uint8_t g_drv_nvm_ready; static uint8_t g_drv_wd_enabled; +/* A load that fails leaves the shadow unready: a read then fails instead of + * returning a fabricated blank sector, and the next access reloads. */ static int wt_conf_drv_nvm_init(void) { if (g_drv_nvm_ready == 0u) { - /* Reload the persisted contents; a blank sector reads back 0xFF, the - * same power-on state the RAM store used to fabricate. */ if (wt_conf_nvm_sync(g_drv_nvm, sizeof(g_drv_nvm), 0) != 0) { - (void)memset(g_drv_nvm, 0xFF, sizeof(g_drv_nvm)); + return -1; } g_drv_nvm_ready = 1u; } @@ -81,7 +81,9 @@ int pal_print(uint8_t c) int pal_nvmem_write(addr_t base, uint32_t offset, void* buffer, int size) { (void)base; - (void)wt_conf_drv_nvm_init(); + if (wt_conf_drv_nvm_init() != 0) { + return 0; + } /* Wrap-safe: offset + size overflows size_t on a 32-bit target, so * compare each side against the array bound without adding them. */ if (buffer == NULL || size < 0 || @@ -89,18 +91,23 @@ int pal_nvmem_write(addr_t base, uint32_t offset, void* buffer, int size) (size_t)size > sizeof(g_drv_nvm) - (size_t)offset) { return 0; } - (void)memcpy(&g_drv_nvm[offset], buffer, (size_t)size); - /* Write through to flash so the value survives an AIRCR reset. */ - if (wt_conf_nvm_sync(g_drv_nvm, sizeof(g_drv_nvm), 1) != 0) { + /* Write through to flash first; the shadow takes the bytes only once + * they are persisted, so a failed store never reads back as committed. */ + (void)memcpy(g_drv_stage, g_drv_nvm, sizeof(g_drv_stage)); + (void)memcpy(&g_drv_stage[offset], buffer, (size_t)size); + if (wt_conf_nvm_sync(g_drv_stage, sizeof(g_drv_stage), 1) != 0) { return 0; } + (void)memcpy(g_drv_nvm, g_drv_stage, sizeof(g_drv_nvm)); return 1; } int pal_nvmem_read(addr_t base, uint32_t offset, void* buffer, int size) { (void)base; - (void)wt_conf_drv_nvm_init(); + if (wt_conf_drv_nvm_init() != 0) { + return 0; + } /* Wrap-safe: offset + size overflows size_t on a 32-bit target, so * compare each side against the array bound without adding them. */ if (buffer == NULL || size < 0 || diff --git a/port/versal/el3_board.c b/port/versal/el3_board.c new file mode 100644 index 00000000..1c31729e --- /dev/null +++ b/port/versal/el3_board.c @@ -0,0 +1,60 @@ +/* el3_board.c + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfTrust. + * + * wolfTrust 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. + * + * wolfTrust 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, see . + */ + +/* Board bring-up the EL3 monitor asks of the Versal port. The PLM has + * already configured the PS UARTs on silicon and on versal-virt. */ + +#include "memory_map.h" +#include "wolftrust/arch/aarch64/el3.h" +#include "wolftrust/arch/aarch64/monitor_abi.h" +#include "wolftrust/arch/aarch64/pl011.h" +#include "wolftrust/arch/aarch64/tables.h" + +/* The SPMC owns the GIC: distributor and the redistributor frames of the + * two APU cores beside the console. */ +static const wt_memory_region_t g_device_regions[] = { + { WT_UART_S_BASE, 0x1000u, + WT_MEM_ATTR_READ | WT_MEM_ATTR_WRITE | WT_MEM_ATTR_DEVICE }, + { WT_GICD_BASE, 0x10000u, + WT_MEM_ATTR_READ | WT_MEM_ATTR_WRITE | WT_MEM_ATTR_DEVICE }, + { WT_GICR_BASE, 0x80000u, + WT_MEM_ATTR_READ | WT_MEM_ATTR_WRITE | WT_MEM_ATTR_DEVICE } +}; + +void wt_platform_board_init(void) +{ +#if !defined(WT_UART_SKIP_INIT) || (WT_UART_SKIP_INIT == 0) + wt_pl011_init(WT_UART_S_BASE, WT_UART_CLOCK_HZ, WT_UART_BAUD); +#endif +} + +/* The model's CRP/CRF/APU/PSM reset blocks are unimplemented stubs, so + * SYSTEM_RESET, a power cycle to its caller (DEN0022 5.11.1), powers the model + * off with the reset exit code and its host powers it on again. */ +void wt_platform_board_system_reset(void) +{ + (void)wt_el3_monitor_call(WT_MON_FID_EXIT, WT_MON_EXIT_RESET); +} + +const wt_memory_region_t* wt_platform_board_device_regions(size_t* count) +{ + *count = sizeof(g_device_regions) / sizeof(g_device_regions[0]); + return g_device_regions; +} diff --git a/port/versal/l3_port.h b/port/versal/l3_port.h new file mode 100644 index 00000000..dc3905f9 --- /dev/null +++ b/port/versal/l3_port.h @@ -0,0 +1,31 @@ +/* l3_port.h + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfTrust. + * + * wolfTrust 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. + * + * wolfTrust 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, see . + */ + +/* Versal inputs to the shared AArch64 isolation level 3 layer. */ + +#ifndef WOLFTRUST_VERSAL_L3_PORT_H +#define WOLFTRUST_VERSAL_L3_PORT_H + +#include "memory_map.h" + +/* A Secure peripheral only the SPM drives, probed by the periphsp negative. */ +#define WT_L3_SPM_PERIPHERAL_BASE WT_UART_S_BASE + +#endif /* WOLFTRUST_VERSAL_L3_PORT_H */ diff --git a/port/versal/manifest-conformance.json b/port/versal/manifest-conformance.json new file mode 100644 index 00000000..4cf1f082 --- /dev/null +++ b/port/versal/manifest-conformance.json @@ -0,0 +1,828 @@ +{ + "format_version": 1, + "generator_version": "WOLFTRUST_GEN_1", + "features": 1, + "isolation_profile": 0, + "profile_capabilities": { + "capabilities": 126, + "max_domains": 11, + "max_memory_resources_per_domain": 3, + "max_interrupts_per_domain": 1 + }, + "domains": [ + { + "id": 0, + "domain_class": 0, + "rot_role": 1, + "security_state": 0, + "privilege_state": 0, + "initial_lifecycle": 1, + "entry_point": 2131755008, + "stack_base": 2132803584, + "stack_size": 4096, + "memory_resources": [ + { + "base": 2131755008, + "size": 1048576, + "attributes": 37, + "share_id": 3 + }, + { + "base": 2132803584, + "size": 4096, + "attributes": 19, + "share_id": 0 + } + ], + "interrupt_resources": [], + "restart_policy": { + "action": 2, + "restart_limit": 0, + "restart_window_ticks": 0, + "initial_delay_ticks": 0 + }, + "required_capabilities": 126, + "launch_required": 0, + "launch_min_version": 0 + }, + { + "id": 1, + "domain_class": 2, + "rot_role": 0, + "security_state": 1, + "privilege_state": 0, + "initial_lifecycle": 1, + "entry_point": 268435456, + "stack_base": 285212672, + "stack_size": 65536, + "memory_resources": [ + { + "base": 268435456, + "size": 1048576, + "attributes": 5, + "share_id": 0 + }, + { + "base": 285212672, + "size": 1048576, + "attributes": 19, + "share_id": 0 + }, + { + "base": 4278190080, + "size": 4096, + "attributes": 43, + "share_id": 2 + } + ], + "interrupt_resources": [], + "restart_policy": { + "action": 1, + "restart_limit": 3, + "restart_window_ticks": 8000, + "initial_delay_ticks": 1 + }, + "required_capabilities": 80, + "launch_required": 1, + "launch_min_version": 1 + }, + { + "id": 2, + "domain_class": 2, + "rot_role": 0, + "security_state": 1, + "privilege_state": 0, + "initial_lifecycle": 1, + "entry_point": 301989888, + "stack_base": 318767104, + "stack_size": 65536, + "memory_resources": [ + { + "base": 301989888, + "size": 1048576, + "attributes": 5, + "share_id": 0 + }, + { + "base": 318767104, + "size": 1048576, + "attributes": 19, + "share_id": 0 + }, + { + "base": 4278190080, + "size": 4096, + "attributes": 43, + "share_id": 2 + } + ], + "interrupt_resources": [], + "restart_policy": { + "action": 1, + "restart_limit": 3, + "restart_window_ticks": 8000, + "initial_delay_ticks": 1 + }, + "required_capabilities": 80, + "launch_required": 1, + "launch_min_version": 1 + }, + { + "id": 3, + "domain_class": 1, + "rot_role": 2, + "security_state": 0, + "privilege_state": 1, + "initial_lifecycle": 0, + "entry_point": 2131755008, + "stack_base": 2133065728, + "stack_size": 16384, + "memory_resources": [ + { + "base": 2131755008, + "size": 1048576, + "attributes": 37, + "share_id": 3 + }, + { + "base": 2133065728, + "size": 16384, + "attributes": 19, + "share_id": 0 + }, + { + "base": 2133999616, + "size": 4096, + "attributes": 19, + "share_id": 0 + } + ], + "interrupt_resources": [], + "restart_policy": { + "action": 1, + "restart_limit": 3, + "restart_window_ticks": 8000, + "initial_delay_ticks": 1 + }, + "required_capabilities": 80, + "launch_required": 0, + "launch_min_version": 0 + }, + { + "id": 4, + "domain_class": 1, + "rot_role": 2, + "security_state": 0, + "privilege_state": 1, + "initial_lifecycle": 0, + "entry_point": 2131755008, + "stack_base": 2133131264, + "stack_size": 16384, + "memory_resources": [ + { + "base": 2131755008, + "size": 1048576, + "attributes": 37, + "share_id": 3 + }, + { + "base": 2133131264, + "size": 16384, + "attributes": 19, + "share_id": 0 + }, + { + "base": 2134003712, + "size": 110592, + "attributes": 19, + "share_id": 0 + } + ], + "interrupt_resources": [], + "restart_policy": { + "action": 1, + "restart_limit": 3, + "restart_window_ticks": 8000, + "initial_delay_ticks": 1 + }, + "required_capabilities": 80, + "launch_required": 0, + "launch_min_version": 0 + }, + { + "id": 5, + "domain_class": 1, + "rot_role": 2, + "security_state": 0, + "privilege_state": 1, + "initial_lifecycle": 0, + "entry_point": 2131755008, + "stack_base": 2133196800, + "stack_size": 16384, + "memory_resources": [ + { + "base": 2131755008, + "size": 1048576, + "attributes": 37, + "share_id": 3 + }, + { + "base": 2133196800, + "size": 16384, + "attributes": 19, + "share_id": 0 + }, + { + "base": 2133852160, + "size": 147456, + "attributes": 19, + "share_id": 0 + } + ], + "interrupt_resources": [], + "restart_policy": { + "action": 1, + "restart_limit": 3, + "restart_window_ticks": 8000, + "initial_delay_ticks": 1 + }, + "required_capabilities": 80, + "launch_required": 0, + "launch_min_version": 0 + }, + { + "id": 6, + "domain_class": 1, + "rot_role": 2, + "security_state": 0, + "privilege_state": 1, + "initial_lifecycle": 0, + "entry_point": 2131755008, + "stack_base": 2133262336, + "stack_size": 16384, + "memory_resources": [ + { + "base": 2131755008, + "size": 1048576, + "attributes": 37, + "share_id": 3 + }, + { + "base": 2133262336, + "size": 16384, + "attributes": 19, + "share_id": 0 + } + ], + "interrupt_resources": [], + "restart_policy": { + "action": 1, + "restart_limit": 3, + "restart_window_ticks": 8000, + "initial_delay_ticks": 1 + }, + "required_capabilities": 80, + "launch_required": 0, + "launch_min_version": 0 + }, + { + "id": 7, + "domain_class": 1, + "rot_role": 2, + "security_state": 0, + "privilege_state": 1, + "initial_lifecycle": 0, + "entry_point": 2131755008, + "stack_base": 2133327872, + "stack_size": 16384, + "memory_resources": [ + { + "base": 2131755008, + "size": 1048576, + "attributes": 37, + "share_id": 3 + }, + { + "base": 2133327872, + "size": 16384, + "attributes": 19, + "share_id": 0 + } + ], + "interrupt_resources": [], + "restart_policy": { + "action": 1, + "restart_limit": 3, + "restart_window_ticks": 8000, + "initial_delay_ticks": 1 + }, + "required_capabilities": 80, + "launch_required": 0, + "launch_min_version": 0 + }, + { + "id": 8, + "domain_class": 1, + "rot_role": 3, + "security_state": 0, + "privilege_state": 1, + "initial_lifecycle": 0, + "entry_point": 2131755008, + "stack_base": 2133393408, + "stack_size": 16384, + "memory_resources": [ + { + "base": 2131755008, + "size": 1048576, + "attributes": 37, + "share_id": 3 + }, + { + "base": 2133393408, + "size": 16384, + "attributes": 19, + "share_id": 0 + } + ], + "interrupt_resources": [], + "restart_policy": { + "action": 1, + "restart_limit": 3, + "restart_window_ticks": 8000, + "initial_delay_ticks": 1 + }, + "required_capabilities": 80, + "launch_required": 0, + "launch_min_version": 0 + }, + { + "id": 9, + "domain_class": 1, + "rot_role": 2, + "security_state": 0, + "privilege_state": 1, + "initial_lifecycle": 0, + "entry_point": 2131755008, + "stack_base": 2133458944, + "stack_size": 16384, + "memory_resources": [ + { + "base": 2131755008, + "size": 1048576, + "attributes": 37, + "share_id": 3 + }, + { + "base": 2133458944, + "size": 16384, + "attributes": 19, + "share_id": 0 + } + ], + "interrupt_resources": [ + { + "interrupt": 63, + "attributes": 0, + "share_id": 0 + } + ], + "restart_policy": { + "action": 1, + "restart_limit": 3, + "restart_window_ticks": 8000, + "initial_delay_ticks": 1 + }, + "required_capabilities": 80, + "launch_required": 0, + "launch_min_version": 0 + }, + { + "id": 10, + "domain_class": 1, + "rot_role": 3, + "security_state": 0, + "privilege_state": 1, + "initial_lifecycle": 0, + "entry_point": 2131755008, + "stack_base": 2133524480, + "stack_size": 16384, + "memory_resources": [ + { + "base": 2131755008, + "size": 1048576, + "attributes": 37, + "share_id": 3 + }, + { + "base": 2133524480, + "size": 16384, + "attributes": 19, + "share_id": 0 + } + ], + "interrupt_resources": [], + "restart_policy": { + "action": 1, + "restart_limit": 3, + "restart_window_ticks": 8000, + "initial_delay_ticks": 1 + }, + "required_capabilities": 80, + "launch_required": 0, + "launch_min_version": 0 + } + ], + "partitions": [ + { + "name": "PARTITION_ATTEST", + "domain_id": 3, + "framework_version": 256, + "model": 0, + "priority": 2, + "services": [ + { + "name": "SERVICE_ATTEST", + "sid": 4096, + "version": 1, + "version_policy": 0, + "signal": 16, + "stateless_handle_index": 0, + "nonsecure_clients": true, + "connection_based": true + } + ], + "dependencies": [ + 4102 + ], + "interrupts": [] + }, + { + "name": "PARTITION_HSM", + "domain_id": 4, + "framework_version": 256, + "model": 0, + "priority": 1, + "services": [ + { + "name": "SERVICE_HSM", + "sid": 4102, + "version": 1, + "version_policy": 0, + "signal": 32, + "stateless_handle_index": 0, + "nonsecure_clients": true, + "connection_based": true + } + ], + "dependencies": [ + 4098 + ], + "interrupts": [] + }, + { + "name": "PARTITION_VAULT", + "domain_id": 5, + "framework_version": 256, + "model": 0, + "priority": 1, + "services": [ + { + "name": "SERVICE_VAULT", + "sid": 4098, + "version": 1, + "version_policy": 0, + "signal": 16, + "stateless_handle_index": 0, + "nonsecure_clients": false, + "connection_based": true + } + ], + "dependencies": [], + "interrupts": [] + }, + { + "name": "PARTITION_ITS", + "domain_id": 6, + "framework_version": 256, + "model": 0, + "priority": 1, + "services": [ + { + "name": "SERVICE_ITS", + "sid": 4099, + "version": 1, + "version_policy": 0, + "signal": 16, + "stateless_handle_index": 0, + "nonsecure_clients": true, + "connection_based": true + } + ], + "dependencies": [ + 4098 + ], + "interrupts": [] + }, + { + "name": "PARTITION_PS", + "domain_id": 7, + "framework_version": 256, + "model": 0, + "priority": 1, + "services": [ + { + "name": "SERVICE_PS", + "sid": 4100, + "version": 1, + "version_policy": 0, + "signal": 16, + "stateless_handle_index": 0, + "nonsecure_clients": true, + "connection_based": true + } + ], + "dependencies": [ + 4098 + ], + "interrupts": [] + }, + { + "name": "SERVER_PARTITION", + "domain_id": 8, + "framework_version": 256, + "model": 0, + "priority": 1, + "services": [ + { + "name": "SERVER_TEST_DISPATCHER", + "sid": 64257, + "version": 1, + "version_policy": 1, + "signal": 16, + "stateless_handle_index": 0, + "nonsecure_clients": true, + "connection_based": true + }, + { + "name": "SERVER_SECURE_CONNECT_ONLY", + "sid": 64258, + "version": 2, + "version_policy": 1, + "signal": 32, + "stateless_handle_index": 0, + "nonsecure_clients": false, + "connection_based": true + }, + { + "name": "SERVER_STRICT_VERSION", + "sid": 64259, + "version": 2, + "version_policy": 0, + "signal": 64, + "stateless_handle_index": 0, + "nonsecure_clients": true, + "connection_based": true + }, + { + "name": "SERVER_UNSPECIFIED_VERSION", + "sid": 64260, + "version": 1, + "version_policy": 0, + "signal": 128, + "stateless_handle_index": 0, + "nonsecure_clients": true, + "connection_based": true + }, + { + "name": "SERVER_RELAX_VERSION", + "sid": 64261, + "version": 2, + "version_policy": 1, + "signal": 256, + "stateless_handle_index": 0, + "nonsecure_clients": true, + "connection_based": true + }, + { + "name": "SERVER_UNEXTERN", + "sid": 64262, + "version": 2, + "version_policy": 1, + "signal": 512, + "stateless_handle_index": 0, + "nonsecure_clients": true, + "connection_based": true + }, + { + "name": "SERVER_CONNECTION_DROP", + "sid": 64263, + "version": 2, + "version_policy": 1, + "signal": 1024, + "stateless_handle_index": 0, + "nonsecure_clients": true, + "connection_based": true + } + ], + "dependencies": [ + 64513, + 64515 + ], + "interrupts": [] + }, + { + "name": "DRIVER_PARTITION", + "domain_id": 9, + "framework_version": 256, + "model": 0, + "priority": 1, + "services": [ + { + "name": "DRIVER_UART", + "sid": 64513, + "version": 1, + "version_policy": 1, + "signal": 16, + "stateless_handle_index": 0, + "nonsecure_clients": true, + "connection_based": true + }, + { + "name": "DRIVER_WATCHDOG", + "sid": 64514, + "version": 1, + "version_policy": 1, + "signal": 32, + "stateless_handle_index": 0, + "nonsecure_clients": true, + "connection_based": true + }, + { + "name": "DRIVER_NVMEM", + "sid": 64515, + "version": 1, + "version_policy": 1, + "signal": 64, + "stateless_handle_index": 0, + "nonsecure_clients": true, + "connection_based": true + }, + { + "name": "DRIVER_TEST", + "sid": 64516, + "version": 1, + "version_policy": 1, + "signal": 128, + "stateless_handle_index": 0, + "nonsecure_clients": true, + "connection_based": true + } + ], + "dependencies": [], + "interrupts": [ + { + "signal_name": "DRIVER_UART_INTR_SIG", + "interrupt": 63, + "signal": 256 + } + ] + }, + { + "name": "CLIENT_PARTITION", + "domain_id": 10, + "framework_version": 256, + "model": 0, + "priority": 1, + "services": [ + { + "name": "CLIENT_TEST_DISPATCHER", + "sid": 64001, + "version": 1, + "version_policy": 1, + "signal": 16, + "stateless_handle_index": 0, + "nonsecure_clients": true, + "connection_based": true + } + ], + "dependencies": [ + 64513, + 64515, + 64516, + 64257, + 64260, + 64259, + 64261, + 64258, + 64263 + ], + "interrupts": [] + } + ], + "limits": { + "max_partitions": 8, + "max_services_per_partition": 7, + "max_dependencies_per_partition": 9, + "max_stateless_handles": 32 + }, + "ffa": { + "partitions": [ + { + "domain_id": 3, + "ffa_version": "1.2", + "uuids": [ + "b606f19b-be86-561c-b866-4178d43941c1" + ], + "execution_contexts": 1, + "runtime_el": "S-EL0", + "messaging": "none", + "ns_interrupt_action": "signaled", + "boot_info_register": "none" + }, + { + "domain_id": 4, + "ffa_version": "1.2", + "uuids": [ + "4fd3da63-102c-5ef8-9ead-376bd722c337" + ], + "execution_contexts": 1, + "runtime_el": "S-EL0", + "messaging": "none", + "ns_interrupt_action": "signaled", + "boot_info_register": "none" + }, + { + "domain_id": 5, + "ffa_version": "1.2", + "uuids": [ + "bbc056fc-6657-5d2b-84c3-6fa550da0a93" + ], + "execution_contexts": 1, + "runtime_el": "S-EL0", + "messaging": "none", + "ns_interrupt_action": "signaled", + "boot_info_register": "none" + }, + { + "domain_id": 6, + "ffa_version": "1.2", + "uuids": [ + "83237edb-31a7-589e-bc5d-0db08b5130d9" + ], + "execution_contexts": 1, + "runtime_el": "S-EL0", + "messaging": "none", + "ns_interrupt_action": "signaled", + "boot_info_register": "none" + }, + { + "domain_id": 7, + "ffa_version": "1.2", + "uuids": [ + "1bed069d-b139-53b9-94e4-d331a18571ae" + ], + "execution_contexts": 1, + "runtime_el": "S-EL0", + "messaging": "none", + "ns_interrupt_action": "signaled", + "boot_info_register": "none" + }, + { + "domain_id": 8, + "ffa_version": "1.2", + "uuids": [ + "5e5b8e6e-5f5c-5a5d-9e01-000000000008" + ], + "execution_contexts": 1, + "runtime_el": "S-EL0", + "messaging": "none", + "ns_interrupt_action": "signaled", + "boot_info_register": "none" + }, + { + "domain_id": 9, + "ffa_version": "1.2", + "uuids": [ + "5e5b8e6e-5f5c-5a5d-9e01-000000000009" + ], + "execution_contexts": 1, + "runtime_el": "S-EL0", + "messaging": "none", + "ns_interrupt_action": "signaled", + "boot_info_register": "none" + }, + { + "domain_id": 10, + "ffa_version": "1.2", + "uuids": [ + "5e5b8e6e-5f5c-5a5d-9e01-00000000000a" + ], + "execution_contexts": 1, + "runtime_el": "S-EL0", + "messaging": "none", + "ns_interrupt_action": "signaled", + "boot_info_register": "none" + } + ] + } +} diff --git a/port/versal/manifest.json b/port/versal/manifest.json new file mode 100644 index 00000000..54f949db --- /dev/null +++ b/port/versal/manifest.json @@ -0,0 +1,577 @@ +{ + "format_version": 1, + "generator_version": "WOLFTRUST_GEN_1", + "features": 1, + "isolation_profile": 0, + "profile_capabilities": { + "capabilities": 126, + "max_domains": 9, + "max_memory_resources_per_domain": 3, + "max_interrupts_per_domain": 0 + }, + "domains": [ + { + "id": 0, + "domain_class": 0, + "rot_role": 1, + "security_state": 0, + "privilege_state": 0, + "initial_lifecycle": 1, + "entry_point": 2131755008, + "stack_base": 2132803584, + "stack_size": 4096, + "memory_resources": [ + { + "base": 2131755008, + "size": 1048576, + "attributes": 37, + "share_id": 3 + }, + { + "base": 2132803584, + "size": 4096, + "attributes": 19, + "share_id": 0 + } + ], + "interrupt_resources": [], + "restart_policy": { + "action": 2, + "restart_limit": 0, + "restart_window_ticks": 0, + "initial_delay_ticks": 0 + }, + "required_capabilities": 126, + "launch_required": 0, + "launch_min_version": 0 + }, + { + "id": 1, + "domain_class": 2, + "rot_role": 0, + "security_state": 1, + "privilege_state": 0, + "initial_lifecycle": 1, + "entry_point": 268435456, + "stack_base": 285212672, + "stack_size": 65536, + "memory_resources": [ + { + "base": 268435456, + "size": 1048576, + "attributes": 5, + "share_id": 0 + }, + { + "base": 285212672, + "size": 1048576, + "attributes": 19, + "share_id": 0 + }, + { + "base": 4278190080, + "size": 4096, + "attributes": 43, + "share_id": 2 + } + ], + "interrupt_resources": [], + "restart_policy": { + "action": 1, + "restart_limit": 3, + "restart_window_ticks": 8000, + "initial_delay_ticks": 1 + }, + "required_capabilities": 80, + "launch_required": 1, + "launch_min_version": 1 + }, + { + "id": 2, + "domain_class": 2, + "rot_role": 0, + "security_state": 1, + "privilege_state": 0, + "initial_lifecycle": 1, + "entry_point": 301989888, + "stack_base": 318767104, + "stack_size": 65536, + "memory_resources": [ + { + "base": 301989888, + "size": 1048576, + "attributes": 5, + "share_id": 0 + }, + { + "base": 318767104, + "size": 1048576, + "attributes": 19, + "share_id": 0 + }, + { + "base": 4278190080, + "size": 4096, + "attributes": 43, + "share_id": 2 + } + ], + "interrupt_resources": [], + "restart_policy": { + "action": 1, + "restart_limit": 3, + "restart_window_ticks": 8000, + "initial_delay_ticks": 1 + }, + "required_capabilities": 80, + "launch_required": 1, + "launch_min_version": 1 + }, + { + "id": 3, + "domain_class": 1, + "rot_role": 2, + "security_state": 0, + "privilege_state": 1, + "initial_lifecycle": 0, + "entry_point": 2131755008, + "stack_base": 2133065728, + "stack_size": 16384, + "memory_resources": [ + { + "base": 2131755008, + "size": 1048576, + "attributes": 37, + "share_id": 3 + }, + { + "base": 2133065728, + "size": 16384, + "attributes": 19, + "share_id": 0 + }, + { + "base": 2133999616, + "size": 4096, + "attributes": 19, + "share_id": 0 + } + ], + "interrupt_resources": [], + "restart_policy": { + "action": 1, + "restart_limit": 3, + "restart_window_ticks": 8000, + "initial_delay_ticks": 1 + }, + "required_capabilities": 80, + "launch_required": 0, + "launch_min_version": 0 + }, + { + "id": 4, + "domain_class": 1, + "rot_role": 2, + "security_state": 0, + "privilege_state": 1, + "initial_lifecycle": 0, + "entry_point": 2131755008, + "stack_base": 2133131264, + "stack_size": 16384, + "memory_resources": [ + { + "base": 2131755008, + "size": 1048576, + "attributes": 37, + "share_id": 3 + }, + { + "base": 2133131264, + "size": 16384, + "attributes": 19, + "share_id": 0 + }, + { + "base": 2134003712, + "size": 110592, + "attributes": 19, + "share_id": 0 + } + ], + "interrupt_resources": [], + "restart_policy": { + "action": 1, + "restart_limit": 3, + "restart_window_ticks": 8000, + "initial_delay_ticks": 1 + }, + "required_capabilities": 80, + "launch_required": 0, + "launch_min_version": 0 + }, + { + "id": 5, + "domain_class": 1, + "rot_role": 2, + "security_state": 0, + "privilege_state": 1, + "initial_lifecycle": 0, + "entry_point": 2131755008, + "stack_base": 2133196800, + "stack_size": 16384, + "memory_resources": [ + { + "base": 2131755008, + "size": 1048576, + "attributes": 37, + "share_id": 3 + }, + { + "base": 2133196800, + "size": 16384, + "attributes": 19, + "share_id": 0 + }, + { + "base": 2133852160, + "size": 147456, + "attributes": 19, + "share_id": 0 + } + ], + "interrupt_resources": [], + "restart_policy": { + "action": 1, + "restart_limit": 3, + "restart_window_ticks": 8000, + "initial_delay_ticks": 1 + }, + "required_capabilities": 80, + "launch_required": 0, + "launch_min_version": 0 + }, + { + "id": 6, + "domain_class": 1, + "rot_role": 2, + "security_state": 0, + "privilege_state": 1, + "initial_lifecycle": 0, + "entry_point": 2131755008, + "stack_base": 2133262336, + "stack_size": 16384, + "memory_resources": [ + { + "base": 2131755008, + "size": 1048576, + "attributes": 37, + "share_id": 3 + }, + { + "base": 2133262336, + "size": 16384, + "attributes": 19, + "share_id": 0 + } + ], + "interrupt_resources": [], + "restart_policy": { + "action": 1, + "restart_limit": 3, + "restart_window_ticks": 8000, + "initial_delay_ticks": 1 + }, + "required_capabilities": 80, + "launch_required": 0, + "launch_min_version": 0 + }, + { + "id": 7, + "domain_class": 1, + "rot_role": 2, + "security_state": 0, + "privilege_state": 1, + "initial_lifecycle": 0, + "entry_point": 2131755008, + "stack_base": 2133327872, + "stack_size": 16384, + "memory_resources": [ + { + "base": 2131755008, + "size": 1048576, + "attributes": 37, + "share_id": 3 + }, + { + "base": 2133327872, + "size": 16384, + "attributes": 19, + "share_id": 0 + } + ], + "interrupt_resources": [], + "restart_policy": { + "action": 1, + "restart_limit": 3, + "restart_window_ticks": 8000, + "initial_delay_ticks": 1 + }, + "required_capabilities": 80, + "launch_required": 0, + "launch_min_version": 0 + }, + { + "id": 8, + "domain_class": 1, + "rot_role": 2, + "security_state": 0, + "privilege_state": 1, + "initial_lifecycle": 0, + "entry_point": 2131755008, + "stack_base": 2133393408, + "stack_size": 16384, + "memory_resources": [ + { + "base": 2131755008, + "size": 1048576, + "attributes": 37, + "share_id": 3 + }, + { + "base": 2133393408, + "size": 16384, + "attributes": 19, + "share_id": 0 + } + ], + "interrupt_resources": [], + "restart_policy": { + "action": 1, + "restart_limit": 3, + "restart_window_ticks": 8000, + "initial_delay_ticks": 1 + }, + "required_capabilities": 80, + "launch_required": 0, + "launch_min_version": 0 + } + ], + "partitions": [ + { + "name": "PARTITION_ATTEST", + "domain_id": 3, + "framework_version": 256, + "model": 0, + "priority": 2, + "services": [ + { + "name": "SERVICE_ATTEST", + "sid": 4096, + "version": 1, + "version_policy": 0, + "signal": 16, + "stateless_handle_index": 0, + "nonsecure_clients": true, + "connection_based": true + } + ], + "dependencies": [ + 4102 + ], + "interrupts": [] + }, + { + "name": "PARTITION_HSM", + "domain_id": 4, + "framework_version": 256, + "model": 0, + "priority": 1, + "services": [ + { + "name": "SERVICE_HSM", + "sid": 4102, + "version": 1, + "version_policy": 0, + "signal": 32, + "stateless_handle_index": 0, + "nonsecure_clients": true, + "connection_based": true + } + ], + "dependencies": [ + 4098 + ], + "interrupts": [] + }, + { + "name": "PARTITION_VAULT", + "domain_id": 5, + "framework_version": 256, + "model": 0, + "priority": 1, + "services": [ + { + "name": "SERVICE_VAULT", + "sid": 4098, + "version": 1, + "version_policy": 0, + "signal": 16, + "stateless_handle_index": 0, + "nonsecure_clients": false, + "connection_based": true + } + ], + "dependencies": [], + "interrupts": [] + }, + { + "name": "PARTITION_ITS", + "domain_id": 6, + "framework_version": 256, + "model": 0, + "priority": 1, + "services": [ + { + "name": "SERVICE_ITS", + "sid": 4099, + "version": 1, + "version_policy": 0, + "signal": 16, + "stateless_handle_index": 0, + "nonsecure_clients": true, + "connection_based": true + } + ], + "dependencies": [ + 4098 + ], + "interrupts": [] + }, + { + "name": "PARTITION_PS", + "domain_id": 7, + "framework_version": 256, + "model": 0, + "priority": 1, + "services": [ + { + "name": "SERVICE_PS", + "sid": 4100, + "version": 1, + "version_policy": 0, + "signal": 16, + "stateless_handle_index": 0, + "nonsecure_clients": true, + "connection_based": true + } + ], + "dependencies": [ + 4098 + ], + "interrupts": [] + }, + { + "name": "PARTITION_FWU", + "domain_id": 8, + "framework_version": 256, + "model": 0, + "priority": 1, + "services": [ + { + "name": "SERVICE_FWU", + "sid": 4101, + "version": 1, + "version_policy": 0, + "signal": 16, + "stateless_handle_index": 0, + "nonsecure_clients": true, + "connection_based": true + } + ], + "dependencies": [], + "interrupts": [] + } + ], + "limits": { + "max_partitions": 6, + "max_services_per_partition": 1, + "max_dependencies_per_partition": 1, + "max_stateless_handles": 32 + }, + "ffa": { + "partitions": [ + { + "domain_id": 3, + "ffa_version": "1.2", + "uuids": [ + "b606f19b-be86-561c-b866-4178d43941c1" + ], + "execution_contexts": 1, + "runtime_el": "S-EL0", + "messaging": "none", + "ns_interrupt_action": "signaled", + "boot_info_register": "none" + }, + { + "domain_id": 4, + "ffa_version": "1.2", + "uuids": [ + "4fd3da63-102c-5ef8-9ead-376bd722c337" + ], + "execution_contexts": 1, + "runtime_el": "S-EL0", + "messaging": "none", + "ns_interrupt_action": "signaled", + "boot_info_register": "none" + }, + { + "domain_id": 5, + "ffa_version": "1.2", + "uuids": [ + "bbc056fc-6657-5d2b-84c3-6fa550da0a93" + ], + "execution_contexts": 1, + "runtime_el": "S-EL0", + "messaging": "none", + "ns_interrupt_action": "signaled", + "boot_info_register": "none" + }, + { + "domain_id": 6, + "ffa_version": "1.2", + "uuids": [ + "83237edb-31a7-589e-bc5d-0db08b5130d9" + ], + "execution_contexts": 1, + "runtime_el": "S-EL0", + "messaging": "none", + "ns_interrupt_action": "signaled", + "boot_info_register": "none" + }, + { + "domain_id": 7, + "ffa_version": "1.2", + "uuids": [ + "1bed069d-b139-53b9-94e4-d331a18571ae" + ], + "execution_contexts": 1, + "runtime_el": "S-EL0", + "messaging": "none", + "ns_interrupt_action": "signaled", + "boot_info_register": "none" + }, + { + "domain_id": 8, + "ffa_version": "1.2", + "uuids": [ + "181c2d9a-3595-5761-93d7-ab39cea21360" + ], + "execution_contexts": 1, + "runtime_el": "S-EL0", + "messaging": "none", + "ns_interrupt_action": "signaled", + "boot_info_register": "none" + } + ] + } +} diff --git a/port/versal/memory_map.h b/port/versal/memory_map.h new file mode 100644 index 00000000..34a7d96f --- /dev/null +++ b/port/versal/memory_map.h @@ -0,0 +1,62 @@ +/* memory_map.h + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfTrust. + * + * wolfTrust 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. + * + * wolfTrust 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, see . + */ + +#ifndef WOLFTRUST_VERSAL_MEMORY_MAP_H +#define WOLFTRUST_VERSAL_MEMORY_MAP_H + +/* AMD Versal (and QEMU xlnx-versal-virt): the EL3 monitor lives in the + * 256 KiB OCM, the Secure band is carved out of DDR, GIC-500 is GICv3, and + * both PS UARTs are PL011s (UART1 is the secure console). */ + +#ifndef WT_EL3_TEXT_BASE +#define WT_EL3_TEXT_BASE 0xFFFC0000u +#endif +#ifndef WT_EL3_RAM_BASE +#define WT_EL3_RAM_BASE 0xFFFE0000u +#endif +#ifndef WT_EL3_RAM_SIZE +#define WT_EL3_RAM_SIZE 0x00020000u +#endif +#define WT_RAM_S_BASE 0x7F000000u +#define WT_RAM_S_SIZE 0x01000000u +/* xlnx-versal-virt models no XMPU/RISAF, so the Normal world can reach this + * band; silicon sets 1 only once it programs and locks the XMPU over it. */ +#define WT_PORT_NS_MEMORY_FENCE 0 +/* The Secure bands, laid out from here by port/common/aarch64/l3_layout.h. */ +#define WT_L3_BAND_BASE 0x7F000000u +/* Normal-world payload load/run address in low DDR, below the secure window. */ +#ifndef WT_NS_IMAGE_PA +#define WT_NS_IMAGE_PA 0x44000000u +#endif + +#define WT_GICD_BASE 0xF9000000u +#define WT_GICR_BASE 0xF9080000u + +#define WT_UART_NS_BASE 0xFF000000u +#define WT_UART_S_BASE 0xFF010000u +#define WT_UART_CLOCK_HZ 100000000u +#define WT_UART_BAUD 115200u + +/* Everything above the low DDR window is device space or OCM. */ +#define WT_PORT_MMIO_WINDOWS { { 0x80000000u, 0x100000000u } } + +#include "../common/aarch64/l3_layout.h" + +#endif /* WOLFTRUST_VERSAL_MEMORY_MAP_H */ diff --git a/port/versal/uart.c b/port/versal/uart.c new file mode 100644 index 00000000..e779d0e4 --- /dev/null +++ b/port/versal/uart.c @@ -0,0 +1,35 @@ +/* uart.c + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfTrust. + * + * wolfTrust 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. + * + * wolfTrust 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, see . + */ + +/* Secure console of the Versal port: PS UART1 (PL011). */ + +#include "memory_map.h" +#include "wolftrust/arch/aarch64/el3.h" +#include "wolftrust/arch/aarch64/pl011.h" + +void wt_platform_console_putc(char c) +{ + wt_pl011_putc(WT_UART_S_BASE, c); +} + +void wt_platform_console_flush(void) +{ + wt_pl011_flush(WT_UART_S_BASE); +} diff --git a/src/arch/aarch64/common/libc_min.c b/src/arch/aarch64/common/libc_min.c new file mode 100644 index 00000000..3d3bdc06 --- /dev/null +++ b/src/arch/aarch64/common/libc_min.c @@ -0,0 +1,88 @@ +/* libc_min.c + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfTrust. + * + * wolfTrust 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. + * + * wolfTrust 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, see . + */ + +/* The routines GCC may emit calls to in a -nostdlib image, plus memmove for + * the wolfHSM client the Normal-world smoke guest links. */ + +#include +#include + +void* memset(void* dest, int value, size_t count); +void* memcpy(void* dest, const void* src, size_t count); +void* memmove(void* dest, const void* src, size_t count); +int memcmp(const void* a, const void* b, size_t count); + +void* memset(void* dest, int value, size_t count) +{ + uint8_t* out = (uint8_t*)dest; + size_t i; + + for (i = 0u; i < count; ++i) { + out[i] = (uint8_t)value; + } + return dest; +} + +void* memcpy(void* dest, const void* src, size_t count) +{ + uint8_t* out = (uint8_t*)dest; + const uint8_t* in = (const uint8_t*)src; + size_t i; + + for (i = 0u; i < count; ++i) { + out[i] = in[i]; + } + return dest; +} + +void* memmove(void* dest, const void* src, size_t count) +{ + uint8_t* out = (uint8_t*)dest; + const uint8_t* in = (const uint8_t*)src; + size_t i; + + /* Backward only when dest starts inside src: an address difference, since + * ordering pointers to unrelated objects is undefined. */ + if (((uintptr_t)out - (uintptr_t)in) < (uintptr_t)count) { + for (i = count; i > 0u; --i) { + out[i - 1u] = in[i - 1u]; + } + } + else { + for (i = 0u; i < count; ++i) { + out[i] = in[i]; + } + } + return dest; +} + +int memcmp(const void* a, const void* b, size_t count) +{ + const uint8_t* pa = (const uint8_t*)a; + const uint8_t* pb = (const uint8_t*)b; + size_t i; + + for (i = 0u; i < count; ++i) { + if (pa[i] != pb[i]) { + return (int)pa[i] - (int)pb[i]; + } + } + return 0; +} diff --git a/src/arch/aarch64/drivers/pl011.c b/src/arch/aarch64/drivers/pl011.c new file mode 100644 index 00000000..ccbfa59c --- /dev/null +++ b/src/arch/aarch64/drivers/pl011.c @@ -0,0 +1,73 @@ +/* pl011.c + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfTrust. + * + * wolfTrust 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. + * + * wolfTrust 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, see . + */ + +#include "wolftrust/arch/aarch64/pl011.h" + +#define PL011_DR 0x000u +#define PL011_FR 0x018u +#define PL011_IBRD 0x024u +#define PL011_FBRD 0x028u +#define PL011_LCR_H 0x02Cu +#define PL011_CR 0x030u +#define PL011_IMSC 0x038u +#define PL011_ICR 0x044u + +#define PL011_FR_BUSY (1u << 3) +#define PL011_FR_TXFF (1u << 5) +#define PL011_LCR_H_FEN (1u << 4) +#define PL011_LCR_H_WLEN8 (3u << 5) +#define PL011_CR_UARTEN (1u << 0) +#define PL011_CR_TXE (1u << 8) +#define PL011_CR_RXE (1u << 9) + +static volatile uint32_t* pl011_reg(uintptr_t base, uint32_t offset) +{ + return (volatile uint32_t*)(base + offset); +} + +void wt_pl011_init(uintptr_t base, uint32_t clock_hz, uint32_t baud) +{ + uint32_t divisor; + + *pl011_reg(base, PL011_CR) = 0u; + while ((*pl011_reg(base, PL011_FR) & PL011_FR_BUSY) != 0u) { + } + /* IBRD.FBRD = clock / (16 * baud) in 16.6 fixed point. */ + divisor = (clock_hz * 4u) / baud; + *pl011_reg(base, PL011_IBRD) = divisor >> 6; + *pl011_reg(base, PL011_FBRD) = divisor & 0x3Fu; + *pl011_reg(base, PL011_LCR_H) = PL011_LCR_H_WLEN8 | PL011_LCR_H_FEN; + *pl011_reg(base, PL011_IMSC) = 0u; + *pl011_reg(base, PL011_ICR) = 0x7FFu; + *pl011_reg(base, PL011_CR) = PL011_CR_UARTEN | PL011_CR_TXE | PL011_CR_RXE; +} + +void wt_pl011_putc(uintptr_t base, char c) +{ + while ((*pl011_reg(base, PL011_FR) & PL011_FR_TXFF) != 0u) { + } + *pl011_reg(base, PL011_DR) = (uint32_t)(uint8_t)c; +} + +void wt_pl011_flush(uintptr_t base) +{ + while ((*pl011_reg(base, PL011_FR) & PL011_FR_BUSY) != 0u) { + } +} diff --git a/src/arch/aarch64/el3/console.c b/src/arch/aarch64/el3/console.c new file mode 100644 index 00000000..54224e45 --- /dev/null +++ b/src/arch/aarch64/el3/console.c @@ -0,0 +1,80 @@ +/* console.c + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfTrust. + * + * wolfTrust 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. + * + * wolfTrust 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, see . + */ + +/* EL3 console: formatting over the port's polled putc; no printf. */ + +#include "wolftrust/arch/aarch64/el3.h" +#include "wolftrust/arch/aarch64/esr.h" +#include "wolftrust/arch/aarch64/monitor_abi.h" +#include "wolftrust/arch/aarch64/sysreg.h" + +void wt_el3_puts(const char* text) +{ + while (*text != '\0') { + wt_platform_console_putc(*text); + text++; + } +} + +void wt_el3_puthex(uint64_t value, unsigned int digits) +{ + static const char table[] = "0123456789abcdef"; + unsigned int shift = digits * 4u; + + while (shift > 0u) { + shift -= 4u; + wt_platform_console_putc(table[(value >> shift) & 0xFu]); + } +} + +void wt_el3_putdec(uint64_t value) +{ + char buf[21]; + int i = 20; + + buf[i] = '\0'; + do { + i--; + buf[i] = (char)('0' + (value % 10u)); + value /= 10u; + } while ((value != 0u) && (i > 0)); + wt_el3_puts(&buf[i]); +} + +void wt_el3_fault(uint64_t kind, uint64_t esr, uint64_t far, uint64_t elr) +{ + char line[80]; + uint32_t el = 3u; + + if (kind >= WT_EL3_VEC_LOWER64_SYNC) { + el = WT_SPSR_M_EL(wt_read_spsr_el3()); + } + (void)wt_esr_format(line, sizeof(line), el, esr, far); + wt_el3_puts(line); + wt_el3_puts(" ELR=0x"); + wt_el3_puthex(elr, 16u); + wt_el3_puts(" vector="); + wt_el3_putdec(kind); + wt_el3_puts("\r\n[EL3] panic code=0x"); + wt_el3_puthex(WT_ESR_EC(esr), 2u); + wt_el3_puts("\r\n"); + wt_platform_console_flush(); + wt_el3_semihost_exit(WT_MON_EXIT_PANIC); +} diff --git a/src/arch/aarch64/el3/el3.ld b/src/arch/aarch64/el3/el3.ld new file mode 100644 index 00000000..469bc814 --- /dev/null +++ b/src/arch/aarch64/el3/el3.ld @@ -0,0 +1,59 @@ +/* EL3 monitor image: code and read-only data at WT_EL3_TEXT_BASE (flash on + * QEMU virt, OCM on Versal), data, bss, and stacks at WT_EL3_RAM_BASE. Both + * addresses arrive as --defsym values from mk/target-.mk. */ +ENTRY(wt_el3_start) + +SECTIONS +{ + . = WT_EL3_TEXT_BASE; + .el3.text : { + KEEP(*(.el3.text.entry)) + KEEP(*(.el3.vectors)) + *(.el3.text .el3.text.*) + *libwt_el3.a:*(.text .text.*) + } + .text : { + *(.text .text.*) + } + .rodata : { + *(.rodata .rodata.*) + . = ALIGN(16); + } + __data_lma = .; + + . = WT_EL3_RAM_BASE; + .data : AT(__data_lma) { + __data_start = .; + *(.data .data.*) + . = ALIGN(16); + __data_end = .; + } + .bss (NOLOAD) : { + __bss_start = .; + *(.bss .bss.*) + *(COMMON) + . = ALIGN(16); + __bss_end = .; + } + . = ALIGN(16); + . += 0x4000; + __el3_stack_top = .; + /* Survives a warm reset: placed past the stack, outside the .bss clear + * range and not loaded from flash, so a re-entry through wt_el3_start + * preserves it (the boot counter that gates PSCI SYSTEM_RESET). */ + .noinit (NOLOAD) : { + *(.noinit .noinit.*) + . = ALIGN(16); + } + __el3_ram_end = .; + + /DISCARD/ : { + *(.note*) + *(.comment) + *(.eh_frame*) + } +} + +ASSERT((wt_el3_vectors & 0x7FF) == 0, "EL3 vector table must be 2 KiB aligned") +ASSERT(__el3_ram_end <= WT_EL3_RAM_BASE + WT_EL3_RAM_SIZE, "EL3 RAM band overflow") +ASSERT(__data_lma + SIZEOF(.data) <= WT_EL3_LOAD_LIMIT, "EL3 load image overruns its flash or OCM band") diff --git a/src/arch/aarch64/el3/el3_main.c b/src/arch/aarch64/el3/el3_main.c new file mode 100644 index 00000000..7a18e1c4 --- /dev/null +++ b/src/arch/aarch64/el3/el3_main.c @@ -0,0 +1,329 @@ +/* el3_main.c + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfTrust. + * + * wolfTrust 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. + * + * wolfTrust 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, see . + */ + +/* EL3 boot core: bring up the board and the GIC, account for the parked + * secondaries, prove the secure timer reaches EL3 as a Group 0 FIQ, print + * the banner, and drop into the Secure EL1 entry. */ + +#include "wolftrust/arch/aarch64/el3.h" +#include "wolftrust/arch/aarch64/ffa_abi.h" +#include "wolftrust/arch/aarch64/ffa_boot_info.h" +#include "wolftrust/arch/aarch64/gic.h" +#include "wolftrust/arch/aarch64/monitor_abi.h" +#include "wolftrust/arch/aarch64/sysreg.h" +#if defined(WT_EL3_TEST_HANDOFF) +#include "wolftrust/boot_handoff.h" +#include "psa/lifecycle.h" +#endif + +#include + +#ifndef WT_PORT_BOOT_CPUS +#define WT_PORT_BOOT_CPUS 1u +#endif +#if (WT_PORT_BOOT_CPUS < 1u) || (WT_PORT_BOOT_CPUS > WT_EL3_MAX_CPUS) +#error "WT_PORT_BOOT_CPUS must be between 1 and WT_EL3_MAX_CPUS" +#endif +#ifndef WT_SPM_BOOT_INFO_PA +#error "the target fragment must place the FF-A boot information page" +#endif +#ifndef WT_PORT_HANDOFF_PA +#define WT_PORT_HANDOFF_PA 0u +#endif +#ifndef WT_PORT_HANDOFF_SIZE +#define WT_PORT_HANDOFF_SIZE 0u +#endif +/* Every core the port declares but the boot core must park in the EL3 pen; + * a core it does not declare may park there too. */ +#define WT_EL3_PARK_EXPECTED ((uint32_t)((1u << WT_PORT_BOOT_CPUS) - 2u)) +#define WT_SPM_BOOT_INFO_LIMIT 4096u +#define WT_EL3_PARK_WAIT_MS 200u +#define WT_EL3_TICK_PERIOD_MS 10u +#define WT_EL3_TICK_WAIT_MS 100u + +/* Monitor panic codes for a boot that must not reach the Secure runtime. */ +#define WT_EL3_PANIC_BOOT_INFO 0xB1u +#define WT_EL3_PANIC_NOT_PARKED 0xB2u +#define WT_EL3_PANIC_RDIST_ASLEEP 0xB3u +#define WT_EL3_PANIC_NO_TICK 0xB4u +#define WT_EL3_PANIC_MDCR 0xB5u + +/* MDCR_EL3 fields every PE implements, plus the PMU, SPE, and TRBE ones the + * PE reports; the rest are RES0 on some PEs and are left out of the compare. */ +#define WT_MDCR_EL3_TPM (1ull << 6) +#define WT_MDCR_EL3_TDA (1ull << 9) +#define WT_MDCR_EL3_TDOSA (1ull << 10) +#define WT_MDCR_EL3_NSPB (3ull << 12) +#define WT_MDCR_EL3_SPD32 (3ull << 14) +#define WT_MDCR_EL3_SDD (1ull << 16) +#define WT_MDCR_EL3_SPME (1ull << 17) +#define WT_MDCR_EL3_STE (1ull << 18) +#define WT_MDCR_EL3_SCCD (1ull << 23) +#define WT_MDCR_EL3_NSTB (3ull << 24) + +volatile uint8_t g_wt_el3_parked[WT_EL3_MAX_CPUS]; +volatile uint32_t g_wt_el3_ready; + +extern uint8_t __spm_stack_top[]; + +static uint64_t deadline_after_ms(uint32_t ms) +{ + return wt_read_cntpct_el0() + ((wt_read_cntfrq_el0() * ms) / 1000u); +} + +static uint32_t parked_mask(void) +{ + uint32_t mask = 0u; + uint32_t i; + + for (i = 0u; i < WT_EL3_MAX_CPUS; i++) { + if (g_wt_el3_parked[i] != 0u) { + mask |= (1u << i); + } + } + return mask; +} + +static uint32_t wait_for_secondaries(void) +{ + uint64_t deadline = deadline_after_ms(WT_EL3_PARK_WAIT_MS); + uint32_t mask; + + do { + mask = parked_mask(); + } while (((mask & WT_EL3_PARK_EXPECTED) != WT_EL3_PARK_EXPECTED) && + (wt_read_cntpct_el0() < deadline)); + return mask; +} + +/* One secure timer period with FIQ unmasked at EL3: the tick must arrive as + * INTID 29 through the vector table before the deadline. */ +static int prove_tick(void) +{ + uint64_t deadline = deadline_after_ms(WT_EL3_TICK_WAIT_MS); + + g_wt_el3_tick_intid = 0u; +#if !defined(WT_EL3_BOOT_NEG_PROBE) || (WT_EL3_BOOT_NEG_PROBE != 2) + wt_gic->enable(WT_GIC_INTID_SECURE_TIMER); +#endif + wt_el3_timer_arm_ms(WT_EL3_TICK_PERIOD_MS); + wt_daif_clear_fiq(); + while ((g_wt_el3_tick_intid == 0u) && (wt_read_cntpct_el0() < deadline)) { + } + wt_daif_set_fiq(); + wt_el3_timer_disable(); + wt_gic->disable(WT_GIC_INTID_SECURE_TIMER); + + if (g_wt_el3_tick_intid == WT_GIC_INTID_SECURE_TIMER) { + wt_el3_puts("[EL3] tick ok intid=29\r\n"); + return 0; + } + wt_el3_puts("[EL3] tick TIMEOUT intid="); + wt_el3_putdec(g_wt_el3_tick_intid); + wt_el3_puts("\r\n"); + return -1; +} + +/* The single-PE isolation model and Secure preemption rest on these: stop + * the boot through the monitor panic path instead of entering the Secure + * runtime. */ +/* The debug and PMU policy start.S must have set: Secure-state counting, + * tracing, profiling, and self-hosted debug all disabled, nothing trapped. + * *mask names the fields the PE implements; the value is what they must read. */ +static uint64_t wt_el3_mdcr_expected(uint64_t* mask) +{ + uint64_t dfr0 = wt_read_id_aa64dfr0_el1(); + uint64_t pmuver = (dfr0 >> 8) & 0xFu; + uint64_t want = WT_MDCR_EL3_SDD | (2ull << 14); + + *mask = WT_MDCR_EL3_TPM | WT_MDCR_EL3_TDA | WT_MDCR_EL3_TDOSA | + WT_MDCR_EL3_SPD32 | WT_MDCR_EL3_SDD | WT_MDCR_EL3_SPME | + WT_MDCR_EL3_STE; + if ((pmuver >= 6u) && (pmuver != 0xFu)) { + *mask |= WT_MDCR_EL3_SCCD; + want |= WT_MDCR_EL3_SCCD; + } + if (((dfr0 >> 32) & 0xFu) != 0u) { + *mask |= WT_MDCR_EL3_NSPB; + want |= (2ull << 12); + } + if (((dfr0 >> 44) & 0xFu) != 0u) { + *mask |= WT_MDCR_EL3_NSTB; + want |= (2ull << 24); + } + return want; +} + +static int wt_el3_mdcr_ok(void) +{ + uint64_t mask = 0u; + uint64_t want = wt_el3_mdcr_expected(&mask); + uint64_t got = wt_read_mdcr_el3(); + + if ((got & mask) == want) { + wt_el3_puts("[EL3] mdcr_el3 ok\r\n"); + return 1; + } + wt_el3_puts("[EL3] mdcr_el3 BAD 0x"); + wt_el3_puthex((uint32_t)got, 8u); + wt_el3_puts("\r\n"); + return 0; +} + +static void require_boot_invariant(int ok, uint64_t code) +{ + if (ok == 0) { + (void)wt_el3_monitor_call(WT_MON_FID_PANIC, code); + } +} + +#if defined(WT_EL3_TEST_HANDOFF) +/* Stand in for the boot loader: an unlocked-lifecycle record whose measurement + * is a fixed pattern. Never built into production images. */ +static void synthesize_test_handoff(void) +{ + wt_boot_handoff_t* rec = (wt_boot_handoff_t*)(uintptr_t)WT_PORT_HANDOFF_PA; + uint32_t i; + + (void)memset(rec, 0, WT_PORT_HANDOFF_SIZE); + rec->magic = WT_BOOT_HANDOFF_MAGIC; + rec->magic_inverse = ~WT_BOOT_HANDOFF_MAGIC; + rec->version = (uint16_t)WT_BOOT_HANDOFF_VERSION; + rec->size = (uint16_t)sizeof(*rec); + rec->lifecycle = PSA_LIFECYCLE_ASSEMBLY_AND_TEST; + rec->image_version = 1u; + rec->hash_algorithm = (uint16_t)WT_BOOT_HANDOFF_HASH_SHA256; + rec->measurement_size = (uint16_t)WT_BOOT_HANDOFF_DIGEST_SIZE; + for (i = 0u; i < WT_BOOT_HANDOFF_DIGEST_SIZE; i++) { + rec->measurement[i] = (uint8_t)(0xA0u + i); + } +} +#endif + +/* 5.4: one 4K page at the start of the SPM band; the wolfBoot handoff record + * rides an IMPDEF descriptor when the port has one (WT-PORT-0020). */ +static uint64_t build_boot_info(void) +{ + uint8_t* blob = (uint8_t*)(uintptr_t)WT_SPM_BOOT_INFO_PA; + wt_ffa_boot_info_item_t item; + uint32_t count = 0u; + uint32_t size = 0u; + int ret; + + item.source = (const void*)(uintptr_t)WT_PORT_HANDOFF_PA; + item.value = 0u; + item.name = WT_FFA_BOOT_INFO_NAME_WT_HANDOFF; + item.size = WT_PORT_HANDOFF_SIZE; + item.type = WT_FFA_BOOT_INFO_TYPE_WT_HANDOFF; + item.name_format = WT_FFA_BOOT_INFO_NAME_STRING; + item.contents_format = WT_FFA_BOOT_INFO_CONTENTS_ADDRESS; + if ((WT_PORT_HANDOFF_PA != 0u) && (WT_PORT_HANDOFF_SIZE != 0u)) { + count = 1u; + } + ret = wt_ffa_boot_info_build(blob, WT_SPM_BOOT_INFO_PA, WT_SPM_BOOT_INFO_LIMIT, + WT_FFA_VERSION_1_2, &item, count, &size); + if (ret != WT_FFA_BOOT_INFO_OK) { + wt_el3_puts("[EL3] boot info build failed\r\n"); + (void)wt_el3_monitor_call(WT_MON_FID_PANIC, WT_EL3_PANIC_BOOT_INFO); + } + if (count != 0u) { + /* The SPMC owns the only copy from here on. */ + (void)memset((void*)(uintptr_t)WT_PORT_HANDOFF_PA, 0, WT_PORT_HANDOFF_SIZE); + } + wt_el3_puts("[EL3] boot info at 0x"); + wt_el3_puthex(WT_SPM_BOOT_INFO_PA, 8u); + wt_el3_puts(" size="); + wt_el3_putdec(size); + wt_el3_puts(" descs="); + wt_el3_putdec(count); + wt_el3_puts("\r\n"); + return WT_SPM_BOOT_INFO_PA; +} + +void wt_el3_main(void) +{ + uint32_t mask; + uint32_t woken; + uint64_t boot_info; + + wt_platform_board_init(); + wt_gic->init_secure(); +#if defined(WT_GIC_SPI_ROUTE_PROBE) && (WT_GIC_SPI_ROUTE_PROBE == 1) && \ + (WT_GIC_VERSION == 3) + wt_el3_puts("[EL3] probe: every SPI routed to an absent PE\r\n"); +#endif + mask = wait_for_secondaries(); + woken = wt_gic_rdist_woken(); +#if defined(WT_EL3_BOOT_NEG_PROBE) && (WT_EL3_BOOT_NEG_PROBE == 1) + woken = 0u; +#endif + + wt_el3_puts("[EL3] wolfTrust monitor cntfrq="); + wt_el3_putdec(wt_read_cntfrq_el0()); + wt_el3_puts(" gic=v"); + wt_el3_putdec(wt_gic->version); + wt_el3_puts(" rdist_woken="); + wt_el3_putdec(woken); + wt_el3_puts(" secondaries parked mask=0x"); + wt_el3_puthex(mask, 1u); + wt_el3_puts("\r\n"); + require_boot_invariant( + (mask & WT_EL3_PARK_EXPECTED) == WT_EL3_PARK_EXPECTED, + WT_EL3_PANIC_NOT_PARKED); + require_boot_invariant(woken != 0u, WT_EL3_PANIC_RDIST_ASLEEP); + require_boot_invariant(wt_el3_mdcr_ok(), WT_EL3_PANIC_MDCR); + require_boot_invariant(prove_tick() == 0, WT_EL3_PANIC_NO_TICK); +#if defined(WT_EL3_TEST_HANDOFF) + synthesize_test_handoff(); +#endif + boot_info = build_boot_info(); + + wt_write_sctlr_el1(WT_SCTLR_EL1_RES1); +#if defined(WT_SPM_FLASH_OFFSET) + /* The SPMC image sits behind the monitor in flash; a boot loader does + * this copy on silicon. */ + (void)memcpy((void*)(uintptr_t)WT_SPM_IMAGE_PA, + (const void*)(uintptr_t)(WT_EL3_TEXT_BASE + WT_SPM_FLASH_OFFSET), + (size_t)WT_SPM_IMAGE_SIZE); +#endif +#if defined(WT_FFA_ACS_FLASH_OFFSET) + /* Conformance image only: the FF-A ACS partition images and the suite's + * test NVM ride behind the SPMC image in flash, since nothing else can + * place them in Secure RAM on this machine. The partition images are laid + * down fresh every boot, but the NVM (behind them, from WT_FFA_ACS_NVM_ + * OFFSET) records the suite's progress and must survive a reset: the + * isolation tests fault a partition on purpose and resume off it. */ + (void)memcpy((void*)(uintptr_t)WT_FFA_ACS_BASE, + (const void*)(uintptr_t)(WT_EL3_TEXT_BASE + WT_FFA_ACS_FLASH_OFFSET), + (size_t)WT_FFA_ACS_NVM_OFFSET); + if (wt_el3_reset_count() == 0u) { + (void)memcpy((void*)(uintptr_t)(WT_FFA_ACS_BASE + WT_FFA_ACS_NVM_OFFSET), + (const void*)(uintptr_t)(WT_EL3_TEXT_BASE + + WT_FFA_ACS_FLASH_OFFSET + + WT_FFA_ACS_NVM_OFFSET), + (size_t)(WT_FFA_ACS_FLASH_SIZE - WT_FFA_ACS_NVM_OFFSET)); + } +#endif + wt_el3_puts("[EL3] spmc image at 0x"); + wt_el3_puthex((uint64_t)WT_SPM_IMAGE_PA, 8u); + wt_el3_puts("\r\n"); + wt_el3_enter_secure_el1((void (*)(void))(uintptr_t)WT_SPM_IMAGE_PA, 0u, + boot_info); +} diff --git a/src/arch/aarch64/el3/esr.c b/src/arch/aarch64/el3/esr.c new file mode 100644 index 00000000..8a8206cf --- /dev/null +++ b/src/arch/aarch64/el3/esr.c @@ -0,0 +1,108 @@ +/* esr.c + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfTrust. + * + * wolfTrust 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. + * + * wolfTrust 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, see . + */ + +#include "wolftrust/arch/aarch64/esr.h" +#include "wolftrust/arch/aarch64/sysreg.h" + +wt_fault_reason_t wt_esr_classify(uint64_t esr, uint64_t far, int from_ns, + uint64_t guard_base, uint64_t guard_size) +{ + uint32_t ec = WT_ESR_EC(esr); + wt_fault_reason_t reason; + + switch (ec) { + case WT_ESR_EC_UNKNOWN: + case WT_ESR_EC_FP_ACCESS: + case WT_ESR_EC_ILLEGAL_STATE: + case WT_ESR_EC_SYSREG: + case WT_ESR_EC_BRK: + reason = WT_FAULT_ILLEGAL_INSTRUCTION; + break; + case WT_ESR_EC_IABT_LOWER: + case WT_ESR_EC_IABT_SAME: + case WT_ESR_EC_DABT_LOWER: + case WT_ESR_EC_DABT_SAME: + if (WT_ESR_FSC_IS_EXTERNAL(WT_ESR_FSC(esr))) { + reason = (from_ns != 0) ? WT_FAULT_SECURE_ESCALATION + : WT_FAULT_PLATFORM; + } + else if ((guard_size != 0u) && (far >= guard_base) && + (far < (guard_base + guard_size))) { + reason = WT_FAULT_STACK_OVERFLOW; + } + else { + reason = WT_FAULT_MEMORY_VIOLATION; + } + break; + case WT_ESR_EC_PC_ALIGN: + case WT_ESR_EC_SP_ALIGN: + reason = WT_FAULT_MEMORY_VIOLATION; + break; + default: + reason = WT_FAULT_PLATFORM; + break; + } + return reason; +} + +static size_t put_text(char* out, size_t out_size, size_t pos, const char* text) +{ + while ((*text != '\0') && ((pos + 1u) < out_size)) { + out[pos] = *text; + pos++; + text++; + } + return pos; +} + +static size_t put_hex(char* out, size_t out_size, size_t pos, uint64_t value, + unsigned int digits) +{ + static const char table[] = "0123456789abcdef"; + unsigned int shift = digits * 4u; + + while ((shift > 0u) && ((pos + 1u) < out_size)) { + shift -= 4u; + out[pos] = table[(value >> shift) & 0xFu]; + pos++; + } + return pos; +} + +size_t wt_esr_format(char* out, size_t out_size, uint32_t el, uint64_t esr, + uint64_t far) +{ + size_t pos = 0u; + + if ((out == NULL) || (out_size == 0u)) { + return 0u; + } + pos = put_text(out, out_size, pos, "[SYNC EL="); + pos = put_hex(out, out_size, pos, el, 1u); + pos = put_text(out, out_size, pos, " EC=0x"); + pos = put_hex(out, out_size, pos, WT_ESR_EC(esr), 2u); + pos = put_text(out, out_size, pos, " ISS=0x"); + pos = put_hex(out, out_size, pos, WT_ESR_ISS(esr), 7u); + pos = put_text(out, out_size, pos, " FAR=0x"); + pos = put_hex(out, out_size, pos, far, 16u); + pos = put_text(out, out_size, pos, "]"); + out[pos] = '\0'; + return pos; +} diff --git a/src/arch/aarch64/el3/monitor_calls.c b/src/arch/aarch64/el3/monitor_calls.c new file mode 100644 index 00000000..7302e710 --- /dev/null +++ b/src/arch/aarch64/el3/monitor_calls.c @@ -0,0 +1,382 @@ +/* monitor_calls.c + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfTrust. + * + * wolfTrust 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. + * + * wolfTrust 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, see . + */ + +/* EL3 vector dispatch: the SMCCC Arm Architecture Calls are answered for + * either world, FF-A calls from the Secure world go to the SPMD handlers, the + * OEM-range test calls to the monitor calls, the secure timer FIQ to the tick + * handler; everything else is a fault. */ + +#include "wolftrust/arch/aarch64/el3.h" +#include "wolftrust/arch/aarch64/ffa.h" +#include "wolftrust/arch/aarch64/ffa_abi.h" +#include "wolftrust/arch/aarch64/ffa_msg.h" +#include "wolftrust/arch/aarch64/gic.h" +#include "wolftrust/arch/aarch64/monitor_abi.h" +#include "wolftrust/arch/aarch64/psci.h" +#include "wolftrust/arch/aarch64/sysreg.h" + +volatile uint32_t g_wt_el3_tick_intid; + +#if defined(WT_EL3_TEST_DRIVER) && (WT_EL3_TEST_DRIVER == 1) +#if defined(WT_GIC_VERSION) && (WT_GIC_VERSION == 3) +WT_SYSREG_READ(icc_igrpen1_el3, "ICC_IGRPEN1_EL3") +WT_SYSREG_WRITE(icc_igrpen1_el3, "ICC_IGRPEN1_EL3") +#define WT_ICC_IGRPEN1_EL3_GRP1NS 1u + +static void test_ns_group(uint64_t on) +{ + uint64_t value = wt_read_icc_igrpen1_el3(); + + value = (on != 0u) ? (value | WT_ICC_IGRPEN1_EL3_GRP1NS) + : (value & ~(uint64_t)WT_ICC_IGRPEN1_EL3_GRP1NS); + wt_write_icc_igrpen1_el3(value); + wt_isb(); +} +#else +/* The GICv2 CPU interface already signals Group 1 to the Secure world. */ +static void test_ns_group(uint64_t on) +{ + (void)on; +} +#endif + +WT_SYSREG_READ(id_aa64pfr0_el1, "ID_AA64PFR0_EL1") +#define WT_EL3_A32_PROBE_DONE 0x830000FFu + +void wt_el3_a32_enter(uint64_t scr_el3, uintptr_t entry, const uint64_t* r); +void wt_el3_a32_leave(void) __attribute__((noreturn)); +extern const uint32_t wt_el3_a32_stub[]; +static uint32_t g_a32_result[3]; +static uint32_t g_a32_active; + +/* The stub's first three SMCs left their results in R8-R10. */ +static void a32_probe_done(const wt_el3_frame_t* frame) +{ + g_a32_active = 0u; + g_a32_result[0] = (uint32_t)frame->x[8]; + g_a32_result[1] = (uint32_t)frame->x[9]; + g_a32_result[2] = (uint32_t)frame->x[10]; + wt_el3_a32_leave(); +} + +/* Drop to an AArch32 Secure EL1 stub, SCR_EL3.RW clear and its stage 1 off, + * so its SMCs take the lower-AArch32 vector, and report their answers. */ +static void a32_probe(void) +{ + uint64_t r[8]; + uint64_t sctlr; + unsigned int i; + + if (((wt_read_id_aa64pfr0_el1() >> 4) & 0xFu) != 2u) { + wt_el3_puts("[EL3] a32 vector probe: no AArch32 EL1\r\n"); + return; + } + for (i = 0u; i < 8u; i++) { + r[i] = 0u; + } + r[0] = WT_SMCCC_VERSION; + r[4] = WT_PSCI_AFFINITY_INFO64; + r[5] = WT_PSCI_VERSION; + r[6] = WT_EL3_A32_PROBE_DONE; + sctlr = wt_read_sctlr_el1(); + wt_write_sctlr_el1(sctlr & ~(uint64_t)1u); + wt_isb(); + g_a32_active = 1u; + wt_el3_a32_enter((uint64_t)(WT_SCR_EL3_SECURE & ~WT_SCR_RW), + (uintptr_t)wt_el3_a32_stub, r); + wt_write_sctlr_el1(sctlr); + wt_isb(); + wt_el3_puts("[EL3] a32 vector smc version=0x"); + wt_el3_puthex(g_a32_result[0], 8u); + wt_el3_puts(" smc64=0x"); + wt_el3_puthex(g_a32_result[1], 8u); + wt_el3_puts(" psci=0x"); + wt_el3_puthex(g_a32_result[2], 8u); + wt_el3_puts("\r\n"); +} +#endif + +uint64_t wt_el3_monitor_call(uint32_t fid, uint64_t arg) +{ + uint64_t result = WT_MON_NOT_SUPPORTED; + + switch (fid) { + case WT_MON_FID_EXIT: +#if defined(WT_EL3_TEST_DRIVER) && (WT_EL3_TEST_DRIVER == 1) + a32_probe(); +#endif + wt_el3_puts("[BKPT] imm=0x"); + wt_el3_puthex(arg & 0xFFu, 2u); + wt_el3_puts("\r\n"); + if ((arg & 0xFFu) == WT_MON_EXIT_SUCCESS) { + wt_el3_puts("[EXPECT BKPT] Success\r\n"); + } + wt_platform_console_flush(); + wt_el3_semihost_exit(((arg & 0xFFu) == WT_MON_EXIT_SUCCESS) + ? 0u : (arg & 0xFFu)); + break; + case WT_MON_FID_PANIC: + wt_el3_puts("[EL3] panic code=0x"); + wt_el3_puthex(arg, 8u); + wt_el3_puts("\r\n"); + wt_platform_console_flush(); + wt_el3_semihost_exit(WT_MON_EXIT_PANIC); + break; + case WT_MON_FID_SYSTEM_RESET: + wt_el3_system_reset("mon"); + break; +#if defined(WT_EL3_TEST_DRIVER) && (WT_EL3_TEST_DRIVER == 1) + case WT_MON_FID_TEST_NS_GROUP: + test_ns_group(arg); + result = 0u; + break; +#endif + default: + break; + } + return result; +} + +static void wt_el3_fiq(void) +{ + uint32_t intid = wt_gic->ack_group0(); + + if (intid == WT_GIC_INTID_SECURE_TIMER) { + wt_el3_timer_disable(); + } + if (intid != WT_GIC_INTID_SPURIOUS) { + g_wt_el3_tick_intid = intid; + wt_gic->eoi_group0(intid); + } +} + +/* A Secure interrupt taken while the Normal world runs (SCR_EL3.FIQ routes it to + * EL3 as a lower-EL FIQ) stays pending in the GIC: the SPMC, which alone + * services the GIC, acknowledges it after FFA_INTERRUPT (9.1, 12.4.1 item 3) + * and yields the Normal world back afterwards. */ +static void ns_fiq(wt_el3_frame_t* frame) +{ + wt_el3_puts("[EL3] ns preempted\r\n"); + wt_platform_console_flush(); + wt_el3_world_preempt_to_secure(frame); +} + +/* SMCCC_VERSION and SMCCC_ARCH_FEATURES (DEN0028 7.2, 7.3), mandatory from + * SMCCC 1.1 whichever world calls; x4-x17 are preserved. */ +static int arch_call(wt_el3_frame_t* frame) +{ + uint32_t fid = (uint32_t)frame->x[0]; + uint32_t query = (uint32_t)frame->x[1]; + + if (fid == WT_SMCCC_VERSION) { + frame->x[0] = WT_SMCCC_VERSION_1_2; + } + else if (fid == WT_SMCCC_ARCH_FEATURES) { + /* No Arm Architecture Service call beyond these two is offered. */ + frame->x[0] = ((query == WT_SMCCC_VERSION) || + (query == WT_SMCCC_ARCH_FEATURES)) ? + 0u : (uint64_t)(uint32_t)WT_SMCCC_NOT_SUPPORTED; + } + else { + return 0; + } + frame->x[1] = 0u; + frame->x[2] = 0u; + frame->x[3] = 0u; + return 1; +} + +/* Only an AArch32 EL directly below EL3 takes this vector, which SCR_EL3.RW=1 + * rules out. Its SMC is still answered: the Arm Architecture Calls, and every + * other id unknown, as the PSCI and FF-A world switches assume AArch64. */ +static void lower32_smc(wt_el3_frame_t* frame) +{ + wt_ffa_regs_normalize(frame->x); + if (arch_call(frame) == 0) { + frame->x[0] = WT_MON_NOT_SUPPORTED; + } +} + +/* A Normal-world SMC: relayed to the SPMC, or PSCI/FF-A served here. */ +static void ns_smc(wt_el3_frame_t* frame) +{ + uint64_t msg[WT_FFA_MSG_REGS_EXT]; + wt_ffa_regs_t regs; + uint32_t fid = (uint32_t)frame->x[0]; + unsigned int i; + + wt_ffa_spmd_ns_note(fid); + /* Discovery and guest-to-SP messaging need the SPMC (the SPMD has no + * manifest): forward the call and run the Secure world. */ + if (wt_ffa_spmd_ns_forwards(fid)) { + if (wt_ffa_spmd_ns_forward(frame->x) != 0) { + wt_el3_world_forward_to_secure(frame); + } + return; + } + /* PSCI is served by the SPMD (WT-FFM-0067); a power operation first goes + * to the SPMC as a framework message (FF-A 18.2.4). */ + if (wt_psci_fid_in_range(fid)) { + for (i = 0u; i < 8u; i++) { + regs.x[i] = frame->x[i]; + } + if (wt_psci_ns_call(®s) == WT_PSCI_ACTION_MESSAGE) { + wt_ffa_fwk_pm_req(msg, fid, frame->x[1], frame->x[2], frame->x[3]); + wt_el3_world_pm_to_secure(frame, msg); + } + for (i = 0u; i < 8u; i++) { + frame->x[i] = regs.x[i]; + } + return; + } + if (wt_ffa_fid_in_range(fid)) { + for (i = 0u; i < 8u; i++) { + regs.x[i] = frame->x[i]; + } + wt_ffa_spmd_ns_call(®s); + for (i = 0u; i < 8u; i++) { + frame->x[i] = regs.x[i]; + } + wt_ffa_reply_clear_ext(fid, frame->x); + return; + } + frame->x[0] = WT_MON_NOT_SUPPORTED; +} + +static void secure_smc(wt_el3_frame_t* frame) +{ + wt_ffa_regs_t regs; + uint32_t fid = (uint32_t)frame->x[0]; + unsigned int pending; + unsigned int i; + int answer; + + wt_ffa_spmd_secure_note(fid); + /* The SPMC's answer to a paused Normal world: its reply to a forwarded call + * (deliver x0-x7) or its yield after handling a preemption (resume as-is). */ + pending = wt_el3_world_ns_pending(); + if (pending == WT_NS_PENDING_PM) { + answer = wt_ffa_spmd_pm_answer(frame->x); + if (answer == WT_SPMD_PM_REFUSED) { + for (i = 8u; i < WT_FFA_MSG_REGS_EXT; i++) { + frame->x[i] = 0u; + } + return; + } + if (answer != WT_SPMD_PM_NONE) { + wt_el3_world_pm_return_to_ns(frame, wt_psci_pm_complete( + (answer == WT_SPMD_PM_GRANTED) ? 1 : 0)); + } + } + if ((pending == WT_NS_PENDING_REPLY) && (wt_ffa_spmd_is_ns_reply(fid) != 0)) { + wt_ffa_spmd_ns_reply(frame->x); + wt_el3_world_return_to_ns(frame); + return; + } + if ((pending == WT_NS_PENDING_RESUME) && (wt_ffa_spmd_is_ns_resume(fid) != 0)) { + wt_el3_world_resume_ns(frame); + return; + } + if (fid == WT_FFA_CONSOLE_LOG64) { + /* Characters span x2-x17: use the saved frame, not the 8-register copy. */ + if (wt_ffa_spmd_secure_available(fid)) { + wt_ffa_spmd_console_call(frame->x, 1u); + } + else { + for (i = 0u; i < WT_FFA_MSG_REGS_EXT; i++) { + frame->x[i] = 0u; + } + frame->x[0] = WT_FFA_ERROR; + frame->x[2] = (uint64_t)(uint32_t)WT_FFA_NOT_SUPPORTED; + } + return; + } + if (wt_ffa_fid_in_range(fid)) { + for (i = 0u; i < 8u; i++) { + regs.x[i] = frame->x[i]; + } + if (wt_ffa_spmd_secure_call(®s) == WT_SPMD_ACTION_LAUNCH) { + /* SPMC init complete: turn on the Normal world (or exit). */ + wt_el3_world_launch_ns(frame); + return; + } + for (i = 0u; i < 8u; i++) { + frame->x[i] = regs.x[i]; + } + /* 11.2: the SPMC's hypcall completes over ERET, x8-x17 MBZ. */ + for (i = 8u; i < WT_FFA_MSG_REGS_EXT; i++) { + frame->x[i] = 0u; + } + return; + } + frame->x[0] = wt_el3_monitor_call(fid, frame->x[1]); +} + +void wt_el3_exception(uint64_t kind, wt_el3_frame_t* frame) +{ + uint64_t esr; + uint32_t ec; + + if (kind == WT_EL3_VEC_CUR_SPX_FIQ) { + wt_el3_fiq(); + return; + } + /* A Secure interrupt taken while the Normal world runs preempts it (Ch.9). */ + if (kind == WT_EL3_VEC_LOWER64_FIQ) { + ns_fiq(frame); + return; + } + esr = wt_read_esr_el3(); + ec = WT_ESR_EC(esr); + if ((kind == WT_EL3_VEC_LOWER32_SYNC) && (ec == WT_ESR_EC_SMC32)) { +#if defined(WT_EL3_TEST_DRIVER) && (WT_EL3_TEST_DRIVER == 1) + if ((g_a32_active != 0u) && + ((uint32_t)frame->x[0] == WT_EL3_A32_PROBE_DONE)) { + a32_probe_done(frame); + } +#endif + lower32_smc(frame); + return; + } + /* EC SMC32 is an SMC from AArch32, an EL1 beneath an NS-EL2 payload; it + * still takes the lower-AArch64 vector, which follows EL2's state. */ + if ((kind == WT_EL3_VEC_LOWER64_SYNC) && + ((ec == WT_ESR_EC_SMC64) || (ec == WT_ESR_EC_SMC32))) { + /* SMCCC 2.7, 5.2: an SMC64 id from AArch32 is unknown. */ + if ((ec == WT_ESR_EC_SMC32) && + (((uint32_t)frame->x[0] & 0x40000000u) != 0u)) { + frame->x[0] = WT_MON_NOT_SUPPORTED; + return; + } + /* An SMC32 call carries W1-W7 only (SMCCC 3.1): no handler, and no + * world a call is relayed to, sees the caller's upper halves. */ + wt_ffa_regs_normalize(frame->x); + if (arch_call(frame) != 0) { + return; + } + if ((wt_read_scr_el3() & WT_SCR_NS) != 0u) { + ns_smc(frame); + return; + } + secure_smc(frame); + return; + } + wt_el3_fault(kind, esr, wt_read_far_el3(), wt_read_elr_el3()); +} diff --git a/src/arch/aarch64/el3/psci.c b/src/arch/aarch64/el3/psci.c new file mode 100644 index 00000000..61f13601 --- /dev/null +++ b/src/arch/aarch64/el3/psci.c @@ -0,0 +1,273 @@ +/* psci.c + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfTrust. + * + * wolfTrust 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. + * + * wolfTrust 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, see . + */ + +/* PSCI 1.1 (DEN0022) at the NS physical instance (WT-FFM-0067). The Normal + * world's machine view (4.4) is the boot core alone, where the uniprocessor + * SPMC is resident: the secondaries the monitor keeps parked are not part of + * it, so no target_cpu but the boot core is a valid MPIDR, and the boot core + * cannot be turned off. A PSCI reply is a single value in x0. */ + +#include "wolftrust/arch/aarch64/el3.h" +#include "wolftrust/arch/aarch64/ffa.h" +#include "wolftrust/arch/aarch64/monitor_abi.h" +#include "wolftrust/arch/aarch64/psci.h" +#include "wolftrust/arch/aarch64/sysreg.h" + +/* target_cpu affinity fields (5.1.4): Aff3 and Aff2-Aff0; the rest MBZ. */ +#define WT_PSCI_AFF_MASK64 0x000000FF00FFFFFFull +#define WT_PSCI_AFF_MASK32 0x0000000000FFFFFFull + +#define WT_PSCI_TARGET_BOOT 0 +#define WT_PSCI_TARGET_INVALID (-1) + +/* A port whose reset hook returns has no machine reset. */ +#define WT_EL3_PANIC_NO_RESET 0xB5u + +/* Boot counter in the .noinit band, 0 on the first power-on. Emulator test + * builds bound their resets with WT_EL3_RESET_LIMIT so a run ends instead of + * looping; production builds leave it unset and every reset proceeds. */ +static uint32_t g_reset_count __attribute__((section(".noinit"))); + +/* DEN0022 5.11: SYSTEM_RESET is a cold reset of the machine, so only an + * emulated one, whose runner stands in for the power cycle, may end instead. */ +#if defined(WT_EL3_RESET_LIMIT) && (WT_EL3_RESET_LIMIT > 0) && \ + (!defined(WT_PORT_EMULATED) || (WT_PORT_EMULATED != 1)) +#error "WT_EL3_RESET_LIMIT ends the run on a reset: emulated targets only" +#endif + +unsigned int wt_el3_reset_count(void) +{ + return g_reset_count; +} + +void wt_el3_system_reset(const char* tag) +{ +#if defined(WT_EL3_RESET_LIMIT) && (WT_EL3_RESET_LIMIT > 0) + if (g_reset_count >= (uint32_t)WT_EL3_RESET_LIMIT) { + wt_el3_puts("[EL3] "); + wt_el3_puts(tag); + wt_el3_puts(" system_reset done\r\n"); + wt_platform_console_flush(); + (void)wt_el3_monitor_call(WT_MON_FID_EXIT, WT_MON_EXIT_SUCCESS); + } +#endif + g_reset_count++; + wt_el3_puts("[EL3] "); + wt_el3_puts(tag); + wt_el3_puts(" system_reset reboot\r\n"); + wt_platform_console_flush(); + /* DEN0022 5.11: a cold reset of the caller's machine, never a warm + * re-entry of the firmware; the port's hook does not return. */ + wt_platform_board_system_reset(); + (void)wt_el3_monitor_call(WT_MON_FID_PANIC, WT_EL3_PANIC_NO_RESET); +} + +/* SMCCC 1.1 and later preserve x4-x17 across a call that returns only x0. */ +static void psci_return(wt_ffa_regs_t* r, uint64_t x0) +{ + unsigned int i; + + for (i = 1u; i < 4u; i++) { + r->x[i] = 0u; + } + r->x[0] = x0; +} + +static int psci_is_smc64(uint32_t fid) +{ + return (fid & 0x40000000u) != 0u; +} + +/* An int32 PSCI result (DEN0022 5.2.1) is W0 for SMC32; SMC64 returns it as a + * 64-bit signed X0 (DEN0028 2.8, 5.1), so negative codes are sign-extended. */ +static uint64_t psci_status(uint32_t fid, int64_t status) +{ + if (psci_is_smc64(fid)) { + return (uint64_t)status; + } + return (uint64_t)(uint32_t)status; +} + +/* Classify a target_cpu: the boot core, or an MPIDR outside the Normal + * world's machine view (a parked secondary is one). */ +static int psci_target(uint64_t target, uint32_t fid) +{ + uint64_t mask = psci_is_smc64(fid) ? WT_PSCI_AFF_MASK64 : WT_PSCI_AFF_MASK32; + uint64_t self = wt_read_mpidr_el1() & mask; + + if (!psci_is_smc64(fid)) { + target &= 0xFFFFFFFFull; + } + /* Only self is masked: a target with an MBZ bit (5.1.4) never equals it. */ + if (target == self) { + return WT_PSCI_TARGET_BOOT; + } + return WT_PSCI_TARGET_INVALID; +} + +/* The only core in the machine view is on, so no OFF-to-ON path exists. */ +static int64_t psci_cpu_on(uint64_t target, uint32_t fid) +{ + if (psci_target(target, fid) == WT_PSCI_TARGET_BOOT) { + return WT_PSCI_ALREADY_ON; + } + return WT_PSCI_INVALID_PARAMS; +} + +/* The resident SPMC is not migrate capable (5.9.1), and a target outside the + * machine view is an invalid MPIDR (5.8.2). */ +static int64_t psci_migrate(uint64_t target, uint32_t fid) +{ + if (psci_target(target, fid) == WT_PSCI_TARGET_BOOT) { + return WT_PSCI_DENIED; + } + return WT_PSCI_INVALID_PARAMS; +} + +static int64_t psci_affinity_info(uint64_t target, uint64_t level, uint32_t fid) +{ + int kind; + + /* From PSCI 1.0 only level 0 must be supported (5.7.1). */ + if ((uint32_t)level != 0u) { + return WT_PSCI_INVALID_PARAMS; + } + kind = psci_target(target, fid); + if (kind == WT_PSCI_TARGET_BOOT) { + return WT_PSCI_AFFINITY_ON; + } + return WT_PSCI_INVALID_PARAMS; +} + +/* The power operation whose message is outstanding. */ +static uint32_t g_pm_fid; + +uint64_t wt_psci_pm_complete(int granted) +{ + uint32_t fid = g_pm_fid; + + g_pm_fid = 0u; + if (granted == 0) { + return psci_status(fid, WT_PSCI_DENIED); + } + switch (fid) { + case WT_PSCI_CPU_SUSPEND32: + case WT_PSCI_CPU_SUSPEND64: + /* Core standby is the only state offered: a WFI (5.4.9). */ + __asm__ volatile("dsb sy\n\twfi" ::: "memory"); + return psci_status(fid, WT_PSCI_SUCCESS); + case WT_PSCI_SYSTEM_OFF: + wt_el3_puts("[EL3] psci system_off\r\n"); + wt_platform_console_flush(); + (void)wt_el3_monitor_call(WT_MON_FID_EXIT, WT_MON_EXIT_SUCCESS); + break; + case WT_PSCI_SYSTEM_RESET: + wt_el3_system_reset("psci"); + break; + default: + /* CPU_OFF: the uniprocessor SPMC is resident on the only core. */ + break; + } + return psci_status(fid, WT_PSCI_DENIED); +} + +static int psci_implements(uint32_t fid) +{ + switch (fid) { + case WT_PSCI_VERSION: + case WT_PSCI_CPU_SUSPEND32: + case WT_PSCI_CPU_SUSPEND64: + case WT_PSCI_CPU_OFF: + case WT_PSCI_CPU_ON32: + case WT_PSCI_CPU_ON64: + case WT_PSCI_AFFINITY_INFO32: + case WT_PSCI_AFFINITY_INFO64: + case WT_PSCI_MIGRATE32: + case WT_PSCI_MIGRATE64: + case WT_PSCI_MIGRATE_INFO_TYPE: + case WT_PSCI_MIGRATE_INFO_UP_CPU32: + case WT_PSCI_MIGRATE_INFO_UP_CPU64: + case WT_PSCI_SYSTEM_OFF: + case WT_PSCI_SYSTEM_RESET: + case WT_PSCI_FEATURES: + case WT_SMCCC_VERSION: + return 1; + default: + return 0; + } +} + +int wt_psci_ns_call(wt_ffa_regs_t* r) +{ + uint32_t fid = (uint32_t)r->x[0]; + + switch (fid) { + case WT_PSCI_VERSION: + psci_return(r, (uint64_t)WT_PSCI_VERSION_1_1); + break; + case WT_PSCI_FEATURES: + /* Every implemented function reports flags 0: CPU_SUSPEND uses the + * original power_state format, platform-coordinated only. */ + psci_return(r, psci_implements((uint32_t)r->x[1]) ? + (uint64_t)(uint32_t)WT_PSCI_SUCCESS : + (uint64_t)(uint32_t)WT_PSCI_NOT_SUPPORTED); + break; + case WT_PSCI_CPU_SUSPEND32: + case WT_PSCI_CPU_SUSPEND64: + if ((uint32_t)r->x[1] != WT_PSCI_STATE_CORE_STANDBY) { + psci_return(r, psci_status(fid, WT_PSCI_INVALID_PARAMS)); + break; + } + g_pm_fid = fid; + return WT_PSCI_ACTION_MESSAGE; + case WT_PSCI_CPU_OFF: + case WT_PSCI_SYSTEM_OFF: + case WT_PSCI_SYSTEM_RESET: + g_pm_fid = fid; + return WT_PSCI_ACTION_MESSAGE; + case WT_PSCI_CPU_ON32: + case WT_PSCI_CPU_ON64: + psci_return(r, psci_status(fid, psci_cpu_on(r->x[1], fid))); + break; + case WT_PSCI_AFFINITY_INFO32: + case WT_PSCI_AFFINITY_INFO64: + psci_return(r, psci_status(fid, psci_affinity_info(r->x[1], + r->x[2], fid))); + break; + case WT_PSCI_MIGRATE32: + case WT_PSCI_MIGRATE64: + psci_return(r, psci_status(fid, psci_migrate(r->x[1], fid))); + break; + case WT_PSCI_MIGRATE_INFO_TYPE: + psci_return(r, (uint64_t)WT_PSCI_TOS_UP_NOT_MIGRATABLE); + break; + case WT_PSCI_MIGRATE_INFO_UP_CPU32: + case WT_PSCI_MIGRATE_INFO_UP_CPU64: + psci_return(r, wt_read_mpidr_el1() & + (psci_is_smc64(fid) ? WT_PSCI_AFF_MASK64 : + WT_PSCI_AFF_MASK32)); + break; + default: + /* SMCCC 5.2: the unknown-function result is -1 sign-extended. */ + psci_return(r, (uint64_t)(int64_t)WT_PSCI_NOT_SUPPORTED); + break; + } + return WT_PSCI_ACTION_REPLY; +} diff --git a/src/arch/aarch64/el3/start.S b/src/arch/aarch64/el3/start.S new file mode 100644 index 00000000..10699fe0 --- /dev/null +++ b/src/arch/aarch64/el3/start.S @@ -0,0 +1,394 @@ +/* start.S + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfTrust. + * + * wolfTrust 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. + * + * wolfTrust 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, see . + */ + +/* EL3 reset entry: park every core but the boot core, set up EL3 with the + * MMU and caches off, initialize the image, and call wt_el3_main. */ + +#define SCTLR_EL3_INIT (0x30C50830 | (1 << 12) | (1 << 3)) +#define CNTHCTL_EL2_INIT 0x3 +#define HCR_EL2_RW 0x80000000 +/* No EL2 traps: bits 13, 9 and 7:0 are RES1 (TZ/TSM too without SVE/SME). */ +#define CPTR_EL2_INIT 0x22FF +/* RW | ST | EA | FIQ: Group 0 interrupts come to EL3 until the Secure world + * runs (an FIQ whose target EL is below the current EL stays pending). */ +#define SCR_EL3_INIT 0xC0C +/* SDD | SPD32 = disabled: Secure self-hosted debug off in both states. */ +#define MDCR_EL3_INIT 0x18000 +/* SPME | STE | NSPB = NSTB = enabled in Secure | TPM | TDA | TDOSA */ +#define MDCR_EL3_DIRTY 0x3063640 + + .section .el3.text.entry, "ax" + .globl wt_el3_start +wt_el3_start: + mrs x0, CurrentEL + lsr x0, x0, #2 + cmp x0, #3 + b.ne wrong_el + mrs x0, MPIDR_EL1 + and x1, x0, #0xffffff + cbz x1, primary + + /* Secondary: report only after the boot core has cleared .bss. */ + ldr x2, =g_wt_el3_ready +7: ldr w3, [x2] + cbnz w3, 8f + wfe + b 7b +8: and x1, x0, #3 + ldr x2, =g_wt_el3_parked + mov w3, #1 + strb w3, [x2, x1] + dsb sy + sev + /* A warm reset re-clears .bss and then signals: report again on every + * wake so the re-entered boot core counts this core too. */ +9: wfe + strb w3, [x2, x1] + dsb sy + b 9b + +primary: + mov x1, #0 + mov x2, #0 + mov x3, #0 + mov x4, #0 + mov x5, #0 + mov x6, #0 + mov x7, #0 + mov x8, #0 + mov x9, #0 + mov x10, #0 + mov x11, #0 + mov x12, #0 + mov x13, #0 + mov x14, #0 + mov x15, #0 + mov x16, #0 + mov x17, #0 + mov x18, #0 + mov x19, #0 + mov x20, #0 + mov x21, #0 + mov x22, #0 + mov x23, #0 + mov x24, #0 + mov x25, #0 + mov x26, #0 + mov x27, #0 + mov x28, #0 + mov x29, #0 + mov x30, #0 + + msr DAIFSet, #0xf + ldr x0, =wt_el3_vectors + msr VBAR_EL3, x0 + mov x0, #SCR_EL3_INIT + msr SCR_EL3, x0 + msr SPSel, #1 + ldr x0, =__el3_stack_top + mov sp, x0 + + msr CPTR_EL3, xzr +#if defined(WT_EL3_EL2_DIRTY_PROBE) && (WT_EL3_EL2_DIRTY_PROBE == 1) + /* Test only: stand in for a stage that left Secure counting, tracing, + * profiling, and self-hosted debug open and PMU accesses trapped. */ + ldr x0, =MDCR_EL3_DIRTY + msr MDCR_EL3, x0 +#endif + /* MDCR_EL3: no Secure-state PMU counting, tracing, or profiling, no + * Secure self-hosted debug, no traps; the fields a missing PMU, SPE, or + * TRBE leaves RES0 stay clear (wt_el3_mdcr_expected mirrors this). */ + mov x0, #MDCR_EL3_INIT + mrs x1, ID_AA64DFR0_EL1 + ubfx x2, x1, #8, #4 + cmp x2, #6 + b.lo 3f + cmp x2, #0xf + b.eq 3f + orr x0, x0, #(1 << 23) +3: ubfx x2, x1, #32, #4 + cbz x2, 4f + orr x0, x0, #(2 << 12) +4: ubfx x2, x1, #44, #4 + cbz x2, 5f + orr x0, x0, #(2 << 24) +5: msr MDCR_EL3, x0 + tlbi alle3 + ic iallu + dsb sy + isb + ldr x0, =SCTLR_EL3_INIT + msr SCTLR_EL3, x0 + dsb sy + isb + +#if !defined(WT_PORT_CNTFRQ_KEEP) || (WT_PORT_CNTFRQ_KEEP == 0) + ldr x0, =WT_PORT_CNTFRQ_HZ + msr CNTFRQ_EL0, x0 +#endif + msr CNTVOFF_EL2, xzr + mov x0, #CNTHCTL_EL2_INIT + msr CNTHCTL_EL2, x0 + + /* EL2 is unused but, when implemented, still applies to NS-EL1: give its + * controls known values instead of whatever an earlier stage left. */ + mrs x1, ID_AA64PFR0_EL1 + ubfx x1, x1, #8, #4 + cbz x1, 15f +#if defined(WT_EL3_EL2_DIRTY_PROBE) && (WT_EL3_EL2_DIRTY_PROBE == 1) + /* Test only: stand in for a stage that left SMC trapped, a foreign + * virtual MPIDR, and the EL2 GIC system-register interface off behind. */ + mov x0, #HCR_EL2_RW + orr x0, x0, #(1 << 19) + msr HCR_EL2, x0 + mrs x0, MPIDR_EL1 + eor x0, x0, #0x100 + msr VMPIDR_EL2, x0 +#if defined(WT_GIC_VERSION) && (WT_GIC_VERSION == 3) + msr ICC_SRE_EL2, xzr +#endif +#endif + mov x0, #HCR_EL2_RW + msr HCR_EL2, x0 + msr HSTR_EL2, xzr + msr VTTBR_EL2, xzr + mrs x0, MIDR_EL1 + msr VPIDR_EL2, x0 + mrs x0, MPIDR_EL1 + msr VMPIDR_EL2, x0 + mov x0, #CPTR_EL2_INIT + mrs x1, ID_AA64PFR0_EL1 + ubfx x1, x1, #32, #4 + cbnz x1, 10f + orr x0, x0, #(1 << 8) +10: mrs x1, ID_AA64PFR1_EL1 + ubfx x1, x1, #24, #4 + cbnz x1, 11f + orr x0, x0, #(1 << 12) +11: msr CPTR_EL2, x0 + /* MDCR_EL2: no traps; EL1 owns every PMU counter and any SPE/TRBE. */ + mov x0, #0 + mrs x1, ID_AA64DFR0_EL1 + ubfx x2, x1, #8, #4 + cbz x2, 12f + cmp x2, #0xf + b.eq 12f + mrs x2, PMCR_EL0 + ubfx x0, x2, #11, #5 +12: ubfx x2, x1, #32, #4 + cbz x2, 13f + orr x0, x0, #(3 << 12) +13: ubfx x2, x1, #44, #4 + cbz x2, 14f + orr x0, x0, #(3 << 24) +14: msr MDCR_EL2, x0 +15: + ldr x0, =__data_lma + ldr x1, =__data_start + ldr x2, =__data_end +1: cmp x1, x2 + b.hs 2f + ldr x3, [x0], #8 + str x3, [x1], #8 + b 1b +2: ldr x0, =__bss_start + ldr x1, =__bss_end +3: cmp x0, x1 + b.hs 4f + str xzr, [x0], #8 + b 3b +4: ldr x0, =g_wt_el3_ready + mov w1, #1 + str w1, [x0] + dsb sy + sev + bl wt_el3_main + b halt + +wrong_el: + mov x0, #2 + b wt_el3_semihost_exit + +halt: + wfi + b halt + +/* void wt_el3_semihost_exit(uint64_t code): SYS_EXIT through hlt #0xf000. */ + .section .el3.text, "ax" + .globl wt_el3_semihost_exit +wt_el3_semihost_exit: + ldr x1, =g_wt_el3_exit_block + ldr x2, =0x20026 + str x2, [x1] + str x0, [x1, #8] + mov x0, #0x18 + hlt #0xf000 + b halt + +/* void wt_el3_enter_secure_el1(void (*entry)(void), uintptr_t stack_top, + * uint64_t x0_arg): x0_arg reaches S-EL1 in x0. */ + .globl wt_el3_enter_secure_el1 +wt_el3_enter_secure_el1: + msr SP_EL1, x1 + msr ELR_EL3, x0 + mov x3, #0x3c5 + msr SPSR_EL3, x3 + mov x3, #0xc08 + msr SCR_EL3, x3 + mov x0, x2 + mov x1, #0 + mov x2, #0 + mov x3, #0 + isb + eret + +/* void wt_el3_enter_ns(void (*entry)(void), uintptr_t sp, uint64_t x0_arg): + * drop to NS-EL1. SCR_EL3 = NS|FIQ|EA|RW|ST (0xc0d): a Secure interrupt taken + * while the Normal world runs traps to the SPMD, so it can preempt NS (Ch.9). + * EL2 is implemented on these machines, so HCR_EL2.RW selects the EL1 register + * width; set it for AArch64 or the ERET is an illegal state change. */ + .globl wt_el3_enter_ns +wt_el3_enter_ns: + mov x4, #HCR_EL2_RW + msr HCR_EL2, x4 + msr SP_EL1, x1 + msr ELR_EL3, x0 + mov x3, #0x3c5 + msr SPSR_EL3, x3 + mov x3, #0xc0d + msr SCR_EL3, x3 + mov x0, x2 + mov x1, #0 + mov x2, #0 + mov x3, #0 + isb + eret + +/* void wt_el3_world_eret(const wt_el3_frame_t* frame, uint64_t scr_el3): + * enter a saved world. Program SCR_EL3 (and HCR_EL2 = RW for a Normal world at + * EL1; an EL2 world, SCR_EL3.HCE, owns HCR_EL2 and may run its EL1 in + * AArch32), reset the EL3 stack (this never returns, so the handler's stack is + * abandoned), load the full saved register file plus ELR and SPSR, then ERET. + * The ISB before the ERET matches wt_el3_enter_ns. */ + .globl wt_el3_world_eret +wt_el3_world_eret: + tbz x1, #0, 1f + tbnz x1, #8, 1f + mov x2, #HCR_EL2_RW + msr HCR_EL2, x2 +1: msr SCR_EL3, x1 + ldr x2, [x0, #248] + msr ELR_EL3, x2 + ldr x2, [x0, #256] + msr SPSR_EL3, x2 + ldr x2, =__el3_stack_top + mov sp, x2 + ldp x2, x3, [x0, #16] + ldp x4, x5, [x0, #32] + ldp x6, x7, [x0, #48] + ldp x8, x9, [x0, #64] + ldp x10, x11, [x0, #80] + ldp x12, x13, [x0, #96] + ldp x14, x15, [x0, #112] + ldp x16, x17, [x0, #128] + ldp x18, x19, [x0, #144] + ldp x20, x21, [x0, #160] + ldp x22, x23, [x0, #176] + ldp x24, x25, [x0, #192] + ldp x26, x27, [x0, #208] + ldp x28, x29, [x0, #224] + ldr x30, [x0, #240] + ldr x1, [x0, #8] + ldr x0, [x0, #0] + isb + eret + +#if defined(WT_EL3_TEST_DRIVER) && (WT_EL3_TEST_DRIVER == 1) +/* void wt_el3_a32_enter(uint64_t scr_el3, uintptr_t entry, const uint64_t* r): + * ERET to AArch32 Secure EL1 SVC mode at entry with r[0..7] in R0-R7, until + * wt_el3_a32_leave, run from the handler of one of its SMCs, returns here. */ + .globl wt_el3_a32_enter +wt_el3_a32_enter: + ldr x9, =g_wt_el3_a32_ctx + stp x19, x20, [x9, #0] + stp x21, x22, [x9, #16] + stp x23, x24, [x9, #32] + stp x25, x26, [x9, #48] + stp x27, x28, [x9, #64] + stp x29, x30, [x9, #80] + mov x10, sp + mrs x11, SCR_EL3 + stp x10, x11, [x9, #96] + msr SCR_EL3, x0 + msr ELR_EL3, x1 + mov x3, #0x1d3 + msr SPSR_EL3, x3 + mov x9, x2 + ldp x0, x1, [x9, #0] + ldp x2, x3, [x9, #16] + ldp x4, x5, [x9, #32] + ldp x6, x7, [x9, #48] + isb + eret + +/* void wt_el3_a32_leave(void): abandon the handler's stack and return from + * wt_el3_a32_enter with its SCR_EL3 and callee-saved registers back. */ + .globl wt_el3_a32_leave +wt_el3_a32_leave: + ldr x9, =g_wt_el3_a32_ctx + ldp x10, x11, [x9, #96] + msr SCR_EL3, x11 + isb + mov sp, x10 + ldp x19, x20, [x9, #0] + ldp x21, x22, [x9, #16] + ldp x23, x24, [x9, #32] + ldp x25, x26, [x9, #48] + ldp x27, x28, [x9, #64] + ldp x29, x30, [x9, #80] + ret + +/* A32: SMC with the ids in R0, R4, R5 (results kept in R8-R10), then the + * done id in R6. */ + .balign 4 + .globl wt_el3_a32_stub +wt_el3_a32_stub: + .word 0xe1600070 /* smc #0 */ + .word 0xe1a08000 /* mov r8, r0 */ + .word 0xe1a00004 /* mov r0, r4 */ + .word 0xe1600070 /* smc #0 */ + .word 0xe1a09000 /* mov r9, r0 */ + .word 0xe1a00005 /* mov r0, r5 */ + .word 0xe1600070 /* smc #0 */ + .word 0xe1a0a000 /* mov r10, r0 */ + .word 0xe1a00006 /* mov r0, r6 */ + .word 0xe1600070 /* smc #0 */ + .word 0xeafffffe /* b . */ +#endif + + .bss + .balign 16 +g_wt_el3_exit_block: + .skip 16 +#if defined(WT_EL3_TEST_DRIVER) && (WT_EL3_TEST_DRIVER == 1) + .balign 16 +g_wt_el3_a32_ctx: + .skip 112 +#endif diff --git a/src/arch/aarch64/el3/timer.c b/src/arch/aarch64/el3/timer.c new file mode 100644 index 00000000..74c5bedc --- /dev/null +++ b/src/arch/aarch64/el3/timer.c @@ -0,0 +1,44 @@ +/* timer.c + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfTrust. + * + * wolfTrust 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. + * + * wolfTrust 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, see . + */ + +/* Secure physical timer (CNTPS, PPI 29) as seen from EL3. */ + +#include "wolftrust/arch/aarch64/el3.h" +#include "wolftrust/arch/aarch64/sysreg.h" + +WT_SYSREG_WRITE(cntps_ctl_el1, "CNTPS_CTL_EL1") +WT_SYSREG_WRITE(cntps_tval_el1, "CNTPS_TVAL_EL1") + +#define CNTPS_CTL_ENABLE (1u << 0) + +void wt_el3_timer_arm_ms(uint32_t ms) +{ + uint64_t ticks = (wt_read_cntfrq_el0() * ms) / 1000u; + + wt_write_cntps_tval_el1(ticks); + wt_write_cntps_ctl_el1(CNTPS_CTL_ENABLE); + wt_isb(); +} + +void wt_el3_timer_disable(void) +{ + wt_write_cntps_ctl_el1(0u); + wt_isb(); +} diff --git a/src/arch/aarch64/el3/vectors.S b/src/arch/aarch64/el3/vectors.S new file mode 100644 index 00000000..2c5926ed --- /dev/null +++ b/src/arch/aarch64/el3/vectors.S @@ -0,0 +1,98 @@ +/* vectors.S + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfTrust. + * + * wolfTrust 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. + * + * wolfTrust 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, see . + */ + +/* EL3 vector table: every slot saves the full x0-x30, ELR, SPSR into a + * wt_el3_frame_t on the EL3 stack and hands its slot index plus the frame to + * wt_el3_exception, which edits the frame in place; the frame is restored. The + * whole register file is captured so a world switch can resume a lower EL from + * the frame alone (x19-x28 carry the SPMC's neutral-core state across NS). */ + + .macro WT_VECTOR index + .balign 0x80 + sub sp, sp, #272 + stp x0, x1, [sp, #0] + stp x2, x3, [sp, #16] + stp x4, x5, [sp, #32] + stp x6, x7, [sp, #48] + stp x8, x9, [sp, #64] + stp x10, x11, [sp, #80] + stp x12, x13, [sp, #96] + stp x14, x15, [sp, #112] + stp x16, x17, [sp, #128] + stp x18, x19, [sp, #144] + stp x20, x21, [sp, #160] + stp x22, x23, [sp, #176] + stp x24, x25, [sp, #192] + stp x26, x27, [sp, #208] + stp x28, x29, [sp, #224] + mrs x0, ELR_EL3 + mrs x1, SPSR_EL3 + stp x30, x0, [sp, #240] + str x1, [sp, #256] + mov x0, #\index + b wt_el3_vector_common + .endm + + .section .el3.vectors, "ax" + .balign 0x800 + .globl wt_el3_vectors +wt_el3_vectors: + WT_VECTOR 0 + WT_VECTOR 1 + WT_VECTOR 2 + WT_VECTOR 3 + WT_VECTOR 4 + WT_VECTOR 5 + WT_VECTOR 6 + WT_VECTOR 7 + WT_VECTOR 8 + WT_VECTOR 9 + WT_VECTOR 10 + WT_VECTOR 11 + WT_VECTOR 12 + WT_VECTOR 13 + WT_VECTOR 14 + WT_VECTOR 15 + + .section .el3.text, "ax" +wt_el3_vector_common: + mov x1, sp + bl wt_el3_exception + ldr x1, [sp, #256] + ldp x30, x0, [sp, #240] + msr ELR_EL3, x0 + msr SPSR_EL3, x1 + ldp x28, x29, [sp, #224] + ldp x26, x27, [sp, #208] + ldp x24, x25, [sp, #192] + ldp x22, x23, [sp, #176] + ldp x20, x21, [sp, #160] + ldp x18, x19, [sp, #144] + ldp x16, x17, [sp, #128] + ldp x14, x15, [sp, #112] + ldp x12, x13, [sp, #96] + ldp x10, x11, [sp, #80] + ldp x8, x9, [sp, #64] + ldp x6, x7, [sp, #48] + ldp x4, x5, [sp, #32] + ldp x2, x3, [sp, #16] + ldp x0, x1, [sp, #0] + add sp, sp, #272 + eret diff --git a/src/arch/aarch64/el3/world.c b/src/arch/aarch64/el3/world.c new file mode 100644 index 00000000..34bd7ac6 --- /dev/null +++ b/src/arch/aarch64/el3/world.c @@ -0,0 +1,274 @@ +/* world.c + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfTrust. + * + * wolfTrust 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. + * + * wolfTrust 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, see . + */ + +/* Normal/Secure world switching at EL3. EL1 system registers are not banked by + * security state on these cores (no Secure-EL2), so each world's full register + * file and EL1 context are saved and restored around every SPMD entry. A guest + * FF-A call the SPMD must forward resumes the SPMC where it blocked in its idle + * FFA_MSG_WAIT; the SPMC's reply is delivered back to the guest the same way. + * The switch edits the live trap frame and the vector epilogue ERETs into the + * chosen world. */ + +#include "wolftrust/arch/aarch64/el3.h" +#include "wolftrust/arch/aarch64/ffa.h" +#include "wolftrust/arch/aarch64/ffa_abi.h" +#include "wolftrust/arch/aarch64/gic.h" +#include "wolftrust/arch/aarch64/monitor_abi.h" +#include "wolftrust/arch/aarch64/sysreg.h" + +#define WT_WORLD_SECURE 0u +#define WT_WORLD_NS 1u + +/* Period after which the Secure timer preempts the Normal world (ffa-preempt). */ +#ifndef WT_NS_PREEMPT_MS +#define WT_NS_PREEMPT_MS 50u +#endif + +static wt_el3_world_t g_world[2]; +static unsigned int g_world_cur = WT_WORLD_SECURE; +static unsigned int g_ns_pending = WT_NS_PENDING_NONE; +static uint32_t g_ns_forwarded_fid; + +static void world_save(wt_el3_world_t* w, const wt_el3_frame_t* frame) +{ + w->frame = *frame; + w->sp_el0 = wt_read_sp_el0(); + w->sp_el1 = wt_read_sp_el1(); + w->sctlr_el1 = wt_read_sctlr_el1(); + w->ttbr0_el1 = wt_read_ttbr0_el1(); + w->ttbr1_el1 = wt_read_ttbr1_el1(); + w->tcr_el1 = wt_read_tcr_el1(); + w->mair_el1 = wt_read_mair_el1(); + w->amair_el1 = wt_read_amair_el1(); + w->vbar_el1 = wt_read_vbar_el1(); + w->tpidr_el0 = wt_read_tpidr_el0(); + w->tpidrro_el0 = wt_read_tpidrro_el0(); + w->tpidr_el1 = wt_read_tpidr_el1(); + w->contextidr_el1 = wt_read_contextidr_el1(); + w->cpacr_el1 = wt_read_cpacr_el1(); + w->elr_el1 = wt_read_elr_el1(); + w->spsr_el1 = wt_read_spsr_el1(); + w->esr_el1 = wt_read_esr_el1(); + w->far_el1 = wt_read_far_el1(); + w->par_el1 = wt_read_par_el1(); + w->mdscr_el1 = wt_read_mdscr_el1(); + w->cntkctl_el1 = wt_read_cntkctl_el1(); +} + +/* Restore a world's EL1 context and ERET into it (never returns). SCR_EL3, + * HCR_EL2.RW, the register file, ELR, and SPSR are programmed in the assembly + * primitive immediately before the ERET; the EL1 system registers are written + * here first (they take effect for EL1 across the exception return). */ +static void world_restore(const wt_el3_world_t* w) __attribute__((noreturn)); +static void world_restore(const wt_el3_world_t* w) +{ + wt_write_sp_el0(w->sp_el0); + wt_write_sp_el1(w->sp_el1); + wt_write_sctlr_el1(w->sctlr_el1); + wt_write_ttbr0_el1(w->ttbr0_el1); + wt_write_ttbr1_el1(w->ttbr1_el1); + wt_write_tcr_el1(w->tcr_el1); + wt_write_mair_el1(w->mair_el1); + wt_write_amair_el1(w->amair_el1); + wt_write_vbar_el1(w->vbar_el1); + wt_write_tpidr_el0(w->tpidr_el0); + wt_write_tpidrro_el0(w->tpidrro_el0); + wt_write_tpidr_el1(w->tpidr_el1); + wt_write_contextidr_el1(w->contextidr_el1); + wt_write_cpacr_el1(w->cpacr_el1); + wt_write_elr_el1(w->elr_el1); + wt_write_spsr_el1(w->spsr_el1); + wt_write_esr_el1(w->esr_el1); + wt_write_far_el1(w->far_el1); + wt_write_par_el1(w->par_el1); + wt_write_mdscr_el1(w->mdscr_el1); + wt_write_cntkctl_el1(w->cntkctl_el1); + wt_el3_world_eret(&w->frame, w->scr_el3); +} + +static void world_switch(wt_el3_frame_t* frame, unsigned int to) + __attribute__((noreturn)); +static void world_switch(wt_el3_frame_t* frame, unsigned int to) +{ + world_save(&g_world[g_world_cur], frame); + g_world_cur = to; + world_restore(&g_world[to]); +} + +#if defined(WT_EL3_NS_SMOKE) && (WT_EL3_NS_SMOKE == 1) +/* A fresh NS-EL1 payload: MMU off, entry at the port NS image base, EL1h with + * DAIF masked; the payload maps its own memory and sets its own stack. */ +static void world_init_ns(wt_el3_world_t* w) +{ + unsigned int i; + + for (i = 0u; i < 31u; i++) { + w->frame.x[i] = 0u; + } + w->frame.elr = (uint64_t)WT_NS_IMAGE_PA; +#if defined(WT_EL3_NS_EL2) && (WT_EL3_NS_EL2 == 1) + w->frame.spsr = WT_SPSR_EL2H_DAIF; + w->scr_el3 = WT_SCR_EL3_NS | WT_SCR_HCE; +#else + w->frame.spsr = WT_SPSR_EL1H_DAIF; + w->scr_el3 = WT_SCR_EL3_NS; +#endif + w->frame.pad = 0u; + w->sp_el0 = 0u; + w->sp_el1 = 0u; + w->sctlr_el1 = WT_SCTLR_EL1_RES1; + w->ttbr0_el1 = 0u; + w->ttbr1_el1 = 0u; + w->tcr_el1 = 0u; + w->mair_el1 = 0u; + w->amair_el1 = 0u; + w->vbar_el1 = 0u; + w->tpidr_el0 = 0u; + w->tpidrro_el0 = 0u; + w->tpidr_el1 = 0u; + w->contextidr_el1 = 0u; + w->cpacr_el1 = 0u; + w->elr_el1 = 0u; + w->spsr_el1 = 0u; + w->esr_el1 = 0u; + w->far_el1 = 0u; + w->par_el1 = 0u; + w->mdscr_el1 = 0u; + w->cntkctl_el1 = 0u; +} +#endif + +unsigned int wt_el3_world_ns_pending(void) +{ + return g_ns_pending; +} + +static void world_to_secure(wt_el3_frame_t* frame, const uint64_t* msg, + unsigned int pending) __attribute__((noreturn)); +static void world_to_secure(wt_el3_frame_t* frame, const uint64_t* msg, + unsigned int pending) +{ + unsigned int count = wt_ffa_msg_reg_count(msg[0]); + unsigned int i; + + /* 11.2: over ERET every unused parameter register is MBZ, so nothing the + * SPMC left in x8-x17 at its last SMC survives into an 8-register call. */ + for (i = 0u; i < count; i++) { + g_world[WT_WORLD_SECURE].frame.x[i] = msg[i]; + } + for (i = count; i < WT_FFA_MSG_REGS_EXT; i++) { + g_world[WT_WORLD_SECURE].frame.x[i] = 0u; + } + g_ns_forwarded_fid = (uint32_t)msg[0]; + g_ns_pending = pending; + world_switch(frame, WT_WORLD_SECURE); +} + +void wt_el3_world_forward_to_secure(wt_el3_frame_t* frame) +{ + world_to_secure(frame, frame->x, WT_NS_PENDING_REPLY); +} + +void wt_el3_world_pm_to_secure(wt_el3_frame_t* frame, const uint64_t* msg) +{ + world_to_secure(frame, msg, WT_NS_PENDING_PM); +} + +void wt_el3_world_pm_return_to_ns(wt_el3_frame_t* frame, uint64_t x0) +{ + unsigned int i; + + g_world[WT_WORLD_NS].frame.x[0] = x0; + for (i = 1u; i < 4u; i++) { + g_world[WT_WORLD_NS].frame.x[i] = 0u; + } + g_ns_pending = WT_NS_PENDING_NONE; + world_switch(frame, WT_WORLD_NS); +} + +void wt_el3_world_preempt_to_secure(wt_el3_frame_t* frame) +{ + unsigned int i; + + /* Hand the SPMC an FFA_INTERRUPT event with w1/w2 MBZ; the preempted NS + * context is saved by the switch and resumed unchanged once the SPMC yields + * the Normal world. */ + for (i = 0u; i < WT_FFA_MSG_REGS_EXT; i++) { + g_world[WT_WORLD_SECURE].frame.x[i] = 0u; + } + g_world[WT_WORLD_SECURE].frame.x[0] = WT_FFA_INTERRUPT; + g_ns_pending = WT_NS_PENDING_RESUME; + world_switch(frame, WT_WORLD_SECURE); +} + +void wt_el3_world_return_to_ns(wt_el3_frame_t* frame) +{ + unsigned int count = wt_ffa_reply_is_ext(g_ns_forwarded_fid, + (uint32_t)frame->x[0]) + ? WT_FFA_MSG_REGS_EXT : WT_FFA_MSG_REGS; + unsigned int i; + + for (i = 0u; i < count; i++) { + g_world[WT_WORLD_NS].frame.x[i] = frame->x[i]; + } + wt_ffa_reply_clear_ext(g_ns_forwarded_fid, g_world[WT_WORLD_NS].frame.x); + g_ns_pending = WT_NS_PENDING_NONE; + world_switch(frame, WT_WORLD_NS); +} + +void wt_el3_world_resume_ns(wt_el3_frame_t* frame) +{ + /* Resume the preempted Normal world exactly as it was: its saved frame is + * restored unchanged (no reply registers). */ + g_ns_pending = WT_NS_PENDING_NONE; + world_switch(frame, WT_WORLD_NS); +} + +void wt_el3_world_launch_ns(wt_el3_frame_t* frame) +{ + wt_el3_puts("[EL3] spmc ready\r\n"); + wt_platform_console_flush(); +#if defined(WT_EL3_NS_SMOKE) && (WT_EL3_NS_SMOKE == 1) + /* Save the SPMC (blocked in its idle FFA_MSG_WAIT) and turn on the Normal + * world; a later forwarded call resumes the SPMC from here. Its SCR_EL3 is a + * fixed property of the Secure world (world_save carries only per-yield + * state), so record it once. */ + world_save(&g_world[WT_WORLD_SECURE], frame); + g_world[WT_WORLD_SECURE].scr_el3 = WT_SCR_EL3_SECURE; + world_init_ns(&g_world[WT_WORLD_NS]); + g_world_cur = WT_WORLD_NS; + wt_el3_puts("[EL3] ns launch pc=0x"); + wt_el3_puthex((uint64_t)WT_NS_IMAGE_PA, 8u); + wt_el3_puts("\r\n"); + wt_platform_console_flush(); +#if defined(WT_NS_PREEMPT) && (WT_NS_PREEMPT == 1) + /* Arm a one-shot Secure tick so it fires while the Normal world runs; with + * SCR_EL3.FIQ set for NS it traps to EL3 as a lower-EL FIQ (ffa-preempt). */ + wt_gic->enable(WT_GIC_INTID_SECURE_TIMER); + wt_el3_timer_arm_ms(WT_NS_PREEMPT_MS); +#endif + world_restore(&g_world[WT_WORLD_NS]); +#else + (void)frame; + (void)wt_el3_monitor_call(WT_MON_FID_EXIT, WT_MON_EXIT_SUCCESS); + for (;;) { + } +#endif +} diff --git a/src/arch/aarch64/ffa/ffa_boot_info.c b/src/arch/aarch64/ffa/ffa_boot_info.c new file mode 100644 index 00000000..70ab6446 --- /dev/null +++ b/src/arch/aarch64/ffa/ffa_boot_info.c @@ -0,0 +1,363 @@ +/* ffa_boot_info.c + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfTrust. + * + * wolfTrust 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. + * + * wolfTrust 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, see . + */ + +/* FF-A boot information blob producer (SPMD side) and consumer (SPMC side), + * DEN0077A 1.2 section 5.4. */ + +#include "wolftrust/arch/aarch64/ffa_abi.h" +#include "wolftrust/arch/aarch64/ffa_boot_info.h" + +#define OFF_SIGNATURE 0u +#define OFF_VERSION 4u +#define OFF_BLOB_SIZE 8u +#define OFF_DESC_SIZE 12u +#define OFF_DESC_COUNT 16u +#define OFF_DESC_OFFSET 20u +#define OFF_RESERVED 24u + +#define DESC_NAME 0u +#define DESC_TYPE 16u +#define DESC_RESERVED 17u +#define DESC_FLAGS 18u +#define DESC_SIZE 20u +#define DESC_CONTENTS 24u + +#define FLAGS_NAME_MASK 0x0003u +#define FLAGS_CONTENTS_MASK 0x000Cu +#define FLAGS_CONTENTS_SHIFT 2u +#define FLAGS_RESERVED_MASK 0xFFF0u + +static uint32_t rd32(const uint8_t* p) +{ + return (uint32_t)p[0] | ((uint32_t)p[1] << 8) | ((uint32_t)p[2] << 16) | + ((uint32_t)p[3] << 24); +} + +static uint64_t rd64(const uint8_t* p) +{ + return (uint64_t)rd32(p) | ((uint64_t)rd32(p + 4) << 32); +} + +static uint16_t rd16(const uint8_t* p) +{ + return (uint16_t)((uint16_t)p[0] | ((uint16_t)p[1] << 8)); +} + +static void wr32(uint8_t* p, uint32_t v) +{ + p[0] = (uint8_t)v; + p[1] = (uint8_t)(v >> 8); + p[2] = (uint8_t)(v >> 16); + p[3] = (uint8_t)(v >> 24); +} + +static void wr64(uint8_t* p, uint64_t v) +{ + wr32(p, (uint32_t)v); + wr32(p + 4, (uint32_t)(v >> 32)); +} + +static void wr16(uint8_t* p, uint16_t v) +{ + p[0] = (uint8_t)v; + p[1] = (uint8_t)(v >> 8); +} + +static size_t align8(size_t v) +{ + return (v + 7u) & ~(size_t)7u; +} + +/* Table 5.8: standard types are FDT (0) and HOB (1) only; a value-form + * descriptor carries 1 to 8 bytes in its Contents field. */ +static int type_and_size_ok(uint8_t type, uint8_t contents_format, + uint32_t size) +{ + if (((type & WT_FFA_BOOT_INFO_TYPE_IMPDEF) == 0u) && + (type > WT_FFA_BOOT_INFO_TYPE_HOB)) { + return 0; + } + if ((contents_format == WT_FFA_BOOT_INFO_CONTENTS_VALUE) && + ((size < 1u) || (size > 8u))) { + return 0; + } + return 1; +} + +size_t wt_ffa_boot_info_array_end(uint32_t desc_count) +{ + return WT_FFA_BOOT_INFO_HEADER_SIZE + + ((size_t)desc_count * WT_FFA_BOOT_INFO_DESC_SIZE); +} + +static int name_ok(const uint8_t* name, uint16_t flags) +{ + unsigned int i; + + if ((flags & FLAGS_NAME_MASK) == WT_FFA_BOOT_INFO_NAME_UUID) { + return 1; + } + for (i = 0u; i < WT_FFA_BOOT_INFO_NAME_SIZE; i++) { + if (name[i] == 0u) { + return 1; + } + } + return 0; +} + +static int put_name(uint8_t* dst, const wt_ffa_boot_info_item_t* item) +{ + unsigned int i; + const uint8_t* src = (const uint8_t*)item->name; + + for (i = 0u; i < WT_FFA_BOOT_INFO_NAME_SIZE; i++) { + dst[i] = 0u; + } + if (src == NULL) { + return WT_FFA_BOOT_INFO_ERROR_ARGUMENT; + } + if (item->name_format == WT_FFA_BOOT_INFO_NAME_UUID) { + for (i = 0u; i < WT_FFA_BOOT_INFO_NAME_SIZE; i++) { + dst[i] = src[i]; + } + return WT_FFA_BOOT_INFO_OK; + } + for (i = 0u; (i < WT_FFA_BOOT_INFO_NAME_SIZE) && (src[i] != 0u); i++) { + dst[i] = src[i]; + } + if (i == WT_FFA_BOOT_INFO_NAME_SIZE) { + return WT_FFA_BOOT_INFO_ERROR_DESC; + } + return WT_FFA_BOOT_INFO_OK; +} + +int wt_ffa_boot_info_build(uint8_t* blob, uint64_t blob_pa, size_t blob_limit, + uint32_t version, + const wt_ffa_boot_info_item_t* items, + uint32_t count, uint32_t* blob_size) +{ + int ret = WT_FFA_BOOT_INFO_OK; + size_t total; + size_t cursor; + size_t i; + size_t k; + uint8_t* desc; + const uint8_t* src; + uint16_t flags; + + if ((blob == NULL) || (blob_size == NULL) || ((count != 0u) && (items == NULL))) { + return WT_FFA_BOOT_INFO_ERROR_ARGUMENT; + } + if (((blob_pa & 7u) != 0u) || ((count != 0u) && (blob_limit > 0xFFFFFFFFu))) { + return WT_FFA_BOOT_INFO_ERROR_ARGUMENT; + } + total = wt_ffa_boot_info_array_end(count); + for (i = 0u; i < count; i++) { + if (items[i].contents_format == WT_FFA_BOOT_INFO_CONTENTS_ADDRESS) { + total = align8(total) + items[i].size; + } + } + total = align8(total); + if ((total > blob_limit) || (total > 0xFFFFFFFFu)) { + return WT_FFA_BOOT_INFO_ERROR_SPACE; + } + + for (i = 0u; i < WT_FFA_BOOT_INFO_HEADER_SIZE; i++) { + blob[i] = 0u; + } + wr32(blob + OFF_SIGNATURE, WT_FFA_BOOT_INFO_SIGNATURE); + wr32(blob + OFF_VERSION, version); + wr32(blob + OFF_BLOB_SIZE, (uint32_t)total); + wr32(blob + OFF_DESC_SIZE, WT_FFA_BOOT_INFO_DESC_SIZE); + wr32(blob + OFF_DESC_COUNT, count); + wr32(blob + OFF_DESC_OFFSET, WT_FFA_BOOT_INFO_HEADER_SIZE); + + cursor = wt_ffa_boot_info_array_end(count); + for (i = 0u; (i < count) && (ret == WT_FFA_BOOT_INFO_OK); i++) { + desc = blob + WT_FFA_BOOT_INFO_HEADER_SIZE + (i * WT_FFA_BOOT_INFO_DESC_SIZE); + if ((items[i].name_format > WT_FFA_BOOT_INFO_NAME_UUID) || + (items[i].contents_format > WT_FFA_BOOT_INFO_CONTENTS_VALUE) || + !type_and_size_ok(items[i].type, items[i].contents_format, + items[i].size)) { + ret = WT_FFA_BOOT_INFO_ERROR_DESC; + break; + } + ret = put_name(desc + DESC_NAME, &items[i]); + if (ret != WT_FFA_BOOT_INFO_OK) { + break; + } + flags = (uint16_t)(items[i].name_format | + (items[i].contents_format << FLAGS_CONTENTS_SHIFT)); + desc[DESC_TYPE] = items[i].type; + desc[DESC_RESERVED] = 0u; + wr16(desc + DESC_FLAGS, flags); + wr32(desc + DESC_SIZE, items[i].size); + if (items[i].contents_format == WT_FFA_BOOT_INFO_CONTENTS_VALUE) { + wr64(desc + DESC_CONTENTS, items[i].value); + } + else { + src = (const uint8_t*)items[i].source; + if ((src == NULL) && (items[i].size != 0u)) { + ret = WT_FFA_BOOT_INFO_ERROR_ARGUMENT; + break; + } + cursor = align8(cursor); + wr64(desc + DESC_CONTENTS, blob_pa + cursor); + for (k = 0u; k < items[i].size; k++) { + blob[cursor + k] = src[k]; + } + cursor += items[i].size; + } + } + if (ret == WT_FFA_BOOT_INFO_OK) { + while (cursor < total) { + blob[cursor] = 0u; + cursor++; + } + *blob_size = (uint32_t)total; + } + return ret; +} + +static int desc_ok(const uint8_t* desc, const wt_ffa_boot_info_t* info) +{ + uint16_t flags = rd16(desc + DESC_FLAGS); + uint64_t contents; + uint32_t size; + uint64_t end; + + if (desc[DESC_RESERVED] != 0u) { + return WT_FFA_BOOT_INFO_ERROR_RESERVED; + } + if (((flags & FLAGS_RESERVED_MASK) != 0u) || + ((flags & FLAGS_NAME_MASK) > WT_FFA_BOOT_INFO_NAME_UUID) || + (((flags & FLAGS_CONTENTS_MASK) >> FLAGS_CONTENTS_SHIFT) > + WT_FFA_BOOT_INFO_CONTENTS_VALUE)) { + return WT_FFA_BOOT_INFO_ERROR_DESC; + } + if (!name_ok(desc + DESC_NAME, flags)) { + return WT_FFA_BOOT_INFO_ERROR_DESC; + } + if (!type_and_size_ok(desc[DESC_TYPE], + (uint8_t)((flags & FLAGS_CONTENTS_MASK) >> + FLAGS_CONTENTS_SHIFT), + rd32(desc + DESC_SIZE))) { + return WT_FFA_BOOT_INFO_ERROR_DESC; + } + if (((flags & FLAGS_CONTENTS_MASK) >> FLAGS_CONTENTS_SHIFT) == + WT_FFA_BOOT_INFO_CONTENTS_ADDRESS) { + contents = rd64(desc + DESC_CONTENTS); + size = rd32(desc + DESC_SIZE); + end = info->blob_pa + info->blob_size; + if ((contents < info->blob_pa) || (contents > end) || + ((uint64_t)size > (end - contents))) { + return WT_FFA_BOOT_INFO_ERROR_LAYOUT; + } + } + return WT_FFA_BOOT_INFO_OK; +} + +int wt_ffa_boot_info_parse(const uint8_t* blob, uint64_t blob_pa, + size_t blob_limit, wt_ffa_boot_info_t* out) +{ + int ret = WT_FFA_BOOT_INFO_OK; + uint32_t i; + uint64_t array_end; + + if ((blob == NULL) || (out == NULL)) { + return WT_FFA_BOOT_INFO_ERROR_ARGUMENT; + } + if (blob_limit < WT_FFA_BOOT_INFO_HEADER_SIZE) { + return WT_FFA_BOOT_INFO_ERROR_LAYOUT; + } + if (rd32(blob + OFF_SIGNATURE) != WT_FFA_BOOT_INFO_SIGNATURE) { + return WT_FFA_BOOT_INFO_ERROR_SIGNATURE; + } + out->blob_pa = blob_pa; + out->version = rd32(blob + OFF_VERSION); + out->blob_size = rd32(blob + OFF_BLOB_SIZE); + out->desc_count = rd32(blob + OFF_DESC_COUNT); + out->desc_offset = rd32(blob + OFF_DESC_OFFSET); + if (((out->version & 0x80000000u) != 0u) || + (WT_FFA_VERSION_MAJOR_OF(out->version) != 1u)) { + return WT_FFA_BOOT_INFO_ERROR_VERSION; + } + if ((rd32(blob + OFF_RESERVED) != 0u) || (rd32(blob + OFF_RESERVED + 4u) != 0u)) { + return WT_FFA_BOOT_INFO_ERROR_RESERVED; + } + if ((rd32(blob + OFF_DESC_SIZE) != WT_FFA_BOOT_INFO_DESC_SIZE) || + (out->desc_offset < WT_FFA_BOOT_INFO_HEADER_SIZE) || + ((out->desc_offset & 7u) != 0u) || + (out->blob_size < WT_FFA_BOOT_INFO_HEADER_SIZE) || + ((size_t)out->blob_size > blob_limit)) { + return WT_FFA_BOOT_INFO_ERROR_LAYOUT; + } + array_end = (uint64_t)out->desc_offset + + ((uint64_t)out->desc_count * WT_FFA_BOOT_INFO_DESC_SIZE); + if (array_end > (uint64_t)out->blob_size) { + return WT_FFA_BOOT_INFO_ERROR_LAYOUT; + } + for (i = 0u; (i < out->desc_count) && (ret == WT_FFA_BOOT_INFO_OK); i++) { + ret = desc_ok(blob + out->desc_offset + (i * WT_FFA_BOOT_INFO_DESC_SIZE), + out); + } + return ret; +} + +int wt_ffa_boot_info_desc(const uint8_t* blob, const wt_ffa_boot_info_t* info, + uint32_t index, wt_ffa_boot_info_desc_t* out) +{ + const uint8_t* desc; + unsigned int i; + + if ((blob == NULL) || (info == NULL) || (out == NULL)) { + return WT_FFA_BOOT_INFO_ERROR_ARGUMENT; + } + if (index >= info->desc_count) { + return WT_FFA_BOOT_INFO_ERROR_NOT_FOUND; + } + desc = blob + info->desc_offset + (index * WT_FFA_BOOT_INFO_DESC_SIZE); + for (i = 0u; i < WT_FFA_BOOT_INFO_NAME_SIZE; i++) { + out->name[i] = (char)desc[DESC_NAME + i]; + } + out->type = desc[DESC_TYPE]; + out->flags = rd16(desc + DESC_FLAGS); + out->size = rd32(desc + DESC_SIZE); + out->contents = rd64(desc + DESC_CONTENTS); + return WT_FFA_BOOT_INFO_OK; +} + +int wt_ffa_boot_info_find(const uint8_t* blob, const wt_ffa_boot_info_t* info, + uint8_t type, wt_ffa_boot_info_desc_t* out) +{ + uint32_t i; + int ret; + + if ((blob == NULL) || (info == NULL) || (out == NULL)) { + return WT_FFA_BOOT_INFO_ERROR_ARGUMENT; + } + for (i = 0u; i < info->desc_count; i++) { + ret = wt_ffa_boot_info_desc(blob, info, i, out); + if ((ret == WT_FFA_BOOT_INFO_OK) && (out->type == type)) { + return WT_FFA_BOOT_INFO_OK; + } + } + return WT_FFA_BOOT_INFO_ERROR_NOT_FOUND; +} diff --git a/src/arch/aarch64/ffa/ffa_mem.c b/src/arch/aarch64/ffa/ffa_mem.c new file mode 100644 index 00000000..56b94ed8 --- /dev/null +++ b/src/arch/aarch64/ffa/ffa_mem.c @@ -0,0 +1,1642 @@ +/* ffa_mem.c + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfTrust. + * + * wolfTrust 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. + * + * wolfTrust 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, see . + */ + +/* FF-A Memory Management (DEN0140) transaction descriptor encode/decode and + * the relayer validation a 1.2 SPMC runs before it acts on a lend, donate, or + * share. Little-endian, byte-packed, over caller-provided buffers. */ + +#include "wolftrust/arch/aarch64/ffa_mem.h" +#include "wolftrust/arch/aarch64/ffa_abi.h" +#include "wolftrust/arch/aarch64/ffa_notif.h" + +static uint32_t rd_u16(const uint8_t* p) +{ + return (uint32_t)p[0] | ((uint32_t)p[1] << 8); +} + +static uint32_t rd_u32(const uint8_t* p) +{ + return (uint32_t)p[0] | ((uint32_t)p[1] << 8) | + ((uint32_t)p[2] << 16) | ((uint32_t)p[3] << 24); +} + +static uint64_t rd_u64(const uint8_t* p) +{ + return (uint64_t)rd_u32(p) | ((uint64_t)rd_u32(&p[4]) << 32); +} + +static void wr_u16(uint8_t* p, uint16_t v) +{ + p[0] = (uint8_t)(v & 0xFFu); + p[1] = (uint8_t)((v >> 8) & 0xFFu); +} + +static void wr_u32(uint8_t* p, uint32_t v) +{ + p[0] = (uint8_t)(v & 0xFFu); + p[1] = (uint8_t)((v >> 8) & 0xFFu); + p[2] = (uint8_t)((v >> 16) & 0xFFu); + p[3] = (uint8_t)((v >> 24) & 0xFFu); +} + +static void wr_u64(uint8_t* p, uint64_t v) +{ + wr_u32(p, (uint32_t)(v & 0xFFFFFFFFu)); + wr_u32(&p[4], (uint32_t)((v >> 32) & 0xFFFFFFFFu)); +} + +/* Both endpoint access descriptor layouts end in eight reserved (SBZ) + * bytes. */ +static int access_size_ok(uint32_t size) +{ + return (size == WT_FFA_MEM_ACCESS_SIZE) || + (size == WT_FFA_MEM_ACCESS_SIZE_V12); +} + +static int layout_v10(uint32_t version) +{ + return (version != 0u) && (version < WT_FFA_VERSION_MAKE(1u, 1u)); +} + +/* The endpoint memory access descriptor of the reader's version (DEN0077A + * 18.5.3): 16 bytes through FF-A 1.1, the 32-byte Table 1.16 from 1.2. */ +static uint32_t access_size_for(uint32_t version) +{ + return ((version != 0u) && (version < WT_FFA_VERSION_1_2)) + ? WT_FFA_MEM_ACCESS_SIZE : WT_FFA_MEM_ACCESS_SIZE_V12; +} + +/* Where a descriptor laid out for version keeps its access descriptors: Table + * 1.20 names their size and offset and reserves [36, 48) (SBZ, ignored), the + * v1.0 layout (Table 4.17) fixes them and reserves byte 3 and [24, 28) (MBZ). + * 0, or INVALID_PARAMETERS for a short header or an MBZ byte set. */ +static int txn_header(const uint8_t* buf, size_t len, uint32_t version, + uint32_t* acc_size, uint32_t* acc_off, uint32_t* hdr) +{ + if (layout_v10(version) != 0) { + if ((len < WT_FFA_MEM_TXN_HDR_SIZE_V10) || + (buf[WT_FFA_MEM_TXN_OFF_ATTRS + 1u] != 0u) || + (rd_u32(&buf[WT_FFA_MEM_TXN_OFF_ACC_SIZE]) != 0u)) { + return WT_FFA_INVALID_PARAMETERS; + } + *acc_size = WT_FFA_MEM_ACCESS_SIZE; + *acc_off = WT_FFA_MEM_TXN_HDR_SIZE_V10; + *hdr = WT_FFA_MEM_TXN_HDR_SIZE_V10; + return 0; + } + if (len < WT_FFA_MEM_TXN_HDR_SIZE) { + return WT_FFA_INVALID_PARAMETERS; + } + *acc_size = rd_u32(&buf[WT_FFA_MEM_TXN_OFF_ACC_SIZE]); + *acc_off = rd_u32(&buf[WT_FFA_MEM_TXN_OFF_ACC_OFFSET]); + *hdr = WT_FFA_MEM_TXN_HDR_SIZE; + return 0; +} + +int wt_ffa_mem_attributes_check(uint16_t attributes) +{ + uint32_t type = (uint32_t)attributes & WT_FFA_MEM_ATTR_TYPE_MASK; + uint32_t cache = (uint32_t)attributes & WT_FFA_MEM_ATTR_CACHE_MASK; + uint32_t share = (uint32_t)attributes & WT_FFA_MEM_ATTR_SHARE_MASK; + int ret = 0; + + if (type == WT_FFA_MEM_ATTR_TYPE_NORMAL) { + if (((cache != WT_FFA_MEM_ATTR_CACHE_NC) && + (cache != WT_FFA_MEM_ATTR_CACHE_WB)) || + (share == WT_FFA_MEM_ATTR_SHARE_RSVD)) { + ret = WT_FFA_INVALID_PARAMETERS; + } + } + else if (type == WT_FFA_MEM_ATTR_TYPE_DEVICE) { + if (share != 0u) { + ret = WT_FFA_INVALID_PARAMETERS; + } + } + else if ((type == WT_FFA_MEM_ATTR_TYPE_MASK) || (cache != 0u) || + (share != 0u)) { + ret = WT_FFA_INVALID_PARAMETERS; + } + return ret; +} + +/* Normal memory shareability in DEN0140 1.10.4 precedence order. */ +static uint32_t share_rank(uint16_t attributes) +{ + uint32_t share = (uint32_t)attributes & WT_FFA_MEM_ATTR_SHARE_MASK; + uint32_t rank = 0u; + + if (share == WT_FFA_MEM_ATTR_SHARE_INNER) { + rank = 1u; + } + else if (share == WT_FFA_MEM_ATTR_SHARE_OUTER) { + rank = 2u; + } + return rank; +} + +/* Non-zero when valid, specified attributes asked are the same as or less + * permissive than the Normal memory attributes limit, each attribute on its + * own (1.10.4: Device < Normal, Non-cacheable < Write-Back, Non-shareable < + * Inner Shareable < Outer Shareable). */ +static int attributes_within(uint16_t asked, uint16_t limit) +{ + uint32_t type = (uint32_t)asked & WT_FFA_MEM_ATTR_TYPE_MASK; + int within = 0; + + if (((uint32_t)limit & WT_FFA_MEM_ATTR_TYPE_MASK) == + WT_FFA_MEM_ATTR_TYPE_NORMAL) { + if (type == WT_FFA_MEM_ATTR_TYPE_DEVICE) { + within = 1; + } + else if (type == WT_FFA_MEM_ATTR_TYPE_NORMAL) { + within = (((uint32_t)asked & WT_FFA_MEM_ATTR_CACHE_MASK) <= + ((uint32_t)limit & WT_FFA_MEM_ATTR_CACHE_MASK)) && + (share_rank(asked) <= share_rank(limit)); + } + } + return within; +} + +int wt_ffa_mem_send_attributes(uint16_t attributes, uint16_t* out) +{ + int ret = 0; + + if ((out == NULL) || + ((attributes & (WT_FFA_MEM_ATTR_RSVD_MASK | WT_FFA_MEM_ATTR_NS)) != 0u) || + (wt_ffa_mem_attributes_check(attributes) != 0)) { + ret = WT_FFA_INVALID_PARAMETERS; + } + else if ((attributes & WT_FFA_MEM_ATTR_TYPE_MASK) == 0u) { + attributes = (uint16_t)WT_FFA_MEM_ATTR_RELAYER; + } + else if (attributes_within(attributes, + (uint16_t)WT_FFA_MEM_ATTR_RELAYER) == 0) { + ret = WT_FFA_DENIED; + } + else if (attributes != (uint16_t)WT_FFA_MEM_ATTR_RELAYER) { + ret = WT_FFA_INVALID_PARAMETERS; + } + if (ret == 0) { + *out = attributes; + } + return ret; +} + +int wt_ffa_mem_txn_build(uint8_t* buf, size_t len, + const wt_ffa_mem_build_t* in, size_t* out_len) +{ + uint64_t total; + uint32_t comp_off; + uint32_t cons_base; + uint32_t sum = 0u; + uint32_t acc_size; + uint32_t hdr; + uint32_t i; + uint8_t* c; + + if ((buf == NULL) || (in == NULL) || (out_len == NULL) || + ((in->constituents == NULL) && (in->constituent_count != 0u))) { + return WT_FFA_INVALID_PARAMETERS; + } + acc_size = (in->access_desc_size != 0u) ? (uint32_t)in->access_desc_size + : access_size_for(in->version); + if ((access_size_ok(acc_size) == 0) || + ((layout_v10(in->version) != 0) && + (acc_size != WT_FFA_MEM_ACCESS_SIZE))) { + return WT_FFA_INVALID_PARAMETERS; + } + hdr = (layout_v10(in->version) != 0) ? WT_FFA_MEM_TXN_HDR_SIZE_V10 + : WT_FFA_MEM_TXN_HDR_SIZE; + + /* With no constituents the receiver named the ranges itself: no + * composite, and its offset is 0 (1.11.3.3). */ + comp_off = (in->constituent_count != 0u) ? (hdr + acc_size) : 0u; + cons_base = hdr + acc_size + WT_FFA_MEM_COMPOSITE_HDR_SIZE; + total = (in->constituent_count != 0u) + ? ((uint64_t)cons_base + + (uint64_t)in->constituent_count * WT_FFA_MEM_CONSTITUENT_SIZE) + : ((uint64_t)hdr + acc_size); + if (total > (uint64_t)len) { + return WT_FFA_NO_MEMORY; + } + + for (i = 0u; i < (uint32_t)total; i++) { + buf[i] = 0u; + } + + wr_u16(&buf[WT_FFA_MEM_TXN_OFF_SENDER], in->sender); + wr_u16(&buf[WT_FFA_MEM_TXN_OFF_ATTRS], in->attributes); + wr_u32(&buf[WT_FFA_MEM_TXN_OFF_FLAGS], in->flags); + wr_u64(&buf[WT_FFA_MEM_TXN_OFF_HANDLE], in->handle); + wr_u64(&buf[WT_FFA_MEM_TXN_OFF_TAG], in->tag); + if (layout_v10(in->version) == 0) { + wr_u32(&buf[WT_FFA_MEM_TXN_OFF_ACC_SIZE], acc_size); + wr_u32(&buf[WT_FFA_MEM_TXN_OFF_ACC_OFFSET], hdr); + } + wr_u32(&buf[WT_FFA_MEM_TXN_OFF_ACC_COUNT], 1u); + + wr_u16(&buf[hdr + WT_FFA_MEM_ACC_OFF_RECEIVER], in->receiver); + buf[hdr + WT_FFA_MEM_ACC_OFF_PERMS] = in->permissions; + wr_u32(&buf[hdr + WT_FFA_MEM_ACC_OFF_COMP_OFF], comp_off); + if ((in->impdef != NULL) && (acc_size == WT_FFA_MEM_ACCESS_SIZE_V12)) { + for (i = 0u; i < WT_FFA_MEM_IMPDEF_SIZE; i++) { + buf[hdr + WT_FFA_MEM_ACC_OFF_IMPDEF + i] = in->impdef[i]; + } + } + + for (i = 0u; i < in->constituent_count; i++) { + sum += in->constituents[i].page_count; + } + if (comp_off != 0u) { + wr_u32(&buf[comp_off + WT_FFA_MEM_COMP_OFF_PAGES], sum); + wr_u32(&buf[comp_off + WT_FFA_MEM_COMP_OFF_COUNT], + in->constituent_count); + } + + for (i = 0u; i < in->constituent_count; i++) { + c = &buf[cons_base + i * WT_FFA_MEM_CONSTITUENT_SIZE]; + wr_u64(&c[WT_FFA_MEM_CONS_OFF_ADDR], in->constituents[i].address); + wr_u32(&c[WT_FFA_MEM_CONS_OFF_PAGES], in->constituents[i].page_count); + } + + *out_len = (size_t)total; + return 0; +} + +/* The count constituents at cons_base, already inside the buffer: each + * page-aligned and non-empty, none overlapping another, summing to total + * (1.11.3.1). 0 or INVALID_PARAMETERS. */ +static int constituents_valid(const uint8_t* buf, uint64_t cons_base, + uint32_t count, uint32_t total) +{ + uint32_t sum_pages = 0u; + uint32_t i; + uint32_t j; + + for (i = 0u; i < count; i++) { + const uint8_t* c = &buf[cons_base + (uint64_t)i * WT_FFA_MEM_CONSTITUENT_SIZE]; + uint64_t addr = rd_u64(&c[WT_FFA_MEM_CONS_OFF_ADDR]); + uint32_t pages = rd_u32(&c[WT_FFA_MEM_CONS_OFF_PAGES]); + uint64_t span; + uint64_t a_end; + + if ((addr & (WT_FFA_MEM_PAGE_SIZE - 1u)) != 0u) { + return WT_FFA_INVALID_PARAMETERS; + } + if (pages < 1u) { + return WT_FFA_INVALID_PARAMETERS; + } + span = (uint64_t)pages * WT_FFA_MEM_PAGE_SIZE; + a_end = addr + span; + if (a_end < addr) { + return WT_FFA_INVALID_PARAMETERS; + } + for (j = 0u; j < i; j++) { + const uint8_t* p = &buf[cons_base + (uint64_t)j * WT_FFA_MEM_CONSTITUENT_SIZE]; + uint64_t paddr = rd_u64(&p[WT_FFA_MEM_CONS_OFF_ADDR]); + uint64_t pend = paddr + + (uint64_t)rd_u32(&p[WT_FFA_MEM_CONS_OFF_PAGES]) * + WT_FFA_MEM_PAGE_SIZE; + + if ((addr < pend) && (paddr < a_end)) { + return WT_FFA_INVALID_PARAMETERS; + } + } + if (pages > (0xFFFFFFFFu - sum_pages)) { + return WT_FFA_INVALID_PARAMETERS; + } + sum_pages += pages; + } + return (sum_pages == total) ? 0 : WT_FFA_INVALID_PARAMETERS; +} + +int wt_ffa_mem_txn_validate(const uint8_t* buf, size_t len, wt_ffa_mem_op_t op, + uint16_t expect_sender, wt_ffa_mem_txn_t* out) +{ + return wt_ffa_mem_txn_validate_at(buf, len, op, expect_sender, + WT_FFA_VERSION_1_2, out); +} + +int wt_ffa_mem_txn_validate_at(const uint8_t* buf, size_t len, + wt_ffa_mem_op_t op, uint16_t expect_sender, + uint32_t version, wt_ffa_mem_txn_t* out) +{ + wt_ffa_mem_txn_t txn; + uint64_t acc_end; + uint64_t cons_base; + uint64_t cons_end; + uint32_t comp_off = 0u; + uint32_t hdr = 0u; + unsigned int r; + + if ((buf == NULL) || (out == NULL) || + (txn_header(buf, len, version, &txn.access_desc_size, + &txn.access_offset, &hdr) != 0)) { + return WT_FFA_INVALID_PARAMETERS; + } + + txn.sender = (uint16_t)rd_u16(&buf[WT_FFA_MEM_TXN_OFF_SENDER]); + /* Table 1.18 bits[15:7] are SBZ. */ + txn.attributes = (uint16_t)(rd_u16(&buf[WT_FFA_MEM_TXN_OFF_ATTRS]) & + ~WT_FFA_MEM_ATTR_RSVD_MASK); + txn.flags = rd_u32(&buf[WT_FFA_MEM_TXN_OFF_FLAGS]); + txn.handle = rd_u64(&buf[WT_FFA_MEM_TXN_OFF_HANDLE]); + txn.tag = rd_u64(&buf[WT_FFA_MEM_TXN_OFF_TAG]); + txn.receiver_count = rd_u32(&buf[WT_FFA_MEM_TXN_OFF_ACC_COUNT]); + + if (txn.sender != expect_sender) { + return WT_FFA_DENIED; + } + if (access_size_ok(txn.access_desc_size) == 0) { + return WT_FFA_NOT_SUPPORTED; + } + if (txn.receiver_count < 1u) { + return WT_FFA_INVALID_PARAMETERS; + } + if ((op == WT_FFA_MEM_OP_DONATE) && (txn.receiver_count != 1u)) { + return WT_FFA_INVALID_PARAMETERS; + } + if (wt_ffa_mem_attributes_check(txn.attributes) != 0) { + return WT_FFA_INVALID_PARAMETERS; + } + /* The security state is the relayer's to report in a retrieve response; a + * sender leaves the NS bit clear (Table 5.18 usage). */ + if ((txn.attributes & WT_FFA_MEM_ATTR_NS) != 0u) { + return WT_FFA_INVALID_PARAMETERS; + } + /* Table 1.21: bit[1] asks for time slicing, which this relayer does not + * do (MBZ); bits[31:2] are SBZ and ignored. */ + if ((txn.flags & WT_FFA_MEM_FLAG_TIME_SLICE) != 0u) { + return WT_FFA_INVALID_PARAMETERS; + } + txn.flags &= WT_FFA_MEM_FLAG_ZERO; + if ((op == WT_FFA_MEM_OP_SHARE) && + ((txn.flags & WT_FFA_MEM_FLAG_ZERO) != 0u)) { + return WT_FFA_INVALID_PARAMETERS; + } + + if ((txn.access_offset < hdr) || + ((txn.access_offset % WT_FFA_MEM_ACC_OFFSET_ALIGN) != 0u)) { + return WT_FFA_INVALID_PARAMETERS; + } + acc_end = (uint64_t)txn.access_offset + + (uint64_t)txn.receiver_count * txn.access_desc_size; + if (acc_end > (uint64_t)len) { + return WT_FFA_INVALID_PARAMETERS; + } + + for (r = 0u; r < txn.receiver_count; r++) { + const uint8_t* acc = &buf[txn.access_offset + r * txn.access_desc_size]; + uint8_t perms = acc[WT_FFA_MEM_ACC_OFF_PERMS]; + uint32_t off = rd_u32(&acc[WT_FFA_MEM_ACC_OFF_COMP_OFF]); + + /* Its flags are MBZ in a send (1.10.1); its reserved tail and the + * permission bits[7:4] are SBZ (Tables 1.15 and 1.16). */ + if (acc[WT_FFA_MEM_ACC_OFF_FLAGS] != 0u) { + return WT_FFA_INVALID_PARAMETERS; + } + if ((perms & WT_FFA_MEM_PERM_DATA_MASK) == WT_FFA_MEM_PERM_DATA_RSVD) { + return WT_FFA_INVALID_PARAMETERS; + } + if ((perms & WT_FFA_MEM_PERM_INSTR_MASK) == WT_FFA_MEM_PERM_INSTR_MASK) { + return WT_FFA_INVALID_PARAMETERS; + } + if (off == 0u) { + return WT_FFA_INVALID_PARAMETERS; + } + if (r == 0u) { + comp_off = off; + } + else if (off != comp_off) { + return WT_FFA_INVALID_PARAMETERS; + } + } + + /* The composite's bytes [8, 16) and each constituent's [12, 16) are SBZ + * (Tables 1.13 and 1.14). */ + if ((comp_off < acc_end) || + (((uint64_t)comp_off + WT_FFA_MEM_COMPOSITE_HDR_SIZE) > (uint64_t)len)) { + return WT_FFA_INVALID_PARAMETERS; + } + txn.total_page_count = rd_u32(&buf[comp_off + WT_FFA_MEM_COMP_OFF_PAGES]); + txn.constituent_count = rd_u32(&buf[comp_off + WT_FFA_MEM_COMP_OFF_COUNT]); + if (txn.constituent_count < 1u) { + return WT_FFA_INVALID_PARAMETERS; + } + cons_base = (uint64_t)comp_off + WT_FFA_MEM_COMPOSITE_HDR_SIZE; + cons_end = cons_base + + (uint64_t)txn.constituent_count * WT_FFA_MEM_CONSTITUENT_SIZE; + /* The length a sender states is the descriptor's, to the byte. */ + if (cons_end != (uint64_t)len) { + return WT_FFA_INVALID_PARAMETERS; + } + /* Bounds the pairwise overlap scan below by what a handle can hold. */ + if (txn.constituent_count > WT_FFA_MEM_MAX_REGIONS) { + return WT_FFA_NO_MEMORY; + } + + if (constituents_valid(buf, cons_base, txn.constituent_count, + txn.total_page_count) != 0) { + return WT_FFA_INVALID_PARAMETERS; + } + + txn.composite_offset = comp_off; + *out = txn; + return 0; +} + +int wt_ffa_mem_send_validate(const uint8_t* buf, size_t len, wt_ffa_mem_op_t op, + uint16_t expect_sender, wt_ffa_mem_txn_t* out) +{ + return wt_ffa_mem_send_validate_at(buf, len, op, expect_sender, + WT_FFA_VERSION_1_2, out); +} + +int wt_ffa_mem_send_validate_at(const uint8_t* buf, size_t len, + wt_ffa_mem_op_t op, uint16_t expect_sender, + uint32_t version, wt_ffa_mem_txn_t* out) +{ + int ret = wt_ffa_mem_txn_validate_at(buf, len, op, expect_sender, version, + out); + + if ((ret == 0) && (out->handle != 0u)) { + ret = WT_FFA_INVALID_PARAMETERS; + } + return ret; +} + +int wt_ffa_mem_receiver(const uint8_t* buf, size_t len, + const wt_ffa_mem_txn_t* txn, uint32_t index, + uint16_t* out_id, uint8_t* out_perms) +{ + const uint8_t* acc; + + if ((buf == NULL) || (txn == NULL) || (out_id == NULL) || + (out_perms == NULL) || (index >= txn->receiver_count)) { + return WT_FFA_INVALID_PARAMETERS; + } + if (((uint64_t)txn->access_offset + + (uint64_t)(index + 1u) * txn->access_desc_size) > (uint64_t)len) { + return WT_FFA_INVALID_PARAMETERS; + } + acc = &buf[txn->access_offset + index * txn->access_desc_size]; + *out_id = (uint16_t)rd_u16(&acc[WT_FFA_MEM_ACC_OFF_RECEIVER]); + *out_perms = (uint8_t)(acc[WT_FFA_MEM_ACC_OFF_PERMS] & + ~WT_FFA_MEM_PERM_RSVD_MASK); + return 0; +} + +int wt_ffa_mem_receiver_impdef(const uint8_t* buf, size_t len, + const wt_ffa_mem_txn_t* txn, uint32_t index, + uint8_t* out16) +{ + const uint8_t* acc; + uint32_t i; + + if ((buf == NULL) || (txn == NULL) || (out16 == NULL) || + (index >= txn->receiver_count) || + (((uint64_t)txn->access_offset + + (uint64_t)(index + 1u) * txn->access_desc_size) > (uint64_t)len)) { + return WT_FFA_INVALID_PARAMETERS; + } + acc = &buf[txn->access_offset + index * txn->access_desc_size]; + for (i = 0u; i < WT_FFA_MEM_IMPDEF_SIZE; i++) { + out16[i] = (txn->access_desc_size == WT_FFA_MEM_ACCESS_SIZE_V12) + ? acc[WT_FFA_MEM_ACC_OFF_IMPDEF + i] : 0u; + } + return 0; +} + +int wt_ffa_mem_constituent(const uint8_t* buf, size_t len, + const wt_ffa_mem_txn_t* txn, uint32_t index, + wt_ffa_mem_constituent_t* out) +{ + const uint8_t* c; + uint64_t base; + + if ((buf == NULL) || (txn == NULL) || (out == NULL) || + (index >= txn->constituent_count)) { + return WT_FFA_INVALID_PARAMETERS; + } + base = (uint64_t)txn->composite_offset + WT_FFA_MEM_COMPOSITE_HDR_SIZE + + (uint64_t)index * WT_FFA_MEM_CONSTITUENT_SIZE; + if ((base + WT_FFA_MEM_CONSTITUENT_SIZE) > (uint64_t)len) { + return WT_FFA_INVALID_PARAMETERS; + } + c = &buf[base]; + out->address = rd_u64(&c[WT_FFA_MEM_CONS_OFF_ADDR]); + out->page_count = rd_u32(&c[WT_FFA_MEM_CONS_OFF_PAGES]); + return 0; +} + +int wt_ffa_mem_regions_from_txn(const uint8_t* buf, size_t len, + const wt_ffa_mem_txn_t* txn, + uint32_t receiver_index, + wt_ffa_mem_region_t* out, uint32_t max, + uint32_t* out_n) +{ + wt_ffa_mem_constituent_t c; + uint16_t rid; + uint8_t perms; + uint8_t ns; + uint32_t i; + int ret; + + if ((out == NULL) || (out_n == NULL) || (txn == NULL)) { + return WT_FFA_INVALID_PARAMETERS; + } + if (txn->constituent_count > max) { + return WT_FFA_NO_MEMORY; + } + ret = wt_ffa_mem_receiver(buf, len, txn, receiver_index, &rid, &perms); + if (ret != 0) { + return ret; + } + (void)rid; + ns = ((txn->attributes & WT_FFA_MEM_ATTR_NS) != 0u) ? 1u : 0u; + for (i = 0u; i < txn->constituent_count; i++) { + ret = wt_ffa_mem_constituent(buf, len, txn, i, &c); + if (ret != 0) { + return ret; + } + out[i].base = c.address; + out[i].page_count = c.page_count; + out[i].permissions = perms; + out[i].ns = ns; + } + *out_n = txn->constituent_count; + return 0; +} + +int wt_ffa_mem_retrieve_req_build(uint8_t* buf, size_t len, uint64_t handle, + uint16_t sender, uint16_t receiver, + uint8_t permissions, size_t* out_len) +{ + return wt_ffa_mem_retrieve_req_build_at(buf, len, handle, sender, receiver, + permissions, + WT_FFA_VERSION_MAKE(1u, 1u), + out_len); +} + +int wt_ffa_mem_retrieve_req_build_at(uint8_t* buf, size_t len, uint64_t handle, + uint16_t sender, uint16_t receiver, + uint8_t permissions, uint32_t version, + size_t* out_len) +{ + const uint32_t acc_size = access_size_for(version); + const uint32_t hdr = (layout_v10(version) != 0) + ? WT_FFA_MEM_TXN_HDR_SIZE_V10 + : WT_FFA_MEM_TXN_HDR_SIZE; + const uint32_t total = hdr + acc_size; + uint32_t i; + + if ((buf == NULL) || (out_len == NULL)) { + return WT_FFA_INVALID_PARAMETERS; + } + if ((uint64_t)total > (uint64_t)len) { + return WT_FFA_NO_MEMORY; + } + for (i = 0u; i < total; i++) { + buf[i] = 0u; + } + wr_u16(&buf[WT_FFA_MEM_TXN_OFF_SENDER], sender); + wr_u64(&buf[WT_FFA_MEM_TXN_OFF_HANDLE], handle); + /* Table 4.17 fixes the v1.0 access array at 32 and reserves [24, 28). */ + if (layout_v10(version) == 0) { + wr_u32(&buf[WT_FFA_MEM_TXN_OFF_ACC_SIZE], acc_size); + wr_u32(&buf[WT_FFA_MEM_TXN_OFF_ACC_OFFSET], hdr); + } + wr_u32(&buf[WT_FFA_MEM_TXN_OFF_ACC_COUNT], 1u); + wr_u16(&buf[hdr + WT_FFA_MEM_ACC_OFF_RECEIVER], receiver); + buf[hdr + WT_FFA_MEM_ACC_OFF_PERMS] = permissions; + *out_len = (size_t)total; + return 0; +} + +int wt_ffa_mem_retrieve_req_parse_ex(const uint8_t* buf, size_t len, + wt_ffa_mem_retrieve_req_t* out) +{ + return wt_ffa_mem_retrieve_req_parse_at(buf, len, WT_FFA_VERSION_1_2, out); +} + +/* The composite at comp that a retrieve request names for the receiver's + * own ranges: past the access array, validated as a sender's (1.11.3.2), and + * ending at *end. */ +static int retrieve_ranges(const uint8_t* buf, size_t len, uint32_t comp, + uint64_t acc_end, wt_ffa_mem_retrieve_req_t* out, + uint64_t* end) +{ + uint64_t cons_base = (uint64_t)comp + WT_FFA_MEM_COMPOSITE_HDR_SIZE; + const uint8_t* c; + uint32_t total; + uint32_t n; + uint32_t i; + + if (((uint64_t)comp < acc_end) || (cons_base > (uint64_t)len)) { + return WT_FFA_INVALID_PARAMETERS; + } + total = rd_u32(&buf[comp + WT_FFA_MEM_COMP_OFF_PAGES]); + n = rd_u32(&buf[comp + WT_FFA_MEM_COMP_OFF_COUNT]); + *end = cons_base + ((uint64_t)n * WT_FFA_MEM_CONSTITUENT_SIZE); + if ((n < 1u) || (*end > (uint64_t)len)) { + return WT_FFA_INVALID_PARAMETERS; + } + if (n > WT_FFA_MEM_MAX_REGIONS) { + return WT_FFA_NO_MEMORY; + } + if (constituents_valid(buf, cons_base, n, total) != 0) { + return WT_FFA_INVALID_PARAMETERS; + } + for (i = 0u; i < n; i++) { + c = &buf[cons_base + ((uint64_t)i * WT_FFA_MEM_CONSTITUENT_SIZE)]; + out->ranges[i].address = rd_u64(&c[WT_FFA_MEM_CONS_OFF_ADDR]); + out->ranges[i].page_count = rd_u32(&c[WT_FFA_MEM_CONS_OFF_PAGES]); + } + out->range_count = n; + return 0; +} + +int wt_ffa_mem_retrieve_req_parse_at(const uint8_t* buf, size_t len, + uint32_t version, + wt_ffa_mem_retrieve_req_t* out) +{ + const uint8_t* acc; + uint64_t end; + uint32_t acc_size = 0u; + uint32_t count; + uint32_t comp; + uint32_t off = 0u; + uint32_t hdr = 0u; + uint32_t i; + uint32_t j; + int ret; + + if ((buf == NULL) || (out == NULL) || + (txn_header(buf, len, version, &acc_size, &off, &hdr) != 0)) { + return WT_FFA_INVALID_PARAMETERS; + } + count = rd_u32(&buf[WT_FFA_MEM_TXN_OFF_ACC_COUNT]); + if (access_size_ok(acc_size) == 0) { + return WT_FFA_NOT_SUPPORTED; + } + if (count < 1u) { + return WT_FFA_INVALID_PARAMETERS; + } + if (count > WT_FFA_MEM_MAX_BORROWERS) { + return WT_FFA_NOT_SUPPORTED; + } + end = (uint64_t)off + ((uint64_t)count * acc_size); + if ((off < hdr) || ((off % WT_FFA_MEM_ACC_OFFSET_ALIGN) != 0u) || + (end > (uint64_t)len)) { + return WT_FFA_INVALID_PARAMETERS; + } + out->range_count = 0u; + out->range_index = 0u; + for (i = 0u; i < count; i++) { + acc = &buf[off + (i * acc_size)]; + comp = rd_u32(&acc[WT_FFA_MEM_ACC_OFF_COMP_OFF]); + /* One receiver may name the ranges it maps the memory at; every + * other entry leaves them to the relayer (1.11.3.2). */ + if ((comp != 0u) && (out->range_count != 0u)) { + return WT_FFA_INVALID_PARAMETERS; + } + if (comp != 0u) { + ret = retrieve_ranges(buf, len, comp, + (uint64_t)off + ((uint64_t)count * acc_size), + out, &end); + if (ret != 0) { + return ret; + } + out->range_index = i; + } + out->receivers[i] = (uint16_t)rd_u16(&acc[WT_FFA_MEM_ACC_OFF_RECEIVER]); + /* Permission bits[7:4] and the reserved tail are SBZ. */ + out->permissions[i] = (uint8_t)(acc[WT_FFA_MEM_ACC_OFF_PERMS] & + ~WT_FFA_MEM_PERM_RSVD_MASK); + /* Bits[7:1] are SBZ: ignored at the higher EL (DEN0077A 7.2.2.3.2). */ + out->access_flags[i] = acc[WT_FFA_MEM_ACC_OFF_FLAGS]; + for (j = 0u; j < WT_FFA_MEM_IMPDEF_SIZE; j++) { + out->impdef[i][j] = (acc_size == WT_FFA_MEM_ACCESS_SIZE_V12) + ? acc[WT_FFA_MEM_ACC_OFF_IMPDEF + j] : 0u; + } + } + if (end != (uint64_t)len) { + return WT_FFA_INVALID_PARAMETERS; + } + out->receiver_count = count; + out->access_desc_size = acc_size; + out->handle = rd_u64(&buf[WT_FFA_MEM_TXN_OFF_HANDLE]); + out->tag = rd_u64(&buf[WT_FFA_MEM_TXN_OFF_TAG]); + /* Table 1.22 bits[31:11] are SBZ. */ + out->flags = rd_u32(&buf[WT_FFA_MEM_TXN_OFF_FLAGS]) & + WT_FFA_MEM_FLAG_RETRIEVE_MASK; + out->sender = (uint16_t)rd_u16(&buf[WT_FFA_MEM_TXN_OFF_SENDER]); + out->attributes = (uint16_t)(rd_u16(&buf[WT_FFA_MEM_TXN_OFF_ATTRS]) & + ~WT_FFA_MEM_ATTR_RSVD_MASK); + return 0; +} + +int wt_ffa_mem_retrieve_req_parse(const uint8_t* buf, size_t len, + uint64_t* out_handle, uint16_t* out_sender, + uint16_t* out_receiver) +{ + wt_ffa_mem_retrieve_req_t req; + int ret; + + if ((out_handle == NULL) || (out_sender == NULL) || (out_receiver == NULL)) { + return WT_FFA_INVALID_PARAMETERS; + } + ret = wt_ffa_mem_retrieve_req_parse_ex(buf, len, &req); + if (ret != 0) { + return ret; + } + if (req.receiver_count != 1u) { + return WT_FFA_NOT_SUPPORTED; + } + *out_handle = req.handle; + *out_sender = req.sender; + *out_receiver = req.receivers[0]; + return 0; +} + +int wt_ffa_mem_relinquish_build(uint8_t* buf, size_t len, uint64_t handle, + uint32_t flags, uint16_t endpoint, + size_t* out_len) +{ + const uint32_t total = WT_FFA_MEM_RELINQ_HDR_SIZE + 2u; + uint32_t i; + + if ((buf == NULL) || (out_len == NULL)) { + return WT_FFA_INVALID_PARAMETERS; + } + if ((flags & ~WT_FFA_MEM_RELINQ_FLAG_MASK) != 0u) { + return WT_FFA_INVALID_PARAMETERS; + } + if ((uint64_t)total > (uint64_t)len) { + return WT_FFA_NO_MEMORY; + } + for (i = 0u; i < total; i++) { + buf[i] = 0u; + } + wr_u64(&buf[WT_FFA_MEM_RELINQ_OFF_HANDLE], handle); + wr_u32(&buf[WT_FFA_MEM_RELINQ_OFF_FLAGS], flags); + wr_u32(&buf[WT_FFA_MEM_RELINQ_OFF_COUNT], 1u); + wr_u16(&buf[WT_FFA_MEM_RELINQ_OFF_ENDPOINTS], endpoint); + *out_len = (size_t)total; + return 0; +} + +int wt_ffa_mem_relinquish_parse(const uint8_t* buf, size_t len, + uint64_t* out_handle, uint16_t* out_endpoint) +{ + uint32_t count; + + if ((buf == NULL) || (out_handle == NULL) || (out_endpoint == NULL) || + (len < WT_FFA_MEM_RELINQ_HDR_SIZE)) { + return WT_FFA_INVALID_PARAMETERS; + } + /* Table 2.25: bit[1] (time slicing) is MBZ here, bits[31:2] are SBZ. */ + if ((rd_u32(&buf[WT_FFA_MEM_RELINQ_OFF_FLAGS]) & + WT_FFA_MEM_FLAG_TIME_SLICE) != 0u) { + return WT_FFA_INVALID_PARAMETERS; + } + count = rd_u32(&buf[WT_FFA_MEM_RELINQ_OFF_COUNT]); + if (count < 1u) { + return WT_FFA_INVALID_PARAMETERS; + } + if (count != 1u) { + return WT_FFA_NOT_SUPPORTED; + } + if (((uint64_t)WT_FFA_MEM_RELINQ_HDR_SIZE + 2u) > (uint64_t)len) { + return WT_FFA_INVALID_PARAMETERS; + } + *out_handle = rd_u64(&buf[WT_FFA_MEM_RELINQ_OFF_HANDLE]); + *out_endpoint = (uint16_t)rd_u16(&buf[WT_FFA_MEM_RELINQ_OFF_ENDPOINTS]); + return 0; +} + +int wt_ffa_mem_relinquish_parse_ex(const uint8_t* buf, size_t len, + uint64_t* out_handle, uint16_t* out_endpoint, + uint32_t* out_flags) +{ + int ret = wt_ffa_mem_relinquish_parse(buf, len, out_handle, out_endpoint); + + if ((ret == 0) && (out_flags != NULL)) { + *out_flags = rd_u32(&buf[WT_FFA_MEM_RELINQ_OFF_FLAGS]) & + WT_FFA_MEM_RELINQ_FLAG_MASK; + } + return ret; +} + +/* Table 2.31: bit[1] (time slicing) is MBZ here, bits[31:2] are SBZ. */ +int wt_ffa_mem_reclaim_flags_check(uint32_t flags) +{ + return ((flags & WT_FFA_MEM_FLAG_TIME_SLICE) != 0u) + ? WT_FFA_INVALID_PARAMETERS : 0; +} + +int wt_ffa_rxtx_validate(uint64_t tx, uint64_t rx, uint32_t pages) +{ + uint64_t span; + uint64_t tx_end; + uint64_t rx_end; + + if ((pages < WT_FFA_RXTX_MIN_PAGES) || (pages > WT_FFA_RXTX_MAX_PAGES)) { + return WT_FFA_INVALID_PARAMETERS; + } + if (((tx & (WT_FFA_MEM_PAGE_SIZE - 1u)) != 0u) || + ((rx & (WT_FFA_MEM_PAGE_SIZE - 1u)) != 0u)) { + return WT_FFA_INVALID_PARAMETERS; + } + if (tx == rx) { + return WT_FFA_INVALID_PARAMETERS; + } + span = (uint64_t)pages * WT_FFA_MEM_PAGE_SIZE; + tx_end = tx + span; + rx_end = rx + span; + if ((tx_end < tx) || (rx_end < rx)) { + return WT_FFA_INVALID_PARAMETERS; + } + if ((tx < rx_end) && (rx < tx_end)) { + return WT_FFA_INVALID_PARAMETERS; + } + return 0; +} + +int wt_ffa_mailbox_map(wt_ffa_mailbox_t* mb, uint64_t tx, uint64_t rx, + uint32_t w3) +{ + uint32_t pages = WT_FFA_RXTX_PAGE_COUNT(w3); + + if (mb == NULL) { + return WT_FFA_INVALID_PARAMETERS; + } + if (mb->mapped != 0u) { + return WT_FFA_DENIED; + } + if (wt_ffa_rxtx_validate(tx, rx, pages) != 0) { + return WT_FFA_INVALID_PARAMETERS; + } + mb->tx = tx; + mb->rx = rx; + mb->pages = pages; + mb->mapped = 1u; + mb->rx_full = 0u; + return 0; +} + +int wt_ffa_mailbox_unmap(wt_ffa_mailbox_t* mb) +{ + if ((mb == NULL) || (mb->mapped == 0u)) { + return WT_FFA_INVALID_PARAMETERS; + } + mb->tx = 0u; + mb->rx = 0u; + mb->pages = 0u; + mb->mapped = 0u; + mb->rx_full = 0u; + return 0; +} + +int wt_ffa_mailbox_overlaps(const wt_ffa_mailbox_t* mb, uint64_t base, + uint64_t size) +{ + uint64_t span; + + if ((mb == NULL) || (mb->mapped == 0u) || (size == 0u)) { + return 0; + } + span = (uint64_t)mb->pages * WT_FFA_MEM_PAGE_SIZE; + return (((base < (mb->tx + span)) && (mb->tx < (base + size))) || + ((base < (mb->rx + span)) && (mb->rx < (base + size)))) ? 1 : 0; +} + +int wt_ffa_mem_tx_buffer(const wt_ffa_mailbox_t* mb, uint64_t addr, + uint32_t pages, uint32_t len, uint64_t* out_tx) +{ + if ((out_tx == NULL) || (addr != 0u) || (pages != 0u) || (mb == NULL) || + (mb->mapped == 0u) || + ((uint64_t)len > ((uint64_t)mb->pages * WT_FFA_MEM_PAGE_SIZE))) { + return WT_FFA_INVALID_PARAMETERS; + } + *out_tx = mb->tx; + return 0; +} + +static int mailbox_rx_fill(wt_ffa_mailbox_t* mb, uint8_t state) +{ + if ((mb == NULL) || (mb->mapped == 0u)) { + return WT_FFA_DENIED; + } + if (mb->rx_full != WT_FFA_RX_EMPTY) { + return WT_FFA_BUSY; + } + mb->rx_full = state; + return 0; +} + +int wt_ffa_mailbox_rx_acquire(wt_ffa_mailbox_t* mb) +{ + return mailbox_rx_fill(mb, (uint8_t)WT_FFA_RX_OWNED); +} + +int wt_ffa_mailbox_rx_post(wt_ffa_mailbox_t* mb) +{ + return mailbox_rx_fill(mb, (uint8_t)WT_FFA_RX_POSTED); +} + +void wt_ffa_mailbox_rx_claim(wt_ffa_mailbox_t* mb, uint64_t framework) +{ + if ((mb != NULL) && (mb->rx_full == WT_FFA_RX_POSTED) && + ((framework & (WT_FFA_NOTIF_FW_SPM_RX_FULL | + WT_FFA_NOTIF_FW_NS_RX_FULL)) != 0u)) { + mb->rx_full = (uint8_t)WT_FFA_RX_OWNED; + } +} + +/* Table 13.22: an endpoint without a registered pair owns no RX buffer, so + * DENIED (the ACS ffa_rx_release test agrees); INVALID_PARAMETERS is for a + * VM the Hypervisor names that has no pair. */ +int wt_ffa_mailbox_rx_release(wt_ffa_mailbox_t* mb) +{ + if ((mb == NULL) || (mb->mapped == 0u) || + (mb->rx_full != WT_FFA_RX_OWNED)) { + return WT_FFA_DENIED; + } + mb->rx_full = (uint8_t)WT_FFA_RX_EMPTY; + return 0; +} + +static uint8_t op_state(wt_ffa_mem_op_t op) +{ + uint8_t state; + + switch (op) { + case WT_FFA_MEM_OP_SHARE: + state = (uint8_t)WT_FFA_MEM_STATE_SHARED; + break; + case WT_FFA_MEM_OP_LEND: + state = (uint8_t)WT_FFA_MEM_STATE_LENT; + break; + case WT_FFA_MEM_OP_DONATE: + state = (uint8_t)WT_FFA_MEM_STATE_DONATED; + break; + default: + state = (uint8_t)WT_FFA_MEM_STATE_FREE; + break; + } + return state; +} + +static wt_ffa_mem_handle_entry_t* find_handle(wt_ffa_mem_registry_t* reg, + uint64_t handle) +{ + unsigned int i; + + for (i = 0u; i < WT_FFA_MEM_MAX_HANDLES; i++) { + if ((reg->entries[i].state != (uint8_t)WT_FFA_MEM_STATE_FREE) && + (reg->entries[i].handle == handle)) { + return ®->entries[i]; + } + } + return NULL; +} + +void wt_ffa_mem_registry_init(wt_ffa_mem_registry_t* reg) +{ + unsigned int i; + + if (reg == NULL) { + return; + } + for (i = 0u; i < WT_FFA_MEM_MAX_HANDLES; i++) { + reg->entries[i].handle = WT_FFA_MEM_HANDLE_INVALID; + reg->entries[i].owner = 0u; + reg->entries[i].borrower = 0u; + reg->entries[i].state = (uint8_t)WT_FFA_MEM_STATE_FREE; + reg->entries[i].retrieved = 0u; + reg->entries[i].region_count = 0u; + reg->entries[i].borrower_count = 0u; + reg->entries[i].attributes = 0u; + } + reg->next_handle = 1u; +} + +uint64_t wt_ffa_mem_handle_reserve(wt_ffa_mem_registry_t* reg) +{ + uint64_t handle; + + if (reg == NULL) { + return 0u; + } + handle = reg->next_handle & 0x7FFFFFFFFFFFFFFFull; + reg->next_handle++; + return handle; +} + +int wt_ffa_mem_share_register_as(wt_ffa_mem_registry_t* reg, + wt_ffa_mem_op_t op, uint16_t owner, + uint16_t borrower, + const wt_ffa_mem_region_t* regions, + uint32_t n, uint64_t handle) +{ + unsigned int i; + uint32_t r; + + if ((reg == NULL) || (handle == 0u) || + (op_state(op) == (uint8_t)WT_FFA_MEM_STATE_FREE)) { + return WT_FFA_INVALID_PARAMETERS; + } + if ((n > WT_FFA_MEM_MAX_REGIONS) || ((n > 0u) && (regions == NULL))) { + return WT_FFA_INVALID_PARAMETERS; + } + for (i = 0u; i < WT_FFA_MEM_MAX_HANDLES; i++) { + if (reg->entries[i].state == (uint8_t)WT_FFA_MEM_STATE_FREE) { + reg->entries[i].handle = handle; + reg->entries[i].owner = owner; + reg->entries[i].borrower = borrower; + reg->entries[i].state = op_state(op); + reg->entries[i].retrieved = 0u; + reg->entries[i].tag = 0u; + reg->entries[i].owner_cookie = 0u; + reg->entries[i].attributes = 0u; + reg->entries[i].borrower_count = 1u; + reg->entries[i].borrowers[0].id = borrower; + reg->entries[i].borrowers[0].permissions = + (n > 0u) ? regions[0].permissions : 0u; + reg->entries[i].borrowers[0].retrieved = 0u; + reg->entries[i].borrowers[0].mapping = 0u; + reg->entries[i].borrowers[0].ever_retrieved = 0u; + for (r = 0u; r < WT_FFA_MEM_IMPDEF_SIZE; r++) { + reg->entries[i].borrowers[0].impdef[r] = 0u; + } + reg->entries[i].region_count = (uint8_t)n; + for (r = 0u; r < n; r++) { + reg->entries[i].regions[r] = regions[r]; + } + return 0; + } + } + return WT_FFA_NO_MEMORY; +} + +int wt_ffa_mem_share_register(wt_ffa_mem_registry_t* reg, wt_ffa_mem_op_t op, + uint16_t owner, uint16_t borrower, + const wt_ffa_mem_region_t* regions, uint32_t n, + uint64_t* out_handle) +{ + uint64_t handle; + int ret; + + if ((reg == NULL) || (out_handle == NULL)) { + return WT_FFA_INVALID_PARAMETERS; + } + handle = reg->next_handle & 0x7FFFFFFFFFFFFFFFull; + ret = wt_ffa_mem_share_register_as(reg, op, owner, borrower, regions, n, + handle); + if (ret == 0) { + reg->next_handle++; + *out_handle = handle; + } + return ret; +} + +static void copy_bytes(uint8_t* dst, const uint8_t* src, uint32_t len) +{ + uint32_t i; + + for (i = 0u; i < len; i++) { + dst[i] = src[i]; + } +} + +int wt_ffa_mem_frag_expected(const uint8_t* frag, uint32_t frag_len, + int retrieve, uint64_t* size) +{ + return wt_ffa_mem_frag_expected_at(frag, frag_len, retrieve, + WT_FFA_VERSION_1_2, size); +} + +int wt_ffa_mem_frag_expected_at(const uint8_t* frag, uint32_t frag_len, + int retrieve, uint32_t version, uint64_t* size) +{ + uint32_t acc_size = WT_FFA_MEM_ACCESS_SIZE; + uint32_t acc_count; + uint32_t acc_off = WT_FFA_MEM_TXN_HDR_SIZE_V10; + uint32_t hdr = (layout_v10(version) != 0) ? WT_FFA_MEM_TXN_HDR_SIZE_V10 + : WT_FFA_MEM_TXN_HDR_SIZE; + uint32_t comp_off; + uint32_t i; + + if ((frag == NULL) || (size == NULL) || (frag_len < hdr)) { + return 0; + } + if (layout_v10(version) == 0) { + acc_size = rd_u32(&frag[WT_FFA_MEM_TXN_OFF_ACC_SIZE]); + acc_off = rd_u32(&frag[WT_FFA_MEM_TXN_OFF_ACC_OFFSET]); + } + acc_count = rd_u32(&frag[WT_FFA_MEM_TXN_OFF_ACC_COUNT]); + if (retrieve != 0) { + /* Only an access array the full parse accepts is walked: a zero + * stride would never leave it. */ + if ((access_size_ok(acc_size) == 0) || (acc_off < hdr) || + ((acc_off % WT_FFA_MEM_ACC_OFFSET_ALIGN) != 0u)) { + return 0; + } + *size = (uint64_t)acc_off + ((uint64_t)acc_count * acc_size); + /* A receiver's own address ranges follow the access array, named by + * any descriptor's offset, so each one must have arrived to tell. */ + for (i = 0u; i < acc_count; i++) { + if (((uint64_t)acc_off + ((uint64_t)i * acc_size) + + WT_FFA_MEM_ACC_OFF_COMP_OFF + 4u) > (uint64_t)frag_len) { + return 0; + } + comp_off = rd_u32(&frag[acc_off + (i * acc_size) + + WT_FFA_MEM_ACC_OFF_COMP_OFF]); + if (comp_off == 0u) { + continue; + } + if (((uint64_t)comp_off + WT_FFA_MEM_COMP_OFF_COUNT + 4u) > + (uint64_t)frag_len) { + return 0; + } + *size = (uint64_t)comp_off + WT_FFA_MEM_COMPOSITE_HDR_SIZE + + ((uint64_t)rd_u32(&frag[comp_off + WT_FFA_MEM_COMP_OFF_COUNT]) * + WT_FFA_MEM_CONSTITUENT_SIZE); + break; + } + return 1; + } + if (((uint64_t)acc_off + WT_FFA_MEM_ACC_OFF_COMP_OFF + 4u) > + (uint64_t)frag_len) { + return 0; + } + comp_off = rd_u32(&frag[acc_off + WT_FFA_MEM_ACC_OFF_COMP_OFF]); + if (((uint64_t)comp_off + WT_FFA_MEM_COMP_OFF_COUNT + 4u) > + (uint64_t)frag_len) { + return 0; + } + *size = (uint64_t)comp_off + WT_FFA_MEM_COMPOSITE_HDR_SIZE + + ((uint64_t)rd_u32(&frag[comp_off + WT_FFA_MEM_COMP_OFF_COUNT]) * + WT_FFA_MEM_CONSTITUENT_SIZE); + return 1; +} + +void wt_ffa_mem_frag_reset(wt_ffa_mem_frag_t* f) +{ + if (f != NULL) { + f->active = 0u; + f->handle = 0u; + f->total = 0u; + f->received = 0u; + f->sender = 0u; + f->op = 0u; + f->aborted = 0u; + } +} + +int wt_ffa_mem_frag_begin(wt_ffa_mem_frag_t* f, uint64_t handle, + uint16_t sender, uint8_t op, const uint8_t* frag, + uint32_t frag_len, uint32_t total) +{ + if ((f == NULL) || (frag == NULL) || (frag_len == 0u) || + (frag_len >= total)) { + return WT_FFA_INVALID_PARAMETERS; + } + if (total > WT_FFA_MEM_FRAG_MAX) { + return WT_FFA_NO_MEMORY; + } + copy_bytes(f->buf, frag, frag_len); + f->handle = handle; + f->total = total; + f->received = frag_len; + f->sender = sender; + f->op = op; + f->active = 1u; + f->aborted = 0u; + return 0; +} + +int wt_ffa_mem_frag_add(wt_ffa_mem_frag_t* f, uint64_t handle, + uint16_t sender, const uint8_t* frag, + uint32_t frag_len, int* done) +{ + if ((f == NULL) || (frag == NULL) || (done == NULL) || (f->active == 0u) || + (handle != f->handle) || (sender != f->sender) || (frag_len == 0u) || + (frag_len > (f->total - f->received))) { + return WT_FFA_INVALID_PARAMETERS; + } + copy_bytes(&f->buf[f->received], frag, frag_len); + f->received += frag_len; + *done = (f->received == f->total) ? 1 : 0; + return 0; +} + +int wt_ffa_mem_handle_alloc(wt_ffa_mem_registry_t* reg, wt_ffa_mem_op_t op, + uint16_t owner, uint16_t borrower, + uint64_t* out_handle) +{ + return wt_ffa_mem_share_register(reg, op, owner, borrower, NULL, 0u, + out_handle); +} + +int wt_ffa_mem_handle_regions(const wt_ffa_mem_registry_t* reg, uint64_t handle, + wt_ffa_mem_region_t* out, uint32_t max, + uint32_t* out_n) +{ + const wt_ffa_mem_handle_entry_t* e; + uint32_t i; + int ret; + + if ((out == NULL) || (out_n == NULL)) { + return WT_FFA_INVALID_PARAMETERS; + } + ret = wt_ffa_mem_handle_lookup(reg, handle, &e); + if (ret != 0) { + return ret; + } + if ((uint32_t)e->region_count > max) { + return WT_FFA_NO_MEMORY; + } + for (i = 0u; i < (uint32_t)e->region_count; i++) { + out[i] = e->regions[i]; + } + *out_n = (uint32_t)e->region_count; + return 0; +} + +int wt_ffa_mem_handle_lookup(const wt_ffa_mem_registry_t* reg, uint64_t handle, + const wt_ffa_mem_handle_entry_t** out) +{ + unsigned int i; + + if ((reg == NULL) || (out == NULL)) { + return WT_FFA_INVALID_PARAMETERS; + } + for (i = 0u; i < WT_FFA_MEM_MAX_HANDLES; i++) { + if ((reg->entries[i].state != (uint8_t)WT_FFA_MEM_STATE_FREE) && + (reg->entries[i].handle == handle)) { + *out = ®->entries[i]; + return 0; + } + } + return WT_FFA_INVALID_PARAMETERS; +} + +void wt_ffa_mem_handle_set_meta(wt_ffa_mem_registry_t* reg, uint64_t handle, + uint64_t tag, uint32_t owner_cookie) +{ + wt_ffa_mem_handle_entry_t* e = (reg != NULL) ? find_handle(reg, handle) : NULL; + + if (e != NULL) { + e->tag = tag; + e->owner_cookie = owner_cookie; + } +} + +void wt_ffa_mem_handle_set_attributes(wt_ffa_mem_registry_t* reg, + uint64_t handle, uint16_t attributes) +{ + wt_ffa_mem_handle_entry_t* e = (reg != NULL) ? find_handle(reg, handle) : NULL; + + if (e != NULL) { + e->attributes = attributes; + } +} + +wt_ffa_mem_borrower_t* wt_ffa_mem_handle_borrower(wt_ffa_mem_registry_t* reg, + uint64_t handle, + uint16_t borrower) +{ + wt_ffa_mem_handle_entry_t* e; + uint32_t i; + + if (reg == NULL) { + return NULL; + } + e = find_handle(reg, handle); + if (e == NULL) { + return NULL; + } + for (i = 0u; i < (uint32_t)e->borrower_count; i++) { + if (e->borrowers[i].id == borrower) { + return &e->borrowers[i]; + } + } + return NULL; +} + +int wt_ffa_mem_handle_add_borrower(wt_ffa_mem_registry_t* reg, uint64_t handle, + uint16_t borrower, uint8_t permissions) +{ + wt_ffa_mem_handle_entry_t* e; + uint32_t i; + + if (reg == NULL) { + return WT_FFA_INVALID_PARAMETERS; + } + e = find_handle(reg, handle); + if ((e == NULL) || (e->retrieved != 0u) || (e->owner == borrower) || + (wt_ffa_mem_handle_borrower(reg, handle, borrower) != NULL)) { + return WT_FFA_INVALID_PARAMETERS; + } + if ((uint32_t)e->borrower_count >= WT_FFA_MEM_MAX_BORROWERS) { + return WT_FFA_NO_MEMORY; + } + e->borrowers[e->borrower_count].id = borrower; + e->borrowers[e->borrower_count].permissions = permissions; + e->borrowers[e->borrower_count].retrieved = 0u; + e->borrowers[e->borrower_count].mapping = 0u; + e->borrowers[e->borrower_count].ever_retrieved = 0u; + for (i = 0u; i < WT_FFA_MEM_IMPDEF_SIZE; i++) { + e->borrowers[e->borrower_count].impdef[i] = 0u; + } + e->borrower_count++; + return 0; +} + +int wt_ffa_mem_registry_overlaps(const wt_ffa_mem_registry_t* reg, + uint64_t base, uint32_t pages) +{ + const wt_ffa_mem_handle_entry_t* e; + uint64_t end = base + ((uint64_t)pages * WT_FFA_MEM_PAGE_SIZE); + uint64_t r_end; + unsigned int i; + uint32_t r; + + if (reg == NULL) { + return 0; + } + for (i = 0u; i < WT_FFA_MEM_MAX_HANDLES; i++) { + e = ®->entries[i]; + if (e->state == (uint8_t)WT_FFA_MEM_STATE_FREE) { + continue; + } + for (r = 0u; r < (uint32_t)e->region_count; r++) { + r_end = e->regions[r].base + + ((uint64_t)e->regions[r].page_count * WT_FFA_MEM_PAGE_SIZE); + if ((base < r_end) && (e->regions[r].base < end)) { + return 1; + } + } + } + return 0; +} + +int wt_ffa_mem_handle_retrieve(wt_ffa_mem_registry_t* reg, uint64_t handle, + uint16_t borrower) +{ + wt_ffa_mem_handle_entry_t* e; + wt_ffa_mem_borrower_t* b; + + if (reg == NULL) { + return WT_FFA_INVALID_PARAMETERS; + } + e = find_handle(reg, handle); + if (e == NULL) { + return WT_FFA_INVALID_PARAMETERS; + } + b = wt_ffa_mem_handle_borrower(reg, handle, borrower); + if (b == NULL) { + return WT_FFA_INVALID_PARAMETERS; + } + if (b->retrieved != 0u) { + return WT_FFA_DENIED; + } + b->retrieved = 1u; + b->ever_retrieved = 1u; + e->retrieved++; + return 0; +} + +int wt_ffa_mem_handle_relinquish(wt_ffa_mem_registry_t* reg, uint64_t handle, + uint16_t borrower) +{ + wt_ffa_mem_handle_entry_t* e; + wt_ffa_mem_borrower_t* b; + + if (reg == NULL) { + return WT_FFA_INVALID_PARAMETERS; + } + e = find_handle(reg, handle); + if (e == NULL) { + return WT_FFA_INVALID_PARAMETERS; + } + b = wt_ffa_mem_handle_borrower(reg, handle, borrower); + if (b == NULL) { + return WT_FFA_INVALID_PARAMETERS; + } + if (b->retrieved == 0u) { + return WT_FFA_DENIED; + } + b->retrieved = 0u; + e->retrieved--; + return 0; +} + +int wt_ffa_mem_handle_free(wt_ffa_mem_registry_t* reg, uint64_t handle) +{ + wt_ffa_mem_handle_entry_t* e; + + if (reg == NULL) { + return WT_FFA_INVALID_PARAMETERS; + } + e = find_handle(reg, handle); + if (e == NULL) { + return WT_FFA_INVALID_PARAMETERS; + } + e->state = (uint8_t)WT_FFA_MEM_STATE_FREE; + e->handle = WT_FFA_MEM_HANDLE_INVALID; + return 0; +} + +int wt_ffa_mem_handle_reclaim(wt_ffa_mem_registry_t* reg, uint64_t handle, + uint16_t owner) +{ + wt_ffa_mem_handle_entry_t* e; + + if (reg == NULL) { + return WT_FFA_INVALID_PARAMETERS; + } + e = find_handle(reg, handle); + if (e == NULL) { + return WT_FFA_INVALID_PARAMETERS; + } + if (e->owner != owner) { + return WT_FFA_INVALID_PARAMETERS; + } + if (e->retrieved != 0u) { + return WT_FFA_DENIED; + } + e->state = (uint8_t)WT_FFA_MEM_STATE_FREE; + e->handle = WT_FFA_MEM_HANDLE_INVALID; + return 0; +} + +uint32_t wt_ffa_mem_type_flag(uint8_t state) +{ + uint32_t flag; + + switch (state) { + case (uint8_t)WT_FFA_MEM_STATE_LENT: + flag = WT_FFA_MEM_FLAG_TYPE_LEND; + break; + case (uint8_t)WT_FFA_MEM_STATE_DONATED: + flag = WT_FFA_MEM_FLAG_TYPE_DONATE; + break; + default: + flag = WT_FFA_MEM_FLAG_TYPE_SHARE; + break; + } + return flag; +} + +static const wt_ffa_mem_borrower_t* entry_borrower( + const wt_ffa_mem_handle_entry_t* e, uint16_t id) +{ + uint32_t i; + + for (i = 0u; i < (uint32_t)e->borrower_count; i++) { + if (e->borrowers[i].id == id) { + return &e->borrowers[i]; + } + } + return NULL; +} + +static int bytes_equal(const uint8_t* a, const uint8_t* b, uint32_t len) +{ + uint32_t i; + + for (i = 0u; i < len; i++) { + if (a[i] != b[i]) { + return 0; + } + } + return 1; +} + +int wt_ffa_mem_retrieve_req_check(const wt_ffa_mem_handle_entry_t* e, + const wt_ffa_mem_retrieve_req_t* rq, + uint16_t receiver) +{ + const wt_ffa_mem_borrower_t* named; + uint32_t type; + uint32_t i; + uint32_t j; + int non_retrieval; + int own; + int repeated = 0; + int ret = 0; + + if ((e == NULL) || (rq == NULL) || + (rq->receiver_count > WT_FFA_MEM_MAX_BORROWERS)) { + ret = WT_FFA_INVALID_PARAMETERS; + } + /* FF-A 1.2: what the owner attached for a borrower is repeated by whoever + * names that borrower in a retrieve request. */ + for (i = 0u; (ret == 0) && (i < rq->receiver_count); i++) { + named = entry_borrower(e, rq->receivers[i]); + if ((named == NULL) || + (bytes_equal(named->impdef, rq->impdef[i], + WT_FFA_MEM_IMPDEF_SIZE) == 0)) { + ret = WT_FFA_INVALID_PARAMETERS; + } + /* Table 1.17 bit[0]: only the caller is retrieved for, so its own + * entry has the flag clear and every other borrower's has it set. */ + own = (rq->receivers[i] == receiver) ? 1 : 0; + non_retrieval = ((rq->access_flags[i] & + WT_FFA_MEM_ACC_FLAG_NON_RETRIEVAL) != 0u) ? 1 : 0; + if (own == non_retrieval) { + ret = WT_FFA_INVALID_PARAMETERS; + } + /* Another borrower exists only in a share or a lend to several, where + * every instruction access is left unspecified (1.10.3 item 1). */ + if ((own == 0) && + (((rq->permissions[i] & WT_FFA_MEM_PERM_INSTR_MASK) != + WT_FFA_MEM_PERM_INSTR_NOT_SPEC) || + ((rq->permissions[i] & WT_FFA_MEM_PERM_DATA_MASK) == + WT_FFA_MEM_PERM_DATA_RSVD))) { + ret = WT_FFA_INVALID_PARAMETERS; + } + for (j = 0u; j < i; j++) { + if (rq->receivers[j] == rq->receivers[i]) { + repeated = 1; + } + } + } + /* Every entry is a borrower, so the same count with no repeat is the + * lender's whole list; the bypass flag's IMPLEMENTATION DEFINED action + * (1.11.3.3) is that the receiver names itself alone. */ + if ((ret == 0) && + ((repeated != 0) || + (rq->receiver_count != + (((rq->flags & WT_FFA_MEM_FLAG_BYPASS_BORROWERS) != 0u) + ? 1u : (uint32_t)e->borrower_count)) || + (((rq->flags & WT_FFA_MEM_FLAG_BYPASS_BORROWERS) != 0u) && + (rq->receivers[0] != receiver)))) { + ret = WT_FFA_INVALID_PARAMETERS; + } + if (ret == 0) { + type = rq->flags & WT_FFA_MEM_FLAG_TYPE_MASK; + if ((rq->tag != e->tag) || + ((rq->flags & ~(WT_FFA_MEM_FLAG_SEND_MASK | + WT_FFA_MEM_FLAG_TYPE_MASK | + WT_FFA_MEM_FLAG_ZERO_AFTER | + WT_FFA_MEM_FLAG_BYPASS_BORROWERS)) != 0u) || + (((rq->flags & WT_FFA_MEM_FLAG_BYPASS_BORROWERS) != 0u) && + (e->borrower_count == 1u)) || + ((type != 0u) && (type != wt_ffa_mem_type_flag(e->state))) || + ((rq->attributes & + (WT_FFA_MEM_ATTR_RSVD_MASK | WT_FFA_MEM_ATTR_NS)) != 0u)) { + ret = WT_FFA_INVALID_PARAMETERS; + } + } + /* Well formed, but not what was sent: only Normal memory is ever lent. */ + if ((ret == 0) && ((rq->attributes & WT_FFA_MEM_ATTR_TYPE_MASK) == + WT_FFA_MEM_ATTR_TYPE_DEVICE)) { + ret = WT_FFA_DENIED; + } + if (ret == 0) { + ret = wt_ffa_mem_attributes_check(rq->attributes); + } + /* Attributes a borrower states are held against those its memory is + * mapped with: more permissive ones fail validation (1.10.4.2 items 1 and + * 2), less permissive ones the relayer cannot map (item 5). */ + if ((ret == 0) && ((rq->attributes & WT_FFA_MEM_ATTR_TYPE_MASK) != 0u)) { + if (attributes_within(rq->attributes, e->attributes) == 0) { + ret = WT_FFA_DENIED; + } + else if (rq->attributes != e->attributes) { + ret = WT_FFA_INVALID_PARAMETERS; + } + } + /* Every other borrower named must carry the data access the lender gave + * it (1.10.2 item 1). */ + for (i = 0u; (ret == 0) && (i < rq->receiver_count); i++) { + named = entry_borrower(e, rq->receivers[i]); + if ((rq->receivers[i] != receiver) && (named != NULL) && + ((rq->permissions[i] & WT_FFA_MEM_PERM_DATA_MASK) != + (named->permissions & WT_FFA_MEM_PERM_DATA_MASK))) { + ret = WT_FFA_DENIED; + } + } + return ret; +} diff --git a/src/arch/aarch64/ffa/ffa_msg.c b/src/arch/aarch64/ffa/ffa_msg.c new file mode 100644 index 00000000..a3dbeabe --- /dev/null +++ b/src/arch/aarch64/ffa/ffa_msg.c @@ -0,0 +1,357 @@ +/* ffa_msg.c + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfTrust. + * + * wolfTrust 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. + * + * wolfTrust 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, see . + */ + +/* FF-A direct messaging register encodings and relayer checks (DEN0077A + * 15.2/15.3, 7.4.2). The SPMD applies the NS-physical checks before forwarding + * a Normal-world request to the SPMC; the SPMC applies the secure-virtual + * checks before it enters a partition or forwards a response. */ + +#include "wolftrust/arch/aarch64/ffa_abi.h" +#include "wolftrust/arch/aarch64/ffa_msg.h" + +#include + +void wt_ffa_direct_build(uint64_t* x, uint32_t fid, uint16_t sender, + uint16_t receiver, const uint32_t* payload) +{ + unsigned int i; + + for (i = 0u; i < 8u; i++) { + x[i] = 0u; + } + x[0] = fid; + x[1] = ((uint64_t)sender << 16) | (uint64_t)receiver; + x[2] = 0u; + for (i = 0u; i < WT_FFA_DIRECT_PAYLOAD_WORDS; i++) { + x[3u + i] = (payload != NULL) ? (uint64_t)payload[i] : 0u; + } +} + +int wt_ffa_direct_req_check(const uint64_t* x, wt_ffa_instance_t inst) +{ + uint32_t fid = (uint32_t)x[0]; + uint16_t sender = wt_ffa_direct_sender(x[1]); + uint16_t receiver = wt_ffa_direct_receiver(x[1]); + + if ((fid != WT_FFA_MSG_SEND_DIRECT_REQ32) && + (fid != WT_FFA_MSG_SEND_DIRECT_REQ64) && + (fid != WT_FFA_MSG_SEND_DIRECT_REQ2)) { + return WT_FFA_INVALID_PARAMETERS; + } + /* REQ2 carries the service UUID in x2/x3 instead of flags. */ + if ((fid != WT_FFA_MSG_SEND_DIRECT_REQ2) && + (((uint32_t)x[2] & WT_FFA_DIRECT_FLAGS_MBZ) != 0u)) { + return WT_FFA_INVALID_PARAMETERS; + } + if (sender == receiver) { + return WT_FFA_INVALID_PARAMETERS; + } + if (inst == WT_FFA_INSTANCE_NS_PHYSICAL) { + /* The SPMD relays only Normal world to a Secure partition. */ + if (wt_ffa_id_is_secure(sender) || !wt_ffa_id_is_secure(receiver)) { + return WT_FFA_INVALID_PARAMETERS; + } + } + else { + /* The SPMC relays a request only between Secure partitions. */ + if (!wt_ffa_id_is_secure(sender) || !wt_ffa_id_is_secure(receiver)) { + return WT_FFA_INVALID_PARAMETERS; + } + } + return 0; +} + +int wt_ffa_direct_resp_check(const uint64_t* x, wt_ffa_instance_t inst) +{ + uint32_t fid = (uint32_t)x[0]; + uint16_t sender = wt_ffa_direct_sender(x[1]); + uint16_t receiver = wt_ffa_direct_receiver(x[1]); + + if ((fid != WT_FFA_MSG_SEND_DIRECT_RESP32) && + (fid != WT_FFA_MSG_SEND_DIRECT_RESP64) && + (fid != WT_FFA_MSG_SEND_DIRECT_RESP2)) { + return WT_FFA_INVALID_PARAMETERS; + } + /* RESP2's x2/x3 are SBZ (Table 15.19). */ + if ((fid != WT_FFA_MSG_SEND_DIRECT_RESP2) && + (((uint32_t)x[2] & WT_FFA_DIRECT_FLAGS_MBZ) != 0u)) { + return WT_FFA_INVALID_PARAMETERS; + } + if (sender == receiver) { + return WT_FFA_INVALID_PARAMETERS; + } + /* A response is emitted by the endpoint that received the request, so its + * sender is always Secure; the receiver is whoever sent the request, which + * at the NS-physical instance is the Normal world the SPMD returns to. */ + if (!wt_ffa_id_is_secure(sender)) { + return WT_FFA_INVALID_PARAMETERS; + } + if ((inst == WT_FFA_INSTANCE_NS_PHYSICAL) && wt_ffa_id_is_secure(receiver)) { + return WT_FFA_INVALID_PARAMETERS; + } + return 0; +} + +void wt_ffa_direct_clear_sbz(uint64_t* x) +{ + uint32_t fid = (uint32_t)x[0]; + + if (fid == WT_FFA_MSG_SEND_DIRECT_REQ2) { + return; + } + x[2] = 0u; + if (fid == WT_FFA_MSG_SEND_DIRECT_RESP2) { + x[3] = 0u; + } +} + +int wt_ffa_run_target(uint32_t w1, uint16_t* id) +{ + *id = (uint16_t)(w1 >> 16); + return ((w1 & 0xFFFFu) == 0u) ? 0 : WT_FFA_INVALID_PARAMETERS; +} + +int wt_ffa_run_busy_check(uint16_t requester, uint16_t caller, + unsigned int yielded, unsigned int preempted) +{ + if ((requester != caller) || ((yielded == 0u) && (preempted == 0u))) { + return WT_FFA_DENIED; + } + return 0; +} + +void wt_ffa_msg_deliver(uint64_t* x, const uint64_t* msg) +{ + uint32_t call = (uint32_t)x[0]; + unsigned int count = wt_ffa_msg_reg_count(msg[0]); + unsigned int i; + + for (i = 0u; i < count; i++) { + x[i] = msg[i]; + } + if (count == WT_FFA_MSG_REGS) { + wt_ffa_reply_clear_ext(call, x); + } +} + +#define WT_FFA_FWK_SPMD_TO_SPMC \ + (((uint32_t)WT_FFA_ID_SPMD << 16) | (uint32_t)WT_FFA_ID_SPMC) +#define WT_FFA_FWK_SPMC_TO_SPMD \ + (((uint32_t)WT_FFA_ID_SPMC << 16) | (uint32_t)WT_FFA_ID_SPMD) + +void wt_ffa_fwk_version_req(uint64_t* x, uint32_t version) +{ + wt_ffa_direct_build(x, WT_FFA_MSG_SEND_DIRECT_REQ32, WT_FFA_ID_SPMD, + WT_FFA_ID_SPMC, NULL); + x[2] = WT_FFA_FWK_VERSION_REQ; + x[3] = version; +} + +int wt_ffa_fwk_version_is_req(const uint64_t* x) +{ + return ((uint32_t)x[0] == WT_FFA_MSG_SEND_DIRECT_REQ32) && + ((uint32_t)x[1] == WT_FFA_FWK_SPMD_TO_SPMC) && + ((uint32_t)x[2] == WT_FFA_FWK_VERSION_REQ); +} + +void wt_ffa_fwk_version_resp(uint64_t* x, int32_t result) +{ + wt_ffa_direct_build(x, WT_FFA_MSG_SEND_DIRECT_RESP32, WT_FFA_ID_SPMC, + WT_FFA_ID_SPMD, NULL); + x[2] = WT_FFA_FWK_VERSION_RESP; + x[3] = (uint64_t)(uint32_t)result; +} + +int32_t wt_ffa_fwk_version_result(const uint64_t* x) +{ + if (((uint32_t)x[0] != WT_FFA_MSG_SEND_DIRECT_RESP32) || + ((uint32_t)x[1] != WT_FFA_FWK_SPMC_TO_SPMD) || + ((uint32_t)x[2] != WT_FFA_FWK_VERSION_RESP)) { + return (int32_t)WT_FFA_NOT_SUPPORTED; + } + return (int32_t)(uint32_t)x[3]; +} + +static uint32_t msg2_read32(const uint8_t* p) +{ + return (uint32_t)p[0] | ((uint32_t)p[1] << 8) | + ((uint32_t)p[2] << 16) | ((uint32_t)p[3] << 24); +} + +static void msg2_write32(uint8_t* p, uint32_t v) +{ + p[0] = (uint8_t)(v & 0xFFu); + p[1] = (uint8_t)((v >> 8) & 0xFFu); + p[2] = (uint8_t)((v >> 16) & 0xFFu); + p[3] = (uint8_t)((v >> 24) & 0xFFu); +} + +uint32_t wt_ffa_msg2_header_size(uint32_t version) +{ + return (version >= WT_FFA_VERSION_1_2) ? WT_FFA_MSG2_HEADER_SIZE : + WT_FFA_MSG2_HEADER_SIZE_V1_1; +} + +int wt_ffa_msg2_parse(const uint8_t* tx, uint32_t tx_size, uint16_t caller, + uint32_t version, wt_ffa_instance_t inst, uint32_t w1, + uint32_t w2, wt_ffa_msg2_t* out) +{ + uint32_t hdr = wt_ffa_msg2_header_size(version); + uint32_t sender_receiver; + unsigned int i; + + if ((tx == NULL) || (out == NULL) || (tx_size < hdr)) { + return WT_FFA_INVALID_PARAMETERS; + } + /* Table 15.3: w1 bits 15:0 and the w2 flags other than the delay-SRI hint + * are SBZ; the w1 sender is MBZ and w2 is ignored at the SVC conduit; the + * hint is MBZ outside the Secure virtual instance (16.5.1). */ + if (inst == WT_FFA_INSTANCE_NS_PHYSICAL) { + if (((w2 & WT_FFA_MSG2_FLAG_DELAY_SRI) != 0u) || + (((w1 >> 16) != 0u) && ((uint16_t)(w1 >> 16) != caller))) { + return WT_FFA_INVALID_PARAMETERS; + } + } + else if ((w1 >> 16) != 0u) { + return WT_FFA_INVALID_PARAMETERS; + } + out->offset = msg2_read32(&tx[8]); + sender_receiver = msg2_read32(&tx[12]); + out->size = msg2_read32(&tx[16]); + for (i = 0u; i < 16u; i++) { + out->uuid[i] = (hdr == WT_FFA_MSG2_HEADER_SIZE) ? tx[24u + i] : 0u; + } + out->sender = (uint16_t)(sender_receiver >> 16); + out->receiver = (uint16_t)(sender_receiver & 0xFFFFu); + if (out->sender != caller) { + return WT_FFA_INVALID_PARAMETERS; + } + if (out->sender == out->receiver) { + return WT_FFA_INVALID_PARAMETERS; + } + if ((out->offset < hdr) || + ((uint64_t)out->offset + (uint64_t)out->size > (uint64_t)tx_size)) { + return WT_FFA_INVALID_PARAMETERS; + } + return 0; +} + +int wt_ffa_msg2_uuid_ok(const uint8_t* header_uuid, const uint8_t* ep_uuid) +{ + unsigned int i; + unsigned int nil = 1u; + + if ((header_uuid == NULL) || (ep_uuid == NULL)) { + return 0; + } + for (i = 0u; i < 16u; i++) { + if (header_uuid[i] != 0u) { + nil = 0u; + } + } + if (nil != 0u) { + return 1; + } + for (i = 0u; i < 16u; i++) { + if (header_uuid[i] != ep_uuid[i]) { + return 0; + } + } + return 1; +} + +uint32_t wt_ffa_msg2_rx_offset(const wt_ffa_msg2_t* msg, uint32_t version) +{ + uint32_t hdr = wt_ffa_msg2_header_size(version); + + return (msg->offset < hdr) ? hdr : msg->offset; +} + +void wt_ffa_msg2_copy(uint8_t* rx, uint32_t rx_size, uint32_t version, + const uint8_t* tx, const wt_ffa_msg2_t* msg) +{ + uint32_t hdr = wt_ffa_msg2_header_size(version); + uint32_t off = wt_ffa_msg2_rx_offset(msg, version); + uint32_t i; + + for (i = 0u; i < rx_size; i++) { + if ((i >= off) && ((i - off) < msg->size)) { + rx[i] = tx[msg->offset + (i - off)]; + } + else { + rx[i] = 0u; + } + } + if (rx_size >= hdr) { + msg2_write32(&rx[8], off); + msg2_write32(&rx[12], ((uint32_t)msg->sender << 16) | + (uint32_t)msg->receiver); + msg2_write32(&rx[16], msg->size); + if (hdr == WT_FFA_MSG2_HEADER_SIZE) { + for (i = 0u; i < 16u; i++) { + rx[24u + i] = msg->uuid[i]; + } + } + } +} + +void wt_ffa_fwk_pm_req(uint64_t* x, uint32_t psci_fid, uint64_t a1, + uint64_t a2, uint64_t a3) +{ + int is64 = (psci_fid & 0x40000000u) != 0u; + uint64_t mask = is64 ? ~(uint64_t)0u : 0xFFFFFFFFull; + unsigned int i; + + for (i = 0u; i < WT_FFA_MSG_REGS_EXT; i++) { + x[i] = 0u; + } + x[0] = is64 ? WT_FFA_MSG_SEND_DIRECT_REQ64 : WT_FFA_MSG_SEND_DIRECT_REQ32; + x[1] = WT_FFA_FWK_SPMD_TO_SPMC; + x[2] = WT_FFA_FWK_PM_PSCI_REQ; + x[3] = psci_fid; + x[4] = a1 & mask; + x[5] = a2 & mask; + x[6] = a3 & mask; +} + +int wt_ffa_fwk_pm_is_req(const uint64_t* x) +{ + return (((uint32_t)x[0] == WT_FFA_MSG_SEND_DIRECT_REQ32) || + ((uint32_t)x[0] == WT_FFA_MSG_SEND_DIRECT_REQ64)) && + ((uint32_t)x[1] == WT_FFA_FWK_SPMD_TO_SPMC) && + ((uint32_t)x[2] == WT_FFA_FWK_PM_PSCI_REQ); +} + +void wt_ffa_fwk_pm_resp(uint64_t* x, int32_t status) +{ + wt_ffa_direct_build(x, WT_FFA_MSG_SEND_DIRECT_RESP32, WT_FFA_ID_SPMC, + WT_FFA_ID_SPMD, NULL); + x[2] = WT_FFA_FWK_PM_RESP; + x[3] = (uint64_t)(uint32_t)status; +} + +int wt_ffa_fwk_pm_granted(const uint64_t* x) +{ + return ((uint32_t)x[0] == WT_FFA_MSG_SEND_DIRECT_RESP32) && + ((uint32_t)x[1] == WT_FFA_FWK_SPMC_TO_SPMD) && + ((uint32_t)x[2] == WT_FFA_FWK_PM_RESP) && + ((uint32_t)x[3] == 0u) && (x[4] == 0u) && (x[5] == 0u) && + (x[6] == 0u) && (x[7] == 0u); +} diff --git a/src/arch/aarch64/ffa/ffa_notif.c b/src/arch/aarch64/ffa/ffa_notif.c new file mode 100644 index 00000000..b308e7d7 --- /dev/null +++ b/src/arch/aarch64/ffa/ffa_notif.c @@ -0,0 +1,532 @@ +/* ffa_notif.c + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfTrust. + * + * wolfTrust 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. + * + * wolfTrust 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, see . + */ + +/* FF-A v1.2 notification state machine (DEN0077A Ch.10). Fixed storage, no + * allocation: a bounded endpoint table seeded by the caller, a 64-bit + * notification id space per receiver, and one pending bitmap per source + * class. Every refusal row follows the ABI error tables of 17.5-17.12. */ + +#include "wolftrust/arch/aarch64/ffa_abi.h" +#include "wolftrust/arch/aarch64/ffa_notif.h" + +#include + +typedef struct wt_notif_ep { + uint64_t bound_mask; + uint64_t bound_pcpu; + uint64_t pend_sp; + uint64_t pend_vm; + uint64_t pend_fw; + uint16_t bound_sender[WT_FFA_NOTIF_COUNT]; + uint16_t id; + uint8_t secure; + int32_t gone; + uint8_t has_bitmap; + uint8_t used; + uint8_t info_reported; +} wt_notif_ep_t; + +static wt_notif_ep_t g_eps[WT_FFA_NOTIF_MAX_EP]; +static uint8_t g_sri_pending; +static uint8_t g_sri_now; + +static wt_notif_ep_t* ep_find(uint16_t id) +{ + unsigned int i; + + for (i = 0u; i < WT_FFA_NOTIF_MAX_EP; i++) { + if ((g_eps[i].used != 0u) && (g_eps[i].id == id)) { + return &g_eps[i]; + } + } + return NULL; +} + +static uint64_t ep_pending(const wt_notif_ep_t* ep) +{ + return ep->pend_sp | ep->pend_vm | ep->pend_fw; +} + +void wt_ffa_notif_reset(void) +{ + unsigned int i; + unsigned int b; + + for (i = 0u; i < WT_FFA_NOTIF_MAX_EP; i++) { + g_eps[i].bound_mask = 0u; + g_eps[i].bound_pcpu = 0u; + g_eps[i].pend_sp = 0u; + g_eps[i].pend_vm = 0u; + g_eps[i].pend_fw = 0u; + for (b = 0u; b < WT_FFA_NOTIF_COUNT; b++) { + g_eps[i].bound_sender[b] = 0u; + } + g_eps[i].id = 0u; + g_eps[i].secure = 0u; + g_eps[i].has_bitmap = 0u; + g_eps[i].used = 0u; + g_eps[i].info_reported = 0u; + g_eps[i].gone = 0; + } + g_sri_pending = 0u; + g_sri_now = 0u; +} + +int wt_ffa_notif_register(uint16_t id, int secure) +{ + unsigned int i; + + if (ep_find(id) != NULL) { + return -1; + } + for (i = 0u; i < WT_FFA_NOTIF_MAX_EP; i++) { + if (g_eps[i].used == 0u) { + g_eps[i].gone = 0; + g_eps[i].id = id; + g_eps[i].secure = (secure != 0) ? 1u : 0u; + /* Partition bitmaps exist from creation; a VM's is made by the + * BITMAP_CREATE ABI. */ + g_eps[i].has_bitmap = (secure != 0) ? 1u : 0u; + g_eps[i].used = 1u; + return 0; + } + } + return -1; +} + +void wt_ffa_notif_retire(uint16_t id, int32_t code) +{ + wt_notif_ep_t* ep = ep_find(id); + unsigned int i; + unsigned int b; + + for (i = 0u; i < WT_FFA_NOTIF_MAX_EP; i++) { + for (b = 0u; b < WT_FFA_NOTIF_COUNT; b++) { + if ((g_eps[i].used != 0u) && (g_eps[i].bound_sender[b] == id) && + ((g_eps[i].bound_mask & (1ull << b)) != 0u)) { + g_eps[i].bound_sender[b] = 0u; + g_eps[i].bound_mask &= ~(1ull << b); + g_eps[i].bound_pcpu &= ~(1ull << b); + /* Only its sender could have pended a bound id. */ + g_eps[i].pend_sp &= ~(1ull << b); + g_eps[i].pend_vm &= ~(1ull << b); + } + } + } + if (ep != NULL) { + for (b = 0u; b < WT_FFA_NOTIF_COUNT; b++) { + ep->bound_sender[b] = 0u; + } + ep->bound_mask = 0u; + ep->bound_pcpu = 0u; + ep->pend_sp = 0u; + ep->pend_vm = 0u; + ep->pend_fw = 0u; + ep->info_reported = 0u; + ep->gone = code; + } +} + +int32_t wt_ffa_notif_bitmap_create(uint16_t caller, uint32_t vm_id, + uint32_t vcpu_count) +{ + wt_notif_ep_t* callerp = ep_find(caller); + wt_notif_ep_t* vm; + + /* 17.5: a VM-only ABI; a partition is refused before anything else. */ + if ((callerp != NULL) && (callerp->secure != 0u)) { + return WT_FFA_NOT_SUPPORTED; + } + if ((vm_id > 0xFFFFu) || (vcpu_count == 0u)) { + return WT_FFA_INVALID_PARAMETERS; + } + vm = ep_find((uint16_t)vm_id); + if ((vm == NULL) || (vm->secure != 0u)) { + return WT_FFA_INVALID_PARAMETERS; + } + /* Without a Hypervisor only the VM itself may create its bitmap. */ + if (vm->id != caller) { + return WT_FFA_DENIED; + } + if (vm->has_bitmap != 0u) { + return WT_FFA_DENIED; + } + if (vcpu_count > WT_FFA_NOTIF_MAX_VCPUS) { + return WT_FFA_NO_MEMORY; + } + vm->has_bitmap = 1u; + return 0; +} + +int32_t wt_ffa_notif_bitmap_destroy(uint16_t caller, uint32_t vm_id) +{ + wt_notif_ep_t* callerp = ep_find(caller); + wt_notif_ep_t* vm; + + if ((callerp != NULL) && (callerp->secure != 0u)) { + return WT_FFA_NOT_SUPPORTED; + } + /* Table 16.7: w1 bits 31:16 are SBZ here (MBZ only for the create). */ + vm = ep_find((uint16_t)(vm_id & 0xFFFFu)); + if ((vm == NULL) || (vm->secure != 0u)) { + return WT_FFA_INVALID_PARAMETERS; + } + if (vm->id != caller) { + return WT_FFA_DENIED; + } + if (vm->has_bitmap == 0u) { + return WT_FFA_DENIED; + } + /* Only a masked (nothing bound) and non-pending bitmap may go. */ + if ((vm->bound_mask != 0u) || (ep_pending(vm) != 0u)) { + return WT_FFA_DENIED; + } + vm->has_bitmap = 0u; + return 0; +} + +int32_t wt_ffa_notif_bind(uint16_t caller, uint32_t w1, uint32_t flags, + uint64_t bitmap) +{ + uint16_t sender_id = WT_FFA_NOTIF_W1_HIGH(w1); + uint16_t receiver_id = WT_FFA_NOTIF_W1_LOW(w1); + wt_notif_ep_t* sender = ep_find(sender_id); + wt_notif_ep_t* receiver = ep_find(receiver_id); + unsigned int b; + + if ((sender == NULL) || (receiver == NULL)) { + return WT_FFA_INVALID_PARAMETERS; + } + /* Table 16.11: flag bits 31:1 are SBZ. */ + flags &= WT_FFA_NOTIF_FLAG_PER_VCPU; + if ((bitmap == 0u) || (sender_id == receiver_id)) { + return WT_FFA_INVALID_PARAMETERS; + } + if (sender->gone != 0) { + return sender->gone; + } + /* A receiver binds its own ids; nothing acts on another's behalf. */ + if (receiver_id != caller) { + return WT_FFA_DENIED; + } + if (receiver->has_bitmap == 0u) { + return WT_FFA_DENIED; + } + if ((receiver->bound_mask & bitmap) != 0u) { + return WT_FFA_DENIED; + } + for (b = 0u; b < WT_FFA_NOTIF_COUNT; b++) { + if ((bitmap & (1ull << b)) != 0u) { + receiver->bound_sender[b] = sender_id; + } + } + receiver->bound_mask |= bitmap; + if ((flags & WT_FFA_NOTIF_FLAG_PER_VCPU) != 0u) { + receiver->bound_pcpu |= bitmap; + } + return 0; +} + +int32_t wt_ffa_notif_unbind(uint16_t caller, uint32_t w1, uint32_t w2, + uint64_t bitmap) +{ + uint16_t sender_id = WT_FFA_NOTIF_W1_HIGH(w1); + uint16_t receiver_id = WT_FFA_NOTIF_W1_LOW(w1); + wt_notif_ep_t* sender = ep_find(sender_id); + wt_notif_ep_t* receiver = ep_find(receiver_id); + unsigned int b; + + /* w2 is Reserved (SBZ): the callee ignores it rather than refusing. */ + (void)w2; + if ((sender == NULL) || (receiver == NULL)) { + return WT_FFA_INVALID_PARAMETERS; + } + if (bitmap == 0u) { + return WT_FFA_INVALID_PARAMETERS; + } + if (sender->gone != 0) { + return sender->gone; + } + if (receiver_id != caller) { + return WT_FFA_DENIED; + } + if ((receiver->bound_mask & bitmap) != bitmap) { + return WT_FFA_INVALID_PARAMETERS; + } + for (b = 0u; b < WT_FFA_NOTIF_COUNT; b++) { + if (((bitmap & (1ull << b)) != 0u) && + (receiver->bound_sender[b] != sender_id)) { + return WT_FFA_DENIED; + } + } + if (((receiver->pend_sp | receiver->pend_vm) & bitmap) != 0u) { + return WT_FFA_DENIED; + } + for (b = 0u; b < WT_FFA_NOTIF_COUNT; b++) { + if ((bitmap & (1ull << b)) != 0u) { + receiver->bound_sender[b] = 0u; + } + } + receiver->bound_mask &= ~bitmap; + receiver->bound_pcpu &= ~bitmap; + return 0; +} + +int32_t wt_ffa_notif_set(uint16_t caller, uint32_t w1, uint32_t flags, + uint64_t bitmap) +{ + uint16_t sender_id = WT_FFA_NOTIF_W1_HIGH(w1); + uint16_t receiver_id = WT_FFA_NOTIF_W1_LOW(w1); + wt_notif_ep_t* callerp = ep_find(caller); + wt_notif_ep_t* sender = ep_find(sender_id); + wt_notif_ep_t* receiver = ep_find(receiver_id); + uint32_t per_vcpu = flags & WT_FFA_NOTIF_FLAG_PER_VCPU; + uint16_t vcpu = (uint16_t)WT_FFA_NOTIF_SET_VCPU(flags); + uint64_t fresh; + unsigned int b; + + if ((sender == NULL) || (receiver == NULL)) { + return WT_FFA_INVALID_PARAMETERS; + } + if ((flags & WT_FFA_NOTIF_SET_MBZ) != 0u) { + return WT_FFA_INVALID_PARAMETERS; + } + /* 16.5.1: the delay hint exists only at the Secure virtual instance. */ + if (((flags & WT_FFA_NOTIF_FLAG_DELAY_SRI) != 0u) && + ((callerp == NULL) || (callerp->secure == 0u))) { + return WT_FFA_INVALID_PARAMETERS; + } + /* One execution context: vCPU 0 is the only target, and naming one at + * all requires the per-vCPU flag. */ + if (vcpu != 0u) { + return WT_FFA_INVALID_PARAMETERS; + } + if (bitmap == 0u) { + return WT_FFA_INVALID_PARAMETERS; + } + if (receiver->gone != 0) { + return receiver->gone; + } + if (sender_id != caller) { + return WT_FFA_DENIED; + } + for (b = 0u; b < WT_FFA_NOTIF_COUNT; b++) { + if ((bitmap & (1ull << b)) == 0u) { + continue; + } + if (((receiver->bound_mask & (1ull << b)) == 0u) || + (receiver->bound_sender[b] != sender_id)) { + return WT_FFA_DENIED; + } + if (((receiver->bound_pcpu & (1ull << b)) != 0u) != (per_vcpu != 0u)) { + return WT_FFA_INVALID_PARAMETERS; + } + } + if (sender->secure != 0u) { + fresh = bitmap & ~receiver->pend_sp; + receiver->pend_sp |= bitmap; + } + else { + fresh = bitmap & ~receiver->pend_vm; + receiver->pend_vm |= bitmap; + } + /* 10.5 rule 3: re-signaling a still-pending id has no effect, so neither + * a new list nor another SRI. */ + if (fresh == 0u) { + return 0; + } + receiver->info_reported = 0u; + /* 16.5.1: a partition that does not delay has the SRI asserted as its + * call completes; a delayed one waits for the next Normal-world entry. */ + if ((sender->secure != 0u) && + ((flags & WT_FFA_NOTIF_FLAG_DELAY_SRI) == 0u)) { + g_sri_now = 1u; + } + else { + g_sri_pending = 1u; + } + return 0; +} + +int32_t wt_ffa_notif_get(uint16_t caller, uint32_t w1, uint32_t flags, + wt_ffa_notif_get_result_t* out) +{ + uint16_t vcpu = WT_FFA_NOTIF_W1_HIGH(w1); + uint16_t receiver_id = WT_FFA_NOTIF_W1_LOW(w1); + wt_notif_ep_t* callerp = ep_find(caller); + wt_notif_ep_t* receiver = ep_find(receiver_id); + + if (out == NULL) { + return WT_FFA_INVALID_PARAMETERS; + } + out->from_sp = 0u; + out->from_vm = 0u; + out->framework = 0u; + if (receiver == NULL) { + return WT_FFA_INVALID_PARAMETERS; + } + if (vcpu != 0u) { + return WT_FFA_INVALID_PARAMETERS; + } + /* Bits 31:4 are SBZ (Table 16.23), but the FF-A ACS notification_get test + * requires them refused with INVALID_PARAMETERS. */ + if ((flags & ~(uint32_t)WT_FFA_NOTIF_GET_FLAG_ALL) != 0u) { + return WT_FFA_INVALID_PARAMETERS; + } + /* REL0 Table 16.23: the VM and Hypervisor flags are SBZ, so ignored, at + * the NS physical instance. */ + if ((callerp == NULL) || (callerp->secure == 0u)) { + flags &= ~(uint32_t)(WT_FFA_NOTIF_GET_FLAG_VM | + WT_FFA_NOTIF_GET_FLAG_HYP); + } + if (receiver_id != caller) { + return WT_FFA_DENIED; + } + if ((flags & WT_FFA_NOTIF_GET_FLAG_SP) != 0u) { + out->from_sp = receiver->pend_sp; + receiver->pend_sp = 0u; + } + if ((flags & WT_FFA_NOTIF_GET_FLAG_VM) != 0u) { + out->from_vm = receiver->pend_vm; + receiver->pend_vm = 0u; + } + if ((flags & WT_FFA_NOTIF_GET_FLAG_SPM) != 0u) { + out->framework |= receiver->pend_fw & WT_FFA_NOTIF_FW_SPM_MASK; + receiver->pend_fw &= ~WT_FFA_NOTIF_FW_SPM_MASK; + } + if ((flags & WT_FFA_NOTIF_GET_FLAG_HYP) != 0u) { + out->framework |= receiver->pend_fw & WT_FFA_NOTIF_FW_HYP_MASK; + receiver->pend_fw &= ~WT_FFA_NOTIF_FW_HYP_MASK; + } + if (ep_pending(receiver) == 0u) { + receiver->info_reported = 0u; + } + return 0; +} + +int32_t wt_ffa_notif_info_get(uint16_t caller, int is64, + wt_ffa_notif_info_result_t* out) +{ + wt_notif_ep_t* callerp = ep_find(caller); + wt_notif_ep_t* ep; + unsigned int slots_per_reg = (is64 != 0) ? 4u : 2u; + unsigned int max_slots = WT_FFA_NOTIF_INFO_MAX_REGS * slots_per_reg; + unsigned int slot = 0u; + unsigned int lists = 0u; + unsigned int more = 0u; + unsigned int i; + unsigned int need; + unsigned int size; + uint64_t sizes = 0u; + + if (out == NULL) { + return WT_FFA_INVALID_PARAMETERS; + } + /* 17.11: only the Normal-world scheduler asks. */ + if ((callerp != NULL) && (callerp->secure != 0u)) { + return WT_FFA_NOT_SUPPORTED; + } + for (i = 0u; i < WT_FFA_NOTIF_INFO_MAX_REGS; i++) { + out->regs[i] = 0u; + } + out->w2 = 0u; + for (i = 0u; i < WT_FFA_NOTIF_MAX_EP; i++) { + ep = &g_eps[i]; + /* A list goes out once; only new pending work re-arms it. */ + if ((ep->used == 0u) || (ep->info_reported != 0u) || + (ep_pending(ep) == 0u)) { + continue; + } + /* One list per endpoint: the id alone for global work, id plus + * vCPU 0 when a per-vCPU notification is pending. Framework + * notifications are always global. */ + size = 0u; + if (((ep->pend_sp | ep->pend_vm) & ep->bound_pcpu) != 0u) { + size = 1u; + } + need = 1u + size; + if ((slot + need) > max_slots) { + more = 1u; + break; + } + out->regs[slot / slots_per_reg] |= + (uint64_t)ep->id << (16u * (slot % slots_per_reg)); + slot += need; + sizes |= (uint64_t)(size & 0x3u) << (12u + (2u * lists)); + lists++; + ep->info_reported = 1u; + } + if (lists == 0u) { + return WT_FFA_NO_DATA; + } + out->w2 = WT_FFA_NOTIF_INFO_COUNT(lists) | sizes | + ((more != 0u) ? WT_FFA_NOTIF_INFO_MORE : 0u); + return 0; +} + +int32_t wt_ffa_notif_frame_ready(uint16_t receiver_id) +{ + wt_notif_ep_t* receiver = ep_find(receiver_id); + + if (receiver == NULL) { + return WT_FFA_INVALID_PARAMETERS; + } + if ((receiver->gone != 0) || (receiver->has_bitmap == 0u)) { + return WT_FFA_DENIED; + } + return 0; +} + +int32_t wt_ffa_notif_frame_rx_full(uint16_t receiver_id, int sender_secure) +{ + wt_notif_ep_t* receiver = ep_find(receiver_id); + int32_t ret = wt_ffa_notif_frame_ready(receiver_id); + + if (ret != 0) { + return ret; + } + receiver->pend_fw |= (sender_secure != 0) ? WT_FFA_NOTIF_FW_SPM_RX_FULL + : WT_FFA_NOTIF_FW_NS_RX_FULL; + /* Every message is new work the receiver must be run for. */ + receiver->info_reported = 0u; + g_sri_pending = 1u; + return 0; +} + +int wt_ffa_notif_sri_take(void) +{ + int was = (int)g_sri_pending; + + g_sri_pending = 0u; + return was; +} + +int wt_ffa_notif_sri_pending(void) +{ + return (int)g_sri_pending; +} + +int wt_ffa_notif_sri_take_now(void) +{ + int was = (int)g_sri_now; + + g_sri_now = 0u; + return was; +} diff --git a/src/arch/aarch64/ffa/ffa_partinfo.c b/src/arch/aarch64/ffa/ffa_partinfo.c new file mode 100644 index 00000000..08ef8a95 --- /dev/null +++ b/src/arch/aarch64/ffa/ffa_partinfo.c @@ -0,0 +1,350 @@ +/* ffa_partinfo.c + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfTrust. + * + * wolfTrust 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. + * + * wolfTrust 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, see . + */ + +/* FFA_PARTITION_INFO_GET descriptor encoding (DEN0077A 1.2 6.1). The SPMC + * walks its configured partitions and writes one Table 6.1 descriptor per + * match into the caller's RX buffer, little-endian and byte-packed. */ + +#include "wolftrust/arch/aarch64/ffa_partinfo.h" +#include "wolftrust/arch/aarch64/ffa_abi.h" +#include "wolftrust/arch/aarch64/ffa_manifest.h" +#include "wolftrust/arch/aarch64/ffa_mem.h" + +uint32_t wt_ffa_partinfo_desc_size(uint32_t caller_version) +{ + uint32_t major = WT_FFA_VERSION_MAJOR_OF(caller_version); + uint32_t minor = WT_FFA_VERSION_MINOR_OF(caller_version); + + /* FF-A 1.1 added the UUID to the descriptor (6.1). */ + if ((major > 1u) || ((major == 1u) && (minor >= 1u))) { + return WT_FFA_PARTINFO_DESC_V11; + } + return WT_FFA_PARTINFO_DESC_V10; +} + +int wt_ffa_partinfo_from_manifest(const wt_ffa_partition_manifest_t* part, + uint16_t id, wt_ffa_partinfo_entry_t* out, + size_t cap, size_t* out_n) +{ + uint32_t u; + unsigned int j; + + if ((part == NULL) || (out_n == NULL)) { + return WT_FFA_INVALID_PARAMETERS; + } + *out_n = 0u; + if (id == 0u) { + return 0; + } + if ((part->uuids == NULL) || (part->uuid_count == 0u) || + (part->uuid_count > WT_FFA_MANIFEST_MAX_UUIDS)) { + return WT_FFA_INVALID_PARAMETERS; + } + if ((out == NULL) || (cap < part->uuid_count)) { + return WT_FFA_NO_MEMORY; + } + for (u = 0u; u < part->uuid_count; u++) { + out[u].id = id; + out[u].exec_contexts = (uint16_t)part->execution_contexts; + out[u].properties = WT_FFA_PARTINFO_PROP_AARCH64; + for (j = 0u; j < 16u; j++) { + out[u].uuid[j] = part->uuids[u].bytes[j]; + } + } + *out_n = part->uuid_count; + return 0; +} + +int wt_ffa_partinfo_props_of(const wt_ffa_partinfo_entry_t* parts, size_t n, + uint16_t id, uint32_t* props) +{ + size_t i; + int ret = WT_FFA_INVALID_PARAMETERS; + + if (props == NULL) { + return WT_FFA_INVALID_PARAMETERS; + } + *props = 0u; + for (i = 0u; (parts != NULL) && (i < n); i++) { + if (parts[i].id == id) { + *props |= parts[i].properties; + ret = 0; + } + } + return ret; +} + +int wt_ffa_direct_req_allowed(uint32_t props, uint32_t fid, int receive) +{ + uint32_t need; + + if (fid == WT_FFA_MSG_SEND_DIRECT_REQ2) { + need = (receive != 0) ? WT_FFA_PARTINFO_PROP_REQ2_RECV + : WT_FFA_PARTINFO_PROP_REQ2_SEND; + } + else { + need = (receive != 0) ? WT_FFA_PARTINFO_PROP_DIRECT_RECV + : WT_FFA_PARTINFO_PROP_DIRECT_SEND; + } + return ((props & need) != 0u) ? 0 : WT_FFA_DENIED; +} + +int wt_ffa_direct_req_authorize(const wt_ffa_partinfo_entry_t* parts, size_t n, + uint16_t sender, uint16_t receiver, + uint32_t fid) +{ + uint32_t props = 0u; + int ret; + + /* A sender discovery does not list advertises nothing, so sends nothing. */ + if (wt_ffa_partinfo_props_of(parts, n, sender, &props) != 0) { + props = 0u; + } + ret = wt_ffa_direct_req_allowed(props, fid, 0); + if (ret == 0) { + ret = wt_ffa_partinfo_props_of(parts, n, receiver, &props); + } + if (ret == 0) { + ret = wt_ffa_direct_req_allowed(props, fid, 1); + } + return ret; +} + +int wt_ffa_msg2_sender_allowed(const wt_ffa_partinfo_entry_t* parts, size_t n, + uint16_t sender) +{ + uint32_t props = 0u; + + if ((sender & 0x8000u) == 0u) { + return 0; + } + if (wt_ffa_partinfo_props_of(parts, n, sender, &props) != 0) { + props = 0u; + } + return ((props & WT_FFA_PARTINFO_PROP_INDIRECT) != 0u) ? 0 : WT_FFA_DENIED; +} + +static int uuid_is_nil(const uint8_t* u) +{ + unsigned int i; + + for (i = 0u; i < 16u; i++) { + if (u[i] != 0u) { + return 0; + } + } + return 1; +} + +static int uuid_equal(const uint8_t* a, const uint8_t* b) +{ + unsigned int i; + + for (i = 0u; i < 16u; i++) { + if (a[i] != b[i]) { + return 0; + } + } + return 1; +} + +static void write_desc(uint8_t* dst, uint32_t desc_size, + const wt_ffa_partinfo_entry_t* p, int nil) +{ + uint32_t props = p->properties; + unsigned int i; + + if (desc_size < WT_FFA_PARTINFO_DESC_V11) { + props &= WT_FFA_PARTINFO_PROP_V10_MASK; + } + for (i = 0u; i < desc_size; i++) { + dst[i] = 0u; + } + dst[0] = (uint8_t)(p->id & 0xFFu); + dst[1] = (uint8_t)((p->id >> 8) & 0xFFu); + dst[2] = (uint8_t)(p->exec_contexts & 0xFFu); + dst[3] = (uint8_t)((p->exec_contexts >> 8) & 0xFFu); + dst[4] = (uint8_t)(props & 0xFFu); + dst[5] = (uint8_t)((props >> 8) & 0xFFu); + dst[6] = (uint8_t)((props >> 16) & 0xFFu); + dst[7] = (uint8_t)((props >> 24) & 0xFFu); + if ((nil != 0) && (desc_size >= WT_FFA_PARTINFO_DESC_V11)) { + for (i = 0u; i < 16u; i++) { + dst[8u + i] = p->uuid[i]; + } + } +} + +int wt_ffa_partinfo_write(uint8_t* rx, size_t rx_size, uint32_t caller_version, + const wt_ffa_partinfo_entry_t* parts, size_t n, + const uint8_t* uuid16, uint32_t flags, + uint32_t* out_count, uint32_t* out_desc_size) +{ + uint32_t desc_size = wt_ffa_partinfo_desc_size(caller_version); + uint32_t count = 0u; + size_t off = 0u; + size_t i; + int nil; + int count_only; + + nil = uuid_is_nil(uuid16); + count_only = (flags & WT_FFA_PARTINFO_FLAG_COUNT) != 0u; + + for (i = 0u; i < n; i++) { + if ((nil == 0) && (uuid_equal(uuid16, parts[i].uuid) == 0)) { + continue; + } + if (count_only == 0) { + if ((rx == NULL) || ((off + desc_size) > rx_size)) { + return WT_FFA_NO_MEMORY; + } + write_desc(&rx[off], desc_size, &parts[i], nil); + off += desc_size; + } + count++; + } + *out_count = count; + *out_desc_size = (count_only != 0) ? 0u : desc_size; + return 0; +} + +int wt_ffa_partinfo_get(const uint64_t* x, uint32_t caller_version, + wt_ffa_mailbox_t* mb, + const wt_ffa_partinfo_entry_t* parts, size_t n, + uint32_t* count, uint32_t* size) +{ + uint8_t uuid[16]; + uint32_t word; + uint32_t flags; + unsigned int i; + int ret; + + if ((x == NULL) || (parts == NULL) || (n == 0u) || (count == NULL) || + (size == NULL)) { + return WT_FFA_INVALID_PARAMETERS; + } + /* Table 13.34: flag bits 31:1 are SBZ. */ + flags = (uint32_t)x[5] & WT_FFA_PARTINFO_FLAG_COUNT; + for (i = 0u; i < 4u; i++) { + word = (uint32_t)x[1u + i]; + uuid[4u * i + 0u] = (uint8_t)(word & 0xFFu); + uuid[4u * i + 1u] = (uint8_t)((word >> 8) & 0xFFu); + uuid[4u * i + 2u] = (uint8_t)((word >> 16) & 0xFFu); + uuid[4u * i + 3u] = (uint8_t)((word >> 24) & 0xFFu); + } + ret = wt_ffa_partinfo_write(NULL, 0u, caller_version, parts, n, uuid, + flags | WT_FFA_PARTINFO_FLAG_COUNT, count, + size); + if ((ret == 0) && (*count == 0u)) { + ret = WT_FFA_INVALID_PARAMETERS; + } + if ((ret != 0) || ((flags & WT_FFA_PARTINFO_FLAG_COUNT) != 0u)) { + return ret; + } + if (wt_ffa_mailbox_rx_acquire(mb) != 0) { + return WT_FFA_BUSY; + } + ret = wt_ffa_partinfo_write((uint8_t*)(uintptr_t)mb->rx, + (size_t)mb->pages * WT_FFA_MEM_PAGE_SIZE, + caller_version, parts, n, uuid, flags, + count, size); + if (ret != 0) { + (void)wt_ffa_mailbox_rx_release(mb); + } + return ret; +} + +static uint64_t uuid_half(const uint8_t* u) +{ + uint64_t v = 0u; + unsigned int i; + + for (i = 0u; i < 8u; i++) { + v |= (uint64_t)u[i] << (8u * i); + } + return v; +} + +int wt_ffa_partinfo_regs(const wt_ffa_partinfo_entry_t* parts, size_t n, + const uint8_t* uuid16, uint16_t start, uint16_t tag, + uint64_t* out18) +{ + uint32_t matches = 0u; + uint32_t written = 0u; + uint32_t last; + uint32_t reg; + size_t i; + int nil; + + if ((parts == NULL) || (uuid16 == NULL) || (out18 == NULL)) { + return WT_FFA_INVALID_PARAMETERS; + } + /* The callee's tag is always 0: MBZ at start 0, stale after it (13.9.2). */ + if (tag != 0u) { + return (start == 0u) ? WT_FFA_INVALID_PARAMETERS : WT_FFA_RETRY; + } + for (i = 0u; i < 18u; i++) { + out18[i] = 0u; + } + nil = uuid_is_nil(uuid16); + for (i = 0u; i < n; i++) { + if ((nil == 0) && (uuid_equal(uuid16, parts[i].uuid) == 0)) { + continue; + } + if ((matches >= start) && (written < WT_FFA_PARTINFO_REGS_PER_CALL)) { + reg = 3u + (3u * written); + out18[reg] = (uint64_t)parts[i].id | + ((uint64_t)parts[i].exec_contexts << 16) | + ((uint64_t)parts[i].properties << 32); + if (nil != 0) { + out18[reg + 1u] = uuid_half(&parts[i].uuid[0]); + out18[reg + 2u] = uuid_half(&parts[i].uuid[8]); + } + written++; + } + matches++; + } + if ((matches == 0u) || (written == 0u)) { + return WT_FFA_INVALID_PARAMETERS; + } + last = matches - 1u; + out18[0] = WT_FFA_SUCCESS64; + out18[2] = (uint64_t)last | + ((uint64_t)((uint32_t)start + written - 1u) << 16) | + ((uint64_t)WT_FFA_PARTINFO_DESC_V11 << 48); + return 0; +} + +int wt_ffa_partinfo_regs_call(const wt_ffa_partinfo_entry_t* parts, size_t n, + const uint64_t* x, uint64_t* out18) +{ + uint8_t uuid[16]; + unsigned int i; + + if ((x == NULL) || (n == 0u)) { + return WT_FFA_INVALID_PARAMETERS; + } + for (i = 0u; i < 16u; i++) { + uuid[i] = (uint8_t)(x[1u + (i / 8u)] >> (8u * (i % 8u))); + } + /* Table 13.39: x3 bits 63:32 are SBZ. */ + return wt_ffa_partinfo_regs(parts, n, uuid, (uint16_t)(x[3] & 0xFFFFu), + (uint16_t)((x[3] >> 16) & 0xFFFFu), out18); +} diff --git a/src/arch/aarch64/ffa/ffa_runtime.c b/src/arch/aarch64/ffa/ffa_runtime.c new file mode 100644 index 00000000..1219f5f5 --- /dev/null +++ b/src/arch/aarch64/ffa/ffa_runtime.c @@ -0,0 +1,159 @@ +/* ffa_runtime.c + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfTrust. + * + * wolfTrust 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. + * + * wolfTrust 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, see . + */ + +/* FF-A partition runtime state machine (DEN0077A Ch.8). The SPMC keeps one of + * these per execution context and consults wt_ffa_rt_transition before it + * enters a partition, so an illegal FF-A call can never resume a partition + * from a state the framework forbids. */ + +#include "wolftrust/arch/aarch64/ffa_abi.h" +#include "wolftrust/arch/aarch64/ffa_runtime.h" + +#include + +int wt_ffa_rt_transition(wt_ffa_rt_state_t *state, wt_ffa_rt_event_t event) +{ + wt_ffa_rt_state_t cur; + + if (state == NULL) { + return WT_FFA_INVALID_PARAMETERS; + } + cur = *state; + + switch (event) { + case WT_FFA_RT_EV_RUN: + /* FFA_RUN can allocate cycles to a waiting, blocked, or preempted + * context (§8.1); a running one is DENIED. */ + if (cur == WT_FFA_RT_WAITING || cur == WT_FFA_RT_BLOCKED || + cur == WT_FFA_RT_PREEMPTED) { + *state = WT_FFA_RT_RUNNING; + return 0; + } + return WT_FFA_DENIED; + case WT_FFA_RT_EV_DIRECT_REQ: + /* A direct request lands only on a waiting receiver (§7.4); any + * other state is BUSY, not DENIED. */ + if (cur == WT_FFA_RT_WAITING) { + *state = WT_FFA_RT_RUNNING; + return 0; + } + return WT_FFA_BUSY; + case WT_FFA_RT_EV_MSG_WAIT: + case WT_FFA_RT_EV_DIRECT_RESP: + if (cur == WT_FFA_RT_RUNNING) { + *state = WT_FFA_RT_WAITING; + return 0; + } + return WT_FFA_DENIED; + case WT_FFA_RT_EV_YIELD: + if (cur == WT_FFA_RT_RUNNING) { + *state = WT_FFA_RT_BLOCKED; + return 0; + } + return WT_FFA_DENIED; + case WT_FFA_RT_EV_INTERRUPT: + if (cur == WT_FFA_RT_RUNNING) { + *state = WT_FFA_RT_PREEMPTED; + return 0; + } + return WT_FFA_DENIED; + default: + return WT_FFA_INVALID_PARAMETERS; + } +} + +int wt_ffa_rt_init_call(uint32_t fid, int target_initialized) +{ + switch (fid) { + case WT_FFA_MSG_SEND_DIRECT_REQ32: + case WT_FFA_MSG_SEND_DIRECT_REQ64: + case WT_FFA_MSG_SEND_DIRECT_REQ2: + return (target_initialized != 0) ? 0 : WT_FFA_DENIED; + case WT_FFA_YIELD: + case WT_FFA_RUN: + case WT_FFA_MSG_SEND_DIRECT_RESP32: + case WT_FFA_MSG_SEND_DIRECT_RESP64: + case WT_FFA_MSG_SEND_DIRECT_RESP2: + return WT_FFA_DENIED; + default: + return 0; + } +} + +int wt_ffa_rt_success_check(const uint64_t* x) +{ + unsigned int i; + + if ((uint32_t)x[0] == WT_FFA_SUCCESS64) { + for (i = 1u; i < 18u; i++) { + if (x[i] != 0u) { + return WT_FFA_INVALID_PARAMETERS; + } + } + return 0; + } + for (i = 1u; i < 8u; i++) { + if ((uint32_t)x[i] != 0u) { + return WT_FFA_INVALID_PARAMETERS; + } + } + return 0; +} + +int wt_ffa_rt_error_check(const uint64_t* x) +{ + if (((uint32_t)x[1] != 0u) || ((int32_t)(uint32_t)x[2] >= 0)) { + return WT_FFA_INVALID_PARAMETERS; + } + return 0; +} + +int wt_ffa_rt_yield_check(const uint64_t* x) +{ + if (((uint32_t)x[1] | (uint32_t)x[2] | (uint32_t)x[3]) != 0u) { + return WT_FFA_INVALID_PARAMETERS; + } + return 0; +} + +int wt_ffa_rt_msg_wait_releases_rx(uint32_t version, const uint64_t* x) +{ + if ((version >= WT_FFA_VERSION_1_2) && + (((uint32_t)x[2] & WT_FFA_MSG_WAIT_RETAIN_RX) != 0u)) { + return 0; + } + return 1; +} + +const char *wt_ffa_rt_state_name(wt_ffa_rt_state_t state) +{ + switch (state) { + case WT_FFA_RT_WAITING: + return "waiting"; + case WT_FFA_RT_RUNNING: + return "running"; + case WT_FFA_RT_PREEMPTED: + return "preempted"; + case WT_FFA_RT_BLOCKED: + return "blocked"; + default: + return "invalid"; + } +} diff --git a/src/arch/aarch64/ffa/ffa_spmd.c b/src/arch/aarch64/ffa/ffa_spmd.c new file mode 100644 index 00000000..59e74043 --- /dev/null +++ b/src/arch/aarch64/ffa/ffa_spmd.c @@ -0,0 +1,516 @@ +/* ffa_spmd.c + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfTrust. + * + * wolfTrust 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. + * + * wolfTrust 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, see . + */ + +/* SPMD: FF-A calls arriving at the Secure physical instance (from the SPMC). + * Every reply zeroes the unused result registers (11.2). */ + +#include "wolftrust/arch/aarch64/el3.h" +#include "wolftrust/arch/aarch64/ffa_abi.h" +#include "wolftrust/arch/aarch64/spm_svc.h" +#include "wolftrust/arch/aarch64/ffa.h" +#include "wolftrust/arch/aarch64/ffa_mem.h" +#include "wolftrust/arch/aarch64/ffa_msg.h" + +static unsigned int g_spmc_ready; +static wt_ffa_version_state_t g_ns_version; +static wt_ffa_version_state_t g_spmc_version; +/* The FFA_VERSION input forwarded to the SPMC, awaiting its Table 13.8 answer. */ +static uint32_t g_ns_version_asked; +static uint8_t g_ns_version_forwarded; + +static void reply_error(wt_ffa_regs_t* r, int32_t code) +{ + unsigned int i; + + for (i = 0u; i < 8u; i++) { + r->x[i] = 0u; + } + r->x[0] = WT_FFA_ERROR; + r->x[2] = (uint64_t)(uint32_t)code; +} + +static void reply_success(wt_ffa_regs_t* r, uint64_t w2, uint64_t w3) +{ + unsigned int i; + + for (i = 0u; i < 8u; i++) { + r->x[i] = 0u; + } + r->x[0] = WT_FFA_SUCCESS32; + r->x[2] = w2; + r->x[3] = w3; +} + +static int spmd_implements(uint32_t fid) +{ + switch (fid) { + case WT_FFA_ERROR: + case WT_FFA_SUCCESS32: + case WT_FFA_SUCCESS64: + case WT_FFA_VERSION: + case WT_FFA_FEATURES: + case WT_FFA_ID_GET: + case WT_FFA_SPM_ID_GET: + case WT_FFA_MSG_WAIT: + case WT_FFA_NORMAL_WORLD_RESUME: + case WT_FFA_CONSOLE_LOG32: + case WT_FFA_CONSOLE_LOG64: + return 1; + default: + return 0; + } +} + +unsigned int wt_ffa_spmd_spmc_ready(void) +{ + return g_spmc_ready; +} + +/* 13.12: w1 = count (bits 31:8 SBZ), characters tightly packed from w2/x2 + * upward; 1..24 characters over w2-w7, 1..128 over x2-x17. */ +void wt_ffa_spmd_console_call(uint64_t* x, unsigned int is64) +{ + uint32_t count = wt_ffa_console_count(x[1], is64); + unsigned int per_reg = (is64 != 0u) ? 8u : 4u; + unsigned int i; + uint64_t reg; + + if (count == 0u) { + reply_error((wt_ffa_regs_t*)x, WT_FFA_INVALID_PARAMETERS); + } + else { + for (i = 0u; i < count; i++) { + reg = x[2u + (i / per_reg)]; + wt_platform_console_putc( + (char)((reg >> (8u * (i % per_reg))) & 0xFFu)); + } + reply_success((wt_ffa_regs_t*)x, 0u, 0u); + } + if (is64 != 0u) { + wt_ffa_reply_clear_ext(WT_FFA_CONSOLE_LOG64, x); + } +} + +#if defined(WT_EL3_TEST_DRIVER) && (WT_EL3_TEST_DRIVER == 1) +/* The monitor exit call lives in the host-untranslatable monitor ABI, so the + * driver alone includes it. */ +#include "wolftrust/arch/aarch64/monitor_abi.h" + +/* Normal-world stand-in for the ffa-direct proof (never in a production + * image): the SPMC's first post-init wait is answered with one direct request + * to the echo partition, and the relayed response ends the run. */ +static int test_driver_request(wt_ffa_regs_t* r) +{ + static const uint32_t payload[WT_FFA_DIRECT_PAYLOAD_WORDS] = { + WT_FFA_TEST_PAYLOAD, 0u, 0u, 0u, 0u + }; + + wt_el3_puts("[EL3] spmc ready\r\n[EL3] direct req to=0x"); + wt_el3_puthex(WT_FFA_ID_ECHO, 4u); + wt_el3_puts("\r\n"); + wt_ffa_direct_build(r->x, WT_FFA_MSG_SEND_DIRECT_REQ32, WT_FFA_ID_NS_PRIMARY, + WT_FFA_ID_ECHO, payload); + return 1; +} + +static int test_driver_response(wt_ffa_regs_t* r) +{ + int ok = (wt_ffa_direct_resp_check(r->x, WT_FFA_INSTANCE_NS_PHYSICAL) == 0) && + (wt_ffa_direct_sender(r->x[1]) == WT_FFA_ID_ECHO) && + (wt_ffa_direct_receiver(r->x[1]) == WT_FFA_ID_NS_PRIMARY) && + ((uint32_t)r->x[3] == (uint32_t)~WT_FFA_TEST_PAYLOAD); + + wt_el3_puts(ok ? "[EL3] direct resp ok from=0x" : "[EL3] direct resp BAD from=0x"); + wt_el3_puthex(wt_ffa_direct_sender(r->x[1]), 4u); + wt_el3_puts(" x3=0x"); + wt_el3_puthex((uint32_t)r->x[3], 8u); + wt_el3_puts("\r\n"); + wt_platform_console_flush(); + (void)wt_el3_monitor_call(WT_MON_FID_EXIT, + ok ? WT_MON_EXIT_SUCCESS : WT_MON_EXIT_PANIC); + return 1; +} +#else +static int test_driver_request(wt_ffa_regs_t* r) +{ + (void)r; + return 0; +} + +static int test_driver_response(wt_ffa_regs_t* r) +{ + (void)r; + return 0; +} +#endif + +static int ns_implements(uint32_t fid) +{ + switch (fid) { + case WT_FFA_ERROR: + case WT_FFA_SUCCESS32: + case WT_FFA_SUCCESS64: + case WT_FFA_INTERRUPT: + case WT_FFA_VERSION: + case WT_FFA_FEATURES: + case WT_FFA_ID_GET: + case WT_FFA_SPM_ID_GET: + case WT_FFA_PARTITION_INFO_GET: + case WT_FFA_PARTITION_INFO_GET_REGS: + case WT_FFA_RXTX_MAP32: + case WT_FFA_RXTX_MAP64: + case WT_FFA_RXTX_UNMAP: + case WT_FFA_RX_RELEASE: + case WT_FFA_MSG_WAIT: + case WT_FFA_RUN: + case WT_FFA_MSG_SEND_DIRECT_REQ32: + case WT_FFA_MSG_SEND_DIRECT_REQ64: + case WT_FFA_MSG_SEND_DIRECT_RESP32: + case WT_FFA_MSG_SEND_DIRECT_RESP64: + case WT_FFA_MSG_SEND_DIRECT_REQ2: + case WT_FFA_MSG_SEND_DIRECT_RESP2: + case WT_FFA_MEM_SHARE32: + case WT_FFA_MEM_SHARE64: + case WT_FFA_MEM_LEND32: + case WT_FFA_MEM_LEND64: + case WT_FFA_MEM_DONATE32: + case WT_FFA_MEM_DONATE64: + case WT_FFA_MEM_RETRIEVE_REQ32: + case WT_FFA_MEM_RETRIEVE_REQ64: + case WT_FFA_MEM_RETRIEVE_RESP: + case WT_FFA_MEM_RELINQUISH: + case WT_FFA_MEM_RECLAIM: + case WT_FFA_MEM_FRAG_RX: + case WT_FFA_MEM_FRAG_TX: + case WT_FFA_NOTIFICATION_BITMAP_CREATE: + case WT_FFA_NOTIFICATION_BITMAP_DESTROY: + case WT_FFA_NOTIFICATION_BIND: + case WT_FFA_NOTIFICATION_UNBIND: + case WT_FFA_NOTIFICATION_SET: + case WT_FFA_NOTIFICATION_GET: + case WT_FFA_NOTIFICATION_INFO_GET32: + case WT_FFA_NOTIFICATION_INFO_GET64: + case WT_FFA_MSG_SEND2: + return 1; + default: + return 0; + } +} + +/* Any NS call but FFA_VERSION settles the version the guest negotiated. */ +void wt_ffa_spmd_ns_note(uint32_t fid) +{ + if (wt_ffa_fid_in_range(fid) && (fid != WT_FFA_VERSION)) { + wt_ffa_version_lock(&g_ns_version, WT_FFA_VERSION_1_2); + } +} + +/* An ABI the SPMC's negotiated version has (13.2.2), for the calls the + * monitor serves outside wt_ffa_spmd_secure_call. */ +int wt_ffa_spmd_secure_available(uint32_t fid) +{ + return wt_ffa_fid_available( + fid, wt_ffa_version_of(&g_spmc_version, WT_FFA_VERSION_1_2)); +} + +/* Any SPMC call but FFA_VERSION settles the version it negotiated (13.2). */ +void wt_ffa_spmd_secure_note(uint32_t fid) +{ + if (wt_ffa_fid_in_range(fid) && (fid != WT_FFA_VERSION)) { + wt_ffa_version_lock(&g_spmc_version, WT_FFA_VERSION_1_2); + } +} + +/* NS-instance FIDs the SPMD cannot answer alone (it has no manifest and owns + * no mailbox): they are forwarded to the SPMC. */ +int wt_ffa_spmd_ns_forwards(uint32_t fid) +{ + if (!wt_ffa_fid_available( + fid, wt_ffa_version_of(&g_ns_version, WT_FFA_VERSION_1_2))) { + return 0; + } + switch (fid) { + case WT_FFA_VERSION: + /* 13.2.3.2: the SPMC chooses what the Normal world negotiates; once + * that is locked the SPMD answers a repeat itself. */ + return (g_ns_version.locked == 0u) ? 1 : 0; + case WT_FFA_RXTX_MAP32: + case WT_FFA_RXTX_MAP64: + case WT_FFA_RXTX_UNMAP: + case WT_FFA_RX_RELEASE: + case WT_FFA_RUN: + case WT_FFA_PARTITION_INFO_GET: + case WT_FFA_PARTITION_INFO_GET_REGS: + case WT_FFA_MSG_SEND_DIRECT_REQ32: + case WT_FFA_MSG_SEND_DIRECT_REQ64: + case WT_FFA_MSG_SEND_DIRECT_REQ2: + case WT_FFA_MEM_SHARE32: + case WT_FFA_MEM_SHARE64: + case WT_FFA_MEM_LEND32: + case WT_FFA_MEM_LEND64: + case WT_FFA_MEM_DONATE32: + case WT_FFA_MEM_DONATE64: + case WT_FFA_MEM_RETRIEVE_REQ32: + case WT_FFA_MEM_RETRIEVE_REQ64: + case WT_FFA_MEM_RELINQUISH: + case WT_FFA_MEM_RECLAIM: + case WT_FFA_MEM_FRAG_RX: + case WT_FFA_MEM_FRAG_TX: + case WT_FFA_NOTIFICATION_BITMAP_CREATE: + case WT_FFA_NOTIFICATION_BITMAP_DESTROY: + case WT_FFA_NOTIFICATION_BIND: + case WT_FFA_NOTIFICATION_UNBIND: + case WT_FFA_NOTIFICATION_SET: + case WT_FFA_NOTIFICATION_GET: + case WT_FFA_NOTIFICATION_INFO_GET32: + case WT_FFA_NOTIFICATION_INFO_GET64: + case WT_FFA_MSG_SEND2: +#if defined(WT_FFA_ACS) && (WT_FFA_ACS == 1) + case WT_SPM_SVC_FID_TIMER_ARM: + case WT_SPM_SVC_FID_TIMER_STOP: +#endif + return 1; + default: + return 0; + } +} + +/* FFA_VERSION reaches the SPMC as the Table 13.7 framework message. 7.4.2: + * the SPMD relays a direct request only from the Normal world to a Secure + * endpoint, so nothing it forwards carries a Secure sender id. */ +int wt_ffa_spmd_ns_forward(uint64_t* x) +{ + uint32_t fid = (uint32_t)x[0]; + + if (fid == WT_FFA_VERSION) { + g_ns_version_asked = (uint32_t)x[1]; + g_ns_version_forwarded = 1u; + wt_ffa_fwk_version_req(x, g_ns_version_asked); + return 1; + } + if (((fid == WT_FFA_MSG_SEND_DIRECT_REQ32) || + (fid == WT_FFA_MSG_SEND_DIRECT_REQ64) || + (fid == WT_FFA_MSG_SEND_DIRECT_REQ2)) && + (wt_ffa_direct_req_check(x, WT_FFA_INSTANCE_NS_PHYSICAL) != 0)) { + reply_error((wt_ffa_regs_t*)x, WT_FFA_INVALID_PARAMETERS); + wt_ffa_reply_clear_ext(fid, x); + return 0; + } + /* Only x0-x7 travel, and an 8-register reply leaves x8-x17 as here. */ + if (wt_ffa_fid_in_range(fid) && + (wt_ffa_msg_reg_count(fid) == WT_FFA_MSG_REGS)) { + wt_ffa_reply_clear_ext(fid, x); + } + return 1; +} + +void wt_ffa_spmd_ns_reply(uint64_t* x) +{ + int32_t result; + unsigned int i; + + if (g_ns_version_forwarded == 0u) { + return; + } + g_ns_version_forwarded = 0u; + result = wt_ffa_fwk_version_result(x); + if (result >= 0) { + g_ns_version.version = wt_ffa_version_settle(g_ns_version_asked, + (uint32_t)result); + } + for (i = 0u; i < 8u; i++) { + x[i] = 0u; + } + x[0] = (uint64_t)(uint32_t)result; +} + +/* An SMC from the SPMC that is the reply to a call the SPMD forwarded from the + * Normal world; it is routed back to the waiting Normal world. */ +int wt_ffa_spmd_is_ns_reply(uint32_t fid) +{ + switch (fid) { + case WT_FFA_SUCCESS32: + case WT_FFA_SUCCESS64: + case WT_FFA_ERROR: + case WT_FFA_INTERRUPT: + case WT_FFA_MEM_FRAG_RX: + case WT_FFA_MSG_SEND_DIRECT_RESP32: + case WT_FFA_MSG_SEND_DIRECT_RESP64: + case WT_FFA_MSG_SEND_DIRECT_RESP2: + case WT_FFA_YIELD: + case WT_FFA_MSG_WAIT: + return 1; + default: + return 0; + } +} + +/* An SMC from the SPMC yielding the CPU back to a preempted Normal world: + * only FFA_NORMAL_WORLD_RESUME does (14.4). */ +int wt_ffa_spmd_is_ns_resume(uint32_t fid) +{ + return (fid == WT_FFA_NORMAL_WORLD_RESUME) ? 1 : 0; +} + +int wt_ffa_spmd_pm_answer(uint64_t* x) +{ + uint32_t fid = (uint32_t)x[0]; + + if ((fid == WT_FFA_MSG_WAIT) || (fid == WT_FFA_YIELD) || + (wt_ffa_spmd_is_ns_resume(fid) != 0)) { + reply_error((wt_ffa_regs_t*)x, WT_FFA_DENIED); + return WT_SPMD_PM_REFUSED; + } + if (wt_ffa_spmd_is_ns_reply(fid) == 0) { + return WT_SPMD_PM_NONE; + } + return (wt_ffa_fwk_pm_granted(x) != 0) ? WT_SPMD_PM_GRANTED + : WT_SPMD_PM_DENIED; +} + +/* NS physical instance (13.x): FF-A calls arriving from the Normal world once + * the SPMD has launched it. B3.2 serves version negotiation and discovery + * (FEATURES, ID_GET, SPM_ID_GET); direct messaging and the interrupt loop + * follow in later B3 slices. */ +void wt_ffa_spmd_ns_call(wt_ffa_regs_t* r) +{ + uint32_t fid = (uint32_t)r->x[0]; + uint32_t w1 = (uint32_t)r->x[1]; + uint32_t version = wt_ffa_version_of(&g_ns_version, WT_FFA_VERSION_1_2); + unsigned int i; + + if (!wt_ffa_fid_available(fid, version)) { + reply_error(r, WT_FFA_NOT_SUPPORTED); + return; + } + switch (fid) { + case WT_FFA_VERSION: + for (i = 1u; i < 8u; i++) { + r->x[i] = 0u; + } + r->x[0] = (uint64_t)(uint32_t)wt_ffa_version_negotiate( + &g_ns_version, w1, WT_FFA_VERSION_1_2); + break; + case WT_FFA_FEATURES: + /* Table 13.11: w2 is MBZ for a feature id query. */ + if ((w1 == WT_FFA_FEATURE_SRI) && ((uint32_t)r->x[2] == 0u)) { + reply_success(r, WT_FFA_SRI_INTID, 0u); + } + else if ((w1 == WT_FFA_MEM_RETRIEVE_REQ32) || + (w1 == WT_FFA_MEM_RETRIEVE_REQ64)) { + /* The NS-bit request is an SP rule (DEN0140 1.10.4.1.1). */ + reply_success(r, WT_FFA_FEATURES_RETRIEVE_NS_BIT, 0u); + } + else if (WT_FFA_FEATURES_IS_FID(w1) && ns_implements(w1) && + wt_ffa_fid_available(w1, version)) { + /* w3[5:0] caps RXTX_MAP at 63 pages: no limit (Tbl 13.25). */ + reply_success(r, 0u, 0u); + } + else { + reply_error(r, WT_FFA_NOT_SUPPORTED); + } + break; + case WT_FFA_ID_GET: + /* The caller's own id: the primary Normal-world endpoint is 0 + * (DEV-01: the SPMD plays the Hypervisor id-allocation role). */ + reply_success(r, WT_FFA_ID_NS_PRIMARY, 0u); + break; + case WT_FFA_SPM_ID_GET: + /* The SPM the Normal world talks to is the SPMC. */ + reply_success(r, WT_FFA_ID_SPMC, 0u); + break; + case WT_FFA_MSG_WAIT: + case WT_FFA_MSG_SEND_DIRECT_RESP32: + case WT_FFA_MSG_SEND_DIRECT_RESP64: + case WT_FFA_MSG_SEND_DIRECT_RESP2: + /* The primary Normal-world endpoint is never a message receiver. */ + reply_error(r, WT_FFA_DENIED); + break; + default: + reply_error(r, WT_FFA_NOT_SUPPORTED); + break; + } +} + +int wt_ffa_spmd_secure_call(wt_ffa_regs_t* r) +{ + uint32_t fid = (uint32_t)r->x[0]; + uint32_t w1 = (uint32_t)r->x[1]; + uint32_t version = wt_ffa_version_of(&g_spmc_version, WT_FFA_VERSION_1_2); + unsigned int i; + + if (!wt_ffa_fid_available(fid, version)) { + reply_error(r, WT_FFA_NOT_SUPPORTED); + return WT_SPMD_ACTION_REPLY; + } + switch (fid) { + case WT_FFA_VERSION: + for (i = 1u; i < 8u; i++) { + r->x[i] = 0u; + } + r->x[0] = (uint64_t)(uint32_t)wt_ffa_version_negotiate( + &g_spmc_version, w1, WT_FFA_VERSION_1_2); + break; + case WT_FFA_FEATURES: + if (WT_FFA_FEATURES_IS_FID(w1) && spmd_implements(w1) && + wt_ffa_fid_available(w1, version)) { + reply_success(r, 0u, 0u); + } + else { + reply_error(r, WT_FFA_NOT_SUPPORTED); + } + break; + case WT_FFA_ID_GET: + reply_success(r, WT_FFA_ID_SPMC, 0u); + break; + case WT_FFA_SPM_ID_GET: + reply_success(r, WT_FFA_ID_SPMD, 0u); + break; + case WT_FFA_CONSOLE_LOG32: + wt_ffa_spmd_console_call(r->x, 0u); + break; + case WT_FFA_MSG_WAIT: + /* 5.5: the first MSG_WAIT from the SPMC ends its initialization; the + * SPMD then turns on the Normal world. */ + if (g_spmc_ready == 0u) { + g_spmc_ready = 1u; + if (test_driver_request(r) != 0) { + break; + } + return WT_SPMD_ACTION_LAUNCH; + } + reply_error(r, WT_FFA_DENIED); + break; + case WT_FFA_NORMAL_WORLD_RESUME: + /* 14.4.1: the monitor resumes a preempted Normal world before this + * dispatch, so reaching it means none was preempted. */ + reply_error(r, WT_FFA_DENIED); + break; + case WT_FFA_MSG_SEND_DIRECT_RESP32: + if (test_driver_response(r) == 0) { + reply_error(r, WT_FFA_NOT_SUPPORTED); + } + break; + default: + reply_error(r, WT_FFA_NOT_SUPPORTED); + break; + } + return WT_SPMD_ACTION_REPLY; +} diff --git a/src/arch/aarch64/ffa/psa_ffa_transport_arch.c b/src/arch/aarch64/ffa/psa_ffa_transport_arch.c new file mode 100644 index 00000000..77f61dbd --- /dev/null +++ b/src/arch/aarch64/ffa/psa_ffa_transport_arch.c @@ -0,0 +1,133 @@ +/* psa_ffa_transport_arch.c + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfTrust. + * + * wolfTrust 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. + * + * wolfTrust 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, see . + */ + +/* AArch64 binding of the PSA client transport. The WolfTrust_FFM_* entry + * points the operating-system-neutral client (src/client/psa_ffm_client.c) + * calls are the CMSE veneers on Armv8-M; here each one is carried as an FF-A + * SMC64 direct request to the PSA framework endpoint over the SMC conduit, + * hiding the preemption resume loop (an FFA_INTERRUPT return is resumed with + * FFA_RUN until the direct response arrives). */ + +#include "wolftrust/arch/aarch64/ffa.h" +#include "wolftrust/arch/aarch64/ffa_abi.h" +#include "wolftrust/arch/aarch64/psa_ffa.h" +#include "wolftrust/ffm_veneer.h" +#include "wolftrust/arch/aarch64/psa_ffa_transport.h" + +#include "psa/client.h" +#include "psa/error.h" + +#if defined(__aarch64__) +static void transport_smc(wt_ffa_regs_t* r) +{ + wt_ffa_smc(r); +} +#else +static void transport_smc(wt_ffa_regs_t* r) +{ + wt_ffa_transport_smc(r); +} +#endif + +int wt_psa_ffa_op(uint32_t op, uint64_t a0, uint64_t a1, uint32_t* result) +{ + wt_ffa_regs_t r; + unsigned int i; + unsigned int guard = 0u; + + for (i = 0u; i < 8u; i++) { + r.x[i] = 0u; + } + r.x[0] = WT_FFA_MSG_SEND_DIRECT_REQ64; + r.x[1] = ((uint64_t)WT_FFA_ID_NS_PRIMARY << 16) | WT_FFA_ID_PSA; + r.x[3] = op; + r.x[4] = a0; + r.x[5] = a1; + for (;;) { + transport_smc(&r); + if ((uint32_t)r.x[0] != WT_FFA_INTERRUPT) { + break; + } + if (++guard > WT_PSA_FFA_MAX_RESUME) { + return -1; + } + for (i = 0u; i < 8u; i++) { + r.x[i] = 0u; + } + r.x[0] = WT_FFA_RUN; + r.x[1] = (uint64_t)WT_FFA_ID_PSA << 16; + } + if ((uint32_t)r.x[0] != WT_FFA_MSG_SEND_DIRECT_RESP64) { + return -1; + } + *result = (uint32_t)r.x[3]; + return 0; +} + +uint32_t WolfTrust_FFM_FrameworkVersion(void) +{ + uint32_t result = 0u; + + if (wt_psa_ffa_op(WT_PSA_FFA_OP_FRAMEWORK_VERSION, 0u, 0u, &result) != 0) { + return 0u; + } + return result; +} + +uint32_t WolfTrust_FFM_ServiceVersion(uint32_t sid) +{ + uint32_t result = 0u; + + if (wt_psa_ffa_op(WT_PSA_FFA_OP_SERVICE_VERSION, sid, 0u, &result) != 0) { + return PSA_VERSION_NONE; + } + return result; +} + +int32_t WolfTrust_FFM_Connect(uint32_t sid, uint32_t version) +{ + uint32_t result = 0u; + + if (wt_psa_ffa_op(WT_PSA_FFA_OP_CONNECT, sid, version, &result) != 0) { + return (int32_t)PSA_ERROR_COMMUNICATION_FAILURE; + } + return (int32_t)result; +} + +int32_t WolfTrust_FFM_Call(int32_t handle, int32_t type, + wt_ffm_veneer_iovec_t* ns_iovec) +{ + uint32_t result = 0u; + uint64_t a1 = ((uint64_t)(uint32_t)type << 32) | (uint64_t)(uint32_t)handle; + + if (wt_psa_ffa_op(WT_PSA_FFA_OP_CALL, (uint64_t)(uintptr_t)ns_iovec, a1, + &result) != 0) { + return (int32_t)PSA_ERROR_COMMUNICATION_FAILURE; + } + return (int32_t)result; +} + +void WolfTrust_FFM_Close(int32_t handle) +{ + uint32_t result = 0u; + + (void)wt_psa_ffa_op(WT_PSA_FFA_OP_CLOSE, (uint64_t)(uint32_t)handle, 0u, + &result); +} diff --git a/src/arch/aarch64/gic/gicv2.c b/src/arch/aarch64/gic/gicv2.c new file mode 100644 index 00000000..09d1f24e --- /dev/null +++ b/src/arch/aarch64/gic/gicv2.c @@ -0,0 +1,189 @@ +/* gicv2.c + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfTrust. + * + * wolfTrust 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. + * + * wolfTrust 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, see . + */ + +/* GICv2 (GIC-400) secure-side driver: memory-mapped distributor and CPU + * interface; Group 0 signals FIQ, Group 1 IRQ. */ + +#include "memory_map.h" +#include "wolftrust/arch/aarch64/gic.h" + +#define GICD_CTLR 0x000u +#define GICD_TYPER 0x004u +#define GICD_IGROUPR 0x080u +#define GICD_ISENABLER 0x100u +#define GICD_ISPENDR 0x200u +#define GICD_ITARGETSR 0x800u +#define GICD_ICENABLER 0x180u +#define GICD_ICPENDR 0x280u +#define GICD_IPRIORITYR 0x400u +#define GICD_SGIR 0xF00u +#define GICD_CTLR_ENABLE_GRP0 (1u << 0) +#define GICD_CTLR_ENABLE_GRP1 (1u << 1) + +#define GICC_CTLR 0x000u +#define GICC_PMR 0x004u +#define GICC_IAR 0x00Cu +#define GICC_EOIR 0x010u +#define GICC_CTLR_ENABLE_GRP0 (1u << 0) +#define GICC_CTLR_ENABLE_GRP1 (1u << 1) +#define GICC_CTLR_FIQ_EN (1u << 3) + +static volatile uint32_t* gicd(uint32_t offset) +{ + return (volatile uint32_t*)(uintptr_t)(WT_GICD_BASE + offset); +} + +static volatile uint32_t* gicc(uint32_t offset) +{ + return (volatile uint32_t*)(uintptr_t)(WT_GICC_BASE + offset); +} + +static uint32_t line_count(void) +{ + return ((*gicd(GICD_TYPER) & 0x1Fu) + 1u) * 32u; +} + +static void gicv2_set_group0(uint32_t intid) +{ + if (intid < WT_GIC_INTID_LIMIT) { + *gicd(GICD_IGROUPR + (intid / 32u) * 4u) &= ~(1u << (intid % 32u)); + } +} + +static void gicv2_enable(uint32_t intid) +{ + if (intid < WT_GIC_INTID_LIMIT) { + if (intid >= 32u) { + /* A GICv2 SPI is delivered only to a targeted CPU interface; route + * it to CPU 0. SGIs and PPIs are per-CPU and need no target. */ + ((volatile uint8_t*)gicd(GICD_ITARGETSR))[intid] = 0x01u; + } + *gicd(GICD_ISENABLER + (intid / 32u) * 4u) = 1u << (intid % 32u); + } +} + +static void gicv2_disable(uint32_t intid) +{ + if (intid < WT_GIC_INTID_LIMIT) { + *gicd(GICD_ICENABLER + (intid / 32u) * 4u) = 1u << (intid % 32u); + } +} + +static void gicv2_set_priority(uint32_t intid, uint8_t priority) +{ + if (intid < WT_GIC_INTID_LIMIT) { + *(volatile uint8_t*)(uintptr_t)(WT_GICD_BASE + GICD_IPRIORITYR + intid) = + priority; + } +} + +static uint32_t gicv2_ack_group0(void) +{ + return *gicc(GICC_IAR) & 0x3FFu; +} + +static void gicv2_eoi_group0(uint32_t intid) +{ + *gicc(GICC_EOIR) = intid; +} + +static void gicv2_init_secure(void) +{ + uint32_t lines = line_count(); + uint32_t i; + + *gicd(GICD_CTLR) = 0u; + for (i = 0u; i < lines; i += 32u) { + *gicd(GICD_ICENABLER + (i / 32u) * 4u) = 0xFFFFFFFFu; + *gicd(GICD_IGROUPR + (i / 32u) * 4u) = 0xFFFFFFFFu; + } + gicv2_set_group0(WT_GIC_INTID_SECURE_TIMER); + gicv2_set_priority(WT_GIC_INTID_SECURE_TIMER, 0x00u); + *gicd(GICD_CTLR) = GICD_CTLR_ENABLE_GRP0 | GICD_CTLR_ENABLE_GRP1; + *gicc(GICC_PMR) = 0xFFu; + *gicc(GICC_CTLR) = GICC_CTLR_ENABLE_GRP0 | GICC_CTLR_ENABLE_GRP1 | + GICC_CTLR_FIQ_EN; +} + +/* Make an interrupt pending in software (SPIs, id >= 32) so a test driver can + * raise a Secure interrupt without external hardware. */ +static void gicv2_set_pending(uint32_t intid) +{ + if (intid < WT_GIC_INTID_LIMIT) { + *gicd(GICD_ISPENDR + (intid / 32u) * 4u) = 1u << (intid % 32u); + } +} + +/* Raise an SGI for the Normal world on this core: init_secure left every id + * Group 1, and NSATT in GICD_SGIR forwards it in that group. */ +static void gicv2_raise_ns_sgi(uint32_t intid) +{ + *gicd(GICD_SGIR) = (2u << 24) | (1u << 15) | (intid & 0xFu); +} + +static uint32_t gicv2_swap_pmr(uint32_t pmr) +{ + uint32_t prev = *gicc(GICC_PMR); + + *gicc(GICC_PMR) = pmr; + return prev; +} + +static void gicv2_clear_pending(uint32_t intid) +{ + if (intid < WT_GIC_INTID_LIMIT) { + *gicd(GICD_ICPENDR + (intid / 32u) * 4u) = 1u << (intid % 32u); + } +} + +static uint32_t gicv2_enabled_word(uint32_t word) +{ + return *gicd(GICD_ISENABLER + word * 4u); +} + +static uint32_t gicv2_not_group0_word(uint32_t word) +{ + return *gicd(GICD_IGROUPR + word * 4u); +} + +static const struct wt_gic_ops gicv2_ops = { + gicv2_init_secure, + gicv2_set_group0, + gicv2_enable, + gicv2_disable, + gicv2_set_priority, + gicv2_ack_group0, + gicv2_eoi_group0, + gicv2_set_pending, + gicv2_raise_ns_sgi, + gicv2_swap_pmr, + gicv2_clear_pending, + gicv2_enabled_word, + gicv2_not_group0_word, + line_count, + 2u +}; + +const struct wt_gic_ops* const wt_gic = &gicv2_ops; + +unsigned int wt_gic_rdist_woken(void) +{ + return 1u; +} diff --git a/src/arch/aarch64/gic/gicv3.c b/src/arch/aarch64/gic/gicv3.c new file mode 100644 index 00000000..6e3e3edb --- /dev/null +++ b/src/arch/aarch64/gic/gicv3.c @@ -0,0 +1,325 @@ +/* gicv3.c + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfTrust. + * + * wolfTrust 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. + * + * wolfTrust 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, see . + */ + +/* GICv3 (GIC-500) secure-side driver: memory-mapped distributor and the boot + * core's redistributor, system-register CPU interface; Group 0 signals FIQ. */ + +#include "memory_map.h" +#include "wolftrust/arch/aarch64/gic.h" +#include "wolftrust/arch/aarch64/sysreg.h" + +#define GICD_CTLR 0x0000u +#define GICD_TYPER 0x0004u +#define GICD_IGROUPR 0x0080u +#define GICD_ISENABLER 0x0100u +#define GICD_ISPENDR 0x0200u +#define GICD_ICENABLER 0x0180u +#define GICD_ICPENDR 0x0280u +#define GICD_IPRIORITYR 0x0400u +#define GICD_IGRPMODR 0x0D00u +#define GICD_IROUTER 0x6000u +/* Aff3 and Aff2-Aff0 where MPIDR_EL1 has them; Interrupt_Routing_Mode 0. */ +#define GICD_IROUTER_AFF_MASK 0x000000FF00FFFFFFull +#define GICD_CTLR_ENABLE_GRP0 (1u << 0) +#define GICD_CTLR_ENABLE_GRP1NS (1u << 1) +#define GICD_CTLR_ENABLE_GRP1S (1u << 2) +#define GICD_CTLR_ARE_S (1u << 4) +#define GICD_CTLR_ARE_NS (1u << 5) +#define GICD_CTLR_RWP (1u << 31) + +#define GICR_FRAME_SIZE 0x20000u +#define GICR_WAKER 0x0014u +#define GICR_WAKER_PROCESSOR_SLEEP (1u << 1) +#define GICR_WAKER_CHILDREN_ASLEEP (1u << 2) +#define GICR_SGI_BASE 0x10000u +#define GICR_IGROUPR0 (GICR_SGI_BASE + 0x0080u) +#define GICR_ISENABLER0 (GICR_SGI_BASE + 0x0100u) +#define GICR_ICENABLER0 (GICR_SGI_BASE + 0x0180u) +#define GICR_ISPENDR0 (GICR_SGI_BASE + 0x0200u) +#define GICR_ICPENDR0 (GICR_SGI_BASE + 0x0280u) +#define GICR_IPRIORITYR (GICR_SGI_BASE + 0x0400u) +#define GICR_IGRPMODR0 (GICR_SGI_BASE + 0x0D00u) + +#define ICC_SRE_SRE (1u << 0) +#define ICC_SRE_DFB (1u << 1) +#define ICC_SRE_DIB (1u << 2) +#define ICC_SRE_EN (1u << 3) + +#define WAKE_POLL_LIMIT 1000000u + +#if defined(WT_GIC_SPI_ROUTE_PROBE) && (WT_GIC_SPI_ROUTE_PROBE == 1) +/* Test only: the route an earlier stage might leave, to an affinity no PE of + * the QEMU machines has. */ +#define WT_GIC_ROUTE_PROBE_ABSENT_PE 0x0000000000FEFEFEull +#endif + +WT_SYSREG_WRITE(icc_sre_el3, "ICC_SRE_EL3") +WT_SYSREG_WRITE(icc_sre_el2, "ICC_SRE_EL2") +WT_SYSREG_READ(id_aa64pfr0_el1, "ID_AA64PFR0_EL1") +WT_SYSREG_WRITE(icc_sre_el1, "ICC_SRE_EL1") +WT_SYSREG_WRITE(icc_pmr_el1, "ICC_PMR_EL1") +WT_SYSREG_READ(icc_pmr_el1, "ICC_PMR_EL1") +WT_SYSREG_WRITE(icc_igrpen0_el1, "ICC_IGRPEN0_EL1") +WT_SYSREG_WRITE(icc_ctlr_el3, "ICC_CTLR_EL3") +WT_SYSREG_WRITE(icc_eoir0_el1, "ICC_EOIR0_EL1") +WT_SYSREG_READ(icc_iar0_el1, "ICC_IAR0_EL1") + +static unsigned int g_rdist_woken; + +static volatile uint32_t* gicd(uint32_t offset) +{ + return (volatile uint32_t*)(uintptr_t)(WT_GICD_BASE + offset); +} + +/* The boot core owns redistributor frame 0; secondaries park before they + * would need theirs. */ +static volatile uint32_t* gicr(uint32_t offset) +{ + return (volatile uint32_t*)(uintptr_t)(WT_GICR_BASE + offset); +} + +static volatile uint64_t* gicd_irouter(uint32_t intid) +{ + return (volatile uint64_t*)(uintptr_t)(WT_GICD_BASE + GICD_IROUTER + + (uintptr_t)intid * 8u); +} + +static void gicd_wait_rwp(void) +{ + uint32_t spin = WAKE_POLL_LIMIT; + + while (((*gicd(GICD_CTLR) & GICD_CTLR_RWP) != 0u) && (spin > 0u)) { + spin--; + } +} + +static void gicv3_set_group0(uint32_t intid) +{ + if (intid < 32u) { + *gicr(GICR_IGROUPR0) &= ~(1u << intid); + *gicr(GICR_IGRPMODR0) &= ~(1u << intid); + } + else if (intid < WT_GIC_INTID_LIMIT) { + *gicd(GICD_IGROUPR + (intid / 32u) * 4u) &= ~(1u << (intid % 32u)); + *gicd(GICD_IGRPMODR + (intid / 32u) * 4u) &= ~(1u << (intid % 32u)); + } +} + +static void gicv3_enable(uint32_t intid) +{ + if (intid < 32u) { + *gicr(GICR_ISENABLER0) = 1u << intid; + } + else if (intid < WT_GIC_INTID_LIMIT) { + /* Under ARE an SPI reaches only the PE its IROUTER names, whose reset + * value is IMPLEMENTATION DEFINED: name this PE, the boot PE. */ + *gicd_irouter(intid) = wt_read_mpidr_el1() & GICD_IROUTER_AFF_MASK; + *gicd(GICD_ISENABLER + (intid / 32u) * 4u) = 1u << (intid % 32u); + } +} + +static void gicv3_disable(uint32_t intid) +{ + if (intid < 32u) { + *gicr(GICR_ICENABLER0) = 1u << intid; + } + else if (intid < WT_GIC_INTID_LIMIT) { + *gicd(GICD_ICENABLER + (intid / 32u) * 4u) = 1u << (intid % 32u); + /* RWP tracks ICENABLER: the line is off only once it clears. */ + gicd_wait_rwp(); + } +} + +static void gicv3_set_priority(uint32_t intid, uint8_t priority) +{ + if (intid < 32u) { + *(volatile uint8_t*)(uintptr_t)(WT_GICR_BASE + GICR_IPRIORITYR + intid) = + priority; + } + else if (intid < WT_GIC_INTID_LIMIT) { + *(volatile uint8_t*)(uintptr_t)(WT_GICD_BASE + GICD_IPRIORITYR + intid) = + priority; + } +} + +static uint32_t gicv3_ack_group0(void) +{ + uint32_t intid = (uint32_t)wt_read_icc_iar0_el1() & 0xFFFFFFu; + + return intid; +} + +static void gicv3_eoi_group0(uint32_t intid) +{ + wt_write_icc_eoir0_el1(intid); + wt_isb(); +} + +static void wake_redistributor(void) +{ + uint32_t spin = WAKE_POLL_LIMIT; + + *gicr(GICR_WAKER) &= ~GICR_WAKER_PROCESSOR_SLEEP; + while (((*gicr(GICR_WAKER) & GICR_WAKER_CHILDREN_ASLEEP) != 0u) && + (spin > 0u)) { + spin--; + } + g_rdist_woken = (spin > 0u) ? 1u : 0u; +} + +static void gicv3_init_secure(void) +{ + uint32_t lines = ((*gicd(GICD_TYPER) & 0x1Fu) + 1u) * 32u; + uint32_t scr; + uint32_t i; + + wt_write_icc_sre_el3(ICC_SRE_SRE | ICC_SRE_DFB | ICC_SRE_DIB | ICC_SRE_EN); + wt_isb(); + /* An implemented EL2, even unused, gates NS-EL1's ICC_SRE_EL1 and SRE. */ + if (((wt_read_id_aa64pfr0_el1() >> 8) & 0xFu) != 0u) { + wt_write_icc_sre_el2(ICC_SRE_SRE | ICC_SRE_DFB | ICC_SRE_DIB | + ICC_SRE_EN); + wt_isb(); + } + + *gicd(GICD_CTLR) = 0u; + gicd_wait_rwp(); + for (i = 32u; i < lines; i += 32u) { + *gicd(GICD_ICENABLER + (i / 32u) * 4u) = 0xFFFFFFFFu; + *gicd(GICD_IGROUPR + (i / 32u) * 4u) = 0xFFFFFFFFu; + *gicd(GICD_IGRPMODR + (i / 32u) * 4u) = 0u; + } + gicd_wait_rwp(); + *gicd(GICD_CTLR) = GICD_CTLR_ARE_S | GICD_CTLR_ARE_NS; + gicd_wait_rwp(); + *gicd(GICD_CTLR) = GICD_CTLR_ARE_S | GICD_CTLR_ARE_NS | + GICD_CTLR_ENABLE_GRP0 | GICD_CTLR_ENABLE_GRP1NS | + GICD_CTLR_ENABLE_GRP1S; + gicd_wait_rwp(); +#if defined(WT_GIC_SPI_ROUTE_PROBE) && (WT_GIC_SPI_ROUTE_PROBE == 1) + for (i = 32u; (i < lines) && (i < WT_GIC_INTID_LIMIT); i++) { + *gicd_irouter(i) = WT_GIC_ROUTE_PROBE_ABSENT_PE; + } +#endif + + wake_redistributor(); + *gicr(GICR_ICENABLER0) = 0xFFFFFFFFu; + *gicr(GICR_IGROUPR0) = 0xFFFFFFFFu; + *gicr(GICR_IGRPMODR0) = 0u; + gicv3_set_group0(WT_GIC_INTID_SECURE_TIMER); + gicv3_set_priority(WT_GIC_INTID_SECURE_TIMER, 0x00u); + + /* ICC_SRE_EL1 is banked by Security state: write it for both worlds. */ + wt_write_icc_sre_el1(ICC_SRE_SRE | ICC_SRE_DFB | ICC_SRE_DIB); + scr = (uint32_t)wt_read_scr_el3(); + wt_write_scr_el3(scr | WT_SCR_NS); + wt_isb(); + wt_write_icc_sre_el1(ICC_SRE_SRE | ICC_SRE_DFB | ICC_SRE_DIB); + wt_isb(); + wt_write_scr_el3(scr); + wt_isb(); + + wt_write_icc_ctlr_el3(0u); + wt_write_icc_pmr_el1(0xFFu); + wt_write_icc_igrpen0_el1(1u); + wt_isb(); +} + +/* Make an interrupt pending in software (SPIs, id >= 32) so a test driver can + * raise a Secure interrupt without external hardware. */ +static void gicv3_set_pending(uint32_t intid) +{ + if (intid < WT_GIC_INTID_LIMIT) { + *gicd(GICD_ISPENDR + (intid / 32u) * 4u) = 1u << (intid % 32u); + } +} + +/* Raise an SGI for the Normal world on this core: init_secure left the + * redistributor's ids NS Group 1, so pending it delivers it there. */ +static void gicv3_raise_ns_sgi(uint32_t intid) +{ + *gicr(GICR_ISPENDR0) = 1u << (intid & 0xFu); +} + +static uint32_t gicv3_swap_pmr(uint32_t pmr) +{ + uint32_t prev = (uint32_t)wt_read_icc_pmr_el1(); + + wt_write_icc_pmr_el1(pmr); + wt_isb(); + return prev; +} + +static void gicv3_clear_pending(uint32_t intid) +{ + if (intid < 32u) { + *gicr(GICR_ICPENDR0) = 1u << intid; + } + else if (intid < WT_GIC_INTID_LIMIT) { + *gicd(GICD_ICPENDR + (intid / 32u) * 4u) = 1u << (intid % 32u); + } +} + +static uint32_t gicv3_enabled_word(uint32_t word) +{ + if (word == 0u) { + return *gicr(GICR_ISENABLER0); + } + return *gicd(GICD_ISENABLER + word * 4u); +} + +/* Group 0 is IGROUPR 0 with IGRPMODR 0; any other pair is Group 1. */ +static uint32_t gicv3_not_group0_word(uint32_t word) +{ + if (word == 0u) { + return *gicr(GICR_IGROUPR0) | *gicr(GICR_IGRPMODR0); + } + return *gicd(GICD_IGROUPR + word * 4u) | *gicd(GICD_IGRPMODR + word * 4u); +} + +static uint32_t gicv3_line_count(void) +{ + return ((*gicd(GICD_TYPER) & 0x1Fu) + 1u) * 32u; +} + +static const struct wt_gic_ops gicv3_ops = { + gicv3_init_secure, + gicv3_set_group0, + gicv3_enable, + gicv3_disable, + gicv3_set_priority, + gicv3_ack_group0, + gicv3_eoi_group0, + gicv3_set_pending, + gicv3_raise_ns_sgi, + gicv3_swap_pmr, + gicv3_clear_pending, + gicv3_enabled_word, + gicv3_not_group0_word, + gicv3_line_count, + 3u +}; + +const struct wt_gic_ops* const wt_gic = &gicv3_ops; + +unsigned int wt_gic_rdist_woken(void) +{ + return g_rdist_woken; +} diff --git a/src/arch/aarch64/spm/coroutine_aarch64.c b/src/arch/aarch64/spm/coroutine_aarch64.c new file mode 100644 index 00000000..b8b28d26 --- /dev/null +++ b/src/arch/aarch64/spm/coroutine_aarch64.c @@ -0,0 +1,1436 @@ +/* coroutine_aarch64.c + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfTrust. + * + * wolfTrust 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. + * + * wolfTrust 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, see . + */ + +/* Coroutine architecture hooks at Secure EL1. Privileged tasklets run at + * S-EL1 through a synchronous save/restore switch. Unprivileged Secure + * Partitions run at S-EL0: entering one ERETs into its saved register + * state under its own translation table, and it comes back when its SVC + * or fault handler unwinds to the bootstrap through wt_sp_el0_leave. */ + +#include "wolftrust/sched/coroutine_internal.h" +#include "wolftrust/arch/aarch64/el3.h" +#include "wolftrust/arch/aarch64/domain.h" +#include "wolftrust/arch/aarch64/ffa_abi.h" +#include "wolftrust/arch/aarch64/ffa_msg.h" +#include "wolftrust/arch/aarch64/ffa_notif.h" +#include "wolftrust/arch/aarch64/ffa_runtime.h" +#include "wolftrust/arch/aarch64/gic.h" +#include "wolftrust/arch/aarch64/spm_mem.h" +#include "wolftrust/arch/aarch64/spm_svc.h" +#include "wolftrust/ffm_domain.h" +#include "wolftrust/arch.h" +#include "wolftrust/platform.h" +#include "wolftrust/services/hsm.h" +#include "wolftrust/spm_transport.h" + +#include +#include +#include + +/* One stage-1 table per coroutine domain, so the table cap must cover every + * coroutine (the boot self-tests keep theirs too). */ +#if WT_DOMAIN_MAX_TABLES < (WT_CO_MAX + WT_DOMAIN_PROOF_TABLES + 1u) +#error "WT_DOMAIN_MAX_TABLES must cover WT_CO_MAX partitions, the boot proofs, and a borrower rebuild" +#endif + +/* FF-A ids: SPMC 0x8000, SPMD 0x8001, partitions follow in creation order. */ +#define WT_SP_FFA_ID_BASE 0x8001u + +volatile uint32_t g_wt_spm_partitions_live; +static uint32_t g_sp_init_count; + +/* The core clears this on a partition fault; the AArch64 switch is + * synchronous and never consults it, but the symbol must resolve. */ +struct wt_co* g_wt_co_pendsv_target; + +uint32_t wt_spm_sp_init_count(void) +{ + return g_sp_init_count; +} + +void wt_co_arch_switch(uintptr_t* save_from_sp, uintptr_t to_sp); +void wt_co_trampoline(void); + +/* Frame the first switch-in restores: x19..x30 at 8-byte slots 0..11. */ +#define WT_CO_FRAME_WORDS 12u +#define WT_CO_SLOT_X19 0u +#define WT_CO_SLOT_X20 1u +#define WT_CO_SLOT_X30 11u +/* EL0t with A masked and IRQ and FIQ open, so the scheduling tick (Group 0) + * preempts a running partition into the SPMC and a pending Normal-world + * Group 1 interrupt preempts it for the Normal world to take (Ch.9). */ +#define WT_SP_SPSR_EL0T 0x100u +/* A partition whose manifest queues Non-secure interrupts runs with the GIC + * priority mask at the top of the Non-secure range, so a Normal-world + * interrupt stays pending until it returns there instead of preempting it + * (ns-interrupts-action = queued); Secure priorities stay below the mask. */ +static uint8_t g_ns_queued[WT_CO_MAX]; +/* Non-secure interrupts stay queued for a run the SPMC scheduled for a Secure + * interrupt (9.2.4 rule 3) and for a callee whose caller queues them (9.3.1.4). */ +static uint8_t g_ns_inherited[WT_CO_MAX]; + +static wt_sp_arch_t* sp_arch(const struct wt_co *co); +static int endpoint_waiting(const struct wt_co* co); +static struct wt_co* sint_take_waiting_owner(void); +static void sp_release(struct wt_co* co, int32_t code); +static int run_endpoint_from(struct wt_co* co, struct wt_co* callee, + uint64_t* out, uint8_t spmc); +/* The endpoint a blocking partition's direct request or FFA_RUN names. */ +static struct wt_co* g_ffa_call_target; +/* Set when a Secure interrupt is queued for a waiting partition while another + * one runs; the run loop stops the runner so the owner is signaled first. */ +static volatile uint32_t g_sint_signal_request; + +#define WT_SP_INIT_MAX_PASSES 16u + +static wt_sp_arch_t g_sp_arch[WT_CO_MAX]; +/* 8.5 progress: initializing, initialized (FFA_MSG_WAIT), or failed + * (FFA_ERROR), after which the partition waits and is never run again. */ +#define WT_SP_INIT_PENDING 0u +#define WT_SP_INIT_DONE 1u +#define WT_SP_INIT_FAILED 2u +static uint8_t g_init_seen[WT_CO_MAX]; +/* Set while a partition is inside the FF-M gate, where blocking (its psa_wait) + * is how an FF-M partition, which makes no FF-A calls, completes its init. */ +static uint8_t g_init_gate[WT_CO_MAX]; +static uint8_t g_faulted_once[WT_CO_MAX]; +static uint8_t g_retired[WT_CO_MAX]; +static struct wt_co* g_created[WT_CO_MAX]; +static uint32_t g_partitions_initialized; + +#if defined(WT_SPM_ECHO_SP) +/* Proof that the init pass resumes a partition preempted before its first + * wait: a tick already due is taken at the echo partition's first instruction. */ +static uint8_t g_init_preempt_armed; + +static void init_preempt_probe(const struct wt_co* co) +{ + if ((g_init_preempt_armed == 0u) && (co == wt_spm_ffa_echo_partition())) { + g_init_preempt_armed = 1u; + wt_gic->enable(WT_GIC_INTID_SECURE_TIMER); + wt_el3_timer_arm_ms(0u); + } +} +#else +static void init_preempt_probe(const struct wt_co* co) +{ + (void)co; +} +#endif + +/* Run one partition from its entry (or its recovery re-arm) until it blocks + * or faults, once; returns non-zero if it ran. A partition that faulted on + * an earlier pass and has been re-armed since prints its restart marker; one + * preempted before its first wait resumes where it was interrupted. */ +static int run_pending_partition(unsigned int i) +{ + uint64_t out[WT_FFA_MSG_REGS_EXT]; + struct wt_co* co = g_created[i]; + wt_co_state_t state; + + if (co == NULL || co->unprivileged == 0u || + g_init_seen[i] != WT_SP_INIT_PENDING) { + return 0; + } + state = wt_co_state((wt_co_t*)co); + if (state == WT_CO_RUNNABLE) { + wt_el3_puts("[SP] init resumed id=0x"); + wt_el3_puthex((uint64_t)wt_spm_sp_ffa_id(co), 4u); + wt_el3_puts("\r\n"); + } + else if (state != WT_CO_BLOCKED) { + return 0; /* FAULTED (restart budget spent) or otherwise not runnable */ + } + else { + if (g_faulted_once[i] != 0u) { + g_faulted_once[i] = 0u; + wt_el3_puts("[SP] restarted id=0x"); + wt_el3_puthex((uint64_t)wt_spm_sp_ffa_id(co), 4u); + wt_el3_puts("\r\n"); + } + init_preempt_probe(co); + wt_co_wake((wt_co_t*)co); + } + g_wt_ffa_sp_exit = WT_FFA_SP_EXIT_NONE; + (void)wt_co_run((wt_co_t*)co); + if (g_wt_ffa_sp_exit == WT_FFA_SP_EXIT_CALL) { + /* 8.5 rule 1: its callee runs now, and it resumes still initializing + * with the reply until it waits. */ + (void)run_endpoint_from(co, g_ffa_call_target, out, 1u); + } + g_wt_ffa_sp_exit = WT_FFA_SP_EXIT_NONE; + if (wt_co_state((wt_co_t*)co) == WT_CO_FAULTED) { + g_faulted_once[i] = 1u; + } + return 1; +} + +/* FF-A init model (5.3, 8.5): before the SPMC waits for events, every + * partition runs from its entry until it signals successful initialization + * with FFA_MSG_WAIT (an FF-M partition by blocking in the FF-M gate). A + * partition that faults during init is routed through the core's restart + * policy (wt_spm_recover_faulted re-arms it) and re-run; one that faults every + * time exhausts its budget and fails closed. */ +#if defined(WT_SPM_ECHO_SP) +/* The FF-A native echo partition for the direct-message proofs: created in the + * init pass, after the core has created the manifest partitions, so it + * initializes (parks in FFA_MSG_WAIT) exactly like they do. It executes the + * shared code every partition maps and owns the band enable_mmu published. */ +static struct wt_co* g_echo_co; +static wt_secure_domain_t g_echo_domain; + +static void create_echo_partition(void) +{ + size_t n; + wt_co_t* co; + + if ((g_echo_co != NULL) || (g_wt_spm_echo_stack_size == 0u)) { + return; + } + n = wt_platform_sp_shared_regions(g_echo_domain.regions, 2u); + if (n != 2u) { + return; + } + g_echo_domain.regions[n].base = g_wt_spm_echo_stack_base; + g_echo_domain.regions[n].size = (size_t)g_wt_spm_echo_stack_size; + g_echo_domain.regions[n].attributes = WT_MEM_ATTR_READ | WT_MEM_ATTR_WRITE; + g_echo_domain.region_count = n + 1u; + co = wt_co_create_blocked_ex((uint8_t*)(uintptr_t)g_wt_spm_echo_stack_base, + (size_t)g_wt_spm_echo_stack_size, + (wt_co_entry_fn)wt_sp_ffa_echo, (void*)0); + if (co == NULL) { + return; + } + wt_co_set_domain(co, &g_echo_domain, 1u); + g_echo_co = (struct wt_co*)co; +} + +struct wt_co* wt_spm_ffa_echo_partition(void) +{ + return g_echo_co; +} +#else +static void create_echo_partition(void) +{ +} + +struct wt_co* wt_spm_ffa_echo_partition(void) +{ + return NULL; +} +#endif + +#if defined(WT_FFA_ACS) && (WT_FFA_ACS == 1) +static struct wt_co* g_native_co[WT_FFA_NATIVE_SP_MAX]; +static wt_secure_domain_t g_native_domain[WT_FFA_NATIVE_SP_MAX]; +static const wt_ffa_native_sp_t* g_native_list; +static size_t g_native_count; + +/* Created in the init pass after the manifest partitions, so each native + * partition initializes (runs to its first FFA_MSG_WAIT) exactly like them. */ +static void create_native_partitions(void) +{ + const wt_ffa_native_sp_t* list; + size_t count = 0u; + size_t i; + size_t j; + wt_co_t* co; + + if (g_native_list != NULL) { + return; + } + list = wt_platform_ffa_native_partitions(&count); + if ((list == NULL) || (count > WT_FFA_NATIVE_SP_MAX)) { + return; + } + for (i = 0u; i < count; i++) { + if (list[i].region_count > WT_FFA_NATIVE_SP_REGIONS) { + wt_platform_panic(); + } + for (j = 0u; j < list[i].region_count; j++) { + g_native_domain[i].regions[j] = list[i].regions[j]; + } + g_native_domain[i].region_count = list[i].region_count; + g_native_domain[i].stack_base = list[i].stack_base; + g_native_domain[i].stack_size = list[i].stack_size; + co = wt_co_create_blocked_ex((uint8_t*)list[i].stack_base, + list[i].stack_size, + (wt_co_entry_fn)list[i].entry, (void*)0); + if (co == NULL) { + wt_platform_panic(); + } + wt_co_set_domain(co, &g_native_domain[i], 1u); + g_ns_queued[((struct wt_co*)co)->id - 1u] = + (list[i].ns_int_action == 0u) ? 1u : 0u; + g_native_co[i] = (struct wt_co*)co; + if (wt_spm_mem_bind(wt_spm_sp_ffa_id((struct wt_co*)co), + (struct wt_co*)co, &g_native_domain[i]) != 0) { + wt_platform_panic(); + } + } + g_native_list = list; + g_native_count = count; +} + +const wt_ffa_native_sp_t* wt_spm_ffa_native_list(size_t* count) +{ + *count = g_native_count; + return g_native_list; +} + +uint16_t wt_spm_ffa_native_id(size_t index) +{ + return (index < g_native_count) ? wt_spm_sp_ffa_id(g_native_co[index]) : 0u; +} + +struct wt_co* wt_spm_ffa_native_by_id(uint16_t id) +{ + size_t i; + + for (i = 0u; i < g_native_count; i++) { + if ((id != 0u) && (wt_spm_sp_ffa_id(g_native_co[i]) == id)) { + return g_native_co[i]; + } + } + return NULL; +} + +static const wt_ffa_native_sp_t* native_of(const struct wt_co* co) +{ + size_t i; + + for (i = 0u; i < g_native_count; i++) { + if (g_native_co[i] == co) { + return &g_native_list[i]; + } + } + return NULL; +} + +int wt_spm_sint_declared_any(uint32_t intid) +{ + size_t i; + + for (i = 0u; i < g_native_count; i++) { + if (wt_spm_native_declares(&g_native_list[i], intid) != 0) { + return 1; + } + } + return 0; +} +#else +static void create_native_partitions(void) +{ +} + +const wt_ffa_native_sp_t* wt_spm_ffa_native_list(size_t* count) +{ + *count = 0u; + return NULL; +} + +uint16_t wt_spm_ffa_native_id(size_t index) +{ + (void)index; + return 0u; +} + +struct wt_co* wt_spm_ffa_native_by_id(uint16_t id) +{ + (void)id; + return NULL; +} + +static const wt_ffa_native_sp_t* native_of(const struct wt_co* co) +{ + (void)co; + return NULL; +} + +int wt_spm_sint_declared_any(uint32_t intid) +{ + (void)intid; + return 0; +} +#endif + +void wt_spm_init_partitions(void) +{ + size_t native_count = 0u; + size_t n; + unsigned int i; + unsigned int pass; + int progressed; + + if (g_partitions_initialized != 0u || g_wt_spm_partitions_live == 0u) { + return; + } + g_partitions_initialized = 1u; +#if defined(WT_ENGINE_HSM) + /* Seat the Normal-world guest's wolfHSM relay server. This port loads the + * guest externally rather than as a port-managed partition, so it reports + * zero guests to the monitor and the core boot loop never seats it; the + * single externally-loaded guest still reaches SERVICE_HSM as guest 0. */ + if (wt_hsm_guest_init_relay((wt_guest_id_t)0) != 0) { + wt_platform_panic(); + } +#if defined(WT_ATTEST_COSE) && (WT_ATTEST_COSE == 1) + /* The core's boot-time attestation bootstrap found no seated guest; run + * it now. A failure leaves attestation failing closed, as at boot. */ + (void)wt_hsm_attest_bootstrap(); +#endif +#endif + create_echo_partition(); + create_native_partitions(); + /* Seed the notification endpoint table: the Normal-world scheduler and + * every native partition; a table past its bound simply lacks + * notifications. */ + wt_ffa_notif_reset(); + (void)wt_ffa_notif_register(WT_FFA_ID_NS_PRIMARY, 0); + (void)wt_spm_ffa_native_list(&native_count); + for (n = 0u; n < native_count; n++) { + (void)wt_ffa_notif_register(wt_spm_ffa_native_id(n), 1); + } + for (i = 0u; i < WT_CO_MAX; i++) { + (void)run_pending_partition(i); + } + for (pass = 0u; pass < WT_SP_INIT_MAX_PASSES; pass++) { + wt_spm_recover_faulted(); + progressed = 0; + for (i = 0u; i < WT_CO_MAX; i++) { + if (run_pending_partition(i) != 0) { + progressed = 1; + } + } + if (progressed == 0) { + break; + } + } + while (wt_co_tick(8u) != 0u) { + } +} + +/* A partition's FF-A endpoint id follows its creation order among the + * partitions (0x8002 up); a privileged tasklet is no endpoint. */ +uint16_t wt_spm_sp_ffa_id(const struct wt_co* co) +{ + uint32_t ordinal = 1u; + uint32_t i; + + if ((co == NULL) || (co->id == 0u) || (co->id > WT_CO_MAX) || + (co->unprivileged == 0u)) { + return 0u; + } + for (i = 0u; i + 1u < co->id; i++) { + if ((g_created[i] != NULL) && (g_created[i]->unprivileged != 0u)) { + ordinal++; + } + } + return (uint16_t)(WT_SP_FFA_ID_BASE + ordinal); +} + +/* The id a requester names an endpoint by: the echo partition's is fixed. */ +static uint16_t endpoint_id(const struct wt_co* co) +{ + if ((co != NULL) && (co == wt_spm_ffa_echo_partition())) { + return (uint16_t)WT_FFA_ID_ECHO; + } + return wt_spm_sp_ffa_id(co); +} + +struct wt_co* wt_spm_ffa_endpoint_by_id(uint16_t id) +{ + if ((id == (uint16_t)WT_FFA_ID_ECHO) && + (wt_spm_ffa_echo_partition() != NULL)) { + return wt_spm_ffa_echo_partition(); + } + return wt_spm_ffa_native_by_id(id); +} + +struct wt_co* wt_spm_sp_by_ffa_id(uint16_t id) +{ + uint32_t i; + + if ((id <= WT_SP_FFA_ID_BASE) || (id > (WT_SP_FFA_ID_BASE + WT_CO_MAX))) { + return NULL; + } + for (i = 0u; i < WT_CO_MAX; i++) { + if ((g_created[i] != NULL) && (wt_spm_sp_ffa_id(g_created[i]) == id)) { + return g_created[i]; + } + } + return NULL; +} + +/* The live endpoint id of the partition confined to a manifest domain, or 0 + * when no created partition runs in it. */ +uint16_t wt_spm_sp_ffa_id_of_domain(uint32_t domain_id) +{ + const struct wt_co* co; + unsigned int i; + + for (i = 0u; i < WT_CO_MAX; i++) { + co = g_created[i]; + if ((co != NULL) && (co->id != 0u) && (co->unprivileged != 0u) && + (co->domain != NULL) && + ((uint32_t)co->domain->domain_id == domain_id)) { + return wt_spm_sp_ffa_id(co); + } + } + return 0u; +} + +/* Non-zero until the partition signals successful initialization. */ +int wt_spm_sp_initializing(const struct wt_co* co) +{ + if ((co == NULL) || (co->id == 0u) || (co->id > WT_CO_MAX)) { + return 0; + } + return (g_init_seen[co->id - 1u] == WT_SP_INIT_PENDING) ? 1 : 0; +} + +int wt_spm_sp_failed_init(const struct wt_co* co) +{ + if ((co == NULL) || (co->id == 0u) || (co->id > WT_CO_MAX)) { + return 0; + } + return (g_init_seen[co->id - 1u] == WT_SP_INIT_FAILED) ? 1 : 0; +} + +void wt_spm_sp_init_complete(const struct wt_co* co) +{ + if ((co == NULL) || (co->id == 0u) || (co->id > WT_CO_MAX) || + (g_init_seen[co->id - 1u] != WT_SP_INIT_PENDING)) { + return; + } + g_init_seen[co->id - 1u] = WT_SP_INIT_DONE; + if (g_wt_spm_partitions_live != 0u) { + g_sp_init_count++; + wt_el3_puts("[SP] init id=0x"); + wt_el3_puthex((uint64_t)wt_spm_sp_ffa_id(co), 4u); + wt_el3_puts("\r\n"); + } +} + +void wt_spm_sp_init_failed(struct wt_co* co, int32_t code) +{ + if ((co == NULL) || (co->id == 0u) || (co->id > WT_CO_MAX) || + (g_init_seen[co->id - 1u] != WT_SP_INIT_PENDING)) { + return; + } + g_init_seen[co->id - 1u] = WT_SP_INIT_FAILED; + sp_release(co, WT_FFA_DENIED); + if (g_wt_spm_partitions_live != 0u) { + wt_el3_puts("[SP] init failed id=0x"); + wt_el3_puthex((uint64_t)wt_spm_sp_ffa_id(co), 4u); + wt_el3_puts(" err=-"); + wt_el3_putdec((uint64_t)(0 - (int64_t)code)); + wt_el3_puts("\r\n"); + } +} + +void wt_spm_sp_in_gate(const struct wt_co* co, unsigned int inside) +{ + if ((co != NULL) && (co->id != 0u) && (co->id <= WT_CO_MAX)) { + g_init_gate[co->id - 1u] = (inside != 0u) ? 1u : 0u; + } +} + +static wt_sp_arch_t* sp_arch(const struct wt_co *co) +{ + if (co->id == 0u || co->id > WT_CO_MAX) { + wt_platform_panic(); + } + return &g_sp_arch[co->id - 1u]; +} + +/* FF-A runtime state of one S-EL0 endpoint (Ch.8): busy while it processes a + * direct request, yielded after FFA_YIELD until FFA_RUN resumes it. */ +typedef struct wt_sp_msg { + uint16_t requester; + uint16_t self; + uint8_t busy; + uint8_t yielded; + uint8_t calling; + uint8_t req2; + /* The requester left service while this endpoint was yielded to it: + * anyone may run it, and its response has no receiver. */ + uint8_t orphaned; +} wt_sp_msg_t; + +static wt_sp_msg_t g_sp_msg[WT_CO_MAX]; +volatile uint32_t g_wt_ffa_sp_exit; + +int wt_spm_ffa_sp_requester(const struct wt_co* co, uint16_t* requester, + uint16_t* self) +{ + const wt_sp_msg_t* m; + + if ((co == NULL) || (co->id == 0u) || (co->id > WT_CO_MAX)) { + return 0; + } + m = &g_sp_msg[co->id - 1u]; + if (m->busy == 0u) { + return 0; + } + *requester = m->requester; + *self = m->self; + return 1; +} + +int wt_spm_ffa_sp_req2(const struct wt_co* co) +{ + if ((co == NULL) || (co->id == 0u) || (co->id > WT_CO_MAX)) { + return 0; + } + return (int)g_sp_msg[co->id - 1u].req2; +} + +int wt_spm_ffa_sp_yielded_to(const struct wt_co* co, uint16_t caller) +{ + const wt_sp_msg_t* m; + + if ((co == NULL) || (co->id == 0u) || (co->id > WT_CO_MAX)) { + return 0; + } + m = &g_sp_msg[co->id - 1u]; + return ((m->yielded != 0u) && (m->busy != 0u) && + (m->requester == caller)) ? 1 : 0; +} + +/* Hand a blocked partition an 8-register event (fid, w1) as the return of the + * call it is blocked in. */ +static void deliver_event(struct wt_co* co, uint32_t fid, uint64_t w1) +{ + uint64_t msg[WT_FFA_MSG_REGS]; + unsigned int i; + + for (i = 0u; i < WT_FFA_MSG_REGS; i++) { + msg[i] = 0u; + } + msg[0] = fid; + msg[1] = w1; + wt_ffa_msg_deliver(sp_arch(co)->frame.x, msg); +} + +/* Stage a queued Secure interrupt for delivery: its FFA_INTERRUPT becomes the + * return of the call the partition is blocked in, with w1/w2 zero as an S-EL0 + * partition reads the id with the get call (12.4.1 item 3). Returns 1 if one + * was. */ +static unsigned int sint_stage(struct wt_co* co) +{ + if (wt_spm_sint_take_pending(co) == 0u) { + return 0u; + } + deliver_event(co, WT_FFA_INTERRUPT, 0u); + return 1u; +} + +/* Run one endpoint until it hands the CPU back, and turn how it did so into + * the registers its invoker sees: its direct response, FFA_YIELD, or + * FFA_MSG_WAIT. WT_FFA_SP_EXIT_CALL and WT_FFA_SP_EXIT_SIGNAL are returned + * as-is for the chain below; *deliver is set when a Secure interrupt queued + * while it ran is staged for it after its response (Table 9.1). */ +static int run_one(struct wt_co* co, uint64_t* out, uint32_t* reason, + unsigned int* deliver) +{ + wt_sp_msg_t* m = &g_sp_msg[co->id - 1u]; + unsigned int i; + + *deliver = 0u; + g_wt_ffa_direct_resp_ready = 0u; + g_wt_ffa_sp_exit = WT_FFA_SP_EXIT_NONE; + wt_co_wake((wt_co_t*)co); + (void)wt_co_run((wt_co_t*)co); + while ((wt_co_state((wt_co_t*)co) == WT_CO_RUNNABLE) && + (g_wt_ffa_sp_exit == WT_FFA_SP_EXIT_NONE) && + (g_sint_signal_request == 0u)) { + (void)wt_co_run((wt_co_t*)co); + } + if ((wt_co_state((wt_co_t*)co) == WT_CO_RUNNABLE) && + (g_wt_ffa_sp_exit == WT_FFA_SP_EXIT_NONE)) { + g_wt_ffa_sp_exit = WT_FFA_SP_EXIT_SIGNAL; + } + *reason = g_wt_ffa_sp_exit; + g_wt_ffa_sp_exit = WT_FFA_SP_EXIT_NONE; + for (i = 0u; i < WT_FFA_MSG_REGS_EXT; i++) { + out[i] = 0u; + } + if (wt_co_state((wt_co_t*)co) == WT_CO_FAULTED) { + (void)memset(m, 0, sizeof(*m)); + *reason = WT_FFA_SP_EXIT_NONE; + return WT_FFA_ABORTED; + } + if ((*reason == WT_FFA_SP_EXIT_CALL) || (*reason == WT_FFA_SP_EXIT_SIGNAL)) { + return 0; + } + if ((*reason == WT_FFA_SP_EXIT_RESP) && (g_wt_ffa_direct_resp_ready != 0u)) { + for (i = 0u; i < WT_FFA_MSG_REGS_EXT; i++) { + out[i] = g_wt_ffa_direct_resp[i]; + } + wt_ffa_regs_normalize(out); + wt_ffa_direct_clear_sbz(out); + if (m->orphaned != 0u) { + /* No requester is left to take the response: the endpoint is + * waiting again, which is what whoever ran it is told. */ + for (i = 0u; i < WT_FFA_MSG_REGS_EXT; i++) { + out[i] = 0u; + } + out[0] = WT_FFA_MSG_WAIT; + out[1] = (uint64_t)endpoint_id(co) << 16; + m->orphaned = 0u; + } + m->busy = 0u; + *deliver = sint_stage(co); + return 0; + } + if (*reason == WT_FFA_SP_EXIT_NSINT) { + out[0] = WT_FFA_INTERRUPT; + out[1] = (uint64_t)endpoint_id(co) << 16; + return 0; + } + if (*reason == WT_FFA_SP_EXIT_YIELD) { + m->yielded = 1u; + out[0] = WT_FFA_YIELD; + out[1] = (uint64_t)endpoint_id(co) << 16; + return 0; + } + if (*reason == WT_FFA_SP_EXIT_WAIT) { + out[0] = WT_FFA_MSG_WAIT; + return 0; + } + m->busy = 0u; + return WT_FFA_DENIED; +} + +/* The core scheduler does not nest, so an endpoint that messages another one + * blocks and names its callee; this loop, on the scheduler's stack, runs the + * callee and writes what it hands back into the caller's saved frame. A + * partition staged a Secure interrupt after its response, or a waiting owner + * an interrupt is signaled to, runs detached: stacked on top with its own + * result discarded, before the frame below resumes (Table 9.1). A detached + * run, and a root run with spmc set, is in the SPMC scheduled mode. */ +static int run_endpoint_from(struct wt_co* co, struct wt_co* callee, + uint64_t* out, uint8_t spmc) +{ + struct wt_co* chain[WT_CO_MAX]; + uint8_t detached[WT_CO_MAX]; + uint64_t root_out[WT_FFA_MSG_REGS_EXT]; + struct wt_co* top; + struct wt_co* caller; + struct wt_co* waiting; + unsigned int depth = 1u; + unsigned int deliver = 0u; + unsigned int i; + uint32_t reason = WT_FFA_SP_EXIT_NONE; + int root_ret = 0; + int ret; + + chain[0] = co; + detached[0] = 0u; + g_ns_inherited[co->id - 1u] = spmc; + if (callee != NULL) { + /* co already blocked calling callee, which holds the request. */ + chain[1] = callee; + detached[1] = 0u; + g_ns_inherited[callee->id - 1u] = + ((g_ns_queued[co->id - 1u] != 0u) || (spmc != 0u)) ? 1u : 0u; + depth = 2u; + } + for (;;) { + top = chain[depth - 1u]; + ret = run_one(top, out, &reason, &deliver); + if ((ret == 0) && (reason == WT_FFA_SP_EXIT_CALL)) { + if ((depth == WT_CO_MAX) || (g_ffa_call_target == NULL)) { + wt_platform_panic(); + } + chain[depth] = g_ffa_call_target; + detached[depth] = 0u; + g_ns_inherited[g_ffa_call_target->id - 1u] = + ((g_ns_queued[top->id - 1u] != 0u) || + (g_ns_inherited[top->id - 1u] != 0u)) ? 1u : 0u; + depth++; + continue; + } + if ((ret == 0) && (reason == WT_FFA_SP_EXIT_SIGNAL)) { + waiting = sint_take_waiting_owner(); + if (waiting != NULL) { + if (depth == WT_CO_MAX) { + wt_platform_panic(); + } + chain[depth] = waiting; + detached[depth] = 1u; + g_ns_inherited[waiting->id - 1u] = 1u; + depth++; + } + continue; + } + if (detached[depth - 1u] != 0u) { + depth--; + g_ns_inherited[top->id - 1u] = 0u; + if (depth == 0u) { + for (i = 0u; i < WT_FFA_MSG_REGS_EXT; i++) { + out[i] = root_out[i]; + } + return root_ret; + } + continue; + } + if (depth == 1u) { + if (deliver == 0u) { + g_ns_inherited[top->id - 1u] = 0u; + return ret; + } + for (i = 0u; i < WT_FFA_MSG_REGS_EXT; i++) { + root_out[i] = out[i]; + } + root_ret = ret; + detached[0] = 1u; + g_ns_inherited[top->id - 1u] = 1u; + continue; + } + depth--; + g_ns_inherited[top->id - 1u] = 0u; + caller = chain[depth - 1u]; + g_sp_msg[caller->id - 1u].calling = 0u; + if (ret != 0) { + for (i = 0u; i < WT_FFA_MSG_REGS_EXT; i++) { + out[i] = 0u; + } + out[0] = WT_FFA_ERROR; + out[2] = (uint64_t)(uint32_t)ret; + } + wt_ffa_msg_deliver(sp_arch(caller)->frame.x, out); + if (deliver != 0u) { + chain[depth] = top; + detached[depth] = 1u; + g_ns_inherited[top->id - 1u] = 1u; + depth++; + } + } +} + +/* Waiting (4.10) is only reached through a completed initialization. */ +static int run_endpoint(struct wt_co* co, uint64_t* out, uint8_t spmc) +{ + return run_endpoint_from(co, NULL, out, spmc); +} + +static int endpoint_waiting(const struct wt_co* co) +{ + const wt_sp_msg_t* m = &g_sp_msg[co->id - 1u]; + + return ((wt_co_state((wt_co_t*)co) == WT_CO_BLOCKED) && (m->busy == 0u) && + (m->yielded == 0u) && (m->calling == 0u) && + (g_init_seen[co->id - 1u] == WT_SP_INIT_DONE)) ? 1 : 0; +} + +static void endpoint_load_request(struct wt_co* co, const uint64_t* req) +{ + wt_sp_arch_t* a = sp_arch(co); + wt_sp_msg_t* m = &g_sp_msg[co->id - 1u]; + unsigned int count = wt_ffa_msg_reg_count(req[0]); + + wt_ffa_msg_deliver(a->frame.x, req); + wt_ffa_regs_normalize(a->frame.x); + wt_ffa_direct_clear_sbz(a->frame.x); + m->busy = 1u; + m->req2 = (count == WT_FFA_MSG_REGS_EXT) ? 1u : 0u; + m->requester = (uint16_t)(req[1] >> 16); + m->self = (uint16_t)(req[1] & 0xFFFFu); +} + +/* FFA_MSG_SEND_DIRECT_REQ2 names a service by UUID in x2/x3 (15.4): it must be + * Nil or the receiver's own. */ +static int req2_uuid_ok(const struct wt_co* co, const uint64_t* req) +{ + const wt_ffa_native_sp_t* natives; + size_t count = 0u; + size_t i; + unsigned int j; + int match; + + if ((uint32_t)req[0] != WT_FFA_MSG_SEND_DIRECT_REQ2) { + return 1; + } + if ((req[2] == 0u) && (req[3] == 0u)) { + return 1; + } + natives = wt_spm_ffa_native_list(&count); + for (i = 0u; i < count; i++) { + if (wt_spm_ffa_native_by_id(wt_spm_ffa_native_id(i)) != co) { + continue; + } + match = 1; + for (j = 0u; j < 16u; j++) { + if (natives[i].uuid[j] != + (uint8_t)(req[2u + (j / 8u)] >> (8u * (j % 8u)))) { + match = 0; + } + } + return match; + } + return 0; +} + +/* A partition's own direct request (req != NULL) or its FFA_RUN of a callee + * that yielded to it (req == NULL): arm the callee; the gate then blocks the + * caller with WT_FFA_SP_EXIT_CALL and the chain above does the rest. */ +int wt_spm_ffa_sp_call(const struct wt_co* caller, struct wt_co* target, + const uint64_t* req) +{ + wt_sp_msg_t* m; + unsigned int preempted; + + int ret; + + if ((caller == NULL) || (target == NULL) || (target == caller) || + (target->unprivileged == 0u)) { + return WT_FFA_INVALID_PARAMETERS; + } + if (wt_spm_sp_unavailable(target) == WT_FFA_ABORTED) { + return WT_FFA_ABORTED; + } + if (wt_spm_sp_initializing(caller) != 0) { + ret = wt_ffa_rt_init_call((req != NULL) ? (uint32_t)req[0] : WT_FFA_RUN, + (g_init_seen[target->id - 1u] == + WT_SP_INIT_DONE) ? 1 : 0); + if (ret != 0) { + return ret; + } + } + if (req != NULL) { + if (req2_uuid_ok(target, req) == 0) { + return WT_FFA_INVALID_PARAMETERS; + } + if (wt_spm_sp_failed_init(target) != 0) { + return WT_FFA_DENIED; + } + if (endpoint_waiting(target) == 0) { + return WT_FFA_BUSY; + } + endpoint_load_request(target, req); + } + else { + m = &g_sp_msg[target->id - 1u]; + preempted = (wt_co_state((wt_co_t*)target) == WT_CO_RUNNABLE) ? 1u : 0u; + /* Orphaned (its requester left service), any partition may resume it, + * as the Normal world may in wt_spm_ffa_run. */ + if ((m->busy == 0u) || + (wt_ffa_run_busy_check(m->requester, + (m->orphaned != 0u) + ? m->requester + : wt_spm_sp_ffa_id(caller), + m->yielded, preempted) != 0)) { + return WT_FFA_DENIED; + } + m->yielded = 0u; + } + g_sp_msg[caller->id - 1u].calling = 1u; + g_ffa_call_target = target; + return 0; +} + +/* The waiting partition's saved frame holds the registers its FFA_MSG_WAIT + * returns with, so the request is written there and the partition resumed. */ +int wt_spm_ffa_direct_deliver(struct wt_co* co, const uint64_t* req, + uint64_t* resp) +{ + if ((co == NULL) || (req == NULL) || (resp == NULL) || (co->unprivileged == 0u)) { + return WT_FFA_INVALID_PARAMETERS; + } + if (wt_spm_sp_unavailable(co) == WT_FFA_ABORTED) { + return WT_FFA_ABORTED; + } + if (req2_uuid_ok(co, req) == 0) { + return WT_FFA_INVALID_PARAMETERS; + } + if (wt_spm_sp_failed_init(co) != 0) { + return WT_FFA_DENIED; + } + if (endpoint_waiting(co) == 0) { + return WT_FFA_BUSY; + } + endpoint_load_request(co, req); + return run_endpoint(co, resp, 0u); +} + +/* FFA_RUN: resume an endpoint that yielded, or give cycles to a waiting one + * (it sees FFA_RUN as the return of its FFA_MSG_WAIT). */ +int wt_spm_ffa_run(struct wt_co* co, uint16_t caller, uint64_t* out) +{ + wt_sp_msg_t* m; + + if ((co == NULL) || (out == NULL) || (co->unprivileged == 0u)) { + return WT_FFA_INVALID_PARAMETERS; + } + if (wt_spm_sp_unavailable(co) == WT_FFA_ABORTED) { + return WT_FFA_ABORTED; + } + if (wt_spm_sp_failed_init(co) != 0) { + return WT_FFA_DENIED; + } + m = &g_sp_msg[co->id - 1u]; + if (wt_co_state((wt_co_t*)co) != WT_CO_BLOCKED) { + /* A partition an NS interrupt preempted mid-request resumes at the + * interrupted instruction and finishes its response, for its + * requester only. */ + if ((wt_co_state((wt_co_t*)co) == WT_CO_RUNNABLE) && + (m->busy != 0u)) { + if ((m->orphaned == 0u) && + (wt_ffa_run_busy_check(m->requester, caller, 0u, 1u) != 0)) { + return WT_FFA_DENIED; + } + return run_endpoint(co, out, 0u); + } + return WT_FFA_BUSY; + } + if (m->yielded != 0u) { + if ((m->busy != 0u) && (m->requester != caller) && + (m->orphaned == 0u)) { + return WT_FFA_DENIED; + } + m->yielded = 0u; + } + else if ((m->busy != 0u) || (m->calling != 0u) || + (wt_spm_sp_initializing(co) != 0)) { + return WT_FFA_DENIED; + } + else { + deliver_event(co, WT_FFA_RUN, (uint64_t)endpoint_id(co) << 16); + } + return run_endpoint(co, out, 0u); +} + +/* A Secure interrupt taken while its owner runs at S-EL0 is queued here and + * delivered as FFA_INTERRUPT on the owner's next FFA_MSG_WAIT (Table 9.1). */ +static wt_spm_sint_fifo_t g_sp_sint_pending[WT_CO_MAX]; +volatile uint32_t g_wt_spm_sint_queued; + +void wt_spm_sint_queue(uint32_t intid) +{ + struct wt_co* current = g_wt_co_current; + + if ((current == &g_wt_co_bootstrap) || (current->unprivileged == 0u)) { + return; + } + if (wt_spm_sint_fifo_push(&g_sp_sint_pending[current->id - 1u], + intid) == 0) { + g_wt_spm_sint_queued = intid; + } +} + +void wt_spm_sint_queue_for(struct wt_co* co, uint32_t intid) +{ + if ((co == NULL) || (co->id == 0u) || (co->id > WT_CO_MAX)) { + return; + } + if (wt_spm_sint_fifo_push(&g_sp_sint_pending[co->id - 1u], intid) == 0) { + g_wt_spm_sint_queued = intid; + } +} + +/* Ownership a partition claims through the para-virtual interrupt enable, + * and the id its last FFA_INTERRUPT carried, answered by the get. */ +#define WT_SPM_SINT_OWNERS 4u +static struct wt_co* g_sint_owner_co[WT_SPM_SINT_OWNERS]; +static uint32_t g_sint_owner_id[WT_SPM_SINT_OWNERS]; +static uint32_t g_sint_delivered[WT_CO_MAX]; + +int wt_spm_sint_own(struct wt_co* co, uint32_t intid, unsigned int enable) +{ + unsigned int i; + + if ((co == NULL) || (intid < 32u) || (intid >= WT_GIC_INTID_LIMIT)) { + return -1; + } + for (i = 0u; i < WT_SPM_SINT_OWNERS; i++) { + if (g_sint_owner_id[i] == intid) { + if (g_sint_owner_co[i] != co) { + return -1; + } + g_sint_owner_co[i] = (enable != 0u) ? co : NULL; + if (enable == 0u) { + g_sint_owner_id[i] = 0u; + } + return 0; + } + } + /* Only an interrupt the platform declares for this partition. */ + if ((enable == 0u) || (wt_spm_native_declares(native_of(co), intid) == 0)) { + return -1; + } + for (i = 0u; i < WT_SPM_SINT_OWNERS; i++) { + if (g_sint_owner_id[i] == 0u) { + g_sint_owner_id[i] = intid; + g_sint_owner_co[i] = co; + return 0; + } + } + return -1; +} + +struct wt_co* wt_spm_sint_owner(uint32_t intid) +{ + unsigned int i; + + for (i = 0u; i < WT_SPM_SINT_OWNERS; i++) { + if ((g_sint_owner_id[i] == intid) && (g_sint_owner_co[i] != NULL)) { + return g_sint_owner_co[i]; + } + } + return NULL; +} + +void wt_spm_sint_set_delivered(const struct wt_co* co, uint32_t intid) +{ + if ((co != NULL) && (co->id != 0u) && (co->id <= WT_CO_MAX)) { + g_sint_delivered[co->id - 1u] = intid; + } +} + +uint32_t wt_spm_sint_delivered(const struct wt_co* co) +{ + if ((co == NULL) || (co->id == 0u) || (co->id > WT_CO_MAX)) { + return 0u; + } + return g_sint_delivered[co->id - 1u]; +} + +uint32_t wt_spm_sint_take_pending(const struct wt_co* co) +{ + uint32_t intid; + + if ((co == NULL) || (co->id == 0u) || (co->id > WT_CO_MAX)) { + return 0u; + } + intid = wt_spm_sint_fifo_pop(&g_sp_sint_pending[co->id - 1u]); + if (intid != 0u) { + g_sint_delivered[co->id - 1u] = intid; + } + return intid; +} + +/* A partition out of service lets go of every Secure interrupt it owns, what + * was queued for it, its direct-message state, RX/TX pair, and memory + * transactions, and its notification bindings, which answer code for it. */ +static void sp_release(struct wt_co* co, int32_t code) +{ + unsigned int i; + + wt_spm_mem_endpoint_teardown(co); + for (i = 0u; i < WT_SPM_SINT_OWNERS; i++) { + if ((g_sint_owner_co[i] == co) && (g_sint_owner_id[i] != 0u)) { + wt_gic->disable(g_sint_owner_id[i]); + g_sint_owner_co[i] = NULL; + g_sint_owner_id[i] = 0u; + } + } + wt_spm_twdog_stop(co); + (void)memset(&g_sp_sint_pending[co->id - 1u], 0, + sizeof(g_sp_sint_pending[0])); + g_sint_delivered[co->id - 1u] = 0u; + (void)memset(&g_sp_msg[co->id - 1u], 0, sizeof(g_sp_msg[0])); + for (i = 0u; i < WT_CO_MAX; i++) { + /* Yielded to it, or preempted mid-request by a Non-secure interrupt + * (runnable): either way only its requester could have run it. */ + if ((i != (co->id - 1u)) && (g_sp_msg[i].busy != 0u) && + (g_sp_msg[i].requester == wt_spm_sp_ffa_id(co))) { + g_sp_msg[i].orphaned = 1u; + } + } + wt_spm_sp_ffa_reset(co); + wt_ffa_notif_retire(wt_spm_sp_ffa_id(co), code); +} + +void wt_spm_sp_retire(struct wt_co* co) +{ + if ((co == NULL) || (co->id == 0u) || (co->id > WT_CO_MAX)) { + return; + } + wt_co_mark_faulted((wt_co_t*)co); + g_retired[co->id - 1u] = 1u; + sp_release(co, WT_FFA_ABORTED); +} + +int32_t wt_spm_sp_unavailable(const struct wt_co* co) +{ + if ((co == NULL) || (co->id == 0u) || (co->id > WT_CO_MAX)) { + return 0; + } + if (g_retired[co->id - 1u] != 0u) { + return WT_FFA_ABORTED; + } + return (wt_spm_sp_failed_init(co) != 0) ? WT_FFA_DENIED : 0; +} + +/* A waiting partition owed a queued Secure interrupt, staged for delivery; + * clears the signal request once none is left. */ +static struct wt_co* sint_take_waiting_owner(void) +{ + struct wt_co* co; + unsigned int i; + + for (i = 0u; i < WT_CO_MAX; i++) { + co = g_created[i]; + if ((co != NULL) && (co->unprivileged != 0u) && + (g_sp_sint_pending[i].count != 0u) && (endpoint_waiting(co) != 0) && + (sint_stage(co) != 0u)) { + return co; + } + } + g_sint_signal_request = 0u; + return NULL; +} + +int wt_spm_sint_signal_needed(struct wt_co* owner) +{ + if ((owner == NULL) || (owner == g_wt_co_current) || + (owner->unprivileged == 0u) || (endpoint_waiting(owner) == 0)) { + return 0; + } + g_sint_signal_request = 1u; + return 1; +} + +/* Signal a waiting partition: FFA_INTERRUPT, w1/w2 zero with the id left to the + * get call, becomes the return of its wait and it runs, with any partition it + * messages, until it waits again. With the interrupt also pending in the GIC + * this drives the queued path instead. */ +int wt_spm_ffa_signal_deliver(struct wt_co* co, uint32_t intid) +{ + uint64_t out[WT_FFA_MSG_REGS_EXT]; + + if ((co == NULL) || (co->unprivileged == 0u)) { + return WT_FFA_INVALID_PARAMETERS; + } + if (endpoint_waiting(co) == 0) { + return WT_FFA_BUSY; + } + deliver_event(co, WT_FFA_INTERRUPT, 0u); + wt_spm_sint_set_delivered(co, intid); + return (run_endpoint(co, out, 1u) == WT_FFA_ABORTED) ? WT_FFA_ABORTED : 0; +} + +static void write_tpidrro(uint64_t value) +{ + __asm__ volatile("msr TPIDRRO_EL0, %0\n\tisb" : : "r"(value)); +} + +static uint64_t read_tpidr(void) +{ + uint64_t value; + + __asm__ volatile("mrs %0, TPIDR_EL0" : "=r"(value)); + return value; +} + +static void write_tpidr(uint64_t value) +{ + __asm__ volatile("msr TPIDR_EL0, %0" : : "r"(value)); +} + +void wt_co_arch_init_stack(struct wt_co *co, wt_co_entry_fn entry, void *arg) +{ + uintptr_t top = ((uintptr_t)co->stack_base + co->stack_size) & + ~(uintptr_t)15u; + uint64_t* frame = (uint64_t*)(top - WT_CO_FRAME_WORDS * sizeof(uint64_t)); + wt_sp_arch_t* a = sp_arch(co); + unsigned int i; + + for (i = 0u; i < WT_CO_FRAME_WORDS; i++) { + frame[i] = 0u; + } + frame[WT_CO_SLOT_X19] = (uint64_t)(uintptr_t)entry; + frame[WT_CO_SLOT_X20] = (uint64_t)(uintptr_t)arg; + frame[WT_CO_SLOT_X30] = (uint64_t)(uintptr_t)&wt_co_trampoline; + co->sp = (uintptr_t)frame; + + /* The S-EL0 form of the same start: the domain flag arrives later. */ + g_init_seen[co->id - 1u] = WT_SP_INIT_PENDING; + g_retired[co->id - 1u] = 0u; + g_init_gate[co->id - 1u] = 0u; + g_created[co->id - 1u] = co; + (void)memset(&g_sp_msg[co->id - 1u], 0, sizeof(g_sp_msg[0])); + wt_spm_sp_ffa_reset(co); + (void)memset(&a->frame, 0, sizeof(a->frame)); + a->tpidr_el0 = 0u; + a->frame.x[0] = (uint64_t)(uintptr_t)arg; + a->frame.elr = (uint64_t)(uintptr_t)entry; + a->frame.sp_el0 = (uint64_t)top; + a->frame.spsr = WT_SP_SPSR_EL0T; +} + +/* The S-EL1 context wt_sp_el0_enter parks for wt_sp_el0_leave to unwind to + * (x19-x30 and sp): one slot, so a nested entry must save and put it back. */ +extern uint64_t g_wt_sp_kernel_ctx[14]; + +/* Runs `to` until it blocks, faults or is preempted, then resumes whoever + * entered it. That is the scheduler on the bootstrap stack, or, for an + * SP-to-SP message, the exception handler of the partition whose call is being + * served: the callee's unwind slot, the handler bookkeeping, the current + * coroutine, and the caller's translation table and thread ids are all + * single-valued, so they are parked here and restored as soon as `to` comes + * back so the caller's handler completes (and may itself block) as if the + * nested run never happened. From the scheduler this reduces to the bootstrap. */ +void wt_co_arch_enter(struct wt_co *to) +{ + const struct wt_secure_domain *domain = to->domain; + uint64_t kernel_ctx[14]; + wt_trap_frame_t* live_frame = g_wt_spm_live_frame; + uint64_t trap_spsr = g_wt_spm_trap_spsr; + uint32_t handler_depth = g_wt_spm_handler_depth; + struct wt_co *handler_co = g_wt_spm_handler_co; + struct wt_co *prev = (handler_depth != 0u) ? handler_co : &g_wt_co_bootstrap; + uint64_t tpidr = 0u; + uint32_t pmr = 0u; + unsigned int masked = 0u; + + /* No FF-A path allocates cycles to a partition that failed to initialize; + * one that tries anyway (the FF-M gate on a pending signal) finds it gone. */ + if ((to->unprivileged != 0u) && + (g_init_seen[to->id - 1u] == WT_SP_INIT_FAILED)) { + wt_co_mark_faulted((wt_co_t*)to); + g_wt_co_current = prev; + return; + } + (void)memcpy(kernel_ctx, g_wt_sp_kernel_ctx, sizeof(kernel_ctx)); + if (domain != NULL) { + wt_arch_program_sp_thread_domain(domain->regions, domain->region_count); + } + if (to->unprivileged != 0u) { + write_tpidrro((uint64_t)to->id); + tpidr = read_tpidr(); + write_tpidr(sp_arch(to)->tpidr_el0); + if ((g_ns_queued[to->id - 1u] != 0u) || + (g_ns_inherited[to->id - 1u] != 0u)) { + pmr = wt_gic->swap_pmr(WT_GIC_PMR_MASK_NS); + masked = 1u; + } + wt_sp_el0_enter(&sp_arch(to)->frame); + if (masked != 0u) { + (void)wt_gic->swap_pmr(pmr); + } + sp_arch(to)->tpidr_el0 = read_tpidr(); + write_tpidr(tpidr); + } + else { + wt_co_arch_switch(&g_wt_co_bootstrap.sp, to->sp); + } + /* `to` blocked, faulted or was preempted. */ + (void)memcpy(g_wt_sp_kernel_ctx, kernel_ctx, sizeof(kernel_ctx)); + g_wt_spm_live_frame = live_frame; + g_wt_spm_trap_spsr = trap_spsr; + g_wt_spm_handler_depth = handler_depth; + g_wt_spm_handler_co = handler_co; + g_wt_co_current = prev; + /* Back at S-EL1: a lower-EL exit restores the id its entry parked. */ + write_tpidrro(0u); + if (prev->domain != NULL) { + wt_arch_program_sp_thread_domain(prev->domain->regions, + prev->domain->region_count); + } + else if (domain != NULL) { + wt_arch_restore_spm_domain(); + } +} + +void wt_co_arch_leave(void) +{ + struct wt_co *current = g_wt_co_current; + + if (current->unprivileged != 0u) { + if (g_wt_spm_live_frame == NULL) { + wt_platform_panic(); + } + sp_arch(current)->frame = *g_wt_spm_live_frame; + g_wt_spm_live_frame = NULL; + g_wt_spm_handler_depth = 0u; + if ((wt_co_state((wt_co_t*)current) == WT_CO_BLOCKED) && + (g_init_gate[current->id - 1u] != 0u)) { + wt_spm_sp_init_complete(current); + } + g_init_gate[current->id - 1u] = 0u; + wt_sp_el0_leave(); + } + wt_co_arch_switch(¤t->sp, g_wt_co_bootstrap.sp); +} + +/* No deferred trigger: AArch64 has no PendSV, so a preempting tick unwinds + * synchronously from the FIQ handler (wt_spm_preempt_from_fiq) instead. */ +void wt_co_arch_request_preempt(void) +{ +} + +/* A Group 0 tick took the running S-EL0 partition to the SPMC. Preserve its + * full interrupted state in the partition's saved frame, mark it runnable + * again, and unwind to the scheduler exactly as a block does; the partition + * resumes at the interrupted instruction the next time it is run. Returns + * without preempting when the SPMC itself (or a privileged tasklet) was + * running, since only an S-EL0 partition can be resumed from a saved frame. */ +void wt_spm_preempt_from_fiq(wt_trap_frame_t* frame) +{ + struct wt_co* current = g_wt_co_current; + + if ((current == &g_wt_co_bootstrap) || (current->unprivileged == 0u)) { + return; + } + g_wt_spm_live_frame = frame; + g_wt_spm_handler_depth = 1u; + if (wt_co_request_preempt() == false) { + g_wt_spm_live_frame = NULL; + g_wt_spm_handler_depth = 0u; + return; + } + wt_co_arch_leave(); +} + +/* Non-zero while an S-EL0 partition (not the SPMC or a privileged tasklet) is + * the running coroutine, so the test-timer only makes its interrupt pending + * while the partition under test executes. */ +int wt_spm_current_is_partition(void) +{ + struct wt_co* current = g_wt_co_current; + + return ((current != &g_wt_co_bootstrap) && (current->unprivileged != 0u)) ? + 1 : 0; +} + +/* A Normal-world Group 1 interrupt asserted while the partition ran: the + * partition is preempted for the Normal world to take it, and the invoker + * sees FFA_INTERRUPT until FFA_RUN resumes the partition (Ch.9). The GIC is + * left untouched; the interrupt is the Normal world's to acknowledge. */ +void wt_spm_preempt_from_irq(wt_trap_frame_t* frame) +{ + struct wt_co* current = g_wt_co_current; + + if ((current == &g_wt_co_bootstrap) || (current->unprivileged == 0u)) { + return; + } + g_wt_spm_live_frame = frame; + g_wt_spm_handler_depth = 1u; + if (wt_co_request_preempt() == false) { + g_wt_spm_live_frame = NULL; + g_wt_spm_handler_depth = 0u; + return; + } + g_wt_ffa_sp_exit = WT_FFA_SP_EXIT_NSINT; + wt_co_arch_leave(); +} diff --git a/src/arch/aarch64/spm/domain.c b/src/arch/aarch64/spm/domain.c new file mode 100644 index 00000000..76a1608c --- /dev/null +++ b/src/arch/aarch64/spm/domain.c @@ -0,0 +1,519 @@ +/* domain.c + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfTrust. + * + * wolfTrust 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. + * + * wolfTrust 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, see . + */ + +/* The wt_arch_* domain operations over prebuilt per-partition tables: + * program = switch TTBR0 to that partition's table (own ASID, no TLBI), + * restore = back to the SPM-only table. */ + +#include "wolftrust/arch.h" +#include "wolftrust/arch/aarch64/domain.h" +#include "wolftrust/arch/aarch64/tables.h" + +typedef struct wt_domain_entry { + const wt_memory_region_t* regions; + size_t count; + wt_tables_t table; + wt_memory_region_t fill[WT_DOMAIN_MAX_FILL]; + size_t fill_count; +} wt_domain_entry_t; + +static wt_tables_pool_t g_pool; +static wt_tables_t g_spm_table; +static const wt_memory_region_t* g_fill; +static size_t g_fill_count; +static wt_domain_entry_t g_entries[WT_DOMAIN_MAX_TABLES]; +static size_t g_built; +static uint64_t g_current_ttbr0; +static unsigned int g_ready; + +static int covers(const wt_memory_region_t* outer, + const wt_memory_region_t* inner) +{ + uintptr_t outer_end = outer->base + outer->size; + uintptr_t inner_end = inner->base + inner->size; + + return (outer->base <= inner->base) && (outer_end >= inner_end) && + (outer_end >= outer->base) && (inner_end >= inner->base); +} + +static int takes_over(const wt_memory_region_t* region, + const wt_memory_region_t* fill) +{ + if ((fill->attributes & WT_DOMAIN_FILL_SHARED) == 0u) { + return 0; + } + if ((fill->attributes & WT_DOMAIN_FILL_OWNED) != 0u) { + return (region->base == fill->base) && (region->size == fill->size); + } + return covers(region, fill); +} + +static size_t partition_fill(wt_domain_entry_t* e, + const wt_memory_region_t* regions, size_t count) +{ + size_t n = 0u; + size_t i; + size_t j; + int replaced; + + for (i = 0u; (i < g_fill_count) && (n < WT_DOMAIN_MAX_FILL); i++) { + replaced = 0; + for (j = 0u; j < count; j++) { + if (takes_over(®ions[j], &g_fill[i])) { + replaced = 1; + break; + } + } + if (!replaced) { + e->fill[n] = g_fill[i]; + n++; + } + } + return n; +} + +int wt_domain_stack_band(const wt_domain_descriptor_t* d, + wt_memory_region_t* band) +{ + const wt_memory_resource_t* r; + const wt_memory_resource_t* found = NULL; + size_t i; + + if ((d == NULL) || (band == NULL) || + ((d->memory_resource_count != 0u) && (d->memory_resources == NULL))) { + return -1; + } + for (i = 0u; i < d->memory_resource_count; i++) { + r = &d->memory_resources[i]; + if (((r->attributes & WT_MEM_ATTR_WRITE) == 0u) || + ((r->attributes & (WT_MEM_ATTR_DEVICE | WT_MEMORY_ATTR_SHARED)) != + 0u)) { + continue; + } + if ((d->stack_size != 0u) && + ((d->stack_base < r->base) || + ((d->stack_base + d->stack_size) > (r->base + r->size)))) { + continue; + } + found = r; + } + if (found == NULL) { + return -1; + } + band->base = found->base; + band->size = found->size; + band->attributes = WT_MEM_ATTR_READ | WT_MEM_ATTR_WRITE; + return 0; +} + +int wt_domain_spm_band(const wt_domain_descriptor_t* d, size_t i, + wt_memory_region_t* band) +{ + wt_memory_region_t stack; + const wt_memory_resource_t* r; + uint32_t scrubbed = WT_MEM_ATTR_WRITE | WT_MEMORY_ATTR_RESTART_CLEAR; + int ret = -1; + + if ((d == NULL) || (band == NULL) || (d->memory_resources == NULL) || + (i >= d->memory_resource_count)) { + return -1; + } + r = &d->memory_resources[i]; + if ((wt_domain_stack_band(d, &stack) == 0) && (stack.base == r->base) && + (stack.size == r->size)) { + ret = 0; + } + else if (((r->attributes & scrubbed) == scrubbed) && + ((r->attributes & (WT_MEM_ATTR_DEVICE | WT_MEMORY_ATTR_SHARED)) == + 0u)) { + ret = 0; + } + if (ret == 0) { + band->base = r->base; + band->size = r->size; + band->attributes = WT_MEM_ATTR_READ | WT_MEM_ATTR_WRITE; + } + return ret; +} + +int wt_domain_fill_foreign(const wt_memory_region_t* fill, + const uint32_t* owners, size_t fill_count, + uint32_t owner, uintptr_t base, size_t size) +{ + uint64_t end = (uint64_t)base + (uint64_t)size; + uint64_t fill_end; + size_t i; + + if ((fill == NULL) || (owners == NULL) || (end < (uint64_t)base)) { + return 1; + } + for (i = 0u; i < fill_count; i++) { + fill_end = (uint64_t)fill[i].base + (uint64_t)fill[i].size; + if (((fill[i].attributes & WT_DOMAIN_FILL_OWNED) != 0u) && + (owners[i] != owner) && ((uint64_t)base < fill_end) && + ((uint64_t)fill[i].base < end)) { + return 1; + } + } + return 0; +} + +uint64_t wt_domain_init(const wt_memory_region_t* fill, size_t fill_count, + uint8_t* pool, uint64_t pool_pa, size_t pool_size) +{ + int ret; + + g_ready = 0u; + g_built = 0u; + g_fill = fill; + g_fill_count = fill_count; + wt_tables_pool_init(&g_pool, pool, pool_pa, pool_size); + /* The SPM-only table keeps every fill entry EL1-only; the builder reads + * only the access bits, so the shareable flag passes through unused. */ + ret = wt_tables_build(&g_spm_table, 0u, NULL, 0u, fill, fill_count, &g_pool); + if (ret != WT_TABLES_OK) { + wt_domain_fail(WT_DOMAIN_FAIL_INIT); + return 0u; + } + g_current_ttbr0 = wt_tables_ttbr0(&g_spm_table); + g_ready = 1u; + return g_current_ttbr0; +} + +uint64_t wt_domain_current_ttbr0(void) +{ + return g_current_ttbr0; +} + +size_t wt_domain_tables_built(void) +{ + return g_built; +} + +size_t wt_domain_pool_pages_used(void) +{ + return wt_tables_pool_pages_used(&g_pool); +} + +static wt_domain_entry_t* find_or_build(const wt_memory_region_t* regions, + size_t count) +{ + wt_domain_entry_t* e; + size_t i; + int ret; + + for (i = 0u; i < g_built; i++) { + if ((g_entries[i].regions == regions) && (g_entries[i].count == count)) { + return &g_entries[i]; + } + } + if (g_built >= WT_DOMAIN_MAX_TABLES) { + wt_domain_fail(WT_DOMAIN_FAIL_SLOTS); + return NULL; + } + e = &g_entries[g_built]; + if (g_fill_count > WT_DOMAIN_MAX_FILL) { + wt_domain_fail(WT_DOMAIN_FAIL_BUILD); + return NULL; + } + e->fill_count = partition_fill(e, regions, count); + ret = wt_tables_build(&e->table, (uint16_t)(g_built + 1u), regions, count, + e->fill, e->fill_count, &g_pool); + if (ret != WT_TABLES_OK) { + wt_domain_fail(WT_DOMAIN_FAIL_BUILD); + return NULL; + } + e->regions = regions; + e->count = count; + g_built++; + return e; +} + +static wt_domain_entry_t* find_built(const wt_memory_region_t* regions, + size_t count) +{ + size_t i; + + for (i = 0u; i < g_built; i++) { + if ((g_entries[i].regions == regions) && (g_entries[i].count == count)) { + return &g_entries[i]; + } + } + return NULL; +} + +/* A page EL0 may now execute can hold instructions the partition wrote as + * data, which EL0 cannot make fetchable itself (SCTLR_EL1.UCI is 0). */ +static void sync_el0_exec(const wt_domain_entry_t* e, uintptr_t va, + size_t pages) +{ + wt_tables_walk_t w; + uint64_t ttbr0 = wt_tables_ttbr0(&e->table); + uint64_t at; + size_t i; + int exec = 0; + + for (i = 0u; (i < pages) && (exec == 0); i++) { + at = (uint64_t)va + ((uint64_t)i * WT_TABLES_PAGE_SIZE); + if ((wt_tables_walk(&e->table, &g_pool, at, &w) == WT_TABLES_OK) && + (w.uxn == 0u)) { + exec = 1; + } + } + if (exec != 0) { + if (ttbr0 != g_current_ttbr0) { + wt_mmu_switch_ttbr0(ttbr0); + } + wt_mmu_sync_icache((uint64_t)va, (uint64_t)pages * WT_TABLES_PAGE_SIZE); + if (ttbr0 != g_current_ttbr0) { + wt_mmu_switch_ttbr0(g_current_ttbr0); + } + } +} + +int wt_domain_set_permissions(const wt_memory_region_t* regions, size_t count, + uintptr_t va, size_t pages, uint32_t attributes) +{ + wt_domain_entry_t* e = find_built(regions, count); + int ret; + + if ((g_ready == 0u) || (e == NULL) || (regions == NULL)) { + return WT_TABLES_ERROR_ARGUMENT; + } + ret = wt_tables_set_el0_attributes(&e->table, &g_pool, (uint64_t)va, pages, + attributes); + if (ret == WT_TABLES_OK) { + wt_mmu_tlbi_asid((uint64_t)e->table.asid); + sync_el0_exec(e, va, pages); + } + return ret; +} + +/* What EL0 access this partition's stage-1 table gives va, whatever its region + * list says: memory a partition owns or was given (a donate) is reachable at + * EL0, so ownership that a transaction moved is still seen. Only Normal + * write-back memory counts, the one type the relayer maps a borrower with. */ +int wt_domain_page_access(const wt_memory_region_t* regions, size_t count, + uintptr_t va) +{ + const wt_domain_entry_t* e = find_built(regions, count); + wt_tables_walk_t w; + + if ((g_ready == 0u) || (e == NULL) || (regions == NULL) || + ((va % WT_TABLES_PAGE_SIZE) != 0u)) { + return WT_DOMAIN_ACCESS_NONE; + } + if ((wt_tables_walk(&e->table, &g_pool, (uint64_t)va, &w) != WT_TABLES_OK) || + (w.attr_index != WT_TABLES_ATTR_NORMAL_WBWA)) { + return WT_DOMAIN_ACCESS_NONE; + } + if (w.ap == WT_TABLES_AP_ALL_RW) { + return WT_DOMAIN_ACCESS_RW; + } + return (w.ap == WT_TABLES_AP_ALL_RO) ? WT_DOMAIN_ACCESS_RO + : WT_DOMAIN_ACCESS_NONE; +} + +int wt_domain_page_claimed(const wt_memory_region_t* regions, size_t count, + uintptr_t va) +{ + const wt_domain_entry_t* e = find_built(regions, count); + wt_tables_walk_t w; + + if ((g_ready == 0u) || (e == NULL) || (regions == NULL) || + ((va % WT_TABLES_PAGE_SIZE) != 0u) || + (wt_tables_walk(&e->table, &g_pool, (uint64_t)va, &w) != WT_TABLES_OK)) { + return 0; + } + return ((w.ap == WT_TABLES_AP_ALL_RW) || (w.ap == WT_TABLES_AP_ALL_RO) || + (w.hidden != 0u) || (w.held != 0u)) ? 1 : 0; +} + +int wt_domain_page_owned(const wt_memory_region_t* regions, size_t count, + uintptr_t va) +{ + const wt_domain_entry_t* e = find_built(regions, count); + wt_tables_walk_t w; + + if ((g_ready == 0u) || (e == NULL) || (regions == NULL) || + ((va % WT_TABLES_PAGE_SIZE) != 0u) || + (wt_tables_walk(&e->table, &g_pool, (uint64_t)va, &w) != WT_TABLES_OK) || + (w.held != 0u)) { + return 0; + } + return ((w.ap == WT_TABLES_AP_ALL_RW) || (w.ap == WT_TABLES_AP_ALL_RO) || + (w.hidden != 0u)) ? 1 : 0; +} + +int wt_domain_page_ns(const wt_memory_region_t* regions, size_t count, + uintptr_t va) +{ + const wt_domain_entry_t* e = find_built(regions, count); + wt_tables_walk_t w; + + if ((g_ready == 0u) || (e == NULL) || (regions == NULL) || + (wt_tables_walk(&e->table, &g_pool, (uint64_t)va, &w) != WT_TABLES_OK)) { + return 0; + } + return (w.ns != 0u) ? 1 : 0; +} + +int wt_domain_grant(const wt_memory_region_t* regions, size_t count, + uintptr_t va, size_t pages, uint32_t attributes, + int* was_mapped) +{ + wt_domain_entry_t* e = find_built(regions, count); + int ret; + + if ((g_ready == 0u) || (e == NULL) || (regions == NULL)) { + return WT_TABLES_ERROR_ARGUMENT; + } + ret = wt_tables_grant_el0(&e->table, &g_pool, (uint64_t)va, pages, + attributes, was_mapped); + wt_mmu_tlbi_asid((uint64_t)e->table.asid); + return ret; +} + +int wt_domain_revoke(const wt_memory_region_t* regions, size_t count, + uintptr_t va, size_t pages, int was_mapped) +{ + wt_domain_entry_t* e = find_built(regions, count); + int ret; + + if ((g_ready == 0u) || (e == NULL) || (regions == NULL)) { + return WT_TABLES_ERROR_ARGUMENT; + } + ret = wt_tables_revoke_el0(&e->table, &g_pool, (uint64_t)va, pages, + was_mapped); + wt_mmu_tlbi_asid((uint64_t)e->table.asid); + return ret; +} + +int wt_domain_owner_hold(const wt_memory_region_t* regions, size_t count, + uintptr_t va, size_t pages, int keep_read) +{ + wt_domain_entry_t* e = find_built(regions, count); + int ret; + + if ((g_ready == 0u) || (e == NULL) || (regions == NULL)) { + return WT_TABLES_ERROR_ARGUMENT; + } + ret = wt_tables_hold_el0(&e->table, &g_pool, (uint64_t)va, pages, + keep_read); + wt_mmu_tlbi_asid((uint64_t)e->table.asid); + return ret; +} + +int wt_domain_owner_withdraw(const wt_memory_region_t* regions, size_t count, + uintptr_t va, size_t pages) +{ + wt_domain_entry_t* e = find_built(regions, count); + int ret; + + if ((g_ready == 0u) || (e == NULL) || (regions == NULL)) { + return WT_TABLES_ERROR_ARGUMENT; + } + ret = wt_tables_withdraw_el0(&e->table, &g_pool, (uint64_t)va, pages); + wt_mmu_tlbi_asid((uint64_t)e->table.asid); + return ret; +} + +int wt_domain_owner_release(const wt_memory_region_t* regions, size_t count, + uintptr_t va, size_t pages) +{ + wt_domain_entry_t* e = find_built(regions, count); + int ret; + + if ((g_ready == 0u) || (e == NULL) || (regions == NULL)) { + return WT_TABLES_ERROR_ARGUMENT; + } + ret = wt_tables_release_el0(&e->table, &g_pool, (uint64_t)va, pages); + wt_mmu_tlbi_asid((uint64_t)e->table.asid); + if (ret == WT_TABLES_OK) { + sync_el0_exec(e, va, pages); + } + return ret; +} + +int wt_domain_get_permissions(const wt_memory_region_t* regions, size_t count, + uintptr_t va, uint32_t* attributes) +{ + const wt_domain_entry_t* e = find_built(regions, count); + wt_tables_walk_t w; + int ret; + + if ((g_ready == 0u) || (e == NULL) || (regions == NULL) || + (attributes == NULL)) { + return WT_TABLES_ERROR_ARGUMENT; + } + if ((va % WT_TABLES_PAGE_SIZE) != 0u) { + return WT_TABLES_ERROR_ALIGN; + } + ret = wt_tables_walk(&e->table, &g_pool, (uint64_t)va, &w); + if (ret == WT_TABLES_OK) { + *attributes = 0u; + if ((w.ap == WT_TABLES_AP_ALL_RW) || (w.ap == WT_TABLES_AP_ALL_RO)) { + *attributes |= WT_MEM_ATTR_READ; + } + if (w.ap == WT_TABLES_AP_ALL_RW) { + *attributes |= WT_MEM_ATTR_WRITE; + } + if (w.uxn == 0u) { + *attributes |= WT_MEM_ATTR_EXEC; + } + } + return ret; +} + +static void switch_to(uint64_t ttbr0) +{ + g_current_ttbr0 = ttbr0; + wt_mmu_switch_ttbr0(ttbr0); +} + +void wt_arch_program_sp_thread_domain(const wt_memory_region_t* regions, + size_t count) +{ + wt_domain_entry_t* e; + + if (g_ready == 0u) { + wt_domain_fail(WT_DOMAIN_FAIL_INIT); + return; + } + e = find_or_build(regions, count); + if (e != NULL) { + switch_to(wt_tables_ttbr0(&e->table)); + } +} + +/* No privileged-default variant exists at S-EL1 (the H5 PRIVDEFENA-off + * form): the partition table is the domain. */ +void wt_arch_program_secure_partition_domain(const wt_memory_region_t* regions, + size_t count) +{ + wt_arch_program_sp_thread_domain(regions, count); +} + +void wt_arch_restore_spm_domain(void) +{ + if (g_ready != 0u) { + switch_to(wt_tables_ttbr0(&g_spm_table)); + } +} diff --git a/src/arch/aarch64/spm/mmu.S b/src/arch/aarch64/spm/mmu.S new file mode 100644 index 00000000..248156a7 --- /dev/null +++ b/src/arch/aarch64/spm/mmu.S @@ -0,0 +1,126 @@ +/* mmu.S + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfTrust. + * + * wolfTrust 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. + * + * wolfTrust 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, see . + */ + +/* void wt_mmu_enable(uint64_t ttbr0, uint64_t mair, uint64_t tcr): S-EL1 + * stage 1 on with M|C|I|SA|SA0|WXN; nTWI/nTWE cleared so EL0 wfi/wfe trap. */ + +#define SCTLR_M (1 << 0) +#define SCTLR_C (1 << 2) +#define SCTLR_SA (1 << 3) +#define SCTLR_SA0 (1 << 4) +#define SCTLR_I (1 << 12) +#define SCTLR_NTWI (1 << 16) +#define SCTLR_NTWE (1 << 18) +#define SCTLR_WXN (1 << 19) + + .section .text.wt_mmu_enable, "ax" + .globl wt_mmu_enable +wt_mmu_enable: + msr MAIR_EL1, x1 + msr TCR_EL1, x2 + msr TTBR0_EL1, x0 + isb + tlbi vmalle1 + dsb nsh + isb + mrs x3, SCTLR_EL1 + ldr x4, =(SCTLR_M | SCTLR_C | SCTLR_SA | SCTLR_SA0 | SCTLR_I | SCTLR_WXN) + orr x3, x3, x4 + ldr x4, =(SCTLR_NTWI | SCTLR_NTWE) + bic x3, x3, x4 + msr SCTLR_EL1, x3 + isb + ret + +/* void wt_mmu_switch_ttbr0(uint64_t ttbr0): distinct ASIDs, no TLBI; the DSB + * publishes a table built just before its first use. */ + .globl wt_mmu_switch_ttbr0 +wt_mmu_switch_ttbr0: + dsb ishst + msr TTBR0_EL1, x0 + isb + ret + +/* void wt_mmu_tlbi_asid(uint64_t asid): drop one domain's cached entries once + * the caller's descriptor stores are visible to the walker. */ + .globl wt_mmu_tlbi_asid +wt_mmu_tlbi_asid: + lsl x0, x0, #48 + dsb ishst + tlbi aside1is, x0 + dsb ish + isb + ret + +/* void wt_mmu_dcache_clean_inval(uint64_t va, uint64_t size): every data cache + * line of the range, stepped by the smallest line CTR_EL0 reports, then a + * full-system barrier so the memory itself holds what was written. */ + .globl wt_mmu_dcache_clean_inval +wt_mmu_dcache_clean_inval: + cbz x1, 2f + mrs x3, CTR_EL0 + ubfx x3, x3, #16, #4 + mov x2, #4 + lsl x2, x2, x3 + add x1, x0, x1 + sub x3, x2, #1 + bic x0, x0, x3 +1: + dc civac, x0 + add x0, x0, x2 + cmp x0, x1 + b.lo 1b +2: + dsb sy + ret + +/* void wt_mmu_sync_icache(uint64_t va, uint64_t size): make instructions + * written as data fetchable from the range: clean each data line to the point + * of unification, DSB ISH, invalidate each instruction line, DSB ISH, ISB. */ + .globl wt_mmu_sync_icache +wt_mmu_sync_icache: + cbz x1, 3f + add x1, x0, x1 + mrs x3, CTR_EL0 + ubfx x4, x3, #16, #4 + mov x2, #4 + lsl x2, x2, x4 + sub x4, x2, #1 + bic x5, x0, x4 +1: + dc cvau, x5 + add x5, x5, x2 + cmp x5, x1 + b.lo 1b + dsb ish + and x4, x3, #0xf + mov x2, #4 + lsl x2, x2, x4 + sub x4, x2, #1 + bic x5, x0, x4 +2: + ic ivau, x5 + add x5, x5, x2 + cmp x5, x1 + b.lo 2b + dsb ish + isb +3: + ret diff --git a/src/arch/aarch64/spm/platform_arch.c b/src/arch/aarch64/spm/platform_arch.c new file mode 100644 index 00000000..5b6dd92e --- /dev/null +++ b/src/arch/aarch64/spm/platform_arch.c @@ -0,0 +1,340 @@ +/* platform_arch.c + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfTrust. + * + * wolfTrust 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. + * + * wolfTrust 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, see . + */ + +/* The wt_arch_* contract at S-EL1 minus the domain ops (domain.c): the + * Secure-side operations are real, the Non-secure ones fail closed until + * the NS gateway lands. */ + +#include "wolftrust/arch/aarch64/context.h" +#include "wolftrust/arch/aarch64/el3.h" +#include "wolftrust/arch/aarch64/gic.h" +#include "wolftrust/arch/aarch64/psa_ffa.h" +#include "wolftrust/arch/aarch64/spm_mem.h" +#include "wolftrust/arch/aarch64/spm_svc.h" +#include "wolftrust/arch.h" +#include "wolftrust/irq_claim.h" +#include "wolftrust/platform.h" +#include "memory_map.h" + +#include +#include +#include + +volatile uint32_t g_wt_spm_handler_depth; +volatile uint64_t g_wt_spm_trap_spsr; + +void wt_arch_init(void) +{ +} + +void wt_arch_start_secure_timer(uint32_t timeslice_ms) +{ + wt_el3_timer_arm_ms(timeslice_ms); +} + +void wt_arch_mask_all_guest_irqs(void) +{ +} + +void wt_arch_apply_irq_mask(const wt_irq_mask_t* mask) +{ + (void)mask; +} + +void wt_arch_quarantine_pending_irqs(const wt_irq_mask_t* allowed_mask) +{ + (void)allowed_mask; +} + +void wt_arch_program_guest_domain(const wt_memory_region_t* regions, + size_t count) +{ + (void)regions; + (void)count; +} + +void wt_arch_guest_context_prepare(wt_guest_id_t guest_id, + const wt_guest_context_t* context) +{ + (void)guest_id; + (void)context; +} + +void wt_arch_guest_context_capture(wt_guest_context_t* context, + const wt_trap_frame_t* frame) +{ + unsigned int i; + + if (context == NULL || frame == NULL) { + return; + } + for (i = 0u; i < 31u; i++) { + context->x[i] = frame->x[i]; + } + context->sp_el0 = frame->sp_el0; + context->elr = frame->elr; + context->spsr = frame->spsr; + context->pc = (uintptr_t)frame->elr; + context->frame_stacked = true; +} + +/* No Normal world yet: dispatching a guest means handing the CPU back to + * the SPMD and waiting for FF-A events. */ +void wt_arch_guest_context_restore(wt_guest_context_t* context) +{ + (void)context; + wt_spm_init_partitions(); + wt_spm_idle(); +} + +bool wt_arch_guest_context_ready(const wt_guest_context_t* context) +{ + return (context != NULL) && (context->pc != 0u); +} + +uintptr_t wt_arch_trap_pc(const wt_trap_frame_t* frame) +{ + return (frame != NULL) ? (uintptr_t)frame->elr : 0u; +} + +uint32_t wt_arch_active_guest_id(void) +{ + return 0u; +} + +void wt_arch_zero_guest_memory(uintptr_t base, size_t size) +{ + if (base != 0u && size != 0u) { + (void)memset((void*)base, 0, size); + } +} + +extern uint8_t _si_vault[], _s_vault[], _e_vault_data[]; +extern uint8_t _si_attest[], _s_attest[], _e_attest_data[]; +extern uint8_t _si_hsm[], _s_hsm[], _e_hsm_data[]; + +/* A keystore band returns to its link-time image: zeroed, then its .data + * reloaded; any other band has no load image and only zeroes. */ +void wt_arch_sp_band_reset(uintptr_t base, size_t size) +{ + static uint8_t* const bands[][3] = { + { _si_vault, _s_vault, _e_vault_data }, + { _si_attest, _s_attest, _e_attest_data }, + { _si_hsm, _s_hsm, _e_hsm_data }, + }; + size_t i; + + wt_arch_zero_guest_memory(base, size); + for (i = 0u; i < sizeof(bands) / sizeof(bands[0]); i++) { + if ((uintptr_t)bands[i][1] == base && + (size_t)(bands[i][2] - bands[i][1]) <= size) { + (void)memcpy(bands[i][1], bands[i][0], + (size_t)(bands[i][2] - bands[i][1])); + } + } +} + +int wt_arch_range_is_mmio(uintptr_t base, size_t size) +{ + static const uint64_t windows[][2] = WT_PORT_MMIO_WINDOWS; + uint64_t end = (uint64_t)base + (uint64_t)size; + size_t i; + + if (size == 0u || end < (uint64_t)base) { + return 1; + } + for (i = 0u; i < sizeof(windows) / sizeof(windows[0]); i++) { + if ((uint64_t)base < windows[i][1] && windows[i][0] < end) { + return 1; + } + } + return 0; +} + +void wt_arch_restore_guest_bank(const wt_guest_context_t* context) +{ + (void)context; +} + +bool wt_arch_in_handler_mode(void) +{ + return g_wt_spm_handler_depth != 0u; +} + +bool wt_arch_trap_from_guest_thread(void) +{ + return false; +} + +bool wt_arch_trap_from_secure_thread(void) +{ + return (g_wt_spm_handler_depth != 0u) && + ((g_wt_spm_trap_spsr & 0xFu) == 0u); +} + +void wt_arch_return_to_secure_thread(void (*entry)(void) + __attribute__((noreturn))) +{ + (void)entry; + wt_platform_panic(); +} + +uintptr_t wt_arch_read_fault_address(void) +{ + uint64_t far; + + __asm__ volatile("mrs %0, FAR_EL1" : "=r"(far)); + return (uintptr_t)far; +} + +/* A partition's manifest interrupt is a Secure (Group 0) source: the GIC + * leaves SPIs Non-secure by default, and a Group 1 line would sit pending + * behind the partition's masked IRQ instead of arriving as the FIQ that + * asserts its signal. */ +void wt_arch_secure_irq_enable(uint32_t irq) +{ + wt_gic->set_group0(irq); + wt_gic->set_priority(irq, 0x00u); + wt_gic->enable(irq); +} + +void wt_arch_secure_irq_disable(uint32_t irq) +{ + wt_gic->disable(irq); +} + +/* wt_irq_claim hands over one line at a time as a word and a one-bit mask. */ +static uint32_t gic_claim_intid(uint32_t word, uint32_t mask) +{ + return word * 32u + (uint32_t)__builtin_ctz(mask); +} + +static void gic_claim_disable(uint32_t word, uint32_t mask) +{ + wt_gic->disable(gic_claim_intid(word, mask)); +} + +static void gic_claim_clear_pending(uint32_t word, uint32_t mask) +{ + wt_gic->clear_pending(gic_claim_intid(word, mask)); +} + +static void gic_claim_route_secure(uint32_t word, uint32_t mask) +{ + wt_gic->set_group0(gic_claim_intid(word, mask)); +} + +static uint32_t gic_claim_enabled(uint32_t word) +{ + return wt_gic->enabled_word(word); +} + +static uint32_t gic_claim_not_group0(uint32_t word) +{ + return wt_gic->not_group0_word(word); +} + +static void gic_claim_barrier(void) +{ + __asm__ volatile("dsb sy\n\tisb" ::: "memory"); +} + +static const wt_nvic_ops_t g_wt_gic_claim_ops = { + gic_claim_disable, + gic_claim_clear_pending, + gic_claim_route_secure, + gic_claim_enabled, + gic_claim_not_group0, + gic_claim_barrier +}; + +/* SPIs only: SGIs and PPIs are banked per PE and never a partition's line. */ +int wt_arch_secure_irq_claim(uint32_t irq) +{ + if (irq < 32u || irq >= WT_GIC_INTID_LIMIT) { + return -1; + } + return wt_irq_claim(&g_wt_gic_claim_ops, irq, wt_gic->line_count() / 32u); +} + +void wt_arch_route_irq_to_guest(uint32_t irq) +{ + (void)irq; +} + +void wt_arch_set_guest_irq_pending(uint32_t irq, bool asserted) +{ + (void)irq; + (void)asserted; +} + +void wt_arch_dmb(void) +{ + __asm__ volatile("dmb sy" ::: "memory"); +} + +void wt_arch_dsb(void) +{ + __asm__ volatile("dsb sy" ::: "memory"); +} + +uintptr_t wt_arch_sp_stack_pointer(void) +{ + uint64_t sp; + + __asm__ volatile("mrs %0, SP_EL0" : "=r"(sp)); + return (uintptr_t)sp; +} + +void wt_arch_sp_redirect_to_panic_trap(wt_trap_frame_t* frame) +{ + if (frame != NULL) { + frame->elr = (uint64_t)(uintptr_t)&wt_spm_sp_panic_trap; + } +} + +void wt_arch_assert_privileged_thread(void) +{ +} + +/* The Normal-world client's memory is reachable only through the Non-secure + * window the SPMC maps EL1-only (wt_spm_psa_init); every vector a guest hands + * the FF-M gateway must lie inside it, in memory the guest has not since lent + * or donated away, and only the primary guest exists. */ +int wt_arch_ns_check_read(wt_guest_id_t guest_id, const void* address, + size_t size) +{ + return (guest_id == (wt_guest_id_t)0) && + wt_spm_ns_window_ok((uintptr_t)address, size) && + wt_spm_mem_ns_access((uint64_t)(uintptr_t)address, (uint64_t)size, 0); +} + +int wt_arch_ns_check_write(wt_guest_id_t guest_id, void* address, size_t size) +{ + return (guest_id == (wt_guest_id_t)0) && + wt_spm_ns_window_ok((uintptr_t)address, size) && + wt_spm_mem_ns_access((uint64_t)(uintptr_t)address, (uint64_t)size, 1); +} + +int wt_arch_ns_check_writable(const void* address, size_t size) +{ + return wt_spm_ns_window_ok((uintptr_t)address, size) && + wt_spm_mem_ns_access((uint64_t)(uintptr_t)address, (uint64_t)size, 1); +} diff --git a/src/arch/aarch64/spm/psa_service.c b/src/arch/aarch64/spm/psa_service.c new file mode 100644 index 00000000..ea49bd92 --- /dev/null +++ b/src/arch/aarch64/spm/psa_service.c @@ -0,0 +1,123 @@ +/* psa_service.c + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfTrust. + * + * wolfTrust 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. + * + * wolfTrust 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, see . + */ + +/* The PSA framework endpoint at the NS physical instance: the AArch64 twin of + * the Armv8-M CMSE veneers. Each Normal-world FrameworkVersion/ServiceVersion/ + * Connect/Close/Call is handed to the neutral FF-M gateway, which owns caller + * identity, handles, versions, and misuse detection; a Call's vectors stay in + * the guest's memory and the core copies them itself (psa_read/psa_write) + * through the Non-secure window the SPMC maps EL1-only. */ + +#include "wolftrust/arch/aarch64/ffa.h" +#include "wolftrust/arch/aarch64/ffa_abi.h" +#include "wolftrust/arch/aarch64/ffa_msg.h" +#include "wolftrust/arch/aarch64/gic.h" +#include "wolftrust/arch/aarch64/psa_ffa.h" +#include "wolftrust/ffm_gateway.h" +#include "wolftrust/ffm_veneer.h" + +#include "psa/client.h" + +static uint64_t g_ns_lo; +static uint64_t g_ns_hi; + +void wt_spm_psa_init(uint64_t ns_lo, uint64_t ns_hi) +{ + g_ns_lo = ns_lo; + g_ns_hi = ns_hi; +} + +/* A non-empty span must lie entirely inside the Non-secure window: this is the + * whole security check - a Secure or out-of-range pointer is refused, so the + * core never reads or writes anything but the caller's own memory. */ +int wt_spm_ns_window_ok(uintptr_t base, size_t len) +{ + uint64_t b = (uint64_t)base; + uint64_t n = (uint64_t)len; + + if (n == 0u) { + return 1; + } + return (b >= g_ns_lo) && (b <= g_ns_hi) && (n <= (g_ns_hi - b)); +} + +int wt_spm_psa_framework(wt_ffa_regs_t* r) +{ + uint32_t fid = (uint32_t)r->x[0]; + uint32_t op = (uint32_t)r->x[3]; + uint32_t a0 = (uint32_t)r->x[4]; + uint32_t a1 = (uint32_t)r->x[5]; + uint16_t sender = wt_ffa_direct_sender(r->x[1]); + int is64 = (fid == WT_FFA_MSG_SEND_DIRECT_REQ64); + int32_t result = 0; + int ok = 1; + unsigned int i; + uint32_t pmr; + + /* 9.3.1.3: answered only with a response, never FFA_INTERRUPT for an + * FFA_RUN to resume, so the partitions an FF-M call runs queue NS-Ints. */ + pmr = wt_gic->swap_pmr(WT_GIC_PMR_MASK_NS); + switch (op) { + case WT_PSA_FFA_OP_FRAMEWORK_VERSION: + result = (int32_t)wt_ffm_gateway_framework_version(); + break; + case WT_PSA_FFA_OP_SERVICE_VERSION: + result = (int32_t)wt_ffm_gateway_service_version(a0); + break; + case WT_PSA_FFA_OP_CONNECT: + result = wt_ffm_gateway_connect(a0, a1); + break; + case WT_PSA_FFA_OP_CLOSE: + wt_ffm_gateway_close((int32_t)a0); + result = (int32_t)PSA_SUCCESS; + break; + case WT_PSA_FFA_OP_CALL: + /* x4 = the client's Non-secure vector block, x5 = handle (31:0) + * and type (63:32); an SMC32 request carries only w4/w5 (7.2.1). */ + if (is64 == 0) { + ok = 0; + break; + } + result = wt_ffm_gateway_call((int32_t)(uint32_t)r->x[5], + (int32_t)(uint32_t)(r->x[5] >> 32), + (wt_ffm_veneer_iovec_t*)(uintptr_t)r->x[4]); + break; + default: + ok = 0; + break; + } + (void)wt_gic->swap_pmr(pmr); + if (ok == 0) { + for (i = 0u; i < 8u; i++) { + r->x[i] = 0u; + } + r->x[0] = WT_FFA_ERROR; + r->x[2] = (uint64_t)(uint32_t)WT_FFA_INVALID_PARAMETERS; + return -1; + } + for (i = 0u; i < 8u; i++) { + r->x[i] = 0u; + } + r->x[0] = (is64 != 0) ? WT_FFA_MSG_SEND_DIRECT_RESP64 + : WT_FFA_MSG_SEND_DIRECT_RESP32; + r->x[1] = ((uint64_t)WT_FFA_ID_PSA << 16) | sender; + r->x[3] = (uint64_t)(uint32_t)result; + return 0; +} diff --git a/src/arch/aarch64/spm/sp_entry.S b/src/arch/aarch64/spm/sp_entry.S new file mode 100644 index 00000000..4ae91b47 --- /dev/null +++ b/src/arch/aarch64/spm/sp_entry.S @@ -0,0 +1,586 @@ +/* sp_entry.S + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfTrust. + * + * wolfTrust 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. + * + * wolfTrust 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, see . + */ + +/* S-EL0 partition entry and exit. wt_sp_el0_enter parks the S-EL1 caller's + * callee-saved state, loads a wt_trap_frame into the EL0 register state and + * ERETs; a lower-EL synchronous exception lands in wt_sp_lower_sync_entry, + * which builds the frame for wt_spm_lower_sync and either ERETs back into + * the partition or, when the handler unwinds through wt_sp_el0_leave, + * resumes the parked S-EL1 caller. Frame layout: include/wolftrust/arch/ + * aarch64/context.h (x0..x30, SP_EL0, ELR, SPSR, ESR, FAR; 288 bytes). */ + +#define FRAME_SP_EL0 248 +#define FRAME_ELR 256 +#define FRAME_SPSR 264 +#define FRAME_ESR 272 +#define FRAME_FAR 280 +#define FRAME_SIZE 288 +/* The frame plus the partition id the entry parks from TPIDRRO_EL0. */ +#define FRAME_STACK 304 + + .section .bss.g_wt_sp_kernel_ctx, "aw", %nobits + .balign 16 + .globl g_wt_sp_kernel_ctx +g_wt_sp_kernel_ctx: + .skip 112 + + .section .text.wt_sp_el0_enter, "ax" + .globl wt_sp_el0_enter +wt_sp_el0_enter: + adrp x1, g_wt_sp_kernel_ctx + add x1, x1, :lo12:g_wt_sp_kernel_ctx + stp x19, x20, [x1, #0] + stp x21, x22, [x1, #16] + stp x23, x24, [x1, #32] + stp x25, x26, [x1, #48] + stp x27, x28, [x1, #64] + stp x29, x30, [x1, #80] + mov x2, sp + str x2, [x1, #96] + ldr x1, [x0, #FRAME_SP_EL0] + msr SP_EL0, x1 + ldr x1, [x0, #FRAME_ELR] + msr ELR_EL1, x1 + ldr x1, [x0, #FRAME_SPSR] + msr SPSR_EL1, x1 + ldp x2, x3, [x0, #16] + ldp x4, x5, [x0, #32] + ldp x6, x7, [x0, #48] + ldp x8, x9, [x0, #64] + ldp x10, x11, [x0, #80] + ldp x12, x13, [x0, #96] + ldp x14, x15, [x0, #112] + ldp x16, x17, [x0, #128] + ldp x18, x19, [x0, #144] + ldp x20, x21, [x0, #160] + ldp x22, x23, [x0, #176] + ldp x24, x25, [x0, #192] + ldp x26, x27, [x0, #208] + ldp x28, x29, [x0, #224] + ldr x30, [x0, #240] + ldp x0, x1, [x0, #0] + eret + + .section .text.wt_sp_el0_leave, "ax" + .globl wt_sp_el0_leave +wt_sp_el0_leave: + adrp x1, g_wt_sp_kernel_ctx + add x1, x1, :lo12:g_wt_sp_kernel_ctx + ldp x19, x20, [x1, #0] + ldp x21, x22, [x1, #16] + ldp x23, x24, [x1, #32] + ldp x25, x26, [x1, #48] + ldp x27, x28, [x1, #64] + ldp x29, x30, [x1, #80] + ldr x2, [x1, #96] + mov sp, x2 + ret + + .section .text.wt_sp_lower_sync_entry, "ax" + .globl wt_sp_lower_sync_entry +wt_sp_lower_sync_entry: + sub sp, sp, #FRAME_STACK + stp x0, x1, [sp, #0] + stp x2, x3, [sp, #16] + stp x4, x5, [sp, #32] + stp x6, x7, [sp, #48] + stp x8, x9, [sp, #64] + stp x10, x11, [sp, #80] + stp x12, x13, [sp, #96] + stp x14, x15, [sp, #112] + stp x16, x17, [sp, #128] + stp x18, x19, [sp, #144] + stp x20, x21, [sp, #160] + stp x22, x23, [sp, #176] + stp x24, x25, [sp, #192] + stp x26, x27, [sp, #208] + stp x28, x29, [sp, #224] + str x30, [sp, #240] + mrs x0, SP_EL0 + str x0, [sp, #FRAME_SP_EL0] + mrs x0, ELR_EL1 + str x0, [sp, #FRAME_ELR] + mrs x0, SPSR_EL1 + str x0, [sp, #FRAME_SPSR] + mrs x0, ESR_EL1 + str x0, [sp, #FRAME_ESR] + mrs x0, FAR_EL1 + str x0, [sp, #FRAME_FAR] + mrs x0, TPIDRRO_EL0 + str x0, [sp, #FRAME_SIZE] + msr TPIDRRO_EL0, xzr + mov x0, sp + bl wt_spm_lower_sync + ldr x0, [sp, #FRAME_SPSR] + msr SPSR_EL1, x0 + ldr x0, [sp, #FRAME_ELR] + msr ELR_EL1, x0 + ldr x0, [sp, #FRAME_SP_EL0] + msr SP_EL0, x0 + ldr x0, [sp, #FRAME_SIZE] + msr TPIDRRO_EL0, x0 + ldr x30, [sp, #240] + ldp x28, x29, [sp, #224] + ldp x26, x27, [sp, #208] + ldp x24, x25, [sp, #192] + ldp x22, x23, [sp, #176] + ldp x20, x21, [sp, #160] + ldp x18, x19, [sp, #144] + ldp x16, x17, [sp, #128] + ldp x14, x15, [sp, #112] + ldp x12, x13, [sp, #96] + ldp x10, x11, [sp, #80] + ldp x8, x9, [sp, #64] + ldp x6, x7, [sp, #48] + ldp x4, x5, [sp, #32] + ldp x2, x3, [sp, #16] + ldp x0, x1, [sp, #0] + add sp, sp, #FRAME_STACK + eret + +/* Lower-EL FIQ (a Group 0 interrupt while an S-EL0 partition runs): build the + * same frame the sync path does and call wt_spm_lower_fiq. It returns here to + * resume the partition when nothing preempts it; when the tick preempts, it + * unwinds through wt_sp_el0_leave to the scheduler and never comes back. */ + .section .text.wt_sp_lower_fiq_entry, "ax" + .globl wt_sp_lower_fiq_entry +wt_sp_lower_fiq_entry: + sub sp, sp, #FRAME_STACK + stp x0, x1, [sp, #0] + stp x2, x3, [sp, #16] + stp x4, x5, [sp, #32] + stp x6, x7, [sp, #48] + stp x8, x9, [sp, #64] + stp x10, x11, [sp, #80] + stp x12, x13, [sp, #96] + stp x14, x15, [sp, #112] + stp x16, x17, [sp, #128] + stp x18, x19, [sp, #144] + stp x20, x21, [sp, #160] + stp x22, x23, [sp, #176] + stp x24, x25, [sp, #192] + stp x26, x27, [sp, #208] + stp x28, x29, [sp, #224] + str x30, [sp, #240] + mrs x0, SP_EL0 + str x0, [sp, #FRAME_SP_EL0] + mrs x0, ELR_EL1 + str x0, [sp, #FRAME_ELR] + mrs x0, SPSR_EL1 + str x0, [sp, #FRAME_SPSR] + mrs x0, ESR_EL1 + str x0, [sp, #FRAME_ESR] + mrs x0, FAR_EL1 + str x0, [sp, #FRAME_FAR] + mrs x0, TPIDRRO_EL0 + str x0, [sp, #FRAME_SIZE] + msr TPIDRRO_EL0, xzr + mov x0, sp + bl wt_spm_lower_fiq + ldr x0, [sp, #FRAME_SPSR] + msr SPSR_EL1, x0 + ldr x0, [sp, #FRAME_ELR] + msr ELR_EL1, x0 + ldr x0, [sp, #FRAME_SP_EL0] + msr SP_EL0, x0 + ldr x0, [sp, #FRAME_SIZE] + msr TPIDRRO_EL0, x0 + ldr x30, [sp, #240] + ldp x28, x29, [sp, #224] + ldp x26, x27, [sp, #208] + ldp x24, x25, [sp, #192] + ldp x22, x23, [sp, #176] + ldp x20, x21, [sp, #160] + ldp x18, x19, [sp, #144] + ldp x16, x17, [sp, #128] + ldp x14, x15, [sp, #112] + ldp x12, x13, [sp, #96] + ldp x10, x11, [sp, #80] + ldp x8, x9, [sp, #64] + ldp x6, x7, [sp, #48] + ldp x4, x5, [sp, #32] + ldp x2, x3, [sp, #16] + ldp x0, x1, [sp, #0] + add sp, sp, #FRAME_STACK + eret + +/* Lower-EL IRQ (a Normal-world Group 1 interrupt while an S-EL0 partition + * runs): the same frame, handed to wt_spm_lower_irq, which preempts the + * partition so the Normal world can take its interrupt. */ + .section .text.wt_sp_lower_irq_entry, "ax" + .globl wt_sp_lower_irq_entry +wt_sp_lower_irq_entry: + sub sp, sp, #FRAME_STACK + stp x0, x1, [sp, #0] + stp x2, x3, [sp, #16] + stp x4, x5, [sp, #32] + stp x6, x7, [sp, #48] + stp x8, x9, [sp, #64] + stp x10, x11, [sp, #80] + stp x12, x13, [sp, #96] + stp x14, x15, [sp, #112] + stp x16, x17, [sp, #128] + stp x18, x19, [sp, #144] + stp x20, x21, [sp, #160] + stp x22, x23, [sp, #176] + stp x24, x25, [sp, #192] + stp x26, x27, [sp, #208] + stp x28, x29, [sp, #224] + str x30, [sp, #240] + mrs x0, SP_EL0 + str x0, [sp, #FRAME_SP_EL0] + mrs x0, ELR_EL1 + str x0, [sp, #FRAME_ELR] + mrs x0, SPSR_EL1 + str x0, [sp, #FRAME_SPSR] + mrs x0, ESR_EL1 + str x0, [sp, #FRAME_ESR] + mrs x0, FAR_EL1 + str x0, [sp, #FRAME_FAR] + mrs x0, TPIDRRO_EL0 + str x0, [sp, #FRAME_SIZE] + msr TPIDRRO_EL0, xzr + mov x0, sp + bl wt_spm_lower_irq + ldr x0, [sp, #FRAME_SPSR] + msr SPSR_EL1, x0 + ldr x0, [sp, #FRAME_ELR] + msr ELR_EL1, x0 + ldr x0, [sp, #FRAME_SP_EL0] + msr SP_EL0, x0 + ldr x0, [sp, #FRAME_SIZE] + msr TPIDRRO_EL0, x0 + ldr x30, [sp, #240] + ldp x28, x29, [sp, #224] + ldp x26, x27, [sp, #208] + ldp x24, x25, [sp, #192] + ldp x22, x23, [sp, #176] + ldp x20, x21, [sp, #160] + ldp x18, x19, [sp, #144] + ldp x16, x17, [sp, #128] + ldp x14, x15, [sp, #112] + ldp x12, x13, [sp, #96] + ldp x10, x11, [sp, #80] + ldp x8, x9, [sp, #64] + ldp x6, x7, [sp, #48] + ldp x4, x5, [sp, #32] + ldp x2, x3, [sp, #16] + ldp x0, x1, [sp, #0] + add sp, sp, #FRAME_STACK + eret + +/* S-EL0 spinner for the preemption self-test: loops until a tick preempts it. */ + .section .text.wt_sp_spin, "ax" + .globl wt_sp_spin +wt_sp_spin: +1: b 1b + +/* S-EL0 FF-A discovery partition: FFA_PARTITION_INFO_GET with a Nil UUID lists + * every configured partition into the RX buffer (its base is x0 on entry). Read + * the match count from w2 and the first descriptor's partition id from the RX + * buffer, and yield both to the SPMC self-test in one token (id in bits 31:16, + * count in bits 15:0). FFA_RXTX_UNMAP and FFA_RX_RELEASE naming its own id are + * then INVALID_PARAMETERS (MBZ at this instance, Tables 13.21 and 13.30) and + * the RX buffer is released with w1 clear. Standing in for a PSA partition, + * which takes no notifications, FFA_FEATURES of FFA_NOTIFICATION_GET and the + * call itself are NOT_SUPPORTED (10.7 rules 5, 6); any other answer yields + * count 0. */ + .section .text.wt_sp_ffa_discover, "ax" + .globl wt_sp_ffa_discover +wt_sp_ffa_discover: + mov x19, x0 /* saved RX base */ + movz w0, #0x0068 /* FFA_PARTITION_INFO_GET (0x84000068) */ + movk w0, #0x8400, lsl #16 + mov x1, #0 /* Nil UUID in w1-w4 */ + mov x2, #0 + mov x3, #0 + mov x4, #0 + mov x5, #0 /* flags: full descriptors */ + svc #0 + uxth w20, w2 /* match count (w2) */ + ldrh w21, [x19] /* first descriptor's partition id */ + movz w22, #0x0060 /* FFA_ERROR (0x84000060) */ + movk w22, #0x8400, lsl #16 + movz w0, #0x0067 /* FFA_RXTX_UNMAP (0x84000067) */ + movk w0, #0x8400, lsl #16 + lsl w1, w21, #16 + svc #0 + cmp w0, w22 + ccmn w2, #2, #0, eq /* INVALID_PARAMETERS (-2) */ + csel w20, w20, wzr, eq + movz w0, #0x0065 /* FFA_RX_RELEASE (0x84000065) */ + movk w0, #0x8400, lsl #16 + mov w1, w21 + svc #0 + cmp w0, w22 + ccmn w2, #2, #0, eq + csel w20, w20, wzr, eq + movz w0, #0x0065 + movk w0, #0x8400, lsl #16 + mov w1, #0 + svc #0 + add w23, w22, #1 /* FFA_SUCCESS32 (0x84000061) */ + cmp w0, w23 + csel w20, w20, wzr, eq + movz w0, #0x0064 /* FFA_FEATURES (0x84000064) */ + movk w0, #0x8400, lsl #16 + movz w1, #0x0082 /* FFA_NOTIFICATION_GET (0x84000082) */ + movk w1, #0x8400, lsl #16 + svc #0 + cmp w0, w22 + ccmn w2, #1, #0, eq /* NOT_SUPPORTED (-1) */ + csel w20, w20, wzr, eq + movz w0, #0x0082 + movk w0, #0x8400, lsl #16 + mov w1, w21 /* its own id, vCPU 0 */ + mov w2, #1 /* the partitions' bitmap */ + svc #0 + cmp w0, w22 + ccmn w2, #1, #0, eq + csel w20, w20, wzr, eq + orr x1, x20, x21, lsl #16 /* token = first_id<<16 | count */ + movz w0, #0x0101 /* WT_SPM_SVC_FID_YIELD (0xC3000101) */ + movk w0, #0xC300, lsl #16 + svc #0 +1: b 1b + +/* S-EL0 memory-sharing borrower: x0 = argument block {handle, shared page base, + * TX base, retrieve request length, own id}. FFA_MEM_RETRIEVE_REQ (request + * pre-written in TX by the SPMC) maps the shared page; read its four seeded + * bytes, write a reply byte through the mapping, and yield the bytes. Then + * build a relinquish descriptor in TX, FFA_MEM_RELINQUISH, and yield the + * status. A failed retrieve yields its status straight away. */ + .section .text.wt_sp_ffa_borrow, "ax" + .globl wt_sp_ffa_borrow +wt_sp_ffa_borrow: + mov x19, x0 + ldr x20, [x19] /* handle */ + ldr x21, [x19, #8] /* shared page base */ + ldr x22, [x19, #16] /* TX base */ + ldr x23, [x19, #24] /* retrieve request length */ + movz w0, #0x0074 /* FFA_MEM_RETRIEVE_REQ32 (0x84000074) */ + movk w0, #0x8400, lsl #16 + mov x1, x23 /* total length */ + mov x2, x23 /* fragment length */ + mov x3, #0 /* descriptor in the TX buffer */ + mov x4, #0 + svc #0 + movz w4, #0x0075 /* FFA_MEM_RETRIEVE_RESP (0x84000075) */ + movk w4, #0x8400, lsl #16 + cmp w0, w4 + b.ne 2f + ldr w1, [x21] /* the four seeded bytes */ + mov w5, #0xEE + strb w5, [x21, #4] /* reply written through the mapping */ + movz w0, #0x0101 /* WT_SPM_SVC_FID_YIELD (0xC3000101) */ + movk w0, #0xC300, lsl #16 + svc #0 + str x20, [x22] /* relinquish descriptor: handle */ + str wzr, [x22, #8] /* flags */ + mov w5, #1 + str w5, [x22, #12] /* endpoint count */ + ldr x5, [x19, #32] + strh w5, [x22, #16] /* endpoint: own id */ + movz w0, #0x0076 /* FFA_MEM_RELINQUISH (0x84000076) */ + movk w0, #0x8400, lsl #16 + svc #0 +2: mov x1, x0 /* yield the FF-A status */ + movz w0, #0x0101 + movk w0, #0xC300, lsl #16 + svc #0 +1: b 1b + +/* S-EL0 probe for the boot self-test of failed initialization (8.5 rule 3): + * FFA_ERROR with w1 set, then with no error code in w2, must each come back + * INVALID_PARAMETERS; FFA_ERROR(NO_MEMORY) then reports the failure and must + * never return. Anything else yields the token 0xBAD. */ + .section .text.wt_sp_ffa_init_fail, "ax" + .globl wt_sp_ffa_init_fail +wt_sp_ffa_init_fail: + movz w0, #0x0060 /* FFA_ERROR (0x84000060) */ + movk w0, #0x8400, lsl #16 + mov x1, #1 /* w1 MBZ at this instance */ + movn w2, #2 /* NO_MEMORY (-3) */ + svc #0 + movz w4, #0x0060 + movk w4, #0x8400, lsl #16 + cmp w0, w4 + b.ne 9f + cmn w2, #2 /* INVALID_PARAMETERS (-2) */ + b.ne 9f + movz w0, #0x0060 + movk w0, #0x8400, lsl #16 + mov x1, #0 + mov x2, #0 /* not an error code */ + svc #0 + movz w4, #0x0060 + movk w4, #0x8400, lsl #16 + cmp w0, w4 + b.ne 9f + cmn w2, #2 + b.ne 9f + movz w0, #0x0060 + movk w0, #0x8400, lsl #16 + mov x1, #0 + movn w2, #2 + svc #0 +9: mov x1, #0xBAD + movz w0, #0x0101 /* WT_SPM_SVC_FID_YIELD (0xC3000101) */ + movk w0, #0xC300, lsl #16 + svc #0 +1: b 1b + +/* S-EL0 probe for the boot self-test: yield twice with distinct tokens. */ + .section .text.wt_sp_el0_probe, "ax" + .globl wt_sp_el0_probe +wt_sp_el0_probe: + mov w0, #0x0101 + movk w0, #0xC300, lsl #16 + mov x1, #0x5A + svc #0 + mov w0, #0x0101 + movk w0, #0xC300, lsl #16 + mov x1, #0xA5 + svc #0 +1: b 1b + +/* S-EL0 FF-A yielding partition: FFA_MSG_WAIT to signal init and wait, then + * for each delivered direct request FFA_YIELD (w1-w3 MBZ, Table 14.9), and once + * FFA_RUN resumes it reply with FFA_MSG_SEND_DIRECT_RESP32, swapping the ids + * and complementing the first payload word, as the echo partition does. */ + .section .text.wt_sp_ffa_yield, "ax" + .globl wt_sp_ffa_yield +wt_sp_ffa_yield: + movz w0, #0x006B /* FFA_MSG_WAIT (0x8400006B) */ + movk w0, #0x8400, lsl #16 + svc #0 +1: mov x19, x1 /* sender/receiver ids of the request */ + mov x20, x3 /* its first payload word */ + movz w0, #0x006C /* FFA_YIELD (0x8400006C) */ + movk w0, #0x8400, lsl #16 + mov x1, #0 + mov x2, #0 + mov x3, #0 + mov x4, #0 + mov x5, #0 + mov x6, #0 + mov x7, #0 + svc #0 + lsr x2, x19, #16 /* original sender */ + ubfx x1, x19, #0, #16 /* original receiver = our id */ + orr x1, x2, x1, lsl #16 /* reply w1 = our_id<<16 | sender */ + mvn x3, x20 /* echo ~payload */ + mov x2, #0 /* w2 SBZ */ + movz w0, #0x0070 /* FFA_MSG_SEND_DIRECT_RESP32 (0x84000070) */ + movk w0, #0x8400, lsl #16 + svc #0 + b 1b + +/* S-EL0 FF-A echo partition: FFA_MSG_WAIT to signal init and wait, then for + * each delivered direct request (x0=REQ fid, x1=sender/receiver ids, x3=first + * payload word) reply with FFA_MSG_SEND_DIRECT_RESP32, swapping the ids and + * complementing the payload word. A delivered FFA_INTERRUPT (x0) is a Secure + * interrupt: acknowledge it by returning to waiting. Its w1/w2 are MBZ for an + * S-EL0 partition (12.4.1 item 3), so a nonzero one faults the partition. The + * response SVC also waits for the next request, so control resumes at label 1 + * with it. Before its first wait it checks that an SVC32 call is read from + * w1-w7 alone (FFA_MEM_PERM_GET32 on its own stack page, with junk above the + * address in x1, must succeed) and that the SPMC, which scheduled its init, + * refuses its FFA_YIELD with DENIED (8.5 rule 4); either failing faults it. + * A request whose first payload word is 0x5CCE is completed with FFA_SUCCESS + * instead of a response (15.2), after checking that one with a nonzero w3 + * (MBZ) is refused. */ + .section .text.wt_sp_ffa_echo, "ax" + .globl wt_sp_ffa_echo +wt_sp_ffa_echo: + mov x1, sp + sub x1, x1, #1 + and x1, x1, #0xFFFFFFFFFFFFF000 /* the stack's top page */ + movk x1, #0xDEAD, lsl #48 /* upper half, not an argument */ + movz w0, #0x0088 /* FFA_MEM_PERM_GET32 (0x84000088) */ + movk w0, #0x8400, lsl #16 + svc #0 + movz w4, #0x0061 /* FFA_SUCCESS32 (0x84000061) */ + movk w4, #0x8400, lsl #16 + cmp w0, w4 + b.ne 9f + movz w0, #0x006C /* FFA_YIELD (0x8400006C) */ + movk w0, #0x8400, lsl #16 + svc #0 + movz w4, #0x0060 /* FFA_ERROR (0x84000060) */ + movk w4, #0x8400, lsl #16 + cmp w0, w4 + b.ne 9f + cmn w2, #6 /* DENIED (-6) */ + b.eq 0f +9: udf #0 +0: movz w0, #0x006B /* FFA_MSG_WAIT (0x8400006B) */ + movk w0, #0x8400, lsl #16 + svc #0 +1: movz w4, #0x0062 /* FFA_INTERRUPT (0x84000062) */ + movk w4, #0x8400, lsl #16 + cmp w0, w4 + b.ne 4f + orr w5, w1, w2 + cbz w5, 0b /* interrupt: acknowledge and re-wait */ + udf #0 +4: movz w4, #0x006D /* FFA_RUN (0x8400006D) */ + movk w4, #0x8400, lsl #16 + cmp w0, w4 + b.eq 0b /* cycles with no request: re-wait */ +2: movz w5, #0x5CCE + cmp w3, w5 + b.ne 3f + movz w0, #0x0061 /* FFA_SUCCESS32 (0x84000061) */ + movk w0, #0x8400, lsl #16 + mov x1, #0 + mov x2, #0 + mov x3, #1 /* MBZ: must be refused */ + mov x4, #0 + mov x5, #0 + mov x6, #0 + mov x7, #0 + svc #0 + movz w4, #0x0060 /* FFA_ERROR (0x84000060) */ + movk w4, #0x8400, lsl #16 + cmp w0, w4 + b.ne 9b + cmn w2, #2 /* INVALID_PARAMETERS (-2) */ + b.ne 9b + movz w0, #0x0061 + movk w0, #0x8400, lsl #16 + mov x1, #0 + mov x2, #0 + mov x3, #0 + mov x4, #0 + mov x5, #0 + mov x6, #0 + mov x7, #0 + svc #0 + b 1b +3: lsr x2, x1, #16 /* original sender */ + ubfx x1, x1, #0, #16 /* original receiver = our id */ + orr x1, x2, x1, lsl #16 /* reply w1 = our_id<<16 | sender */ + mvn x3, x3 /* echo ~payload */ + mov x2, #0 /* w2 SBZ */ + movz w0, #0x0070 /* FFA_MSG_SEND_DIRECT_RESP32 (0x84000070) */ + movk w0, #0x8400, lsl #16 + svc #0 + b 1b diff --git a/src/arch/aarch64/spm/sp_trap.c b/src/arch/aarch64/spm/sp_trap.c new file mode 100644 index 00000000..d0a99e28 --- /dev/null +++ b/src/arch/aarch64/spm/sp_trap.c @@ -0,0 +1,121 @@ +/* sp_trap.c + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfTrust. + * + * wolfTrust 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. + * + * wolfTrust 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, see . + */ + +/* SP-side half of the SVC gate: the partition-message hypercall and the + * deliberate faults the gate contract owes the architecture. */ + +#include "wolftrust/arch/aarch64/spm_svc.h" +#include "wolftrust/arch.h" +#include "wolftrust/spm_gate.h" + +#include + +int wt_arch_sp_trap(wt_spm_call_t* call) +{ + register uint64_t x0 __asm__("x0") = WT_SPM_SVC_FID_CALL; + register uint64_t x1 __asm__("x1") = (uint64_t)(uintptr_t)call; + register uint64_t x8 __asm__("x8") = (uint64_t)call->op; + + __asm__ volatile("svc #0" : "+r"(x0) : "r"(x1), "r"(x8) : "memory"); + return (int)(int32_t)x0; +} + +/* Landing pad for an FF-M PROGRAMMER ERROR: a permanently undefined + * instruction at EL0 takes the graceful quarantine path (EC 0x00). */ +__attribute__((naked, used)) +void wt_spm_sp_panic_trap(void) +{ + __asm__ volatile(".inst 0x00000000"); +} + +#if defined(WT_SVC_NEG_PROBE) && (WT_SVC_NEG_PROBE == 1) +void wt_arch_sp_guest_return_probe(void) +{ + register uint64_t x0 __asm__("x0") = WT_SPM_SVC_FID_YIELD; + + __asm__ volatile("svc #0" : "+r"(x0) : : "memory"); +} +#endif + +void wt_arch_sp_fault_probe(unsigned int code) +{ +#if defined(WT_FP_NEG_PROBE) && (WT_FP_NEG_PROBE == 1) + /* fpneg: CPACR_EL1 traps FP at S-EL0, so this fmov d0, x0 (raw, as + * -mgeneral-regs-only refuses it) must fault; the brk marks an escape. */ + __asm__ volatile(".inst 0x9e670000"); + __asm__ volatile("brk #0x4e"); +#endif + switch (code) { + case 1u: + __asm__ volatile("brk #1"); + break; + case 2u: + __asm__ volatile("brk #2"); + break; + case 3u: + __asm__ volatile("brk #3"); + break; + case 4u: + __asm__ volatile("brk #4"); + break; + default: + __asm__ volatile(".inst 0x00000000"); + break; + } +} + +/* Pin the diagnostics into callee-saved registers the fault dump shows. */ +__attribute__((noreturn, noinline)) +void wt_arch_sp_panic(uint32_t op, uint32_t code, uint32_t extra) +{ + register uint64_t diag_op __asm__("x19") = op; + register uint64_t diag_code __asm__("x20") = code; + register uint64_t diag_extra __asm__("x21") = extra; + + __asm__ volatile("brk #0xF0" : : "r"(diag_op), "r"(diag_code), + "r"(diag_extra)); + for (;;) { + } +} + +__attribute__((noinline)) +void wt_arch_diag_trap(uint32_t a, uint32_t b, uint32_t c) +{ + register uint64_t diag_a __asm__("x19") = a; + register uint64_t diag_b __asm__("x20") = b; + register uint64_t diag_c __asm__("x21") = c; + +#if defined(WT_CONF_DIAG_TRAP) && (WT_CONF_DIAG_TRAP == 0) + (void)diag_a; + (void)diag_b; + (void)diag_c; +#else + __asm__ volatile("brk #0xF1" : : "r"(diag_a), "r"(diag_b), "r"(diag_c)); +#endif +} + +/* Every lower-EL entry parks and clears the id, so S-EL1 reads zero. */ +int wt_arch_thread_unprivileged(void) +{ + uint64_t id; + + __asm__ volatile("mrs %0, TPIDRRO_EL0" : "=r"(id)); + return (id != 0u) ? 1 : 0; +} diff --git a/src/arch/aarch64/spm/spm_entry.S b/src/arch/aarch64/spm/spm_entry.S new file mode 100644 index 00000000..12e96ada --- /dev/null +++ b/src/arch/aarch64/spm/spm_entry.S @@ -0,0 +1,141 @@ +/* spm_entry.S + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfTrust. + * + * wolfTrust 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. + * + * wolfTrust 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, see . + */ + +/* Secure EL1 entry the monitor drops into with x0 = FF-A boot information + * blob: point VBAR_EL1 at a table that reports every exception to EL3, take + * the SPM stack, call wt_spm_main(blob). */ + +#define WT_MON_FID_PANIC 0xC3000002 + +/* Panic code 0xE0 + slot names the vector that fired. */ + .macro WT_SPM_VECTOR_PANIC slot + .balign 0x80 + ldr x0, =WT_MON_FID_PANIC + mov x1, #(0xE0 + \slot) + smc #0 +1: wfi + b 1b + .endm + + .macro WT_SPM_VECTOR_BRANCH target + .balign 0x80 + b \target + .endm + +/* Copy [x10, x11) from x9 and zero [x12, x13), 16 bytes at a time; the + * linker script aligns every band edge to 16. */ + .macro WT_SPM_INIT_BAND lma, start, end, bss_start, bss_end + ldr x9, =\lma + ldr x10, =\start + ldr x11, =\end +1: cmp x10, x11 + b.hs 2f + ldp x14, x15, [x9], #16 + stp x14, x15, [x10], #16 + b 1b +2: ldr x12, =\bss_start + ldr x13, =\bss_end +3: cmp x12, x13 + b.hs 4f + stp xzr, xzr, [x12], #16 + b 3b +4: + .endm + + .section .text.wt_spm_entry, "ax" + .globl wt_spm_entry +wt_spm_entry: + mov x19, x0 + ldr x9, =wt_spm_vectors + msr VBAR_EL1, x9 + ldr x9, =__spm_stack_top + mov sp, x9 + msr TPIDRRO_EL0, xzr + /* Trap FP/SIMD, SVE, and SME at S-EL1/S-EL0 so the Normal world's vector + * state is never read or clobbered across a world switch. */ + msr CPACR_EL1, xzr + isb + WT_SPM_INIT_BAND __data_lma, __data_start, __data_end, __bss_start, __bss_end + WT_SPM_INIT_BAND _si_vault, _s_vault, _e_vault_data, _s_vault_bss, _e_vault + WT_SPM_INIT_BAND _si_attest, _s_attest, _e_attest_data, _s_attest_bss, _e_attest + WT_SPM_INIT_BAND _si_hsm, _s_hsm, _e_hsm_data, _s_hsm_bss, _e_hsm + WT_SPM_INIT_BAND _si_conf_data, _s_conf_data, _e_conf_data_data, _s_conf_bss, _e_conf_bss + mov x0, x19 + bl wt_spm_main +2: wfi + b 2b + +/* Current-EL SPx FIQ (slot 6): Group 0 interrupts reach the SPMC while the + * Secure world runs; save the caller-saved state and run wt_spm_fiq. */ + .section .text.wt_spm_fiq_entry, "ax" +wt_spm_fiq_entry: + sub sp, sp, #192 + stp x0, x1, [sp, #0] + stp x2, x3, [sp, #16] + stp x4, x5, [sp, #32] + stp x6, x7, [sp, #48] + stp x8, x9, [sp, #64] + stp x10, x11, [sp, #80] + stp x12, x13, [sp, #96] + stp x14, x15, [sp, #112] + stp x16, x17, [sp, #128] + stp x18, x29, [sp, #144] + mrs x0, ELR_EL1 + mrs x1, SPSR_EL1 + stp x30, x0, [sp, #160] + str x1, [sp, #176] + bl wt_spm_fiq + ldr x1, [sp, #176] + ldp x30, x0, [sp, #160] + msr ELR_EL1, x0 + msr SPSR_EL1, x1 + ldp x18, x29, [sp, #144] + ldp x16, x17, [sp, #128] + ldp x14, x15, [sp, #112] + ldp x12, x13, [sp, #96] + ldp x10, x11, [sp, #80] + ldp x8, x9, [sp, #64] + ldp x6, x7, [sp, #48] + ldp x4, x5, [sp, #32] + ldp x2, x3, [sp, #16] + ldp x0, x1, [sp, #0] + add sp, sp, #192 + eret + + .section .text.wt_spm_vectors, "ax" + .balign 0x800 + .globl wt_spm_vectors +wt_spm_vectors: + WT_SPM_VECTOR_PANIC 0 + WT_SPM_VECTOR_PANIC 1 + WT_SPM_VECTOR_PANIC 2 + WT_SPM_VECTOR_PANIC 3 + WT_SPM_VECTOR_PANIC 4 + WT_SPM_VECTOR_PANIC 5 + WT_SPM_VECTOR_BRANCH wt_spm_fiq_entry + WT_SPM_VECTOR_PANIC 7 + WT_SPM_VECTOR_BRANCH wt_sp_lower_sync_entry + WT_SPM_VECTOR_BRANCH wt_sp_lower_irq_entry + WT_SPM_VECTOR_BRANCH wt_sp_lower_fiq_entry + WT_SPM_VECTOR_PANIC 11 + WT_SPM_VECTOR_PANIC 12 + WT_SPM_VECTOR_PANIC 13 + WT_SPM_VECTOR_PANIC 14 + WT_SPM_VECTOR_PANIC 15 diff --git a/src/arch/aarch64/spm/spm_irq.c b/src/arch/aarch64/spm/spm_irq.c new file mode 100644 index 00000000..ab612bed --- /dev/null +++ b/src/arch/aarch64/spm/spm_irq.c @@ -0,0 +1,416 @@ +/* spm_irq.c + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfTrust. + * + * wolfTrust 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. + * + * wolfTrust 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, see . + */ + +/* S-EL1 Group 0 interrupt handling for the SPMC (DEN0077A Ch.9: every + * interrupt reaches the SPMC while the Secure world runs). The secure timer + * is the only source until the partitions arrive. */ + +#include "wolftrust/arch/aarch64/el3.h" +#include "wolftrust/arch/aarch64/gic.h" +#include "wolftrust/arch/aarch64/spm_svc.h" +#include "wolftrust/arch/aarch64/sysreg.h" +#include "wolftrust/spm_transport.h" + +#include + +#define WT_SPM_TICK_PERIOD_MS 10u +#define WT_SPM_TWDOG_PERIOD_MS 1u +#define WT_SPM_TICK_WAIT_MS 100u +/* A shared-peripheral interrupt id used only by the secure-interrupt tests. */ +#define WT_SPM_TEST_SPI 40u + +volatile uint32_t g_wt_spm_tick_intid; + +void wt_spm_fiq(void); +void wt_spm_lower_fiq(wt_trap_frame_t* frame); +int wt_spm_prove_tick(void); +uint32_t wt_spm_prove_sint(void); + +static uint32_t ack_group0_tick(void) +{ + uint32_t intid = wt_gic->ack_group0(); + + if (intid == WT_GIC_INTID_SECURE_TIMER) { + wt_el3_timer_disable(); + } + if (intid != WT_GIC_INTID_SPURIOUS) { + g_wt_spm_tick_intid = intid; + wt_gic->eoi_group0(intid); + } + return intid; +} + +int wt_spm_sint_fifo_push(wt_spm_sint_fifo_t* q, uint32_t intid) +{ + uint32_t i; + + if ((q == NULL) || (intid == 0u)) { + return -1; + } + for (i = 0u; i < q->count; i++) { + if (q->intid[i] == intid) { + return 0; + } + } + if (q->count >= WT_SPM_SINT_QUEUE_MAX) { + return -1; + } + q->intid[q->count] = intid; + q->count++; + return 0; +} + +uint32_t wt_spm_sint_fifo_pop(wt_spm_sint_fifo_t* q) +{ + uint32_t intid; + uint32_t i; + + if ((q == NULL) || (q->count == 0u)) { + return 0u; + } + intid = q->intid[0]; + for (i = 1u; i < q->count; i++) { + q->intid[i - 1u] = q->intid[i]; + } + q->count--; + q->intid[q->count] = 0u; + return intid; +} + +/* The Secure interrupt that preempted the Normal world: the SPMD hands it over + * still pending, so the SPMC acknowledges it here. Returns its id, or + * WT_GIC_INTID_SPURIOUS when none is pending. */ +uint32_t wt_spm_ns_sint_take(void) +{ + return ack_group0_tick(); +} + +/* Lower-EL FIQ: an S-EL0 partition was running. The scheduling tick preempts + * it (an NS-Int, DEV-04) and the handler does not return here; any other + * declared Secure interrupt is queued for the partition and delivered as + * FFA_INTERRUPT on its next FFA_MSG_WAIT (Table 9.1). */ +/* A Group 0 interrupt other than the tick: a manifest-declared partition + * interrupt becomes that partition's FF-M signal (consumed by psa_wait and + * released by psa_eoi); the FF-A test SPI is queued for its waiting endpoint. */ +static void wt_spm_declared_irq(uint32_t intid, wt_trap_frame_t* frame) +{ + struct wt_co* owner = wt_spm_sint_owner(intid); + + /* An interrupt a partition claimed through the para-virtual enable is + * queued for that owner; an owner that is waiting is signaled, so the + * partition that was running is preempted to let the SPMC do it. */ + if (owner != NULL) { + wt_spm_sint_queue_for(owner, intid); + if ((wt_spm_sint_signal_needed(owner) != 0) && (frame != NULL)) { + wt_spm_preempt_from_fiq(frame); + } + return; + } +#if defined(WT_CONFORMANCE) && (WT_CONFORMANCE == 1) + if (intid != WT_SPM_TEST_SPI) { + wt_spm_conf_irq(intid); + return; + } +#endif + wt_spm_sint_queue(intid); +} + +/* Current-EL FIQ: the SPMC itself was running (only the boot proofs unmask + * FIQ at S-EL1), so acknowledge and resume; a declared interrupt still takes + * the routing above, with no partition to preempt. */ +void wt_spm_fiq(void) +{ + uint32_t intid = ack_group0_tick(); + + if ((intid != WT_GIC_INTID_SECURE_TIMER) && + (intid != WT_GIC_INTID_SPURIOUS)) { + wt_spm_declared_irq(intid, NULL); + } +} + +void wt_spm_lower_fiq(wt_trap_frame_t* frame) +{ + uint32_t intid = ack_group0_tick(); + + if (intid == WT_GIC_INTID_SECURE_TIMER) { + wt_spm_twdog_tick(); + wt_spm_preempt_from_fiq(frame); + } + else if (intid != WT_GIC_INTID_SPURIOUS) { + wt_spm_declared_irq(intid, frame); + } + else if (wt_gic->version == 3u) { + /* GICv3 signals a Group 1 Non-secure interrupt as FIQ while the PE is + * Secure; nothing Group 0 is pending, so it is the Normal world's. */ + wt_spm_preempt_from_irq(frame); + } +} + +/* A Normal-world Group 1 interrupt asserted while an S-EL0 partition ran: + * hand the CPU back so the Normal world can take it (Ch.9). Nothing is + * acknowledged here; the interrupt is not this world's. */ +void wt_spm_lower_irq(wt_trap_frame_t* frame) +{ + wt_spm_preempt_from_irq(frame); +} + +/* The test-timer service of the ACS platform layer, one slot per armed + * interrupt on the shared secure timer, bound to whoever armed it. A + * partition's expires at its deadline whatever runs, and the declared routing + * above delivers it by the owner's state, as long as its arming partition + * still owns the interrupt; a Normal-world one stands for a peripheral that + * fires while a partition works, so it is made pending on the first tick at or + * past its deadline that lands on a partition. */ +#define WT_SPM_TWDOG_SLOTS 4u + +static uint32_t g_twdog_intid[WT_SPM_TWDOG_SLOTS]; +static uint64_t g_twdog_deadline[WT_SPM_TWDOG_SLOTS]; +/* The partition that armed each slot, NULL for the Normal world. */ +static const struct wt_co* g_twdog_owner[WT_SPM_TWDOG_SLOTS]; + +int wt_spm_native_declares(const wt_ffa_native_sp_t* sp, uint32_t intid) +{ + uint32_t i; + + if ((sp == NULL) || (sp->intid_count > WT_FFA_NATIVE_SP_INTIDS)) { + return 0; + } + for (i = 0u; i < sp->intid_count; i++) { + if (sp->intids[i] == intid) { + return 1; + } + } + return 0; +} + +/* A partition's timer raises only an interrupt it owns; the Normal world's + * only an SPI no partition owns or may claim. */ +static int twdog_arm_allowed(const struct wt_co* caller, uint32_t intid) +{ + if ((intid < 32u) || (intid >= WT_GIC_INTID_LIMIT)) { + return 0; + } + if (caller != NULL) { + return (wt_spm_sint_owner(intid) == caller) ? 1 : 0; + } + return ((wt_spm_sint_owner(intid) == NULL) && + (wt_spm_sint_declared_any(intid) == 0)) ? 1 : 0; +} + +int wt_spm_twdog_arm(const struct wt_co* caller, uint32_t intid, uint32_t ms) +{ + unsigned int slot = WT_SPM_TWDOG_SLOTS; + unsigned int i; + + if (twdog_arm_allowed(caller, intid) == 0) { + return -1; + } + for (i = 0u; i < WT_SPM_TWDOG_SLOTS; i++) { + if (g_twdog_intid[i] == intid) { + slot = i; + } + } + for (i = 0u; (i < WT_SPM_TWDOG_SLOTS) && (slot == WT_SPM_TWDOG_SLOTS); i++) { + if (g_twdog_intid[i] == 0u) { + slot = i; + } + } + if ((intid == 0u) || (intid >= WT_GIC_INTID_LIMIT) || + (slot == WT_SPM_TWDOG_SLOTS)) { + return -1; + } + g_twdog_deadline[slot] = wt_read_cntpct_el0() + + ((wt_read_cntfrq_el0() * (uint64_t)ms) / 1000u); + g_twdog_intid[slot] = intid; + g_twdog_owner[slot] = caller; + wt_gic->enable(WT_GIC_INTID_SECURE_TIMER); + wt_el3_timer_arm_ms(WT_SPM_TWDOG_PERIOD_MS); + return 0; +} + +/* Stop the timers the caller armed: a partition's, or with no owner the + * Normal world's. */ +void wt_spm_twdog_stop(const struct wt_co* owner) +{ + unsigned int i; + + for (i = 0u; i < WT_SPM_TWDOG_SLOTS; i++) { + if ((g_twdog_intid[i] != 0u) && (g_twdog_owner[i] == owner)) { + g_twdog_intid[i] = 0u; + } + } +} + +void wt_spm_twdog_tick(void) +{ + uint64_t now = wt_read_cntpct_el0(); + unsigned int armed = 0u; + unsigned int i; + int due; + + for (i = 0u; i < WT_SPM_TWDOG_SLOTS; i++) { + if (g_twdog_intid[i] != 0u) { + if (g_twdog_owner[i] == NULL) { + due = ((now >= g_twdog_deadline[i]) && + (wt_spm_current_is_partition() != 0)) ? 1 : 0; + } + else if (wt_spm_sint_owner(g_twdog_intid[i]) != g_twdog_owner[i]) { + /* Released or reclaimed since it was armed: never raised. */ + g_twdog_intid[i] = 0u; + continue; + } + else { + due = (now >= g_twdog_deadline[i]) ? 1 : 0; + } + if (due != 0) { + wt_gic->set_pending(g_twdog_intid[i]); + g_twdog_intid[i] = 0u; + } + else { + armed = 1u; + } + } + } + if (armed != 0u) { + wt_el3_timer_arm_ms(WT_SPM_TWDOG_PERIOD_MS); + } +} + +/* Arm the secure timer for a preemption tick with S-EL1 FIQ masked, so only + * the running S-EL0 partition takes it (a current-EL tick would consume the + * one-shot before the partition ever runs). */ +void wt_spm_preempt_timer_arm(void) +{ + wt_daif_set_fiq(); + g_wt_spm_tick_intid = 0u; + wt_gic->enable(WT_GIC_INTID_SECURE_TIMER); + wt_el3_timer_arm_ms(WT_SPM_TICK_PERIOD_MS); +} + +void wt_spm_preempt_timer_stop(void) +{ + wt_el3_timer_disable(); + wt_gic->disable(WT_GIC_INTID_SECURE_TIMER); +} + +/* Raise a Secure shared-peripheral interrupt in software and confirm it + * reaches the SPMC as a Group 0 FIQ, so the GIC path a manifest-declared + * Secure interrupt uses is proven before it is routed to a partition. + * Returns the received interrupt id, or 0 if it did not arrive. */ +uint32_t wt_spm_prove_sint(void) +{ + uint64_t deadline = wt_read_cntpct_el0() + + ((wt_read_cntfrq_el0() * WT_SPM_TICK_WAIT_MS) / 1000u); + + g_wt_spm_tick_intid = 0u; + wt_gic->set_group0(WT_SPM_TEST_SPI); + wt_gic->set_priority(WT_SPM_TEST_SPI, 0x00u); + wt_gic->enable(WT_SPM_TEST_SPI); + wt_gic->set_pending(WT_SPM_TEST_SPI); + wt_daif_clear_fiq(); + while ((g_wt_spm_tick_intid == 0u) && (wt_read_cntpct_el0() < deadline)) { + } + wt_daif_set_fiq(); + wt_gic->disable(WT_SPM_TEST_SPI); + return (g_wt_spm_tick_intid == WT_SPM_TEST_SPI) ? WT_SPM_TEST_SPI : 0u; +} + +#if defined(WT_EL3_TEST_DRIVER) && (WT_EL3_TEST_DRIVER == 1) +/* The monitor ABI is host-untranslatable, so the driver alone includes it. */ +#include "wolftrust/arch/aarch64/monitor_abi.h" +#include "wolftrust/sched/coroutine.h" + +/* A Normal-world interrupt: Group 1 with a Non-secure priority. */ +#define WT_SPM_TEST_NS_SPI 41u +#define WT_SPM_TEST_NS_PRIO 0xA0u + +static int test_ns_int_hold(void) +{ + if (wt_mon_call(WT_MON_FID_TEST_NS_GROUP, 1u) != 0u) { + return -1; + } + wt_gic->set_priority(WT_SPM_TEST_NS_SPI, WT_SPM_TEST_NS_PRIO); + wt_gic->enable(WT_SPM_TEST_NS_SPI); + wt_gic->set_pending(WT_SPM_TEST_NS_SPI); + return 0; +} + +static void test_ns_int_release(void) +{ + wt_gic->disable(WT_SPM_TEST_NS_SPI); + (void)wt_mon_call(WT_MON_FID_TEST_NS_GROUP, 0u); +} + +/* Route the test Secure interrupt to a partition both ways (Table 9.1). First + * signalled: raise it while the SPMC runs (proven by wt_spm_prove_sint) so it + * is taken at S-EL1, then hand FFA_INTERRUPT to the waiting owner with a + * Normal-world interrupt pending: the chain the SPMC scheduled keeps it queued + * (9.2.4 rule 3), so the owner finishes and waits again. Then queued: + * raise it with S-EL1 FIQ masked while the owner handles another interrupt (the + * tick's id stands in), so the lower-EL FIQ queues it and the gate delivers it + * on the owner's next FFA_MSG_WAIT; the get must then name it, not the tick. */ +void wt_spm_prove_sint_route(struct wt_co* co) +{ + if (co == NULL) { + return; + } + if ((wt_spm_prove_sint() == WT_SPM_TEST_SPI) && + (test_ns_int_hold() == 0) && + (wt_spm_ffa_signal_deliver(co, WT_SPM_TEST_SPI) == 0) && + (wt_co_state((const wt_co_t*)co) == WT_CO_BLOCKED)) { + wt_el3_puts("[SPM] sint signaled id=0x"); + wt_el3_puthex(WT_SPM_TEST_SPI, 2u); + wt_el3_puts("\r\n"); + } + test_ns_int_release(); + g_wt_spm_sint_queued = 0u; + wt_gic->set_group0(WT_SPM_TEST_SPI); + wt_gic->set_priority(WT_SPM_TEST_SPI, 0x00u); + wt_gic->enable(WT_SPM_TEST_SPI); + wt_gic->set_pending(WT_SPM_TEST_SPI); + (void)wt_spm_ffa_signal_deliver(co, WT_GIC_INTID_SECURE_TIMER); + wt_gic->disable(WT_SPM_TEST_SPI); + if ((g_wt_spm_sint_queued == WT_SPM_TEST_SPI) && + (wt_spm_sint_delivered(co) == WT_SPM_TEST_SPI)) { + wt_el3_puts("[SPM] sint queued id=0x"); + wt_el3_puthex(WT_SPM_TEST_SPI, 2u); + wt_el3_puts("\r\n"); + } +} +#endif + +/* One secure timer period with FIQ unmasked at S-EL1: the tick must arrive + * as INTID 29 through the S-EL1 vector table before the deadline. */ +int wt_spm_prove_tick(void) +{ + uint64_t deadline = wt_read_cntpct_el0() + + ((wt_read_cntfrq_el0() * WT_SPM_TICK_WAIT_MS) / 1000u); + + g_wt_spm_tick_intid = 0u; + wt_gic->enable(WT_GIC_INTID_SECURE_TIMER); + wt_el3_timer_arm_ms(WT_SPM_TICK_PERIOD_MS); + wt_daif_clear_fiq(); + while ((g_wt_spm_tick_intid == 0u) && (wt_read_cntpct_el0() < deadline)) { + } + wt_daif_set_fiq(); + wt_el3_timer_disable(); + wt_gic->disable(WT_GIC_INTID_SECURE_TIMER); + return (g_wt_spm_tick_intid == WT_GIC_INTID_SECURE_TIMER) ? 1 : 0; +} diff --git a/src/arch/aarch64/spm/spm_main.c b/src/arch/aarch64/spm/spm_main.c new file mode 100644 index 00000000..d69e1054 --- /dev/null +++ b/src/arch/aarch64/spm/spm_main.c @@ -0,0 +1,2000 @@ +/* spm_main.c + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfTrust. + * + * wolfTrust 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. + * + * wolfTrust 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, see . + */ + +/* SPMC entry at S-EL1: consume the FF-A boot information blob, negotiate + * with the SPMD at the Secure physical instance, and complete + * initialization with FFA_MSG_WAIT (5.5). The partitions arrive with the + * tables and the SVC gate. */ + +#include "wolftrust/arch/aarch64/el3.h" +#include "wolftrust/arch/aarch64/ffa.h" +#include "wolftrust/arch/aarch64/ffa_abi.h" +#include "wolftrust/arch/aarch64/ffa_boot_info.h" +#include "wolftrust/arch/aarch64/ffa_manifest.h" +#include "wolftrust/arch/aarch64/ffa_msg.h" +#include "wolftrust/arch/aarch64/domain.h" +#include "wolftrust/arch/aarch64/monitor_abi.h" +#include "wolftrust/arch/aarch64/ffa_mem.h" +#include "wolftrust/arch/aarch64/ffa_notif.h" +#include "wolftrust/arch/aarch64/ffa_partinfo.h" +#include "wolftrust/arch/aarch64/gic.h" +#include "wolftrust/arch/aarch64/psa_ffa.h" +#include "wolftrust/arch/aarch64/psci.h" +#include "wolftrust/arch/aarch64/spm_mem.h" +#include "wolftrust/arch/aarch64/spm_svc.h" +#include "wolftrust/arch/aarch64/sysreg.h" +#include "wolftrust/arch/aarch64/tables.h" +#include "wolftrust/boot.h" +#include "wolftrust/ffm_domain.h" +#include "wolftrust/manifest.h" +#include "wolftrust/platform.h" +#include "wolftrust/sched/coroutine.h" + +#include + +const wt_system_manifest_t* wt_generated_manifest_get(void); + +#define WT_SPMC_UNKNOWN_FID (WT_FFA_FID32_LAST - 0xFu) +#define WT_SPMC_BOOT_INFO_LIMIT 4096u +#define WT_SPMC_MAX_FILL 32u +/* Non-secure window the SPMC maps EL1-only to reach a guest's psa_call buffers + * (the guest image plus its stack live at WT_NS_IMAGE_PA). */ +#ifndef WT_PSA_NS_WINDOW_SIZE +#define WT_PSA_NS_WINDOW_SIZE 0x00100000u +#endif + +extern uint8_t _e_secure_text[]; +extern uint8_t _e_secure_rodata[]; +extern uint8_t __image_end[]; +extern uint8_t __spm_ram_end[]; +extern uint8_t __spm_stack_guard[]; + +void wt_spm_main(uint64_t boot_info_pa); +int wt_spm_prove_tick(void); + +/* The fill list outlives init: every partition table maps it EL1-only. */ +static wt_memory_region_t g_fill[WT_SPMC_MAX_FILL]; +/* The manifest domain each owned fill entry belongs to; WT_DOMAIN_ID_INVALID + * marks one no manifest partition may name (the SPMC's, the echo's, a native + * partition's). */ +static uint32_t g_fill_owner[WT_SPMC_MAX_FILL]; + +static void spmc_fail(const char* what, uint64_t value) +{ + wt_el3_puts("[SPM] FAIL "); + wt_el3_puts(what); + wt_el3_puts(" x0=0x"); + wt_el3_puthex(value, 8u); + wt_el3_puts("\r\n"); + wt_platform_console_flush(); + (void)wt_mon_call(WT_MON_FID_PANIC, 0xF1u); + for (;;) { + __asm__ volatile("wfi"); + } +} + +static void ffa_call(wt_ffa_regs_t* r, uint32_t fid, uint64_t x1) +{ + unsigned int i; + + for (i = 0u; i < 8u; i++) { + r->x[i] = 0u; + } + r->x[0] = fid; + r->x[1] = x1; + wt_ffa_smc(r); +} + +uintptr_t g_wt_spm_handoff_pa; +size_t g_wt_spm_handoff_size; + +static void consume_boot_info(uint64_t boot_info_pa) +{ + const uint8_t* blob = (const uint8_t*)(uintptr_t)boot_info_pa; + wt_ffa_boot_info_t info; + wt_ffa_boot_info_desc_t desc; + uint64_t handoff = 0u; + int ret; + + ret = wt_ffa_boot_info_parse(blob, boot_info_pa, WT_SPMC_BOOT_INFO_LIMIT, &info); + if (ret != WT_FFA_BOOT_INFO_OK) { + spmc_fail("boot info", (uint64_t)(uint32_t)ret); + } + if (wt_ffa_boot_info_find(blob, &info, WT_FFA_BOOT_INFO_TYPE_WT_HANDOFF, &desc) == + WT_FFA_BOOT_INFO_OK) { + handoff = desc.contents; + g_wt_spm_handoff_pa = (uintptr_t)desc.contents; + g_wt_spm_handoff_size = (size_t)desc.size; + } + wt_el3_puts("[SPM] boot info ok descs="); + wt_el3_putdec(info.desc_count); + wt_el3_puts(" handoff=0x"); + wt_el3_puthex(handoff, 8u); + wt_el3_puts("\r\n"); +} + +static void discover_spmd(void) +{ + wt_ffa_regs_t r; + + ffa_call(&r, WT_FFA_VERSION, WT_FFA_VERSION_1_2); + if ((uint32_t)r.x[0] != WT_FFA_VERSION_1_2) { + spmc_fail("ffa version", r.x[0]); + } + wt_el3_puts("[SPM] ffa version 1.2 negotiated\r\n"); + + ffa_call(&r, WT_SMCCC_VERSION, 0u); + if ((uint32_t)r.x[0] != WT_SMCCC_VERSION_1_2) { + spmc_fail("smccc version", r.x[0]); + } + ffa_call(&r, WT_SMCCC_ARCH_FEATURES, WT_SMCCC_VERSION); + if ((uint32_t)r.x[0] != 0u) { + spmc_fail("smccc arch_features", r.x[0]); + } + wt_el3_puts("[SPM] smccc version 1.2\r\n"); + + ffa_call(&r, WT_FFA_ID_GET, 0u); + if (((uint32_t)r.x[0] != WT_FFA_SUCCESS32) || (r.x[2] != WT_FFA_ID_SPMC)) { + spmc_fail("ffa id_get", r.x[0]); + } + ffa_call(&r, WT_FFA_SPM_ID_GET, 0u); + if (((uint32_t)r.x[0] != WT_FFA_SUCCESS32) || (r.x[2] != WT_FFA_ID_SPMD)) { + spmc_fail("ffa spm_id_get", r.x[0]); + } + ffa_call(&r, WT_FFA_FEATURES, WT_FFA_VERSION); + if ((uint32_t)r.x[0] != WT_FFA_SUCCESS32) { + spmc_fail("ffa features(version)", r.x[0]); + } + ffa_call(&r, WT_SPMC_UNKNOWN_FID, 0u); + if (((uint32_t)r.x[0] != WT_FFA_ERROR) || + ((int32_t)(uint32_t)r.x[2] != WT_FFA_NOT_SUPPORTED)) { + spmc_fail("ffa unknown fid", r.x[0]); + } + wt_el3_puts("[SPM] ffa discovery ok id=0x8000 spmd=0x8001\r\n"); +} + +static void pack_chars(wt_ffa_regs_t* r, const char* text, unsigned int count, + unsigned int per_reg) +{ + unsigned int i; + + for (i = 0u; i < 8u; i++) { + r->x[i] = 0u; + } + for (i = 0u; i < count; i++) { + r->x[2u + (i / per_reg)] |= (uint64_t)(uint8_t)text[i] + << (8u * (i % per_reg)); + } + r->x[1] = count; +} + +/* 13.12 at the Secure physical instance: both conventions log through the + * SPMD, and the count rules are enforced. */ +static void prove_console_log(void) +{ + static const char msg32[] = "[SPM] console32 ok\r\n"; + static const char msg64[] = "[SPM] console64 ok\r\n"; + wt_ffa_regs_t r; + + ffa_call(&r, WT_FFA_CONSOLE_LOG32, 0u); + if ((uint32_t)r.x[0] != WT_FFA_ERROR || + (int32_t)(uint32_t)r.x[2] != WT_FFA_INVALID_PARAMETERS) { + spmc_fail("console_log count 0", r.x[0]); + } + ffa_call(&r, WT_FFA_CONSOLE_LOG32, 25u); + if ((uint32_t)r.x[0] != WT_FFA_ERROR || + (int32_t)(uint32_t)r.x[2] != WT_FFA_INVALID_PARAMETERS) { + spmc_fail("console_log count 25", r.x[0]); + } + pack_chars(&r, msg32, (unsigned int)(sizeof(msg32) - 1u), 4u); + r.x[0] = WT_FFA_CONSOLE_LOG32; + wt_ffa_smc(&r); + if ((uint32_t)r.x[0] != WT_FFA_SUCCESS32) { + spmc_fail("console_log32", r.x[0]); + } + pack_chars(&r, msg64, (unsigned int)(sizeof(msg64) - 1u), 8u); + r.x[0] = WT_FFA_CONSOLE_LOG64; + wt_ffa_smc(&r); + if ((uint32_t)r.x[0] != WT_FFA_SUCCESS32) { + spmc_fail("console_log64", r.x[0]); + } +} + +static uintptr_t page_up(uintptr_t v) +{ + return (v + WT_TABLES_PAGE_SIZE - 1u) & ~(uintptr_t)(WT_TABLES_PAGE_SIZE - 1u); +} + +/* SPM-only table (ASID 0): image text, constant data, the SPM RAM band + * (data, bss, stacks), every partition band, the boot information page, + * the table pool, the board devices. */ +void wt_domain_fail(int code) +{ + spmc_fail("domain", (uint64_t)(uint32_t)code); +} + +uintptr_t g_wt_spm_echo_stack_base; +uintptr_t g_wt_spm_echo_stack_size; + +/* A fill entry dropped for want of room would leave memory the SPMC writes + * unmapped, so a full list fails closed instead. */ +static void fill_check(size_t n) +{ + if (n >= WT_SPMC_MAX_FILL) { + spmc_fail("fill full", (uint64_t)n); + } +} + +/* Manifest partition stacks sit on WT_SPMC_STACK_STRIDE boundaries; the test + * echo partition takes the next one past the last, published as a shareable + * fill entry so its own table maps it EL0 while every other table keeps it + * EL1-only. */ +#define WT_SPMC_STACK_STRIDE 0x10000u + +#if defined(WT_SPM_ECHO_SP) +static size_t add_echo_band(wt_memory_region_t* fill, size_t n, + uintptr_t last_end, uintptr_t band_size) +{ + if (band_size == 0u) { + return n; + } + fill_check(n); + g_wt_spm_echo_stack_base = (last_end + WT_SPMC_STACK_STRIDE - 1u) & + ~(uintptr_t)(WT_SPMC_STACK_STRIDE - 1u); + g_wt_spm_echo_stack_size = band_size; + fill[n].base = g_wt_spm_echo_stack_base; + fill[n].size = (size_t)band_size; + fill[n].attributes = WT_MEM_ATTR_READ | WT_MEM_ATTR_WRITE | + WT_DOMAIN_FILL_SHARED | WT_DOMAIN_FILL_OWNED; + return n + 1u; +} +#else +static size_t add_echo_band(wt_memory_region_t* fill, size_t n, + uintptr_t last_end, uintptr_t band_size) +{ + (void)fill; + (void)last_end; + (void)band_size; + return n; +} +#endif + +#if defined(WT_FFA_ACS) && (WT_FFA_ACS == 1) +/* Each native partition's memory is a shareable fill entry: its own table maps + * it at EL0 with the listed permissions, every other table keeps it EL1-only + * so the SPMC can seed its stack and reach its message buffers. */ +static size_t add_native_bands(wt_memory_region_t* fill, size_t n) +{ + const wt_ffa_native_sp_t* list; + size_t count = 0u; + size_t i; + size_t j; + + list = wt_platform_ffa_native_partitions(&count); + for (i = 0u; (list != NULL) && (i < count); i++) { + for (j = 0u; j < list[i].region_count; j++) { + fill_check(n); + fill[n].base = list[i].regions[j].base; + fill[n].size = list[i].regions[j].size; + fill[n].attributes = WT_MEM_ATTR_READ | WT_MEM_ATTR_WRITE | + WT_DOMAIN_FILL_SHARED | WT_DOMAIN_FILL_OWNED; + n++; + } + } + return n; +} +#else +static size_t add_native_bands(wt_memory_region_t* fill, size_t n) +{ + (void)fill; + return n; +} +#endif + +/* No manifest partition may name memory an owned fill entry gives another + * endpoint (the manifest validator never sees the echo, native, or SPMC + * bands), since its table would take that entry over. */ +static void check_fill_owners(const wt_system_manifest_t* manifest, + const wt_memory_region_t* fill, size_t n) +{ + const wt_domain_descriptor_t* d; + const wt_memory_resource_t* r; + wt_memory_region_t grants[WT_MAX_MEMORY_REGIONS]; + size_t count = 0u; + size_t i; + size_t j; + + for (i = 0u; i < manifest->domain_count; i++) { + d = &manifest->domains[i]; + if (d->domain_class != WT_DOMAIN_CLASS_SECURE_PARTITION) { + continue; + } + for (j = 0u; j < d->memory_resource_count; j++) { + r = &d->memory_resources[j]; + if (wt_domain_fill_foreign(fill, g_fill_owner, n, (uint32_t)d->id, + r->base, r->size) != 0) { + spmc_fail("fill owner", (uint64_t)d->id); + } + } +#if defined(WT_CONFORMANCE) && (WT_CONFORMANCE == 1) + count = wt_platform_conf_sp_grants((int32_t)d->id, grants, 0u, + WT_MAX_MEMORY_REGIONS); +#endif + for (j = 0u; j < count; j++) { + if (wt_domain_fill_foreign(fill, g_fill_owner, n, (uint32_t)d->id, + grants[j].base, grants[j].size) != 0) { + spmc_fail("fill owner", (uint64_t)d->id); + } + } + } +} + +static void enable_mmu(uint64_t boot_info_pa) +{ + const wt_system_manifest_t* manifest = wt_generated_manifest_get(); + wt_memory_region_t* fill = g_fill; + const wt_memory_region_t* devices; + size_t device_count = 0u; + size_t n = 0u; + size_t i; + size_t j; + uint64_t ttbr0; + wt_memory_region_t band; + wt_memory_region_t stack; + uintptr_t last_end = 0u; + uintptr_t band_size = 0u; + + /* Shareable entries: a partition whose regions cover one takes it over + * (EL0 + EL1), and an owned one only the region that is exactly it; the + * SPM RAM band, boot page, pool, and devices stay EL1-only in every + * table. */ + for (i = 0u; i < WT_SPMC_MAX_FILL; i++) { + g_fill_owner[i] = WT_DOMAIN_ID_INVALID; + } + fill[n].base = (uintptr_t)WT_SPM_IMAGE_PA; + fill[n].size = (uintptr_t)_e_secure_text - (uintptr_t)WT_SPM_IMAGE_PA; + fill[n].attributes = WT_MEM_ATTR_READ | WT_MEM_ATTR_EXEC | WT_DOMAIN_FILL_SHARED; + n++; + fill[n].base = (uintptr_t)_e_secure_text; + fill[n].size = (uintptr_t)_e_secure_rodata - (uintptr_t)_e_secure_text; + fill[n].attributes = WT_MEM_ATTR_READ | WT_DOMAIN_FILL_SHARED; + n++; + /* Load images of initialized data (function pointers too) stay EL1-only. */ + if (page_up((uintptr_t)__image_end) > (uintptr_t)_e_secure_rodata) { + fill[n].base = (uintptr_t)_e_secure_rodata; + fill[n].size = page_up((uintptr_t)__image_end) - + (uintptr_t)_e_secure_rodata; + fill[n].attributes = WT_MEM_ATTR_READ; + n++; + } + fill[n].base = (uintptr_t)WT_SPM_RAM_PA; + fill[n].size = (uintptr_t)__spm_stack_guard - (uintptr_t)WT_SPM_RAM_PA; + fill[n].attributes = WT_MEM_ATTR_READ | WT_MEM_ATTR_WRITE; +#if defined(WT_TABLES_NEGATIVE) && (WT_TABLES_NEGATIVE == 1) + /* A writable+executable region must be refused at build (W^X), panicking + * through wt_domain_fail before any partition initializes. */ + fill[n].attributes |= WT_MEM_ATTR_EXEC; +#endif + n++; + /* The stack guard page is skipped: SPM RAM resumes past it. */ + fill[n].base = (uintptr_t)__spm_stack_guard + WT_TABLES_PAGE_SIZE; + fill[n].size = page_up((uintptr_t)__spm_ram_end) - fill[n].base; + fill[n].attributes = WT_MEM_ATTR_READ | WT_MEM_ATTR_WRITE; + n++; + /* The FF-A RX/TX buffer band (7.2): the SPMC writes partition information + * into it at S-EL1, and the discovering partition maps and reads it at + * S-EL0, so it is shareable and taken over by that partition's table. */ + fill[n].base = (uintptr_t)WT_SPM_RXTX_PA; + fill[n].size = (size_t)WT_SPM_RXTX_SIZE; + fill[n].attributes = WT_MEM_ATTR_READ | WT_MEM_ATTR_WRITE | + WT_DOMAIN_FILL_SHARED | WT_DOMAIN_FILL_OWNED; + n++; + /* The memory-sharing self-test page: the SPMC seeds it at S-EL1 and a + * partition maps it at S-EL0 only through FFA_MEM_RETRIEVE_REQ. */ + fill[n].base = (uintptr_t)WT_SPM_SHARE_PA; + fill[n].size = (size_t)WT_SPM_SHARE_SIZE; + fill[n].attributes = WT_MEM_ATTR_READ | WT_MEM_ATTR_WRITE | + WT_DOMAIN_FILL_SHARED | WT_DOMAIN_FILL_OWNED; + n++; + /* Every partition resource the SPMC itself writes from EL1 (the stack it + * seeds and scrubs, a data band the fault scrub clears), whole; the owner + * maps its own at EL0. */ + for (i = 0u; i < manifest->domain_count; i++) { + const wt_domain_descriptor_t* d = &manifest->domains[i]; + + if (d->domain_class != WT_DOMAIN_CLASS_SECURE_PARTITION) { + continue; + } + for (j = 0u; j < d->memory_resource_count; j++) { + if (wt_domain_spm_band(d, j, &band) == 0) { + fill_check(n); + fill[n] = band; + fill[n].attributes |= WT_DOMAIN_FILL_SHARED | + WT_DOMAIN_FILL_OWNED; + g_fill_owner[n] = (uint32_t)d->id; + n++; + } + } + if ((wt_domain_stack_band(d, &stack) == 0) && + ((stack.base + stack.size) > last_end)) { + last_end = stack.base + stack.size; + band_size = (uintptr_t)stack.size; + } + } + n = add_echo_band(fill, n, last_end, band_size); + n = add_native_bands(fill, n); + fill_check(n + 1u); + fill[n].base = (uintptr_t)boot_info_pa; + fill[n].size = WT_TABLES_PAGE_SIZE; + fill[n].attributes = WT_MEM_ATTR_READ; + if (g_wt_spm_handoff_pa != 0u) { + /* The handoff record rides in this page and is cleared once consumed. */ + fill[n].attributes |= WT_MEM_ATTR_WRITE; + } + n++; + fill[n].base = (uintptr_t)WT_SPM_TABLE_POOL_PA; + fill[n].size = (size_t)WT_SPM_TABLE_POOL_PAGES * WT_TABLES_PAGE_SIZE; + fill[n].attributes = WT_MEM_ATTR_READ | WT_MEM_ATTR_WRITE; + n++; + devices = wt_platform_board_device_regions(&device_count); + for (i = 0u; i < device_count; i++) { + fill_check(n); + fill[n] = devices[i]; + n++; + } +#if defined(WT_EL3_NS_SMOKE) + /* A guest's psa_call buffers live in Non-secure RAM: map the window + * EL1-only and Non-secure so the SPMC can copy them. A retrieve maps its + * pages at EL0, so the window is non-global in every table. */ + fill_check(n); + fill[n].base = (uintptr_t)WT_NS_IMAGE_PA; + fill[n].size = (size_t)WT_PSA_NS_WINDOW_SIZE; + fill[n].attributes = WT_MEM_ATTR_READ | WT_MEM_ATTR_WRITE | + WT_TABLES_ATTR_NS | WT_TABLES_ATTR_NG; + n++; +#endif + check_fill_owners(manifest, fill, n); + + ttbr0 = wt_domain_init(fill, n, (uint8_t*)(uintptr_t)WT_SPM_TABLE_POOL_PA, + WT_SPM_TABLE_POOL_PA, + (size_t)WT_SPM_TABLE_POOL_PAGES * WT_TABLES_PAGE_SIZE); + if (ttbr0 == 0u) { + spmc_fail("spm table", 0u); + } + wt_mmu_enable(ttbr0, WT_TABLES_MAIR_EL1, WT_TABLES_TCR_EL1); + wt_el3_puts("[SPM] mmu on ttbr0=0x"); + wt_el3_puthex(ttbr0, 16u); + wt_el3_puts(" pool_pages="); + wt_el3_putdec(wt_domain_pool_pages_used()); + wt_el3_puts("\r\n"); +} + +/* Prove the S-EL1 coroutine switch: run a privileged coroutine that yields + * back, resumes, and yields again, checking its progress each time. */ +static uint8_t g_prove_co_stack[4096] __attribute__((aligned(16))); +static volatile int g_prove_co_step; + +static void prove_co_body(void* arg) +{ + g_prove_co_step = (int)(intptr_t)arg; + wt_co_block(); + g_prove_co_step = 99; + wt_co_block(); +} + +static int prove_coroutine(void) +{ + wt_co_t* co; + + wt_co_init(); + co = wt_co_create_blocked_ex(g_prove_co_stack, sizeof(g_prove_co_stack), + prove_co_body, (void*)(intptr_t)7); + if (co == NULL) { + return 0; + } + wt_co_wake(co); + if (wt_co_run(co) != 1u || g_prove_co_step != 7) { + return 0; + } + wt_co_wake(co); + if (wt_co_run(co) != 1u || g_prove_co_step != 99) { + return 0; + } + return 1; +} + +/* Prove the S-EL0 path: an unprivileged coroutine confined to the shared + * text band and one partition band runs at EL0, yields through SVC with a + * token, resumes after the SVC, and yields again; the S-EL1 handler of each + * yield must not take itself for the unprivileged caller. */ +extern void wt_sp_el0_probe(void); +static wt_secure_domain_t g_el0_domain; + +/* The first configured partition and the stack band enable_mmu mapped for it, + * which the boot proofs borrow. */ +static const wt_domain_descriptor_t* first_partition_domain( + wt_memory_region_t* band) +{ + const wt_system_manifest_t* manifest = wt_generated_manifest_get(); + size_t i; + + for (i = 0u; i < manifest->domain_count; i++) { + if (manifest->domains[i].domain_class == WT_DOMAIN_CLASS_SECURE_PARTITION) { + return (wt_domain_stack_band(&manifest->domains[i], band) == 0) ? + &manifest->domains[i] : NULL; + } + } + return NULL; +} + +static int prove_el0(void) +{ + wt_memory_region_t band; + const wt_domain_descriptor_t* d = first_partition_domain(&band); + uint8_t* stack; + wt_co_t* co; + + if (d == NULL) { + return 0; + } + stack = (uint8_t*)band.base; + g_el0_domain.regions[0].base = (uintptr_t)WT_SPM_IMAGE_PA; + g_el0_domain.regions[0].size = (uintptr_t)_e_secure_text - (uintptr_t)WT_SPM_IMAGE_PA; + g_el0_domain.regions[0].attributes = WT_MEM_ATTR_READ | WT_MEM_ATTR_EXEC; + g_el0_domain.regions[1].base = (uintptr_t)stack; + g_el0_domain.regions[1].size = band.size; + g_el0_domain.regions[1].attributes = WT_MEM_ATTR_READ | WT_MEM_ATTR_WRITE; + g_el0_domain.region_count = 2u; + co = wt_co_create_blocked_ex(stack, band.size, + (wt_co_entry_fn)wt_sp_el0_probe, (void*)0x11); + if (co == NULL) { + return 0; + } + wt_co_set_domain(co, &g_el0_domain, 1u); + wt_co_wake(co); + if (wt_co_run(co) != 1u || wt_spm_yield_token() != 0x5Au || + wt_spm_yield_unprivileged() != 0u) { + return 0; + } + wt_co_wake(co); + if (wt_co_run(co) != 1u || wt_spm_yield_token() != 0xA5u || + wt_spm_yield_unprivileged() != 0u) { + return 0; + } + return 1; +} + +/* Prove FF-A direct messaging at the Secure virtual instance: an S-EL0 echo + * partition parks in FFA_MSG_WAIT, is delivered two direct requests in turn + * through its saved frame, and answers each by FFA_MSG_SEND_DIRECT_RESP32 + * with the ids swapped and the payload word complemented. First, on the same + * band, a partition that reports failed initialization with FFA_ERROR must + * wait, never having initialized, and never run again. */ +static wt_secure_domain_t g_echo_domain; +extern void wt_sp_ffa_init_fail(void); + +static int prove_init_failure(uint8_t* stack, size_t size) +{ + static const uint32_t probe[WT_FFA_DIRECT_PAYLOAD_WORDS] = { 0u }; + uint64_t req[WT_FFA_MSG_REGS_EXT]; + uint64_t resp[WT_FFA_MSG_REGS_EXT]; + wt_co_t* co; + int ok; + + co = wt_co_create_blocked_ex(stack, size, + (wt_co_entry_fn)wt_sp_ffa_init_fail, (void*)0); + if (co == NULL) { + return 0; + } + wt_co_set_domain(co, &g_echo_domain, 1u); + wt_co_wake(co); + (void)wt_co_run(co); + /* 8.5 rule 3: it waits, neither initializing nor initialized, and a + * request or FFA_RUN finds it not in a state to handle one. */ + wt_ffa_direct_build(req, WT_FFA_MSG_SEND_DIRECT_REQ32, WT_FFA_ID_NS_PRIMARY, + WT_FFA_ID_SP_FIRST, probe); + ok = ((wt_co_state(co) == WT_CO_BLOCKED) && + (wt_spm_sp_failed_init((const struct wt_co*)co) != 0) && + (wt_spm_sp_initializing((const struct wt_co*)co) == 0) && + (wt_spm_ffa_direct_deliver((struct wt_co*)co, req, resp) == + WT_FFA_DENIED) && + (wt_spm_ffa_run((struct wt_co*)co, WT_FFA_ID_NS_PRIMARY, resp) == + WT_FFA_DENIED)) ? 1 : 0; + /* A scheduler that wakes it anyway never gets it running again. */ + wt_co_wake(co); + (void)wt_co_run(co); + return ((ok != 0) && (wt_co_state(co) == WT_CO_FAULTED) && + (wt_spm_yield_token() != 0xBADu)) ? 1 : 0; +} + +static int prove_ffa_direct(void) +{ + static const uint32_t first[WT_FFA_DIRECT_PAYLOAD_WORDS] = { + 0x5A5A00FFu, 0u, 0u, 0u, 0u + }; + static const uint32_t second[WT_FFA_DIRECT_PAYLOAD_WORDS] = { + 0x0000C3C3u, 0u, 0u, 0u, 0u + }; + static const uint32_t complete[WT_FFA_DIRECT_PAYLOAD_WORDS] = { + 0x00005CCEu, 0u, 0u, 0u, 0u + }; + unsigned int i; + wt_memory_region_t band; + const wt_domain_descriptor_t* d = first_partition_domain(&band); + uint64_t req[WT_FFA_MSG_REGS_EXT]; + uint64_t resp[WT_FFA_MSG_REGS_EXT]; + uint8_t* stack; + wt_co_t* co; + + if (d == NULL) { + return 0; + } + stack = (uint8_t*)band.base; + g_echo_domain.regions[0].base = (uintptr_t)WT_SPM_IMAGE_PA; + g_echo_domain.regions[0].size = (uintptr_t)_e_secure_text - (uintptr_t)WT_SPM_IMAGE_PA; + g_echo_domain.regions[0].attributes = WT_MEM_ATTR_READ | WT_MEM_ATTR_EXEC; + g_echo_domain.regions[1].base = (uintptr_t)stack; + g_echo_domain.regions[1].size = band.size; + g_echo_domain.regions[1].attributes = WT_MEM_ATTR_READ | WT_MEM_ATTR_WRITE; + g_echo_domain.region_count = 2u; + if (prove_init_failure(stack, band.size) == 0) { + return 0; + } + co = wt_co_create_blocked_ex(stack, band.size, + (wt_co_entry_fn)wt_sp_ffa_echo, (void*)0); + if (co == NULL) { + return 0; + } + wt_co_set_domain(co, &g_echo_domain, 1u); + wt_co_wake(co); + if (wt_co_run(co) != 1u || wt_co_state(co) != WT_CO_BLOCKED) { + return 0; + } + + wt_ffa_direct_build(req, WT_FFA_MSG_SEND_DIRECT_REQ32, WT_FFA_ID_NS_PRIMARY, + WT_FFA_ID_SP_FIRST, first); + if (wt_spm_ffa_direct_deliver((struct wt_co*)co, req, resp) != 0) { + return 0; + } + if (((uint32_t)resp[0] != WT_FFA_MSG_SEND_DIRECT_RESP32) || + (wt_ffa_direct_sender(resp[1]) != WT_FFA_ID_SP_FIRST) || + (wt_ffa_direct_receiver(resp[1]) != WT_FFA_ID_NS_PRIMARY) || + ((uint32_t)resp[2] != 0u) || + ((uint32_t)resp[3] != (uint32_t)~first[0])) { + return 0; + } + + wt_ffa_direct_build(req, WT_FFA_MSG_SEND_DIRECT_REQ32, WT_FFA_ID_NS_PRIMARY, + WT_FFA_ID_SP_FIRST, second); + if (wt_spm_ffa_direct_deliver((struct wt_co*)co, req, resp) != 0) { + return 0; + } + if (((uint32_t)resp[0] != WT_FFA_MSG_SEND_DIRECT_RESP32) || + ((uint32_t)resp[3] != (uint32_t)~second[0]) || + (wt_co_state(co) != WT_CO_BLOCKED)) { + return 0; + } + + /* 15.2: completed with FFA_SUCCESS instead, the echo waits once more. */ + wt_ffa_direct_build(req, WT_FFA_MSG_SEND_DIRECT_REQ32, WT_FFA_ID_NS_PRIMARY, + WT_FFA_ID_SP_FIRST, complete); + if ((wt_spm_ffa_direct_deliver((struct wt_co*)co, req, resp) != 0) || + ((uint32_t)resp[0] != WT_FFA_SUCCESS32)) { + return 0; + } + for (i = 1u; i < WT_FFA_MSG_REGS; i++) { + if (resp[i] != 0u) { + return 0; + } + } + wt_ffa_direct_build(req, WT_FFA_MSG_SEND_DIRECT_REQ32, WT_FFA_ID_NS_PRIMARY, + WT_FFA_ID_SP_FIRST, first); + if ((wt_spm_ffa_direct_deliver((struct wt_co*)co, req, resp) != 0) || + ((uint32_t)resp[0] != WT_FFA_MSG_SEND_DIRECT_RESP32) || + ((uint32_t)resp[3] != (uint32_t)~first[0])) { + return 0; + } + return 1; +} + +/* Prove asynchronous preemption: an S-EL0 partition that never yields is + * entered with the secure timer armed, taken by a Group 0 tick mid-spin, and + * left runnable (not blocked or faulted) so the scheduler could resume it. */ +static wt_secure_domain_t g_spin_domain; + +static int prove_preempt(void) +{ + static const uint32_t none[WT_FFA_DIRECT_PAYLOAD_WORDS]; + wt_memory_region_t band; + const wt_domain_descriptor_t* d = first_partition_domain(&band); + uint64_t req[WT_FFA_MSG_REGS_EXT]; + uint64_t resp[WT_FFA_MSG_REGS_EXT]; + wt_ffa_mailbox_t* mb; + uint8_t* stack; + wt_co_t* co; + int preempted; + int mapped; + + if (d == NULL) { + return 0; + } + stack = (uint8_t*)band.base; + g_spin_domain.regions[0].base = (uintptr_t)WT_SPM_IMAGE_PA; + g_spin_domain.regions[0].size = (uintptr_t)_e_secure_text - (uintptr_t)WT_SPM_IMAGE_PA; + g_spin_domain.regions[0].attributes = WT_MEM_ATTR_READ | WT_MEM_ATTR_EXEC; + g_spin_domain.regions[1].base = (uintptr_t)stack; + g_spin_domain.regions[1].size = band.size; + g_spin_domain.regions[1].attributes = WT_MEM_ATTR_READ | WT_MEM_ATTR_WRITE; + g_spin_domain.region_count = 2u; + co = wt_co_create_blocked_ex(stack, band.size, + (wt_co_entry_fn)wt_sp_spin, (void*)0); + if (co == NULL) { + return 0; + } + wt_co_set_domain(co, &g_spin_domain, 1u); + wt_co_wake(co); + wt_spm_preempt_timer_arm(); + (void)wt_co_run(co); + wt_spm_preempt_timer_stop(); + preempted = (wt_co_state(co) == WT_CO_RUNNABLE) ? 1 : 0; + /* Retired for good, it lets go of its RX/TX pair, and a direct request or + * FFA_RUN naming it is ABORTED (Tables 15.8, 14.14). */ + mb = wt_spm_sp_mailbox_of((const struct wt_co*)co); + mapped = ((mb != NULL) && + (wt_ffa_mailbox_map(mb, (uint64_t)WT_SPM_RXTX_PA + + WT_FFA_MEM_PAGE_SIZE, + (uint64_t)WT_SPM_RXTX_PA, 1u) == 0)) ? 1 : 0; + wt_spm_sp_retire((struct wt_co*)co); + wt_ffa_direct_build(req, WT_FFA_MSG_SEND_DIRECT_REQ32, WT_FFA_ID_NS_PRIMARY, + WT_FFA_ID_SP_FIRST, none); + return ((preempted != 0) && (mapped != 0) && (mb->mapped == 0u) && + (wt_spm_ffa_direct_deliver((struct wt_co*)co, req, resp) == + WT_FFA_ABORTED) && + (wt_spm_ffa_run((struct wt_co*)co, WT_FFA_ID_NS_PRIMARY, resp) == + WT_FFA_ABORTED)) ? 1 : 0; +} + +/* Prove that an endpoint yielded to a requester that then leaves service is + * orphaned: until then only that requester may resume it, so the Normal + * world's FFA_RUN and another partition's are DENIED and a direct request + * finds it busy; after, a direct request still finds it busy, the Normal + * world's FFA_RUN runs it and is told FFA_MSG_WAIT (its response has no + * receiver), after which it takes a request as usual, and another + * partition's FFA_RUN of a second orphan is accepted. */ +static wt_secure_domain_t g_yield_domain; + +static int prove_orphan(void) +{ + static const uint32_t none[WT_FFA_DIRECT_PAYLOAD_WORDS]; + wt_memory_region_t band; + const wt_domain_descriptor_t* d = first_partition_domain(&band); + uint64_t req[WT_FFA_MSG_REGS_EXT]; + uint64_t out[WT_FFA_MSG_REGS_EXT]; + uint8_t* stack; + size_t half; + wt_co_t* a; + wt_co_t* y1; + wt_co_t* y2; + uint16_t a_id; + + if (d == NULL) { + return 0; + } + stack = (uint8_t*)band.base; + half = (band.size / 2u) & ~(size_t)15u; + g_yield_domain.regions[0].base = (uintptr_t)WT_SPM_IMAGE_PA; + g_yield_domain.regions[0].size = (uintptr_t)_e_secure_text - (uintptr_t)WT_SPM_IMAGE_PA; + g_yield_domain.regions[0].attributes = WT_MEM_ATTR_READ | WT_MEM_ATTR_EXEC; + g_yield_domain.regions[1].base = (uintptr_t)stack; + g_yield_domain.regions[1].size = band.size; + g_yield_domain.regions[1].attributes = WT_MEM_ATTR_READ | WT_MEM_ATTR_WRITE; + g_yield_domain.region_count = 2u; + /* The requester only lends its id and never runs. */ + a = wt_co_create_blocked_ex(g_prove_co_stack, sizeof(g_prove_co_stack), + (wt_co_entry_fn)wt_sp_spin, (void*)0); + y1 = wt_co_create_blocked_ex(stack, half, (wt_co_entry_fn)wt_sp_ffa_yield, + (void*)0); + y2 = wt_co_create_blocked_ex(stack + half, half, + (wt_co_entry_fn)wt_sp_ffa_yield, (void*)0); + if ((a == NULL) || (y1 == NULL) || (y2 == NULL)) { + return 0; + } + wt_co_set_domain(a, &g_yield_domain, 1u); + wt_co_set_domain(y1, &g_yield_domain, 1u); + wt_co_set_domain(y2, &g_yield_domain, 1u); + a_id = wt_spm_sp_ffa_id((const struct wt_co*)a); + wt_co_wake(y1); + if ((wt_co_run(y1) != 1u) || (wt_co_state(y1) != WT_CO_BLOCKED)) { + return 0; + } + wt_co_wake(y2); + if ((wt_co_run(y2) != 1u) || (wt_co_state(y2) != WT_CO_BLOCKED)) { + return 0; + } + wt_ffa_direct_build(req, WT_FFA_MSG_SEND_DIRECT_REQ32, a_id, + wt_spm_sp_ffa_id((const struct wt_co*)y1), none); + if ((wt_spm_ffa_direct_deliver((struct wt_co*)y1, req, out) != 0) || + ((uint32_t)out[0] != WT_FFA_YIELD)) { + return 0; + } + wt_ffa_direct_build(req, WT_FFA_MSG_SEND_DIRECT_REQ32, a_id, + wt_spm_sp_ffa_id((const struct wt_co*)y2), none); + if ((wt_spm_ffa_direct_deliver((struct wt_co*)y2, req, out) != 0) || + ((uint32_t)out[0] != WT_FFA_YIELD)) { + return 0; + } + wt_ffa_direct_build(req, WT_FFA_MSG_SEND_DIRECT_REQ32, WT_FFA_ID_NS_PRIMARY, + wt_spm_sp_ffa_id((const struct wt_co*)y1), none); + if ((wt_spm_ffa_run((struct wt_co*)y1, WT_FFA_ID_NS_PRIMARY, out) != + WT_FFA_DENIED) || + (wt_spm_ffa_sp_call((const struct wt_co*)y1, (struct wt_co*)y2, + NULL) != WT_FFA_DENIED) || + (wt_spm_ffa_direct_deliver((struct wt_co*)y1, req, out) != + WT_FFA_BUSY)) { + return 0; + } + wt_spm_sp_retire((struct wt_co*)a); + if (wt_spm_ffa_direct_deliver((struct wt_co*)y1, req, out) != WT_FFA_BUSY) { + return 0; + } + if ((wt_spm_ffa_run((struct wt_co*)y1, WT_FFA_ID_NS_PRIMARY, out) != 0) || + ((uint32_t)out[0] != WT_FFA_MSG_WAIT) || + (out[1] != ((uint64_t)wt_spm_sp_ffa_id((const struct wt_co*)y1) << 16)) || + (wt_co_state(y1) != WT_CO_BLOCKED)) { + return 0; + } + if ((wt_spm_ffa_direct_deliver((struct wt_co*)y1, req, out) != 0) || + ((uint32_t)out[0] != WT_FFA_YIELD) || + (wt_spm_ffa_run((struct wt_co*)y1, WT_FFA_ID_NS_PRIMARY, out) != 0) || + ((uint32_t)out[0] != WT_FFA_MSG_SEND_DIRECT_RESP32) || + (wt_ffa_direct_sender(out[1]) != + wt_spm_sp_ffa_id((const struct wt_co*)y1)) || + (wt_ffa_direct_receiver(out[1]) != WT_FFA_ID_NS_PRIMARY) || + ((uint32_t)out[3] != (uint32_t)~none[0])) { + return 0; + } + return (wt_spm_ffa_sp_call((const struct wt_co*)y1, (struct wt_co*)y2, + NULL) == 0) ? 1 : 0; +} + +/* Prove FF-A partition discovery: an S-EL0 partition standing in for the + * first configured partition (its domain and stack) calls + * FFA_PARTITION_INFO_GET with a Nil UUID while no other partition exists. The + * SPMC lists exactly that domain's descriptors, under the id FFA_ID_GET gives + * the caller, in the RX buffer of the pair mapped for it; the partition reads + * the count and the first descriptor's id back out at S-EL0. A Normal-world + * FFA_MSG_SEND2 to it, a PSA partition, is then DENIED (Table 15.4). */ +static wt_secure_domain_t g_discover_domain; +static uint8_t g_msg2_probe[WT_FFA_MSG2_HEADER_SIZE]; + +static int msg2_to_psa(uint16_t receiver) +{ + g_msg2_probe[8] = (uint8_t)WT_FFA_MSG2_HEADER_SIZE; + g_msg2_probe[12] = (uint8_t)(receiver & 0xFFu); + g_msg2_probe[13] = (uint8_t)(receiver >> 8); + return wt_spm_msg2_deliver(WT_FFA_ID_NS_PRIMARY, WT_FFA_VERSION_1_2, + g_msg2_probe, (uint32_t)sizeof(g_msg2_probe), + WT_FFA_INSTANCE_NS_PHYSICAL, 0u, 0u); +} + +static int prove_partinfo(uint32_t* out_count) +{ + wt_memory_region_t band; + const wt_domain_descriptor_t* d = first_partition_domain(&band); + const wt_ffa_partition_manifest_t* parts; + wt_ffa_mailbox_t* mb; + uint32_t expect = 0u; + uint32_t ran; + size_t np = 0u; + size_t i; + uint8_t* stack; + wt_co_t* co; + uint64_t token; + + parts = wt_generated_ffa_partitions_get(&np); + if ((d == NULL) || (parts == NULL)) { + return 0; + } + for (i = 0u; i < np; i++) { + if (parts[i].domain_id == (uint32_t)d->id) { + expect = parts[i].uuid_count; + } + } + if (expect == 0u) { + return 0; + } + stack = (uint8_t*)band.base; + g_discover_domain.regions[0].base = (uintptr_t)WT_SPM_IMAGE_PA; + g_discover_domain.regions[0].size = (uintptr_t)_e_secure_text - (uintptr_t)WT_SPM_IMAGE_PA; + g_discover_domain.regions[0].attributes = WT_MEM_ATTR_READ | WT_MEM_ATTR_EXEC; + g_discover_domain.regions[1].base = (uintptr_t)stack; + g_discover_domain.regions[1].size = band.size; + g_discover_domain.regions[1].attributes = WT_MEM_ATTR_READ | WT_MEM_ATTR_WRITE; + g_discover_domain.regions[2].base = (uintptr_t)WT_SPM_RXTX_PA; + g_discover_domain.regions[2].size = (size_t)WT_SPM_RXTX_SIZE; + g_discover_domain.regions[2].attributes = WT_MEM_ATTR_READ | WT_MEM_ATTR_WRITE; + g_discover_domain.region_count = 3u; + g_discover_domain.domain_id = d->id; + co = wt_co_create_blocked_ex(stack, band.size, + (wt_co_entry_fn)wt_sp_ffa_discover, + (void*)(uintptr_t)WT_SPM_RXTX_PA); + if (co == NULL) { + return 0; + } + wt_co_set_domain(co, &g_discover_domain, 1u); + mb = wt_spm_sp_mailbox_of((const struct wt_co*)co); + if ((mb == NULL) || + (wt_ffa_mailbox_map(mb, (uint64_t)WT_SPM_RXTX_PA + WT_FFA_MEM_PAGE_SIZE, + (uint64_t)WT_SPM_RXTX_PA, 1u) != 0)) { + return 0; + } + wt_co_wake(co); + ran = wt_co_run(co); + /* Its slot is reused by a real partition, which maps its own pair. */ + if ((wt_ffa_mailbox_unmap(mb) != 0) || (ran != 1u)) { + return 0; + } + token = wt_spm_yield_token(); + *out_count = (uint32_t)(token & 0xFFFFu); + if ((uint32_t)((token >> 16) & 0xFFFFu) != + (uint32_t)wt_spm_sp_ffa_id((struct wt_co*)co)) { + return 0; + } + if (msg2_to_psa(wt_spm_sp_ffa_id((struct wt_co*)co)) != WT_FFA_DENIED) { + return 0; + } + return (*out_count == expect) ? 1 : 0; +} + +/* Prove FF-A memory sharing end to end: the SPMC (owner) seeds the share page + * and shares it to the borrower endpoint; an S-EL0 partition retrieves it + * through the SVC gate (which maps it into the partition's table), reads the + * seeded bytes and writes a reply at S-EL0, relinquishes it (the gate unmaps + * it), and the owner reclaims the handle, after which a retrieve is refused. + * The partition's table changes only through the two transactions. */ +static wt_secure_domain_t g_borrow_domain; +static uint8_t g_share_desc[WT_FFA_MEM_TXN_HDR_SIZE + WT_FFA_MEM_ACCESS_SIZE_V12 + + WT_FFA_MEM_COMPOSITE_HDR_SIZE + + WT_FFA_MEM_CONSTITUENT_SIZE]; + +/* The share, the borrower's two runs, and the reclaim, with the borrower + * bound to the relayer. */ +static int mem_share_exchange(wt_co_t* co, uint64_t* out_handle) +{ + volatile uint8_t* share = (volatile uint8_t*)(uintptr_t)WT_SPM_SHARE_PA; + uint8_t* rx = (uint8_t*)(uintptr_t)WT_SPM_RXTX_PA; + uint8_t* tx = rx + WT_FFA_MEM_PAGE_SIZE; + uint64_t* arg = (uint64_t*)(rx + WT_FFA_MEM_PAGE_SIZE - 64u); + wt_ffa_mem_constituent_t cons; + wt_ffa_mem_build_t in; + uint64_t handle = 0u; + size_t len = 0u; + size_t req_len = 0u; + + share[0] = 0x5Au; + share[1] = 0xA5u; + share[2] = 0x3Cu; + share[3] = 0xC3u; + share[4] = 0u; + + (void)memset(&in, 0, sizeof(in)); + cons.address = (uint64_t)WT_SPM_SHARE_PA; + cons.page_count = 1u; + in.constituents = &cons; + in.constituent_count = 1u; + in.tag = 0u; + in.handle = 0u; + in.flags = 0u; + in.op = WT_FFA_MEM_OP_SHARE; + in.sender = WT_FFA_ID_SPMC; + in.receiver = WT_FFA_ID_MEM_BORROWER; + in.attributes = (uint16_t)(WT_FFA_MEM_ATTR_TYPE_NORMAL | + (0x3u << WT_FFA_MEM_ATTR_CACHE_SHIFT) | + WT_FFA_MEM_ATTR_SHARE_INNER); + in.permissions = (uint8_t)WT_FFA_MEM_PERM_DATA_RW; + if (wt_ffa_mem_txn_build(g_share_desc, sizeof(g_share_desc), &in, &len) != 0) { + return 0; + } + if (wt_spm_mem_share(g_share_desc, len, WT_FFA_MEM_OP_SHARE, WT_FFA_ID_SPMC, + &handle) != 0) { + return 0; + } + *out_handle = handle; + + /* The borrower's retrieve request waits in its TX buffer; its arguments + * sit at the tail of its RX buffer, clear of the retrieve response. */ + if (wt_ffa_mem_retrieve_req_build_at(tx, WT_FFA_MEM_PAGE_SIZE, handle, + WT_FFA_ID_SPMC, WT_FFA_ID_MEM_BORROWER, + in.permissions, WT_FFA_VERSION_1_2, + &req_len) != 0) { + return 0; + } + arg[0] = handle; + arg[1] = (uint64_t)WT_SPM_SHARE_PA; + arg[2] = (uint64_t)(uintptr_t)tx; + arg[3] = (uint64_t)req_len; + arg[4] = (uint64_t)WT_FFA_ID_MEM_BORROWER; + + /* Run 1: retrieve, read the seed, write the reply at S-EL0, yield the seed. */ + wt_co_wake(co); + if (wt_co_run(co) != 1u) { + return 0; + } + if ((uint32_t)wt_spm_yield_token() != 0xC33CA55Au) { + return 0; + } + if (share[4] != 0xEEu) { + return 0; + } + + /* Run 2: relinquish, yield the status; the region is gone from the table. */ + wt_co_wake(co); + if (wt_co_run(co) != 1u) { + return 0; + } + if ((uint32_t)wt_spm_yield_token() != WT_FFA_SUCCESS32) { + return 0; + } + + if (wt_spm_mem_reclaim(handle, WT_FFA_ID_SPMC, 0u) != 0) { + return 0; + } + if (wt_spm_mem_retrieve(tx, req_len, WT_FFA_ID_MEM_BORROWER, rx, + WT_FFA_MEM_PAGE_SIZE, &len) == 0) { + return 0; + } + return 1; +} + +static int prove_mem_share(uint64_t* out_handle) +{ + wt_memory_region_t band; + const wt_domain_descriptor_t* d = first_partition_domain(&band); + uint64_t* arg = (uint64_t*)(uintptr_t)(WT_SPM_RXTX_PA + + WT_FFA_MEM_PAGE_SIZE - 64u); + wt_ffa_mailbox_t* mb; + uint8_t* stack; + wt_co_t* co; + int ok; + + if (d == NULL) { + return 0; + } + stack = (uint8_t*)band.base; + g_borrow_domain.regions[0].base = (uintptr_t)WT_SPM_IMAGE_PA; + g_borrow_domain.regions[0].size = (uintptr_t)_e_secure_text - (uintptr_t)WT_SPM_IMAGE_PA; + g_borrow_domain.regions[0].attributes = WT_MEM_ATTR_READ | WT_MEM_ATTR_EXEC; + g_borrow_domain.regions[1].base = (uintptr_t)stack; + g_borrow_domain.regions[1].size = band.size; + g_borrow_domain.regions[1].attributes = WT_MEM_ATTR_READ | WT_MEM_ATTR_WRITE; + g_borrow_domain.regions[2].base = (uintptr_t)WT_SPM_RXTX_PA; + g_borrow_domain.regions[2].size = (size_t)WT_SPM_RXTX_SIZE; + g_borrow_domain.regions[2].attributes = WT_MEM_ATTR_READ | WT_MEM_ATTR_WRITE; + g_borrow_domain.region_count = 3u; + + /* The borrower exists before the share names it. */ + co = wt_co_create_blocked_ex(stack, band.size, + (wt_co_entry_fn)wt_sp_ffa_borrow, (void*)arg); + if (co == NULL) { + return 0; + } + wt_co_set_domain(co, &g_borrow_domain, 1u); + /* The borrower's RX/TX pair is the band its domain maps, registered as + * FFA_RXTX_MAP would. */ + mb = wt_spm_sp_mailbox_of(co); + if (wt_ffa_mailbox_map(mb, (uint64_t)WT_SPM_RXTX_PA + WT_FFA_MEM_PAGE_SIZE, + (uint64_t)WT_SPM_RXTX_PA, 1u) != 0) { + return 0; + } + ok = (wt_spm_mem_bind(WT_FFA_ID_MEM_BORROWER, co, &g_borrow_domain) == 0) + ? mem_share_exchange(co, out_handle) : 0; + /* The coroutine slot outlives the proof: a partition created in it later + * must not inherit the borrower's binding or buffers. */ + wt_spm_mem_unbind(co); + if (wt_ffa_mailbox_unmap(mb) != 0) { + ok = 0; + } + /* However far the proof got, its share ends before any partition runs. */ + if ((wt_spm_mem_in_transaction((uint64_t)WT_SPM_SHARE_PA, + WT_FFA_MEM_PAGE_SIZE) != 0) && + (wt_spm_mem_reclaim(*out_handle, WT_FFA_ID_SPMC, 0u) != 0)) { + spmc_fail("mem share reclaim", *out_handle); + } + return ok; +} + +uint32_t wt_spm_prove_sint(void); + +void wt_spm_main(uint64_t boot_info_pa) +{ + wt_ffa_regs_t r; + uint32_t sint_id; + uint32_t partinfo_n = 0u; + uint64_t share_handle = 0u; + int shared; + + wt_el3_puts("[SPM] spmc entered at S-EL1\r\n"); + consume_boot_info(boot_info_pa); + enable_mmu(boot_info_pa); + wt_spm_mem_init(); + wt_spm_mem_ns_window((uint64_t)WT_NS_IMAGE_PA, + (uint64_t)WT_PSA_NS_WINDOW_SIZE); +#if defined(WT_EL3_NS_SMOKE) + wt_spm_psa_init((uint64_t)WT_NS_IMAGE_PA, + (uint64_t)WT_NS_IMAGE_PA + WT_PSA_NS_WINDOW_SIZE); +#endif + if (wt_spm_prove_tick()) { + wt_el3_puts("[SPM] tick ok intid=29\r\n"); + } + else { + spmc_fail("tick", 0u); + } +#if defined(WT_FFA_ACS) && (WT_FFA_ACS == 1) + /* The conformance partitions time their waits on the virtual counter + * (CNTKCTL_EL1.EL0VCTEN); production partitions get no EL0 time source. */ + wt_write_cntkctl_el1(wt_read_cntkctl_el1() | 0x2u); + wt_isb(); +#endif + if (prove_coroutine()) { + wt_el3_puts("[SPM] coroutine ok\r\n"); + } + else { + spmc_fail("coroutine", 0u); + } + if (prove_el0()) { + wt_el3_puts("[SPM] el0 svc ok\r\n"); + } + else { + spmc_fail("el0 svc", 0u); + } + if (prove_ffa_direct()) { + wt_el3_puts("[SPM] ffa direct ok\r\n"); + } + else { + spmc_fail("ffa direct", 0u); + } + if (prove_preempt()) { + wt_el3_puts("[SPM] preempt ok\r\n"); + } + else { + spmc_fail("preempt", 0u); + } + if (prove_orphan()) { + wt_el3_puts("[SPM] orphan ok\r\n"); + } + else { + spmc_fail("orphan", 0u); + } + sint_id = wt_spm_prove_sint(); + if (sint_id != 0u) { + wt_el3_puts("[SPM] sint gic ok intid=0x"); + wt_el3_puthex(sint_id, 2u); + wt_el3_puts("\r\n"); + } + else { + spmc_fail("sint gic", 0u); + } + if (prove_partinfo(&partinfo_n)) { + wt_el3_puts("[SPM] partinfo ok n="); + wt_el3_putdec(partinfo_n); + wt_el3_puts("\r\n"); + } + else { + spmc_fail("partinfo", partinfo_n); + } + shared = prove_mem_share(&share_handle); +#if defined(WT_EL3_BOOT_NEG_PROBE) && (WT_EL3_BOOT_NEG_PROBE == 3) + shared = 0; +#endif + if (shared) { + wt_el3_puts("[SPM] mem share ok handle=0x"); + wt_el3_puthex(share_handle, 4u); + wt_el3_puts("\r\n"); + } + else { + spmc_fail("mem share", share_handle); + } + discover_spmd(); + prove_console_log(); + wt_platform_console_flush(); + + /* The neutral core takes over: partitions, services, then the FF-A + * idle through wt_spm_idle when no Normal world is runnable. */ + g_wt_spm_partitions_live = 1u; +#if defined(WT_MSP_OVF_PROBE) && (WT_MSP_OVF_PROBE == 1) + /* mspovfneg: push on the SPM stack until it reaches the guard page. */ + __asm__ volatile("1: stp xzr, xzr, [sp, #-16]!\n b 1b" ::: "memory"); +#endif + wt_boot_run(); + + ffa_call(&r, WT_FFA_MSG_WAIT, 0u); + spmc_fail("boot_run returned", r.x[0]); +} + +/* Answer a forwarded call: FFA_SUCCESS with w2/w3, or FFA_ERROR with ret. The + * idle loop issues the reply SMC, whose return is the next event. */ +static void ns_reply(wt_ffa_regs_t* r, int ret, uint64_t w2, uint64_t w3) +{ + unsigned int i; + + for (i = 0u; i < 8u; i++) { + r->x[i] = 0u; + } + if (ret == 0) { + r->x[0] = WT_FFA_SUCCESS32; + r->x[2] = w2; + r->x[3] = w3; + } + else { + r->x[0] = WT_FFA_ERROR; + r->x[2] = (uint64_t)(uint32_t)ret; + } +} + +/* Answer with what an endpoint handed back: x0-x7, or x0-x17 for RESP2. */ +static void ns_reply_regs(wt_ffa_regs_ext_t* e, const uint64_t* out) +{ + unsigned int count = wt_ffa_msg_reg_count(out[0]); + unsigned int i; + + for (i = 0u; i < WT_FFA_MSG_REGS; i++) { + e->base.x[i] = out[i]; + } + for (i = WT_FFA_MSG_REGS; i < count; i++) { + e->ext[i - WT_FFA_MSG_REGS] = out[i]; + } +} + +/* Nothing to run on the Secure side: every event the SPMD delivers is + * reported until the Secure virtual instance dispatches them. */ +/* The SPMC's own receivers, the PSA framework endpoint and the test echo + * partition, answer FFA_MSG_SEND_DIRECT_REQ32/64 only. */ +#define WT_SPM_DIRECT_REQ_ONLY WT_FFA_PARTINFO_PROP_DIRECT_RECV + +/* A direct request the SPMD relayed from the Normal world: validate it at the + * NS-physical instance, deliver it to the waiting receiver, and send the + * partition's response back with FFA_MSG_SEND_DIRECT_RESP32; that SMC's + * return is the next event. A request is answered with FFA_ERROR instead: + * DENIED when the receiver does not take that kind of request (Tables 15.8 + * and 15.16), INVALID_PARAMETERS when its id names no endpoint. */ +static void direct_request(wt_ffa_regs_ext_t* e) +{ + uint64_t req[WT_FFA_MSG_REGS_EXT]; + uint64_t resp[WT_FFA_MSG_REGS_EXT]; + wt_ffa_regs_t* r = &e->base; + struct wt_co* co = NULL; + uint16_t receiver = wt_ffa_direct_receiver(r->x[1]); + uint32_t fid = (uint32_t)r->x[0]; + uint32_t props = 0u; + int ret = wt_ffa_direct_req_check(r->x, WT_FFA_INSTANCE_NS_PHYSICAL); + unsigned int i; + + wt_el3_puts("[SPM] direct req from=0x"); + wt_el3_puthex(wt_ffa_direct_sender(r->x[1]), 4u); + wt_el3_puts(" to=0x"); + wt_el3_puthex(receiver, 4u); + wt_el3_puts("\r\n"); + if ((ret == 0) && + (wt_ffa_direct_sender(r->x[1]) != WT_FFA_ID_NS_PRIMARY)) { + ret = WT_FFA_INVALID_PARAMETERS; + } + if ((ret == 0) && (receiver == WT_FFA_ID_PSA)) { + ret = wt_ffa_direct_req_allowed(WT_SPM_DIRECT_REQ_ONLY, fid, 1); + if (ret == 0) { + (void)wt_spm_psa_framework(r); + return; + } + } + if ((ret == 0) && (receiver == WT_FFA_ID_ECHO)) { + co = wt_spm_ffa_echo_partition(); + props = WT_SPM_DIRECT_REQ_ONLY; + ret = (co != NULL) ? 0 : WT_FFA_INVALID_PARAMETERS; + } + else if (ret == 0) { + ret = wt_spm_partition_props(receiver, &props); + co = wt_spm_ffa_native_by_id(receiver); + } + if (ret == 0) { + ret = wt_ffa_direct_req_allowed(props, fid, 1); + } + if (ret == 0) { + for (i = 0u; i < WT_FFA_MSG_REGS; i++) { + req[i] = r->x[i]; + } + for (i = WT_FFA_MSG_REGS; i < WT_FFA_MSG_REGS_EXT; i++) { + req[i] = e->ext[i - WT_FFA_MSG_REGS]; + } + ret = (co != NULL) ? wt_spm_ffa_direct_deliver(co, req, resp) + : WT_FFA_INVALID_PARAMETERS; + } + if (ret != 0) { + ns_reply(r, ret, 0u, 0u); + return; + } + ns_reply_regs(e, resp); +} + +/* The Normal-world endpoint's mailbox (7.2.2): the SPMC is the producer of its + * RX buffer, so the pair and its ownership live here, not in the SPMD. */ +static wt_ffa_mailbox_t g_ns_mailbox; + +/* The version the Normal world negotiated (13.2.3.2): the SPMD forwards each + * FFA_VERSION it makes until that version is locked. */ +static wt_ffa_version_state_t g_ns_version; + +static void ns_version(wt_ffa_regs_t* r) +{ + wt_ffa_fwk_version_resp(r->x, wt_ffa_version_negotiate( + &g_ns_version, (uint32_t)r->x[3], WT_FFA_VERSION_1_2)); +} + +/* 18.2.4: no partition registers for power management messages (manifest + * partitions take no FF-A messages), so the SPMC answers alone. It denies + * CPU_OFF, being resident on the only core, and any id 18.2.4 does not list. */ +static void ns_power(wt_ffa_regs_t* r) +{ + uint32_t psci = (uint32_t)r->x[3]; + int32_t status = WT_FFA_DENIED; + + switch (psci) { + case WT_PSCI_CPU_SUSPEND32: + case WT_PSCI_CPU_SUSPEND64: + case WT_PSCI_SYSTEM_OFF: + case WT_PSCI_SYSTEM_RESET: + status = 0; + break; + default: + break; + } + wt_el3_puts("[SPM] pm msg psci=0x"); + wt_el3_puthex(psci, 8u); + wt_el3_puts(" resp=0x"); + wt_el3_puthex((uint32_t)status, 8u); + wt_el3_puts("\r\n"); + wt_ffa_fwk_pm_resp(r->x, status); +} + +uint32_t wt_spm_ns_ffa_version(void) +{ + return wt_ffa_version_of(&g_ns_version, WT_FFA_VERSION_1_2); +} + +static int ns_range_ok(uint64_t addr, uint64_t len) +{ + uint64_t base = (uint64_t)WT_NS_IMAGE_PA; + uint64_t limit = base + (uint64_t)WT_PSA_NS_WINDOW_SIZE; + + return ((len != 0u) && (addr >= base) && (addr < limit) && + (len <= (limit - addr))) ? 1 : 0; +} + +int wt_spm_ns_mailbox_overlaps(uint64_t base, uint64_t size) +{ + return wt_ffa_mailbox_overlaps(&g_ns_mailbox, base, size); +} + +/* FFA_RXTX_MAP from the Normal world: both buffers must lie in the window of + * Non-secure memory the SPMC maps, still the Normal world's and clear of every + * memory transaction. */ +static void ns_rxtx_map(wt_ffa_regs_t* r) +{ + uint64_t span = (uint64_t)((uint32_t)r->x[3] & 0x3Fu) * WT_FFA_MEM_PAGE_SIZE; + int ret = 0; + + if ((g_ns_mailbox.mapped == 0u) && + ((ns_range_ok(r->x[1], span) == 0) || (ns_range_ok(r->x[2], span) == 0) || + (wt_spm_mem_ns_owns(r->x[1], span) == 0) || + (wt_spm_mem_ns_owns(r->x[2], span) == 0) || + (wt_spm_mem_in_transaction(r->x[1], span) != 0) || + (wt_spm_mem_in_transaction(r->x[2], span) != 0))) { + ret = WT_FFA_INVALID_PARAMETERS; + } + if (ret == 0) { + ret = wt_ffa_mailbox_map(&g_ns_mailbox, r->x[1], r->x[2], + (uint32_t)r->x[3]); + } + ns_reply(r, ret, 0u, 0u); +} + +static void ns_rxtx_unmap(wt_ffa_regs_t* r) +{ + int ret = ((((uint32_t)r->x[1] >> 16) & 0xFFFFu) == WT_FFA_ID_NS_PRIMARY) + ? wt_ffa_mailbox_unmap(&g_ns_mailbox) + : WT_FFA_INVALID_PARAMETERS; + + if (ret == 0) { + wt_spm_mem_frag_abort(WT_FFA_ID_NS_PRIMARY); + } + ns_reply(r, ret, 0u, 0u); +} + +/* FFA_PARTITION_INFO_GET forwarded from the Normal world: descriptors go to + * the guest's RX buffer, a count-only request needs none. */ +static void ns_partition_info_get(wt_ffa_regs_t* r) +{ + uint32_t count = 0u; + uint32_t size = 0u; + int ret = wt_spm_partition_info(r->x, wt_spm_ns_ffa_version(), + &g_ns_mailbox, &count, &size); + + ns_reply(r, ret, count, size); +} + +/* FFA_RUN forwarded from the Normal world: w1 names the endpoint and its + * vCPU; what it hands back (response, FFA_YIELD, FFA_MSG_WAIT) is the reply. */ +static void ns_run(wt_ffa_regs_ext_t* e) +{ + uint64_t out[WT_FFA_MSG_REGS_EXT]; + struct wt_co* co = NULL; + uint16_t id = 0u; + int ret = wt_ffa_run_target((uint32_t)e->base.x[1], &id); + + if (ret == 0) { + co = wt_spm_ffa_endpoint_by_id(id); + ret = (co != NULL) ? wt_spm_ffa_run(co, WT_FFA_ID_NS_PRIMARY, out) + : WT_FFA_INVALID_PARAMETERS; + } + + if (ret != 0) { + ns_reply(&e->base, ret, 0u, 0u); + return; + } + ns_reply_regs(e, out); +} + +/* FFA_INTERRUPT the SPMD signalled because a Secure interrupt preempted the + * Normal world (Ch.9). It carries no id (12.4.1 item 3): the SPMC takes the + * interrupt from the GIC, schedules (nothing else is runnable here) and yields + * the CPU back with FFA_NORMAL_WORLD_RESUME. The resume SMC's return is the + * next event. */ +static void ns_interrupt(wt_ffa_regs_t* r) +{ + uint32_t intid = wt_spm_ns_sint_take(); + struct wt_co* owner; + unsigned int i; + + if (intid == WT_GIC_INTID_SECURE_TIMER) { + wt_spm_twdog_tick(); + wt_el3_puts("[SPM] ns preempt intid=0x"); + wt_el3_puthex(intid, 2u); + wt_el3_puts("\r\n"); + } + else { + owner = wt_spm_sint_owner(intid); + if (owner != NULL) { + /* Table 9.1: signal a waiting owner, queue for a busy one. */ + if (wt_spm_ffa_signal_deliver(owner, intid) != 0) { + wt_spm_sint_queue_for(owner, intid); + } + } + else { + wt_el3_puts("[SPM] ns preempt intid=0x"); + wt_el3_puthex(intid, 3u); + wt_el3_puts("\r\n"); + } + } + for (i = 0u; i < 8u; i++) { + r->x[i] = 0u; + } + r->x[0] = WT_FFA_NORMAL_WORLD_RESUME; +} + +/* At this physical instance FFA_MEM_FRAG_RX/TX carry the Owner's id in + * w4[31:16], bits[15:0] SBZ (Table 4.7); the Normal-world owner is the primary + * endpoint. */ +#define WT_NS_FRAG_W4 ((uint64_t)WT_FFA_ID_NS_PRIMARY << 16) + +static void ns_frag_rx_reply(wt_ffa_regs_t* r, uint64_t handle, uint32_t offset) +{ + unsigned int i; + + for (i = 0u; i < 8u; i++) { + r->x[i] = 0u; + } + r->x[0] = WT_FFA_MEM_FRAG_RX; + r->x[1] = handle & 0xFFFFFFFFu; + r->x[2] = handle >> 32; + r->x[3] = (uint64_t)offset; + r->x[4] = WT_NS_FRAG_W4; +} + +static void ns_handle_reply(wt_ffa_regs_t* r, int ret, uint64_t handle) +{ + unsigned int i; + + for (i = 0u; i < 8u; i++) { + r->x[i] = 0u; + } + if (ret == 0) { + r->x[0] = WT_FFA_SUCCESS32; + r->x[2] = handle & 0xFFFFFFFFu; + r->x[3] = handle >> 32; + } + else { + r->x[0] = WT_FFA_ERROR; + r->x[2] = (uint64_t)(uint32_t)ret; + } +} + +/* FFA_MEM_SHARE / LEND / DONATE forwarded from the Normal world: the guest's + * descriptor (length in w1, this fragment's in w2) is in its TX buffer inside + * the SPMC's Non-secure window; w3/x3 and w4 name no dynamically allocated + * buffer, which FFA_FEATURES does not offer. Validate and register it; reply + * with the handle in w2/w3 or an error. A malformed descriptor is refused, + * never a crash. The reply SMC's return is the next event. */ +static void ns_mem_send(wt_ffa_regs_t* r, wt_ffa_mem_op_t op) +{ + uint64_t addr = 0u; + uint32_t total = (uint32_t)r->x[1]; + uint32_t frag = (uint32_t)r->x[2]; + uint64_t handle = 0u; + int ret; + + ret = wt_ffa_mem_tx_buffer(&g_ns_mailbox, r->x[3], (uint32_t)r->x[4], frag, + &addr); + if ((ret == 0) && ((frag < 1u) || (frag > total) || + (ns_range_ok(addr, (uint64_t)frag) == 0))) { + ret = WT_FFA_INVALID_PARAMETERS; + } + if (ret == 0) { + ret = wt_spm_mem_ns_send(op, (const uint8_t*)(uintptr_t)addr, frag, + total, &handle); + } + if ((ret == 0) && (frag < total)) { + ns_frag_rx_reply(r, handle, frag); + return; + } + ns_handle_reply(r, ret, handle); +} + +/* FFA_MEM_FRAG_TX forwarded from the Normal world: the next fragment of a + * descriptor it began sending, in the TX buffer the first one used, which is + * still mapped (an unmap aborts the transfer); the last one completes the + * send. */ +static void ns_mem_frag_tx(wt_ffa_regs_t* r) +{ + uint64_t handle = (uint64_t)(uint32_t)r->x[1] | + ((uint64_t)(uint32_t)r->x[2] << 32); + uint32_t len = (uint32_t)r->x[3]; + const uint8_t* frag = NULL; + uint64_t tx = 0u; + uint32_t offset = 0u; + int done = 0; + int ret = WT_FFA_INVALID_PARAMETERS; + + if (((uint32_t)r->x[4] & 0xFFFF0000u) == (uint32_t)WT_NS_FRAG_W4) { + if ((wt_ffa_mem_tx_buffer(&g_ns_mailbox, 0u, 0u, len, &tx) == 0) && + (ns_range_ok(tx, (uint64_t)len) != 0)) { + frag = (const uint8_t*)(uintptr_t)tx; + } + ret = wt_spm_mem_frag_next(handle, WT_FFA_ID_NS_PRIMARY, frag, len, + &offset, &done); + } + if ((ret == 0) && (done == 0)) { + ns_frag_rx_reply(r, handle, offset); + return; + } + if (ret == 0) { + ret = wt_spm_mem_frag_share(handle, WT_FFA_ID_NS_PRIMARY); + } + ns_handle_reply(r, ret, handle); +} + +/* FFA_MEM_RECLAIM forwarded from the Normal world: w1/w2 = handle. */ +static void ns_mem_reclaim(wt_ffa_regs_t* r) +{ + uint64_t handle = (uint64_t)(uint32_t)r->x[1] | + ((uint64_t)(uint32_t)r->x[2] << 32); + unsigned int i; + int ret = wt_spm_mem_reclaim(handle, WT_FFA_ID_NS_PRIMARY, + (uint32_t)r->x[3]); + + for (i = 0u; i < 8u; i++) { + r->x[i] = 0u; + } + if (ret == 0) { + r->x[0] = WT_FFA_SUCCESS32; + } + else { + r->x[0] = WT_FFA_ERROR; + r->x[2] = (uint64_t)(uint32_t)ret; + } +} + +/* The Normal-world scheduler's notification calls, forwarded by the SPMD. + * The primary NS endpoint is both the only VM and its own scheduler. */ +static void ns_notif_bind(wt_ffa_regs_t* r, unsigned int unbind) +{ + uint32_t w1 = (uint32_t)r->x[1]; + uint32_t w2 = (uint32_t)r->x[2]; + uint64_t bitmap = (uint64_t)(uint32_t)r->x[3] | + ((uint64_t)(uint32_t)r->x[4] << 32); + int32_t ret; + + if (unbind != 0u) { + ret = wt_ffa_notif_unbind(WT_FFA_ID_NS_PRIMARY, w1, w2, bitmap); + } + else { + ret = wt_ffa_notif_bind(WT_FFA_ID_NS_PRIMARY, w1, w2, bitmap); + } + ns_reply(r, ret, 0u, 0u); +} + +static void ns_notif_set(wt_ffa_regs_t* r) +{ + uint64_t bitmap = (uint64_t)(uint32_t)r->x[3] | + ((uint64_t)(uint32_t)r->x[4] << 32); + + ns_reply(r, wt_spm_notif_set(WT_FFA_ID_NS_PRIMARY, (uint32_t)r->x[1], + (uint32_t)r->x[2], bitmap), 0u, 0u); +} + +static void ns_notif_get(wt_ffa_regs_t* r) +{ + wt_ffa_notif_get_result_t got; + int32_t ret = wt_ffa_notif_get(WT_FFA_ID_NS_PRIMARY, (uint32_t)r->x[1], + (uint32_t)r->x[2], &got); + + ns_reply(r, (int)ret, 0u, 0u); + if (ret == 0) { + wt_ffa_mailbox_rx_claim(&g_ns_mailbox, got.framework); + r->x[2] = (uint32_t)got.from_sp; + r->x[3] = (uint32_t)(got.from_sp >> 32); + r->x[4] = (uint32_t)got.from_vm; + r->x[5] = (uint32_t)(got.from_vm >> 32); + r->x[6] = (uint32_t)got.framework; + r->x[7] = (uint32_t)(got.framework >> 32); + } +} + +static void ns_notif_info_get(wt_ffa_regs_t* r, unsigned int is64) +{ + wt_ffa_notif_info_result_t info; + int32_t ret = wt_ffa_notif_info_get(WT_FFA_ID_NS_PRIMARY, + (is64 != 0u) ? 1 : 0, &info); + unsigned int i; + + ns_reply(r, (int)ret, 0u, 0u); + if (ret == 0) { + r->x[0] = (is64 != 0u) ? WT_FFA_SUCCESS64 : WT_FFA_SUCCESS32; + r->x[2] = info.w2; + for (i = 0u; i < WT_FFA_NOTIF_INFO_MAX_REGS; i++) { + r->x[3u + i] = info.regs[i]; + } + } +} + +/* FFA_MSG_SEND2 (16.4), shared by both conduits: the partition message in + * the caller's TX buffer is copied into the receiver's RX, whose RX-full + * framework notification tells the Normal-world scheduler to run it. Only a + * sender and a receiver whose properties advertise indirect messaging take + * part. */ +int wt_spm_msg2_deliver(uint16_t caller, uint32_t version, const uint8_t* tx, + uint32_t tx_size, wt_ffa_instance_t inst, uint32_t w1, + uint32_t w2) +{ + static const uint8_t ns_uuid[16]; + wt_ffa_msg2_t msg; + const wt_ffa_native_sp_t* natives; + const uint8_t* uuid = NULL; + uint32_t properties = 0u; + uint32_t rx_version = WT_FFA_VERSION_1_2; + wt_ffa_mailbox_t* mb = NULL; + size_t count = 0u; + size_t i; + uint64_t total; + int ret; + + ret = wt_ffa_msg2_parse(tx, tx_size, caller, version, inst, w1, w2, &msg); + if (ret == 0) { + ret = wt_spm_msg2_sender_allowed(caller); + } + if (ret == 0) { + if (msg.receiver == WT_FFA_ID_NS_PRIMARY) { + uuid = ns_uuid; + properties = WT_FFA_PARTINFO_PROP_INDIRECT; + rx_version = wt_spm_ns_ffa_version(); + mb = &g_ns_mailbox; + } + else { + natives = wt_spm_ffa_native_list(&count); + for (i = 0u; i < count; i++) { + if (wt_spm_ffa_native_id(i) == msg.receiver) { + uuid = natives[i].uuid; + properties = natives[i].properties; + rx_version = wt_spm_sp_ffa_version( + wt_spm_ffa_native_by_id(msg.receiver)); + mb = wt_spm_sp_mailbox_of( + wt_spm_ffa_native_by_id(msg.receiver)); + } + } + if (uuid == NULL) { + /* Table 15.4: a listed partition taking no indirect messages + * (a PSA partition) is DENIED, only an unknown id refused. */ + ret = (wt_spm_partition_props(msg.receiver, &properties) == 0) ? + WT_FFA_DENIED : WT_FFA_INVALID_PARAMETERS; + } + else if (wt_spm_sp_unavailable( + wt_spm_ffa_native_by_id(msg.receiver)) != 0) { + ret = WT_FFA_DENIED; + } + } + } + if ((ret == 0) && ((properties & WT_FFA_PARTINFO_PROP_INDIRECT) == 0u)) { + ret = WT_FFA_DENIED; + } + if ((ret == 0) && (wt_ffa_msg2_uuid_ok(msg.uuid, uuid) == 0)) { + ret = WT_FFA_INVALID_PARAMETERS; + } + if (ret == 0) { + ret = wt_ffa_notif_frame_ready(msg.receiver); + } + if (ret == 0) { + total = (uint64_t)wt_ffa_msg2_rx_offset(&msg, rx_version) + + (uint64_t)msg.size; + if ((mb != NULL) && (mb->mapped != 0u) && + (total > (uint64_t)(mb->pages * (uint32_t)WT_TABLES_PAGE_SIZE))) { + ret = WT_FFA_INVALID_PARAMETERS; + } + else { + ret = wt_ffa_mailbox_rx_post(mb); + } + } + if (ret == 0) { + wt_ffa_msg2_copy((uint8_t*)(uintptr_t)mb->rx, + mb->pages * (uint32_t)WT_TABLES_PAGE_SIZE, + rx_version, tx, &msg); + (void)wt_ffa_notif_frame_rx_full(msg.receiver, + wt_ffa_id_is_secure(caller)); + } + return ret; +} + +/* FFA_MSG_SEND2 forwarded from the Normal world. */ +static void ns_msg_send2(wt_ffa_regs_t* r) +{ + int ret; + + if (g_ns_mailbox.mapped == 0u) { + ns_reply(r, WT_FFA_DENIED, 0u, 0u); + return; + } + ret = wt_spm_msg2_deliver(WT_FFA_ID_NS_PRIMARY, wt_spm_ns_ffa_version(), + (const uint8_t*)(uintptr_t)g_ns_mailbox.tx, + g_ns_mailbox.pages * (uint32_t)WT_TABLES_PAGE_SIZE, + WT_FFA_INSTANCE_NS_PHYSICAL, + (uint32_t)r->x[1], (uint32_t)r->x[2]); + ns_reply(r, ret, 0u, 0u); +} + +/* FFA_PARTITION_INFO_GET_REGS forwarded from the Normal world. */ +static void ns_partition_info_get_regs(wt_ffa_regs_ext_t* e) +{ + uint64_t out[WT_FFA_MSG_REGS_EXT]; + int ret = wt_spm_partition_info_regs(e->base.x, out); + unsigned int i; + + if (ret != 0) { + ns_reply(&e->base, ret, 0u, 0u); + return; + } + for (i = 0u; i < WT_FFA_MSG_REGS; i++) { + e->base.x[i] = out[i]; + } + for (i = WT_FFA_MSG_REGS; i < WT_FFA_MSG_REGS_EXT; i++) { + e->ext[i - WT_FFA_MSG_REGS] = out[i]; + } +} + +/* One forwarded event; the reply is left in e for the loop's SMC. */ +#if defined(WT_EL3_NS_SMOKE) +/* An 8-register event delivered over ERET must arrive with x8-x17 zero + * (11.2), never with what the SPMC handed the monitor at its last SMC. */ +static void eret_sbz_probe(const wt_ffa_regs_ext_t* e) +{ + static uint8_t seen; + unsigned int i; + int clean = 1; + + if (wt_ffa_msg_reg_count((uint32_t)e->base.x[0]) != WT_FFA_MSG_REGS) { + return; + } + for (i = 0u; i < (WT_FFA_MSG_REGS_EXT - WT_FFA_MSG_REGS); i++) { + if (e->ext[i] != 0u) { + clean = 0; + } + } + if (!clean) { + wt_el3_puts("[SPM] eret sbz BAD\r\n"); + } + else if (seen == 0u) { + seen = 1u; + wt_el3_puts("[SPM] eret sbz ok\r\n"); + } +} +#endif + +static void idle_dispatch(wt_ffa_regs_ext_t* e) +{ + wt_ffa_regs_t* r = &e->base; + +#if defined(WT_EL3_NS_SMOKE) + eret_sbz_probe(e); +#endif + switch ((uint32_t)r->x[0]) { + case WT_FFA_MSG_SEND_DIRECT_REQ32: + case WT_FFA_MSG_SEND_DIRECT_REQ64: + case WT_FFA_MSG_SEND_DIRECT_REQ2: + if (wt_ffa_fwk_version_is_req(r->x) != 0) { + ns_version(r); + } + else if (wt_ffa_fwk_pm_is_req(r->x) != 0) { + ns_power(r); + } + else { + direct_request(e); + } + break; + case WT_FFA_RUN: + ns_run(e); + break; + case WT_FFA_PARTITION_INFO_GET: + ns_partition_info_get(r); + break; + case WT_FFA_PARTITION_INFO_GET_REGS: + ns_partition_info_get_regs(e); + break; + case WT_FFA_RXTX_MAP32: + case WT_FFA_RXTX_MAP64: + ns_rxtx_map(r); + break; + case WT_FFA_RXTX_UNMAP: + ns_rxtx_unmap(r); + break; + case WT_FFA_RX_RELEASE: + /* w1[15:0] names the VM whose RX buffer is released (Table + * 13.21); only the primary endpoint has a pair here. */ + ns_reply(r, + (((uint32_t)r->x[1] & 0xFFFFu) == WT_FFA_ID_NS_PRIMARY) + ? wt_ffa_mailbox_rx_release(&g_ns_mailbox) + : WT_FFA_INVALID_PARAMETERS, + 0u, 0u); + break; + case WT_FFA_INTERRUPT: + ns_interrupt(r); + break; + case WT_FFA_MEM_SHARE32: + case WT_FFA_MEM_SHARE64: + ns_mem_send(r, WT_FFA_MEM_OP_SHARE); + break; + case WT_FFA_MEM_LEND32: + case WT_FFA_MEM_LEND64: + ns_mem_send(r, WT_FFA_MEM_OP_LEND); + break; + case WT_FFA_MEM_DONATE32: + case WT_FFA_MEM_DONATE64: + ns_mem_send(r, WT_FFA_MEM_OP_DONATE); + break; + case WT_FFA_MEM_RETRIEVE_REQ32: + case WT_FFA_MEM_RETRIEVE_REQ64: + case WT_FFA_MEM_RELINQUISH: + /* The Normal world only ever owns: nothing is lent to it. */ + ns_reply(r, WT_FFA_DENIED, 0u, 0u); + break; + case WT_FFA_MEM_RECLAIM: + ns_mem_reclaim(r); + break; + case WT_FFA_MEM_FRAG_TX: + ns_mem_frag_tx(r); + break; + case WT_FFA_MEM_FRAG_RX: + /* Nothing is lent to the Normal world, so no retrieve response + * is ever outstanding for it to ask the rest of. */ + ns_reply(r, WT_FFA_INVALID_PARAMETERS, 0u, 0u); + break; + case WT_FFA_NOTIFICATION_BITMAP_CREATE: + ns_reply(r, wt_ffa_notif_bitmap_create(WT_FFA_ID_NS_PRIMARY, + (uint32_t)r->x[1], (uint32_t)r->x[2]), 0u, 0u); + break; + case WT_FFA_NOTIFICATION_BITMAP_DESTROY: + ns_reply(r, wt_ffa_notif_bitmap_destroy(WT_FFA_ID_NS_PRIMARY, + (uint32_t)r->x[1]), 0u, 0u); + break; + case WT_FFA_NOTIFICATION_BIND: + ns_notif_bind(r, 0u); + break; + case WT_FFA_NOTIFICATION_UNBIND: + ns_notif_bind(r, 1u); + break; + case WT_FFA_NOTIFICATION_SET: + ns_notif_set(r); + break; + case WT_FFA_NOTIFICATION_GET: + ns_notif_get(r); + break; + case WT_FFA_NOTIFICATION_INFO_GET32: + ns_notif_info_get(r, 0u); + break; + case WT_FFA_NOTIFICATION_INFO_GET64: + ns_notif_info_get(r, 1u); + break; + case WT_FFA_MSG_SEND2: + ns_msg_send2(r); + break; +#if defined(WT_FFA_ACS) && (WT_FFA_ACS == 1) + case WT_SPM_SVC_FID_TIMER_ARM: + /* The ACS platform layer's test timer, from the Normal world: + * the armed id keeps its Normal-world group so its expiry + * preempts a running partition for the Normal world to take. */ + ns_reply(r, (wt_spm_twdog_arm(NULL, (uint32_t)r->x[1], + (uint32_t)r->x[2]) == 0) ? + 0 : WT_FFA_INVALID_PARAMETERS, 0u, 0u); + break; + case WT_SPM_SVC_FID_TIMER_STOP: + wt_spm_twdog_stop(NULL); + ns_reply(r, 0, 0u, 0u); + break; +#endif + default: + wt_el3_puts("[SPM] unexpected event x0=0x"); + wt_el3_puthex(r->x[0], 8u); + wt_el3_puts("\r\n"); + ns_reply(r, WT_FFA_NOT_SUPPORTED, 0u, 0u); + break; + } +} + +void wt_spm_idle(void) +{ + wt_ffa_regs_ext_t e; + uint32_t event = 0u; + unsigned int i; + +#if defined(WT_EL3_TEST_DRIVER) && (WT_EL3_TEST_DRIVER == 1) + /* With every partition initialized and waiting, route the test Secure + * interrupt to the echo partition (signalled, then queued) before idling. */ + wt_spm_prove_sint_route(wt_spm_ffa_echo_partition()); +#endif + (void)memset(&e, 0, sizeof(e)); + e.base.x[0] = WT_FFA_MSG_WAIT; + for (;;) { + /* Only an extended reply carries x8-x17 out of the SPMC. */ + if (wt_ffa_reply_is_ext(event, (uint32_t)e.base.x[0]) == 0) { + for (i = 0u; i < (WT_FFA_MSG_REGS_EXT - WT_FFA_MSG_REGS); i++) { +#if defined(WT_EL3_NS_SMOKE) + /* Test only: a value the monitor must not hand back (11.2). */ + e.ext[i] = 0xC0DE0000u + i; +#else + e.ext[i] = 0u; +#endif + } + } + /* Every hand-off to the Normal world funnels through this SMC, so + * pending notification work raises the schedule-receiver SGI here. */ + if (wt_ffa_notif_sri_take() != 0) { + wt_gic->raise_ns_sgi(WT_FFA_SRI_INTID); + wt_el3_puts("[SPM] sri sgi\r\n"); + } + wt_platform_console_flush(); + wt_ffa_smc_ext(&e); + event = (uint32_t)e.base.x[0]; + idle_dispatch(&e); + } +} diff --git a/src/arch/aarch64/spm/spm_mem.c b/src/arch/aarch64/spm/spm_mem.c new file mode 100644 index 00000000..0b8c4445 --- /dev/null +++ b/src/arch/aarch64/spm/spm_mem.c @@ -0,0 +1,1469 @@ +/* spm_mem.c + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfTrust. + * + * wolfTrust 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. + * + * wolfTrust 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, see . + */ + +/* The SPMC memory-sharing relayer. Every page that can be shared is already in + * every partition's table as an EL1-only entry, so a retrieve flips the + * borrower's entries to EL0 in place and a relinquish flips them back; a lend + * does the reverse to the owner. No table is rebuilt and the pool never grows; + * each flip invalidates that table's ASID. */ + +#include "wolftrust/arch/aarch64/spm_mem.h" +#include "wolftrust/arch/aarch64/domain.h" +#include "wolftrust/arch/aarch64/ffa_abi.h" +#include "wolftrust/arch/aarch64/ffa_mem.h" +#include "wolftrust/arch/aarch64/spm_svc.h" +#include "wolftrust/arch/aarch64/tables.h" +#include "wolftrust/arch.h" +#include "wolftrust/types.h" + +#include + +#define WT_SPM_MEM_MAX_BIND 8u +/* Borrower mapping cookie: region i's entry existed before the grant. */ +#define WT_SPM_MEM_MAP_REGION(i) (1u << (i)) +/* Borrower mapping cookie: the retrieve granted read-only data access. */ +#define WT_SPM_MEM_MAP_RO 0x10u +/* Borrower mapping cookie: the retrieve asked for a wipe once the borrower + * lets go, which a relinquish may override but a fault cannot. */ +#define WT_SPM_MEM_MAP_ZERO_AFTER 0x20u +/* Owner cookie, above the send flags it keeps: a borrower asked for the memory + * to be zeroed, which happens once no borrower maps it any more. */ +#define WT_SPM_MEM_COOKIE_ZERO_PENDING 0x80000000u +/* Owner cookie: the owner itself only reads some of the memory, so nothing may + * wipe it or hand out write access to it. */ +#define WT_SPM_MEM_COOKIE_OWNER_RO 0x40000000u +/* Owner cookie: the owner faulted while a borrower held the memory; the last + * borrower to let go ends the transaction. */ +#define WT_SPM_MEM_COOKIE_OWNER_GONE 0x20000000u +/* Owner cookie: a share named the owner itself read-only, so it keeps only + * read access until it reclaims. */ +#define WT_SPM_MEM_COOKIE_SELF_RO 0x10000000u +/* No endpoint memory access descriptor index. */ +#define WT_SPM_MEM_NO_INDEX 0xFFFFFFFFu + +#if WT_FFA_MEM_MAX_REGIONS > 4u +#error "the borrower mapping cookie holds one bit per region, four at most" +#endif + +static wt_ffa_mem_registry_t g_reg; +static wt_spm_mem_binding_t g_bind[WT_SPM_MEM_MAX_BIND]; +static uint64_t g_ns_base; +static uint64_t g_ns_limit; + +void wt_spm_mem_init(void) +{ + unsigned int i; + + wt_ffa_mem_registry_init(&g_reg); + for (i = 0u; i < WT_SPM_MEM_MAX_BIND; i++) { + g_bind[i].co = NULL; + g_bind[i].dom = NULL; + g_bind[i].id = 0u; + g_bind[i].live = 0u; + } +} + +void wt_spm_mem_ns_window(uint64_t base, uint64_t size) +{ + g_ns_base = base; + g_ns_limit = base + size; +} + +static wt_spm_mem_binding_t* bind_by_id(uint16_t id) +{ + unsigned int i; + + for (i = 0u; i < WT_SPM_MEM_MAX_BIND; i++) { + if ((g_bind[i].live != 0u) && (g_bind[i].id == id)) { + return &g_bind[i]; + } + } + return NULL; +} + +int wt_spm_mem_bind(uint16_t id, struct wt_co* co, wt_secure_domain_t* dom) +{ + wt_spm_mem_binding_t* b; + unsigned int i; + + if ((co == NULL) || (dom == NULL)) { + return WT_FFA_INVALID_PARAMETERS; + } + b = bind_by_id(id); + if (b == NULL) { + for (i = 0u; i < WT_SPM_MEM_MAX_BIND; i++) { + if (g_bind[i].live == 0u) { + b = &g_bind[i]; + break; + } + } + } + if (b == NULL) { + return WT_FFA_NO_MEMORY; + } + b->co = co; + b->dom = dom; + b->id = id; + b->live = 1u; + return 0; +} + +const wt_spm_mem_binding_t* wt_spm_mem_binding(const struct wt_co* co) +{ + unsigned int i; + + for (i = 0u; i < WT_SPM_MEM_MAX_BIND; i++) { + if ((g_bind[i].live != 0u) && (g_bind[i].co == co)) { + return &g_bind[i]; + } + } + return NULL; +} + +static int id_is_secure(uint16_t id) +{ + return (id & 0x8000u) != 0u; +} + +/* The FF-A version endpoint id negotiated, which the descriptors it sends and + * receives follow (DEN0077A 18.5.3); the SPMC's own is 1.2. */ +static uint32_t caller_version(uint16_t id) +{ + const wt_spm_mem_binding_t* b; + + if (!id_is_secure(id)) { + return wt_spm_ns_ffa_version(); + } + b = bind_by_id(id); + return (b != NULL) ? wt_spm_sp_ffa_version(b->co) : WT_FFA_VERSION_1_2; +} + +static int ranges_overlap(uint64_t base, uint64_t size, uintptr_t other, + size_t other_size) +{ + return (base < ((uint64_t)other + (uint64_t)other_size)) && + ((uint64_t)other < (base + size)); +} + +/* The image every partition executes. */ +static int platform_shared(uint64_t base, uint64_t size) +{ + wt_memory_region_t shared[4]; + size_t n = wt_platform_sp_shared_regions(shared, 4u); + size_t i; + + for (i = 0u; i < n; i++) { + if (ranges_overlap(base, size, shared[i].base, shared[i].size)) { + return 1; + } + } + return 0; +} + +/* The image every partition executes and memory a manifest marks shared are + * no one partition's own, even where its table reaches them at EL0. */ +static int common_memory(const wt_secure_domain_t* dom, uint64_t base, + uint64_t size) +{ + size_t i; + + if (platform_shared(base, size) != 0) { + return 1; + } + for (i = 0u; i < dom->region_count; i++) { + if (((dom->regions[i].attributes & WT_MEMORY_ATTR_SHARED) != 0u) && + ranges_overlap(base, size, dom->regions[i].base, + dom->regions[i].size)) { + return 1; + } + } + return 0; +} + +/* A partition whose table holds a page of the Normal world's window as its + * own, even one it made no-access or lends on, was donated it or borrows it, + * so the page is no longer the Normal world's (1.3.1 rules 5 and 6). */ +static int ns_page_held(uint64_t at) +{ + unsigned int i; + + for (i = 0u; i < WT_SPM_MEM_MAX_BIND; i++) { + if ((g_bind[i].live != 0u) && + (wt_domain_page_claimed(g_bind[i].dom->regions, + g_bind[i].dom->region_count, + (uintptr_t)at) != 0)) { + return 1; + } + } + return 0; +} + +int wt_spm_mem_ns_owns(uint64_t base, uint64_t size) +{ + uint64_t at; + + if ((size == 0u) || (base < g_ns_base) || (base >= g_ns_limit) || + (size > (g_ns_limit - base))) { + return 0; + } + for (at = base & ~(uint64_t)(WT_FFA_MEM_PAGE_SIZE - 1u); at < (base + size); + at += WT_FFA_MEM_PAGE_SIZE) { + if (ns_page_held(at) != 0) { + return 0; + } + } + return 1; +} + +/* The live transaction one of whose regions holds the page at, or NULL. */ +static const wt_ffa_mem_handle_entry_t* covering_entry(uint64_t at) +{ + const wt_ffa_mem_handle_entry_t* e; + unsigned int i; + uint32_t r; + + for (i = 0u; i < WT_FFA_MEM_MAX_HANDLES; i++) { + e = &g_reg.entries[i]; + if (e->state == (uint8_t)WT_FFA_MEM_STATE_FREE) { + continue; + } + for (r = 0u; r < (uint32_t)e->region_count; r++) { + if ((at >= e->regions[r].base) && + ((at - e->regions[r].base) < + ((uint64_t)e->regions[r].page_count * WT_FFA_MEM_PAGE_SIZE))) { + return e; + } + } + } + return NULL; +} + +/* A lender keeps no access and a donor no ownership (Table 1.3 Owner-LA and + * !Owner-NA); a share leaves the owner its access (Owner-SA), read-only where + * the share named it so (1.11.3.1). */ +int wt_spm_mem_ns_access(uint64_t base, uint64_t size, int write) +{ + const wt_ffa_mem_handle_entry_t* e; + uint64_t at; + int ok; + + if (size == 0u) { + return 1; + } + ok = ((base >= g_ns_base) && (base < g_ns_limit) && + (size <= (g_ns_limit - base))) ? 1 : 0; + for (at = base & ~(uint64_t)(WT_FFA_MEM_PAGE_SIZE - 1u); + (ok != 0) && (at < (base + size)); at += WT_FFA_MEM_PAGE_SIZE) { + e = covering_entry(at); + if (e == NULL) { + ok = (ns_page_held(at) == 0) ? 1 : 0; + } + else { + ok = ((e->owner == WT_FFA_ID_NS_PRIMARY) && + (e->state == (uint8_t)WT_FFA_MEM_STATE_SHARED) && + ((write == 0) || + ((e->owner_cookie & WT_SPM_MEM_COOKIE_SELF_RO) == 0u))) + ? 1 : 0; + } + } + return ok; +} + +/* Only memory the sender owns outright may be sent (10.10): Non-secure memory + * inside the window the SPMC maps that no partition holds, or Normal pages a + * partition reaches at EL0, all in one security state (*ns). A Device page is + * DENIED: a borrower's Normal mapping of it would be more permissive than the + * sender's (1.10.4.2 item 1). The result is the least access it has over the + * range (WT_DOMAIN_ACCESS_*). The SPMC's own sends are its boot self-test. */ +static int sender_owns(uint16_t sender, const wt_ffa_mem_region_t* r, + uint8_t* ns) +{ + const wt_spm_mem_binding_t* b; + uint64_t size = (uint64_t)r->page_count * WT_FFA_MEM_PAGE_SIZE; + uint64_t at; + uint8_t page_ns; + int access = WT_DOMAIN_ACCESS_RW; + int page; + + *ns = 0u; + if (sender == WT_FFA_ID_SPMC) { + return WT_DOMAIN_ACCESS_RW; + } + /* With no Hypervisor the Normal-world kernel is the relayer for its own + * mappings and takes its access away itself (DEN0140 1.4.1). */ + if (!id_is_secure(sender)) { + *ns = 1u; + return (wt_spm_mem_ns_owns(r->base, size) != 0) ? WT_DOMAIN_ACCESS_RW + : WT_DOMAIN_ACCESS_NONE; + } + b = bind_by_id(sender); + if ((b == NULL) || (common_memory(b->dom, r->base, size) != 0)) { + return WT_DOMAIN_ACCESS_NONE; + } + for (at = r->base; + (at < (r->base + size)) && (access != WT_DOMAIN_ACCESS_NONE); + at += WT_FFA_MEM_PAGE_SIZE) { + page = wt_domain_page_access(b->dom->regions, b->dom->region_count, + (uintptr_t)at); + page_ns = (uint8_t)wt_domain_page_ns(b->dom->regions, + b->dom->region_count, + (uintptr_t)at); + if (at == r->base) { + *ns = page_ns; + } + else if (page_ns != *ns) { + page = WT_DOMAIN_ACCESS_NONE; + } + if (page < access) { + access = page; + } + } + return access; +} + +/* Any partition the SPMC runs is an endpoint it manages (1.11.3.3); one not + * bound never retrieves, and its owner reclaims. One out of service never + * retrieves either, so a transaction naming it is refused (Table 2.5). */ +static int receiver_state(uint16_t id) +{ + const wt_spm_mem_binding_t* b = bind_by_id(id); + const struct wt_co* co = (b != NULL) ? b->co : wt_spm_sp_by_ffa_id(id); + + if ((b == NULL) && (co == NULL)) { + return WT_FFA_INVALID_PARAMETERS; + } + return (int)wt_spm_sp_unavailable(co); +} + +#define WT_SPM_MEM_OWNER_HOLD 0 +#define WT_SPM_MEM_OWNER_RELEASE 1 +#define WT_SPM_MEM_OWNER_WITHDRAW 2 + +/* A lend or donate takes the owner's own access away until it reclaims + * (10.10.1), and a share it named itself read-only in lowers it to reading + * (2.3.1.2 item 10); a reclaim puts back each page exactly as it was (1.10.2 + * item 4), withdrawing even that reading while it wipes. Only a partition's + * access is the SPMC's to take. */ +static void owner_access(const wt_ffa_mem_handle_entry_t* e, int how) +{ + const wt_spm_mem_binding_t* b = bind_by_id(e->owner); + int keep_read = (e->state == (uint8_t)WT_FFA_MEM_STATE_SHARED) ? 1 : 0; + uint32_t i; + + if ((b == NULL) || ((keep_read != 0) && + ((e->owner_cookie & WT_SPM_MEM_COOKIE_SELF_RO) == 0u))) { + return; + } + for (i = 0u; i < (uint32_t)e->region_count; i++) { + if (how == WT_SPM_MEM_OWNER_RELEASE) { + (void)wt_domain_owner_release(b->dom->regions, + b->dom->region_count, + (uintptr_t)e->regions[i].base, + e->regions[i].page_count); + } + else if (how == WT_SPM_MEM_OWNER_WITHDRAW) { + (void)wt_domain_owner_withdraw(b->dom->regions, + b->dom->region_count, + (uintptr_t)e->regions[i].base, + e->regions[i].page_count); + } + else { + (void)wt_domain_owner_hold(b->dom->regions, b->dom->region_count, + (uintptr_t)e->regions[i].base, + e->regions[i].page_count, keep_read); + } + } +} + +/* The zeros go out to memory, not just the SPMC's cache (1.11.4.1). */ +static void zero_regions(const wt_ffa_mem_handle_entry_t* e) +{ + uint64_t size; + uint32_t i; + + for (i = 0u; i < (uint32_t)e->region_count; i++) { + size = (uint64_t)e->regions[i].page_count * WT_FFA_MEM_PAGE_SIZE; + (void)memset((void*)(uintptr_t)e->regions[i].base, 0, (size_t)size); + wt_mmu_dcache_clean_inval(e->regions[i].base, size); + } +} + +/* The access permissions a sender may state (Table 5.14 usage): instruction + * access is always the relayer's to fill in (it only ever answers + * not-executable); a share or lend names the data access it grants, a donate + * hands over full ownership and names none. */ +static int send_permissions_ok(wt_ffa_mem_op_t op, uint8_t perms) +{ + uint8_t data = perms & WT_FFA_MEM_PERM_DATA_MASK; + + if ((perms & WT_FFA_MEM_PERM_INSTR_MASK) != WT_FFA_MEM_PERM_INSTR_NOT_SPEC) { + return WT_FFA_INVALID_PARAMETERS; + } + if (op == WT_FFA_MEM_OP_DONATE) { + return (data == WT_FFA_MEM_PERM_DATA_NOT_SPEC) ? 0 + : WT_FFA_INVALID_PARAMETERS; + } + return ((data == WT_FFA_MEM_PERM_DATA_RO) || + (data == WT_FFA_MEM_PERM_DATA_RW)) ? 0 : WT_FFA_INVALID_PARAMETERS; +} + +static int mem_share(const uint8_t* desc, size_t len, wt_ffa_mem_op_t op, + uint16_t sender, uint64_t reserved, uint64_t* out_handle) +{ + const wt_ffa_mem_handle_entry_t* e; + wt_ffa_mem_borrower_t* named; + wt_ffa_mem_txn_t txn; + wt_ffa_mem_region_t regs[WT_FFA_MEM_MAX_REGIONS]; + uint32_t n = 0u; + uint32_t i; + uint32_t self = WT_SPM_MEM_NO_INDEX; + uint32_t first = WT_SPM_MEM_NO_INDEX; + int owner_ro = 0; + int self_ro = 0; + uint16_t receiver = 0u; + uint16_t attributes = 0u; + uint8_t perms = 0u; + int access = WT_DOMAIN_ACCESS_NONE; + int ret; + + if (out_handle == NULL) { + return WT_FFA_INVALID_PARAMETERS; + } + ret = wt_ffa_mem_send_validate_at(desc, len, op, sender, + caller_version(sender), &txn); + if ((ret == 0) && (txn.receiver_count > WT_FFA_MEM_MAX_BORROWERS)) { + ret = WT_FFA_NO_MEMORY; + } + for (i = 0u; (ret == 0) && (i < txn.receiver_count); i++) { + ret = wt_ffa_mem_receiver(desc, len, &txn, i, &receiver, &perms); + /* A share may name the lender itself, once, with the data access it + * keeps while the memory is shared (1.11.3.1). */ + if ((ret == 0) && (receiver == sender) && + (op == WT_FFA_MEM_OP_SHARE) && (self == WT_SPM_MEM_NO_INDEX)) { + self = i; + self_ro = ((perms & WT_FFA_MEM_PERM_DATA_MASK) == + WT_FFA_MEM_PERM_DATA_RO) ? 1 : 0; + ret = send_permissions_ok(op, perms); + continue; + } + if ((ret == 0) && (first == WT_SPM_MEM_NO_INDEX)) { + first = i; + } + /* No SP-to-NS-Endpoint send is a DEN0140 Table 1.7 combination; rule + * 4 of 2.1.1.2, 2.2.1.2, and 2.3.1.2 makes one of Secure memory DENIED. */ + if ((ret == 0) && id_is_secure(sender) && !id_is_secure(receiver)) { + ret = WT_FFA_DENIED; + } + /* A borrower is a partition the SPMC can map into, never the sender. */ + if ((ret == 0) && (receiver == sender)) { + ret = WT_FFA_INVALID_PARAMETERS; + } + if (ret == 0) { + ret = receiver_state(receiver); + } + if (ret == 0) { + ret = send_permissions_ok(op, perms); + } + } + if ((ret == 0) && (first == WT_SPM_MEM_NO_INDEX)) { + ret = WT_FFA_INVALID_PARAMETERS; + } + /* Memory that becomes one receiver's alone (a donate, a lend to a single + * borrower) has its type chosen by that receiver; a share or a lend to + * several names it (Table 5.18 usage). */ + if ((ret == 0) && + ((op == WT_FFA_MEM_OP_DONATE) || + ((op == WT_FFA_MEM_OP_LEND) && (txn.receiver_count == 1u))) && + ((txn.attributes & WT_FFA_MEM_ATTR_TYPE_MASK) != 0u)) { + ret = WT_FFA_INVALID_PARAMETERS; + } + if (ret == 0) { + ret = wt_ffa_mem_regions_from_txn(desc, len, &txn, first, regs, + WT_FFA_MEM_MAX_REGIONS, &n); + } + for (i = 0u; (ret == 0) && (i < n); i++) { + access = sender_owns(sender, ®s[i], ®s[i].ns); + if (access == WT_DOMAIN_ACCESS_RO) { + owner_ro = 1; + } + /* A mapped RX/TX pair is the SPMC's to write and read until it is + * unmapped (DEN0077A 7.2.2.2), so it is never the sender's to hand on; + * and one memory region has one security state (Table 1.19). */ + if ((access == WT_DOMAIN_ACCESS_NONE) || + (regs[i].ns != regs[0].ns) || + (wt_ffa_mem_registry_overlaps(&g_reg, regs[i].base, + regs[i].page_count) != 0) || + (wt_spm_mailbox_overlaps(regs[i].base, + (uint64_t)regs[i].page_count * + WT_FFA_MEM_PAGE_SIZE) != 0)) { + ret = WT_FFA_DENIED; + } + } + /* A donate makes the receiver the owner with the owner's own data access, + * which the one permission a retrieve maps every region with must not + * exceed anywhere (1.10.2 item 2). */ + for (i = 0u; (ret == 0) && (op == WT_FFA_MEM_OP_DONATE) && (i < n); i++) { + regs[i].permissions = (uint8_t)(((owner_ro != 0) + ? WT_FFA_MEM_PERM_DATA_RO + : WT_FFA_MEM_PERM_DATA_RW) | + WT_FFA_MEM_PERM_INSTR_NX); + } + if (ret == 0) { + ret = wt_ffa_mem_send_attributes(txn.attributes, &attributes); + } + /* An owner that only reads the memory cannot have it wiped, nor hand out + * write access it does not hold, to a borrower or to itself (Table 5.20, + * 10.10.2, 1.10.2 item 1). */ + if ((ret == 0) && (owner_ro != 0)) { + if ((txn.flags & WT_FFA_MEM_FLAG_ZERO) != 0u) { + ret = WT_FFA_DENIED; + } + for (i = 0u; (ret == 0) && (i < txn.receiver_count); i++) { + ret = wt_ffa_mem_receiver(desc, len, &txn, i, &receiver, &perms); + if ((ret == 0) && ((perms & WT_FFA_MEM_PERM_DATA_MASK) == + WT_FFA_MEM_PERM_DATA_RW)) { + ret = WT_FFA_DENIED; + } + } + } + if (ret == 0) { + ret = wt_ffa_mem_receiver(desc, len, &txn, first, &receiver, &perms); + } + if ((ret == 0) && (reserved != 0u)) { + ret = wt_ffa_mem_share_register_as(&g_reg, op, sender, receiver, regs, + n, reserved); + if (ret == 0) { + *out_handle = reserved; + } + } + else if (ret == 0) { + ret = wt_ffa_mem_share_register(&g_reg, op, sender, receiver, regs, n, + out_handle); + } + for (i = first + 1u; (ret == 0) && (i < txn.receiver_count); i++) { + if (i == self) { + continue; + } + ret = wt_ffa_mem_receiver(desc, len, &txn, i, &receiver, &perms); + if (ret == 0) { + ret = wt_ffa_mem_handle_add_borrower(&g_reg, *out_handle, receiver, + perms); + } + if (ret != 0) { + (void)wt_ffa_mem_handle_reclaim(&g_reg, *out_handle, sender); + } + } + for (i = 0u; (ret == 0) && (i < txn.receiver_count); i++) { + ret = wt_ffa_mem_receiver(desc, len, &txn, i, &receiver, &perms); + named = (ret == 0) ? wt_ffa_mem_handle_borrower(&g_reg, *out_handle, + receiver) : NULL; + if (named != NULL) { + ret = wt_ffa_mem_receiver_impdef(desc, len, &txn, i, named->impdef); + } + } + if (ret == 0) { + /* The cookie keeps the owner's flags: it may ask for the memory to be + * zeroed before a borrower sees it. */ + wt_ffa_mem_handle_set_meta(&g_reg, *out_handle, txn.tag, + txn.flags | + ((owner_ro != 0) + ? WT_SPM_MEM_COOKIE_OWNER_RO + : 0u) | + ((self_ro != 0) + ? WT_SPM_MEM_COOKIE_SELF_RO + : 0u)); + wt_ffa_mem_handle_set_attributes(&g_reg, *out_handle, attributes); + if (wt_ffa_mem_handle_lookup(&g_reg, *out_handle, &e) == 0) { + owner_access(e, WT_SPM_MEM_OWNER_HOLD); + /* Once, with the owner's access gone and before any borrower can + * map the memory (Table 1.21 bit[0]). */ + if ((txn.flags & WT_FFA_MEM_FLAG_ZERO) != 0u) { + zero_regions(e); + } + } + } + return ret; +} + +/* Bits[31:3] are SBZ and ignored (Table 2.40). No access (b'00) maps the page + * away from EL0 whatever bit[2] says: S-EL1 keeps writing it, so + * SCTLR_EL1.WXN makes it execute-never (2.8.0.0.1). */ +static int perm_to_attributes(uint32_t perm, uint32_t* attributes) +{ + uint32_t data = perm & WT_FFA_PERM_DATA_MASK; + + if (data == WT_FFA_PERM_DATA_NONE) { + *attributes = 0u; + return 0; + } + if ((data == WT_FFA_PERM_DATA_RW) && ((perm & WT_FFA_PERM_XN) != 0u)) { + *attributes = WT_MEM_ATTR_READ | WT_MEM_ATTR_WRITE; + return 0; + } + if (data == WT_FFA_PERM_DATA_RO) { + *attributes = WT_MEM_ATTR_READ; + if ((perm & WT_FFA_PERM_XN) == 0u) { + *attributes |= WT_MEM_ATTR_EXEC; + } + return 0; + } + return WT_FFA_INVALID_PARAMETERS; +} + +/* Non-zero when a page of [base, base + size) lies in a region dom's manifest + * makes writable. */ +static int manifest_writable(const wt_secure_domain_t* dom, uint64_t base, + uint64_t size) +{ + size_t i; + + for (i = 0u; i < dom->region_count; i++) { + if (((dom->regions[i].attributes & WT_MEM_ATTR_WRITE) != 0u) && + ranges_overlap(base, size, dom->regions[i].base, + dom->regions[i].size)) { + return 1; + } + } + return 0; +} + +static int manifest_covers(const wt_secure_domain_t* dom, uint64_t at) +{ + size_t i; + + for (i = 0u; i < dom->region_count; i++) { + if ((at >= (uint64_t)dom->regions[i].base) && + ((at - (uint64_t)dom->regions[i].base) < + (uint64_t)dom->regions[i].size)) { + return 1; + } + } + return 0; +} + +static const wt_spm_mem_binding_t* bind_by_dom(const wt_secure_domain_t* dom) +{ + unsigned int i; + + for (i = 0u; i < WT_SPM_MEM_MAX_BIND; i++) { + if ((g_bind[i].live != 0u) && (g_bind[i].dom == dom)) { + return &g_bind[i]; + } + } + return NULL; +} + +/* The page at is one the partition may access as its own memory: its + * manifest names it, or a donate made it the receiver's (Owner-EA, DEN0140 + * 2.4.1.2 item 12), which is a page its table still gives it that no live + * transaction covers and no manifest shares. */ +static int partition_reaches(const wt_secure_domain_t* dom, uint64_t at) +{ + if (manifest_covers(dom, at) != 0) { + return 1; + } + return ((bind_by_dom(dom) != NULL) && + (wt_domain_page_owned(dom->regions, dom->region_count, + (uintptr_t)at) != 0) && + (common_memory(dom, at, WT_FFA_MEM_PAGE_SIZE) == 0) && + (wt_ffa_mem_registry_overlaps(&g_reg, at, 1u) == 0)) ? 1 : 0; +} + +/* Of those, the pages that are the partition's alone: never the image every + * partition runs or memory its manifest shares with another. */ +static int partition_owns(const wt_secure_domain_t* dom, uint64_t at) +{ + return ((common_memory(dom, at, WT_FFA_MEM_PAGE_SIZE) == 0) && + (partition_reaches(dom, at) != 0)) ? 1 : 0; +} + +int wt_spm_mem_rxtx_ok(const wt_secure_domain_t* dom, uint64_t va) +{ + if ((dom == NULL) || ((va % WT_FFA_MEM_PAGE_SIZE) != 0u) || + (va >= WT_TABLES_VA_LIMIT) || (partition_owns(dom, va) == 0)) { + return 0; + } + return ((wt_domain_page_access(dom->regions, dom->region_count, + (uintptr_t)va) == WT_DOMAIN_ACCESS_RW) && + (wt_domain_page_ns(dom->regions, dom->region_count, + (uintptr_t)va) == 0) && + (wt_ffa_mem_registry_overlaps(&g_reg, va, 1u) == 0)) ? 1 : 0; +} + +int wt_spm_mem_perm_get(const wt_secure_domain_t* dom, uint64_t va, + uint32_t* perm) +{ + uint32_t attributes = 0u; + + if ((dom == NULL) || (perm == NULL) || (va >= WT_TABLES_VA_LIMIT) || + ((va % WT_FFA_MEM_PAGE_SIZE) != 0u) || + (partition_reaches(dom, va) == 0) || + (wt_domain_get_permissions(dom->regions, dom->region_count, + (uintptr_t)va, &attributes) != + WT_TABLES_OK)) { + return WT_FFA_INVALID_PARAMETERS; + } + *perm = WT_FFA_PERM_DATA_NONE; + if ((attributes & WT_MEM_ATTR_WRITE) != 0u) { + *perm = WT_FFA_PERM_DATA_RW; + } + else if ((attributes & WT_MEM_ATTR_READ) != 0u) { + *perm = WT_FFA_PERM_DATA_RO; + } + if ((attributes & WT_MEM_ATTR_EXEC) == 0u) { + *perm |= WT_FFA_PERM_XN; + } + return 0; +} + +int wt_spm_mem_perm_set(const wt_secure_domain_t* dom, + const wt_ffa_mailbox_t* mb, uint64_t va, + uint32_t pages, uint32_t perm) +{ + uint64_t size = (uint64_t)pages * WT_FFA_MEM_PAGE_SIZE; + uint64_t at; + uint32_t attributes = 0u; + int ret; + + if (dom == NULL) { + return WT_FFA_INVALID_PARAMETERS; + } + ret = perm_to_attributes(perm, &attributes); + if ((ret == 0) && + ((pages == 0u) || (va >= WT_TABLES_VA_LIMIT) || + ((va % WT_FFA_MEM_PAGE_SIZE) != 0u) || + (pages > ((WT_TABLES_VA_LIMIT - va) / WT_TABLES_PAGE_SIZE)))) { + ret = WT_FFA_INVALID_PARAMETERS; + } + for (at = va; (ret == 0) && (at < (va + size)); + at += WT_FFA_MEM_PAGE_SIZE) { + if (partition_owns(dom, at) == 0) { + ret = WT_FFA_INVALID_PARAMETERS; + } + } + /* Table 2.41 keeps DENIED for a caller out of its initialization, so a + * region it may not re-permission is INVALID_PARAMETERS; an owner cannot + * change its access while a borrower may hold the memory (1.3.1 rule 7). */ + if ((ret == 0) && (wt_ffa_mem_registry_overlaps(&g_reg, va, pages) != 0)) { + ret = WT_FFA_INVALID_PARAMETERS; + } + if ((ret == 0) && (platform_shared(va, size) != 0)) { + ret = WT_FFA_INVALID_PARAMETERS; + } + /* Pages the SPMC writes at S-EL1 through the partition's own table, where + * an EL0 read-only page is read-only too, keep their data access: the + * mapped RX/TX pair, and the writable manifest memory of a partition the + * relayer does not bind, where the FF-M gate writes the buffers it names. */ + if ((ret == 0) && (wt_ffa_mailbox_overlaps(mb, va, size) != 0)) { + ret = WT_FFA_INVALID_PARAMETERS; + } + if ((ret == 0) && ((attributes & WT_MEM_ATTR_READ) != 0u) && + ((attributes & WT_MEM_ATTR_WRITE) == 0u) && + (bind_by_dom(dom) == NULL) && + (manifest_writable(dom, va, size) != 0)) { + ret = WT_FFA_INVALID_PARAMETERS; + } + if ((ret == 0) && + (wt_domain_set_permissions(dom->regions, dom->region_count, + (uintptr_t)va, (size_t)pages, attributes) != + WT_TABLES_OK)) { + ret = WT_FFA_INVALID_PARAMETERS; + } + return ret; +} + +int wt_spm_mem_in_transaction(uint64_t base, uint64_t size) +{ + return wt_ffa_mem_registry_overlaps(&g_reg, base, + (uint32_t)((size + WT_FFA_MEM_PAGE_SIZE - + 1u) / WT_FFA_MEM_PAGE_SIZE)); +} + +int wt_spm_mem_share(const uint8_t* desc, size_t len, wt_ffa_mem_op_t op, + uint16_t sender, uint64_t* out_handle) +{ + return mem_share(desc, len, op, sender, 0u, out_handle); +} + +/* Transactions whose descriptor is still arriving in fragments, at most one + * per sender (DEN0140 4.1.2). The first slot is the Normal world's alone, so + * partitions that hold theirs open can never starve it. */ +#define WT_SPM_MEM_FRAG_SLOTS 3u +#define WT_SPM_MEM_FRAG_NS_SLOTS 1u +static wt_ffa_mem_frag_t g_frag[WT_SPM_MEM_FRAG_SLOTS]; + +static wt_ffa_mem_frag_t* frag_slot(uint64_t handle, uint16_t sender) +{ + unsigned int i; + + for (i = 0u; i < WT_SPM_MEM_FRAG_SLOTS; i++) { + if ((g_frag[i].active != 0u) && (g_frag[i].handle == handle) && + (g_frag[i].sender == sender)) { + return &g_frag[i]; + } + } + return NULL; +} + +int wt_spm_mem_frag_begin(uint8_t op, uint16_t sender, const uint8_t* frag, + uint32_t frag_len, uint32_t total, uint64_t* handle) +{ + wt_ffa_mem_frag_t* slot = NULL; + uint64_t named = 0u; + uint64_t size = 0u; + unsigned int first = id_is_secure(sender) ? WT_SPM_MEM_FRAG_NS_SLOTS : 0u; + unsigned int last = id_is_secure(sender) ? WT_SPM_MEM_FRAG_SLOTS + : WT_SPM_MEM_FRAG_NS_SLOTS; + unsigned int i; + int ret = 0; + + if ((frag == NULL) || (handle == NULL)) { + return WT_FFA_INVALID_PARAMETERS; + } + /* A total the descriptor's own headers contradict is an invalid length, + * not the start of a transfer. */ + if ((wt_ffa_mem_frag_expected_at(frag, frag_len, + (op == WT_SPM_MEM_FRAG_OP_RETRIEVE) ? 1 : 0, + caller_version(sender), &size) != 0) && + (size != (uint64_t)total)) { + return WT_FFA_INVALID_PARAMETERS; + } + /* A retrieve request fragment names its region's handle up front. */ + if (op == WT_SPM_MEM_FRAG_OP_RETRIEVE) { + if (frag_len < (WT_FFA_MEM_TXN_OFF_HANDLE + 8u)) { + return WT_FFA_INVALID_PARAMETERS; + } + for (i = 0u; i < 8u; i++) { + named |= (uint64_t)frag[WT_FFA_MEM_TXN_OFF_HANDLE + i] << (8u * i); + } + } + /* The TX buffer stays busy with an unfinished transfer, which its sender + * may not abort (DEN0140 4.1.2 rules 6 and 9); one the relayer already + * aborted is dropped for the new one. */ + for (i = 0u; i < WT_SPM_MEM_FRAG_SLOTS; i++) { + if ((g_frag[i].active != 0u) && (g_frag[i].sender == sender)) { + if (g_frag[i].aborted == 0u) { + return WT_FFA_BUSY; + } + wt_ffa_mem_frag_reset(&g_frag[i]); + } + } + for (i = first; (i < last) && (slot == NULL); i++) { + if (g_frag[i].active == 0u) { + slot = &g_frag[i]; + } + } + if (slot == NULL) { + ret = WT_FFA_NO_MEMORY; + } + if (ret == 0) { + *handle = (op == WT_SPM_MEM_FRAG_OP_RETRIEVE) ? + named : wt_ffa_mem_handle_reserve(&g_reg); + ret = wt_ffa_mem_frag_begin(slot, *handle, sender, op, frag, frag_len, + total); + } + return ret; +} + +int wt_spm_mem_frag_next(uint64_t handle, uint16_t sender, const uint8_t* frag, + uint32_t frag_len, uint32_t* offset, int* done) +{ + wt_ffa_mem_frag_t* slot = frag_slot(handle, sender); + int ret; + + if ((slot == NULL) || (offset == NULL)) { + return WT_FFA_INVALID_PARAMETERS; + } + /* The relayer ends the transfer and says so (DEN0140 4.1.2 rule 8). */ + if (slot->aborted != 0u) { + wt_ffa_mem_frag_reset(slot); + return WT_FFA_ABORTED; + } + ret = wt_ffa_mem_frag_add(slot, handle, sender, frag, frag_len, done); + *offset = slot->received; + return ret; +} + +const uint8_t* wt_spm_mem_frag_desc(uint64_t handle, uint16_t sender, + uint32_t* len, uint8_t* op) +{ + wt_ffa_mem_frag_t* slot = frag_slot(handle, sender); + + if ((slot == NULL) || (slot->aborted != 0u) || + (slot->received != slot->total) || (len == NULL) || (op == NULL)) { + return NULL; + } + *len = slot->total; + *op = slot->op; + return slot->buf; +} + +void wt_spm_mem_frag_release(uint64_t handle, uint16_t sender) +{ + wt_ffa_mem_frag_reset(frag_slot(handle, sender)); +} + +/* Later fragments must come through the buffer the first one did (DEN0140 + * 4.1.2 rule 6), which an unmap takes away. */ +void wt_spm_mem_frag_abort(uint16_t sender) +{ + unsigned int i; + + for (i = 0u; i < WT_SPM_MEM_FRAG_SLOTS; i++) { + if ((g_frag[i].active != 0u) && (g_frag[i].sender == sender)) { + g_frag[i].aborted = 1u; + } + } +} + +int wt_spm_mem_frag_share(uint64_t handle, uint16_t sender) +{ + const uint8_t* desc; + uint64_t out = 0u; + uint32_t len = 0u; + uint8_t op = 0u; + int ret = WT_FFA_INVALID_PARAMETERS; + + desc = wt_spm_mem_frag_desc(handle, sender, &len, &op); + if ((desc != NULL) && (op != WT_SPM_MEM_FRAG_OP_RETRIEVE)) { + ret = mem_share(desc, (size_t)len, (wt_ffa_mem_op_t)op, sender, handle, + &out); + } + wt_spm_mem_frag_release(handle, sender); + return ret; +} + +/* The Normal world can rewrite its TX buffer while the SPMC reads it, so its + * descriptor is parsed only from one Secure copy, no larger than a reassembled + * one. */ +static uint8_t g_ns_desc[WT_FFA_MEM_FRAG_MAX]; + +int wt_spm_mem_ns_send(wt_ffa_mem_op_t op, const uint8_t* tx, + uint32_t frag_len, uint32_t total, uint64_t* handle) +{ + int ret = 0; + + if ((tx == NULL) || (handle == NULL) || (frag_len == 0u) || + (frag_len > total)) { + ret = WT_FFA_INVALID_PARAMETERS; + } + else if (frag_len > (uint32_t)sizeof(g_ns_desc)) { + ret = WT_FFA_NO_MEMORY; + } + if (ret == 0) { + (void)memcpy(g_ns_desc, tx, (size_t)frag_len); + if (frag_len < total) { + ret = wt_spm_mem_frag_begin((uint8_t)op, WT_FFA_ID_NS_PRIMARY, + g_ns_desc, frag_len, total, handle); + } + else { + ret = mem_share(g_ns_desc, (size_t)total, op, WT_FFA_ID_NS_PRIMARY, + 0u, handle); + } + } + return ret; +} + +/* What the borrower asked for against what the owner granted it (11.4.2): + * it may ask for less, never for more, and never for execution. */ +static int effective_permissions(int exclusive, int donate, uint8_t granted, + uint8_t asked, uint8_t* out) +{ + uint8_t data = asked & WT_FFA_MEM_PERM_DATA_MASK; + uint8_t instr = asked & WT_FFA_MEM_PERM_INSTR_MASK; + uint8_t granted_data = granted & WT_FFA_MEM_PERM_DATA_MASK; + + /* A lend or share borrower must state the data access it wants (DEN0140 + * 1.10.2 item 1, validated per 1.11.3.3). */ + if ((data == WT_FFA_MEM_PERM_DATA_RSVD) || + ((data == WT_FFA_MEM_PERM_DATA_NOT_SPEC) && (donate == 0)) || + (instr == WT_FFA_MEM_PERM_INSTR_MASK)) { + return WT_FFA_INVALID_PARAMETERS; + } + /* A receiver states the instruction access it wants only when the memory + * becomes its alone (a donate, or a lend to one borrower: 1.10.3 item 2); + * for a share or a lend to several it is the relayer's (item 1). */ + if ((exclusive != 0) ? (instr == WT_FFA_MEM_PERM_INSTR_NOT_SPEC) + : (instr != WT_FFA_MEM_PERM_INSTR_NOT_SPEC)) { + return WT_FFA_INVALID_PARAMETERS; + } + if ((asked & WT_FFA_MEM_PERM_INSTR_MASK) == WT_FFA_MEM_PERM_INSTR_X) { + return WT_FFA_DENIED; + } + /* A donate's receiver only should state it (1.10.2 item 2): the grant + * stands, instructions NX. */ + if (data == WT_FFA_MEM_PERM_DATA_NOT_SPEC) { + data = granted_data; + } + if ((data == WT_FFA_MEM_PERM_DATA_RW) && + (granted_data != WT_FFA_MEM_PERM_DATA_RW)) { + return WT_FFA_DENIED; + } + *out = (uint8_t)(data | WT_FFA_MEM_PERM_INSTR_NX); + return 0; +} + +/* Retrieve flags bits 9:5: with the valid bit clear the hint is MBZ; with it + * set, n asks for a 2^n x 4 KB boundary. Partitions see memory at its physical + * address, so a region either already sits on that boundary or cannot. */ +#define WT_FFA_MEM_FLAG_ALIGN_VALID (1u << 9) +#define WT_FFA_MEM_FLAG_ALIGN_SHIFT 5u + +static int alignment_hint_ok(const wt_ffa_mem_handle_entry_t* e, uint32_t flags) +{ + uint32_t hint = (flags >> WT_FFA_MEM_FLAG_ALIGN_SHIFT) & 0xFu; + uint64_t boundary; + uint32_t i; + + if ((flags & WT_FFA_MEM_FLAG_ALIGN_VALID) == 0u) { + return (hint == 0u) ? 0 : WT_FFA_INVALID_PARAMETERS; + } + /* Table 1.22 prints 2*n x 4KB, read as 2^n: n = 0 would be no boundary. */ + boundary = (uint64_t)WT_FFA_MEM_PAGE_SIZE << hint; + for (i = 0u; i < (uint32_t)e->region_count; i++) { + if ((e->regions[i].base % boundary) != 0u) { + return WT_FFA_DENIED; + } + } + return 0; +} + +/* A receiver that names the ranges to map the memory at (1.11.3.2) names + * them for itself, with no alignment hint (Table 1.22 bits[9:5]) and covering + * exactly the sender's pages (1.11.3.3). The relayer maps S-EL0 memory only + * at its own address, so the ranges must be the region's pages in order. */ +static int receiver_ranges_ok(const wt_ffa_mem_handle_entry_t* e, + const wt_ffa_mem_retrieve_req_t* rq, + uint16_t receiver) +{ + uint64_t want; + uint32_t i = 0u; + uint32_t r = 0u; + uint32_t ioff = 0u; + uint32_t roff = 0u; + uint32_t step; + + if ((rq->receivers[rq->range_index] != receiver) || + ((rq->flags & (WT_FFA_MEM_FLAG_ALIGN_VALID | + (0xFu << WT_FFA_MEM_FLAG_ALIGN_SHIFT))) != 0u)) { + return WT_FFA_INVALID_PARAMETERS; + } + while ((i < rq->range_count) && (r < (uint32_t)e->region_count)) { + want = e->regions[r].base + ((uint64_t)roff * WT_FFA_MEM_PAGE_SIZE); + if ((rq->ranges[i].address + ((uint64_t)ioff * WT_FFA_MEM_PAGE_SIZE)) != + want) { + return WT_FFA_INVALID_PARAMETERS; + } + step = rq->ranges[i].page_count - ioff; + if ((e->regions[r].page_count - roff) < step) { + step = e->regions[r].page_count - roff; + } + ioff += step; + roff += step; + if (ioff == rq->ranges[i].page_count) { + i++; + ioff = 0u; + } + if (roff == e->regions[r].page_count) { + r++; + roff = 0u; + } + } + return ((i == rq->range_count) && (r == (uint32_t)e->region_count)) + ? 0 : WT_FFA_INVALID_PARAMETERS; +} + +/* Take the first count regions of e back out of a borrower's table, each to + * the entry it held before the grant. */ +static void borrower_unmap(const wt_spm_mem_binding_t* b, + const wt_ffa_mem_handle_entry_t* e, uint8_t mapping, + uint32_t count) +{ + uint32_t i; + + for (i = 0u; i < count; i++) { + (void)wt_domain_revoke(b->dom->regions, b->dom->region_count, + (uintptr_t)e->regions[i].base, + e->regions[i].page_count, + ((mapping & WT_SPM_MEM_MAP_REGION(i)) != 0u) ? 1 + : 0); + } +} + +int wt_spm_mem_retrieve(const uint8_t* req, size_t len, uint16_t receiver, + uint8_t* resp, size_t resp_cap, size_t* out_resp_len) +{ + const wt_ffa_mem_handle_entry_t* e; + wt_ffa_mem_borrower_t* borrower; + wt_spm_mem_binding_t* b; + wt_ffa_mem_retrieve_req_t rq; + wt_ffa_mem_constituent_t cons[WT_FFA_MEM_MAX_REGIONS]; + wt_ffa_mem_build_t in; + uint32_t attributes; + uint32_t version = caller_version(receiver); + uint32_t i; + uint32_t done = 0u; + uint8_t mapping = 0u; + uint8_t perms = 0u; + uint8_t asked = 0u; + int named = 0; + int was_mapped = 0; + int zero = 0; + int ret; + + if ((resp == NULL) || (out_resp_len == NULL)) { + return WT_FFA_INVALID_PARAMETERS; + } + ret = wt_ffa_mem_retrieve_req_parse_at(req, len, version, &rq); + if (ret != 0) { + return ret; + } + for (i = 0u; i < rq.receiver_count; i++) { + if (rq.receivers[i] == receiver) { + asked = rq.permissions[i]; + named = 1; + } + } + if (named == 0) { + return WT_FFA_DENIED; + } + ret = wt_ffa_mem_handle_lookup(&g_reg, rq.handle, &e); + if (ret != 0) { + return ret; + } + if (e->owner != rq.sender) { + return WT_FFA_DENIED; + } + borrower = wt_ffa_mem_handle_borrower(&g_reg, rq.handle, receiver); + b = bind_by_id(receiver); + /* 1.11.1: a handle not sent to this receiver is INVALID_PARAMETERS. */ + if (borrower == NULL) { + return WT_FFA_INVALID_PARAMETERS; + } + if ((b == NULL) || (borrower->retrieved != 0u)) { + return WT_FFA_DENIED; + } + ret = wt_ffa_mem_retrieve_req_check(e, &rq, receiver); + if ((ret == 0) && (rq.range_count != 0u)) { + ret = receiver_ranges_ok(e, &rq, receiver); + } + if (ret != 0) { + return ret; + } + ret = effective_permissions( + ((e->state == (uint8_t)WT_FFA_MEM_STATE_DONATED) || + ((e->state == (uint8_t)WT_FFA_MEM_STATE_LENT) && + (e->borrower_count == 1u))) ? 1 : 0, + (e->state == (uint8_t)WT_FFA_MEM_STATE_DONATED) ? 1 : 0, + borrower->permissions, asked, &perms); + if (ret != 0) { + return ret; + } + /* The zero-memory flags are MBZ for shared memory (Table 5.22); a + * read-only borrower cannot have lent memory wiped either. */ + if ((rq.flags & (WT_FFA_MEM_FLAG_ZERO | WT_FFA_MEM_FLAG_ZERO_AFTER)) != 0u) { + if (e->state == (uint8_t)WT_FFA_MEM_STATE_SHARED) { + return WT_FFA_INVALID_PARAMETERS; + } + /* Bit 0 is MBZ once this borrower has retrieved the region before + * (Table 1.22): the wipe ran once, ahead of its first retrieval. */ + if (((rq.flags & WT_FFA_MEM_FLAG_ZERO) != 0u) && + (borrower->ever_retrieved != 0u)) { + return WT_FFA_INVALID_PARAMETERS; + } + if ((perms & WT_FFA_MEM_PERM_DATA_MASK) == WT_FFA_MEM_PERM_DATA_RO) { + return WT_FFA_DENIED; + } + /* Bit 0 from a borrower means: only if the owner asked for the wipe. */ + if (((rq.flags & WT_FFA_MEM_FLAG_ZERO) != 0u) && + ((e->owner_cookie & WT_FFA_MEM_FLAG_ZERO) == 0u)) { + return WT_FFA_DENIED; + } + } + ret = alignment_hint_ok(e, rq.flags); + if (ret != 0) { + return ret; + } + for (i = 0u; i < (uint32_t)e->region_count; i++) { + cons[i].address = e->regions[i].base; + cons[i].page_count = e->regions[i].page_count; + } + (void)memset(&in, 0, sizeof(in)); + if (rq.range_count == 0u) { + in.constituents = cons; + in.constituent_count = (uint32_t)e->region_count; + } + in.tag = e->tag; + in.handle = rq.handle; + /* Only a first retrieval follows the wipe (Table 1.23 bit[0]). */ + zero = (((e->owner_cookie & WT_FFA_MEM_FLAG_ZERO) != 0u) && + (borrower->ever_retrieved == 0u)) ? 1 : 0; + in.flags = wt_ffa_mem_type_flag(e->state) | + ((zero != 0) ? WT_FFA_MEM_FLAG_ZERO : 0u); + in.op = WT_FFA_MEM_OP_SHARE; + in.sender = e->owner; + in.receiver = receiver; + /* A v1.0 borrower that never asked for the NS bit is not told it + * (DEN0140 Table 1.19 row 5). */ + in.attributes = (uint16_t)(e->attributes | + (((e->regions[0].ns != 0u) && + (wt_spm_sp_ffa_ns_bit(b->co) != 0)) + ? WT_FFA_MEM_ATTR_NS : 0u)); + in.permissions = perms; + /* The response is in the borrower's own version, whatever size its + * request used (DEN0077A 18.5.3). */ + in.access_desc_size = 0u; + in.impdef = borrower->impdef; + in.version = version; + ret = wt_ffa_mem_txn_build(resp, resp_cap, &in, out_resp_len); + if (ret != 0) { + return ret; + } + attributes = WT_MEM_ATTR_READ; + if ((perms & WT_FFA_MEM_PERM_DATA_MASK) == WT_FFA_MEM_PERM_DATA_RW) { + attributes |= WT_MEM_ATTR_WRITE; + } + else { + mapping |= (uint8_t)WT_SPM_MEM_MAP_RO; + } + if ((rq.flags & WT_FFA_MEM_FLAG_ZERO_AFTER) != 0u) { + mapping |= (uint8_t)WT_SPM_MEM_MAP_ZERO_AFTER; + } + if (e->regions[0].ns != 0u) { + attributes |= WT_TABLES_ATTR_NS; + } + for (i = 0u; (ret == 0) && (i < (uint32_t)e->region_count); i++) { + if (wt_domain_grant(b->dom->regions, b->dom->region_count, + (uintptr_t)e->regions[i].base, + e->regions[i].page_count, attributes, + &was_mapped) != WT_TABLES_OK) { + ret = WT_FFA_NO_MEMORY; + } + else { + if (was_mapped != 0) { + mapping |= (uint8_t)WT_SPM_MEM_MAP_REGION(i); + } + done++; + } + } + if (ret != 0) { + borrower_unmap(b, e, mapping, done); + return ret; + } + borrower->mapping = mapping; + ret = wt_ffa_mem_handle_retrieve(&g_reg, rq.handle, receiver); + /* A donate hands ownership over for good: the region is now the receiver's + * own writable memory (the owner's access was dropped at donate time), so + * the transaction is consumed and there is nothing to reclaim. */ + if ((ret == 0) && (e->state == (uint8_t)WT_FFA_MEM_STATE_DONATED)) { + (void)wt_ffa_mem_handle_free(&g_reg, rq.handle); + } + return ret; +} + +/* A borrower has let go: with several borrowers a wipe waits until the last + * of them has been unmapped, as the relayer zeroes memory only once no other + * component maps it (DEN0140 1.11.4.1), and a transaction whose owner is gone + * ends with it, its memory left to the SPM. */ +static void borrower_released(uint64_t handle, + const wt_ffa_mem_handle_entry_t* e, + uint32_t cookie) +{ + if (((cookie & WT_SPM_MEM_COOKIE_ZERO_PENDING) != 0u) && + (e->retrieved == 0u)) { + zero_regions(e); + cookie &= ~WT_SPM_MEM_COOKIE_ZERO_PENDING; + } + wt_ffa_mem_handle_set_meta(&g_reg, handle, e->tag, cookie); + if (((cookie & WT_SPM_MEM_COOKIE_OWNER_GONE) != 0u) && + (e->retrieved == 0u)) { + (void)wt_ffa_mem_handle_free(&g_reg, handle); + } +} + +int wt_spm_mem_relinquish(const uint8_t* rel, size_t len, uint16_t endpoint) +{ + const wt_ffa_mem_handle_entry_t* e; + wt_ffa_mem_borrower_t* borrower; + wt_spm_mem_binding_t* b; + uint64_t handle = 0u; + uint32_t flags = 0u; + uint32_t cookie; + uint16_t ep = 0u; + int ret; + + ret = wt_ffa_mem_relinquish_parse_ex(rel, len, &handle, &ep, &flags); + if (ret != 0) { + return ret; + } + if (ep != endpoint) { + return WT_FFA_INVALID_PARAMETERS; + } + ret = wt_ffa_mem_handle_lookup(&g_reg, handle, &e); + if (ret != 0) { + return ret; + } + borrower = wt_ffa_mem_handle_borrower(&g_reg, handle, endpoint); + b = bind_by_id(endpoint); + /* 2.6.1.2: a caller the region was not sent to is INVALID_PARAMETERS; + * one that has not retrieved it is DENIED. */ + if (borrower == NULL) { + return WT_FFA_INVALID_PARAMETERS; + } + if ((b == NULL) || (borrower->retrieved == 0u)) { + return WT_FFA_DENIED; + } + /* The zero-memory flag is MBZ for shared memory, and for a borrower + * whose retrieve left it read-only access (Table 2.25). */ + if ((flags & WT_FFA_MEM_RELINQ_FLAG_ZERO) != 0u) { + if (e->state == (uint8_t)WT_FFA_MEM_STATE_SHARED) { + return WT_FFA_INVALID_PARAMETERS; + } + if ((borrower->mapping & WT_SPM_MEM_MAP_RO) != 0u) { + return WT_FFA_DENIED; + } + } + ret = wt_ffa_mem_handle_relinquish(&g_reg, handle, endpoint); + if (ret != 0) { + return ret; + } + borrower_unmap(b, e, borrower->mapping, (uint32_t)e->region_count); + /* The flag here, not the one at retrieve, decides. */ + cookie = e->owner_cookie; + if ((flags & WT_FFA_MEM_RELINQ_FLAG_ZERO) != 0u) { + cookie |= WT_SPM_MEM_COOKIE_ZERO_PENDING; + } + borrower_released(handle, e, cookie); + return 0; +} + +int wt_spm_mem_reclaim(uint64_t handle, uint16_t owner, uint32_t flags) +{ + const wt_ffa_mem_handle_entry_t* e; + wt_ffa_mem_handle_entry_t snapshot; + int ret; + + ret = wt_ffa_mem_reclaim_flags_check(flags); + if (ret != 0) { + return ret; + } + ret = wt_ffa_mem_handle_lookup(&g_reg, handle, &e); + if (ret != 0) { + return ret; + } + if (((flags & WT_FFA_MEM_RELINQ_FLAG_ZERO) != 0u) && + ((e->owner_cookie & WT_SPM_MEM_COOKIE_OWNER_RO) != 0u)) { + return (e->owner == owner) ? WT_FFA_DENIED : WT_FFA_INVALID_PARAMETERS; + } + snapshot = *e; + ret = wt_ffa_mem_handle_reclaim(&g_reg, handle, owner); + if (ret != 0) { + return ret; + } + /* The wipe comes before the owner's mapping does (Table 2.31 bit[0]), and + * goes through S-EL1-only entries: one a share left EL0 read-only is + * read-only at S-EL1 too. */ + if ((flags & WT_FFA_MEM_RELINQ_FLAG_ZERO) != 0u) { + owner_access(&snapshot, WT_SPM_MEM_OWNER_WITHDRAW); + zero_regions(&snapshot); + } + owner_access(&snapshot, WT_SPM_MEM_OWNER_RELEASE); + return 0; +} + +/* A borrower that faulted lets go of everything it retrieved, zeroed where + * its retrieve asked (Table 1.22 bit[2]). */ +static void borrower_teardown(const wt_spm_mem_binding_t* b, uint64_t handle) +{ + const wt_ffa_mem_handle_entry_t* e; + wt_ffa_mem_borrower_t* borrower; + uint32_t cookie; + + borrower = wt_ffa_mem_handle_borrower(&g_reg, handle, b->id); + if ((borrower == NULL) || (borrower->retrieved == 0u) || + (wt_ffa_mem_handle_lookup(&g_reg, handle, &e) != 0) || + (wt_ffa_mem_handle_relinquish(&g_reg, handle, b->id) != 0)) { + return; + } + borrower_unmap(b, e, borrower->mapping, (uint32_t)e->region_count); + cookie = e->owner_cookie; + if ((borrower->mapping & WT_SPM_MEM_MAP_ZERO_AFTER) != 0u) { + cookie |= WT_SPM_MEM_COOKIE_ZERO_PENDING; + } + borrower_released(handle, e, cookie); +} + +/* A bound partition that faults is terminated, never restarted, so what it + * owned goes to the SPM, not back to it (1.3.1 rule 9): a transaction no + * borrower holds ends now, one a borrower still maps when the last lets go. */ +static void owner_teardown(uint16_t owner, uint64_t handle) +{ + const wt_ffa_mem_handle_entry_t* e; + + if ((wt_ffa_mem_handle_lookup(&g_reg, handle, &e) != 0) || + (e->owner != owner)) { + return; + } + if (e->retrieved != 0u) { + wt_ffa_mem_handle_set_meta(&g_reg, handle, e->tag, + e->owner_cookie | + WT_SPM_MEM_COOKIE_OWNER_GONE); + return; + } + (void)wt_ffa_mem_handle_reclaim(&g_reg, handle, owner); +} + +void wt_spm_mem_endpoint_teardown(const struct wt_co* co) +{ + const wt_spm_mem_binding_t* b = wt_spm_mem_binding(co); + uint64_t handle; + unsigned int i; + + if (b == NULL) { + return; + } + for (i = 0u; i < WT_SPM_MEM_FRAG_SLOTS; i++) { + if ((g_frag[i].active != 0u) && (g_frag[i].sender == b->id)) { + wt_ffa_mem_frag_reset(&g_frag[i]); + } + } + for (i = 0u; i < WT_FFA_MEM_MAX_HANDLES; i++) { + if (g_reg.entries[i].state != (uint8_t)WT_FFA_MEM_STATE_FREE) { + handle = g_reg.entries[i].handle; + borrower_teardown(b, handle); + owner_teardown(b->id, handle); + } + } +} + +void wt_spm_mem_unbind(const struct wt_co* co) +{ + unsigned int i; + + wt_spm_mem_endpoint_teardown(co); + for (i = 0u; i < WT_SPM_MEM_MAX_BIND; i++) { + if ((g_bind[i].live != 0u) && (g_bind[i].co == co)) { + g_bind[i].co = NULL; + g_bind[i].dom = NULL; + g_bind[i].id = 0u; + g_bind[i].live = 0u; + } + } +} diff --git a/src/arch/aarch64/spm/spm_svc_glue.c b/src/arch/aarch64/spm/spm_svc_glue.c new file mode 100644 index 00000000..e0a47e57 --- /dev/null +++ b/src/arch/aarch64/spm/spm_svc_glue.c @@ -0,0 +1,1330 @@ +/* spm_svc_glue.c + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfTrust. + * + * wolfTrust 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. + * + * wolfTrust 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, see . + */ + +/* Lower-EL synchronous exceptions at the SPMC: the Secure virtual instance + * (SVC from an S-EL0 partition) and partition faults. Runs on the + * bootstrap stack underneath the wt_co_arch_enter that started the + * partition; blocking unwinds to it through wt_co_arch_leave. */ + +#include "wolftrust/arch/aarch64/domain.h" +#include "wolftrust/arch/aarch64/el3.h" +#include "wolftrust/arch/aarch64/esr.h" +#include "wolftrust/arch/aarch64/ffa_abi.h" +#include "wolftrust/arch/aarch64/ffa_manifest.h" +#include "wolftrust/arch/aarch64/ffa_msg.h" +#include "wolftrust/arch/aarch64/ffa_mem.h" +#include "wolftrust/arch/aarch64/ffa_notif.h" +#include "wolftrust/arch/aarch64/ffa_partinfo.h" +#include "wolftrust/arch/aarch64/ffa_runtime.h" +#include "wolftrust/arch/aarch64/gic.h" +#include "wolftrust/arch/aarch64/spm_mem.h" +#include "wolftrust/arch/aarch64/spm_svc.h" +#include "wolftrust/arch/aarch64/tables.h" +#include "wolftrust/arch.h" +#include "wolftrust/ffm_domain.h" +#include "wolftrust/platform.h" +#include "wolftrust/sched/coroutine.h" +#include "wolftrust/sched/coroutine_internal.h" +#include "wolftrust/spm_gate.h" +#include "wolftrust/spm_sched.h" +#include "wolftrust/spm_transport.h" + +#include +#include +#include + +#define WT_ESR_EC_SVC64 0x15u +/* A partition's RX and TX buffers are one 4K page each. */ +#define WT_SP_RXTX_PAGES 1u + +wt_trap_frame_t* volatile g_wt_spm_live_frame; +struct wt_co* volatile g_wt_spm_handler_co; +static uint64_t g_yield_token; +static uint32_t g_yield_unprivileged; +uint64_t g_wt_ffa_direct_resp[18]; +volatile uint32_t g_wt_ffa_direct_resp_ready; + +uint64_t wt_spm_yield_token(void) +{ + return g_yield_token; +} + +uint32_t wt_spm_yield_unprivileged(void) +{ + return g_yield_unprivileged; +} + +static void ffa_error(wt_trap_frame_t* frame, int32_t code) +{ + unsigned int i; + + for (i = 0u; i < 8u; i++) { + frame->x[i] = 0u; + } + frame->x[0] = WT_FFA_ERROR; + frame->x[2] = (uint64_t)(uint32_t)code; +} + +/* A partition's direct response: validate it at the Secure virtual instance, + * keep it for the deliverer, and park the partition back in waiting. A + * malformed response is returned to the partition as an error instead. */ +static void ffa_direct_resp(wt_trap_frame_t* frame, const struct wt_co* co) +{ + unsigned int i; + uint16_t requester = 0u; + uint16_t self = 0u; + int ret = wt_ffa_direct_resp_check(frame->x, WT_FFA_INSTANCE_SECURE_VIRTUAL); + + if (ret != 0) { + ffa_error(frame, (int32_t)ret); + return; + } + if (wt_spm_ffa_sp_requester(co, &requester, &self) == 0) { + ffa_error(frame, WT_FFA_DENIED); + return; + } + if ((wt_ffa_direct_sender(frame->x[1]) != self) || + (wt_ffa_direct_receiver(frame->x[1]) != requester)) { + ffa_error(frame, WT_FFA_INVALID_PARAMETERS); + return; + } + /* 15.5: a REQ2 is answered with RESP2 and nothing else is. */ + if ((wt_ffa_msg_reg_count(frame->x[0]) == WT_FFA_MSG_REGS_EXT) != + (wt_spm_ffa_sp_req2(co) != 0)) { + ffa_error(frame, WT_FFA_DENIED); + return; + } + for (i = 0u; i < WT_FFA_MSG_REGS_EXT; i++) { + g_wt_ffa_direct_resp[i] = frame->x[i]; + } + g_wt_ffa_direct_resp_ready = 1u; + g_wt_ffa_sp_exit = WT_FFA_SP_EXIT_RESP; + wt_co_block(); +} + +/* 15.2/15.4: the callee may complete the direct request it is processing with + * FFA_SUCCESS in place of a response, every other register MBZ; its requester + * is handed that FFA_SUCCESS and the callee waits for the next message. */ +static void ffa_direct_success(wt_trap_frame_t* frame, const struct wt_co* co) +{ + uint32_t fid = (uint32_t)frame->x[0]; + uint16_t requester = 0u; + uint16_t self = 0u; + unsigned int i; + + if (wt_spm_ffa_sp_requester(co, &requester, &self) == 0) { + ffa_error(frame, WT_FFA_DENIED); + return; + } + if (wt_ffa_rt_success_check(frame->x) != 0) { + ffa_error(frame, WT_FFA_INVALID_PARAMETERS); + return; + } + for (i = 0u; i < WT_FFA_MSG_REGS_EXT; i++) { + g_wt_ffa_direct_resp[i] = 0u; + } + g_wt_ffa_direct_resp[0] = fid; + g_wt_ffa_direct_resp_ready = 1u; + g_wt_ffa_sp_exit = WT_FFA_SP_EXIT_RESP; + wt_co_block(); +} + +#if defined(WT_XN_NEG_PROBE) && (WT_XN_NEG_PROBE == 1) +static uint32_t g_xn_probe_thunk[1] __attribute__((aligned(4))); +static uint8_t g_xn_probe_fired; + +/* xnneg: from a partition's SVC, under its table, the SPMC calls a `ret` + * written into SPM bss; the fetch must abort at S-EL1 before it returns. */ +static void xn_probe(void) +{ + void (*thunk)(void); + + if (g_xn_probe_fired != 0u) { + return; + } + g_xn_probe_fired = 1u; + g_xn_probe_thunk[0] = 0xd65f03c0u; + __asm__ volatile("dsb ish\n\tic iallu\n\tdsb ish\n\tisb" ::: "memory"); + wt_el3_puts("[SPM] xn probe\r\n"); + thunk = (void (*)(void))(uintptr_t)g_xn_probe_thunk; + thunk(); + wt_el3_puts("[SPM] xn escape\r\n"); +} +#endif + +static void report_partition_fault(const wt_trap_frame_t* frame) +{ + char line[80]; + + (void)wt_esr_format(line, sizeof(line), 0u, frame->esr, frame->far); + wt_el3_puts(line); + wt_el3_puts("\r\n"); +} + +static void ffa_not_supported(wt_trap_frame_t* frame) +{ + ffa_error(frame, WT_FFA_NOT_SUPPORTED); +} + +static void ffa_success(wt_trap_frame_t* frame, uint64_t w2, uint64_t w3) +{ + unsigned int i; + + for (i = 0u; i < 8u; i++) { + frame->x[i] = 0u; + } + frame->x[0] = WT_FFA_SUCCESS32; + frame->x[2] = w2; + frame->x[3] = w3; +} + +/* The configured partitions as FFA_PARTITION_INFO_GET source records: each + * manifest partition under the live id of the partition running in its + * domain (the id its FFA_ID_GET returns), one not running omitted, with a + * record per exported UUID. */ +#define WT_SPM_PARTINFO_MAX \ + (WT_FFA_NATIVE_SP_MAX + (WT_CO_MAX * WT_FFA_MANIFEST_MAX_UUIDS)) +static wt_ffa_partinfo_entry_t g_partinfo[WT_SPM_PARTINFO_MAX]; + +static size_t partinfo_collect(void) +{ + const wt_ffa_partition_manifest_t* parts; + const wt_ffa_native_sp_t* natives; + size_t native_count = 0u; + size_t part_count = 0u; + size_t cap = sizeof(g_partinfo) / sizeof(g_partinfo[0]); + size_t added = 0u; + size_t n = 0u; + size_t i; + unsigned int j; + + /* FF-A native endpoints lead the listing: a register-based caller sees + * five descriptors per call and looks for message receivers first. */ + natives = wt_spm_ffa_native_list(&native_count); + for (i = 0u; (i < native_count) && (n < cap); i++) { + g_partinfo[n].id = wt_spm_ffa_native_id(i); + g_partinfo[n].exec_contexts = 1u; + g_partinfo[n].properties = natives[i].properties; + for (j = 0u; j < 16u; j++) { + g_partinfo[n].uuid[j] = natives[i].uuid[j]; + } + n++; + } + parts = wt_generated_ffa_partitions_get(&part_count); + if (parts == NULL) { + return 0u; + } + for (i = 0u; i < part_count; i++) { + if (wt_ffa_partinfo_from_manifest( + &parts[i], wt_spm_sp_ffa_id_of_domain(parts[i].domain_id), + &g_partinfo[n], cap - n, &added) != 0) { + return 0u; + } + n += added; + } + return n; +} + +/* FFA_PARTITION_INFO_GET (6.1) for either instance: x = the call's registers, + * mb = the caller's mailbox, whose RX buffer must be mapped and free for + * descriptors (a count needs none). */ +int wt_spm_partition_info(const uint64_t* x, uint32_t caller_version, + wt_ffa_mailbox_t* mb, uint32_t* count, uint32_t* size) +{ + size_t n = partinfo_collect(); + + return wt_ffa_partinfo_get(x, caller_version, mb, g_partinfo, n, count, + size); +} + +/* FFA_PARTITION_INFO_GET_REGS for either instance: UUID in x1/x2, start index + * and tag in x3 (bits 63:32 SBZ); the reply fills out18. */ +int wt_spm_partition_info_regs(const uint64_t* x, uint64_t* out18) +{ + size_t n = partinfo_collect(); + + return wt_ffa_partinfo_regs_call(g_partinfo, n, x, out18); +} + +/* The Table 6.2 properties discovery lists for id: 0, or INVALID_PARAMETERS + * for an id that names no listed partition. */ +int wt_spm_partition_props(uint16_t id, uint32_t* props) +{ + size_t n = partinfo_collect(); + + return wt_ffa_partinfo_props_of(g_partinfo, n, id, props); +} + +int wt_spm_msg2_sender_allowed(uint16_t id) +{ + size_t n = partinfo_collect(); + + return wt_ffa_msg2_sender_allowed(g_partinfo, n, id); +} + +/* 10.7 rule 3: discovery's Table 6.2 bit 3 is whether an endpoint takes + * notifications, and no PSA partition does. 1 listed with it, 0 listed + * without it, -1 not listed. */ +static int notif_receiver(uint16_t id) +{ + uint32_t props = 0u; + + if (wt_spm_partition_props(id, &props) != 0) { + return -1; + } + return ((props & WT_FFA_PARTINFO_PROP_NOTIF) != 0u) ? 1 : 0; +} + +int32_t wt_spm_notif_set(uint16_t caller, uint32_t w1, uint32_t w2, + uint64_t bitmap) +{ + if (notif_receiver(WT_FFA_NOTIF_W1_LOW(w1)) == 0) { + return WT_FFA_DENIED; + } + return wt_ffa_notif_set(caller, w1, w2, bitmap); +} + +static wt_ffa_mailbox_t* sp_mailbox(void); + +static void ffa_partition_info_get_regs(wt_trap_frame_t* frame) +{ + uint64_t out[WT_FFA_MSG_REGS_EXT]; + unsigned int i; + int ret = wt_spm_partition_info_regs(frame->x, out); + + if (ret != 0) { + ffa_error(frame, ret); + return; + } + for (i = 0u; i < WT_FFA_MSG_REGS_EXT; i++) { + frame->x[i] = out[i]; + } +} + +static wt_ffa_version_state_t g_sp_version[WT_CO_MAX]; +/* What a partition's last FFA_FEATURES(FFA_MEM_RETRIEVE_REQ) asked about the + * NS bit (DEN0140 1.10.4.1.1). */ +static uint8_t g_sp_ns_bit[WT_CO_MAX]; + +/* The version a partition negotiated, which its data structures follow. */ +static uint32_t sp_version(const struct wt_co* co) +{ + if ((co == NULL) || (co->id == 0u) || (co->id > WT_CO_MAX)) { + return WT_FFA_VERSION_1_2; + } + return wt_ffa_version_of(&g_sp_version[co->id - 1u], WT_FFA_VERSION_1_2); +} + +uint32_t wt_spm_sp_ffa_version(const struct wt_co* co) +{ + return sp_version(co); +} + +int wt_spm_sp_ffa_ns_bit(const struct wt_co* co) +{ + int asked = 0; + + if ((co != NULL) && (co->id != 0u) && (co->id <= WT_CO_MAX)) { + asked = (g_sp_ns_bit[co->id - 1u] != 0u) ? 1 : 0; + } + return wt_ffa_ns_bit_used(sp_version(co), asked); +} + +static void ffa_partition_info_get(wt_trap_frame_t* frame, + const struct wt_co* co) +{ + uint32_t count = 0u; + uint32_t size = 0u; + int ret = wt_spm_partition_info(frame->x, sp_version(co), sp_mailbox(), + &count, &size); + + if (ret != 0) { + ffa_error(frame, ret); + return; + } + ffa_success(frame, count, size); +} + +/* FFA_RX_RELEASE (7.2.2.4): ownership of the RX buffer returns to the SPMC. + * The VM id in w1[15:0] is MBZ at this instance (Table 13.21). */ +static void ffa_rx_release(wt_trap_frame_t* frame) +{ + int ret = WT_FFA_INVALID_PARAMETERS; + + if (((uint32_t)frame->x[1] & 0xFFFFu) == 0u) { + ret = wt_ffa_mailbox_rx_release(sp_mailbox()); + } + + if (ret != 0) { + ffa_error(frame, ret); + return; + } + ffa_success(frame, 0u, 0u); +} + +/* RX/TX pairs the partitions registered with FFA_RXTX_MAP (7.2.2), indexed by + * coroutine id; a partition that never registered has no buffer to use. */ +static wt_ffa_mailbox_t g_sp_mailbox[WT_CO_MAX]; + +struct wt_ffa_mailbox* wt_spm_sp_mailbox_of(const struct wt_co* co) +{ + if ((co == NULL) || (co->id == 0u) || (co->id > WT_CO_MAX)) { + return NULL; + } + return &g_sp_mailbox[co->id - 1u]; +} + +int wt_spm_mailbox_overlaps(uint64_t base, uint64_t size) +{ + unsigned int i; + + for (i = 0u; i < WT_CO_MAX; i++) { + if (wt_ffa_mailbox_overlaps(&g_sp_mailbox[i], base, size) != 0) { + return 1; + } + } + return wt_spm_ns_mailbox_overlaps(base, size); +} + +static wt_ffa_mailbox_t* sp_mailbox(void) +{ + const struct wt_co* co = (const struct wt_co*)wt_co_current(); + + if ((co == NULL) || (co->id == 0u) || (co->id > WT_CO_MAX)) { + return NULL; + } + return &g_sp_mailbox[co->id - 1u]; +} + +/* FFA_RXTX_MAP (13.5): x1 = TX, x2 = RX, w3 = page count. The pair must be + * two distinct writable pages of the caller's own memory that no memory + * transaction covers. */ +static void ffa_rxtx_map(wt_trap_frame_t* frame, const struct wt_co* co) +{ + wt_ffa_mailbox_t* mb = sp_mailbox(); + uintptr_t tx = (uintptr_t)frame->x[1]; + uintptr_t rx = (uintptr_t)frame->x[2]; + uint32_t w3 = (uint32_t)frame->x[3]; + int ret; + + if (mb == NULL) { + ffa_error(frame, WT_FFA_DENIED); + return; + } + if ((mb->mapped == 0u) && + ((WT_FFA_RXTX_PAGE_COUNT(w3) != WT_SP_RXTX_PAGES) || + (wt_spm_mem_rxtx_ok(co->domain, (uint64_t)tx) == 0) || + (wt_spm_mem_rxtx_ok(co->domain, (uint64_t)rx) == 0))) { + ffa_error(frame, WT_FFA_INVALID_PARAMETERS); + return; + } + ret = wt_ffa_mailbox_map(mb, (uint64_t)tx, (uint64_t)rx, w3); + if (ret != 0) { + ffa_error(frame, ret); + return; + } + ffa_success(frame, 0u, 0u); +} + +/* FFA_RXTX_UNMAP (13.7): the id in w1[31:16] is MBZ at this instance (Table + * 13.30); w1[15:0] is SBZ. A descriptor the caller was still sending through + * the TX buffer is aborted. */ +static void ffa_rxtx_unmap(wt_trap_frame_t* frame) +{ + const wt_spm_mem_binding_t* b = wt_spm_mem_binding(wt_co_current()); + int ret; + + if ((((uint32_t)frame->x[1] >> 16) & 0xFFFFu) != 0u) { + ffa_error(frame, WT_FFA_INVALID_PARAMETERS); + return; + } + ret = wt_ffa_mailbox_unmap(sp_mailbox()); + if (ret != 0) { + ffa_error(frame, ret); + return; + } + if (b != NULL) { + wt_spm_mem_frag_abort(b->id); + } + ffa_success(frame, 0u, 0u); +} + +/* A direct request from a partition (15.2): only to another FF-A endpoint the + * SPMC hosts that takes this kind of request (DENIED otherwise, Tables 15.8 + * and 15.16), which must be waiting, and only from a listed partition that + * advertises sending it (7.4 relayer rule 2). The caller blocks; its callee's + * response (or FFA_YIELD) is written into its frame before it resumes. */ +static void ffa_direct_req(wt_trap_frame_t* frame, const struct wt_co* co) +{ + struct wt_co* target; + uint16_t receiver = wt_ffa_direct_receiver(frame->x[1]); + int ret = wt_ffa_direct_req_check(frame->x, WT_FFA_INSTANCE_SECURE_VIRTUAL); + + if ((ret == 0) && + (wt_ffa_direct_sender(frame->x[1]) != wt_spm_sp_ffa_id(co))) { + ret = WT_FFA_INVALID_PARAMETERS; + } + if (ret == 0) { + ret = wt_ffa_direct_req_authorize(g_partinfo, partinfo_collect(), + wt_spm_sp_ffa_id(co), receiver, + (uint32_t)frame->x[0]); + } + if (ret == 0) { + target = wt_spm_ffa_native_by_id(receiver); + ret = wt_spm_ffa_sp_call(co, target, frame->x); + } + if (ret != 0) { + ffa_error(frame, ret); + return; + } + g_wt_ffa_sp_exit = WT_FFA_SP_EXIT_CALL; + wt_co_block(); +} + +/* FFA_RUN from a partition: only to resume an endpoint that yielded inside a + * direct request this partition sent it. */ +static void ffa_run(wt_trap_frame_t* frame, const struct wt_co* co) +{ + uint16_t id = 0u; + int ret = wt_ffa_run_target((uint32_t)frame->x[1], &id); + + if (ret == 0) { + ret = wt_spm_ffa_sp_call(co, wt_spm_ffa_native_by_id(id), NULL); + } + + if (ret != 0) { + ffa_error(frame, ret); + return; + } + g_wt_ffa_sp_exit = WT_FFA_SP_EXIT_CALL; + wt_co_block(); +} + +/* FFA_ERROR (Table 12.4: w1 MBZ here, w2 an error code) is how an initializing + * partition reports failed initialization and enters the waiting state (8.5 + * rule 3, Figure 8.4); any other partition has no call it could be answering, + * an invalid transition (8.1 rule 4). */ +static void ffa_init_failed(wt_trap_frame_t* frame, wt_co_t* co) +{ + int32_t code = (int32_t)(uint32_t)frame->x[2]; + + if (wt_spm_sp_initializing((const struct wt_co*)co) == 0) { + ffa_error(frame, WT_FFA_DENIED); + return; + } + if (wt_ffa_rt_error_check(frame->x) != 0) { + ffa_error(frame, WT_FFA_INVALID_PARAMETERS); + return; + } + wt_spm_mem_endpoint_teardown((const struct wt_co*)co); + wt_spm_sp_init_failed((struct wt_co*)co, code); + g_wt_ffa_sp_exit = WT_FFA_SP_EXIT_WAIT; + wt_co_block(); +} + +/* FFA_YIELD (8.2): hand the CPU back to whoever entered this partition; the + * call returns FFA_SUCCESS once FFA_RUN resumes it. An initializing partition + * was scheduled by the SPMC and may not yield (8.5 rule 4), and a partition + * cannot ask for a timeout (Table 14.9). */ +static void ffa_yield(wt_trap_frame_t* frame, const struct wt_co* co) +{ + if ((wt_spm_sp_initializing(co) != 0) && + (wt_ffa_rt_init_call(WT_FFA_YIELD, 0) != 0)) { + ffa_error(frame, WT_FFA_DENIED); + return; + } + if (wt_ffa_rt_yield_check(frame->x) != 0) { + ffa_error(frame, WT_FFA_INVALID_PARAMETERS); + return; + } + ffa_success(frame, 0u, 0u); + g_wt_ffa_sp_exit = WT_FFA_SP_EXIT_YIELD; + wt_co_block(); +} + +/* A descriptor handed over in the caller's TX buffer: w1 = total length, + * w2 = length of the fragment in TX (DEN0140 4.1.2 when shorter), w3/w4 = 0 + * (not an address). */ +static int tx_descriptor(const wt_trap_frame_t* frame, uint32_t* total, + uint32_t* frag, const uint8_t** tx) +{ + uint64_t addr = 0u; + int ret = 0; + + *total = (uint32_t)frame->x[1]; + *frag = (uint32_t)frame->x[2]; + if ((*frag < 1u) || (*frag > *total) || (*frag > WT_FFA_MEM_PAGE_SIZE) || + ((frame->x[4] >> 32) != 0u)) { + ret = WT_FFA_INVALID_PARAMETERS; + } + if (ret == 0) { + ret = wt_ffa_mem_tx_buffer(sp_mailbox(), frame->x[3], + (uint32_t)frame->x[4], *frag, &addr); + } + *tx = (const uint8_t*)(uintptr_t)addr; + return ret; +} + +/* Ask the sender for the rest of a descriptor: FFA_MEM_FRAG_RX with the + * transaction's handle and the bytes held; w4 is MBZ at this virtual + * instance. */ +static void frag_rx_reply(wt_trap_frame_t* frame, uint64_t handle, + uint32_t offset) +{ + unsigned int i; + + for (i = 0u; i < 8u; i++) { + frame->x[i] = 0u; + } + frame->x[0] = WT_FFA_MEM_FRAG_RX; + frame->x[1] = handle & 0xFFFFFFFFu; + frame->x[2] = handle >> 32; + frame->x[3] = (uint64_t)offset; +} + +/* Answer a whole retrieve request with FFA_MEM_RETRIEVE_RESP, the response + * descriptor in the caller's RX buffer (it always fits in one fragment). */ +static void retrieve_answer(wt_trap_frame_t* frame, uint16_t receiver, + const uint8_t* req, size_t len) +{ + wt_ffa_mailbox_t* mb = sp_mailbox(); + size_t resp_len = 0u; + unsigned int i; + int ret; + + ret = wt_ffa_mailbox_rx_acquire(mb); + if (ret == 0) { + ret = wt_spm_mem_retrieve(req, len, receiver, + (uint8_t*)(uintptr_t)mb->rx, + WT_FFA_MEM_PAGE_SIZE, &resp_len); + if (ret != 0) { + (void)wt_ffa_mailbox_rx_release(mb); + } + } + if (ret != 0) { + ffa_error(frame, ret); + return; + } + for (i = 0u; i < 8u; i++) { + frame->x[i] = 0u; + } + frame->x[0] = WT_FFA_MEM_RETRIEVE_RESP; + frame->x[1] = (uint64_t)resp_len; + frame->x[2] = (uint64_t)resp_len; +} + +/* FFA_MEM_SHARE / LEND / DONATE from a partition: the relayer validates the + * descriptor in its TX buffer and returns the handle in w2/w3, or asks for + * the remaining fragments first. */ +static void ffa_mem_send(wt_trap_frame_t* frame, wt_ffa_mem_op_t op) +{ + const wt_spm_mem_binding_t* b = wt_spm_mem_binding(wt_co_current()); + const uint8_t* tx = NULL; + uint64_t handle = 0u; + uint32_t total = 0u; + uint32_t frag = 0u; + int ret; + + if (b == NULL) { + ffa_error(frame, WT_FFA_DENIED); + return; + } + ret = tx_descriptor(frame, &total, &frag, &tx); + if ((ret == 0) && (frag < total)) { + ret = wt_spm_mem_frag_begin((uint8_t)op, b->id, tx, frag, total, + &handle); + if (ret == 0) { + frag_rx_reply(frame, handle, frag); + return; + } + } + else if (ret == 0) { + ret = wt_spm_mem_share(tx, (size_t)total, op, b->id, &handle); + } + if (ret != 0) { + ffa_error(frame, ret); + return; + } + ffa_success(frame, handle & 0xFFFFFFFFu, handle >> 32); +} + +/* FFA_MEM_RETRIEVE_REQ from a partition: map the region and answer with + * FFA_MEM_RETRIEVE_RESP, or ask for the rest of a fragmented request. */ +static void ffa_mem_retrieve(wt_trap_frame_t* frame) +{ + const wt_spm_mem_binding_t* b = wt_spm_mem_binding(wt_co_current()); + const uint8_t* tx = NULL; + uint64_t handle = 0u; + uint32_t total = 0u; + uint32_t frag = 0u; + int ret; + + if (b == NULL) { + ffa_error(frame, WT_FFA_DENIED); + return; + } + ret = tx_descriptor(frame, &total, &frag, &tx); + if ((ret == 0) && (frag < total)) { + ret = wt_spm_mem_frag_begin(WT_SPM_MEM_FRAG_OP_RETRIEVE, b->id, tx, + frag, total, &handle); + if (ret == 0) { + frag_rx_reply(frame, handle, frag); + } + else { + ffa_error(frame, ret); + } + return; + } + if (ret != 0) { + ffa_error(frame, ret); + return; + } + retrieve_answer(frame, b->id, tx, (size_t)total); +} + +/* FFA_MEM_FRAG_TX (DEN0140 4.1.2.5): the next fragment, in the TX buffer the + * first one used, which is still mapped (an unmap aborts the transfer); the + * last completes the call that sent the first. */ +static void ffa_mem_frag_tx(wt_trap_frame_t* frame) +{ + const wt_spm_mem_binding_t* b = wt_spm_mem_binding(wt_co_current()); + uint64_t handle = (uint64_t)(uint32_t)frame->x[1] | + ((uint64_t)(uint32_t)frame->x[2] << 32); + uint32_t len = (uint32_t)frame->x[3]; + const uint8_t* frag = NULL; + const uint8_t* desc; + uint64_t tx = 0u; + uint32_t offset = 0u; + uint32_t total = 0u; + uint8_t op = 0u; + int done = 0; + int ret = WT_FFA_INVALID_PARAMETERS; + + if ((b != NULL) && ((uint32_t)frame->x[4] == 0u) && + (len <= WT_FFA_MEM_PAGE_SIZE)) { + if (wt_ffa_mem_tx_buffer(sp_mailbox(), 0u, 0u, len, &tx) == 0) { + frag = (const uint8_t*)(uintptr_t)tx; + } + ret = wt_spm_mem_frag_next(handle, b->id, frag, len, &offset, &done); + } + if (ret != 0) { + ffa_error(frame, ret); + return; + } + if (done == 0) { + frag_rx_reply(frame, handle, offset); + return; + } + desc = wt_spm_mem_frag_desc(handle, b->id, &total, &op); + if ((desc != NULL) && (op == WT_SPM_MEM_FRAG_OP_RETRIEVE)) { + retrieve_answer(frame, b->id, desc, (size_t)total); + wt_spm_mem_frag_release(handle, b->id); + return; + } + ret = wt_spm_mem_frag_share(handle, b->id); + if (ret != 0) { + ffa_error(frame, ret); + return; + } + ffa_success(frame, handle & 0xFFFFFFFFu, handle >> 32); +} + +/* FFA_MEM_RELINQUISH from a partition: the descriptor is in its TX buffer. */ +static void ffa_mem_relinquish(wt_trap_frame_t* frame) +{ + const wt_spm_mem_binding_t* b = wt_spm_mem_binding(wt_co_current()); + uint64_t tx = 0u; + int ret; + + if (b == NULL) { + ffa_error(frame, WT_FFA_DENIED); + return; + } + ret = wt_ffa_mem_tx_buffer(sp_mailbox(), 0u, 0u, WT_FFA_MEM_PAGE_SIZE, + &tx); + if (ret == 0) { + ret = wt_spm_mem_relinquish((const uint8_t*)(uintptr_t)tx, + WT_FFA_MEM_PAGE_SIZE, b->id); + } + if (ret != 0) { + ffa_error(frame, ret); + return; + } + ffa_success(frame, 0u, 0u); +} + +/* FFA_MEM_RECLAIM from a partition: w1/w2 = handle, w3 = flags. */ +static void ffa_mem_reclaim(wt_trap_frame_t* frame) +{ + const wt_spm_mem_binding_t* b = wt_spm_mem_binding(wt_co_current()); + uint64_t handle = (uint64_t)(uint32_t)frame->x[1] | + ((uint64_t)(uint32_t)frame->x[2] << 32); + int ret; + + if (b == NULL) { + ffa_error(frame, WT_FFA_DENIED); + return; + } + ret = wt_spm_mem_reclaim(handle, b->id, (uint32_t)frame->x[3]); + if (ret != 0) { + ffa_error(frame, ret); + return; + } + ffa_success(frame, 0u, 0u); +} + +void wt_spm_sp_ffa_reset(const struct wt_co* co) +{ + if ((co == NULL) || (co->id == 0u) || (co->id > WT_CO_MAX)) { + return; + } + (void)memset(&g_sp_version[co->id - 1u], 0, sizeof(g_sp_version[0])); + g_sp_ns_bit[co->id - 1u] = 0u; + (void)memset(&g_sp_mailbox[co->id - 1u], 0, sizeof(g_sp_mailbox[0])); +} + +/* FFA_VERSION (13.2): the result is returned in w0 alone. */ +static void ffa_version(wt_trap_frame_t* frame, const struct wt_co* co) +{ + uint32_t requested = (uint32_t)frame->x[1]; + unsigned int i; + + for (i = 1u; i < 8u; i++) { + frame->x[i] = 0u; + } + frame->x[0] = (uint64_t)(uint32_t)wt_ffa_version_negotiate( + &g_sp_version[co->id - 1u], requested, WT_FFA_VERSION_1_2); +} + +static int sp_implements(uint32_t fid) +{ + switch (fid) { + case WT_FFA_ERROR: + case WT_FFA_SUCCESS32: + case WT_FFA_SUCCESS64: + case WT_FFA_INTERRUPT: + case WT_FFA_VERSION: + case WT_FFA_FEATURES: + case WT_FFA_RX_RELEASE: + case WT_FFA_RXTX_MAP32: + case WT_FFA_RXTX_MAP64: + case WT_FFA_RXTX_UNMAP: + case WT_FFA_PARTITION_INFO_GET: + case WT_FFA_PARTITION_INFO_GET_REGS: + case WT_FFA_ID_GET: + case WT_FFA_SPM_ID_GET: + case WT_FFA_MSG_WAIT: + case WT_FFA_YIELD: + case WT_FFA_RUN: + case WT_FFA_MSG_SEND_DIRECT_REQ32: + case WT_FFA_MSG_SEND_DIRECT_REQ64: + case WT_FFA_MSG_SEND_DIRECT_RESP32: + case WT_FFA_MSG_SEND_DIRECT_RESP64: + case WT_FFA_MSG_SEND_DIRECT_REQ2: + case WT_FFA_MSG_SEND_DIRECT_RESP2: + case WT_FFA_MEM_SHARE32: + case WT_FFA_MEM_SHARE64: + case WT_FFA_MEM_LEND32: + case WT_FFA_MEM_LEND64: + case WT_FFA_MEM_DONATE32: + case WT_FFA_MEM_DONATE64: + case WT_FFA_MEM_RETRIEVE_REQ32: + case WT_FFA_MEM_RETRIEVE_REQ64: + case WT_FFA_MEM_RETRIEVE_RESP: + case WT_FFA_MEM_RELINQUISH: + case WT_FFA_MEM_RECLAIM: + case WT_FFA_MEM_FRAG_RX: + case WT_FFA_MEM_FRAG_TX: + case WT_FFA_MEM_PERM_GET32: + case WT_FFA_MEM_PERM_GET64: + case WT_FFA_MEM_PERM_SET32: + case WT_FFA_MEM_PERM_SET64: + case WT_FFA_CONSOLE_LOG32: + case WT_FFA_CONSOLE_LOG64: + case WT_FFA_NOTIFICATION_BIND: + case WT_FFA_NOTIFICATION_UNBIND: + case WT_FFA_NOTIFICATION_SET: + case WT_FFA_NOTIFICATION_GET: + case WT_FFA_MSG_SEND2: + return 1; + default: + return 0; + } +} + +/* 10.7 rules 5 and 6: a partition that does not take notifications has none + * of the notification ABIs this instance serves partitions. */ +static int sp_notif_denied(uint32_t fid, const struct wt_co* co) +{ + if ((fid != WT_FFA_NOTIFICATION_BIND) && + (fid != WT_FFA_NOTIFICATION_UNBIND) && + (fid != WT_FFA_NOTIFICATION_SET) && + (fid != WT_FFA_NOTIFICATION_GET)) { + return 0; + } + return (notif_receiver(wt_spm_sp_ffa_id(co)) != 1) ? 1 : 0; +} + +/* FFA_FEATURES (13.3): exactly the function ids this instance serves; no + * optional feature id is implemented. FFA_RXTX_MAP reports the one-page + * buffer limit ffa_rxtx_map enforces (7.2.2.3). */ +static void ffa_features(wt_trap_frame_t* frame, const struct wt_co* co) +{ + uint32_t query = (uint32_t)frame->x[1]; + uint32_t props = (uint32_t)frame->x[2]; + int32_t ret; + + if ((query == WT_FFA_MEM_RETRIEVE_REQ32) || + (query == WT_FFA_MEM_RETRIEVE_REQ64)) { + ret = wt_ffa_features_retrieve_check(sp_version(co), props); + if (ret != 0) { + ffa_error(frame, ret); + } + else { + g_sp_ns_bit[co->id - 1u] = + ((props & WT_FFA_FEATURES_RETRIEVE_NS_BIT) != 0u) ? 1u : 0u; + ffa_success(frame, WT_FFA_FEATURES_RETRIEVE_NS_BIT, 0u); + } + } + else if ((query == WT_FFA_RXTX_MAP32) || (query == WT_FFA_RXTX_MAP64)) { + ffa_success(frame, WT_FFA_FEATURES_RXTX_MAX_PAGES(WT_SP_RXTX_PAGES), + 0u); + } + else if (WT_FFA_FEATURES_IS_FID(query) && (sp_implements(query) != 0) && + wt_ffa_fid_available(query, sp_version(co)) && + (sp_notif_denied(query, co) == 0)) { + ffa_success(frame, 0u, 0u); + } + else { + ffa_not_supported(frame); + } +} + +/* FFA_CONSOLE_LOG (13.12): w1 = count (bits 31:8 SBZ), characters packed + * from w2/x2 upward; 1..24 over w2-w7, 1..128 over x2-x17. */ +static void ffa_console_log(wt_trap_frame_t* frame, unsigned int is64) +{ + uint32_t count = wt_ffa_console_count(frame->x[1], is64); + unsigned int per_reg = (is64 != 0u) ? 8u : 4u; + unsigned int i; + uint64_t reg; + + if (count == 0u) { + ffa_error(frame, WT_FFA_INVALID_PARAMETERS); + return; + } + for (i = 0u; i < count; i++) { + reg = frame->x[2u + (i / per_reg)]; + wt_platform_console_putc((char)((reg >> (8u * (i % per_reg))) & 0xFFu)); + } + ffa_success(frame, 0u, 0u); +} + +/* A partition's notification calls share the Normal world's state machine; + * the bitmap and info-get ABIs stay unhandled here so a partition caller + * gets NOT_SUPPORTED, as the scheduler-side ABIs require. */ +static void ffa_notif_bind(wt_trap_frame_t* frame, const struct wt_co* co, + unsigned int unbind) +{ + uint16_t caller = (uint16_t)wt_spm_sp_ffa_id(co); + uint32_t w1 = (uint32_t)frame->x[1]; + uint32_t w2 = (uint32_t)frame->x[2]; + uint64_t bitmap = (uint64_t)(uint32_t)frame->x[3] | + ((uint64_t)(uint32_t)frame->x[4] << 32); + int32_t ret; + + if (unbind != 0u) { + ret = wt_ffa_notif_unbind(caller, w1, w2, bitmap); + } + else { + ret = wt_ffa_notif_bind(caller, w1, w2, bitmap); + } + if (ret == 0) { + ffa_success(frame, 0u, 0u); + } + else { + ffa_error(frame, ret); + } +} + +static void ffa_notif_set(wt_trap_frame_t* frame, const struct wt_co* co) +{ + uint16_t caller = (uint16_t)wt_spm_sp_ffa_id(co); + uint64_t bitmap = (uint64_t)(uint32_t)frame->x[3] | + ((uint64_t)(uint32_t)frame->x[4] << 32); + int32_t ret = wt_spm_notif_set(caller, (uint32_t)frame->x[1], + (uint32_t)frame->x[2], bitmap); + + if (ret == 0) { + if (wt_ffa_notif_sri_take_now() != 0) { + wt_gic->raise_ns_sgi(WT_FFA_SRI_INTID); + wt_el3_puts("[SPM] sri sgi\r\n"); + } + ffa_success(frame, 0u, 0u); + } + else { + ffa_error(frame, ret); + } +} + +/* FFA_MSG_SEND2 from a partition: the shared delivery engine reads the + * message from the caller's own TX buffer. */ +static void ffa_msg_send2(wt_trap_frame_t* frame, const struct wt_co* co) +{ + wt_ffa_mailbox_t* mb = wt_spm_sp_mailbox_of(co); + int ret; + + if ((mb == NULL) || (mb->mapped == 0u)) { + ffa_error(frame, WT_FFA_DENIED); + return; + } + ret = wt_spm_msg2_deliver((uint16_t)wt_spm_sp_ffa_id(co), sp_version(co), + (const uint8_t*)(uintptr_t)mb->tx, + mb->pages * (uint32_t)WT_TABLES_PAGE_SIZE, + WT_FFA_INSTANCE_SECURE_VIRTUAL, + (uint32_t)frame->x[1], (uint32_t)frame->x[2]); + if (ret == 0) { + ffa_success(frame, 0u, 0u); + } + else { + ffa_error(frame, ret); + } +} + +static void ffa_notif_get(wt_trap_frame_t* frame, const struct wt_co* co) +{ + uint16_t caller = (uint16_t)wt_spm_sp_ffa_id(co); + wt_ffa_notif_get_result_t got; + int32_t ret = wt_ffa_notif_get(caller, (uint32_t)frame->x[1], + (uint32_t)frame->x[2], &got); + + if (ret == 0) { + wt_ffa_mailbox_rx_claim(sp_mailbox(), got.framework); + ffa_success(frame, (uint32_t)got.from_sp, + (uint32_t)(got.from_sp >> 32)); + frame->x[4] = (uint32_t)got.from_vm; + frame->x[5] = (uint32_t)(got.from_vm >> 32); + frame->x[6] = (uint32_t)got.framework; + frame->x[7] = (uint32_t)(got.framework >> 32); + } + else { + ffa_error(frame, ret); + } +} + +/* FFA_MEM_PERM_SET (DEN0140 2.9): an S-EL0 partition re-permissions its own + * pages, during its initialization only (DENIED otherwise, the one DENIED in + * Table 2.41). w1 = base VA, w2 = page count, w3 = perms. */ +static void ffa_mem_perm_set(wt_trap_frame_t* frame, const struct wt_co* co) +{ + int ret; + + if (wt_spm_sp_initializing(co) == 0) { + ffa_error(frame, WT_FFA_DENIED); + return; + } + ret = wt_spm_mem_perm_set(co->domain, wt_spm_sp_mailbox_of(co), + (uint64_t)frame->x[1], (uint32_t)frame->x[2], + (uint32_t)frame->x[3]); + if (ret != 0) { + ffa_error(frame, ret); + return; + } + ffa_success(frame, 0u, 0u); +} + +/* FFA_MEM_PERM_GET (DEN0140 2.8): w1 = base VA of a page; the permissions + * return in w2. DENIED only outside initialization (Table 2.37). */ +static void ffa_mem_perm_get(wt_trap_frame_t* frame, const struct wt_co* co) +{ + uint32_t perm = 0u; + int ret; + + if (wt_spm_sp_initializing(co) == 0) { + ffa_error(frame, WT_FFA_DENIED); + return; + } + ret = wt_spm_mem_perm_get(co->domain, (uint64_t)frame->x[1], &perm); + if (ret != 0) { + ffa_error(frame, ret); + return; + } + ffa_success(frame, perm, 0u); +} + +void wt_spm_lower_sync(wt_trap_frame_t* frame) +{ + uint32_t ec = (uint32_t)(frame->esr >> 26) & 0x3Fu; + uint32_t fid = (uint32_t)frame->x[0]; + wt_co_t* co = wt_co_current(); + uint32_t sint; + uint16_t requester = 0u; + uint16_t self = 0u; + unsigned int i; + int busy; + + g_wt_spm_live_frame = frame; + g_wt_spm_trap_spsr = frame->spsr; + g_wt_spm_handler_co = co; + g_wt_spm_handler_depth++; + + if (ec != WT_ESR_EC_SVC64) { + report_partition_fault(frame); + wt_spm_mem_endpoint_teardown(co); +#if defined(WT_CONFORMANCE) && (WT_CONFORMANCE == 1) + /* The Arm isolation tests fault inside a partition on purpose and + * expect a system restart (val resumes off its NVM boot flag); the + * quarantine below would leave the server dead for every later test. */ + wt_platform_system_reset(); +#endif + /* Route a partition fault through the core's restart policy; one + * that is not a scheduled SP (an FF-A native, a boot self-test) is + * retired here. */ + if (wt_spm_sp_fault(co) != WT_FFM_SUCCESS) { + wt_spm_sp_retire((struct wt_co*)co); + } + g_wt_spm_live_frame = NULL; + g_wt_spm_handler_depth = 0u; + wt_sp_el0_leave(); + } + + /* 4.4: the SVC32 convention mirrors SMC32, so a 32-bit call carries w1-w7 + * only, as the monitor normalizes an SMC32 call. */ + if (wt_ffa_fid_in_range(fid)) { + wt_ffa_regs_normalize(frame->x); + } + if (wt_ffa_fid_in_range(fid) && (fid != WT_FFA_VERSION) && (co != NULL) && + (co->id != 0u) && (co->id <= WT_CO_MAX)) { + wt_ffa_version_lock(&g_sp_version[co->id - 1u], WT_FFA_VERSION_1_2); + } + + if (fid == WT_SPM_SVC_FID_CALL) { + wt_spm_call_t* call = (wt_spm_call_t*)(uintptr_t)frame->x[1]; + + /* A blocking op unwinds inside the dispatch with this frame already + * captured, so the resumed partition returns from its svc with this + * value: SUCCESS makes the SVC transport re-issue around the block. */ + frame->x[0] = (uint64_t)WT_FFM_SUCCESS; +#if defined(WT_XN_NEG_PROBE) && (WT_XN_NEG_PROBE == 1) + xn_probe(); +#endif + wt_spm_sp_in_gate((const struct wt_co*)co, 1u); + frame->x[0] = (uint64_t)(int64_t)wt_spm_dispatch_call(call, frame); + wt_spm_sp_in_gate((const struct wt_co*)co, 0u); + } + else if ((fid == WT_SPM_SVC_FID_YIELD) && + (wt_spm_sched_current_is_partition() != 0)) { + /* The SPMC's own yield is no service partition's to issue: a + * programmer error that panics only the caller (FF-M). */ + wt_arch_sp_redirect_to_panic_trap(frame); + } + else if (fid == WT_SPM_SVC_FID_YIELD) { + g_yield_token = frame->x[1]; + g_yield_unprivileged = (uint32_t)wt_arch_thread_unprivileged(); + frame->x[0] = 0u; + wt_co_block(); + } +#if defined(WT_FFA_ACS) && (WT_FFA_ACS == 1) + else if (fid == WT_SPM_HVC_INTERRUPT_ENABLE) { + sint = (uint32_t)frame->x[1]; + if (wt_spm_sint_own((struct wt_co*)co, sint, + (frame->x[2] != 0u) ? 1u : 0u) == 0) { + if (frame->x[2] != 0u) { + wt_gic->set_group0(sint); + wt_gic->set_priority(sint, 0x10u); + wt_gic->enable(sint); + } + else { + wt_gic->disable(sint); + } + frame->x[0] = 0u; + } + else { + frame->x[0] = (uint64_t)(int64_t)-1; + } + } + else if (fid == WT_SPM_HVC_INTERRUPT_GET) { + frame->x[0] = (uint64_t)wt_spm_sint_delivered((const struct wt_co*)co); + } + else if (fid == WT_SPM_HVC_INTERRUPT_DEACTIVATE) { + frame->x[0] = 0u; + } + else if (fid == WT_SPM_SVC_FID_TIMER_ARM) { + frame->x[0] = (wt_spm_twdog_arm((const struct wt_co*)co, + (uint32_t)frame->x[1], + (uint32_t)frame->x[2]) == 0) ? + 0u : (uint64_t)(int64_t)-1; + } + else if (fid == WT_SPM_SVC_FID_TIMER_STOP) { + wt_spm_twdog_stop((const struct wt_co*)co); + frame->x[0] = 0u; + } +#endif + else if (wt_ffa_fid_in_range(fid) && (co != NULL) && + !wt_ffa_fid_available(fid, sp_version((const struct wt_co*)co))) { + /* 13.2.2: an ABI from after the partition's negotiated version. */ + ffa_not_supported(frame); + } + else if (fid == WT_FFA_MSG_WAIT) { + /* 8.2/8.5: the partition enters the waiting state, which for one + * still initializing signals success; the next direct request is + * delivered as this call's return registers. A Secure interrupt + * queued while it ran (Table 9.1) is delivered here as FFA_INTERRUPT + * instead of blocking, w1/w2 zero (12.4.1 item 3). Either way the + * call hands the RX buffer back (7.2.2.4.2) unless a v1.2 caller + * keeps it with w2 bit 0 (Table 14.3). */ + busy = wt_spm_ffa_sp_requester((const struct wt_co*)co, &requester, + &self); + sint = 0u; + if (busy == 0) { + wt_spm_sp_init_complete((const struct wt_co*)co); + sint = wt_spm_sint_take_pending(co); + /* DEN0077A v1.2 REL0 Table 14.3 defines w2 bit 0 (SBZ in ALP1). */ + if (wt_ffa_rt_msg_wait_releases_rx(sp_version(co), + frame->x) != 0) { + (void)wt_ffa_mailbox_rx_release(sp_mailbox()); + } + } + if (busy != 0) { + ffa_error(frame, WT_FFA_DENIED); + } + else if (sint != 0u) { + for (i = 0u; i < 8u; i++) { + frame->x[i] = 0u; + } + frame->x[0] = WT_FFA_INTERRUPT; + } + else { + g_wt_ffa_sp_exit = WT_FFA_SP_EXIT_WAIT; + wt_co_block(); + } + } + else if ((fid == WT_FFA_MSG_SEND_DIRECT_REQ32) || + (fid == WT_FFA_MSG_SEND_DIRECT_REQ64) || + (fid == WT_FFA_MSG_SEND_DIRECT_REQ2)) { + ffa_direct_req(frame, (const struct wt_co*)co); + } + else if (fid == WT_FFA_RUN) { + ffa_run(frame, (const struct wt_co*)co); + } + else if (fid == WT_FFA_YIELD) { + ffa_yield(frame, (const struct wt_co*)co); + } + else if (fid == WT_FFA_ERROR) { + ffa_init_failed(frame, co); + } + else if ((fid == WT_FFA_SUCCESS32) || (fid == WT_FFA_SUCCESS64)) { + ffa_direct_success(frame, (const struct wt_co*)co); + } + else if ((fid == WT_FFA_MSG_SEND_DIRECT_RESP32) || + (fid == WT_FFA_MSG_SEND_DIRECT_RESP64) || + (fid == WT_FFA_MSG_SEND_DIRECT_RESP2)) { + ffa_direct_resp(frame, (const struct wt_co*)co); + } + else if (fid == WT_FFA_VERSION) { + ffa_version(frame, (const struct wt_co*)co); + } + else if (fid == WT_FFA_FEATURES) { + ffa_features(frame, (const struct wt_co*)co); + } + else if (fid == WT_FFA_ID_GET) { + ffa_success(frame, wt_spm_sp_ffa_id((const struct wt_co*)co), 0u); + } + else if (fid == WT_FFA_SPM_ID_GET) { + ffa_success(frame, WT_FFA_ID_SPMC, 0u); + } + else if ((fid == WT_FFA_CONSOLE_LOG32) || (fid == WT_FFA_CONSOLE_LOG64)) { + ffa_console_log(frame, (fid == WT_FFA_CONSOLE_LOG64) ? 1u : 0u); + } + else if (sp_notif_denied(fid, (const struct wt_co*)co) != 0) { + ffa_not_supported(frame); + } + else if ((fid == WT_FFA_NOTIFICATION_BIND) || + (fid == WT_FFA_NOTIFICATION_UNBIND)) { + ffa_notif_bind(frame, (const struct wt_co*)co, + (fid == WT_FFA_NOTIFICATION_UNBIND) ? 1u : 0u); + } + else if (fid == WT_FFA_NOTIFICATION_SET) { + ffa_notif_set(frame, (const struct wt_co*)co); + } + else if (fid == WT_FFA_NOTIFICATION_GET) { + ffa_notif_get(frame, (const struct wt_co*)co); + } + else if (fid == WT_FFA_MSG_SEND2) { + ffa_msg_send2(frame, (const struct wt_co*)co); + } + else if ((fid == WT_FFA_MEM_PERM_SET32) || (fid == WT_FFA_MEM_PERM_SET64)) { + ffa_mem_perm_set(frame, (const struct wt_co*)co); + } + else if ((fid == WT_FFA_MEM_PERM_GET32) || (fid == WT_FFA_MEM_PERM_GET64)) { + ffa_mem_perm_get(frame, (const struct wt_co*)co); + } + else if ((fid == WT_FFA_RXTX_MAP32) || (fid == WT_FFA_RXTX_MAP64)) { + ffa_rxtx_map(frame, (const struct wt_co*)co); + } + else if (fid == WT_FFA_RXTX_UNMAP) { + ffa_rxtx_unmap(frame); + } + else if (fid == WT_FFA_PARTITION_INFO_GET) { + ffa_partition_info_get(frame, (const struct wt_co*)co); + } + else if (fid == WT_FFA_PARTITION_INFO_GET_REGS) { + ffa_partition_info_get_regs(frame); + } + else if (fid == WT_FFA_RX_RELEASE) { + ffa_rx_release(frame); + } + else if ((fid == WT_FFA_MEM_SHARE32) || (fid == WT_FFA_MEM_SHARE64)) { + ffa_mem_send(frame, WT_FFA_MEM_OP_SHARE); + } + else if ((fid == WT_FFA_MEM_LEND32) || (fid == WT_FFA_MEM_LEND64)) { + ffa_mem_send(frame, WT_FFA_MEM_OP_LEND); + } + else if ((fid == WT_FFA_MEM_DONATE32) || (fid == WT_FFA_MEM_DONATE64)) { + ffa_mem_send(frame, WT_FFA_MEM_OP_DONATE); + } + else if ((fid == WT_FFA_MEM_RETRIEVE_REQ32) || + (fid == WT_FFA_MEM_RETRIEVE_REQ64)) { + ffa_mem_retrieve(frame); + } + else if (fid == WT_FFA_MEM_RELINQUISH) { + ffa_mem_relinquish(frame); + } + else if (fid == WT_FFA_MEM_RECLAIM) { + ffa_mem_reclaim(frame); + } + else if (fid == WT_FFA_MEM_FRAG_TX) { + ffa_mem_frag_tx(frame); + } + else if (fid == WT_FFA_MEM_FRAG_RX) { + /* A retrieve response always fits the caller's RX buffer, so no + * fragment is ever outstanding for it to ask for. */ + ffa_error(frame, WT_FFA_INVALID_PARAMETERS); + } + else { + ffa_not_supported(frame); + } + if (wt_ffa_fid_in_range(fid)) { + wt_ffa_reply_clear_ext(fid, frame->x); + } + + g_wt_spm_handler_depth--; + g_wt_spm_live_frame = NULL; +} diff --git a/src/arch/aarch64/spm/spm_switch.S b/src/arch/aarch64/spm/spm_switch.S new file mode 100644 index 00000000..f8fa1292 --- /dev/null +++ b/src/arch/aarch64/spm/spm_switch.S @@ -0,0 +1,86 @@ +/* spm_switch.S + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfTrust. + * + * wolfTrust 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. + * + * wolfTrust 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, see . + */ + +/* Cooperative S-EL1 coroutine switch: save the callee-saved state of the + * running context, hand its stack pointer back through *x0, load the + * target's, and resume it. The AAPCS64 caller-saved registers are already + * dead across a call, so only x19-x30 and SP need preserving. */ + + .section .text.wt_co_arch_switch, "ax" + .globl wt_co_arch_switch +/* void wt_co_arch_switch(uintptr_t* save_from_sp, uintptr_t to_sp) */ +wt_co_arch_switch: + sub sp, sp, #96 + stp x19, x20, [sp, #0] + stp x21, x22, [sp, #16] + stp x23, x24, [sp, #32] + stp x25, x26, [sp, #48] + stp x27, x28, [sp, #64] + stp x29, x30, [sp, #80] + mov x2, sp + str x2, [x0] + mov sp, x1 + ldp x19, x20, [sp, #0] + ldp x21, x22, [sp, #16] + ldp x23, x24, [sp, #32] + ldp x25, x26, [sp, #48] + ldp x27, x28, [sp, #64] + ldp x29, x30, [sp, #80] + add sp, sp, #96 + ret + +/* First switch-in lands here with x19 = entry, x20 = arg (see + * wt_co_arch_init_stack). A coroutine entry must never return. */ + .section .text.wt_co_trampoline, "ax" + .globl wt_co_trampoline +wt_co_trampoline: + mov x0, x20 + blr x19 + brk #0x52 +1: b 1b + +/* void wt_ffa_smc_ext(wt_ffa_regs_ext_t* r): one SMC with x0-x17 loaded from + * and captured back into r (FFA_MSG_SEND_DIRECT_REQ2/RESP2 use them all). */ + .section .text.wt_ffa_smc_ext, "ax" + .globl wt_ffa_smc_ext +wt_ffa_smc_ext: + stp x19, x30, [sp, #-16]! + mov x19, x0 + ldp x0, x1, [x19, #0] + ldp x2, x3, [x19, #16] + ldp x4, x5, [x19, #32] + ldp x6, x7, [x19, #48] + ldp x8, x9, [x19, #64] + ldp x10, x11, [x19, #80] + ldp x12, x13, [x19, #96] + ldp x14, x15, [x19, #112] + ldp x16, x17, [x19, #128] + smc #0 + stp x0, x1, [x19, #0] + stp x2, x3, [x19, #16] + stp x4, x5, [x19, #32] + stp x6, x7, [x19, #48] + stp x8, x9, [x19, #64] + stp x10, x11, [x19, #80] + stp x12, x13, [x19, #96] + stp x14, x15, [x19, #112] + stp x16, x17, [x19, #128] + ldp x19, x30, [sp], #16 + ret diff --git a/src/arch/aarch64/spm/tables.c b/src/arch/aarch64/spm/tables.c new file mode 100644 index 00000000..72e190dd --- /dev/null +++ b/src/arch/aarch64/spm/tables.c @@ -0,0 +1,657 @@ +/* tables.c + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfTrust. + * + * wolfTrust 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. + * + * wolfTrust 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, see . + */ + +/* Stage-1 table builder for the S-EL0 partition domains (VMSAv8-64, 4 KB + * granule, pages only). Identity-mapped: VA == PA for every region. */ + +#include "wolftrust/arch/aarch64/tables.h" + +#define DESC_VALID (1ull << 0) +#define DESC_TABLE (1ull << 1) +#define DESC_PAGE (1ull << 1) +#define PTE_ATTR_SHIFT 2u +#define PTE_NS (1ull << 5) +#define PTE_AP_SHIFT 6u +#define PTE_AP_EL0 (1ull << 6) +#define PTE_AP_RO (1ull << 7) +#define PTE_SH_INNER (3ull << 8) +#define PTE_AF (1ull << 10) +#define PTE_NG (1ull << 11) +#define PTE_PXN (1ull << 53) +#define PTE_UXN (1ull << 54) +/* Bits[58:55] are software's: a held owner page keeps its own AP[2], UXN, and + * PXN there until it is released; on a page that is not held, bit 56 marks an + * EL0 page its owner made no-access with FFA_MEM_PERM_SET. */ +#define PTE_SW_HELD (1ull << 55) +#define PTE_SW_AP_RO (1ull << 56) +#define PTE_SW_UXN (1ull << 57) +#define PTE_SW_PXN (1ull << 58) +#define PTE_SW_MASK (PTE_SW_HELD | PTE_SW_AP_RO | PTE_SW_UXN | PTE_SW_PXN) +#define PTE_SW_HIDDEN PTE_SW_AP_RO +#define PTE_ADDR_MASK 0x0000FFFFFFFFF000ull + +#define L1_SHIFT 30u +#define L2_SHIFT 21u +#define L3_SHIFT 12u +#define INDEX_MASK (WT_TABLES_ENTRIES - 1u) + +void wt_tables_pool_init(wt_tables_pool_t* pool, uint8_t* base, uint64_t base_pa, + size_t size) +{ + if (pool != NULL) { + pool->base = base; + pool->size = size; + pool->used = 0u; + pool->base_pa = base_pa; + } +} + +uint64_t wt_tables_pool_pa(const wt_tables_pool_t* pool, const void* page) +{ + return pool->base_pa + (uint64_t)((const uint8_t*)page - pool->base); +} + +size_t wt_tables_pool_pages_used(const wt_tables_pool_t* pool) +{ + return (pool != NULL) ? (pool->used / WT_TABLES_PAGE_SIZE) : 0u; +} + +static uint64_t* pool_page(wt_tables_pool_t* pool) +{ + uint64_t* page; + size_t i; + + if ((pool->used + WT_TABLES_PAGE_SIZE) > pool->size) { + return NULL; + } + page = (uint64_t*)(pool->base + pool->used); + pool->used += WT_TABLES_PAGE_SIZE; + for (i = 0u; i < WT_TABLES_ENTRIES; i++) { + page[i] = 0u; + } + return page; +} + +static uint64_t* table_at(const wt_tables_pool_t* pool, uint64_t desc) +{ + return (uint64_t*)(pool->base + (size_t)((desc & PTE_ADDR_MASK) - pool->base_pa)); +} + +/* Attribute word -> page descriptor bits, or a negative error. */ +static int64_t encode(uint32_t attributes, int el1_only) +{ + uint64_t pte = DESC_VALID | DESC_PAGE | PTE_SH_INNER | PTE_AF; + int readable = (attributes & WT_MEM_ATTR_READ) != 0u; + int writable = (attributes & WT_MEM_ATTR_WRITE) != 0u; + int exec = (attributes & WT_MEM_ATTR_EXEC) != 0u; + int device = (attributes & WT_MEM_ATTR_DEVICE) != 0u; + + if (!readable) { + return WT_TABLES_ERROR_ARGUMENT; + } + if (writable && exec) { + return WT_TABLES_ERROR_WX; + } + if (device && exec) { + return WT_TABLES_ERROR_WX; + } + if (device) { + pte |= (uint64_t)WT_TABLES_ATTR_DEVICE_NGNRE << PTE_ATTR_SHIFT; + pte |= PTE_UXN | PTE_PXN; + } + else { + pte |= (uint64_t)WT_TABLES_ATTR_NORMAL_WBWA << PTE_ATTR_SHIFT; + if (!exec) { + pte |= PTE_UXN | PTE_PXN; + } + else if (el1_only) { + pte |= PTE_UXN; + } + } + if (el1_only) { + pte |= (uint64_t)(writable ? WT_TABLES_AP_EL1_RW : WT_TABLES_AP_EL1_RO) + << PTE_AP_SHIFT; + } + else { + pte |= (uint64_t)(writable ? WT_TABLES_AP_ALL_RW : WT_TABLES_AP_ALL_RO) + << PTE_AP_SHIFT; + } + /* A range any table maps at EL0 must never be a global entry: global + * TLB entries match under every ASID and would serve a partition's + * EL0 fetch with the EL1-only permissions cached by the SPM. */ + if (!el1_only || (attributes & WT_TABLES_ATTR_NG) != 0u) { + pte |= PTE_NG; + } + if ((attributes & WT_TABLES_ATTR_NS) != 0u) { + pte |= PTE_NS; + } + return (int64_t)pte; +} + +static int map_page(wt_tables_t* t, wt_tables_pool_t* pool, uint64_t va, + uint64_t pte) +{ + uint64_t* l2; + uint64_t* l3; + uint64_t desc; + uint32_t i1 = (uint32_t)((va >> L1_SHIFT) & INDEX_MASK); + uint32_t i2 = (uint32_t)((va >> L2_SHIFT) & INDEX_MASK); + uint32_t i3 = (uint32_t)((va >> L3_SHIFT) & INDEX_MASK); + + desc = t->l1[i1]; + if ((desc & DESC_VALID) == 0u) { + l2 = pool_page(pool); + if (l2 == NULL) { + return WT_TABLES_ERROR_POOL; + } + t->l1[i1] = wt_tables_pool_pa(pool, l2) | DESC_VALID | DESC_TABLE; + } + else { + l2 = table_at(pool, desc); + } + desc = l2[i2]; + if ((desc & DESC_VALID) == 0u) { + l3 = pool_page(pool); + if (l3 == NULL) { + return WT_TABLES_ERROR_POOL; + } + l2[i2] = wt_tables_pool_pa(pool, l3) | DESC_VALID | DESC_TABLE; + } + else { + l3 = table_at(pool, desc); + } + if ((l3[i3] & DESC_VALID) != 0u) { + return WT_TABLES_ERROR_OVERLAP; + } + l3[i3] = pte | (va & PTE_ADDR_MASK); + return WT_TABLES_OK; +} + +static int map_regions(wt_tables_t* t, wt_tables_pool_t* pool, + const wt_memory_region_t* regions, size_t count, + int el1_only) +{ + int ret = WT_TABLES_OK; + int64_t pte; + uint64_t va; + uint64_t end; + size_t i; + + for (i = 0u; (i < count) && (ret == WT_TABLES_OK); i++) { + if (regions[i].size == 0u) { + continue; + } + if (((regions[i].base % WT_TABLES_PAGE_SIZE) != 0u) || + ((regions[i].size % WT_TABLES_PAGE_SIZE) != 0u)) { + ret = WT_TABLES_ERROR_ALIGN; + break; + } + end = (uint64_t)regions[i].base + (uint64_t)regions[i].size; + if ((end > WT_TABLES_VA_LIMIT) || (end < (uint64_t)regions[i].base)) { + ret = WT_TABLES_ERROR_RANGE; + break; + } + pte = encode(regions[i].attributes, el1_only); + if (pte < 0) { + ret = (int)pte; + break; + } + for (va = regions[i].base; (va < end) && (ret == WT_TABLES_OK); + va += WT_TABLES_PAGE_SIZE) { + ret = map_page(t, pool, va, (uint64_t)pte); + } + } + return ret; +} + +int wt_tables_build(wt_tables_t* t, uint16_t asid, + const wt_memory_region_t* el0_regions, size_t el0_count, + const wt_memory_region_t* el1_regions, size_t el1_count, + wt_tables_pool_t* pool) +{ + int ret; + + if ((t == NULL) || (pool == NULL) || (pool->base == NULL) || + ((el0_count != 0u) && (el0_regions == NULL)) || + ((el1_count != 0u) && (el1_regions == NULL)) || (asid > 0xFFu) || + (((uintptr_t)pool->base % WT_TABLES_PAGE_SIZE) != 0u) || + ((pool->base_pa % WT_TABLES_PAGE_SIZE) != 0u)) { + return WT_TABLES_ERROR_ARGUMENT; + } + t->l1 = pool_page(pool); + if (t->l1 == NULL) { + return WT_TABLES_ERROR_POOL; + } + t->l1_pa = wt_tables_pool_pa(pool, t->l1); + t->asid = asid; + ret = map_regions(t, pool, el1_regions, el1_count, 1); + if (ret == WT_TABLES_OK) { + ret = map_regions(t, pool, el0_regions, el0_count, 0); + } + return ret; +} + +int wt_tables_walk(const wt_tables_t* t, const wt_tables_pool_t* pool, + uint64_t va, wt_tables_walk_t* out) +{ + const uint64_t* l2; + const uint64_t* l3; + uint64_t desc; + + if ((t == NULL) || (pool == NULL) || (out == NULL) || (t->l1 == NULL)) { + return WT_TABLES_ERROR_ARGUMENT; + } + if (va >= WT_TABLES_VA_LIMIT) { + return WT_TABLES_ERROR_RANGE; + } + desc = t->l1[(va >> L1_SHIFT) & INDEX_MASK]; + if ((desc & DESC_VALID) == 0u) { + return WT_TABLES_ERROR_UNMAPPED; + } + l2 = table_at(pool, desc); + desc = l2[(va >> L2_SHIFT) & INDEX_MASK]; + if ((desc & DESC_VALID) == 0u) { + return WT_TABLES_ERROR_UNMAPPED; + } + l3 = table_at(pool, desc); + desc = l3[(va >> L3_SHIFT) & INDEX_MASK]; + if ((desc & DESC_VALID) == 0u) { + return WT_TABLES_ERROR_UNMAPPED; + } + out->pa = (desc & PTE_ADDR_MASK) | (va & (WT_TABLES_PAGE_SIZE - 1u)); + out->attr_index = (uint32_t)((desc >> PTE_ATTR_SHIFT) & 0x7u); + out->ap = (uint32_t)((desc >> PTE_AP_SHIFT) & 0x3u); + out->uxn = ((desc & PTE_UXN) != 0u) ? 1u : 0u; + out->pxn = ((desc & PTE_PXN) != 0u) ? 1u : 0u; + out->ng = ((desc & PTE_NG) != 0u) ? 1u : 0u; + out->ns = ((desc & PTE_NS) != 0u) ? 1u : 0u; + out->held = ((desc & PTE_SW_HELD) != 0u) ? 1u : 0u; + out->hidden = ((desc & (PTE_SW_HELD | PTE_SW_HIDDEN)) == PTE_SW_HIDDEN) + ? 1u : 0u; + return WT_TABLES_OK; +} + +static uint64_t* l3_entry(const wt_tables_t* t, const wt_tables_pool_t* pool, + uint64_t va) +{ + uint64_t* table; + uint64_t desc; + + desc = t->l1[(va >> L1_SHIFT) & INDEX_MASK]; + if ((desc & DESC_VALID) == 0u) { + return NULL; + } + table = table_at(pool, desc); + desc = table[(va >> L2_SHIFT) & INDEX_MASK]; + if ((desc & DESC_VALID) == 0u) { + return NULL; + } + table = table_at(pool, desc); + return &table[(va >> L3_SHIFT) & INDEX_MASK]; +} + +static int hidden_page(uint64_t desc) +{ + return (desc & (PTE_SW_HELD | PTE_SW_HIDDEN)) == PTE_SW_HIDDEN; +} + +/* An EL0 page the partition may re-permission, or one it made no-access, of + * Normal or Device memory; a Non-secure one is memory donated to it. */ +static int el0_owned_page(uint64_t desc) +{ + uint32_t ap = (uint32_t)((desc >> PTE_AP_SHIFT) & 0x3u); + uint32_t attr = (uint32_t)((desc >> PTE_ATTR_SHIFT) & 0x7u); + + return ((desc & DESC_VALID) != 0u) && ((desc & PTE_SW_HELD) == 0u) && + ((attr == WT_TABLES_ATTR_NORMAL_WBWA) || + (attr == WT_TABLES_ATTR_DEVICE_NGNRE)) && + ((ap == WT_TABLES_AP_ALL_RW) || (ap == WT_TABLES_AP_ALL_RO) || + hidden_page(desc)); +} + +/* The entry attributes give a page of desc's memory type and security state; + * no access (0) leaves it S-EL1 read-write and marked. */ +static int64_t owned_encoding(uint64_t desc, uint32_t attributes) +{ + int64_t pte; + + if (((desc >> PTE_ATTR_SHIFT) & 0x7u) == WT_TABLES_ATTR_DEVICE_NGNRE) { + attributes |= WT_MEM_ATTR_DEVICE; + } + if ((desc & PTE_NS) != 0u) { + /* The Normal world can write it: S-EL0 never executes it. */ + if ((attributes & WT_MEM_ATTR_EXEC) != 0u) { + return WT_TABLES_ERROR_WX; + } + attributes |= WT_TABLES_ATTR_NS; + } + if ((attributes & ~(WT_MEM_ATTR_DEVICE | WT_TABLES_ATTR_NS)) == 0u) { + pte = encode(attributes | WT_MEM_ATTR_READ | WT_MEM_ATTR_WRITE | + WT_TABLES_ATTR_NG, 1); + if (pte >= 0) { + pte |= (int64_t)PTE_SW_HIDDEN; + } + } + else { + pte = encode(attributes, 0); + } + return pte; +} + +int wt_tables_set_el0_attributes(wt_tables_t* t, const wt_tables_pool_t* pool, + uint64_t va, size_t pages, uint32_t attributes) +{ + const uint64_t* probe; + uint64_t* entry; + uint64_t end; + uint64_t at; + int64_t pte; + + if ((t == NULL) || (pool == NULL) || (t->l1 == NULL) || (pages == 0u) || + ((attributes & (WT_MEM_ATTR_DEVICE | WT_TABLES_ATTR_NS)) != 0u)) { + return WT_TABLES_ERROR_ARGUMENT; + } + if ((va % WT_TABLES_PAGE_SIZE) != 0u) { + return WT_TABLES_ERROR_ALIGN; + } + if ((va >= WT_TABLES_VA_LIMIT) || + (pages > ((WT_TABLES_VA_LIMIT - va) / WT_TABLES_PAGE_SIZE))) { + return WT_TABLES_ERROR_RANGE; + } + end = va + ((uint64_t)pages * WT_TABLES_PAGE_SIZE); + for (at = va; at < end; at += WT_TABLES_PAGE_SIZE) { + probe = l3_entry(t, pool, at); + if ((probe == NULL) || !el0_owned_page(*probe)) { + return WT_TABLES_ERROR_UNMAPPED; + } + pte = owned_encoding(*probe, attributes); + if (pte < 0) { + return (int)pte; + } + } + for (at = va; at < end; at += WT_TABLES_PAGE_SIZE) { + entry = l3_entry(t, pool, at); + *entry = (uint64_t)owned_encoding(*entry, attributes) | + (*entry & PTE_ADDR_MASK); + } + return WT_TABLES_OK; +} + +static int el1_only_page(uint64_t desc) +{ + uint32_t ap = (uint32_t)((desc >> PTE_AP_SHIFT) & 0x3u); + + return ((desc & DESC_VALID) != 0u) && + ((ap == WT_TABLES_AP_EL1_RW) || (ap == WT_TABLES_AP_EL1_RO)); +} + +/* The EL1-only entry a revoke puts back: non-global Normal read-write data, + * Secure or Non-secure as the page is. */ +static int64_t window_home(uint64_t desc) +{ + uint32_t attributes = WT_MEM_ATTR_READ | WT_MEM_ATTR_WRITE | + WT_TABLES_ATTR_NG; + + if ((desc & PTE_NS) != 0u) { + attributes |= WT_TABLES_ATTR_NS; + } + return encode(attributes, 1); +} + +/* Only a non-global entry may become an EL0 page: a global one cached under + * any ASID would still match after the grant's per-ASID invalidation. A page + * its owner made no-access is still its own, never a window. And only the + * exact entry a revoke rebuilds is granted, so a revoke restores it; a held + * page reaches here only once its donate completed, leaving the hold spent. */ +static int grantable_page(uint64_t desc) +{ + uint64_t hw = desc & ~PTE_ADDR_MASK; + + if ((desc & PTE_SW_HELD) != 0u) { + hw &= ~PTE_SW_MASK; + } + return hw == (uint64_t)window_home(desc); +} + +static int window_range_ok(const wt_tables_t* t, uint64_t va, size_t pages) +{ + if ((t == NULL) || (t->l1 == NULL) || (pages == 0u)) { + return WT_TABLES_ERROR_ARGUMENT; + } + if ((va % WT_TABLES_PAGE_SIZE) != 0u) { + return WT_TABLES_ERROR_ALIGN; + } + if ((va >= WT_TABLES_VA_LIMIT) || + (pages > ((WT_TABLES_VA_LIMIT - va) / WT_TABLES_PAGE_SIZE))) { + return WT_TABLES_ERROR_RANGE; + } + return WT_TABLES_OK; +} + +int wt_tables_grant_el0(wt_tables_t* t, const wt_tables_pool_t* pool, + uint64_t va, size_t pages, uint32_t attributes, + int* was_mapped) +{ + const uint64_t* probe; + uint64_t* entry; + uint64_t end; + uint64_t at; + int64_t pte; + int ret = window_range_ok(t, va, pages); + + if ((ret == WT_TABLES_OK) && + ((pool == NULL) || (was_mapped == NULL) || + ((attributes & (WT_MEM_ATTR_DEVICE | WT_MEM_ATTR_EXEC)) != 0u))) { + ret = WT_TABLES_ERROR_ARGUMENT; + } + if (ret != WT_TABLES_OK) { + return ret; + } + pte = encode(attributes, 0); + if (pte < 0) { + return (int)pte; + } + end = va + ((uint64_t)pages * WT_TABLES_PAGE_SIZE); + for (at = va; at < end; at += WT_TABLES_PAGE_SIZE) { + probe = l3_entry(t, pool, at); + if ((probe == NULL) || ((*probe & DESC_VALID) == 0u)) { + return WT_TABLES_ERROR_UNMAPPED; + } + if (!grantable_page(*probe) || + (((*probe & PTE_NS) != 0u) != + ((attributes & WT_TABLES_ATTR_NS) != 0u))) { + return WT_TABLES_ERROR_OVERLAP; + } + } + for (at = va; at < end; at += WT_TABLES_PAGE_SIZE) { + entry = l3_entry(t, pool, at); + *entry = (uint64_t)pte | (*entry & PTE_ADDR_MASK); + } + *was_mapped = 1; + return WT_TABLES_OK; +} + +int wt_tables_revoke_el0(wt_tables_t* t, const wt_tables_pool_t* pool, + uint64_t va, size_t pages, int was_mapped) +{ + uint64_t* entry; + uint64_t end; + uint64_t at; + int ret = window_range_ok(t, va, pages); + + if ((ret == WT_TABLES_OK) && (pool == NULL)) { + ret = WT_TABLES_ERROR_ARGUMENT; + } + if (ret != WT_TABLES_OK) { + return ret; + } + end = va + ((uint64_t)pages * WT_TABLES_PAGE_SIZE); + for (at = va; at < end; at += WT_TABLES_PAGE_SIZE) { + entry = l3_entry(t, pool, at); + if ((entry == NULL) || ((*entry & DESC_VALID) == 0u) || + el1_only_page(*entry)) { + return WT_TABLES_ERROR_UNMAPPED; + } + } + for (at = va; at < end; at += WT_TABLES_PAGE_SIZE) { + entry = l3_entry(t, pool, at); + if (was_mapped == 0) { + *entry = 0u; + continue; + } + *entry = (uint64_t)window_home(*entry) | (*entry & PTE_ADDR_MASK); + } + return WT_TABLES_OK; +} + +static uint64_t* el0_entry(const wt_tables_t* t, const wt_tables_pool_t* pool, + uint64_t va, uint64_t sw) +{ + uint64_t* entry = l3_entry(t, pool, va); + uint32_t ap; + + if ((entry == NULL) || ((*entry & DESC_VALID) == 0u) || + ((*entry & PTE_SW_HELD) != sw)) { + return NULL; + } + ap = (uint32_t)((*entry >> PTE_AP_SHIFT) & 0x3u); + if ((sw == 0u) && (ap != WT_TABLES_AP_ALL_RW) && (ap != WT_TABLES_AP_ALL_RO)) { + return NULL; + } + return entry; +} + +int wt_tables_hold_el0(wt_tables_t* t, const wt_tables_pool_t* pool, + uint64_t va, size_t pages, int keep_read) +{ + uint64_t* entry; + uint64_t end; + uint64_t at; + uint64_t saved; + int ret = window_range_ok(t, va, pages); + + if ((ret == WT_TABLES_OK) && (pool == NULL)) { + ret = WT_TABLES_ERROR_ARGUMENT; + } + if (ret != WT_TABLES_OK) { + return ret; + } + end = va + ((uint64_t)pages * WT_TABLES_PAGE_SIZE); + for (at = va; at < end; at += WT_TABLES_PAGE_SIZE) { + if (el0_entry(t, pool, at, 0u) == NULL) { + return WT_TABLES_ERROR_UNMAPPED; + } + } + for (at = va; at < end; at += WT_TABLES_PAGE_SIZE) { + entry = el0_entry(t, pool, at, 0u); + saved = PTE_SW_HELD; + if ((*entry & PTE_AP_RO) != 0u) { + saved |= PTE_SW_AP_RO; + } + if ((*entry & PTE_UXN) != 0u) { + saved |= PTE_SW_UXN; + } + if ((*entry & PTE_PXN) != 0u) { + saved |= PTE_SW_PXN; + } + if (keep_read != 0) { + *entry = (*entry & ~PTE_SW_MASK) | PTE_AP_RO | saved; + } + else { + *entry = (*entry & ~(PTE_AP_EL0 | PTE_SW_MASK)) | PTE_UXN | + PTE_PXN | saved; + } + } + return WT_TABLES_OK; +} + +int wt_tables_release_el0(wt_tables_t* t, const wt_tables_pool_t* pool, + uint64_t va, size_t pages) +{ + uint64_t* entry; + uint64_t end; + uint64_t at; + uint64_t desc; + int ret = window_range_ok(t, va, pages); + + if ((ret == WT_TABLES_OK) && (pool == NULL)) { + ret = WT_TABLES_ERROR_ARGUMENT; + } + if (ret != WT_TABLES_OK) { + return ret; + } + end = va + ((uint64_t)pages * WT_TABLES_PAGE_SIZE); + for (at = va; at < end; at += WT_TABLES_PAGE_SIZE) { + if (el0_entry(t, pool, at, PTE_SW_HELD) == NULL) { + return WT_TABLES_ERROR_UNMAPPED; + } + } + for (at = va; at < end; at += WT_TABLES_PAGE_SIZE) { + entry = el0_entry(t, pool, at, PTE_SW_HELD); + desc = (*entry & ~(PTE_AP_RO | PTE_UXN | PTE_PXN | PTE_SW_MASK)) | + PTE_AP_EL0; + if ((*entry & PTE_SW_AP_RO) != 0u) { + desc |= PTE_AP_RO; + } + if ((*entry & PTE_SW_UXN) != 0u) { + desc |= PTE_UXN; + } + if ((*entry & PTE_SW_PXN) != 0u) { + desc |= PTE_PXN; + } + *entry = desc; + } + return WT_TABLES_OK; +} + +int wt_tables_withdraw_el0(wt_tables_t* t, const wt_tables_pool_t* pool, + uint64_t va, size_t pages) +{ + uint64_t* entry; + uint64_t end; + uint64_t at; + int ret = window_range_ok(t, va, pages); + + if ((ret == WT_TABLES_OK) && (pool == NULL)) { + ret = WT_TABLES_ERROR_ARGUMENT; + } + if (ret != WT_TABLES_OK) { + return ret; + } + end = va + ((uint64_t)pages * WT_TABLES_PAGE_SIZE); + for (at = va; at < end; at += WT_TABLES_PAGE_SIZE) { + if (el0_entry(t, pool, at, PTE_SW_HELD) == NULL) { + return WT_TABLES_ERROR_UNMAPPED; + } + } + for (at = va; at < end; at += WT_TABLES_PAGE_SIZE) { + entry = el0_entry(t, pool, at, PTE_SW_HELD); + *entry = (*entry & ~(PTE_AP_EL0 | PTE_AP_RO)) | PTE_UXN | PTE_PXN; + if ((*entry & PTE_SW_AP_RO) != 0u) { + *entry |= PTE_AP_RO; + } + } + return WT_TABLES_OK; +} + +uint64_t wt_tables_ttbr0(const wt_tables_t* t) +{ + return (t->l1_pa & PTE_ADDR_MASK) | ((uint64_t)t->asid << 48); +} diff --git a/src/arch/aarch64/spm/wolftrust.ld b/src/arch/aarch64/spm/wolftrust.ld new file mode 100644 index 00000000..bce7bafc --- /dev/null +++ b/src/arch/aarch64/spm/wolftrust.ld @@ -0,0 +1,210 @@ +/* S-EL1 SPMC image: code and constant data in the SPM image band (loaded by + * the monitor on QEMU virt, by the loader on Versal), data, bss, and the + * bootstrap stack in the SPM RAM band, the vault, attestation and crypto + * state each in its own band so each partition is granted only its own. + * Band addresses arrive as --defsym values read from the shared layout, + * port/common/aarch64/l3_layout.h, by mk/arch-aarch64.mk. */ +ENTRY(wt_spm_entry) + +MEMORY +{ + IMAGE (rx) : ORIGIN = WT_SPM_IMAGE_PA, LENGTH = WT_SPM_IMAGE_SIZE + RAM (rw) : ORIGIN = WT_SPM_RAM_PA, LENGTH = WT_SPM_RAM_SIZE + /* One private band per keystore partition, from the shared layout; the + * build checks the port manifests grant exactly these. */ + VAULTDATA (rw) : ORIGIN = WT_SPM_VAULT_PA, LENGTH = WT_SPM_VAULT_SIZE + ATTESTDATA (rw) : ORIGIN = WT_SPM_ATTEST_PA, LENGTH = WT_SPM_ATTEST_SIZE + HSMDATA (rw) : ORIGIN = WT_SPM_HSMDATA_PA, LENGTH = WT_SPM_HSMDATA_SIZE + CONFDATA (rw) : ORIGIN = WT_SPM_CONFDATA_PA, LENGTH = WT_SPM_CONFDATA_SIZE +} + +SECTIONS +{ + .text : { + KEEP(*(.text.wt_spm_entry)) + *(.text .text.*) + . = ALIGN(4096); + _e_secure_text = .; + } > IMAGE + .rodata (READONLY) : { + KEEP(*(.wt_guest_meas)) + /* wolfCrypt globals that are only ever read (the AES prefetch anchor, + * the DRBG selection flag): read-only, so every partition sees them. */ + *(.data.always_prefetch* .bss.always_prefetch*) + *(.data.sha256DrbgDisabled* .bss.sha256DrbgDisabled*) + *(.rodata .rodata.*) + . = ALIGN(4096); + _e_secure_rodata = .; + } > IMAGE + + .vault_data : { + _s_vault = .; + _s_keystore = .; + *nvm_store.o(.data .data.*) + *wt_hsm_vault.o(.data .data.*) + *wt_hsm_seal.o(.data .data.*) + *wt_hsm_lock.o(.data .data.*) + *vault_service.o(.data .data.*) + *hsm_nvm.o(.data .data.*) + *wh_sec_wh_nvm.o(.data .data.*) + *wh_sec_wh_nvm_flash.o(.data .data.*) + *wh_sec_wh_flash_unit.o(.data .data.*) + *wh_sec_wh_lock.o(.data .data.*) + . = ALIGN(16); + _e_vault_data = .; + } > VAULTDATA AT > IMAGE + _si_vault = LOADADDR(.vault_data); + .vault_bss (NOLOAD) : { + _s_vault_bss = .; + *nvm_store.o(.bss .bss.* COMMON) + *wt_hsm_vault.o(.bss .bss.* COMMON) + *wt_hsm_seal.o(.bss .bss.* COMMON) + *wt_hsm_lock.o(.bss .bss.* COMMON) + *vault_service.o(.bss .bss.* COMMON) + *hsm_nvm.o(.bss .bss.* COMMON) + *wh_sec_wh_nvm.o(.bss .bss.* COMMON) + *wh_sec_wh_nvm_flash.o(.bss .bss.* COMMON) + *wh_sec_wh_flash_unit.o(.bss .bss.* COMMON) + *wh_sec_wh_lock.o(.bss .bss.* COMMON) + . = ALIGN(16); + _e_vault = .; + } > VAULTDATA + .attest_data : { + _s_attest = .; + *attestation_service.o(.data .data.*) + *initial_attestation.o(.data .data.*) + *attestation_cose.o(.data .data.*) + *wolfcose*.o(.data .data.*) + . = ALIGN(16); + _e_attest_data = .; + } > ATTESTDATA AT > IMAGE + _si_attest = LOADADDR(.attest_data); + .attest_bss (NOLOAD) : { + _s_attest_bss = .; + *attestation_service.o(.bss .bss.* COMMON) + *initial_attestation.o(.bss .bss.* COMMON) + *attestation_cose.o(.bss .bss.* COMMON) + *wolfcose*.o(.bss .bss.* COMMON) + . = ALIGN(16); + _e_attest = .; + } > ATTESTDATA + .hsm_data : { + _s_hsm = .; + *wt_hsm.o(.data .data.*) + *hsm_relay_service.o(.data .data.*) + *nvm_client.o(.data .data.*) + *crypto_native.o(.data .data.*) + *keyvault.o(.data .data.*) + *native_wire.o(.data .data.*) + *wh_sec_*.o(.data .data.*) + *wc_sec_cryptocb.o(.data .data.*) + . = ALIGN(16); + _e_hsm_data = .; + } > HSMDATA AT > IMAGE + _si_hsm = LOADADDR(.hsm_data); + .hsm_bss (NOLOAD) : { + _s_hsm_bss = .; + *wt_hsm.o(.bss .bss.* COMMON) + *hsm_relay_service.o(.bss .bss.* COMMON) + *nvm_client.o(.bss .bss.* COMMON) + *crypto_native.o(.bss .bss.* COMMON) + *keyvault.o(.bss .bss.* COMMON) + *native_wire.o(.bss .bss.* COMMON) + *wh_sec_*.o(.bss .bss.* COMMON) + *wc_sec_cryptocb.o(.bss .bss.* COMMON) + . = ALIGN(16); + _e_hsm = .; + _e_keystore = .; + } > HSMDATA + + /* The Arm conformance partitions run at S-EL0 and keep their writable + * state in this band while SPM RAM stays EL1-only: one page-aligned + * segment per partition, each granted to its owner alone + * (wt_platform_conf_sp_grants). Empty in non-conformance builds. */ + .conf_data : { + _s_conf_data = .; + _s_conf_server_data = .; + *conf_sec_server_partition.o(.data .data.* .bss .bss.* COMMON) + *conf_sec_test_supp_*.o(.data .data.* .bss .bss.* COMMON) + . = ALIGN(4096); + _e_conf_server_data = .; + _s_conf_client_data = .; + *conf_sec_client_partition.o(.data .data.* .bss .bss.* COMMON) + *conf_sec_test_i*.o(.data .data.* .bss .bss.* COMMON) + . = ALIGN(4096); + _e_conf_client_data = .; + _s_conf_driver_data = .; + *conf_sec_driver_partition.o(.data .data.* .bss .bss.* COMMON) + *conf_sec_val_driver_service_apis.o(.data .data.* .bss .bss.* COMMON) + *conf_sec_val_log.o(.data .data.* .bss .bss.* COMMON) + *conf_sec_pal_driver_intf.o(.data .data.* .bss .bss.* COMMON) + *conf_sec_conf_nvm_sync.o(.data .data.* .bss .bss.* COMMON) + . = ALIGN(4096); + _e_conf_driver_data = .; + _s_conf_unowned = .; + *conf_sec_*.o(.data .data.*) + _e_conf_unowned = .; + . = ALIGN(16); + _e_conf_data_data = .; + } > CONFDATA AT > IMAGE + _si_conf_data = LOADADDR(.conf_data); + .conf_bss (NOLOAD) : { + _s_conf_bss = .; + *conf_sec_*.o(.bss .bss.* COMMON) + . = ALIGN(16); + _e_conf_bss = .; + } > CONFDATA + _e_conf_data = .; + + .data : { + . = ALIGN(16); + __data_start = .; + *(.data .data.*) + . = ALIGN(16); + __data_end = .; + } > RAM AT > IMAGE + __data_lma = LOADADDR(.data); + __image_end = __data_lma + SIZEOF(.data); + .bss (NOLOAD) : { + . = ALIGN(16); + __bss_start = .; + *(.bss .bss.*) + *(COMMON) + . = ALIGN(16); + __bss_end = .; + } > RAM + /* The page below the SPM stack stays unmapped, so an overflow aborts at + * S-EL1 and panics through the vector table instead of overwriting bss. */ + .stack (NOLOAD) : { + . = ALIGN(4096); + __spm_stack_guard = .; + . += 4096; + . += 0x4000; + __spm_stack_top = .; + } > RAM + /* Outside the bss-clear range and NOLOAD, so the conformance NVM shadow + * survives the EL3 warm reset the panic tests use (QEMU keeps RAM across a + * warm reset). Empty in non-conformance builds. */ + .noinit (NOLOAD) : { + . = ALIGN(16); + *(.noinit .noinit.*) + . = ALIGN(16); + } > RAM + __spm_ram_end = .; + + /DISCARD/ : { + *(.note*) + *(.comment) + *(.eh_frame*) + } +} + +ASSERT((wt_spm_vectors & 0x7FF) == 0, "S-EL1 vector table must be 2 KiB aligned") +ASSERT(__image_end <= WT_SPM_IMAGE_PA + WT_SPM_IMAGE_SIZE, "SPM image band overflow") +ASSERT(_e_keystore <= WT_SPM_KEYSTORE_PA + WT_SPM_KEYSTORE_SIZE, "keystore band overflow") +ASSERT(WT_SPM_HSMDATA_PA + WT_SPM_HSMDATA_SIZE == WT_SPM_KEYSTORE_PA + WT_SPM_KEYSTORE_SIZE, "the keystore bands must tile the keystore window") +ASSERT(_e_conf_data <= WT_SPM_CONFDATA_PA + WT_SPM_CONFDATA_SIZE, "conformance data band overflow") +ASSERT(_e_conf_bss <= WT_SPM_CONFDATA_PA + 0x1C000, "conformance data reaches the pseudo-MMIO pages") +ASSERT((_s_conf_data & 0xFFF) == 0, "conformance partition segments must be page aligned") +ASSERT((_e_conf_unowned == _s_conf_unowned) && (_e_conf_bss == _s_conf_bss), + "a conformance object has writable data no partition owns") diff --git a/src/arch/common/spm_gate_core.c b/src/arch/common/spm_gate_core.c index 2c920693..f5ecf021 100644 --- a/src/arch/common/spm_gate_core.c +++ b/src/arch/common/spm_gate_core.c @@ -406,13 +406,14 @@ void wt_spm_set_hsm_partition(int32_t partition_id) * asserted suspends the coroutine; the SP-side transport re-issues the trap * on wake. Returns the gate-level status the decoder hands back to the SP. */ static int wt_spm_dispatch_held(wt_spm_sp_t* slot, wt_spm_call_t* call, - wt_trap_frame_t* frame); + wt_trap_frame_t* frame, int* block); int wt_spm_dispatch_call(wt_spm_call_t* call, wt_trap_frame_t* frame) { wt_spm_call_t held; wt_spm_sp_t* slot; int status; + int block = 0; slot = wt_spm_slot_for_current(); if (g_spm_svc_runtime == NULL || slot == NULL || @@ -433,13 +434,18 @@ int wt_spm_dispatch_call(wt_spm_call_t* call, wt_trap_frame_t* frame) /* The block lives in the partition's memory: read it once, so every * privileged check and use below sees the same request. */ (void)memcpy(&held, call, sizeof(held)); - status = wt_spm_dispatch_held(slot, &held, frame); + status = wt_spm_dispatch_held(slot, &held, frame, &block); (void)memcpy(call, &held, sizeof(held)); + /* Block only once the reply is in the partition's block: an AArch64 + * switch is immediate and never returns to finish the copy-back. */ + if (block != 0) { + wt_co_block(); + } return status; } static int wt_spm_dispatch_held(wt_spm_sp_t* slot, wt_spm_call_t* call, - wt_trap_frame_t* frame) + wt_trap_frame_t* frame, int* block) { const wt_scheduler_state_t* sched; int status; @@ -451,6 +457,11 @@ static int wt_spm_dispatch_held(wt_spm_sp_t* slot, wt_spm_call_t* call, call->ret_tick = (sched != NULL) ? sched->monotonic_ticks : 0u; #if defined(WT_CONFORMANCE) && (WT_CONFORMANCE == 1) + if ((call->op == WT_SPM_OP_CONF_NVM_SYNC || + call->op == WT_SPM_OP_CONF_IRQ_SET) && + slot->partition_id != DRIVER_PARTITION_ID) { + return WT_FFM_ERROR_ARGUMENT; + } /* Platform NVM service (P5 K2): the unprivileged DRIVER partition cannot * touch the flash controller, so it traps its shadow buffer here for the * privileged sync. Validate the buffer inside the caller's domain (written @@ -637,7 +648,7 @@ static int wt_spm_dispatch_held(wt_spm_sp_t* slot, wt_spm_call_t* call, * and report retry; the release hands the mutex over * before waking, so the re-issue observes ownership. */ slot->wait_kind = WT_SPM_WAIT_LOCK; - wt_co_block(); + *block = 1; } else if (ks_ret == 0) { slot->wait_kind = WT_SPM_WAIT_NONE; @@ -769,7 +780,7 @@ static int wt_spm_dispatch_held(wt_spm_sp_t* slot, wt_spm_call_t* call, slot->wait_kind = WT_SPM_WAIT_MSG; slot->wait_msg = call->pending_msg; } - wt_co_block(); + *block = 1; } return status; } @@ -1204,6 +1215,11 @@ int wt_spm_partition_memory_ok(int32_t partition_id, const void* address, return 0; } +int wt_spm_sched_current_is_partition(void) +{ + return (wt_spm_slot_for_current() != NULL) ? 1 : 0; +} + int wt_spm_sched_validate(void) { wt_memory_region_t spm_ram[2]; diff --git a/src/client/psa_ffm_client.c b/src/client/psa_ffm_client.c index dcba4267..01a9312a 100644 --- a/src/client/psa_ffm_client.c +++ b/src/client/psa_ffm_client.c @@ -60,6 +60,19 @@ void psa_close(psa_handle_t handle) WolfTrust_FFM_Close((int32_t)handle); } +/* The veneer carries 32-bit counts and lengths: an LP64 value above them + * saturates, so an over-count or an oversized vector stays invalid at the + * Secure gateway instead of wrapping into an accepted one. */ +static uint32_t veneer_size(size_t value) +{ +#if SIZE_MAX > UINT32_MAX + if (value > (size_t)UINT32_MAX) { + return UINT32_MAX; + } +#endif + return (uint32_t)value; +} + psa_status_t psa_call(psa_handle_t handle, int32_t type, const psa_invec* in_vec, size_t in_len, psa_outvec* out_vec, size_t out_len) @@ -77,17 +90,17 @@ psa_status_t psa_call(psa_handle_t handle, int32_t type, if (in_len <= WT_FFM_VENEER_IOVEC_MAX) { for (i = 0u; i < in_len; i++) { iovec.in[i].base = in_vec[i].base; - iovec.in[i].len = (uint32_t)in_vec[i].len; + iovec.in[i].len = veneer_size(in_vec[i].len); } } if (out_len <= WT_FFM_VENEER_IOVEC_MAX) { for (i = 0u; i < out_len; i++) { iovec.out[i].base = out_vec[i].base; - iovec.out[i].len = (uint32_t)out_vec[i].len; + iovec.out[i].len = veneer_size(out_vec[i].len); } } - iovec.in_count = (uint32_t)in_len; - iovec.out_count = (uint32_t)out_len; + iovec.in_count = veneer_size(in_len); + iovec.out_count = veneer_size(out_len); status = (psa_status_t)WolfTrust_FFM_Call((int32_t)handle, type, &iovec); if (out_len <= WT_FFM_VENEER_IOVEC_MAX) { for (i = 0u; i < out_len; i++) { diff --git a/tests/conformance/ffa-acs/build_acs.sh b/tests/conformance/ffa-acs/build_acs.sh new file mode 100755 index 00000000..8f7a50f6 --- /dev/null +++ b/tests/conformance/ffa-acs/build_acs.sh @@ -0,0 +1,110 @@ +#!/usr/bin/env bash +# build_acs.sh +# +# Copyright (C) 2026 wolfSSL Inc. +# +# This file is part of wolfTrust. +# +# wolfTrust 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. +# +# wolfTrust 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, see . + +# Build the Arm FF-A ACS endpoint images (sp1..sp4 at S-EL0, vm1 as the +# Normal-world dispatcher) for a wolfTrust QEMU machine. +# +# build_acs.sh +# +# SUITE= (default all) +# TOOLPREFIX (default aarch64-none-elf-) WT_ACS_SP_ID_BASE (default 0x8002) +set -euo pipefail + +machine="${1:-}" +out="${2:-}" +if [ -z "$machine" ] || [ -z "$out" ]; then + echo "usage: $0 " >&2 + exit 2 +fi + +here="$(cd "$(dirname "$0")" && pwd)" +repo="$(cd "$here/../../.." && pwd)" +TOOLPREFIX="${TOOLPREFIX:-aarch64-none-elf-}" +SUITE="${SUITE:-all}" +sp_id_base="${WT_ACS_SP_ID_BASE:-0x8002}" + +case "$machine" in + virt) + secure_base=0x0E000000 + ns_uart=0x09000000; s_uart=0x09040000 + gicd=0x08000000; gicc=0x08010000; gicr=0x080A0000 ;; + versal-virt) + secure_base=0x7F000000 + ns_uart=0xFF000000; s_uart=0xFF010000 + gicd=0xF9000000; gicc=0xF9040000; gicr=0xF9080000 ;; + *) echo "unsupported machine $machine (virt or versal-virt)" >&2; exit 2 ;; +esac + +mkdir -p "$out" +out="$(cd "$out" && pwd)" +acs="$out/ff-a-acs" +"$repo/tests/upstream/fetch_ffa_acs.sh" "$acs" >/dev/null + +# Platform exclusions, each a recorded patch: the suite stays pinned and any +# test the platform cannot host skips by name instead of hanging the run. A +# rebuild in the same directory first drops the last build's patches, wherever +# they landed. +git -C "$acs" checkout -q -- . +for patch in "$here"/patches/*.patch; do + git -C "$acs" apply "$patch" +done + +target="$acs/platform/pal_baremetal/tgt_wolftrust_qemu" +rm -rf "$target" +cp -R "$here/tgt_wolftrust_qemu" "$target" + +# The endpoint image bands sit behind the SPMC's own bands in Secure RAM: +# four 1 MB partition images, the 64 KB test NVM, one read-only test page. +cat > "$target/inc/wt_acs_machine.h" < cmake.log 2>&1 \ + && make -j"$(nproc 2>/dev/null || echo 4)" > make.log 2>&1 ) || { + tail -40 "$build/cmake.log" "$build/make.log" 2>/dev/null >&2 + echo "FF-A ACS build failed" >&2 + exit 1 + } + +for image in sp1 sp2 sp3 sp4 vm1; do + cp "$build/output/$image.bin" "$out/$image.bin" +done +echo "$out" diff --git a/tests/conformance/ffa-acs/patches/0001-skip-up-migrate-on-a-single-pe-platform.patch b/tests/conformance/ffa-acs/patches/0001-skip-up-migrate-on-a-single-pe-platform.patch new file mode 100644 index 00000000..5b6190f5 --- /dev/null +++ b/tests/conformance/ffa-acs/patches/0001-skip-up-migrate-on-a-single-pe-platform.patch @@ -0,0 +1,18 @@ +diff --git a/test/v1.0/setup_discovery/up_migrate_capable/up_migrate_capable_client.c b/test/v1.0/setup_discovery/up_migrate_capable/up_migrate_capable_client.c +index b68b1c7..90daf61 100644 +--- a/test/v1.0/setup_discovery/up_migrate_capable/up_migrate_capable_client.c ++++ b/test/v1.0/setup_discovery/up_migrate_capable/up_migrate_capable_client.c +@@ -44,6 +44,13 @@ uint32_t up_migrate_capable_client(uint32_t test_run_data) + uint32_t status = VAL_ERROR; + uint32_t test_num = GET_TEST_NUM(test_run_data); + ++ /* A single-PE platform has no second execution context to migrate to */ ++ if (total_cpus < 2) ++ { ++ LOG(TEST, "Skipping the check, platform has a single PE"); ++ return VAL_SKIP_CHECK; ++ } ++ + /* Run server test on boot cpu */ + payload = val_select_server_fn_direct(test_run_data, 0, 0, 0, 0); + if (payload.fid != FFA_MSG_SEND_DIRECT_RESP_32) diff --git a/tests/conformance/ffa-acs/patches/0002-read-the-memory-handle-before-features-overwrites-it.patch b/tests/conformance/ffa-acs/patches/0002-read-the-memory-handle-before-features-overwrites-it.patch new file mode 100644 index 00000000..edc25bd0 --- /dev/null +++ b/tests/conformance/ffa-acs/patches/0002-read-the-memory-handle-before-features-overwrites-it.patch @@ -0,0 +1,44 @@ +diff --git a/test/v1.0/memory_manage/multiple_retrievals_by_borrower/lend_multiple_retrievals_server.c b/test/v1.0/memory_manage/multiple_retrievals_by_borrower/lend_multiple_retrievals_server.c +index 6338758..a5f7277 100644 +--- a/test/v1.0/memory_manage/multiple_retrievals_by_borrower/lend_multiple_retrievals_server.c ++++ b/test/v1.0/memory_manage/multiple_retrievals_by_borrower/lend_multiple_retrievals_server.c +@@ -64,6 +64,9 @@ uint32_t lend_multiple_retrievals_server(ffa_args_t args) + goto rxtx_unmap; + } + ++ /* The handle arrived in the direct request; FFA_FEATURES reuses payload */ ++ handle = payload.arg3; ++ + val_memset(&payload, 0, sizeof(ffa_args_t)); + if (fid == FFA_MEM_LEND_64) + payload.arg1 = FFA_MEM_RETRIEVE_REQ_64; +@@ -95,7 +98,6 @@ uint32_t lend_multiple_retrievals_server(ffa_args_t args) + LOG(TEST, "Outstanding retrievals count %d", outstanding_retrieve_count); + } + +- handle = payload.arg3; + + val_memset(&mem_region_init, 0x0, sizeof(mem_region_init)); + mem_region_init.memory_region = mb.send; +diff --git a/test/v1.0/memory_manage/multiple_retrievals_by_borrower/share_multiple_retrievals_server.c b/test/v1.0/memory_manage/multiple_retrievals_by_borrower/share_multiple_retrievals_server.c +index 6b2e758..c93fec5 100644 +--- a/test/v1.0/memory_manage/multiple_retrievals_by_borrower/share_multiple_retrievals_server.c ++++ b/test/v1.0/memory_manage/multiple_retrievals_by_borrower/share_multiple_retrievals_server.c +@@ -64,6 +64,9 @@ uint32_t share_multiple_retrievals_server(ffa_args_t args) + goto rxtx_unmap; + } + ++ /* The handle arrived in the direct request; FFA_FEATURES reuses payload */ ++ handle = payload.arg3; ++ + val_memset(&payload, 0, sizeof(ffa_args_t)); + if (fid == FFA_MEM_SHARE_64) + payload.arg1 = FFA_MEM_RETRIEVE_REQ_64; +@@ -95,7 +98,6 @@ uint32_t share_multiple_retrievals_server(ffa_args_t args) + LOG(TEST, "Outstanding retrievals count %d", outstanding_retrieve_count); + } + +- handle = payload.arg3; + + val_memset(&mem_region_init, 0x0, sizeof(mem_region_init)); + mem_region_init.memory_region = mb.send; diff --git a/tests/conformance/ffa-acs/patches/0003-clear-the-memory-region-request-before-filling-it.patch b/tests/conformance/ffa-acs/patches/0003-clear-the-memory-region-request-before-filling-it.patch new file mode 100644 index 00000000..baee3508 --- /dev/null +++ b/tests/conformance/ffa-acs/patches/0003-clear-the-memory-region-request-before-filling-it.patch @@ -0,0 +1,574 @@ +diff --git a/test/v1.0/memory_manage/donate_input_error_checks/donate_input_error_checks_client.c b/test/v1.0/memory_manage/donate_input_error_checks/donate_input_error_checks_client.c +index be467db..40edd93 100644 +--- a/test/v1.0/memory_manage/donate_input_error_checks/donate_input_error_checks_client.c ++++ b/test/v1.0/memory_manage/donate_input_error_checks/donate_input_error_checks_client.c +@@ -36,6 +36,7 @@ static uint32_t mem_donate_invalid_epid_check(void *tx_buf, + /* Relayer must ensure that the Endpoint ID field in each Memory access permissions descriptor + * specifies a valid endpoint. The Relayer must return INVALID_PARAMETERS in case of an error. + */ ++ val_memset(&mem_region_init, 0x0, sizeof(mem_region_init)); + mem_region_init.sender = sender; + mem_region_init.receiver = receiver; + mem_region_init.memory_region = tx_buf; +@@ -101,6 +102,7 @@ static uint32_t mem_donate_data_access_perm_check(void *tx_buf, ffa_endpoint_id_ + constituents[0].address = val_mem_virt_to_phys((void *)pages); + constituents[0].page_count = 1; + ++ val_memset(&mem_region_init, 0x0, sizeof(mem_region_init)); + mem_region_init.memory_region = tx_buf; + mem_region_init.sender = sender; + mem_region_init.receiver = recipient; +@@ -170,6 +172,7 @@ static uint32_t mem_donate_mem_attribute_check(void *tx_buf, ffa_endpoint_id_t s + constituents[0].address = val_mem_virt_to_phys((void *)pages); + constituents[0].page_count = 1; + ++ val_memset(&mem_region_init, 0x0, sizeof(mem_region_init)); + mem_region_init.memory_region = tx_buf; + mem_region_init.sender = sender; + mem_region_init.receiver = recipient; +@@ -237,6 +240,7 @@ static uint32_t mem_donate_mmio_check(void *tx_buf, ffa_endpoint_id_t sender, ui + + constituents[0].page_count = 1; + ++ val_memset(&mem_region_init, 0x0, sizeof(mem_region_init)); + mem_region_init.memory_region = tx_buf; + mem_region_init.sender = sender; + mem_region_init.receiver = recipient; +@@ -298,6 +302,7 @@ static uint32_t mem_donate_instruction_access_perm_check(void *tx_buf, ffa_endpo + constituents[0].address = val_mem_virt_to_phys((void *)pages); + constituents[0].page_count = 1; + ++ val_memset(&mem_region_init, 0x0, sizeof(mem_region_init)); + mem_region_init.memory_region = tx_buf; + mem_region_init.sender = sender; + mem_region_init.receiver = recipient; +@@ -370,6 +375,7 @@ static uint32_t mem_donate_invalid_ep_count_check(void *tx_buf, ffa_endpoint_id_ + constituents[0].address = val_mem_virt_to_phys((void *)pages); + constituents[0].page_count = 1; + ++ val_memset(&mem_region_init, 0x0, sizeof(mem_region_init)); + mem_region_init.memory_region = tx_buf; + mem_region_init.sender = sender; + mem_region_init.receiver = recipient; +@@ -444,6 +450,7 @@ static uint32_t mem_donate_invalid_ep_desc_offset_check(void *tx_buf, ffa_endpoi + constituents[0].address = val_mem_virt_to_phys((void *)pages); + constituents[0].page_count = 1; + ++ val_memset(&mem_region_init, 0x0, sizeof(mem_region_init)); + mem_region_init.memory_region = tx_buf; + mem_region_init.sender = sender; + mem_region_init.receiver = recipient; +diff --git a/test/v1.0/memory_manage/donate_invalid_handle_tag/donate_invalid_handle_tag_server.c b/test/v1.0/memory_manage/donate_invalid_handle_tag/donate_invalid_handle_tag_server.c +index e4f3c28..94b898d 100644 +--- a/test/v1.0/memory_manage/donate_invalid_handle_tag/donate_invalid_handle_tag_server.c ++++ b/test/v1.0/memory_manage/donate_invalid_handle_tag/donate_invalid_handle_tag_server.c +@@ -134,6 +134,7 @@ static uint32_t mem_donate_invalid_handle_tag_check(ffa_memory_handle_t handle, + /* MEM_DONATE: The Relayer must ensure the Tag value specified by the Receiver + * is equal to the value that was specified by the Sender. + */ ++ val_memset(&mem_region_init, 0x0, sizeof(mem_region_init)); + mem_region_init.memory_region = tx_buf; + mem_region_init.sender = sender; + mem_region_init.receiver = receiver; +diff --git a/test/v1.0/memory_manage/donate_retrieve_with_address_range/donate_retrieve_with_address_range_server.c b/test/v1.0/memory_manage/donate_retrieve_with_address_range/donate_retrieve_with_address_range_server.c +index 3eca657..d1c700a 100644 +--- a/test/v1.0/memory_manage/donate_retrieve_with_address_range/donate_retrieve_with_address_range_server.c ++++ b/test/v1.0/memory_manage/donate_retrieve_with_address_range/donate_retrieve_with_address_range_server.c +@@ -190,8 +190,8 @@ uint32_t donate_retrieve_with_address_range_server(ffa_args_t args) + * INVALID_PARAMETERS. + */ + flags = VAL_SET_BITS(flags, 5, 5, 0x11); +- mem_region_init.flags = flags; + val_memset(&mem_region_init, 0x0, sizeof(mem_region_init)); ++ mem_region_init.flags = flags; + mem_region_init.memory_region = mb.send; + mem_region_init.sender = receiver; + mem_region_init.receiver = sender; +diff --git a/test/v1.0/memory_manage/lend_device_attr/lend_device_attr1_client.c b/test/v1.0/memory_manage/lend_device_attr/lend_device_attr1_client.c +index 96f7044..93f99d6 100644 +--- a/test/v1.0/memory_manage/lend_device_attr/lend_device_attr1_client.c ++++ b/test/v1.0/memory_manage/lend_device_attr/lend_device_attr1_client.c +@@ -45,6 +45,7 @@ static uint32_t mem_lend_device_attr_check(uint32_t test_run_data, uint32_t fid, + constituents[0].address = val_mem_virt_to_phys((void *)pages); + constituents[0].page_count = 1; + ++ val_memset(&mem_region_init, 0x0, sizeof(mem_region_init)); + mem_region_init.memory_region = tx_buf; + mem_region_init.sender = sender; + mem_region_init.receiver = receiver; +diff --git a/test/v1.0/memory_manage/lend_device_attr/lend_device_attr1_server.c b/test/v1.0/memory_manage/lend_device_attr/lend_device_attr1_server.c +index 84aa586..887e2d6 100644 +--- a/test/v1.0/memory_manage/lend_device_attr/lend_device_attr1_server.c ++++ b/test/v1.0/memory_manage/lend_device_attr/lend_device_attr1_server.c +@@ -16,6 +16,7 @@ static uint32_t mem_lend_device_attr_check(ffa_memory_handle_t handle, uint32_t + ffa_args_t payload; + uint32_t status = VAL_SUCCESS; + ++ val_memset(&mem_region_init, 0x0, sizeof(mem_region_init)); + mem_region_init.memory_region = tx_buf; + mem_region_init.sender = sender; + mem_region_init.receiver = receiver; +diff --git a/test/v1.0/memory_manage/lend_device_attr/lend_device_attr_client.c b/test/v1.0/memory_manage/lend_device_attr/lend_device_attr_client.c +index cacafb5..3c531f4 100644 +--- a/test/v1.0/memory_manage/lend_device_attr/lend_device_attr_client.c ++++ b/test/v1.0/memory_manage/lend_device_attr/lend_device_attr_client.c +@@ -45,6 +45,7 @@ static uint32_t mem_lend_device_attr_check(uint32_t test_run_data, uint32_t fid, + constituents[0].address = val_mem_virt_to_phys((void *)pages); + constituents[0].page_count = 1; + ++ val_memset(&mem_region_init, 0x0, sizeof(mem_region_init)); + mem_region_init.memory_region = tx_buf; + mem_region_init.sender = sender; + mem_region_init.receiver = receiver; +diff --git a/test/v1.0/memory_manage/lend_device_attr/lend_device_attr_server.c b/test/v1.0/memory_manage/lend_device_attr/lend_device_attr_server.c +index 8fee2ef..1e2738c 100644 +--- a/test/v1.0/memory_manage/lend_device_attr/lend_device_attr_server.c ++++ b/test/v1.0/memory_manage/lend_device_attr/lend_device_attr_server.c +@@ -16,6 +16,7 @@ static uint32_t mem_lend_device_attr_check(ffa_memory_handle_t handle, uint32_t + ffa_args_t payload; + uint32_t status = VAL_SUCCESS; + ++ val_memset(&mem_region_init, 0x0, sizeof(mem_region_init)); + mem_region_init.memory_region = tx_buf; + mem_region_init.sender = sender; + mem_region_init.receiver = receiver; +diff --git a/test/v1.0/memory_manage/lend_input_error_checks/lend_input_error_checks1_client.c b/test/v1.0/memory_manage/lend_input_error_checks/lend_input_error_checks1_client.c +index 7167a82..7a5f560 100644 +--- a/test/v1.0/memory_manage/lend_input_error_checks/lend_input_error_checks1_client.c ++++ b/test/v1.0/memory_manage/lend_input_error_checks/lend_input_error_checks1_client.c +@@ -38,6 +38,7 @@ static uint32_t mem_lend_invalid_sender_id_check(void *tx_buf, ffa_endpoint_id_t + * that the Lender is the Owner of the memory region and a PE endpoint. + * Must return DENIED in case of an error. + */ ++ val_memset(&mem_region_init, 0x0, sizeof(mem_region_init)); + mem_region_init.sender = sender; + mem_region_init.receiver = recipient; + mem_region_init.memory_region = tx_buf; +@@ -100,6 +101,7 @@ static uint32_t mem_lend_sp_to_ns_check(void *tx_buf, ffa_endpoint_id_t sender, + constituents[0].address = val_mem_virt_to_phys((void *)pages); + constituents[0].page_count = 1; + ++ val_memset(&mem_region_init, 0x0, sizeof(mem_region_init)); + mem_region_init.sender = sender; + mem_region_init.receiver = recipient; + mem_region_init.memory_region = tx_buf; +@@ -167,6 +169,7 @@ static uint32_t mem_lend_invalid_total_length_check(void *tx_buf, ffa_endpoint_i + constituents[0].address = val_mem_virt_to_phys((void *)pages); + constituents[0].page_count = 1; + ++ val_memset(&mem_region_init, 0x0, sizeof(mem_region_init)); + mem_region_init.memory_region = tx_buf; + mem_region_init.sender = sender; + mem_region_init.receiver = recipient; +@@ -235,6 +238,7 @@ static uint32_t mem_lend_mem_attribute_check(void *tx_buf, ffa_endpoint_id_t sen + constituents[0].address = val_mem_virt_to_phys((void *)pages); + constituents[0].page_count = 1; + ++ val_memset(&mem_region_init, 0x0, sizeof(mem_region_init)); + mem_region_init.memory_region = tx_buf; + mem_region_init.sender = sender; + mem_region_init.receiver = recipient; +@@ -307,6 +311,7 @@ static uint32_t mem_lend_instruction_access_check(void *tx_buf, ffa_endpoint_id_ + constituents[0].address = val_mem_virt_to_phys((void *)pages); + constituents[0].page_count = 1; + ++ val_memset(&mem_region_init, 0x0, sizeof(mem_region_init)); + mem_region_init.memory_region = tx_buf; + mem_region_init.sender = sender; + mem_region_init.receiver = recipient; +@@ -374,6 +379,7 @@ static uint32_t mem_lend_invalid_ep_count_check(void *tx_buf, ffa_endpoint_id_t + constituents[0].address = val_mem_virt_to_phys((void *)pages); + constituents[0].page_count = 1; + ++ val_memset(&mem_region_init, 0x0, sizeof(mem_region_init)); + mem_region_init.memory_region = tx_buf; + mem_region_init.sender = sender; + mem_region_init.receiver = recipient; +@@ -447,6 +453,7 @@ static uint32_t mem_lend_invalid_ep_desc_offset_check(void *tx_buf, ffa_endpoint + constituents[0].address = val_mem_virt_to_phys((void *)pages); + constituents[0].page_count = 1; + ++ val_memset(&mem_region_init, 0x0, sizeof(mem_region_init)); + mem_region_init.memory_region = tx_buf; + mem_region_init.sender = sender; + mem_region_init.receiver = recipient; +diff --git a/test/v1.0/memory_manage/lend_input_error_checks/lend_input_error_checks_client.c b/test/v1.0/memory_manage/lend_input_error_checks/lend_input_error_checks_client.c +index 18f70e0..07e3fa7 100644 +--- a/test/v1.0/memory_manage/lend_input_error_checks/lend_input_error_checks_client.c ++++ b/test/v1.0/memory_manage/lend_input_error_checks/lend_input_error_checks_client.c +@@ -36,6 +36,7 @@ static uint32_t mem_lend_invalid_epid_check(void *tx_buf, + /* Relayer must ensure that the Endpoint ID field in each Memory access permissions descriptor + * specifies a valid endpoint. The Relayer must return INVALID_PARAMETERS in case of an error. + */ ++ val_memset(&mem_region_init, 0x0, sizeof(mem_region_init)); + mem_region_init.sender = sender; + mem_region_init.receiver = receiver; + mem_region_init.memory_region = tx_buf; +@@ -108,6 +109,7 @@ static uint32_t mem_lend_address_ranges_overlap_check(void *tx_buf, + constituents[0].address = val_mem_virt_to_phys((void *)pages); + constituents[0].page_count = 2; + ++ val_memset(&mem_region_init, 0x0, sizeof(mem_region_init)); + mem_region_init.sender = sender; + mem_region_init.receiver = recipient; + mem_region_init.memory_region = tx_buf; +@@ -222,6 +224,7 @@ static uint32_t mem_lend_mmio_check(void *tx_buf, ffa_endpoint_id_t sender, uint + + constituents[0].page_count = 1; + ++ val_memset(&mem_region_init, 0x0, sizeof(mem_region_init)); + mem_region_init.memory_region = tx_buf; + mem_region_init.sender = sender; + mem_region_init.receiver = recipient; +diff --git a/test/v1.0/memory_manage/lend_invalid_handle_tag/lend_invalid_handle_tag_server.c b/test/v1.0/memory_manage/lend_invalid_handle_tag/lend_invalid_handle_tag_server.c +index 1418d75..10ab5a4 100644 +--- a/test/v1.0/memory_manage/lend_invalid_handle_tag/lend_invalid_handle_tag_server.c ++++ b/test/v1.0/memory_manage/lend_invalid_handle_tag/lend_invalid_handle_tag_server.c +@@ -39,6 +39,7 @@ static uint32_t mem_lend_invalid_handle_tag_check(ffa_memory_handle_t handle, ui + /* MEM_LEND: The Relayer must ensure the Tag value specified by the Receiver + * is equal to the value that was specified by the Sender. + */ ++ val_memset(&mem_region_init, 0x0, sizeof(mem_region_init)); + mem_region_init.memory_region = tx_buf; + mem_region_init.sender = sender; + mem_region_init.receiver = receiver; +diff --git a/test/v1.0/memory_manage/lend_retrieve_alignment_hint/lend_retrieve_align_hint_check_server.c b/test/v1.0/memory_manage/lend_retrieve_alignment_hint/lend_retrieve_align_hint_check_server.c +index ae1a3ad..43a89f8 100644 +--- a/test/v1.0/memory_manage/lend_retrieve_alignment_hint/lend_retrieve_align_hint_check_server.c ++++ b/test/v1.0/memory_manage/lend_retrieve_alignment_hint/lend_retrieve_align_hint_check_server.c +@@ -34,6 +34,7 @@ static uint32_t retrieve_align_hint_err_check(ffa_memory_handle_t handle, uint32 + ffa_args_t payload; + uint32_t status = VAL_SUCCESS; + ++ val_memset(&mem_region_init, 0x0, sizeof(mem_region_init)); + mem_region_init.memory_region = tx_buf; + mem_region_init.sender = sender; + mem_region_init.receiver = receiver; +diff --git a/test/v1.0/memory_manage/lend_retrieve_input_checks/lend_retrieve_input_checks2_server.c b/test/v1.0/memory_manage/lend_retrieve_input_checks/lend_retrieve_input_checks2_server.c +index c013fd7..833c80f 100644 +--- a/test/v1.0/memory_manage/lend_retrieve_input_checks/lend_retrieve_input_checks2_server.c ++++ b/test/v1.0/memory_manage/lend_retrieve_input_checks/lend_retrieve_input_checks2_server.c +@@ -34,6 +34,7 @@ static uint32_t retrieve_zero_flag_check(ffa_memory_handle_t handle, uint32_t fi + ffa_args_t payload; + uint32_t status = VAL_SUCCESS; + ++ val_memset(&mem_region_init, 0x0, sizeof(mem_region_init)); + mem_region_init.memory_region = tx_buf; + mem_region_init.sender = sender; + mem_region_init.receiver = receiver; +@@ -91,6 +92,7 @@ static uint32_t retrieve_with_invalid_mem_transaction_type_check(ffa_memory_hand + ffa_args_t payload; + uint32_t status = VAL_SUCCESS; + ++ val_memset(&mem_region_init, 0x0, sizeof(mem_region_init)); + mem_region_init.memory_region = tx_buf; + mem_region_init.sender = sender; + mem_region_init.receiver = receiver; +diff --git a/test/v1.0/memory_manage/lend_retrieve_input_checks/lend_retrieve_input_checks_server.c b/test/v1.0/memory_manage/lend_retrieve_input_checks/lend_retrieve_input_checks_server.c +index c894478..e427f30 100644 +--- a/test/v1.0/memory_manage/lend_retrieve_input_checks/lend_retrieve_input_checks_server.c ++++ b/test/v1.0/memory_manage/lend_retrieve_input_checks/lend_retrieve_input_checks_server.c +@@ -34,6 +34,7 @@ static uint32_t retrieve_zero_flag_check_for_ro_mem(ffa_memory_handle_t handle, + ffa_args_t payload; + uint32_t status = VAL_SUCCESS; + ++ val_memset(&mem_region_init, 0x0, sizeof(mem_region_init)); + mem_region_init.memory_region = tx_buf; + mem_region_init.sender = sender; + mem_region_init.receiver = receiver; +diff --git a/test/v1.0/memory_manage/lend_retrieve_with_address_range/lend_retrieve_with_address_range_server.c b/test/v1.0/memory_manage/lend_retrieve_with_address_range/lend_retrieve_with_address_range_server.c +index ae1f831..fb462e2 100644 +--- a/test/v1.0/memory_manage/lend_retrieve_with_address_range/lend_retrieve_with_address_range_server.c ++++ b/test/v1.0/memory_manage/lend_retrieve_with_address_range/lend_retrieve_with_address_range_server.c +@@ -101,8 +101,8 @@ uint32_t lend_retrieve_with_address_range_server(ffa_args_t args) + * INVALID_PARAMETERS. + */ + flags = VAL_SET_BITS(flags, 5, 5, 0x11); +- mem_region_init.flags = flags; + val_memset(&mem_region_init, 0x0, sizeof(mem_region_init)); ++ mem_region_init.flags = flags; + mem_region_init.memory_region = mb.send; + mem_region_init.sender = receiver; + mem_region_init.receiver = sender; +diff --git a/test/v1.0/memory_manage/lend_shareability_attr/lend_shareability_attr_server.c b/test/v1.0/memory_manage/lend_shareability_attr/lend_shareability_attr_server.c +index 5c14ba9..14fec41 100644 +--- a/test/v1.0/memory_manage/lend_shareability_attr/lend_shareability_attr_server.c ++++ b/test/v1.0/memory_manage/lend_shareability_attr/lend_shareability_attr_server.c +@@ -16,6 +16,7 @@ static uint32_t mem_lend_shareability_attr_check(ffa_memory_handle_t handle, uin + ffa_args_t payload; + uint32_t status = VAL_SUCCESS; + ++ val_memset(&mem_region_init, 0x0, sizeof(mem_region_init)); + mem_region_init.memory_region = tx_buf; + mem_region_init.sender = sender; + mem_region_init.receiver = receiver; +diff --git a/test/v1.0/memory_manage/share_device_attr/share_device_attr1_client.c b/test/v1.0/memory_manage/share_device_attr/share_device_attr1_client.c +index 4c57f86..8091b3d 100644 +--- a/test/v1.0/memory_manage/share_device_attr/share_device_attr1_client.c ++++ b/test/v1.0/memory_manage/share_device_attr/share_device_attr1_client.c +@@ -34,6 +34,7 @@ static uint32_t mem_share_device_attr_check(uint32_t test_run_data, uint32_t fid + constituents[0].address = val_mem_virt_to_phys((void *)pages); + constituents[0].page_count = 1; + ++ val_memset(&mem_region_init, 0x0, sizeof(mem_region_init)); + mem_region_init.memory_region = tx_buf; + mem_region_init.sender = sender; + mem_region_init.receiver = receiver; +diff --git a/test/v1.0/memory_manage/share_device_attr/share_device_attr1_server.c b/test/v1.0/memory_manage/share_device_attr/share_device_attr1_server.c +index 4f11235..b2496a1 100644 +--- a/test/v1.0/memory_manage/share_device_attr/share_device_attr1_server.c ++++ b/test/v1.0/memory_manage/share_device_attr/share_device_attr1_server.c +@@ -16,6 +16,7 @@ static uint32_t mem_share_device_attr_check(ffa_memory_handle_t handle, uint32_t + ffa_args_t payload; + uint32_t status = VAL_SUCCESS; + ++ val_memset(&mem_region_init, 0x0, sizeof(mem_region_init)); + mem_region_init.memory_region = tx_buf; + mem_region_init.sender = sender; + mem_region_init.receiver = receiver; +diff --git a/test/v1.0/memory_manage/share_device_attr/share_device_attr_client.c b/test/v1.0/memory_manage/share_device_attr/share_device_attr_client.c +index c834eaf..c4228cc 100644 +--- a/test/v1.0/memory_manage/share_device_attr/share_device_attr_client.c ++++ b/test/v1.0/memory_manage/share_device_attr/share_device_attr_client.c +@@ -34,6 +34,7 @@ static uint32_t mem_share_device_attr_check(uint32_t test_run_data, uint32_t fid + constituents[0].address = val_mem_virt_to_phys((void *)pages); + constituents[0].page_count = 1; + ++ val_memset(&mem_region_init, 0x0, sizeof(mem_region_init)); + mem_region_init.memory_region = tx_buf; + mem_region_init.sender = sender; + mem_region_init.receiver = receiver; +diff --git a/test/v1.0/memory_manage/share_device_attr/share_device_attr_server.c b/test/v1.0/memory_manage/share_device_attr/share_device_attr_server.c +index cd746f6..610c71e 100644 +--- a/test/v1.0/memory_manage/share_device_attr/share_device_attr_server.c ++++ b/test/v1.0/memory_manage/share_device_attr/share_device_attr_server.c +@@ -16,6 +16,7 @@ static uint32_t mem_share_device_attr_check(ffa_memory_handle_t handle, uint32_t + ffa_args_t payload; + uint32_t status = VAL_SUCCESS; + ++ val_memset(&mem_region_init, 0x0, sizeof(mem_region_init)); + mem_region_init.memory_region = tx_buf; + mem_region_init.sender = sender; + mem_region_init.receiver = receiver; +diff --git a/test/v1.0/memory_manage/share_input_error_checks/share_input_error_checks1_client.c b/test/v1.0/memory_manage/share_input_error_checks/share_input_error_checks1_client.c +index b5c4fba..794f334 100644 +--- a/test/v1.0/memory_manage/share_input_error_checks/share_input_error_checks1_client.c ++++ b/test/v1.0/memory_manage/share_input_error_checks/share_input_error_checks1_client.c +@@ -37,6 +37,7 @@ static uint32_t mem_share_invalid_total_page_count_check(void *tx_buf, ffa_endpo + constituents[1].address = val_mem_virt_to_phys((void *)pages + PAGE_SIZE_4K * 1); + constituents[1].page_count = 1; + ++ val_memset(&mem_region_init, 0x0, sizeof(mem_region_init)); + mem_region_init.sender = sender; + mem_region_init.receiver = recipient; + mem_region_init.memory_region = tx_buf; +@@ -126,6 +127,7 @@ static uint32_t mem_share_address_ranges_overlap_check(void *tx_buf, ffa_endpoin + constituents[1].address = val_mem_virt_to_phys((void *)pages + PAGE_SIZE_4K * 1); + constituents[1].page_count = 1; + ++ val_memset(&mem_region_init, 0x0, sizeof(mem_region_init)); + mem_region_init.sender = sender; + mem_region_init.receiver = recipient; + mem_region_init.memory_region = tx_buf; +@@ -211,6 +213,7 @@ static uint32_t mem_share_invalid_sender_id_check(void *tx_buf, ffa_endpoint_id_ + * that the Lender is the Owner of the memory region and a PE endpoint. + * Must return DENIED in case of an error. + */ ++ val_memset(&mem_region_init, 0x0, sizeof(mem_region_init)); + mem_region_init.sender = sender; + mem_region_init.receiver = recipient; + mem_region_init.memory_region = tx_buf; +@@ -279,6 +282,7 @@ static uint32_t mem_share_sp_to_ns_check(void *tx_buf, ffa_endpoint_id_t sender, + constituents[0].address = val_mem_virt_to_phys((void *)pages); + constituents[0].page_count = 1; + ++ val_memset(&mem_region_init, 0x0, sizeof(mem_region_init)); + mem_region_init.sender = sender; + mem_region_init.receiver = recipient; + mem_region_init.memory_region = tx_buf; +@@ -348,6 +352,7 @@ static uint32_t mem_share_invalid_total_length_check(void *tx_buf, ffa_endpoint_ + constituents[0].address = val_mem_virt_to_phys((void *)pages); + constituents[0].page_count = 1; + ++ val_memset(&mem_region_init, 0x0, sizeof(mem_region_init)); + mem_region_init.memory_region = tx_buf; + mem_region_init.sender = sender; + mem_region_init.receiver = recipient; +@@ -419,6 +424,7 @@ static uint32_t mem_share_invalid_ep_count_check(void *tx_buf, ffa_endpoint_id_t + constituents[0].address = val_mem_virt_to_phys((void *)pages); + constituents[0].page_count = 1; + ++ val_memset(&mem_region_init, 0x0, sizeof(mem_region_init)); + mem_region_init.memory_region = tx_buf; + mem_region_init.sender = sender; + mem_region_init.receiver = recipient; +@@ -494,6 +500,7 @@ static uint32_t mem_share_invalid_ep_desc_offset_check(void *tx_buf, ffa_endpoin + constituents[0].address = val_mem_virt_to_phys((void *)pages); + constituents[0].page_count = 1; + ++ val_memset(&mem_region_init, 0x0, sizeof(mem_region_init)); + mem_region_init.memory_region = tx_buf; + mem_region_init.sender = sender; + mem_region_init.receiver = recipient; +diff --git a/test/v1.0/memory_manage/share_input_error_checks/share_input_error_checks_client.c b/test/v1.0/memory_manage/share_input_error_checks/share_input_error_checks_client.c +index d692b72..8dd1801 100644 +--- a/test/v1.0/memory_manage/share_input_error_checks/share_input_error_checks_client.c ++++ b/test/v1.0/memory_manage/share_input_error_checks/share_input_error_checks_client.c +@@ -36,6 +36,7 @@ static uint32_t mem_share_invalid_epid_check(void *tx_buf, + /* Relayer must ensure that the Endpoint ID field in each Memory access permissions descriptor + * specifies a valid endpoint. The Relayer must return INVALID_PARAMETERS in case of an error. + */ ++ val_memset(&mem_region_init, 0x0, sizeof(mem_region_init)); + mem_region_init.sender = sender; + mem_region_init.receiver = receiver; + mem_region_init.memory_region = tx_buf; +@@ -101,6 +102,7 @@ static uint32_t mem_share_zero_flag_check(void *tx_buf, ffa_endpoint_id_t sender + constituents[0].address = val_mem_virt_to_phys((void *)pages); + constituents[0].page_count = 1; + ++ val_memset(&mem_region_init, 0x0, sizeof(mem_region_init)); + mem_region_init.sender = sender; + mem_region_init.receiver = recipient; + mem_region_init.memory_region = tx_buf; +@@ -172,6 +174,7 @@ static uint32_t mem_share_inst_perm_check(void *tx_buf, ffa_endpoint_id_t sender + constituents[0].address = val_mem_virt_to_phys((void *)pages); + constituents[0].page_count = 1; + ++ val_memset(&mem_region_init, 0x0, sizeof(mem_region_init)); + mem_region_init.sender = sender; + mem_region_init.receiver = recipient; + mem_region_init.memory_region = tx_buf; +@@ -240,6 +243,7 @@ static uint32_t mem_share_mmio_check(void *tx_buf, ffa_endpoint_id_t sender, uin + + constituents[0].page_count = 1; + ++ val_memset(&mem_region_init, 0x0, sizeof(mem_region_init)); + mem_region_init.memory_region = tx_buf; + mem_region_init.sender = sender; + mem_region_init.receiver = recipient; +diff --git a/test/v1.0/memory_manage/share_invalid_handle_tag/share_invalid_handle_tag_server.c b/test/v1.0/memory_manage/share_invalid_handle_tag/share_invalid_handle_tag_server.c +index 74fd2e9..c182961 100644 +--- a/test/v1.0/memory_manage/share_invalid_handle_tag/share_invalid_handle_tag_server.c ++++ b/test/v1.0/memory_manage/share_invalid_handle_tag/share_invalid_handle_tag_server.c +@@ -36,6 +36,7 @@ static uint32_t mem_share_invalid_handle_tag_check(ffa_memory_handle_t handle, u + /* MEM_SHARE: Relayer must ensure that the handle was allocated to the owner + * specified in the sender endpoint ID field of the transaction descriptor + */ ++ val_memset(&mem_region_init, 0x0, sizeof(mem_region_init)); + mem_region_init.memory_region = tx_buf; + mem_region_init.sender = sender; + mem_region_init.receiver = receiver; +diff --git a/test/v1.0/memory_manage/share_retrieve_alignment_hint/share_retrieve_align_hint_check_server.c b/test/v1.0/memory_manage/share_retrieve_alignment_hint/share_retrieve_align_hint_check_server.c +index 37cc7e5..c613015 100644 +--- a/test/v1.0/memory_manage/share_retrieve_alignment_hint/share_retrieve_align_hint_check_server.c ++++ b/test/v1.0/memory_manage/share_retrieve_alignment_hint/share_retrieve_align_hint_check_server.c +@@ -34,6 +34,7 @@ static uint32_t retrieve_align_hint_err_check(ffa_memory_handle_t handle, uint32 + ffa_args_t payload; + uint32_t status = VAL_SUCCESS; + ++ val_memset(&mem_region_init, 0x0, sizeof(mem_region_init)); + mem_region_init.memory_region = tx_buf; + mem_region_init.sender = sender; + mem_region_init.receiver = receiver; +diff --git a/test/v1.0/memory_manage/share_retrieve_input_checks/share_retrieve_input_checks_server.c b/test/v1.0/memory_manage/share_retrieve_input_checks/share_retrieve_input_checks_server.c +index 72c86ab..409ba3b 100644 +--- a/test/v1.0/memory_manage/share_retrieve_input_checks/share_retrieve_input_checks_server.c ++++ b/test/v1.0/memory_manage/share_retrieve_input_checks/share_retrieve_input_checks_server.c +@@ -34,6 +34,7 @@ static uint32_t retrieve_zero_flag_check(ffa_memory_handle_t handle, uint32_t fi + ffa_args_t payload; + uint32_t status = VAL_SUCCESS; + ++ val_memset(&mem_region_init, 0x0, sizeof(mem_region_init)); + mem_region_init.memory_region = tx_buf; + mem_region_init.sender = sender; + mem_region_init.receiver = receiver; +@@ -100,6 +101,7 @@ static uint32_t retrieve_with_invalid_cache_attr_check(ffa_memory_handle_t handl + ffa_args_t payload; + uint32_t status = VAL_SUCCESS; + ++ val_memset(&mem_region_init, 0x0, sizeof(mem_region_init)); + mem_region_init.memory_region = tx_buf; + mem_region_init.sender = sender; + mem_region_init.receiver = receiver; +@@ -153,6 +155,7 @@ static uint32_t retrieve_with_invalid_total_length_check(ffa_memory_handle_t han + ffa_args_t payload; + uint32_t status = VAL_SUCCESS; + ++ val_memset(&mem_region_init, 0x0, sizeof(mem_region_init)); + mem_region_init.memory_region = tx_buf; + mem_region_init.sender = sender; + mem_region_init.receiver = receiver; +@@ -209,6 +212,7 @@ static uint32_t retrieve_with_invalid_mem_transaction_type_check(ffa_memory_hand + ffa_args_t payload; + uint32_t status = VAL_SUCCESS; + ++ val_memset(&mem_region_init, 0x0, sizeof(mem_region_init)); + mem_region_init.memory_region = tx_buf; + mem_region_init.sender = sender; + mem_region_init.multi_share = false; +@@ -264,6 +268,7 @@ static uint32_t retrieve_with_invalid_sender_id(ffa_memory_handle_t handle, uint + ffa_args_t payload; + uint32_t status = VAL_SUCCESS; + ++ val_memset(&mem_region_init, 0x0, sizeof(mem_region_init)); + mem_region_init.memory_region = tx_buf; + mem_region_init.sender = sender; + mem_region_init.receiver = receiver; +@@ -320,6 +325,7 @@ static uint32_t retrieve_with_invalid_inst_perm(ffa_memory_handle_t handle, uint + ffa_args_t payload; + uint32_t status = VAL_SUCCESS; + ++ val_memset(&mem_region_init, 0x0, sizeof(mem_region_init)); + mem_region_init.memory_region = tx_buf; + mem_region_init.sender = sender; + mem_region_init.receiver = receiver; +diff --git a/test/v1.0/memory_manage/share_retrieve_with_address_range/share_retrieve_with_address_range_server.c b/test/v1.0/memory_manage/share_retrieve_with_address_range/share_retrieve_with_address_range_server.c +index 412f0bd..3fb9186 100644 +--- a/test/v1.0/memory_manage/share_retrieve_with_address_range/share_retrieve_with_address_range_server.c ++++ b/test/v1.0/memory_manage/share_retrieve_with_address_range/share_retrieve_with_address_range_server.c +@@ -101,8 +101,8 @@ uint32_t share_retrieve_with_address_range_server(ffa_args_t args) + * INVALID_PARAMETERS. + */ + flags = VAL_SET_BITS(flags, 5, 5, 0x11); +- mem_region_init.flags = flags; + val_memset(&mem_region_init, 0x0, sizeof(mem_region_init)); ++ mem_region_init.flags = flags; + mem_region_init.memory_region = mb.send; + mem_region_init.sender = receiver; + mem_region_init.receiver = sender; +diff --git a/test/v1.0/memory_manage/share_shareability_attr/share_shareability_attr1_client.c b/test/v1.0/memory_manage/share_shareability_attr/share_shareability_attr1_client.c +index e54636f..ffb18a4 100644 +--- a/test/v1.0/memory_manage/share_shareability_attr/share_shareability_attr1_client.c ++++ b/test/v1.0/memory_manage/share_shareability_attr/share_shareability_attr1_client.c +@@ -34,6 +34,7 @@ static uint32_t mem_share_shareability_attr_check(uint32_t test_run_data, uint32 + constituents[0].address = val_mem_virt_to_phys((void *)pages); + constituents[0].page_count = 1; + ++ val_memset(&mem_region_init, 0x0, sizeof(mem_region_init)); + mem_region_init.memory_region = tx_buf; + mem_region_init.sender = sender; + mem_region_init.receiver = receiver; +diff --git a/test/v1.0/memory_manage/share_shareability_attr/share_shareability_attr1_server.c b/test/v1.0/memory_manage/share_shareability_attr/share_shareability_attr1_server.c +index 21c3365..ae2ca7e 100644 +--- a/test/v1.0/memory_manage/share_shareability_attr/share_shareability_attr1_server.c ++++ b/test/v1.0/memory_manage/share_shareability_attr/share_shareability_attr1_server.c +@@ -16,6 +16,7 @@ static uint32_t mem_share_shareability_attr_check(ffa_memory_handle_t handle, ui + ffa_args_t payload; + uint32_t status = VAL_SUCCESS; + ++ val_memset(&mem_region_init, 0x0, sizeof(mem_region_init)); + mem_region_init.memory_region = tx_buf; + mem_region_init.sender = sender; + mem_region_init.receiver = receiver; +diff --git a/test/v1.0/memory_manage/share_shareability_attr/share_shareability_attr_server.c b/test/v1.0/memory_manage/share_shareability_attr/share_shareability_attr_server.c +index 7b10e03..48960fa 100644 +--- a/test/v1.0/memory_manage/share_shareability_attr/share_shareability_attr_server.c ++++ b/test/v1.0/memory_manage/share_shareability_attr/share_shareability_attr_server.c +@@ -16,6 +16,7 @@ static uint32_t mem_share_shareability_attr_check(ffa_memory_handle_t handle, ui + ffa_args_t payload; + uint32_t status = VAL_SUCCESS; + ++ val_memset(&mem_region_init, 0x0, sizeof(mem_region_init)); + mem_region_init.memory_region = tx_buf; + mem_region_init.sender = sender; + mem_region_init.receiver = receiver; diff --git a/tests/conformance/ffa-acs/patches/0004-let-the-platform-describe-its-endpoint-properties.patch b/tests/conformance/ffa-acs/patches/0004-let-the-platform-describe-its-endpoint-properties.patch new file mode 100644 index 00000000..8ed86c9a --- /dev/null +++ b/tests/conformance/ffa-acs/patches/0004-let-the-platform-describe-its-endpoint-properties.patch @@ -0,0 +1,64 @@ +diff --git a/platform/common/inc/pal_endpoint_info.h b/platform/common/inc/pal_endpoint_info.h +index 956c894..a6e6591 100644 +--- a/platform/common/inc/pal_endpoint_info.h ++++ b/platform/common/inc/pal_endpoint_info.h +@@ -63,12 +63,14 @@ + #define PLATFORM_SP3_EP_PROPERTIES (FFA_RECEIPT_DIRECT_REQUEST_SUPPORT | \ + FFA_DIRECT_REQUEST_SEND) + #else ++#ifndef PLATFORM_VM1_EP_PROPERTIES + #define PLATFORM_VM1_EP_PROPERTIES (FFA_RECEIPT_DIRECT_REQUEST_SUPPORT | \ + FFA_DIRECT_REQUEST_SEND | \ + FFA_INDIRECT_MESSAGE_SUPPORT | \ + FFA_PARTITION_EXEC_STATE_ARCH64 | \ + FFA_RECEIPT_DIRECT_REQUEST2_SUPPORT | \ + FFA_DIRECT_REQUEST2_SEND) ++#endif + #define PLATFORM_VM1_UUID {0, 0, 0, 0} + + #define PLATFORM_VM2_EP_PROPERTIES (FFA_RECEIPT_DIRECT_REQUEST_SUPPORT | \ +@@ -83,6 +85,7 @@ + FFA_PARTITION_EXEC_STATE_ARCH64) + #define PLATFORM_VM3_UUID {0, 0, 0, 2} + ++#ifndef PLATFORM_SP1_EP_PROPERTIES + #define PLATFORM_SP1_EP_PROPERTIES (FFA_RECEIPT_DIRECT_REQUEST_SUPPORT | \ + FFA_INDIRECT_MESSAGE_SUPPORT | \ + FFA_NOTIFICATION_SUPPORT | \ +@@ -90,7 +93,9 @@ + FFA_PARTITION_EXEC_STATE_ARCH64 | \ + FFA_RECEIPT_DIRECT_REQUEST2_SUPPORT | \ + FFA_DIRECT_REQUEST2_SEND) ++#endif + ++#ifndef PLATFORM_SP2_EP_PROPERTIES + #define PLATFORM_SP2_EP_PROPERTIES (FFA_RECEIPT_DIRECT_REQUEST_SUPPORT | \ + FFA_INDIRECT_MESSAGE_SUPPORT | \ + FFA_NOTIFICATION_SUPPORT | \ +@@ -98,14 +103,18 @@ + FFA_PARTITION_EXEC_STATE_ARCH64 | \ + FFA_RECEIPT_DIRECT_REQUEST2_SUPPORT | \ + FFA_DIRECT_REQUEST2_SEND) ++#endif + ++#ifndef PLATFORM_SP3_EP_PROPERTIES + #define PLATFORM_SP3_EP_PROPERTIES (FFA_RECEIPT_DIRECT_REQUEST_SUPPORT | \ + FFA_NOTIFICATION_SUPPORT | \ + FFA_DIRECT_REQUEST_SEND | \ + FFA_PARTITION_EXEC_STATE_ARCH64 | \ + FFA_RECEIPT_DIRECT_REQUEST2_SUPPORT | \ + FFA_DIRECT_REQUEST2_SEND) ++#endif + ++#ifndef PLATFORM_SP4_EP_PROPERTIES + #define PLATFORM_SP4_EP_PROPERTIES (FFA_RECEIPT_DIRECT_REQUEST_SUPPORT | \ + FFA_NOTIFICATION_SUPPORT | \ + FFA_DIRECT_REQUEST_SEND | \ +@@ -113,6 +122,7 @@ + FFA_RECEIPT_DIRECT_REQUEST2_SUPPORT | \ + FFA_DIRECT_REQUEST2_SEND) + #endif ++#endif + #define PLATFORM_SP1_UUID {0x1e67b5b4, 0xe14f904a, 0x13fb1fb8, 0xcbdae1da} + + #define PLATFORM_SP2_UUID {0x092358d1, 0xb94723f0, 0x64447c82, 0xc88f57f5} diff --git a/tests/conformance/ffa-acs/patches/0005-let-the-build-enable-the-partition-message-uuid-field.patch b/tests/conformance/ffa-acs/patches/0005-let-the-build-enable-the-partition-message-uuid-field.patch new file mode 100644 index 00000000..7f7624ca --- /dev/null +++ b/tests/conformance/ffa-acs/patches/0005-let-the-build-enable-the-partition-message-uuid-field.patch @@ -0,0 +1,14 @@ +diff --git a/CMakeLists.txt b/CMakeLists.txt +index b032c04..71c0b85 100644 +--- a/CMakeLists.txt ++++ b/CMakeLists.txt +@@ -284,6 +284,9 @@ add_definitions(-DCMAKE_BUILD) + add_definitions(-DVERBOSITY=${VERBOSE}) + add_definitions(-DPLATFORM_SPMC_EL=${PLATFORM_SPMC_EL}) + add_definitions(-DPLATFORM_SP_EL=${PLATFORM_SP_EL}) ++if(DEFINED INDIRECT_MESSAGE_UUID_SUPPORT) ++add_definitions(-DINDIRECT_MESSAGE_UUID_SUPPORT=${INDIRECT_MESSAGE_UUID_SUPPORT}) ++endif() + add_definitions(-DPLATFORM_NS_HYPERVISOR_PRESENT=${PLATFORM_NS_HYPERVISOR_PRESENT}) + add_definitions(-DPLATFORM_FFA_V_1_0=${PLATFORM_FFA_V_1_0}) + add_definitions(-DPLATFORM_FFA_V_1_1=${PLATFORM_FFA_V_1_1}) diff --git a/tests/conformance/ffa-acs/patches/0006-prime-the-receiver-server-with-logical-endpoint-ids.patch b/tests/conformance/ffa-acs/patches/0006-prime-the-receiver-server-with-logical-endpoint-ids.patch new file mode 100644 index 00000000..21091b05 --- /dev/null +++ b/tests/conformance/ffa-acs/patches/0006-prime-the-receiver-server-with-logical-endpoint-ids.patch @@ -0,0 +1,13 @@ +diff --git a/test/v1.1/indirect_messaging/ffa_msg_send2/ffa_msg_send2_sp_client.c b/test/v1.1/indirect_messaging/ffa_msg_send2/ffa_msg_send2_sp_client.c +index b278226..7da4538 100644 +--- a/test/v1.1/indirect_messaging/ffa_msg_send2/ffa_msg_send2_sp_client.c ++++ b/test/v1.1/indirect_messaging/ffa_msg_send2/ffa_msg_send2_sp_client.c +@@ -34,7 +34,7 @@ uint32_t ffa_msg_send2_sp_client(uint32_t test_run_data) + #endif + + test_run_data_rx = TEST_RUN_DATA(GET_TEST_NUM((uint32_t)test_run_data), +- (uint32_t)sender, (uint32_t)receiver_rx, GET_TEST_TYPE((uint32_t)test_run_data)); ++ client_logical_id, SP1, GET_TEST_TYPE((uint32_t)test_run_data)); + + if (val_is_ffa_feature_supported(FFA_MSG_SEND2_32)) + { diff --git a/tests/conformance/ffa-acs/patches/0007-set-up-the-gicv2-cpu-interface-during-gic-init.patch b/tests/conformance/ffa-acs/patches/0007-set-up-the-gicv2-cpu-interface-during-gic-init.patch new file mode 100644 index 00000000..b3d13942 --- /dev/null +++ b/tests/conformance/ffa-acs/patches/0007-set-up-the-gicv2-cpu-interface-during-gic-init.patch @@ -0,0 +1,14 @@ +diff --git a/platform/driver/src/gic/pal_arm_gic_v2v3.c b/platform/driver/src/gic/pal_arm_gic_v2v3.c +index 78b45d3..2353cb4 100644 +--- a/platform/driver/src/gic/pal_arm_gic_v2v3.c ++++ b/platform/driver/src/gic/pal_arm_gic_v2v3.c +@@ -194,6 +194,9 @@ void arm_gic_init(uintptr_t gicc_base, + } else { + gicv2_init(gicc_base, gicd_base); + PAL_LOG(INFO, "GICv2 mode detected"); ++ arm_gic_setup_global(); ++ arm_gic_setup_local(); ++ PAL_LOG(INFO, "GICv2 local and global initialisation done"); + } + + } diff --git a/tests/conformance/ffa-acs/patches/0008-seat-the-blocked-test-server-with-logical-endpoint-ids.patch b/tests/conformance/ffa-acs/patches/0008-seat-the-blocked-test-server-with-logical-endpoint-ids.patch new file mode 100644 index 00000000..c4eef80d --- /dev/null +++ b/tests/conformance/ffa-acs/patches/0008-seat-the-blocked-test-server-with-logical-endpoint-ids.patch @@ -0,0 +1,13 @@ +diff --git a/test/v1.1/interrupts/sp_el0_blocked/sp_el0_blocked_server.c b/test/v1.1/interrupts/sp_el0_blocked/sp_el0_blocked_server.c +index cce88d3..e567188 100644 +--- a/test/v1.1/interrupts/sp_el0_blocked/sp_el0_blocked_server.c ++++ b/test/v1.1/interrupts/sp_el0_blocked/sp_el0_blocked_server.c +@@ -33,7 +33,7 @@ static uint32_t sp_el0_server_interrupt(ffa_args_t args) + uint32_t test_run_data = (uint32_t)args.arg3; + + test_run_data = TEST_RUN_DATA(GET_TEST_NUM((uint32_t)test_run_data), +- (uint32_t)sender, (uint32_t)receiver_1, GET_TEST_TYPE((uint32_t)test_run_data)); ++ SP1, SP2, GET_TEST_TYPE((uint32_t)test_run_data)); + + mb_buf_t mb; + uint64_t size = 0x1000; diff --git a/tests/conformance/ffa-acs/patches/0009-let-the-platform-time-el0-sleeps-on-the-virtual-counter.patch b/tests/conformance/ffa-acs/patches/0009-let-the-platform-time-el0-sleeps-on-the-virtual-counter.patch new file mode 100644 index 00000000..7d3023d0 --- /dev/null +++ b/tests/conformance/ffa-acs/patches/0009-let-the-platform-time-el0-sleeps-on-the-virtual-counter.patch @@ -0,0 +1,27 @@ +diff --git a/platform/common/src/pal_sp_helpers.c b/platform/common/src/pal_sp_helpers.c +index 34e556c..e45e397 100644 +--- a/platform/common/src/pal_sp_helpers.c ++++ b/platform/common/src/pal_sp_helpers.c +@@ -40,8 +40,10 @@ uint64_t sp_sleep_elapsed_time(uint64_t ms) + return ((time2 - time1) * 1000) / timer_freq; + } + +-#if ((PLATFORM_SP_EL == 0) && !defined(VM1_COMPILE)) +-/* Need to be adjusted based on platform */ ++#if ((PLATFORM_SP_EL == 0) && !defined(VM1_COMPILE) && \ ++ !defined(PLATFORM_SP_EL0_COUNTER_SLEEP)) ++/* Need to be adjusted based on platform, or define ++ * PLATFORM_SP_EL0_COUNTER_SLEEP where EL0 may read the virtual counter */ + #define ITERATIONS_PER_MS 10000 + + static inline void while_wait_loop(uint64_t ms) +@@ -60,7 +62,8 @@ static inline void while_wait_loop(uint64_t ms) + + void sp_sleep(uint64_t ms) + { +-#if ((PLATFORM_SP_EL == 0) && !defined(VM1_COMPILE)) ++#if ((PLATFORM_SP_EL == 0) && !defined(VM1_COMPILE) && \ ++ !defined(PLATFORM_SP_EL0_COUNTER_SLEEP)) + while_wait_loop(ms); + #else + (void)sp_sleep_elapsed_time(ms); diff --git a/tests/conformance/ffa-acs/patches/0010-keep-the-preempted-test-rx-buffer-across-its-wait.patch b/tests/conformance/ffa-acs/patches/0010-keep-the-preempted-test-rx-buffer-across-its-wait.patch new file mode 100644 index 00000000..7666fb87 --- /dev/null +++ b/tests/conformance/ffa-acs/patches/0010-keep-the-preempted-test-rx-buffer-across-its-wait.patch @@ -0,0 +1,17 @@ +diff --git a/test/v1.1/interrupts/sp_preempted_el0/sp_preempted_el0_server.c b/test/v1.1/interrupts/sp_preempted_el0/sp_preempted_el0_server.c +index ba24e17..bb1a627 100644 +--- a/test/v1.1/interrupts/sp_preempted_el0/sp_preempted_el0_server.c ++++ b/test/v1.1/interrupts/sp_preempted_el0/sp_preempted_el0_server.c +@@ -170,11 +170,11 @@ uint32_t sp_preempted_el0_server(ffa_args_t args) + val_twdog_intr_disable(); + + val_memset(&payload, 0, sizeof(ffa_args_t)); +- val_ffa_msg_wait(&payload); + #if (PLATFORM_FFA_V >= FFA_V_1_2) + /* Prevent RX Buffer Release*/ + payload.arg2 = 0x1; + #endif ++ val_ffa_msg_wait(&payload); + if (payload.fid != FFA_MSG_SEND_DIRECT_REQ_32) + { + LOG(ERROR, "DIRECT_REQ_32 not received fid %x", payload.fid); diff --git a/tests/conformance/ffa-acs/patches/0011-read-a-vm-senders-rx-full-with-the-hypervisor-flag.patch b/tests/conformance/ffa-acs/patches/0011-read-a-vm-senders-rx-full-with-the-hypervisor-flag.patch new file mode 100644 index 00000000..e1cd7ed2 --- /dev/null +++ b/tests/conformance/ffa-acs/patches/0011-read-a-vm-senders-rx-full-with-the-hypervisor-flag.patch @@ -0,0 +1,39 @@ +diff --git a/test/v1.1/direct_messaging/direct_msg_sp_to_vm/direct_msg_sp_to_vm_server.c b/test/v1.1/direct_messaging/direct_msg_sp_to_vm/direct_msg_sp_to_vm_server.c +index ad5a809..c1ec11c 100644 +--- a/test/v1.1/direct_messaging/direct_msg_sp_to_vm/direct_msg_sp_to_vm_server.c ++++ b/test/v1.1/direct_messaging/direct_msg_sp_to_vm/direct_msg_sp_to_vm_server.c +@@ -98,7 +98,7 @@ uint32_t direct_msg_sp_to_vm_server(ffa_args_t args) + + val_memset(&payload, 0, sizeof(ffa_args_t)); + payload.arg1 = sender; +- payload.arg2 = FFA_NOTIFICATIONS_FLAG_BITMAP_SPM; ++ payload.arg2 = FFA_NOTIFICATIONS_FLAG_BITMAP_HYP; + val_ffa_notification_get(&payload); + if (payload.fid == FFA_ERROR_32) + { +diff --git a/test/v1.1/indirect_messaging/ffa_msg_send2/ffa_msg_send2_server.c b/test/v1.1/indirect_messaging/ffa_msg_send2/ffa_msg_send2_server.c +index 2de2f0d..d41407e 100644 +--- a/test/v1.1/indirect_messaging/ffa_msg_send2/ffa_msg_send2_server.c ++++ b/test/v1.1/indirect_messaging/ffa_msg_send2/ffa_msg_send2_server.c +@@ -99,7 +99,7 @@ uint32_t ffa_msg_send2_server(ffa_args_t args) + + val_memset(&payload, 0, sizeof(ffa_args_t)); + payload.arg1 = sender; +- payload.arg2 = FFA_NOTIFICATIONS_FLAG_BITMAP_SPM; ++ payload.arg2 = FFA_NOTIFICATIONS_FLAG_BITMAP_HYP; + val_ffa_notification_get(&payload); + if (payload.fid == FFA_ERROR_32) + { +diff --git a/test/v1.2/indirect_messaging/ffa_msg_send2_uuid_checl/ffa_msg_send2_uuid_check_server.c b/test/v1.2/indirect_messaging/ffa_msg_send2_uuid_checl/ffa_msg_send2_uuid_check_server.c +index 3bed5b8..f49169b 100644 +--- a/test/v1.2/indirect_messaging/ffa_msg_send2_uuid_checl/ffa_msg_send2_uuid_check_server.c ++++ b/test/v1.2/indirect_messaging/ffa_msg_send2_uuid_checl/ffa_msg_send2_uuid_check_server.c +@@ -99,7 +99,7 @@ uint32_t ffa_msg_send2_uuid_check_server(ffa_args_t args) + + val_memset(&payload, 0, sizeof(ffa_args_t)); + payload.arg1 = sender; +- payload.arg2 = FFA_NOTIFICATIONS_FLAG_BITMAP_SPM; ++ payload.arg2 = FFA_NOTIFICATIONS_FLAG_BITMAP_HYP; + val_ffa_notification_get(&payload); + if (payload.fid == FFA_ERROR_32) + { diff --git a/tests/conformance/ffa-acs/patches/0012-pack-the-direct-error-tests-sender-and-receiver-ids.patch b/tests/conformance/ffa-acs/patches/0012-pack-the-direct-error-tests-sender-and-receiver-ids.patch new file mode 100644 index 00000000..0f0e4be6 --- /dev/null +++ b/tests/conformance/ffa-acs/patches/0012-pack-the-direct-error-tests-sender-and-receiver-ids.patch @@ -0,0 +1,48 @@ +diff --git a/test/v1.0/direct_messaging/ffa_direct_message_error/ffa_direct_message_error_client.c b/test/v1.0/direct_messaging/ffa_direct_message_error/ffa_direct_message_error_client.c +index 8ed2e4c..f7bb5ed 100644 +--- a/test/v1.0/direct_messaging/ffa_direct_message_error/ffa_direct_message_error_client.c ++++ b/test/v1.0/direct_messaging/ffa_direct_message_error/ffa_direct_message_error_client.c +@@ -87,7 +87,8 @@ uint32_t ffa_direct_message_error_client(uint32_t test_run_data) + if (val_is_ffa_feature_supported(FFA_MSG_SEND_DIRECT_REQ_32) == VAL_SUCCESS) + { + val_memset(&payload, 0, sizeof(ffa_args_t)); +- payload.arg1 = val_get_endpoint_id(client_logical_id | (uint32_t)info[i].id << 16); ++ payload.arg1 = ((uint32_t)val_get_endpoint_id(client_logical_id) << 16) | ++ info[i].id; + LOG(DBG, "Sending direct msg to epid=0x%x", info[i].id); + val_ffa_msg_send_direct_req_32(&payload); + if ((payload.fid != FFA_ERROR_32) || (payload.arg2 != FFA_ERROR_DENIED)) +@@ -100,7 +101,8 @@ uint32_t ffa_direct_message_error_client(uint32_t test_run_data) + else if (val_is_ffa_feature_supported(FFA_MSG_SEND_DIRECT_REQ_64) == VAL_SUCCESS) + { + val_memset(&payload, 0, sizeof(ffa_args_t)); +- payload.arg1 = val_get_endpoint_id(client_logical_id | (uint32_t)info[i].id << 16); ++ payload.arg1 = ((uint32_t)val_get_endpoint_id(client_logical_id) << 16) | ++ info[i].id; + LOG(DBG, "Sending direct msg to epid=0x%x", info[i].id); + val_ffa_msg_send_direct_req_64(&payload); + if ((payload.fid != FFA_ERROR_32) || (payload.arg2 != FFA_ERROR_DENIED)) +diff --git a/test/v1.0/direct_messaging/ffa_direct_message_error1/ffa_direct_message_error1_client.c b/test/v1.0/direct_messaging/ffa_direct_message_error1/ffa_direct_message_error1_client.c +index e0f004c..53bc91c 100644 +--- a/test/v1.0/direct_messaging/ffa_direct_message_error1/ffa_direct_message_error1_client.c ++++ b/test/v1.0/direct_messaging/ffa_direct_message_error1/ffa_direct_message_error1_client.c +@@ -87,7 +87,8 @@ uint32_t ffa_direct_message_error1_client(uint32_t test_run_data) + if (val_is_ffa_feature_supported(FFA_MSG_SEND_DIRECT_REQ_32) == VAL_SUCCESS) + { + val_memset(&payload, 0, sizeof(ffa_args_t)); +- payload.arg1 = val_get_endpoint_id(client_logical_id | (uint32_t)info[i].id << 16); ++ payload.arg1 = ((uint32_t)val_get_endpoint_id(client_logical_id) << 16) | ++ info[i].id; + LOG(DBG, "Sending direct req to epid=0x%x", info[i].id, 0); + val_ffa_msg_send_direct_req_32(&payload); + if ((payload.fid != FFA_ERROR_32) || (payload.arg2 != FFA_ERROR_DENIED)) +@@ -101,7 +102,8 @@ uint32_t ffa_direct_message_error1_client(uint32_t test_run_data) + else if (val_is_ffa_feature_supported(FFA_MSG_SEND_DIRECT_REQ_64) == VAL_SUCCESS) + { + val_memset(&payload, 0, sizeof(ffa_args_t)); +- payload.arg1 = val_get_endpoint_id(client_logical_id | (uint32_t)info[i].id << 16); ++ payload.arg1 = ((uint32_t)val_get_endpoint_id(client_logical_id) << 16) | ++ info[i].id; + LOG(DBG, "Sending direct req to epid=0x%x", info[i].id); + val_ffa_msg_send_direct_req_64(&payload); + if ((payload.fid != FFA_ERROR_32) || (payload.arg2 != FFA_ERROR_DENIED)) diff --git a/tests/conformance/ffa-acs/patches/0013-mark-the-other-borrower-non-retrieval-in-a-retrieve-request.patch b/tests/conformance/ffa-acs/patches/0013-mark-the-other-borrower-non-retrieval-in-a-retrieve-request.patch new file mode 100644 index 00000000..84e48aa2 --- /dev/null +++ b/tests/conformance/ffa-acs/patches/0013-mark-the-other-borrower-non-retrieval-in-a-retrieve-request.patch @@ -0,0 +1,221 @@ +diff --git a/test/v1.0/memory_manage/donate_state_machine/donate_state_machine_3_server.c b/test/v1.0/memory_manage/donate_state_machine/donate_state_machine_3_server.c +index 37e5bd0..16308a3 100644 +--- a/test/v1.0/memory_manage/donate_state_machine/donate_state_machine_3_server.c ++++ b/test/v1.0/memory_manage/donate_state_machine/donate_state_machine_3_server.c +@@ -75,10 +75,10 @@ uint32_t donate_state_machine_3_server(ffa_args_t args) + + mem_region_init.receivers[0].receiver_permissions.receiver = borrower_1; + mem_region_init.receivers[0].receiver_permissions.permissions = FFA_DATA_ACCESS_RW; +- mem_region_init.receivers[0].receiver_permissions.flags = 0; ++ mem_region_init.receivers[0].receiver_permissions.flags = (borrower_1 == sender) ? 0 : 1; + mem_region_init.receivers[1].receiver_permissions.receiver = borrower_2; + mem_region_init.receivers[1].receiver_permissions.permissions = FFA_DATA_ACCESS_RW; +- mem_region_init.receivers[1].receiver_permissions.flags = 0; ++ mem_region_init.receivers[1].receiver_permissions.flags = (borrower_2 == sender) ? 0 : 1; + #endif + + #if (PLATFORM_OUTER_SHAREABLE_SUPPORT_ONLY == 1) +diff --git a/test/v1.0/memory_manage/donate_state_machine/donate_state_machine_5_server.c b/test/v1.0/memory_manage/donate_state_machine/donate_state_machine_5_server.c +index 25a70d6..fcb5277 100644 +--- a/test/v1.0/memory_manage/donate_state_machine/donate_state_machine_5_server.c ++++ b/test/v1.0/memory_manage/donate_state_machine/donate_state_machine_5_server.c +@@ -83,10 +83,10 @@ uint32_t donate_state_machine_5_server(ffa_args_t args) + + mem_region_init.receivers[0].receiver_permissions.receiver = borrower_1; + mem_region_init.receivers[0].receiver_permissions.permissions = FFA_DATA_ACCESS_RW; +- mem_region_init.receivers[0].receiver_permissions.flags = 0; ++ mem_region_init.receivers[0].receiver_permissions.flags = (borrower_1 == sender) ? 0 : 1; + mem_region_init.receivers[1].receiver_permissions.receiver = borrower_2; + mem_region_init.receivers[1].receiver_permissions.permissions = FFA_DATA_ACCESS_RW; +- mem_region_init.receivers[1].receiver_permissions.flags = 0; ++ mem_region_init.receivers[1].receiver_permissions.flags = (borrower_2 == sender) ? 0 : 1; + #endif + + msg_size = val_ffa_memory_retrieve_request_init(&mem_region_init, handle); +diff --git a/test/v1.0/memory_manage/lend_retrieve_input_checks/lend_retrieve_input_checks5_server.c b/test/v1.0/memory_manage/lend_retrieve_input_checks/lend_retrieve_input_checks5_server.c +index aa9f5a2..aead642 100644 +--- a/test/v1.0/memory_manage/lend_retrieve_input_checks/lend_retrieve_input_checks5_server.c ++++ b/test/v1.0/memory_manage/lend_retrieve_input_checks/lend_retrieve_input_checks5_server.c +@@ -89,10 +89,10 @@ uint32_t lend_retrieve_input_checks5_server(ffa_args_t args) + + mem_region_init.receivers[0].receiver_permissions.receiver = borrower_1; + mem_region_init.receivers[0].receiver_permissions.permissions = FFA_DATA_ACCESS_RW; +- mem_region_init.receivers[0].receiver_permissions.flags = 0; ++ mem_region_init.receivers[0].receiver_permissions.flags = (borrower_1 == sender) ? 0 : 1; + mem_region_init.receivers[1].receiver_permissions.receiver = borrower_2; + mem_region_init.receivers[1].receiver_permissions.permissions = FFA_DATA_ACCESS_RW; +- mem_region_init.receivers[1].receiver_permissions.flags = 0; ++ mem_region_init.receivers[1].receiver_permissions.flags = (borrower_2 == sender) ? 0 : 1; + #endif + + #if (PLATFORM_OUTER_SHAREABLE_SUPPORT_ONLY == 1) +diff --git a/test/v1.0/memory_manage/lend_retrieve_input_checks/lend_retrieve_input_checks9_server.c b/test/v1.0/memory_manage/lend_retrieve_input_checks/lend_retrieve_input_checks9_server.c +index 18099d4..c935a28 100644 +--- a/test/v1.0/memory_manage/lend_retrieve_input_checks/lend_retrieve_input_checks9_server.c ++++ b/test/v1.0/memory_manage/lend_retrieve_input_checks/lend_retrieve_input_checks9_server.c +@@ -81,10 +81,10 @@ uint32_t lend_retrieve_input_checks9_server(ffa_args_t args) + + mem_region_init.receivers[0].receiver_permissions.receiver = borrower_1; + mem_region_init.receivers[0].receiver_permissions.permissions = FFA_DATA_ACCESS_RW; +- mem_region_init.receivers[0].receiver_permissions.flags = 0; ++ mem_region_init.receivers[0].receiver_permissions.flags = (borrower_1 == sender) ? 0 : 1; + mem_region_init.receivers[1].receiver_permissions.receiver = borrower_2; + mem_region_init.receivers[1].receiver_permissions.permissions = FFA_DATA_ACCESS_RW; +- mem_region_init.receivers[1].receiver_permissions.flags = 0; ++ mem_region_init.receivers[1].receiver_permissions.flags = (borrower_2 == sender) ? 0 : 1; + #endif + + #if (PLATFORM_OUTER_SHAREABLE_SUPPORT_ONLY == 1) +diff --git a/test/v1.0/memory_manage/lend_state_machine/lend_state_machine_1_server.c b/test/v1.0/memory_manage/lend_state_machine/lend_state_machine_1_server.c +index ff5d681..b1d8f27 100644 +--- a/test/v1.0/memory_manage/lend_state_machine/lend_state_machine_1_server.c ++++ b/test/v1.0/memory_manage/lend_state_machine/lend_state_machine_1_server.c +@@ -76,10 +76,10 @@ uint32_t lend_state_machine_1_server(ffa_args_t args) + + mem_region_init.receivers[0].receiver_permissions.receiver = borrower_1; + mem_region_init.receivers[0].receiver_permissions.permissions = FFA_DATA_ACCESS_RW; +- mem_region_init.receivers[0].receiver_permissions.flags = 0; ++ mem_region_init.receivers[0].receiver_permissions.flags = (borrower_1 == sender) ? 0 : 1; + mem_region_init.receivers[1].receiver_permissions.receiver = borrower_2; + mem_region_init.receivers[1].receiver_permissions.permissions = FFA_DATA_ACCESS_RW; +- mem_region_init.receivers[1].receiver_permissions.flags = 0; ++ mem_region_init.receivers[1].receiver_permissions.flags = (borrower_2 == sender) ? 0 : 1; + #endif + + #if (PLATFORM_OUTER_SHAREABLE_SUPPORT_ONLY == 1) +diff --git a/test/v1.0/memory_manage/lend_state_machine/lend_state_machine_4_server.c b/test/v1.0/memory_manage/lend_state_machine/lend_state_machine_4_server.c +index 4e66950..7a9b319 100644 +--- a/test/v1.0/memory_manage/lend_state_machine/lend_state_machine_4_server.c ++++ b/test/v1.0/memory_manage/lend_state_machine/lend_state_machine_4_server.c +@@ -77,10 +77,10 @@ uint32_t lend_state_machine_4_server(ffa_args_t args) + + mem_region_init.receivers[0].receiver_permissions.receiver = borrower_1; + mem_region_init.receivers[0].receiver_permissions.permissions = FFA_DATA_ACCESS_RW; +- mem_region_init.receivers[0].receiver_permissions.flags = 0; ++ mem_region_init.receivers[0].receiver_permissions.flags = (borrower_1 == sender) ? 0 : 1; + mem_region_init.receivers[1].receiver_permissions.receiver = borrower_2; + mem_region_init.receivers[1].receiver_permissions.permissions = FFA_DATA_ACCESS_RW; +- mem_region_init.receivers[1].receiver_permissions.flags = 0; ++ mem_region_init.receivers[1].receiver_permissions.flags = (borrower_2 == sender) ? 0 : 1; + #endif + + #if (PLATFORM_OUTER_SHAREABLE_SUPPORT_ONLY == 1) +diff --git a/test/v1.0/memory_manage/lend_state_machine/lend_state_machine_6_server.c b/test/v1.0/memory_manage/lend_state_machine/lend_state_machine_6_server.c +index e5790b6..d73959d 100644 +--- a/test/v1.0/memory_manage/lend_state_machine/lend_state_machine_6_server.c ++++ b/test/v1.0/memory_manage/lend_state_machine/lend_state_machine_6_server.c +@@ -83,10 +83,10 @@ uint32_t lend_state_machine_6_server(ffa_args_t args) + + mem_region_init.receivers[0].receiver_permissions.receiver = borrower_1; + mem_region_init.receivers[0].receiver_permissions.permissions = FFA_DATA_ACCESS_RW; +- mem_region_init.receivers[0].receiver_permissions.flags = 0; ++ mem_region_init.receivers[0].receiver_permissions.flags = (borrower_1 == sender) ? 0 : 1; + mem_region_init.receivers[1].receiver_permissions.receiver = borrower_2; + mem_region_init.receivers[1].receiver_permissions.permissions = FFA_DATA_ACCESS_RW; +- mem_region_init.receivers[1].receiver_permissions.flags = 0; ++ mem_region_init.receivers[1].receiver_permissions.flags = (borrower_2 == sender) ? 0 : 1; + #endif + + msg_size = val_ffa_memory_retrieve_request_init(&mem_region_init, handle); +diff --git a/test/v1.0/memory_manage/relinquish_state_machine/relinquish_state_machine_4_server.c b/test/v1.0/memory_manage/relinquish_state_machine/relinquish_state_machine_4_server.c +index 486b94e..af6b451 100644 +--- a/test/v1.0/memory_manage/relinquish_state_machine/relinquish_state_machine_4_server.c ++++ b/test/v1.0/memory_manage/relinquish_state_machine/relinquish_state_machine_4_server.c +@@ -90,10 +90,10 @@ uint32_t relinquish_state_machine_4_server(ffa_args_t args) + + mem_region_init.receivers[0].receiver_permissions.receiver = borrower_1; + mem_region_init.receivers[0].receiver_permissions.permissions = FFA_DATA_ACCESS_RW; +- mem_region_init.receivers[0].receiver_permissions.flags = 0; ++ mem_region_init.receivers[0].receiver_permissions.flags = (borrower_1 == sender) ? 0 : 1; + mem_region_init.receivers[1].receiver_permissions.receiver = borrower_2; + mem_region_init.receivers[1].receiver_permissions.permissions = FFA_DATA_ACCESS_RW; +- mem_region_init.receivers[1].receiver_permissions.flags = 0; ++ mem_region_init.receivers[1].receiver_permissions.flags = (borrower_2 == sender) ? 0 : 1; + #endif + + #if (PLATFORM_OUTER_SHAREABLE_SUPPORT_ONLY == 1) +diff --git a/test/v1.0/memory_manage/share_state_machine/share_state_machine_1_server.c b/test/v1.0/memory_manage/share_state_machine/share_state_machine_1_server.c +index 47f118b..77e1d50 100644 +--- a/test/v1.0/memory_manage/share_state_machine/share_state_machine_1_server.c ++++ b/test/v1.0/memory_manage/share_state_machine/share_state_machine_1_server.c +@@ -92,10 +92,10 @@ uint32_t share_state_machine_1_server(ffa_args_t args) + + mem_region_init.receivers[0].receiver_permissions.receiver = borrower_1; + mem_region_init.receivers[0].receiver_permissions.permissions = FFA_DATA_ACCESS_RW; +- mem_region_init.receivers[0].receiver_permissions.flags = 0; ++ mem_region_init.receivers[0].receiver_permissions.flags = (borrower_1 == sender) ? 0 : 1; + mem_region_init.receivers[1].receiver_permissions.receiver = borrower_2; + mem_region_init.receivers[1].receiver_permissions.permissions = FFA_DATA_ACCESS_RW; +- mem_region_init.receivers[1].receiver_permissions.flags = 0; ++ mem_region_init.receivers[1].receiver_permissions.flags = (borrower_2 == sender) ? 0 : 1; + #endif + + msg_size = val_ffa_memory_retrieve_request_init(&mem_region_init, handle); +diff --git a/test/v1.0/memory_manage/share_state_machine/share_state_machine_4_server.c b/test/v1.0/memory_manage/share_state_machine/share_state_machine_4_server.c +index af6ca52..471449e 100644 +--- a/test/v1.0/memory_manage/share_state_machine/share_state_machine_4_server.c ++++ b/test/v1.0/memory_manage/share_state_machine/share_state_machine_4_server.c +@@ -92,10 +92,10 @@ uint32_t share_state_machine_4_server(ffa_args_t args) + + mem_region_init.receivers[0].receiver_permissions.receiver = borrower_1; + mem_region_init.receivers[0].receiver_permissions.permissions = FFA_DATA_ACCESS_RW; +- mem_region_init.receivers[0].receiver_permissions.flags = 0; ++ mem_region_init.receivers[0].receiver_permissions.flags = (borrower_1 == sender) ? 0 : 1; + mem_region_init.receivers[1].receiver_permissions.receiver = borrower_2; + mem_region_init.receivers[1].receiver_permissions.permissions = FFA_DATA_ACCESS_RW; +- mem_region_init.receivers[1].receiver_permissions.flags = 0; ++ mem_region_init.receivers[1].receiver_permissions.flags = (borrower_2 == sender) ? 0 : 1; + #endif + + msg_size = val_ffa_memory_retrieve_request_init(&mem_region_init, handle); +diff --git a/test/v1.0/memory_manage/share_state_machine/share_state_machine_6_server.c b/test/v1.0/memory_manage/share_state_machine/share_state_machine_6_server.c +index 2d26bfe..06c4aee 100644 +--- a/test/v1.0/memory_manage/share_state_machine/share_state_machine_6_server.c ++++ b/test/v1.0/memory_manage/share_state_machine/share_state_machine_6_server.c +@@ -92,10 +92,10 @@ uint32_t share_state_machine_6_server(ffa_args_t args) + + mem_region_init.receivers[0].receiver_permissions.receiver = borrower_1; + mem_region_init.receivers[0].receiver_permissions.permissions = FFA_DATA_ACCESS_RW; +- mem_region_init.receivers[0].receiver_permissions.flags = 0; ++ mem_region_init.receivers[0].receiver_permissions.flags = (borrower_1 == sender) ? 0 : 1; + mem_region_init.receivers[1].receiver_permissions.receiver = borrower_2; + mem_region_init.receivers[1].receiver_permissions.permissions = FFA_DATA_ACCESS_RW; +- mem_region_init.receivers[1].receiver_permissions.flags = 0; ++ mem_region_init.receivers[1].receiver_permissions.flags = (borrower_2 == sender) ? 0 : 1; + #endif + + msg_size = val_ffa_memory_retrieve_request_init(&mem_region_init, handle); +diff --git a/test/v1.2/memory_manage/mem_lend_impdef/mem_lend_impdef_server.c b/test/v1.2/memory_manage/mem_lend_impdef/mem_lend_impdef_server.c +index 38dfa60..a6911df 100644 +--- a/test/v1.2/memory_manage/mem_lend_impdef/mem_lend_impdef_server.c ++++ b/test/v1.2/memory_manage/mem_lend_impdef/mem_lend_impdef_server.c +@@ -73,10 +73,10 @@ uint32_t mem_lend_impdef_server(ffa_args_t args) + borrower_2 = (uint16_t)(borrower_list >> 16 & 0xFFFF); + mem_region_init.receivers[0].receiver_permissions.receiver = borrower_1; + mem_region_init.receivers[0].receiver_permissions.permissions = FFA_DATA_ACCESS_RW; +- mem_region_init.receivers[0].receiver_permissions.flags = 0; ++ mem_region_init.receivers[0].receiver_permissions.flags = (borrower_1 == sender) ? 0 : 1; + mem_region_init.receivers[1].receiver_permissions.receiver = borrower_2; + mem_region_init.receivers[1].receiver_permissions.permissions = FFA_DATA_ACCESS_RW; +- mem_region_init.receivers[1].receiver_permissions.flags = 0; ++ mem_region_init.receivers[1].receiver_permissions.flags = (borrower_2 == sender) ? 0 : 1; + + #if (PLATFORM_OUTER_SHAREABLE_SUPPORT_ONLY == 1) + mem_region_init.shareability = FFA_MEMORY_OUTER_SHAREABLE; +diff --git a/test/v1.2/memory_manage/mem_share_impdef/mem_share_impdef_server.c b/test/v1.2/memory_manage/mem_share_impdef/mem_share_impdef_server.c +index 63f4e45..d2a5f2d 100644 +--- a/test/v1.2/memory_manage/mem_share_impdef/mem_share_impdef_server.c ++++ b/test/v1.2/memory_manage/mem_share_impdef/mem_share_impdef_server.c +@@ -91,10 +91,10 @@ uint32_t mem_share_impdef_server(ffa_args_t args) + + mem_region_init.receivers[0].receiver_permissions.receiver = borrower_1; + mem_region_init.receivers[0].receiver_permissions.permissions = FFA_DATA_ACCESS_RW; +- mem_region_init.receivers[0].receiver_permissions.flags = 0; ++ mem_region_init.receivers[0].receiver_permissions.flags = (borrower_1 == sender) ? 0 : 1; + mem_region_init.receivers[1].receiver_permissions.receiver = borrower_2; + mem_region_init.receivers[1].receiver_permissions.permissions = FFA_DATA_ACCESS_RW; +- mem_region_init.receivers[1].receiver_permissions.flags = 0; ++ mem_region_init.receivers[1].receiver_permissions.flags = (borrower_2 == sender) ? 0 : 1; + + #if (PLATFORM_FFA_V >= FFA_V_1_2) + /* Try Mem Retrieve with Invalid Impdef Values */ diff --git a/tests/conformance/ffa-acs/patches/0014-expect-the-callee-version-for-a-later-caller-version.patch b/tests/conformance/ffa-acs/patches/0014-expect-the-callee-version-for-a-later-caller-version.patch new file mode 100644 index 00000000..480dceba --- /dev/null +++ b/tests/conformance/ffa-acs/patches/0014-expect-the-callee-version-for-a-later-caller-version.patch @@ -0,0 +1,26 @@ +diff --git a/test/v1.0/setup_discovery/ffa_version/ffa_version_data.h b/test/v1.0/setup_discovery/ffa_version/ffa_version_data.h +index 9d8d2da..611bd45 100644 +--- a/test/v1.0/setup_discovery/ffa_version/ffa_version_data.h ++++ b/test/v1.0/setup_discovery/ffa_version/ffa_version_data.h +@@ -37,21 +37,13 @@ static const test_data check[] = { + { + .major = FFA_VERSION_MAJOR, + .minor = FFA_VERSION_MINOR + 2, +-#if (PLATFORM_FFA_V == FFA_V_1_0) + .expected_status = ((FFA_VERSION_MAJOR << 16) | FFA_VERSION_MINOR), +-#elif (PLATFORM_FFA_V >= FFA_V_1_1) +- .expected_status = FFA_ERROR_NOT_SUPPORTED, +-#endif + }, + /* Index-3: Incompatible major version check */ + { + .major = FFA_VERSION_MAJOR + 1, + .minor = FFA_VERSION_MINOR, +-#if (PLATFORM_FFA_V == FFA_V_1_0) + .expected_status = ((FFA_VERSION_MAJOR << 16) | FFA_VERSION_MINOR), +-#elif (PLATFORM_FFA_V >= FFA_V_1_1) +- .expected_status = FFA_ERROR_NOT_SUPPORTED, +-#endif + }, + /* Index-4: bit-31 must be zero check */ + { diff --git a/tests/conformance/ffa-acs/patches/0015-state-not-executable-in-single-borrower-lend-and-donate-retrieves.patch b/tests/conformance/ffa-acs/patches/0015-state-not-executable-in-single-borrower-lend-and-donate-retrieves.patch new file mode 100644 index 00000000..013ca489 --- /dev/null +++ b/tests/conformance/ffa-acs/patches/0015-state-not-executable-in-single-borrower-lend-and-donate-retrieves.patch @@ -0,0 +1,87 @@ +diff --git a/test/v1.0/memory_manage/donate_invalid_handle_tag/donate_invalid_handle_tag_server.c b/test/v1.0/memory_manage/donate_invalid_handle_tag/donate_invalid_handle_tag_server.c +index 94b898d..bdf3534 100644 +--- a/test/v1.0/memory_manage/donate_invalid_handle_tag/donate_invalid_handle_tag_server.c ++++ b/test/v1.0/memory_manage/donate_invalid_handle_tag/donate_invalid_handle_tag_server.c +@@ -141,7 +141,7 @@ static uint32_t mem_donate_invalid_handle_tag_check(ffa_memory_handle_t handle, + mem_region_init.tag = tag; + mem_region_init.flags = 0; + mem_region_init.data_access = FFA_DATA_ACCESS_RW; +- mem_region_init.instruction_access = FFA_INSTRUCTION_ACCESS_NOT_SPECIFIED; ++ mem_region_init.instruction_access = FFA_INSTRUCTION_ACCESS_NX; + mem_region_init.type = FFA_MEMORY_NORMAL_MEM; + mem_region_init.cacheability = FFA_MEMORY_CACHE_WRITE_BACK; + #if (PLATFORM_OUTER_SHAREABLE_SUPPORT_ONLY == 1) +diff --git a/test/v1.0/memory_manage/lend_invalid_handle_tag/lend_invalid_handle_tag_server.c b/test/v1.0/memory_manage/lend_invalid_handle_tag/lend_invalid_handle_tag_server.c +index 10ab5a4..ca5bac7 100644 +--- a/test/v1.0/memory_manage/lend_invalid_handle_tag/lend_invalid_handle_tag_server.c ++++ b/test/v1.0/memory_manage/lend_invalid_handle_tag/lend_invalid_handle_tag_server.c +@@ -46,7 +46,7 @@ static uint32_t mem_lend_invalid_handle_tag_check(ffa_memory_handle_t handle, ui + mem_region_init.tag = tag; + mem_region_init.flags = 0; + mem_region_init.data_access = FFA_DATA_ACCESS_RW; +- mem_region_init.instruction_access = FFA_INSTRUCTION_ACCESS_NOT_SPECIFIED; ++ mem_region_init.instruction_access = FFA_INSTRUCTION_ACCESS_NX; + mem_region_init.type = FFA_MEMORY_NORMAL_MEM; + mem_region_init.cacheability = FFA_MEMORY_CACHE_WRITE_BACK; + #if (PLATFORM_OUTER_SHAREABLE_SUPPORT_ONLY == 1) +diff --git a/test/v1.0/memory_manage/lend_retrieve_alignment_hint/lend_retrieve_align_hint_check_server.c b/test/v1.0/memory_manage/lend_retrieve_alignment_hint/lend_retrieve_align_hint_check_server.c +index 43a89f8..aa6828c 100644 +--- a/test/v1.0/memory_manage/lend_retrieve_alignment_hint/lend_retrieve_align_hint_check_server.c ++++ b/test/v1.0/memory_manage/lend_retrieve_alignment_hint/lend_retrieve_align_hint_check_server.c +@@ -41,7 +41,7 @@ static uint32_t retrieve_align_hint_err_check(ffa_memory_handle_t handle, uint32 + mem_region_init.tag = 0; + mem_region_init.flags = flags; + mem_region_init.data_access = FFA_DATA_ACCESS_RW; +- mem_region_init.instruction_access = FFA_INSTRUCTION_ACCESS_NOT_SPECIFIED; ++ mem_region_init.instruction_access = FFA_INSTRUCTION_ACCESS_NX; + mem_region_init.type = FFA_MEMORY_NORMAL_MEM; + mem_region_init.cacheability = FFA_MEMORY_CACHE_NON_CACHEABLE; + #if (PLATFORM_OUTER_SHAREABLE_SUPPORT_ONLY == 1) +@@ -100,7 +100,7 @@ static uint32_t retrieve_align_hint_check(ffa_memory_handle_t handle, uint32_t f + mem_region_init.tag = 0; + mem_region_init.flags = flags; + mem_region_init.data_access = FFA_DATA_ACCESS_RW; +- mem_region_init.instruction_access = FFA_INSTRUCTION_ACCESS_NOT_SPECIFIED; ++ mem_region_init.instruction_access = FFA_INSTRUCTION_ACCESS_NX; + mem_region_init.type = FFA_MEMORY_NORMAL_MEM; + mem_region_init.cacheability = FFA_MEMORY_CACHE_NON_CACHEABLE; + #if (PLATFORM_OUTER_SHAREABLE_SUPPORT_ONLY == 1) +diff --git a/test/v1.0/memory_manage/lend_retrieve_input_checks/lend_retrieve_input_checks6_server.c b/test/v1.0/memory_manage/lend_retrieve_input_checks/lend_retrieve_input_checks6_server.c +index e72fa30..66a00a3 100644 +--- a/test/v1.0/memory_manage/lend_retrieve_input_checks/lend_retrieve_input_checks6_server.c ++++ b/test/v1.0/memory_manage/lend_retrieve_input_checks/lend_retrieve_input_checks6_server.c +@@ -59,7 +59,7 @@ uint32_t lend_retrieve_input_checks6_server(ffa_args_t args) + mem_region_init.tag = 0; + mem_region_init.flags = 0; + mem_region_init.data_access = FFA_DATA_ACCESS_RW; +- mem_region_init.instruction_access = FFA_INSTRUCTION_ACCESS_NOT_SPECIFIED; ++ mem_region_init.instruction_access = FFA_INSTRUCTION_ACCESS_NX; + mem_region_init.type = FFA_MEMORY_NORMAL_MEM; + mem_region_init.cacheability = FFA_MEMORY_CACHE_WRITE_BACK; + #if (PLATFORM_OUTER_SHAREABLE_SUPPORT_ONLY == 1) +diff --git a/test/v1.0/memory_manage/lend_ro_retrieve_rw/lend_ro_retrieve_rw_server.c b/test/v1.0/memory_manage/lend_ro_retrieve_rw/lend_ro_retrieve_rw_server.c +index 54f5bfe..36c59b5 100644 +--- a/test/v1.0/memory_manage/lend_ro_retrieve_rw/lend_ro_retrieve_rw_server.c ++++ b/test/v1.0/memory_manage/lend_ro_retrieve_rw/lend_ro_retrieve_rw_server.c +@@ -71,7 +71,7 @@ uint32_t lend_ro_retrieve_rw_server(ffa_args_t args) + * Relayer must return DENIED if validation fails. + * */ + mem_region_init.data_access = FFA_DATA_ACCESS_RW; +- mem_region_init.instruction_access = FFA_INSTRUCTION_ACCESS_NOT_SPECIFIED; ++ mem_region_init.instruction_access = FFA_INSTRUCTION_ACCESS_NX; + mem_region_init.type = FFA_MEMORY_NORMAL_MEM; + mem_region_init.cacheability = FFA_MEMORY_CACHE_WRITE_BACK; + #if (PLATFORM_OUTER_SHAREABLE_SUPPORT_ONLY == 1) +diff --git a/test/v1.0/memory_manage/multiple_retrievals_by_borrower/lend_multiple_retrievals_server.c b/test/v1.0/memory_manage/multiple_retrievals_by_borrower/lend_multiple_retrievals_server.c +index a5f7277..103ec8f 100644 +--- a/test/v1.0/memory_manage/multiple_retrievals_by_borrower/lend_multiple_retrievals_server.c ++++ b/test/v1.0/memory_manage/multiple_retrievals_by_borrower/lend_multiple_retrievals_server.c +@@ -109,7 +109,7 @@ uint32_t lend_multiple_retrievals_server(ffa_args_t args) + #if (PLATFORM_FFA_V >= FFA_V_1_1) + mem_region_init.instruction_access = FFA_INSTRUCTION_ACCESS_NX; + #else +- mem_region_init.instruction_access = FFA_INSTRUCTION_ACCESS_NOT_SPECIFIED; ++ mem_region_init.instruction_access = FFA_INSTRUCTION_ACCESS_NX; + #endif + mem_region_init.type = FFA_MEMORY_NORMAL_MEM; + mem_region_init.multi_share = false; diff --git a/tests/conformance/ffa-acs/tgt_wolftrust_qemu/inc/pal_config_def.h b/tests/conformance/ffa-acs/tgt_wolftrust_qemu/inc/pal_config_def.h new file mode 100644 index 00000000..06be1805 --- /dev/null +++ b/tests/conformance/ffa-acs/tgt_wolftrust_qemu/inc/pal_config_def.h @@ -0,0 +1,113 @@ +/* pal_config_def.h + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfTrust. + * + * wolfTrust 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. + * + * wolfTrust 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, see . + */ + +/* FF-A ACS target configuration for the wolfTrust QEMU machines. The machine + * addresses and the endpoint ids come from wt_acs_machine.h, which the build + * script writes for the selected machine. */ + +#ifndef _PAL_CONFIG_H_ +#define _PAL_CONFIG_H_ + +#include "wt_acs_machine.h" + +#ifndef CMAKE_BUILD +#define VERBOSITY 3 +#define PLATFORM_NS_HYPERVISOR_PRESENT 0 +#define PLATFORM_SPMC_EL 1 +#define PLATFORM_SP_EL 0 +#define SUITE all +#endif + +/* The SPMC grants these S-EL0 partitions the virtual counter, so their waits + * run in real time instead of a loop calibrated for another platform. */ +#define PLATFORM_SP_EL0_COUNTER_SLEEP 1 + +#define PLATFORM_SP_IMAGE_OFFSET 0x4000 +#define PLATFORM_VM_IMAGE_OFFSET 0x0 + +#define PLATFORM_PAGE_SIZE 0x1000 +#define PAGE_SIZE_4K 0x1000 +#define PAGE_SIZE_16K (4 * 0x1000) +#define PAGE_SIZE_64K (16 * 0x1000) + +#define PLATFORM_MEM_RETRIEVE_USING_ADDRESS_RANGES 0 + +#define PLATFORM_OUTER_SHAREABLE_SUPPORT_ONLY 0 +#define PLATFORM_INNER_SHAREABLE_SUPPORT_ONLY 1 +#define PLATFORM_INNER_OUTER_SHAREABLE_SUPPORT 0 + +#define PLATFORM_NS_UART_BASE WT_ACS_NS_UART_BASE +#define PLATFORM_NS_UART_SIZE 0x1000 +#define PLATFORM_S_UART_BASE WT_ACS_S_UART_BASE +#define PLATFORM_S_UART_SIZE 0x1000 + +#define PLATFORM_NVM_BASE WT_ACS_NVM_BASE +#define PLATFORM_NVM_SIZE 0x10000 + +/* Neither machine models the suite's watchdogs, reference-clock timer, or + * SMMU test engine; the ids below only keep the interrupt tests compiling. */ +#define PLATFORM_WDOG_INTR 32 +#define PLATFORM_TWDOG_INTID 56 +#define PALTFORM_AP_REFCLK_CNTPSIRQ1 58 +#define PLATFORM_NS_WD_INTR 59 + +#define PLATFORM_MEM_READ_ONLY_BASE WT_ACS_RO_MEM_BASE +#define PLATFORM_MEM_READ_ONLY_SIZE 0x1000 + +#define PLAT_SMMU_UPSTREAM_DEVICE_MEM_REGION 0x0 +#define PLAT_SMMU_UPSTREAM_DEVICE_MEM_REGION_INVALID 0x0 +#define PLAT_SMMU_UPSTREAM_DEVICE_MEM_SIZE 0x10000 +#define PLATFORM_SMMU_STREAM_ID 1 +#define PLATFORM_SMMU_STREAM_ID_INVALID 2 + +#define GICD_BASE WT_ACS_GICD_BASE +#define GICR_BASE WT_ACS_GICR_BASE +#define GICC_BASE WT_ACS_GICC_BASE +#define GICD_SIZE 0x10000 +#define GICR_SIZE 0x100000 +#define GICC_SIZE 0x2000 + +#define IRQ_PHY_TIMER_EL1 30 +#define IRQ_VIRT_TIMER_EL1 27 +#define IRQ_PHY_TIMER_EL2 26 + +#define PLATFORM_SP1_ID WT_ACS_SP1_ID +#define PLATFORM_SP2_ID WT_ACS_SP2_ID +#define PLATFORM_SP3_ID WT_ACS_SP3_ID +#define PLATFORM_SP4_ID WT_ACS_SP4_ID + +#define PLATFORM_PRIMARY_SCHEDULER_EL 1 + +/* One processing element: the SPMC runs uniprocessor, secondaries stay parked. */ +#define PLATFORM_NO_OF_CPUS 1 + +/* What this platform gives each endpoint: direct messaging (both request + * forms), AArch64. It has no indirect messaging and no notifications, so the + * suite skips those checks instead of demanding them. Bit values are the + * FFA_PARTITION_INFO_GET properties (Table 6.2). */ +#define WT_ACS_EP_PROPERTIES 0x70F +#define WT_ACS_EP_PROPERTIES_NO_INDIRECT 0x70B +#define PLATFORM_VM1_EP_PROPERTIES WT_ACS_EP_PROPERTIES +#define PLATFORM_SP1_EP_PROPERTIES WT_ACS_EP_PROPERTIES +#define PLATFORM_SP2_EP_PROPERTIES WT_ACS_EP_PROPERTIES +#define PLATFORM_SP3_EP_PROPERTIES WT_ACS_EP_PROPERTIES_NO_INDIRECT +#define PLATFORM_SP4_EP_PROPERTIES WT_ACS_EP_PROPERTIES_NO_INDIRECT + +#endif /* _PAL_CONFIG_H_ */ diff --git a/tests/conformance/ffa-acs/tgt_wolftrust_qemu/pal.cmake b/tests/conformance/ffa-acs/tgt_wolftrust_qemu/pal.cmake new file mode 100644 index 00000000..e733f4f8 --- /dev/null +++ b/tests/conformance/ffa-acs/tgt_wolftrust_qemu/pal.cmake @@ -0,0 +1,56 @@ +# pal.cmake +# +# Copyright (C) 2026 wolfSSL Inc. +# +# This file is part of wolfTrust. +# +# wolfTrust 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. +# +# wolfTrust 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, see . + +# FF-A ACS platform layer for the wolfTrust QEMU targets. The interrupt and +# MMIO sources are the suite's own generic ones; only the machine-specific +# files live in this target. +set(PAL_SRC + ${ROOT_DIR}/platform/common/src/pal_libc.c + ${ROOT_DIR}/platform/common/src/pal_misc_asm.S + ${ROOT_DIR}/platform/common/src/pal_spinlock.S + ${ROOT_DIR}/platform/common/src/pal_sp_helpers.c + ${ROOT_DIR}/platform/common/src/pal_spm_helpers.c + ${ROOT_DIR}/platform/common/src/pal_asm_smc.S + ${ROOT_DIR}/platform/pal_baremetal/${TARGET}/src/pal_console.c + ${ROOT_DIR}/platform/pal_baremetal/${TARGET}/src/pal_driver.c + ${ROOT_DIR}/platform/pal_baremetal/${TARGET}/src/pal_misc.c + ${ROOT_DIR}/platform/pal_baremetal/${TARGET}/src/pal_vcpu_setup.c + ${ROOT_DIR}/platform/pal_baremetal/tgt_tfa_fvp/src/pal_mmio.c + ${ROOT_DIR}/platform/pal_baremetal/tgt_tfa_fvp/src/pal_irq.c + ${ROOT_DIR}/platform/driver/src/pal_log.c + ${ROOT_DIR}/platform/driver/src/pal_nvm.c + ${ROOT_DIR}/platform/driver/src/gic/pal_arm_gic_v2v3.c + ${ROOT_DIR}/platform/driver/src/gic/pal_gic_common.c + ${ROOT_DIR}/platform/driver/src/gic/pal_arm_gic_v2.c + ${ROOT_DIR}/platform/driver/src/gic/pal_gic_v3.c + ${ROOT_DIR}/platform/driver/src/gic/pal_gic_v2.c + ${ROOT_DIR}/platform/driver/src/gic/platform.S +) + +add_library(${PAL_LIB} STATIC ${PAL_SRC}) + +target_include_directories(${PAL_LIB} PRIVATE + ${CMAKE_CURRENT_BINARY_DIR} + ${ROOT_DIR}/platform/common/inc/ + ${ROOT_DIR}/platform/common/inc/aarch64/ + ${ROOT_DIR}/platform/pal_baremetal/${TARGET}/inc + ${ROOT_DIR}/platform/driver/inc/ +) + +unset(PAL_SRC) diff --git a/tests/conformance/ffa-acs/tgt_wolftrust_qemu/src/pal_console.c b/tests/conformance/ffa-acs/tgt_wolftrust_qemu/src/pal_console.c new file mode 100644 index 00000000..8777059f --- /dev/null +++ b/tests/conformance/ffa-acs/tgt_wolftrust_qemu/src/pal_console.c @@ -0,0 +1,48 @@ +/* pal_console.c + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfTrust. + * + * wolfTrust 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. + * + * wolfTrust 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, see . + */ + +/* Console backend the suite's logger calls: the Normal-world dispatcher owns + * the Non-secure PL011; no partition maps a UART, so each one prints through + * FFA_CONSOLE_LOG like the endpoints the suite never gives a UART. */ + +#include "pal_interfaces.h" +#include "pal_misc_asm.h" + +void driver_uart_pl011_putc(uint8_t c); + +#if defined(VM1_COMPILE) +#define WT_ACS_UART_DR 0x00u +#define WT_ACS_UART_FR 0x18u +#define WT_ACS_UART_FR_TXFF (1u << 5) + +void driver_uart_pl011_putc(uint8_t c) +{ + volatile uint32_t* uart = (volatile uint32_t*)(uintptr_t)PLATFORM_NS_UART_BASE; + + while ((uart[WT_ACS_UART_FR / 4u] & WT_ACS_UART_FR_TXFF) != 0u) { + } + uart[WT_ACS_UART_DR / 4u] = (uint32_t)c; +} +#else +void driver_uart_pl011_putc(uint8_t c) +{ + pal_uart_putc_hypcall((char)c); +} +#endif diff --git a/tests/conformance/ffa-acs/tgt_wolftrust_qemu/src/pal_driver.c b/tests/conformance/ffa-acs/tgt_wolftrust_qemu/src/pal_driver.c new file mode 100644 index 00000000..f005c70e --- /dev/null +++ b/tests/conformance/ffa-acs/tgt_wolftrust_qemu/src/pal_driver.c @@ -0,0 +1,178 @@ +/* pal_driver.c + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfTrust. + * + * wolfTrust 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. + * + * wolfTrust 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, see . + */ + +/* FF-A ACS device layer for the wolfTrust QEMU machines: the test NVM is a + * Secure RAM band owned by SP1; the machines model no SP805 watchdogs, no + * reference-clock timer and no SMMU test engine. */ + +#include "pal_interfaces.h" +#include "pal_nvm.h" +#include "pal_spm_helpers.h" + +uint32_t pal_nvm_write(uint32_t offset, void *buffer, size_t size) +{ + return driver_nvm_write(offset, buffer, size); +} + +uint32_t pal_nvm_read(uint32_t offset, void *buffer, size_t size) +{ + return driver_nvm_read(offset, buffer, size); +} + +/* Recovery aid only: the scenario runner's emulator timeout ends a hung run. */ +uint32_t pal_watchdog_enable(void) +{ + return PAL_SUCCESS; +} + +uint32_t pal_watchdog_disable(void) +{ + return PAL_SUCCESS; +} + +uint32_t pal_ap_phy_refclk_en(uint32_t us) +{ + (void)us; + return PAL_ERROR; +} + +uint32_t pal_ap_phy_refclk_dis(bool int_mask) +{ + (void)int_mask; + return PAL_ERROR; +} + +uint32_t pal_ap_virt_refclk_en(uint32_t us) +{ + (void)us; + return PAL_ERROR; +} + +uint32_t pal_ap_virt_refclk_dis(bool int_mask) +{ + (void)int_mask; + return PAL_ERROR; +} + +/* The SPMC's test-timer service: a deadline against its scheduling tick that + * makes the named interrupt pending on expiry. A partition reaches it by SVC, + * the Normal-world dispatcher by SMC, through the same conduit helper. */ +#define WT_ACS_SVC_TIMER_ARM 0xC3000102U +#define WT_ACS_SVC_TIMER_STOP 0xC3000103U + +smc_ret_values asm_smc64(uint32_t fid, u_register_t arg1, u_register_t arg2, + u_register_t arg3, u_register_t arg4, + u_register_t arg5, u_register_t arg6, + u_register_t arg7); + +static uint32_t wt_acs_timer_call(uint32_t fid, uint32_t intid, uint32_t ms) +{ + smc_args args = { + .fid = fid, + .arg1 = intid, + .arg2 = ms + }; +#if defined(VM1_COMPILE) + /* The dispatcher reaches the monitor by SMC; its hvc conduit is a real + * hypervisor call this system has nothing to take. */ + smc_ret_values ret = asm_smc64(args.fid, args.arg1, args.arg2, args.arg3, + args.arg4, args.arg5, args.arg6, args.arg7); +#else + smc_ret_values ret = pal_hvc(&args); +#endif + + return (ret.ret0 == 0U) ? PAL_SUCCESS : PAL_ERROR; +} + +uint32_t pal_twdog_enable(uint32_t ms) +{ + (void)spm_interrupt_enable(PLATFORM_TWDOG_INTID, true, INTERRUPT_TYPE_IRQ); + return wt_acs_timer_call(WT_ACS_SVC_TIMER_ARM, PLATFORM_TWDOG_INTID, ms); +} + +uint32_t pal_twdog_disable(void) +{ + return wt_acs_timer_call(WT_ACS_SVC_TIMER_STOP, 0U, 0U); +} + +void pal_twdog_intr_enable(void) +{ + (void)spm_interrupt_enable(PLATFORM_TWDOG_INTID, true, INTERRUPT_TYPE_IRQ); +} + +void pal_twdog_intr_disable(void) +{ + (void)spm_interrupt_enable(PLATFORM_TWDOG_INTID, false, INTERRUPT_TYPE_IRQ); +} + +/* The reference driver loads this value straight into a watchdog counting + * the system counter, so it is counter ticks; the SPMC's timer takes ms. */ +void pal_ns_wdog_enable(uint32_t ms) +{ + uint64_t freq; + + __asm__ volatile("mrs %0, cntfrq_el0" : "=r"(freq)); + if (freq == 0U) { + freq = 1U; + } + (void)wt_acs_timer_call(WT_ACS_SVC_TIMER_ARM, PLATFORM_NS_WD_INTR, + (uint32_t)(((uint64_t)ms * 1000U) / freq)); +} + +void pal_ns_wdog_disable(void) +{ + (void)wt_acs_timer_call(WT_ACS_SVC_TIMER_STOP, 0U, 0U); +} + +void pal_ns_wdog_intr_enable(void) +{ +} + +void pal_ns_wdog_intr_disable(void) +{ +} + +void pal_secure_intr_enable(uint32_t int_id, enum interrupt_pin pin) +{ + (void)int_id; + (void)pin; +} + +void pal_secure_intr_disable(uint32_t int_id, enum interrupt_pin pin) +{ + (void)int_id; + (void)pin; +} + +uint64_t pal_sleep(uint32_t ms) +{ + return sp_sleep_elapsed_time(ms); +} + +uint32_t pal_smmu_device_configure(uint32_t stream_id, uint64_t source, + uint64_t dest, uint64_t size, bool secure) +{ + (void)stream_id; + (void)source; + (void)dest; + (void)size; + (void)secure; + return PAL_ERROR; +} diff --git a/tests/conformance/ffa-acs/tgt_wolftrust_qemu/src/pal_misc.c b/tests/conformance/ffa-acs/tgt_wolftrust_qemu/src/pal_misc.c new file mode 100644 index 00000000..bdba88dc --- /dev/null +++ b/tests/conformance/ffa-acs/tgt_wolftrust_qemu/src/pal_misc.c @@ -0,0 +1,135 @@ +/* pal_misc.c + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfTrust. + * + * wolfTrust 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. + * + * wolfTrust 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, see . + */ + +#include "pal_interfaces.h" +#include "pal_misc_asm.h" + +#define WT_ACS_PSCI_SYSTEM_OFF 0x84000008u +#define WT_ACS_BUFFER_COUNT 5 +/* Each page records the span of the allocation it heads, or that it is the + * tail of a two-page one. */ +#define WT_ACS_BUFFER_TAIL 3u + +static uint32_t is_buffer_in_use[WT_ACS_BUFFER_COUNT]; +__attribute__ ((aligned (PAGE_SIZE_4K))) +static uint8_t pal_buffer_4k[WT_ACS_BUFFER_COUNT][PAGE_SIZE_4K]; + +#if defined(SP1_COMPILE) || defined(VM1_COMPILE) +static memory_region_descriptor_t endpoint_device_regions[] = { +#if defined(SP1_COMPILE) + {PLATFORM_NVM_BASE, PLATFORM_NVM_BASE, PLATFORM_NVM_SIZE, ATTR_DEVICE_RW_S}, +#endif +#if defined(VM1_COMPILE) + {PLATFORM_NS_UART_BASE, PLATFORM_NS_UART_BASE, PLATFORM_NS_UART_SIZE, + ATTR_DEVICE_RW}, + {GICD_BASE, GICD_BASE, GICD_SIZE, ATTR_DEVICE_RW}, + {GICR_BASE, GICR_BASE, GICR_SIZE, ATTR_DEVICE_RW}, + {GICC_BASE, GICC_BASE, GICC_SIZE, ATTR_DEVICE_RW}, +#endif +}; +#endif + +uint32_t pal_get_endpoint_device_map(void **region_list, + size_t *no_of_mem_regions) +{ +#if defined(SP1_COMPILE) || defined(VM1_COMPILE) + *region_list = (void *)endpoint_device_regions; + *no_of_mem_regions = sizeof(endpoint_device_regions) / + sizeof(endpoint_device_regions[0]); +#else + *region_list = NULL; + *no_of_mem_regions = 0u; +#endif + return PAL_SUCCESS; +} + +/* The dispatcher ends the emulator run through PSCI; a partition only parks. */ +uint32_t pal_terminate_simulation(void) +{ +#if defined(VM1_COMPILE) + (void)pal_syscall_for_psci(WT_ACS_PSCI_SYSTEM_OFF, 0, 0, 0); +#endif + while (1) { + } + return PAL_SUCCESS; +} + +void *pal_memory_alloc(uint64_t size) +{ + int span = 0; + int i; + int r; + uint32_t b; + + if (size == PAGE_SIZE_4K) { + span = 1; + } + else if (size == (PAGE_SIZE_4K * 2)) { + span = 2; + } + for (i = 0; (span != 0) && ((i + span) <= WT_ACS_BUFFER_COUNT); i++) { + if ((is_buffer_in_use[i] == 0u) && + ((span == 1) || (is_buffer_in_use[i + 1] == 0u))) { + /* The pool reuses pages across tests; a message header built in + * one must not inherit reserved or UUID bytes from another. */ + for (r = i; r < (i + span); r++) { + is_buffer_in_use[r] = (r == i) ? (uint32_t)span + : WT_ACS_BUFFER_TAIL; + for (b = 0u; b < PAGE_SIZE_4K; b++) { + pal_buffer_4k[r][b] = 0u; + } + } + return &pal_buffer_4k[i][0]; + } + } + return NULL; +} + +/* Only the head of a live allocation of the same span is released; anything + * else is refused, so a wrong size can never free a neighbour's page. */ +uint32_t pal_memory_free(void *address, uint64_t size) +{ + int span = 0; + int i; + + if (size == PAGE_SIZE_4K) { + span = 1; + } + else if (size == (PAGE_SIZE_4K * 2)) { + span = 2; + } + for (i = 0; (span != 0) && ((i + span) <= WT_ACS_BUFFER_COUNT); i++) { + if ((&pal_buffer_4k[i][0] == address) && + (is_buffer_in_use[i] == (uint32_t)span)) { + is_buffer_in_use[i] = 0u; + if (span == 2) { + is_buffer_in_use[i + 1] = 0u; + } + return PAL_SUCCESS; + } + } + return PAL_ERROR; +} + +/* Every endpoint runs identity-mapped. */ +void *pal_mem_virt_to_phys(void *va) +{ + return va; +} diff --git a/tests/conformance/ffa-acs/tgt_wolftrust_qemu/src/pal_vcpu_setup.c b/tests/conformance/ffa-acs/tgt_wolftrust_qemu/src/pal_vcpu_setup.c new file mode 100644 index 00000000..95ac32ac --- /dev/null +++ b/tests/conformance/ffa-acs/tgt_wolftrust_qemu/src/pal_vcpu_setup.c @@ -0,0 +1,57 @@ +/* pal_vcpu_setup.c + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfTrust. + * + * wolfTrust 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. + * + * wolfTrust 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, see . + */ + +/* One processing element: the boot core has affinity 0 on both machines and + * the secondaries stay parked, so no core can be powered on or off. */ + +#include "pal_interfaces.h" +#include + +uint32_t pal_get_no_of_cpus(void) +{ + return PLATFORM_NO_OF_CPUS; +} + +uint32_t pal_get_cpuid(uint64_t mpid) +{ + if ((mpid & MPIDR_AFFINITY_MASK) == 0u) { + return 0u; + } + return PAL_INVALID_CPU_INFO; +} + +uint64_t pal_get_mpid(uint32_t cpuid) +{ + if (cpuid == 0u) { + return 0u; + } + return PAL_INVALID_CPU_INFO; +} + +uint32_t pal_power_on_cpu(uint64_t mpid) +{ + (void)mpid; + return PAL_ERROR; +} + +uint32_t pal_power_off_cpu(void) +{ + return PAL_ERROR; +} diff --git a/tests/firmware/aarch64-ns-smoke/Makefile b/tests/firmware/aarch64-ns-smoke/Makefile new file mode 100644 index 00000000..3610508f --- /dev/null +++ b/tests/firmware/aarch64-ns-smoke/Makefile @@ -0,0 +1,320 @@ +# Makefile +# +# Copyright (C) 2026 wolfSSL Inc. +# +# This file is part of wolfTrust. +# +# wolfTrust 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. +# +# wolfTrust 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 + +# Normal-world payload for tests/target/run_qemu_a_scenario.sh (ns-smoke). +# MACHINE picks the NS console; the run address is NS DRAM (WT_NS_BASE): the +# loaded image first, then the RAM the payload copies .data into at every entry. +ROOT ?= ../../.. +TOOLPREFIX ?= aarch64-none-elf- +MACHINE ?= virt +WT_NS_BASE ?= 0x40000000 +WT_CONF_SUITE ?= ipc +# The crypto and attestation suites link wolfPSA, wolfCrypt, and a heap. +ifneq ($(filter crypto attestation,$(WT_CONF_SUITE)),) +WT_NS_IMG_SIZE ?= 0x100000 +WT_NS_RAM_SIZE ?= 0x100000 +WT_NS_STACK_SIZE ?= 0x40000 +endif +WT_NS_IMG_SIZE ?= 0x60000 +WT_NS_RAM_SIZE ?= 0x20000 +WT_RUN_CONFORMANCE ?= 0 +ifeq ($(WT_RUN_CONFORMANCE),1) +WT_NS_STACK_SIZE ?= 0x10000 +endif +WT_NS_STACK_SIZE ?= 0x4000 +BUILD_DIR ?= build/$(MACHINE) + +CC := $(TOOLPREFIX)gcc +OBJCOPY := $(TOOLPREFIX)objcopy + +# GICR: the boot core's GICv3 redistributor, probed only under a GICv3. +ifeq ($(MACHINE),virt) +UART := 0x09000000 +GICR := 0x080A0000 +WT_NS_CONFDATA_PA ?= 0x0E2C0000 +else ifeq ($(MACHINE),versal-virt) +UART := 0xFF000000 +GICR := 0xF9080000 +WT_NS_CONFDATA_PA ?= 0x7F2C0000 +else +$(error unsupported MACHINE=$(MACHINE) (virt or versal-virt)) +endif + +# Send a direct request to the Secure echo partition (ffa-guest-direct proof). +WT_NS_GUEST_ECHO ?= 0 +CFLAGS := -mcpu=generic -mgeneral-regs-only -mstrict-align -ffreestanding \ + -fno-builtin -fno-pic -fno-stack-protector -nostdlib -O2 -g \ + -Wall -Wextra -Werror -I$(ROOT)/include -DWT_NS_UART=$(UART)u \ + -DWT_NS_GICR=$(GICR)u +ifeq ($(WT_NS_GUEST_ECHO),1) +CFLAGS += -DWT_NS_GUEST_ECHO=1 +endif +# Power off through PSCI at the end of the run (the psci proof). +WT_NS_GUEST_PSCI ?= 0 +ifeq ($(WT_NS_GUEST_PSCI),1) +CFLAGS += -DWT_NS_GUEST_PSCI=1 +endif +# Spin long enough to be preempted by a Secure tick (the ffa-preempt proof). +WT_NS_PREEMPT ?= 0 +ifeq ($(WT_NS_PREEMPT),1) +CFLAGS += -DWT_NS_PREEMPT=1 +endif +# Reach the Secure services over the PSA FF-A transport (the positive/guest1 +# proofs); WT_NS_GUEST_ID stamps the guest marker. +WT_NS_GUEST_PSA ?= 0 +WT_NS_GUEST_ID ?= 0 +WT_NS_VAULT_SECURED ?= 0 +ifeq ($(WT_NS_VAULT_SECURED),1) +CFLAGS += -DWT_NS_VAULT_SECURED=1 +endif +# Forge a client id and an NVM-group request at the relay (the hsmattackneg proof). +WT_NS_HSM_ATTACK ?= 0 +ifeq ($(WT_NS_HSM_ATTACK),1) +CFLAGS += -DWT_NS_HSM_ATTACK=1 +endif +# Sweep unimplemented function ids and confirm each is refused (the smcfuzz proof). +WT_NS_GUEST_FUZZ ?= 0 +ifeq ($(WT_NS_GUEST_FUZZ),1) +CFLAGS += -DWT_NS_GUEST_FUZZ=1 +endif +# Attempt to read Secure RAM from the Normal world (the secramneg proof). +WT_NS_GUEST_SECRAM ?= 0 +WT_NS_SECURE_PROBE_PA ?= 0x0E300000 +ifeq ($(WT_NS_GUEST_SECRAM),1) +CFLAGS += -DWT_NS_GUEST_SECRAM=1 -DWT_NS_SECURE_PROBE_PA=$(WT_NS_SECURE_PROBE_PA)u +endif +# Request a PSCI system reset that re-enters the boot chain (the resetneg proof). +WT_NS_GUEST_RESET ?= 0 +ifeq ($(WT_NS_GUEST_RESET),1) +CFLAGS += -DWT_NS_GUEST_RESET=1 +endif +# Offer malformed FFA_MEM_SHARE descriptors, each refused (the ffa-memneg proof). +WT_NS_GUEST_MEMNEG ?= 0 +# Round-trip an ITS object through SERVICE_ITS and the VAULT partition behind +# it (the storage proof: the first SP-to-SP message driven from the Normal +# world). +WT_NS_GUEST_STORAGE ?= 0 +LDFLAGS := -nostartfiles -Wl,--build-id=none -T ns.ld \ + -Wl,--defsym=WT_NS_BASE=$(WT_NS_BASE) \ + -Wl,--defsym=WT_NS_IMG_SIZE=$(WT_NS_IMG_SIZE) \ + -Wl,--defsym=WT_NS_RAM_SIZE=$(WT_NS_RAM_SIZE) \ + -Wl,--defsym=WT_NS_STACK_SIZE=$(WT_NS_STACK_SIZE) + +SRCS := ns.S ns.c +# The PSA, storage and conformance guests link the same OS-neutral PSA client +# every Armv8-M guest does plus the FF-A binding of its WolfTrust_FFM_* entry +# points; the service ids come from the generated manifest header of the +# matching secure build (WT_NS_MANIFEST_INC). +WT_NS_MANIFEST_INC ?= +WOLFHSM_DIR ?= $(ROOT)/lib/wolfHSM +ifneq ($(filter 1,$(WT_NS_GUEST_PSA) $(WT_NS_GUEST_STORAGE) $(WT_NS_GUEST_MEMNEG) $(WT_RUN_CONFORMANCE)),) +ifneq ($(WT_NS_MANIFEST_INC),) +CFLAGS += -I$(WT_NS_MANIFEST_INC) +endif +SRCS += $(ROOT)/src/client/psa_ffm_client.c \ + $(ROOT)/src/arch/aarch64/ffa/psa_ffa_transport_arch.c \ + $(ROOT)/src/arch/aarch64/common/libc_min.c +endif +# The crypto engine of the matching secure build picks the guest's client. +WT_ENGINE ?= native +ifneq ($(words $(WT_ENGINE))/$(filter native hsm,$(WT_ENGINE)),1/$(WT_ENGINE)) +$(error unsupported WT_ENGINE='$(WT_ENGINE)' (want native or hsm)) +endif +ifeq ($(WT_NS_HSM_ATTACK)/$(WT_ENGINE),1/native) +$(error WT_NS_HSM_ATTACK=1 drives the wolfHSM wire and needs WT_ENGINE=hsm) +endif +ifeq ($(WT_NS_GUEST_PSA)/$(WT_ENGINE),1/native) +CFLAGS += -DWT_NS_GUEST_PSA=1 -DWT_NS_GUEST_ID=$(WT_NS_GUEST_ID) \ + -DWT_NS_ENGINE_NATIVE=1 +SRCS += $(ROOT)/src/client/crypto_native_client.c +endif +ifeq ($(WT_NS_GUEST_PSA)/$(WT_ENGINE),1/hsm) +CFLAGS += -DWT_NS_GUEST_PSA=1 -DWT_NS_GUEST_ID=$(WT_NS_GUEST_ID) \ + -DWOLFHSM_CFG -I. -I$(WOLFHSM_DIR) +SRCS += $(ROOT)/src/client/hsm_psa_transport.c \ + $(WOLFHSM_DIR)/src/wh_client.c \ + $(WOLFHSM_DIR)/src/wh_comm.c \ + $(WOLFHSM_DIR)/src/wh_message_comm.c \ + $(WOLFHSM_DIR)/src/wh_message_keystore.c \ + $(WOLFHSM_DIR)/src/wh_message_counter.c \ + $(WOLFHSM_DIR)/src/wh_message_customcb.c \ + $(WOLFHSM_DIR)/src/wh_message_nvm.c \ + $(WOLFHSM_DIR)/src/wh_utils.c +endif +ifeq ($(WT_NS_GUEST_MEMNEG),1) +CFLAGS += -DWT_NS_GUEST_MEMNEG=1 +SRCS += $(ROOT)/src/arch/aarch64/ffa/ffa_mem.c +endif +ifeq ($(WT_NS_GUEST_STORAGE),1) +CFLAGS += -DWT_NS_GUEST_STORAGE=1 +endif +# The memneg guest names memory it lent away in ITS calls. +ifneq ($(filter 1,$(WT_NS_GUEST_STORAGE) $(WT_NS_GUEST_MEMNEG)),) +SRCS += $(ROOT)/src/client/psa_storage_client.c +endif +# Run the unmodified Arm psa-arch-tests val NSPE from this payload: the FF-M +# IPC suite (confboot, WT_CONF_SUITE=ipc) or the dev_apis storage suite +# (devstorage, WT_CONF_SUITE=storage). val and the scheduled tests come from +# the secure build's fetched upstream tree and generated test lists; +# wolfTrust's psa/*.h stay first on the include path, the NS test list before +# the SPE one. +CONF_OBJS := +ifeq ($(WT_RUN_CONFORMANCE),1) +WT_NS_CONF_UPSTREAM ?= $(ROOT)/build/upstream/psa-arch-tests/api-tests +ifeq ($(WT_CONF_SUITE),storage) +CONF_LIST_DIR := $(WT_NS_MANIFEST_INC)/storage/ns +CONF_SUITE_DIR := $(WT_NS_CONF_UPSTREAM)/dev_apis/storage +CONF_SUITE_DEFS := -DSTORAGE +CONF_SUITE_SRCS := $(CONF_SUITE_DIR)/common/test_storage_common.c +CONF_SUITE_INCS := -I$(CONF_SUITE_DIR)/common +else ifeq ($(WT_CONF_SUITE),crypto) +CONF_LIST_DIR := $(WT_NS_MANIFEST_INC)/crypto/ns +CONF_SUITE_DIR := $(WT_NS_CONF_UPSTREAM)/dev_apis/crypto +CONF_PAL_DIR := $(WT_NS_CONF_UPSTREAM)/platform/targets/common/nspe/crypto +CONF_SUITE_DEFS := -DCRYPTO +CONF_SUITE_SRCS := $(CONF_SUITE_DIR)/common/test_crypto_common.c \ + $(CONF_PAL_DIR)/pal_crypto_intf.c +CONF_SUITE_INCS := -I$(CONF_SUITE_DIR)/common -I$(CONF_PAL_DIR) +CONF_PSA := 1 +else ifeq ($(WT_CONF_SUITE),attestation) +# The token parses with the wolfCOSE shim by default; WT_ATTEST_CBOR=qcbor +# swaps in the reference QCBOR library (test-only, fetch_qcbor.sh). +WT_ATTEST_CBOR ?= shim +ifeq ($(WT_ATTEST_CBOR),qcbor) +WT_QCBOR_DIR ?= $(ROOT)/build/upstream/qcbor +ATTEST_CBOR_SRCS := $(WT_QCBOR_DIR)/src/UsefulBuf.c \ + $(WT_QCBOR_DIR)/src/qcbor_encode.c $(WT_QCBOR_DIR)/src/qcbor_decode.c \ + $(WT_QCBOR_DIR)/src/ieee754.c +# QCBOR carries double types, so its objects use FP and the payload enables it. +ATTEST_CBOR_INCS := -I$(WT_QCBOR_DIR)/inc -I$(WT_QCBOR_DIR)/inc/qcbor \ + -DWT_NS_ENABLE_FP=1 +else +ATTEST_CBOR_SRCS := $(ROOT)/tests/conformance/qcbor-shim/qcbor_shim.c +ATTEST_CBOR_INCS := -I$(ROOT)/tests/conformance/qcbor-shim +endif +CONF_LIST_DIR := $(WT_NS_MANIFEST_INC)/initial_attestation/ns +CONF_SUITE_DIR := $(WT_NS_CONF_UPSTREAM)/dev_apis/initial_attestation +CONF_PAL_DIR := $(WT_NS_CONF_UPSTREAM)/platform/targets/common/nspe/initial_attestation +CONF_SUITE_DEFS := -DINITIAL_ATTESTATION -DPSA_ATTESTATION_PROFILE_2 \ + -DPSA_INITIAL_ATTEST_MAX_TOKEN_SIZE=640 +CONF_SUITE_SRCS := $(CONF_PAL_DIR)/pal_attestation_intf.c \ + $(CONF_PAL_DIR)/pal_attestation_crypto.c \ + $(ROOT)/tests/firmware/zephyr-stm32h5/module/wolftrust-tee/src/wolftrust_attestation_client.c \ + $(ATTEST_CBOR_SRCS) +CONF_SUITE_INCS := -I$(CONF_PAL_DIR) $(ATTEST_CBOR_INCS) \ + -I$(ROOT)/tests/firmware/zephyr-stm32h5/module/wolftrust-tee/include +# attestneg runs a Normal-world negative probe instead of val, reusing this +# build's attestation client and COSE stack plus the guest's own token verify. +WT_NS_ATTEST_NEG ?= 0 +ifeq ($(WT_NS_ATTEST_NEG),1) +CONF_SUITE_DEFS += -DWT_NS_ATTEST_NEG=1 +CONF_SUITE_SRCS += $(ROOT)/tests/firmware/zephyr-stm32h5/apps/guest0_psa/src/attestation_verify.c \ + $(ROOT)/lib/wolfCOSE/src/wolfcose_recipient.c +CONF_SUITE_INCS += -I$(ROOT)/tests/firmware/zephyr-stm32h5/apps/guest0_psa/src +endif +CONF_PSA := 1 +else +CONF_LIST_DIR := $(WT_NS_MANIFEST_INC)/ns +CONF_SUITE_DIR := $(WT_NS_CONF_UPSTREAM)/ff/ipc +CONF_SUITE_DEFS := -DIPC +CONF_SUITE_SRCS := +CONF_SUITE_INCS := +endif +CONF_TESTS := $(shell cat $(CONF_LIST_DIR)/testlist.txt 2>/dev/null) +ifeq ($(CONF_TESTS),) +$(error WT_RUN_CONFORMANCE=1 needs the secure build's generated test list at $(CONF_LIST_DIR)/testlist.txt) +endif +CFLAGS := -I$(CONF_LIST_DIR) $(CFLAGS) \ + -DWT_NS_GUEST_CONF=1 $(CONF_SUITE_DEFS) -DVAL_NSPE_BUILD \ + -DNONSECURE_TEST_BUILD -DVERBOSITY=3 \ + -DWT_SPM_CONFDATA_PA=$(WT_NS_CONFDATA_PA)u \ + -ffunction-sections -fdata-sections \ + -I$(WT_NS_CONF_UPSTREAM)/val/common \ + -I$(WT_NS_CONF_UPSTREAM)/val/nspe \ + -I$(WT_NS_CONF_UPSTREAM)/val/spe \ + -I$(WT_NS_CONF_UPSTREAM)/platform/targets/common/nspe \ + -I$(ROOT)/port/common/aarch64/conformance \ + $(CONF_SUITE_INCS) \ + $(foreach t,$(CONF_TESTS),-I$(CONF_SUITE_DIR)/$(t)) +LDFLAGS += -Wl,--gc-sections +SRCS += conformance_pal.c $(ROOT)/src/client/psa_storage_client.c +CONF_CFLAGS := $(filter-out -Werror,$(CFLAGS)) -Wno-unused-function \ + -Wno-unused-variable -Wno-unused-parameter -Wno-type-limits +CONF_SRCS := $(WT_NS_CONF_UPSTREAM)/val/common/val_log.c \ + $(addprefix $(WT_NS_CONF_UPSTREAM)/val/nspe/,val_entry.c val_dispatcher.c \ + val_framework.c val_interfaces.c val_peripherals.c val_platform.c \ + val_crypto.c val_storage.c val_attestation.c) \ + $(CONF_SUITE_SRCS) \ + $(foreach t,$(CONF_TESTS),$(CONF_SUITE_DIR)/$(t)/$(t).c \ + $(CONF_SUITE_DIR)/$(t)/test_entry_$(t:test_%=%).c) +ifeq ($(CONF_PSA),1) +# wolfPSA over wolfCrypt runs in the guest itself; persistent keys ride the +# ITS client through wolfPSA's secure-storage backend. +WOLFSSL_DIR ?= $(ROOT)/lib/wolfSSL +WOLFPSA_DIR ?= $(ROOT)/lib/wolfPSA +WOLFCOSE_DIR ?= $(ROOT)/lib/wolfCOSE +CFLAGS += -DWOLFSSL_USER_SETTINGS -DWT_NS_HEAP=1 -Icrypto \ + -I$(WOLFSSL_DIR) -I$(WOLFPSA_DIR) -I$(WOLFPSA_DIR)/wolfpsa \ + -I$(WOLFPSA_DIR)/src -I$(WOLFCOSE_DIR)/include \ + -DWOLFCOSE_LEAN -DWOLFCOSE_ENABLE_DEPRECATED_ALGS -DWOLFCOSE_NO_SIGN1_SIGN \ + -DWOLFCOSE_NO_ENCRYPT0 -DWOLFCOSE_NO_MAC0 -DWOLFCOSE_NO_KEY_ENCODE \ + -DWOLFCOSE_NO_KEY_DECODE +CONF_CFLAGS := $(filter-out -Werror,$(CFLAGS)) -Wno-unused-function \ + -Wno-unused-variable -Wno-unused-parameter -Wno-type-limits +CONF_SRCS += crypto/ns_crypto_port.c crypto/psa_store_ns.c \ + $(filter-out %/psa_store_posix.c %/psa_store_zephyr.c %/psa_attestation.c,$(wildcard $(WOLFPSA_DIR)/src/*.c)) \ + $(addprefix $(WOLFSSL_DIR)/wolfcrypt/src/,aes.c asn.c chacha.c \ + chacha20_poly1305.c cmac.c coding.c cryptocb.c des3.c ecc.c error.c \ + hash.c hmac.c kdf.c logging.c md5.c memory.c poly1305.c pwdbased.c \ + random.c ripemd.c rsa.c sha.c sha256.c sha3.c sha512.c signature.c \ + sp_c64.c sp_int.c wc_encrypt.c wc_port.c wolfmath.c) \ + $(addprefix $(WOLFCOSE_DIR)/src/,wolfcose_cbor.c wolfcose_util.c \ + wolfcose_alg.c wolfcose_ecc.c wolfcose_hdr.c wolfcose_key.c \ + wolfcose_struct.c wolfcose_sign1.c) +endif +CONF_OBJS := $(addprefix $(BUILD_DIR)/conf/,$(notdir $(CONF_SRCS:.c=.o))) +vpath %.c $(sort $(dir $(CONF_SRCS))) +endif +ELF := $(BUILD_DIR)/ns.elf +BIN := $(BUILD_DIR)/ns.bin + +.PHONY: all clean + +all: $(ELF) $(BIN) + +$(BUILD_DIR) $(BUILD_DIR)/conf: + mkdir -p $@ + +$(BUILD_DIR)/conf/%.o: %.c | $(BUILD_DIR)/conf + $(CC) $(CONF_CFLAGS) -c -o $@ $< + +ifeq ($(WT_ATTEST_CBOR),qcbor) +$(addprefix $(BUILD_DIR)/conf/,UsefulBuf.o qcbor_encode.o qcbor_decode.o ieee754.o): \ + CONF_CFLAGS := $(filter-out -mgeneral-regs-only,$(CONF_CFLAGS)) +endif + +$(ELF): $(SRCS) $(CONF_OBJS) ns.ld | $(BUILD_DIR) + $(CC) $(CFLAGS) $(LDFLAGS) -o $@ $(SRCS) $(CONF_OBJS) -lgcc + +$(BIN): $(ELF) + $(OBJCOPY) -O binary $< $@ + +clean: + rm -rf build diff --git a/tests/firmware/aarch64-ns-smoke/conformance_pal.c b/tests/firmware/aarch64-ns-smoke/conformance_pal.c new file mode 100644 index 00000000..18418a2d --- /dev/null +++ b/tests/firmware/aarch64-ns-smoke/conformance_pal.c @@ -0,0 +1,321 @@ +/* conformance_pal.c + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfTrust. + * + * wolfTrust 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. + * + * wolfTrust 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, see . + */ + +/* Bare-metal Non-secure PAL for the Arm psa-arch-tests val NSPE, the AArch64 + * twin of the Zephyr guest's conformance_pal.c: val prints through one char + * sink, keeps its boot flag in the DRIVER partition's NVMEM service (shared + * with the SPE val, backed by the Secure reset-surviving store) and reaches the + * SPM through the same OS-neutral PSA client the production guest links. */ + +#include +#include +#include + +/* This TU implements the real PSA client API; without IPC the dev_apis builds + * get pal_common.h's fallback psa_invec/psa_outvec typedefs, which collide + * with psa/client.h. */ +#ifndef IPC +#define IPC 1 +#endif + +#include "psa/client.h" +#include "psa_manifest/sid.h" +#include "pal_common.h" +#include "pal_interfaces_ns.h" + +#include "psa/storage_common.h" +#include "psa/internal_trusted_storage.h" +#include "psa/protected_storage.h" +#if defined(INITIAL_ATTESTATION) +#include "psa/crypto.h" +#include "wolftrust/attestation.h" +#endif + +extern void ns_putc(char c); + +uint8_t test_status_buffer[256] = {0}; + +int pal_print(uint8_t c) +{ + ns_putc((char)c); + return 0; +} + +int pal_print_ns(const char* str, int32_t data) +{ + (void)data; + while ((str != NULL) && (*str != '\0')) { + ns_putc(*str); + str++; + } + return 0; +} + +unsigned int pal_platform_init(void) +{ + return 0u; +} + +bool_t pal_is_test_enabled(test_id_t test_id) +{ + (void)test_id; + return 1; +} + +void pal_set_custom_test_list(char* custom_test_list) +{ + (void)custom_test_list; +} + +static psa_status_t wt_conf_nvm_call(uint32_t fn_type, uint32_t offset, + void* buffer, size_t size) +{ + nvmem_param_t param; + psa_invec invec[2]; + psa_outvec outvec[1]; + psa_handle_t handle; + psa_status_t status; + + param.nvmem_fn_type = (nvmem_fn_type_t)fn_type; + param.base = (addr_t)PLATFORM_NVM_BASE; + param.offset = offset; + param.size = (int)size; + handle = psa_connect(DRIVER_NVMEM_SID, DRIVER_NVMEM_VERSION); + if (handle <= 0) { + return PSA_ERROR_CONNECTION_REFUSED; + } + invec[0].base = ¶m; + invec[0].len = sizeof(param); + if (fn_type == (uint32_t)NVMEM_WRITE) { + invec[1].base = buffer; + invec[1].len = size; + status = psa_call(handle, 0, invec, 2u, NULL, 0u); + } + else { + outvec[0].base = buffer; + outvec[0].len = size; + status = psa_call(handle, 0, invec, 1u, outvec, 1u); + } + psa_close(handle); + return status; +} + +int pal_nvm_read(uint32_t offset, void* buffer, size_t size) +{ + if (buffer == NULL) { + return 1; + } + if (wt_conf_nvm_call((uint32_t)NVMEM_READ, offset, buffer, size) != + PSA_SUCCESS) { + return 1; + } + return 0; +} + +int pal_nvm_write(uint32_t offset, void* buffer, size_t size) +{ + if (buffer == NULL) { + return 1; + } + if (wt_conf_nvm_call((uint32_t)NVMEM_WRITE, offset, buffer, size) != + PSA_SUCCESS) { + return 1; + } + return 0; +} + +int pal_watchdog_enable(void) +{ + return 0; +} + +int pal_watchdog_disable(void) +{ + return 0; +} + +int pal_uart_init_ns(void) +{ + return 0; +} + +int pal_wd_timer_init_ns(uint32_t time_us, uint32_t timer_tick_us) +{ + (void)time_us; + (void)timer_tick_us; + return 0; +} + +int pal_wd_timer_enable_ns(void) +{ + return 0; +} + +int pal_wd_timer_disable_ns(void) +{ + return 0; +} + +int pal_system_reset(void) +{ + return 0; +} + +void pal_terminate_simulation(void) +{ + pal_print_ns("wolfTrust FF-M conformance: val_entry returned\n", 0); +} + +#if !defined(CRYPTO) +int32_t pal_crypto_function(int type, va_list valist) +{ + (void)type; + (void)valist; + return -1; +} +#endif + +/* dev_apis storage: dispatch val's ITS/PS function codes (val_storage.h's + * storage_function_type_t: ITS SET/GET/GET_INFO/REMOVE = 0x1..0x4, PS + * SET/GET/GET_INFO/REMOVE/CREATE/SET_EXTENDED/GET_SUPPORT = 0x5..0xB) onto + * the OS-neutral PSA storage client, which marshals each onto SERVICE_ITS / + * SERVICE_PS over the routed gateway. */ +uint32_t pal_its_function(int type, va_list valist) +{ + psa_storage_uid_t uid; + uint32_t data_size; + uint32_t offset; + const void* p_write_data; + void* p_read_data; + size_t* p_data_length; + psa_storage_create_flags_t create_flags; + struct psa_storage_info_t* p_info; + + switch (type) { + case 0x1: + uid = va_arg(valist, psa_storage_uid_t); + data_size = va_arg(valist, uint32_t); + p_write_data = va_arg(valist, const void*); + create_flags = va_arg(valist, psa_storage_create_flags_t); + return (uint32_t)psa_its_set(uid, data_size, p_write_data, + create_flags); + case 0x2: + uid = va_arg(valist, psa_storage_uid_t); + offset = va_arg(valist, uint32_t); + data_size = va_arg(valist, uint32_t); + p_read_data = va_arg(valist, void*); + p_data_length = va_arg(valist, size_t*); + return (uint32_t)psa_its_get(uid, offset, data_size, p_read_data, + p_data_length); + case 0x3: + uid = va_arg(valist, psa_storage_uid_t); + p_info = va_arg(valist, struct psa_storage_info_t*); + return (uint32_t)psa_its_get_info(uid, p_info); + case 0x4: + uid = va_arg(valist, psa_storage_uid_t); + return (uint32_t)psa_its_remove(uid); + default: + return PAL_STATUS_UNSUPPORTED_FUNC; + } +} + +uint32_t pal_ps_function(int type, va_list valist) +{ + psa_storage_uid_t uid; + uint32_t data_size; + uint32_t size; + uint32_t offset; + const void* p_write_data; + void* p_read_data; + size_t* p_data_length; + psa_storage_create_flags_t create_flags; + struct psa_storage_info_t* p_info; + + switch (type) { + case 0x5: + uid = va_arg(valist, psa_storage_uid_t); + data_size = va_arg(valist, uint32_t); + p_write_data = va_arg(valist, const void*); + create_flags = va_arg(valist, psa_storage_create_flags_t); + return (uint32_t)psa_ps_set(uid, data_size, p_write_data, + create_flags); + case 0x6: + uid = va_arg(valist, psa_storage_uid_t); + offset = va_arg(valist, uint32_t); + data_size = va_arg(valist, uint32_t); + p_read_data = va_arg(valist, void*); + p_data_length = va_arg(valist, size_t*); + return (uint32_t)psa_ps_get(uid, offset, data_size, p_read_data, + p_data_length); + case 0x7: + uid = va_arg(valist, psa_storage_uid_t); + p_info = va_arg(valist, struct psa_storage_info_t*); + return (uint32_t)psa_ps_get_info(uid, p_info); + case 0x8: + uid = va_arg(valist, psa_storage_uid_t); + return (uint32_t)psa_ps_remove(uid); + case 0x9: + uid = va_arg(valist, psa_storage_uid_t); + size = va_arg(valist, uint32_t); + create_flags = va_arg(valist, psa_storage_create_flags_t); + return (uint32_t)psa_ps_create(uid, size, create_flags); + case 0xA: + uid = va_arg(valist, psa_storage_uid_t); + offset = va_arg(valist, uint32_t); + data_size = va_arg(valist, uint32_t); + p_write_data = va_arg(valist, const void*); + return (uint32_t)psa_ps_set_extended(uid, offset, data_size, + p_write_data); + case 0xB: + return psa_ps_get_support(); + default: + return PAL_STATUS_UNSUPPORTED_FUNC; + } +} + +#if defined(INITIAL_ATTESTATION) +/* val's own COSE_Sign1 verify asks for the key the token was signed with: + * the runtime IAK public key, read through SERVICE_ATTEST. */ +int32_t tfm_initial_attest_get_public_key(uint8_t* public_key_buff, + size_t public_key_buf_size, size_t* public_key_len, + psa_ecc_family_t* elliptic_family_type) +{ + psa_status_t status; + + if ((public_key_buff == NULL) || (public_key_len == NULL) || + (elliptic_family_type == NULL)) { + return PSA_ERROR_INVALID_ARGUMENT; + } + status = wolftrust_attestation_get_iak_public_key(public_key_buff, + public_key_buf_size, + public_key_len); + if (status == PSA_SUCCESS) { + *elliptic_family_type = PSA_ECC_FAMILY_SECP_R1; + } + return status; +} +#else +int32_t pal_attestation_function(int type, va_list valist) +{ + (void)type; + (void)valist; + return -1; +} +#endif diff --git a/tests/firmware/aarch64-ns-smoke/crypto/ns_crypto_port.c b/tests/firmware/aarch64-ns-smoke/crypto/ns_crypto_port.c new file mode 100644 index 00000000..f5e1341f --- /dev/null +++ b/tests/firmware/aarch64-ns-smoke/crypto/ns_crypto_port.c @@ -0,0 +1,259 @@ +/* ns_crypto_port.c + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfTrust. + * + * wolfTrust 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. + * + * wolfTrust 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, see . + */ + +/* Runtime the bare-metal conformance guest needs under wolfCrypt and wolfPSA: + * a first-fit heap, the string helpers libc_min.c lacks, and a DRBG seed. The + * seed is emulator test entropy only, never a silicon source. */ + +#include +#include +#include + +#ifndef WT_NS_HEAP_SIZE +#define WT_NS_HEAP_SIZE 0x80000u +#endif +#define WT_NS_HEAP_ALIGN 16u + +typedef struct wt_ns_block { + size_t size; + size_t used; +} wt_ns_block_t; + +static uint8_t g_heap[WT_NS_HEAP_SIZE] __attribute__((aligned(16))); +static int g_heap_ready; + +void* memcpy(void* dst, const void* src, size_t n); +void* memset(void* dst, int c, size_t n); + +static wt_ns_block_t* heap_next(wt_ns_block_t* b) +{ + return (wt_ns_block_t*)(void*)((uint8_t*)b + sizeof(*b) + b->size); +} + +static int heap_in_range(const wt_ns_block_t* b) +{ + uintptr_t at = (uintptr_t)b; + uintptr_t lo = (uintptr_t)g_heap; + uintptr_t hi = lo + sizeof(g_heap); + + return (at >= lo) && (at <= (hi - sizeof(*b))); +} + +static void heap_init(void) +{ + wt_ns_block_t* first = (wt_ns_block_t*)(void*)g_heap; + + first->size = sizeof(g_heap) - sizeof(*first); + first->used = 0u; + g_heap_ready = 1; +} + +void* malloc(size_t n) +{ + wt_ns_block_t* b; + wt_ns_block_t* split; + wt_ns_block_t* next; + void* ret = NULL; + + if (g_heap_ready == 0) { + heap_init(); + } + if (n == 0u) { + n = 1u; + } + if (n > (sizeof(g_heap) - sizeof(*b))) { + return NULL; + } + n = (n + (WT_NS_HEAP_ALIGN - 1u)) & ~(size_t)(WT_NS_HEAP_ALIGN - 1u); + b = (wt_ns_block_t*)(void*)g_heap; + while ((ret == NULL) && heap_in_range(b)) { + if (b->used == 0u) { + /* Merge the free run ahead so freed neighbours are reusable. */ + next = heap_next(b); + while (heap_in_range(next) && (next->used == 0u)) { + b->size += sizeof(*next) + next->size; + next = heap_next(b); + } + if (b->size >= n) { + if (b->size >= (n + sizeof(*b) + WT_NS_HEAP_ALIGN)) { + split = (wt_ns_block_t*)(void*)((uint8_t*)b + sizeof(*b) + n); + split->size = b->size - n - sizeof(*b); + split->used = 0u; + b->size = n; + } + b->used = 1u; + ret = (uint8_t*)b + sizeof(*b); + } + } + if (ret == NULL) { + b = heap_next(b); + } + } + return ret; +} + +void free(void* p) +{ + wt_ns_block_t* b; + + if (p != NULL) { + b = (wt_ns_block_t*)(void*)((uint8_t*)p - sizeof(*b)); + b->used = 0u; + } +} + +void* calloc(size_t count, size_t size) +{ + size_t total = count * size; + void* p = NULL; + + if ((size == 0u) || ((total / size) == count)) { + p = malloc(total); + } + if (p != NULL) { + (void)memset(p, 0, total); + } + return p; +} + +void* realloc(void* p, size_t n) +{ + wt_ns_block_t* b; + void* q; + + if (p == NULL) { + return malloc(n); + } + b = (wt_ns_block_t*)(void*)((uint8_t*)p - sizeof(*b)); + if (b->size >= n) { + return p; + } + q = malloc(n); + if (q != NULL) { + (void)memcpy(q, p, b->size); + free(p); + } + return q; +} + +size_t strlen(const char* s) +{ + size_t n = 0u; + + while (s[n] != '\0') { + n++; + } + return n; +} + +int strcmp(const char* a, const char* b) +{ + while ((*a != '\0') && (*a == *b)) { + a++; + b++; + } + return (int)(unsigned char)*a - (int)(unsigned char)*b; +} + +int strncmp(const char* a, const char* b, size_t n) +{ + while ((n > 0u) && (*a != '\0') && (*a == *b)) { + a++; + b++; + n--; + } + if (n == 0u) { + return 0; + } + return (int)(unsigned char)*a - (int)(unsigned char)*b; +} + +char* strncpy(char* dst, const char* src, size_t n) +{ + size_t i = 0u; + + while ((i < n) && (src[i] != '\0')) { + dst[i] = src[i]; + i++; + } + while (i < n) { + dst[i] = '\0'; + i++; + } + return dst; +} + +/* wolfPSA's trace helper links the hosted stdio; tracing stays off here. */ +void* _impure_ptr; + +char* getenv(const char* name) +{ + (void)name; + return NULL; +} + +int fputs(const char* s, void* stream) +{ + (void)s; + (void)stream; + return 0; +} + +int fputc(int c, void* stream) +{ + (void)stream; + return c; +} + +int vfprintf(void* stream, const char* fmt, va_list args) +{ + (void)stream; + (void)fmt; + (void)args; + return 0; +} + +static uint64_t read_cntpct(void) +{ + uint64_t v; + + __asm__ volatile("isb\n\tmrs %0, CNTPCT_EL0" : "=r"(v)); + return v; +} + +int wt_ns_generate_seed(unsigned char* output, unsigned int sz) +{ + static uint64_t state; + uint64_t x; + unsigned int i; + + if (state == 0u) { + state = read_cntpct() | 1u; + } + for (i = 0u; i < sz; i++) { + x = state ^ read_cntpct(); + x ^= x << 13; + x ^= x >> 7; + x ^= x << 17; + state = x; + output[i] = (unsigned char)((x * 0x2545F4914F6CDD1Dull) >> 56); + } + return 0; +} diff --git a/tests/firmware/aarch64-ns-smoke/crypto/psa_store_ns.c b/tests/firmware/aarch64-ns-smoke/crypto/psa_store_ns.c new file mode 100644 index 00000000..78ba5866 --- /dev/null +++ b/tests/firmware/aarch64-ns-smoke/crypto/psa_store_ns.c @@ -0,0 +1,193 @@ +/* psa_store_ns.c + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfTrust. + * + * wolfTrust 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. + * + * wolfTrust 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, see . + */ + +/* wolfPSA persistent-key store backend for the bare-metal conformance guest: + * the same object-at-a-time ITS backend wolfPSA ships for Zephyr, riding the + * routed SERVICE_ITS client. A key id of zero (an invalid/absent handle, not a + * real record) reports NOT_AVAILABLE so wolfPSA answers INVALID_HANDLE, not + * STORAGE_FAILURE. */ + +#include +#include +#include +#include +#include "psa/internal_trusted_storage.h" + +typedef struct WolfpsaNsStore { + psa_storage_uid_t uid; + unsigned char* buf; + size_t len; + size_t off; + int write; +} WolfpsaNsStore; + +static psa_storage_uid_t wolfpsa_store_uid(int type, unsigned long id1, + unsigned long id2) +{ + (void)type; + (void)id2; + return (psa_storage_uid_t)id1; +} + +int wolfPSA_Store_OpenSz(int type, unsigned long id1, unsigned long id2, int read, + int variableSz, void** store) +{ + int ret = WOLFPSA_STORE_OK; + psa_storage_uid_t uid; + WolfpsaNsStore* ctx = NULL; + struct psa_storage_info_t info; + psa_status_t st; + + (void)variableSz; + + if (store == NULL) { + return WOLFPSA_STORE_IO_ERROR; + } + *store = NULL; + + uid = wolfpsa_store_uid(type, id1, id2); + if (uid == 0) { + /* PSA_KEY_ID_NULL is never persisted: report the record absent so an + * invalid or zero handle becomes INVALID_HANDLE, not STORAGE_FAILURE. */ + return WOLFPSA_STORE_NOT_AVAILABLE; + } + + if (read) { + st = psa_its_get_info(uid, &info); + if (st == PSA_ERROR_DOES_NOT_EXIST) { + return WOLFPSA_STORE_NOT_AVAILABLE; + } + if (st != PSA_SUCCESS) { + return WOLFPSA_STORE_IO_ERROR; + } + } + + ctx = (WolfpsaNsStore*)XMALLOC(sizeof(*ctx), NULL, DYNAMIC_TYPE_TMP_BUFFER); + if (ctx == NULL) { + return WOLFPSA_STORE_IO_ERROR; + } + XMEMSET(ctx, 0, sizeof(*ctx)); + ctx->uid = uid; + ctx->write = (read == 0); + if (read) { + ctx->len = (size_t)info.size; + } + + *store = ctx; + return ret; +} + +int wolfPSA_Store_Open(int type, unsigned long id1, unsigned long id2, int read, + void** store) +{ + return wolfPSA_Store_OpenSz(type, id1, id2, read, 0, store); +} + +int wolfPSA_Store_Remove(int type, unsigned long id1, unsigned long id2) +{ + psa_storage_uid_t uid; + psa_status_t st; + + uid = wolfpsa_store_uid(type, id1, id2); + if (uid == 0) { + return WOLFPSA_STORE_NOT_AVAILABLE; + } + + st = psa_its_remove(uid); + if (st == PSA_ERROR_DOES_NOT_EXIST) { + return WOLFPSA_STORE_NOT_AVAILABLE; + } + if (st != PSA_SUCCESS) { + return WOLFPSA_STORE_IO_ERROR; + } + return WOLFPSA_STORE_OK; +} + +void wolfPSA_Store_Close(void* store) +{ + WolfpsaNsStore* ctx = (WolfpsaNsStore*)store; + + if (ctx != NULL) { + if (ctx->buf != NULL) { + wc_ForceZero(ctx->buf, ctx->len); + XFREE(ctx->buf, NULL, DYNAMIC_TYPE_TMP_BUFFER); + } + XMEMSET(ctx, 0, sizeof(*ctx)); + XFREE(ctx, NULL, DYNAMIC_TYPE_TMP_BUFFER); + } +} + +int wolfPSA_Store_Read(void* store, unsigned char* buffer, int len) +{ + WolfpsaNsStore* ctx = (WolfpsaNsStore*)store; + psa_status_t st; + size_t got = 0; + + if (ctx == NULL || ctx->write || buffer == NULL || len < 0) { + return WOLFPSA_STORE_IO_ERROR; + } + if (len == 0) { + return 0; + } + + st = psa_its_get(ctx->uid, ctx->off, (size_t)len, buffer, &got); + if (st != PSA_SUCCESS) { + return WOLFPSA_STORE_IO_ERROR; + } + ctx->off += got; + return (int)got; +} + +int wolfPSA_Store_Write(void* store, unsigned char* buffer, int len) +{ + WolfpsaNsStore* ctx = (WolfpsaNsStore*)store; + unsigned char* grown; + psa_status_t st; + + if (ctx == NULL || ctx->write == 0 || buffer == NULL || len < 0) { + return WOLFPSA_STORE_IO_ERROR; + } + + if (len > 0) { + grown = (unsigned char*)XMALLOC(ctx->len + (size_t)len, NULL, + DYNAMIC_TYPE_TMP_BUFFER); + if (grown == NULL) { + return WOLFPSA_STORE_IO_ERROR; + } + if (ctx->buf != NULL) { + XMEMCPY(grown, ctx->buf, ctx->len); + wc_ForceZero(ctx->buf, ctx->len); + XFREE(ctx->buf, NULL, DYNAMIC_TYPE_TMP_BUFFER); + } + XMEMCPY(grown + ctx->len, buffer, (size_t)len); + ctx->buf = grown; + ctx->len += (size_t)len; + } + + if (ctx->len == 0) { + return len; + } + + st = psa_its_set(ctx->uid, ctx->len, ctx->buf, 0); + if (st != PSA_SUCCESS) { + return WOLFPSA_STORE_IO_ERROR; + } + return len; +} diff --git a/tests/firmware/aarch64-ns-smoke/crypto/user_settings.h b/tests/firmware/aarch64-ns-smoke/crypto/user_settings.h new file mode 100644 index 00000000..07c69616 --- /dev/null +++ b/tests/firmware/aarch64-ns-smoke/crypto/user_settings.h @@ -0,0 +1,102 @@ +/* user_settings.h + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfTrust. + * + * wolfTrust 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. + * + * wolfTrust 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, see . + */ + +/* wolfCrypt for the bare-metal Normal-world conformance guest: the algorithm + * set wolfPSA needs for the Arm dev_apis crypto and attestation suites. */ + +#ifndef WT_NS_GUEST_USER_SETTINGS_H +#define WT_NS_GUEST_USER_SETTINGS_H + +#define WOLFCRYPT_ONLY +#define SINGLE_THREADED +#define NO_FILESYSTEM +#define NO_WOLFSSL_DIR +#define WOLFSSL_USER_IO +#define NO_WRITEV +#define NO_ASN_TIME +#define NO_ERROR_STRINGS +#define WOLFSSL_IGNORE_FILE_WARN +#define SIZEOF_LONG_LONG 8 +#define WOLFSSL_PSA_ENGINE +#define WOLFPSA_CUSTOM_STORE + +/* C-only 64-bit SP math: the guest builds with -mgeneral-regs-only. */ +#define WOLFSSL_SP_MATH_ALL +#define WOLFSSL_HAVE_SP_RSA +#define WOLFSSL_HAVE_SP_ECC +#define WOLFSSL_SP_384 +#define HAVE_SP_ECC +#define SP_WORD_SIZE 64 +#define HAVE___UINT128_T 1 +#define WOLFSSL_SP_NO_DYN_STACK + +#define HAVE_HASHDRBG +#define CUSTOM_RAND_GENERATE_SEED wt_ns_generate_seed +int wt_ns_generate_seed(unsigned char* output, unsigned int sz); + +#define RSA_MIN_SIZE 1024 +#define WOLFSSL_KEY_GEN +#define TFM_TIMING_RESISTANT +#define ECC_TIMING_RESISTANT +#define WC_RSA_BLINDING +#define WC_RSA_PSS +#define WOLFSSL_PSS_SALT_LEN_DISCOVER +#define WOLFSSL_RSA_OAEP + +#define HAVE_ECC +#define HAVE_ECC384 +#define HAVE_ECC_KEY_EXPORT +#define HAVE_ECC_KEY_IMPORT +#define WOLFSSL_ECDSA_DETERMINISTIC_K + +#define WOLFSSL_HAVE_PRF +#define HAVE_HKDF +#define HAVE_PBKDF2 +#define WOLFSSL_MD5 +#define WOLFSSL_RIPEMD +#define WOLFSSL_SHA224 +#define WOLFSSL_SHA256 +#define WOLFSSL_SHA384 +#define WOLFSSL_SHA512 +#define WOLFSSL_SHA3 +#undef NO_MD5 +#undef NO_DES3 +#define WOLFSSL_DES3 +#define WOLFSSL_DES_ECB + +#define HAVE_AESGCM +#define GCM_SMALL +#define HAVE_AESCCM +#define HAVE_AES_ECB +#define WOLFSSL_AES_COUNTER +#define WOLFSSL_AES_CFB +#define WOLFSSL_AES_OFB +#define WOLFSSL_AES_DIRECT +#define WOLFSSL_CMAC +#define HAVE_CHACHA +#define HAVE_POLY1305 + +#define NO_DSA +#define NO_RC4 +#define NO_MD4 +#define NO_DH +#define NO_PKCS12 + +#endif /* WT_NS_GUEST_USER_SETTINGS_H */ diff --git a/tests/firmware/aarch64-ns-smoke/ns.S b/tests/firmware/aarch64-ns-smoke/ns.S new file mode 100644 index 00000000..c1325160 --- /dev/null +++ b/tests/firmware/aarch64-ns-smoke/ns.S @@ -0,0 +1,99 @@ +/* ns.S + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfTrust. + * + * wolfTrust 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. + * + * wolfTrust 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, see . + */ + +/* Normal-world payload entry: the SPMD ERETs here at NS-EL1. Set the stack, + * copy .data from its pristine load image, clear .bss, run ns_main, then exit + * through semihosting so QEMU reports the run's status. */ + + .section .text.entry, "ax" + .globl _start +_start: +#if defined(WT_NS_ENABLE_FP) && (WT_NS_ENABLE_FP == 1) + mov x0, #(3 << 20) /* CPACR_EL1.FPEN: no FP trap at EL1/EL0 */ + msr CPACR_EL1, x0 + isb +#endif + ldr x0, =__stack_top + mov sp, x0 + ldr x0, =__data_load + ldr x1, =__data_start + ldr x2, =__data_end +5: cmp x1, x2 + b.hs 6f + ldr x3, [x0], #8 + str x3, [x1], #8 + b 5b +6: ldr x0, =__bss_start + ldr x1, =__bss_end +1: cmp x0, x1 + b.hs 2f + str xzr, [x0], #8 + b 1b +2: bl ns_main + mov x0, #0 + b ns_exit + +/* Semihosting SYS_EXIT: x0 = exit code, reported through QEMU's exit status. */ + .globl ns_exit +ns_exit: + ldr x1, =g_exit_block + ldr x2, =0x20026 + str x2, [x1] + str x0, [x1, #8] + mov x0, #0x18 + hlt #0xf000 +3: wfi + b 3b + +#if defined(WT_NS_GUEST_SECRAM) || defined(WT_NS_GUEST_CONF) +/* Normal-world EL1 exception vectors: a Secure-RAM read (secramneg) or one of + * the conformance suite's by-design client aborts lands here, so the guest + * catches its own abort and answers it instead of hanging on an unset vector. + * Every entry funnels to one handler with ESR, FAR, ELR, and its slot. */ + .section .text + .balign 0x800 + .globl _ns_vectbl +_ns_vectbl: + .irp slot, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 + .balign 0x80 + mov x3, #\slot + b _ns_abort + .endr +_ns_abort: + mrs x0, esr_el1 + mrs x1, far_el1 + mrs x2, elr_el1 + bl ns_abort_report +4: wfi + b 4b + +#if defined(WT_NS_GUEST_SECRAM) +/* The secramneg probe's one load, so the handler can tell its abort apart. */ + .globl ns_secram_load +ns_secram_load: + ldr w0, [x0] + ret +#endif +#endif + + .bss + .balign 16 +g_exit_block: + .skip 16 diff --git a/tests/firmware/aarch64-ns-smoke/ns.c b/tests/firmware/aarch64-ns-smoke/ns.c new file mode 100644 index 00000000..19ea4975 --- /dev/null +++ b/tests/firmware/aarch64-ns-smoke/ns.c @@ -0,0 +1,2219 @@ +/* ns.c + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfTrust. + * + * wolfTrust 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. + * + * wolfTrust 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, see . + */ + +/* A minimal Normal-world payload for the ns-smoke scenario: print the exception + * level on the NS console, negotiate FF-A with an SMC to the SPMD, and report + * the version. It proves the world switch and the SPMD NS physical instance. */ + +#include "wolftrust/arch/aarch64/ffa_abi.h" +#include "wolftrust/arch/aarch64/psci.h" + +#if defined(WT_NS_GUEST_PSA) +#include "psa/client.h" +#include "psa/error.h" +#include "psa_manifest/sid.h" +#if defined(WT_NS_ENGINE_NATIVE) +#include "wolftrust/crypto_native_client.h" +#else +#include "wolfhsm/wh_client.h" +#include "wolfhsm/wh_error.h" +#if defined(WT_NS_HSM_ATTACK) +#include "wolfhsm/wh_message.h" +#include "wolfhsm/wh_message_nvm.h" +#endif +#include "wolftrust/hsm_psa_transport.h" +#endif +#ifndef WT_NS_GUEST_ID +#define WT_NS_GUEST_ID 0 +#endif +#endif + +#include + +#define UART_DR 0x00u +#define UART_FR 0x18u +#define UART_FR_TXFF (1u << 5) + +static volatile uint32_t* uart_reg(uint32_t offset) +{ + return (volatile uint32_t*)(uintptr_t)(WT_NS_UART + offset); +} + +static void put_char(char c) +{ + while ((*uart_reg(UART_FR) & UART_FR_TXFF) != 0u) { + } + *uart_reg(UART_DR) = (uint32_t)(uint8_t)c; +} + +static void put_str(const char* s) +{ + while (*s != '\0') { + put_char(*s); + s++; + } +} + +static void put_hex(uint32_t value) +{ + static const char digits[] = "0123456789abcdef"; + char buf[9]; + int i = 8; + + buf[i] = '\0'; + do { + i--; + buf[i] = digits[value & 0xFu]; + value >>= 4; + } while (value != 0u && i > 0); + put_str(&buf[i]); +} + +static void put_dec(uint32_t value) +{ + char buf[11]; + int i = 10; + + buf[i] = '\0'; + do { + i--; + buf[i] = (char)('0' + (char)(value % 10u)); + value /= 10u; + } while (value != 0u && i > 0); + put_str(&buf[i]); +} + +/* One FF-A SMC to the SPMD at the NS physical instance: in0/in1 in x0/x1, the + * reply x0-x3 written back to out[0..3]. */ +static void ffa_smc(uint64_t in0, uint64_t in1, uint64_t* out) +{ + register uint64_t r0 __asm__("x0") = in0; + register uint64_t r1 __asm__("x1") = in1; + register uint64_t r2 __asm__("x2") = 0; + register uint64_t r3 __asm__("x3") = 0; + + __asm__ volatile("smc #0" + : "+r"(r0), "+r"(r1), "+r"(r2), "+r"(r3) + : + : "x4", "x5", "x6", "x7", "x8", "x9", "x10", "x11", "x12", + "x13", "x14", "x15", "x16", "x17", "memory"); + out[0] = r0; + out[1] = r1; + out[2] = r2; + out[3] = r3; +} + +/* FFA_PARTITION_INFO_GET with the count-only flag (Nil UUID lists all): the SPMD + * forwards it to the SPMC, which replies with the partition count in x2. Returns + * the count, or 0 on error. Passes the flag in x5, so it cannot use ffa_smc. */ +static uint32_t partition_count(void) +{ + register uint64_t r0 __asm__("x0") = WT_FFA_PARTITION_INFO_GET; + register uint64_t r1 __asm__("x1") = 0; + register uint64_t r2 __asm__("x2") = 0; + register uint64_t r3 __asm__("x3") = 0; + register uint64_t r4 __asm__("x4") = 0; + register uint64_t r5 __asm__("x5") = WT_FFA_PARTINFO_FLAG_COUNT; + + __asm__ volatile("smc #0" + : "+r"(r0), "+r"(r1), "+r"(r2), "+r"(r3), "+r"(r4), "+r"(r5) + : + : "x6", "x7", "x8", "x9", "x10", "x11", "x12", "x13", "x14", + "x15", "x16", "x17", "memory"); + if ((uint32_t)r0 != WT_FFA_SUCCESS32) { + return 0u; + } + return (uint32_t)r2; +} + +/* One SMC with x0-x4 in and x0-x4 back, for the fragment exchange, the + * SMC32 register probe, and the notification probe. */ +static void smc5(uint64_t* x) +{ + register uint64_t r0 __asm__("x0") = x[0]; + register uint64_t r1 __asm__("x1") = x[1]; + register uint64_t r2 __asm__("x2") = x[2]; + register uint64_t r3 __asm__("x3") = x[3]; + register uint64_t r4 __asm__("x4") = x[4]; + + __asm__ volatile("smc #0" + : "+r"(r0), "+r"(r1), "+r"(r2), "+r"(r3), "+r"(r4) + : + : "x5", "x6", "x7", "x8", "x9", "x10", "x11", "x12", "x13", + "x14", "x15", "x16", "x17", "memory"); + x[0] = r0; + x[1] = r1; + x[2] = r2; + x[3] = r3; + x[4] = r4; +} + +/* A PSA partition takes no notifications (discovery leaves its property bit 3 + * clear), so a SET naming the first one the receiver is DENIED (DEN0077A + * Table 16.20), not refused as an unknown id. */ +static void notif_psa_denied(void) +{ + uint64_t x[5]; + + x[0] = WT_FFA_NOTIFICATION_SET; + x[1] = WT_FFA_ID_SP_FIRST; + x[2] = 0u; + x[3] = 1u; + x[4] = 0u; + smc5(x); + if (((uint32_t)x[0] == WT_FFA_ERROR) && + ((int32_t)(uint32_t)x[2] == WT_FFA_DENIED)) { + put_str("[NS] notif set to a PSA partition denied\r\n"); + } + else { + put_str("[NS] notif BAD x0=0x"); + put_hex((uint32_t)x[0]); + put_str(" w2=0x"); + put_hex((uint32_t)x[2]); + put_str("\r\n"); + } +} + +#if defined(WT_NS_GUEST_ECHO) +/* Send an FF-A direct request to the Secure echo partition and check it + * complements the payload (7.4): proves the guest->SP->guest message path + * relayed through the SPMD and the SPMC. */ +static void guest_direct(void) +{ + register uint64_t r0 __asm__("x0") = WT_FFA_MSG_SEND_DIRECT_REQ32; + register uint64_t r1 __asm__("x1") = + ((uint64_t)WT_FFA_ID_NS_PRIMARY << 16) | WT_FFA_ID_ECHO; + register uint64_t r2 __asm__("x2") = 0; + register uint64_t r3 __asm__("x3") = WT_FFA_TEST_PAYLOAD; + register uint64_t r4 __asm__("x4") = 0; + register uint64_t r5 __asm__("x5") = 0; + register uint64_t r6 __asm__("x6") = 0; + register uint64_t r7 __asm__("x7") = 0; + + __asm__ volatile("smc #0" + : "+r"(r0), "+r"(r1), "+r"(r2), "+r"(r3), "+r"(r4), + "+r"(r5), "+r"(r6), "+r"(r7) + : + : "x8", "x9", "x10", "x11", "x12", "x13", "x14", + "x15", "x16", "x17", "memory"); + if (((uint32_t)r0 == WT_FFA_MSG_SEND_DIRECT_RESP32) && + ((uint32_t)r3 == (uint32_t)~WT_FFA_TEST_PAYLOAD)) { + put_str("[NS] direct resp ok x3=0x"); + } + else { + put_str("[NS] direct resp BAD x3=0x"); + } + put_hex((uint32_t)r3); + put_str("\r\n"); +} + +/* One SMC with x0-x17 in and x0-x17 back through x[0..17]. */ +static void smc18(uint64_t* x) +{ + register uint64_t* p __asm__("x19") = x; + + __asm__ volatile("ldp x0, x1, [%0, #0]\n\t" + "ldp x2, x3, [%0, #16]\n\t" + "ldp x4, x5, [%0, #32]\n\t" + "ldp x6, x7, [%0, #48]\n\t" + "ldp x8, x9, [%0, #64]\n\t" + "ldp x10, x11, [%0, #80]\n\t" + "ldp x12, x13, [%0, #96]\n\t" + "ldp x14, x15, [%0, #112]\n\t" + "ldp x16, x17, [%0, #128]\n\t" + "smc #0\n\t" + "stp x0, x1, [%0, #0]\n\t" + "stp x2, x3, [%0, #16]\n\t" + "stp x4, x5, [%0, #32]\n\t" + "stp x6, x7, [%0, #48]\n\t" + "stp x8, x9, [%0, #64]\n\t" + "stp x10, x11, [%0, #80]\n\t" + "stp x12, x13, [%0, #96]\n\t" + "stp x14, x15, [%0, #112]\n\t" + "stp x16, x17, [%0, #128]" + : + : "r"(p) + : "x0", "x1", "x2", "x3", "x4", "x5", "x6", "x7", "x8", + "x9", "x10", "x11", "x12", "x13", "x14", "x15", "x16", + "x17", "memory"); +} + +/* The echo partition takes no REQ2, so the SPMC refuses one with FFA_ERROR; + * that 8-register reply to an SMC64 call returns x8-x17 zero (11.2), not the + * request's own payload. */ +static void guest_req2_refused(void) +{ + uint64_t x[18]; + uint64_t ext = 0u; + unsigned int i; + + for (i = 0u; i < 18u; i++) { + x[i] = 0x0101010101010101ull * (uint64_t)(i + 1u); + } + x[0] = WT_FFA_MSG_SEND_DIRECT_REQ2; + x[1] = ((uint64_t)WT_FFA_ID_NS_PRIMARY << 16) | WT_FFA_ID_ECHO; + x[2] = 0u; + x[3] = 0u; + smc18(x); + for (i = 8u; i < 18u; i++) { + ext |= x[i]; + } + if (((uint32_t)x[0] == WT_FFA_ERROR) && (ext == 0u)) { + put_str("[NS] req2 refused x8-x17 zero w2=0x"); + } + else { + put_str("[NS] req2 refused BAD x0=0x"); + put_hex((uint32_t)x[0]); + put_str(" x8=0x"); + put_hex((uint32_t)x[8]); + put_str(" w2=0x"); + } + put_hex((uint32_t)x[2]); + put_str("\r\n"); +} +#endif + +#if defined(WT_NS_GUEST_PSA) +/* Reach the real Secure services over the operating-system-neutral PSA client + * (src/client/psa_ffm_client.c, the same client an Armv8-M guest links) whose + * WolfTrust_FFM_* entry points are the FF-A binding here: read the framework + * and service versions, connect to SERVICE_HSM, prove an unknown service is + * refused, close, then run the wolfHSM client over the same SPM-mediated + * transport every Armv8-M guest uses (src/client/hsm_psa_transport.c) for one + * echo through the relay partition and the wolfHSM server - the Secure side + * only ever sees SPM-mediated copies of the guest's vectors. */ +#if defined(WT_NS_ENGINE_NATIVE) +/* Native engine: two random draws over the native crypto wire, each a + * header in-vector and a data out-vector copied by the SPM. */ +static int guest_native_random(void) +{ + uint8_t a[32]; + uint8_t b[32]; + uint8_t any = 0u; + uint8_t diff = 0u; + psa_status_t st; + uint32_t i; + + for (i = 0u; i < sizeof(a); i++) { + a[i] = 0u; + b[i] = 0u; + } + st = wt_crypto_native_random(a, sizeof(a)); + if (st == PSA_SUCCESS) { + st = wt_crypto_native_random(b, sizeof(b)); + } + put_str("[NS] native random st=0x"); + put_hex((uint32_t)st); + put_str("\r\n"); + if (st != PSA_SUCCESS) { + return 0; + } + for (i = 0u; i < sizeof(a); i++) { + any |= a[i]; + diff |= (uint8_t)(a[i] ^ b[i]); + } + return (any != 0u) && (diff != 0u); +} +#else +static const uint8_t g_hsm_echo_in[] = "wolfTrust FF-A SERVICE_HSM relay echo"; +static wt_hsm_psa_transport_ctx_t g_hsm_tx; +static const wt_hsm_psa_transport_cfg_t g_hsm_tx_cfg = { + .sid = SERVICE_HSM_SID, + .version = 1u +}; +static whCommClientConfig g_hsm_comm_cfg; +static whClientConfig g_hsm_client_cfg; +static whClientContext g_hsm_client; + +static int guest_hsm_echo(void) +{ + uint8_t echo_out[sizeof(g_hsm_echo_in)]; + uint16_t echo_len = 0u; + uint16_t want = (uint16_t)(sizeof(g_hsm_echo_in) - 1u); + uint16_t i; + int ok = 0; + int rc; + + g_hsm_comm_cfg.transport_cb = &wt_hsm_psa_transport_cb; + g_hsm_comm_cfg.transport_context = &g_hsm_tx; + g_hsm_comm_cfg.transport_config = &g_hsm_tx_cfg; + /* wolfHSM refuses client id 0; the Secure side binds the real identity */ + g_hsm_comm_cfg.client_id = 1u; + g_hsm_client_cfg.comm = &g_hsm_comm_cfg; + + rc = wh_Client_Init(&g_hsm_client, &g_hsm_client_cfg); + put_str("[NS] hsm client init rc=0x"); + put_hex((uint32_t)rc); + put_str("\r\n"); + if (rc != WH_ERROR_OK) { + return 0; + } + + for (i = 0u; i < sizeof(echo_out); i++) { + echo_out[i] = 0u; + } + rc = wh_Client_Echo(&g_hsm_client, want, g_hsm_echo_in, &echo_len, + echo_out); + put_str("[NS] hsm echo rc=0x"); + put_hex((uint32_t)rc); + put_str(" len="); + put_dec(echo_len); + put_str("\r\n"); + if ((rc == WH_ERROR_OK) && (echo_len == want)) { + ok = 1; + for (i = 0u; i < want; i++) { + if (echo_out[i] != g_hsm_echo_in[i]) { + ok = 0; + } + } + } + (void)wh_Client_Cleanup(&g_hsm_client); + return ok; +} +#endif + +#if defined(WT_NS_HSM_ATTACK) && !defined(WT_NS_ENGINE_NATIVE) +/* Compromised-guest probe: a COMM_INIT forging the attestation-reserved client + * id must not reach the committed IAK (key 0xF0), a raw NVM-group request must + * never reach the server, and the guest's own relay namespace still works. */ +#define WT_HSM_ATTACK_IAK_KEY_ID 0xF0u +#define WT_HSM_ATTACK_ROLLBACK_ID 0x0122u /* WT_HSM_ROLLBACK_TABLE_ID */ + +static void guest_hsm_attack(void) +{ + uint8_t label[WH_NVM_LABEL_LEN]; + uint8_t key[64]; + uint8_t nvmbuf[16]; + uint16_t keySz = (uint16_t)sizeof(key); + uint16_t rGroup = 0u; + uint16_t rAction = 0u; + uint16_t rSize = (uint16_t)sizeof(nvmbuf); + uint32_t outClientId = 0u; + uint32_t outServerId = 0u; + int guard = 1000; + unsigned int i; + int rc; + + g_hsm_comm_cfg.transport_cb = &wt_hsm_psa_transport_cb; + g_hsm_comm_cfg.transport_context = &g_hsm_tx; + g_hsm_comm_cfg.transport_config = &g_hsm_tx_cfg; + g_hsm_comm_cfg.client_id = (uint8_t)WH_CLIENT_ID_MAX; + g_hsm_client_cfg.comm = &g_hsm_comm_cfg; + rc = wh_Client_Init(&g_hsm_client, &g_hsm_client_cfg); + if (rc == WH_ERROR_OK) { + rc = wh_Client_CommInit(&g_hsm_client, &outClientId, &outServerId); + } + put_str("[NS] hsmattack forged COMM_INIT client_id="); + put_dec(outClientId); + put_str(" rc=0x"); + put_hex((uint32_t)rc); + put_str("\r\n"); + + rc = wh_Client_KeyExport(&g_hsm_client, WT_HSM_ATTACK_IAK_KEY_ID, label, + (uint16_t)sizeof(label), key, &keySz); + if (rc != WH_ERROR_OK) { + put_str("[NS] hsmattack IAK read refused rc=0x"); + put_hex((uint32_t)rc); + put_str("\r\n"); + } + else { + put_str("[NS] hsmattack IAK read SUCCEEDED\r\n"); + } + + for (i = 0u; i < sizeof(nvmbuf); i++) { + nvmbuf[i] = 0u; + } + nvmbuf[0] = (uint8_t)(WT_HSM_ATTACK_ROLLBACK_ID & 0xFFu); + nvmbuf[1] = (uint8_t)((WT_HSM_ATTACK_ROLLBACK_ID >> 8) & 0xFFu); + rc = wh_Client_SendRequest(&g_hsm_client, WH_MESSAGE_GROUP_NVM, + WH_MESSAGE_NVM_ACTION_READ, + (uint16_t)sizeof(nvmbuf), nvmbuf); + if (rc == WH_ERROR_OK) { + do { + rc = wh_Client_RecvResponse(&g_hsm_client, &rGroup, &rAction, + &rSize, (uint16_t)sizeof(nvmbuf), + nvmbuf); + } while ((rc == WH_ERROR_NOTREADY) && (guard-- > 0)); + } + if (rc != WH_ERROR_OK) { + put_str("[NS] hsmattack rollback NVM group refused rc=0x"); + put_hex((uint32_t)rc); + put_str("\r\n"); + } + else { + put_str("[NS] hsmattack rollback NVM group SUCCEEDED\r\n"); + } + (void)wh_Client_Cleanup(&g_hsm_client); + + if (guest_hsm_echo() != 0) { + put_str("[NS] hsmattack own-namespace relay still works\r\n"); + } + else { + put_str("[NS] hsmattack own-namespace relay BROKEN\r\n"); + } +} +#endif + +#if defined(WT_NS_VAULT_SECURED) +/* SERVICE_ATTEST's IAK public-key query (WT_ATTEST_OP_PUBLIC_KEY). */ +#define WT_NS_ATTEST_OP_PUBLIC_KEY 2 + +/* vaultrecoversec: the boot met a foreign vault under a locked lifecycle. Had + * it reformatted the vault, a fresh IAK would answer this query; it must fail + * closed instead. */ +static void guest_vault_secured(void) +{ + uint8_t key[65]; + psa_outvec out; + psa_handle_t handle; + psa_status_t st; + + out.base = key; + out.len = sizeof(key); + handle = psa_connect(SERVICE_ATTEST_SID, SERVICE_ATTEST_VERSION); + if (!PSA_HANDLE_IS_VALID(handle)) { + put_str("[NS] vault attest connect FAIL\r\n"); + return; + } + st = psa_call(handle, WT_NS_ATTEST_OP_PUBLIC_KEY, NULL, 0u, &out, 1u); + psa_close(handle); + if (st == PSA_SUCCESS) { + put_str("[NS] vault attest key ISSUED\r\n"); + } + else { + put_str("[NS] vault attest refused st=0x"); + put_hex((uint32_t)st); + put_str("\r\n"); + } +} +#endif + +#endif + +#if defined(WT_NS_GUEST_PSA) || defined(WT_NS_GUEST_ECHO) +/* SGI 15 in the boot core's GICv3 redistributor SGI frame. */ +#define NS_IRQ_PROBE_SGI 15u +#define NS_GICR_SGI_BASE (WT_NS_GICR + 0x10000u) +#define NS_GICR_ISENABLER0 (NS_GICR_SGI_BASE + 0x0100u) +#define NS_GICR_ICENABLER0 (NS_GICR_SGI_BASE + 0x0180u) +#define NS_GICR_ISPENDR0 (NS_GICR_SGI_BASE + 0x0200u) +#define NS_GICR_ICPENDR0 (NS_GICR_SGI_BASE + 0x0280u) +#define NS_GICR_IPRIORITYR (NS_GICR_SGI_BASE + 0x0400u) + +static volatile uint32_t* ns_gicr(uint32_t addr) +{ + return (volatile uint32_t*)(uintptr_t)addr; +} + +/* Leave a Normal-world interrupt pending and signaled to this core, masked + * only by PSTATE.I here; returns 0 without a GICv3 system-register interface. */ +static int ns_irq_hold(void) +{ + uint64_t pfr0; + uint64_t sre; + + __asm__ volatile("mrs %0, id_aa64pfr0_el1" : "=r"(pfr0)); + if (((pfr0 >> 24) & 0xFu) == 0u) { + return 0; + } + __asm__ volatile("mrs %0, icc_sre_el1" : "=r"(sre)); + if ((sre & 1u) == 0u) { + return 0; + } + *(volatile uint8_t*)(uintptr_t)(NS_GICR_IPRIORITYR + NS_IRQ_PROBE_SGI) = + 0x40u; + *ns_gicr(NS_GICR_ISENABLER0) = 1u << NS_IRQ_PROBE_SGI; + __asm__ volatile("msr icc_pmr_el1, %0\n\t" + "msr icc_igrpen1_el1, %1\n\t" + "isb" : : "r"((uint64_t)0xFFu), "r"((uint64_t)1u)); + *ns_gicr(NS_GICR_ISPENDR0) = 1u << NS_IRQ_PROBE_SGI; + __asm__ volatile("dsb sy\n\tisb" ::: "memory"); + return 1; +} + +/* Non-zero if the interrupt was still pending; it is then withdrawn. */ +static int ns_irq_release(void) +{ + uint32_t pending = (*ns_gicr(NS_GICR_ISPENDR0) >> NS_IRQ_PROBE_SGI) & 1u; + + *ns_gicr(NS_GICR_ICPENDR0) = 1u << NS_IRQ_PROBE_SGI; + *ns_gicr(NS_GICR_ICENABLER0) = 1u << NS_IRQ_PROBE_SGI; + __asm__ volatile("msr icc_igrpen1_el1, xzr\n\tisb" ::: "memory"); + return (int)pending; +} +#endif + +#if defined(WT_NS_GUEST_ECHO) +/* Ch.9: a Normal-world interrupt preempts the echo partition mid-request, the + * guest is told FFA_INTERRUPT with the echo's id, and FFA_RUN with that id + * resumes it to its direct response. */ +static void guest_direct_preempted(void) +{ + uint64_t x[18]; + uint32_t target = 0u; + unsigned int i; + int ok = 0; + + if (ns_irq_hold() == 0) { + put_str("[NS] direct preempt skipped: no GICv3\r\n"); + return; + } + for (i = 0u; i < 18u; i++) { + x[i] = 0u; + } + x[0] = WT_FFA_MSG_SEND_DIRECT_REQ32; + x[1] = ((uint64_t)WT_FFA_ID_NS_PRIMARY << 16) | WT_FFA_ID_ECHO; + x[3] = WT_FFA_TEST_PAYLOAD; + smc18(x); + (void)ns_irq_release(); + if (((uint32_t)x[0] == WT_FFA_INTERRUPT) && + (((uint32_t)x[1] >> 16) == WT_FFA_ID_ECHO)) { + target = (uint32_t)x[1]; + for (i = 0u; i < 18u; i++) { + x[i] = 0u; + } + x[0] = WT_FFA_RUN; + x[1] = target; + smc18(x); + ok = ((uint32_t)x[0] == WT_FFA_MSG_SEND_DIRECT_RESP32) && + ((uint32_t)x[3] == (uint32_t)~WT_FFA_TEST_PAYLOAD); + } + put_str(ok ? "[NS] direct preempt resumed ok w1=0x" + : "[NS] direct preempt BAD w1=0x"); + put_hex(target); + put_str(" x0=0x"); + put_hex((uint32_t)x[0]); + put_str("\r\n"); +} + +/* FFA_RUN to the echo while it waits gives it cycles it waits out. */ +static void guest_idle_echo_run(void) +{ + uint64_t x[18]; + unsigned int i; + + for (i = 0u; i < 18u; i++) { + x[i] = 0u; + } + x[0] = WT_FFA_RUN; + x[1] = (uint64_t)WT_FFA_ID_ECHO << 16; + smc18(x); + put_str(((uint32_t)x[0] == WT_FFA_MSG_WAIT) ? "[NS] idle echo run waits x0=0x" + : "[NS] idle echo run BAD x0=0x"); + put_hex((uint32_t)x[0]); + put_str("\r\n"); +} +#endif + +#if defined(WT_NS_GUEST_PSA) + +/* 9.3.1.3: a data-carrying call made with a Normal-world interrupt pending + * completes, and hands that interrupt back still pending. */ +static void guest_psa_ns_irq(void) +{ + int ok; + + if (ns_irq_hold() == 0) { + put_str("[NS] psa call with an ns irq pending skipped: no GICv3\r\n"); + return; + } +#if defined(WT_NS_ENGINE_NATIVE) + ok = guest_native_random(); +#else + ok = guest_hsm_echo(); +#endif + if ((ns_irq_release() != 0) && (ok != 0)) { + put_str("[NS] psa call with an ns irq pending ok\r\n"); + } + else { + put_str("[NS] psa call with an ns irq pending BAD\r\n"); + } +} + +static void guest_psa(void) +{ + uint32_t fw; + uint32_t ver; + psa_handle_t handle; + psa_handle_t refused; + + fw = psa_framework_version(); + put_str("[NS] psa framework 0x"); + put_hex(fw); + put_str("\r\n"); + + ver = psa_version(SERVICE_HSM_SID); + put_str("[NS] psa version v="); + put_dec(ver); + put_str("\r\n"); + + handle = psa_connect(SERVICE_HSM_SID, 1u); + if (PSA_HANDLE_IS_VALID(handle)) { + put_str("[NS] psa connect ok handle="); + put_dec((uint32_t)handle); + put_str("\r\n"); + } + else { + put_str("[NS] psa connect FAIL\r\n"); + } + + refused = psa_connect(0x9999u, 1u); + if (!PSA_HANDLE_IS_VALID(refused)) { + put_str("[NS] psa connect refused\r\n"); + } + else { + put_str("[NS] psa connect NOT refused\r\n"); + psa_close(refused); + } + + if (PSA_HANDLE_IS_VALID(handle)) { + psa_close(handle); + put_str("[NS] psa close ok\r\n"); + } + +#if defined(WT_NS_ENGINE_NATIVE) + if (guest_native_random() != 0) { + put_str("[NS] psa call ok\r\n"); + put_str("[NS] native random ok\r\n"); + } +#else + if (guest_hsm_echo() != 0) { + put_str("[NS] psa call ok\r\n"); + put_str("[NS] hsm echo ok\r\n"); + } +#endif + else { + put_str("[NS] psa call BAD\r\n"); + } + guest_psa_ns_irq(); +#if defined(WT_NS_HSM_ATTACK) && !defined(WT_NS_ENGINE_NATIVE) + guest_hsm_attack(); +#endif +#if defined(WT_NS_VAULT_SECURED) + guest_vault_secured(); +#endif + + put_str("[NS] guest"); + put_dec((uint32_t)WT_NS_GUEST_ID); + put_str(" ok\r\n"); +} +#endif + +#if defined(WT_NS_GUEST_STORAGE) +#include "psa/client.h" +#include "psa/error.h" +#include "psa/storage_common.h" +#include "psa/internal_trusted_storage.h" +#include "psa_manifest/sid.h" + +/* An ITS round trip is the first Normal-world request whose service calls a + * second partition: SERVICE_ITS fronts the VAULT partition, so every op below + * crosses the SVC gate SP-to-SP and back before the reply reaches the guest. */ +static int bytes_equal(const uint8_t* a, const uint8_t* b, size_t n) +{ + size_t i; + + for (i = 0u; i < n; i++) { + if (a[i] != b[i]) { + return 0; + } + } + return 1; +} + +static void guest_storage(void) +{ + static const uint8_t its_in[4] = { 0x11u, 0x22u, 0x33u, 0x44u }; + uint8_t its_out[4] = { 0u, 0u, 0u, 0u }; + struct psa_storage_info_t info; + psa_storage_uid_t uid = 0x5A5Au; + size_t got = 0u; + psa_status_t st_info; + psa_status_t st_set; + psa_status_t st_get; + + st_info = psa_its_get_info(uid, &info); + put_str("[NS] its info st=0x"); + put_hex((uint32_t)st_info); + put_str("\r\n"); + st_set = psa_its_set(uid, sizeof(its_in), its_in, PSA_STORAGE_FLAG_NONE); + put_str("[NS] its set st=0x"); + put_hex((uint32_t)st_set); + put_str("\r\n"); + st_get = psa_its_get(uid, 0u, sizeof(its_out), its_out, &got); + put_str("[NS] its get st=0x"); + put_hex((uint32_t)st_get); + put_str(" len="); + put_dec((uint32_t)got); + put_str("\r\n"); + if ((st_info == PSA_ERROR_DOES_NOT_EXIST) && (st_set == PSA_SUCCESS) && + (st_get == PSA_SUCCESS) && (got == sizeof(its_in)) && + bytes_equal(its_out, its_in, sizeof(its_in))) { + put_str("[NS] its ok\r\n"); + } + else { + put_str("[NS] its BAD\r\n"); + } + (void)psa_its_remove(uid); +} +#endif + +#if defined(WT_NS_GUEST_CONF) +extern char _ns_vectbl[]; +extern int32_t val_entry(void); + +#if defined(WT_NS_HEAP) && (WT_NS_HEAP == 1) +#include + +extern void* malloc(size_t n); +extern void* realloc(void* p, size_t n); +extern void free(void* p); + +/* The guest heap (crypto/ns_crypto_port.c) must refuse a request that the + * alignment rounding would wrap into a small one, through realloc too. */ +static void heap_probe(void) +{ + void* p = malloc(64u); + int ok = (p != NULL); + + ok = ok && (malloc(SIZE_MAX) == NULL); + ok = ok && (malloc(SIZE_MAX - 8u) == NULL); + ok = ok && (realloc(p, SIZE_MAX - 8u) == NULL); + free(p); + put_str(ok ? "[NS] heap bound ok\r\n" : "[NS] heap BAD\r\n"); +} +#endif + +void ns_putc(char c) +{ + put_char(c); +} + +/* The suite's Normal-world PROGRAMMER-ERROR checks may abort the client by + * design; answer as the conformance monitor does on Armv8-M, with a system + * reset that val resumes from off its NVM boot flag. */ +void ns_abort_report(uint64_t esr) +{ + uint64_t o[4]; + + put_str("[NS] abort esr=0x"); + put_hex((uint32_t)esr); + put_str(" reset\r\n"); + ffa_smc(WT_PSCI_SYSTEM_RESET, 0u, o); + for (;;) { + __asm__ volatile("wfi"); + } +} + +/* Run the unmodified Arm psa-arch-tests val NSPE against the SPMC: every test + * reaches the SERVER/CLIENT/DRIVER partitions through the routed PSA client. */ +#if defined(CRYPTO) || defined(INITIAL_ATTESTATION) +int32_t psa_crypto_init(void); +#endif + +#if defined(WT_NS_ATTEST_NEG) +#include "psa/error.h" +#include +#include "wolftrust/attestation.h" +#include "attestation_verify.h" + +/* attestneg: the secure attestation service must reject invalid get_token + * requests over the routed FF-A path, and a tampered or misattributed token + * must fail the guest COSE_Sign1 verify. NS-side probe only; the attestation + * service and EAT code are untouched. The guest measurement differs per image, + * so the token's real lifecycle is discovered from a deliberately mismatched + * verify and the measurement check runs in report-only mode. */ +static void guest_attest_neg(void) +{ + uint8_t challenge[PSA_INITIAL_ATTEST_CHALLENGE_SIZE_64 + 1u]; + uint8_t token[640]; + uint8_t publicKey[65]; + size_t tokenSize = 0u; + size_t publicKeySize = 0u; + size_t querySize = 0u; + uint32_t lifecycle = 0u; + psa_status_t status; + int verify; + unsigned int i; + + for (i = 0u; i < sizeof(challenge); i++) { + challenge[i] = (uint8_t)(0xC0u + i); + } + + status = psa_initial_attest_get_token_size(sizeof(challenge), &querySize); + if (status != PSA_ERROR_INVALID_ARGUMENT) { + put_str("[NS] attestneg oversized challenge ACCEPTED st=0x"); + put_hex((uint32_t)status); + put_str("\r\n"); + return; + } + put_str("[NS] attestneg oversized challenge rejected\r\n"); + + status = psa_initial_attest_get_token(challenge, + PSA_INITIAL_ATTEST_CHALLENGE_SIZE_32, token, 0u, &tokenSize); + if (status != PSA_ERROR_INVALID_ARGUMENT) { + put_str("[NS] attestneg zero token buffer ACCEPTED st=0x"); + put_hex((uint32_t)status); + put_str("\r\n"); + return; + } + put_str("[NS] attestneg zero token buffer rejected\r\n"); + + status = psa_initial_attest_get_token(challenge, + PSA_INITIAL_ATTEST_CHALLENGE_SIZE_32, token, sizeof(token), &tokenSize); + if (status != PSA_SUCCESS) { + put_str("[NS] attestneg baseline token FAIL st=0x"); + put_hex((uint32_t)status); + put_str("\r\n"); + return; + } + status = wolftrust_attestation_get_iak_public_key(publicKey, + sizeof(publicKey), &publicKeySize); + if (status != PSA_SUCCESS) { + put_str("[NS] attestneg public key FAIL st=0x"); + put_hex((uint32_t)status); + put_str("\r\n"); + return; + } + + verify = wt_attestation_verify_ex(token, tokenSize, publicKey, + publicKeySize, challenge, PSA_INITIAL_ATTEST_CHALLENGE_SIZE_32, NULL, + 0xEEEEu, &lifecycle, NULL); + if (verify == 0) { + put_str("[NS] attestneg lifecycle mismatch ACCEPTED\r\n"); + return; + } + put_str("[NS] attestneg lifecycle mismatch rejected\r\n"); + + verify = wt_attestation_verify_ex(token, tokenSize, publicKey, + publicKeySize, challenge, PSA_INITIAL_ATTEST_CHALLENGE_SIZE_32, NULL, + lifecycle, &lifecycle, NULL); + if (verify != 0) { + put_str("[NS] attestneg baseline verify FAIL\r\n"); + return; + } + put_str("[NS] attestneg baseline token verified\r\n"); + + token[tokenSize - 1u] ^= 0x01u; + verify = wt_attestation_verify_ex(token, tokenSize, publicKey, + publicKeySize, challenge, PSA_INITIAL_ATTEST_CHALLENGE_SIZE_32, NULL, + lifecycle, &lifecycle, NULL); + token[tokenSize - 1u] ^= 0x01u; + if (verify == 0) { + put_str("[NS] attestneg tampered token ACCEPTED\r\n"); + return; + } + put_str("[NS] attestneg tampered token rejected\r\n"); + + put_str("[NS] attestneg ok\r\n"); +} +#endif + +static void guest_conformance(void) +{ + __asm__ volatile("msr vbar_el1, %0\n\tisb" : : "r"(_ns_vectbl)); +#if defined(CRYPTO) || defined(INITIAL_ATTESTATION) + /* wolfPSA runs in this guest; nothing else brings it up on bare metal. */ + if (psa_crypto_init() != 0) { + put_str("[NS] psa_crypto_init FAIL\r\n"); + } +#endif +#if defined(WT_NS_ATTEST_NEG) + guest_attest_neg(); + return; +#endif +#if defined(WT_NS_HEAP) && (WT_NS_HEAP == 1) + heap_probe(); +#endif + put_str("[NS] conformance val_entry start\r\n"); + (void)val_entry(); + put_str("[NS] conformance val_entry returned\r\n"); +} +#endif + +#if defined(WT_NS_GUEST_MEMNEG) +#include "wolftrust/arch/aarch64/ffa_mem.h" +#include "psa/client.h" +#include "psa/error.h" +#include "psa/internal_trusted_storage.h" +#include "psa_manifest/sid.h" + +/* A page the guest offers to share (its content is irrelevant to descriptor + * validation) and its RX/TX pair, all in the guest's NS window; every + * descriptor is written into the TX buffer. */ +static uint8_t g_memneg_page[4096] __attribute__((aligned(4096))); +static uint8_t g_memneg_desc[4096] __attribute__((aligned(4096))); +static uint8_t g_memneg_rx[4096] __attribute__((aligned(4096))); + +/* FFA_MEM_SHARE naming the descriptor's buffer in x3/w4 (both zero for the TX + * buffer, DEN0140 4.1.1.3): the SPMD forwards it, the SPMC reads and validates + * the descriptor. Returns the FF-A status (x0); w2 and w3 carry the handle on + * success, w2 the error code. */ +static uint32_t mem_share_buf_smc(uint64_t addr, uint64_t pages, uint32_t len, + uint64_t* w2, uint64_t* w3) +{ + register uint64_t r0 __asm__("x0") = WT_FFA_MEM_SHARE32; + register uint64_t r1 __asm__("x1") = len; + register uint64_t r2 __asm__("x2") = len; + register uint64_t r3 __asm__("x3") = addr; + register uint64_t r4 __asm__("x4") = pages; + + __asm__ volatile("smc #0" + : "+r"(r0), "+r"(r1), "+r"(r2), "+r"(r3), "+r"(r4) + : + : "x5", "x6", "x7", "x8", "x9", "x10", "x11", "x12", "x13", + "x14", "x15", "x16", "x17", "memory"); + *w2 = r2; + *w3 = r3; + return (uint32_t)r0; +} + +static uint32_t mem_share_smc(uint32_t len, uint64_t* w2, uint64_t* w3) +{ + return mem_share_buf_smc(0u, 0u, len, w2, w3); +} + +static uint32_t mem_reclaim_smc(uint64_t handle) +{ + register uint64_t r0 __asm__("x0") = WT_FFA_MEM_RECLAIM; + register uint64_t r1 __asm__("x1") = handle & 0xFFFFFFFFu; + register uint64_t r2 __asm__("x2") = handle >> 32; + register uint64_t r3 __asm__("x3") = 0; + + __asm__ volatile("smc #0" + : "+r"(r0), "+r"(r1), "+r"(r2), "+r"(r3) + : + : "x4", "x5", "x6", "x7", "x8", "x9", "x10", "x11", "x12", + "x13", "x14", "x15", "x16", "x17", "memory"); + return (uint32_t)r0; +} + +/* Build a well-formed single-constituent share, lend, or donate of page into + * g_memneg_desc; returns its length or 0. A donate names no access, and a + * lend to one borrower or a donate no memory type. */ +static uint32_t memneg_build_op(const uint8_t* page, wt_ffa_mem_op_t op) +{ + wt_ffa_mem_constituent_t cons; + wt_ffa_mem_build_t in; + size_t len = 0u; + int share = (op == WT_FFA_MEM_OP_SHARE) ? 1 : 0; + + cons.address = (uint64_t)(uintptr_t)page; + cons.page_count = 1u; + in.constituents = &cons; + in.constituent_count = 1u; + in.tag = 0u; + in.handle = 0u; + in.flags = 0u; + in.op = op; + in.sender = WT_FFA_ID_NS_PRIMARY; + in.receiver = WT_FFA_ID_SP_FIRST; + in.attributes = (share != 0) + ? (uint16_t)(WT_FFA_MEM_ATTR_TYPE_NORMAL | + (0x3u << WT_FFA_MEM_ATTR_CACHE_SHIFT) | + WT_FFA_MEM_ATTR_SHARE_INNER) + : 0u; + in.permissions = (op != WT_FFA_MEM_OP_DONATE) + ? (uint8_t)WT_FFA_MEM_PERM_DATA_RW : 0u; + in.access_desc_size = 0u; + in.impdef = NULL; + if (wt_ffa_mem_txn_build(g_memneg_desc, sizeof(g_memneg_desc), &in, + &len) != 0) { + return 0u; + } + return (uint32_t)len; +} + +static uint32_t memneg_build_page(const uint8_t* page) +{ + return memneg_build_op(page, WT_FFA_MEM_OP_SHARE); +} + +static uint32_t memneg_build(void) +{ + return memneg_build_page(g_memneg_page); +} + +/* FFA_MEM_DONATE is offered at the Normal world's instance (DEN0140 Table + * 1.24): a donate of the guest's page is accepted, and reclaimed before any + * partition retrieves it. */ +static int memneg_donate(void) +{ + uint64_t x[5]; + uint64_t handle; + uint32_t len = memneg_build_op(g_memneg_page, WT_FFA_MEM_OP_DONATE); + int ok; + + x[0] = WT_FFA_FEATURES; + x[1] = WT_FFA_MEM_DONATE32; + x[2] = 0u; + x[3] = 0u; + x[4] = 0u; + smc5(x); + ok = (len != 0u) && ((uint32_t)x[0] == WT_FFA_SUCCESS32); + x[0] = WT_FFA_MEM_DONATE32; + x[1] = len; + x[2] = len; + x[3] = 0u; + x[4] = 0u; + smc5(x); + handle = (x[2] & 0xFFFFFFFFu) | ((x[3] & 0xFFFFFFFFu) << 32); + ok = ok && ((uint32_t)x[0] == WT_FFA_SUCCESS32); + return ok && (mem_reclaim_smc(handle) == WT_FFA_SUCCESS32); +} + +/* Send g_memneg_page with op through the TX buffer; *handle gets the handle. + * Returns non-zero on FFA_SUCCESS. */ +static int memneg_send(uint32_t fid, wt_ffa_mem_op_t op, uint64_t* handle) +{ + uint64_t x[5]; + uint32_t len = memneg_build_op(g_memneg_page, op); + + x[0] = fid; + x[1] = len; + x[2] = len; + x[3] = 0u; + x[4] = 0u; + smc5(x); + *handle = (x[2] & 0xFFFFFFFFu) | ((x[3] & 0xFFFFFFFFu) << 32); + return (len != 0u) && ((uint32_t)x[0] == WT_FFA_SUCCESS32); +} + +/* The ITS wire header psa_storage_client.c sends: GET_INFO (op 3) of uid. */ +#define MEMNEG_ITS_GET_INFO 3 +static const uint64_t g_memneg_uid = 0x5A5Bu; + +/* One ITS GET_INFO whose request header is read from g_memneg_page, and one + * ITS get whose data is written to it; each status in st[0] and st[1]. */ +static void memneg_its_calls(psa_status_t* st) +{ + psa_handle_t handle; + psa_invec in_vec; + psa_outvec out_vec; + uint32_t reply[4]; + size_t got = 0u; + uint32_t i; + + for (i = 0u; i < 16u; i++) { + g_memneg_page[i] = 0u; + } + for (i = 0u; i < 8u; i++) { + g_memneg_page[i] = (uint8_t)(g_memneg_uid >> (8u * i)); + } + st[0] = PSA_ERROR_GENERIC_ERROR; + handle = psa_connect(SERVICE_ITS_SID, 1u); + if (handle > 0) { + in_vec.base = g_memneg_page; + in_vec.len = 16u; + out_vec.base = reply; + out_vec.len = sizeof(reply); + st[0] = psa_call(handle, MEMNEG_ITS_GET_INFO, &in_vec, 1u, &out_vec, + 1u); + psa_close(handle); + } + st[1] = psa_its_get(g_memneg_uid, 0u, 16u, &g_memneg_page[64], &got); +} + +/* A PSA call reaches only memory the Normal world still has (DEN0140 Table + * 1.3): a vector in a page it lent or donated is a PROGRAMMER_ERROR before + * any byte is read or written; one in a page it shares, or has reclaimed, is + * served (the object does not exist). */ +static int memneg_psa_owned(void) +{ + static const uint32_t fids[3] = { + WT_FFA_MEM_SHARE32, WT_FFA_MEM_LEND32, WT_FFA_MEM_DONATE32 + }; + static const wt_ffa_mem_op_t ops[3] = { + WT_FFA_MEM_OP_SHARE, WT_FFA_MEM_OP_LEND, WT_FFA_MEM_OP_DONATE + }; + psa_status_t st[2]; + psa_status_t want; + uint64_t handle = 0u; + uint32_t i; + int ok = 1; + + for (i = 0u; i < 3u; i++) { + ok = ok && memneg_send(fids[i], ops[i], &handle); + memneg_its_calls(st); + want = (i == 0u) ? PSA_ERROR_DOES_NOT_EXIST + : PSA_ERROR_PROGRAMMER_ERROR; + ok = ok && (st[0] == want) && (st[1] == want); + ok = ok && (mem_reclaim_smc(handle) == WT_FFA_SUCCESS32); + memneg_its_calls(st); + ok = ok && (st[0] == PSA_ERROR_DOES_NOT_EXIST) && + (st[1] == PSA_ERROR_DOES_NOT_EXIST); + } + return ok; +} + +/* The guest's own mapped RX buffer is the SPMC's to write, never the guest's + * to share: DENIED. */ +static int memneg_rx_share_denied(void) +{ + uint64_t w2 = 0u; + uint64_t w3 = 0u; + uint32_t len = memneg_build_page(g_memneg_rx); + + return (len != 0u) && (mem_share_smc(len, &w2, &w3) == WT_FFA_ERROR) && + ((int32_t)(uint32_t)w2 == WT_FFA_DENIED); +} + +/* While a share holds g_memneg_page, an RX/TX pair naming it is refused as + * INVALID_PARAMETERS, and the guest's own pair maps back. */ +static int memneg_rxtx_over_shared(void) +{ + uint64_t x[5]; + int ok; + + x[0] = WT_FFA_RXTX_UNMAP; + x[1] = 0u; + x[2] = 0u; + x[3] = 0u; + x[4] = 0u; + smc5(x); + ok = ((uint32_t)x[0] == WT_FFA_SUCCESS32); + x[0] = WT_FFA_RXTX_MAP64; + x[1] = (uint64_t)(uintptr_t)g_memneg_desc; + x[2] = (uint64_t)(uintptr_t)g_memneg_page; + x[3] = 1u; + x[4] = 0u; + smc5(x); + ok = ok && ((uint32_t)x[0] == WT_FFA_ERROR) && + ((int32_t)(uint32_t)x[2] == WT_FFA_INVALID_PARAMETERS); + x[0] = WT_FFA_RXTX_MAP64; + x[1] = (uint64_t)(uintptr_t)g_memneg_desc; + x[2] = (uint64_t)(uintptr_t)g_memneg_rx; + x[3] = 1u; + x[4] = 0u; + smc5(x); + return ok && ((uint32_t)x[0] == WT_FFA_SUCCESS32); +} + +/* Send a well-formed share in two fragments through the same buffer (DEN0140 + * 4.1.2): the SPMC asks for the rest with FFA_MEM_FRAG_RX under the handle it + * reserved, refuses a fragment for any other handle, and completes the share + * under that same handle once the descriptor is whole. */ +static int memfrag_share(void) +{ + static uint8_t full[256]; + uint64_t x[5]; + uint64_t handle; + uint32_t len = memneg_build(); + uint32_t split = 40u; + uint32_t i; + int ok; + + if (len <= split) { + return 0; + } + for (i = 0u; i < len; i++) { + full[i] = g_memneg_desc[i]; + } + x[0] = WT_FFA_MEM_SHARE32; + x[1] = len; + x[2] = split; + x[3] = 0u; + x[4] = 0u; + smc5(x); + handle = (x[1] & 0xFFFFFFFFu) | ((x[2] & 0xFFFFFFFFu) << 32); + ok = ((uint32_t)x[0] == WT_FFA_MEM_FRAG_RX) && ((uint32_t)x[3] == split) && + ((uint32_t)x[4] == 0u) && (handle != 0u); + + /* A second first fragment mid-transfer is BUSY and drops nothing. */ + x[0] = WT_FFA_MEM_SHARE32; + x[1] = len; + x[2] = split; + x[3] = 0u; + x[4] = 0u; + smc5(x); + ok = ok && ((uint32_t)x[0] == WT_FFA_ERROR) && + ((int32_t)(uint32_t)x[2] == WT_FFA_BUSY); + + for (i = split; i < len; i++) { + g_memneg_desc[i - split] = full[i]; + } + x[0] = WT_FFA_MEM_FRAG_TX; + x[1] = (handle + 1u) & 0xFFFFFFFFu; + x[2] = (handle + 1u) >> 32; + x[3] = len - split; + x[4] = 0u; + smc5(x); + ok = ok && ((uint32_t)x[0] == WT_FFA_ERROR) && + ((int32_t)(uint32_t)x[2] == WT_FFA_INVALID_PARAMETERS); + + x[0] = WT_FFA_MEM_FRAG_TX; + x[1] = handle & 0xFFFFFFFFu; + x[2] = handle >> 32; + x[3] = len - split; + x[4] = 0xFFFFu; /* w4[15:0] SBZ (Table 4.7) */ + smc5(x); + ok = ok && ((uint32_t)x[0] == WT_FFA_SUCCESS32) && + (((x[2] & 0xFFFFFFFFu) | ((x[3] & 0xFFFFFFFFu) << 32)) == handle); + + x[0] = WT_FFA_MEM_FRAG_RX; + x[1] = handle & 0xFFFFFFFFu; + x[2] = handle >> 32; + x[3] = 0u; + x[4] = 0u; + smc5(x); + ok = ok && ((uint32_t)x[0] == WT_FFA_ERROR) && + ((int32_t)(uint32_t)x[2] == WT_FFA_INVALID_PARAMETERS); + + return ok && (mem_reclaim_smc(handle) == WT_FFA_SUCCESS32); +} + +/* Unmap and remap the RX/TX pair between two fragments of a share: the TX + * buffer the first one used is gone (DEN0140 4.1.2 rule 6), so the next + * FFA_MEM_FRAG_TX is ABORTED (rule 8), the handle names nothing after, and no + * share was made. */ +static int memfrag_unmap_aborts(void) +{ + static uint8_t full[256]; + uint64_t x[5]; + uint64_t handle; + uint32_t len = memneg_build(); + uint32_t split = 40u; + uint32_t i; + int ok; + + if (len <= split) { + return 0; + } + for (i = 0u; i < len; i++) { + full[i] = g_memneg_desc[i]; + } + x[0] = WT_FFA_MEM_SHARE32; + x[1] = len; + x[2] = split; + x[3] = 0u; + x[4] = 0u; + smc5(x); + handle = (x[1] & 0xFFFFFFFFu) | ((x[2] & 0xFFFFFFFFu) << 32); + ok = ((uint32_t)x[0] == WT_FFA_MEM_FRAG_RX); + + x[0] = WT_FFA_RXTX_UNMAP; + x[1] = 0u; + x[2] = 0u; + x[3] = 0u; + x[4] = 0u; + smc5(x); + ok = ok && ((uint32_t)x[0] == WT_FFA_SUCCESS32); + x[0] = WT_FFA_RXTX_MAP64; + x[1] = (uint64_t)(uintptr_t)g_memneg_desc; + x[2] = (uint64_t)(uintptr_t)g_memneg_rx; + x[3] = 1u; + x[4] = 0u; + smc5(x); + ok = ok && ((uint32_t)x[0] == WT_FFA_SUCCESS32); + + for (i = split; i < len; i++) { + g_memneg_desc[i - split] = full[i]; + } + x[0] = WT_FFA_MEM_FRAG_TX; + x[1] = handle & 0xFFFFFFFFu; + x[2] = handle >> 32; + x[3] = len - split; + x[4] = 0u; + smc5(x); + ok = ok && ((uint32_t)x[0] == WT_FFA_ERROR) && + ((int32_t)(uint32_t)x[2] == WT_FFA_ABORTED); + x[0] = WT_FFA_MEM_FRAG_TX; + x[1] = handle & 0xFFFFFFFFu; + x[2] = handle >> 32; + x[3] = len - split; + x[4] = 0u; + smc5(x); + ok = ok && ((uint32_t)x[0] == WT_FFA_ERROR) && + ((int32_t)(uint32_t)x[2] == WT_FFA_INVALID_PARAMETERS); + return ok && (mem_reclaim_smc(handle) == WT_FFA_ERROR); +} + +/* SBZ fields set in every descriptor part (DEN0140 Tables 1.13-1.16, 1.18, + * 1.20, 1.21) are ignored: the share is accepted and reclaimed. */ +static int memneg_sbz_ignored(uint32_t len) +{ + uint64_t w2 = 0u; + uint64_t w3 = 0u; + uint32_t i; + + (void)memneg_build(); + for (i = 36u; i < 48u; i++) { + g_memneg_desc[i] = 0xA5u; /* header reserved */ + } + g_memneg_desc[3] |= 0x80u; /* attributes bits[15:8] */ + g_memneg_desc[7] = 0x80u; /* flags bit[31] */ + g_memneg_desc[50] |= 0xF0u; /* permission bits[7:4] */ + g_memneg_desc[len - 1u] = 0xA5u; /* constituent reserved */ + if (mem_share_smc(len, &w2, &w3) != WT_FFA_SUCCESS32) { + return 0; + } + return mem_reclaim_smc((w2 & 0xFFFFFFFFu) | (w3 << 32)) == + WT_FFA_SUCCESS32; +} + +static int memneg_refused(uint32_t len) +{ + uint64_t w2 = 0u; + uint64_t w3 = 0u; + + return mem_share_smc(len, &w2, &w3) == WT_FFA_ERROR; +} + +/* A well-formed share the SPMC must refuse as INVALID_PARAMETERS for how it + * names its buffer. */ +static int memneg_bad_buffer(uint64_t addr, uint64_t pages, uint32_t len) +{ + uint64_t w2 = 0u; + uint64_t w3 = 0u; + + return (mem_share_buf_smc(addr, pages, len, &w2, &w3) == WT_FFA_ERROR) && + ((int32_t)(uint32_t)w2 == WT_FFA_INVALID_PARAMETERS); +} + +/* Before its RX/TX pair is mapped a share is INVALID_PARAMETERS; after, offer + * a well-formed FFA_MEM_SHARE (accepted, a handle returned), then a set of + * malformed descriptors and dynamically allocated buffers (each refused with + * no crash), reclaim the good handle, and confirm a second reclaim of the + * now-dead handle is refused. */ +/* FFA_RX_RELEASE(vm) at the NS physical instance: the reply's error code, or + * 0 on FFA_SUCCESS. */ +static int32_t memneg_rx_release_smc(uint32_t vm) +{ + uint64_t x[5]; + + x[0] = WT_FFA_RX_RELEASE; + x[1] = vm; + x[2] = 0u; + x[3] = 0u; + x[4] = 0u; + smc5(x); + return ((uint32_t)x[0] == WT_FFA_SUCCESS32) ? 0 : (int32_t)(uint32_t)x[2]; +} + +static int memneg_rx_release_expect(uint32_t vm, int32_t want, const char* what) +{ + int32_t got = memneg_rx_release_smc(vm); + + if (got == want) { + return 1; + } + put_str("[NS] memneg BAD rx release "); + put_str(what); + put_str(" x2=0x"); + put_hex((uint32_t)got); + put_str("\r\n"); + return 0; +} + +/* FFA_PARTITION_INFO_GET listing every partition into the RX buffer, which + * the caller then owns (7.2.2.4.2). */ +static int memneg_fill_rx(void) +{ + register uint64_t r0 __asm__("x0") = WT_FFA_PARTITION_INFO_GET; + register uint64_t r1 __asm__("x1") = 0; + register uint64_t r2 __asm__("x2") = 0; + register uint64_t r3 __asm__("x3") = 0; + register uint64_t r4 __asm__("x4") = 0; + register uint64_t r5 __asm__("x5") = 0; + + __asm__ volatile("smc #0" + : "+r"(r0), "+r"(r1), "+r"(r2), "+r"(r3), "+r"(r4), "+r"(r5) + : + : "x6", "x7", "x8", "x9", "x10", "x11", "x12", "x13", "x14", + "x15", "x16", "x17", "memory"); + return ((uint32_t)r0 == WT_FFA_SUCCESS32) && (r2 != 0u); +} + +/* Table 13.21: w1[15:0] names the VM whose RX buffer is released, and only + * the primary endpoint has a pair; Table 13.22: a VM with no pair is + * INVALID_PARAMETERS, a buffer the caller does not own is DENIED. */ +static int memneg_rx_release_rows(void) +{ + int ok; + + ok = memneg_rx_release_expect(0u, WT_FFA_DENIED, "unowned"); + if (!memneg_fill_rx()) { + put_str("[NS] memneg BAD rx release partinfo\r\n"); + return 0; + } + ok = ok && memneg_rx_release_expect(1u, WT_FFA_INVALID_PARAMETERS, + "foreign vm"); + ok = ok && memneg_rx_release_expect(0u, 0, "owned"); + ok = ok && memneg_rx_release_expect(0u, WT_FFA_DENIED, "released"); + if (ok) { + put_str("[NS] rx release ok\r\n"); + } + return ok; +} + +static void guest_memneg(void) +{ + uint64_t x[5]; + uint64_t handle; + uint64_t w2 = 0u; + uint64_t w3 = 0u; + uint32_t len; + int ok = 1; + + len = memneg_build(); + if ((len == 0u) || (g_memneg_desc[WT_FFA_MEM_TXN_OFF_ACC_SIZE] != + WT_FFA_MEM_ACCESS_SIZE_V12)) { + put_str("[NS] memneg BAD build\r\n"); + return; + } + ok = ok && memneg_bad_buffer(0u, 0u, len); /* no RX/TX pair mapped */ + ok = ok && memneg_rx_release_expect(0u, WT_FFA_DENIED, "unmapped"); + x[0] = WT_FFA_RXTX_MAP64; + x[1] = (uint64_t)(uintptr_t)g_memneg_desc; + x[2] = (uint64_t)(uintptr_t)g_memneg_rx; + x[3] = 1u; + x[4] = 0u; + smc5(x); + if ((uint32_t)x[0] != WT_FFA_SUCCESS32) { + put_str("[NS] memneg BAD rxtx map\r\n"); + return; + } + ok = ok && memneg_rx_release_rows(); + if (mem_share_smc(len, &w2, &w3) != WT_FFA_SUCCESS32) { + put_str("[NS] memneg BAD share\r\n"); + return; + } + handle = (w2 & 0xFFFFFFFFu) | (w3 << 32); + + (void)memneg_build(); + ok = ok && memneg_bad_buffer((uint64_t)(uintptr_t)g_memneg_desc, 1u, len); + ok = ok && memneg_bad_buffer((uint64_t)(uintptr_t)g_memneg_desc, 0u, len); + ok = ok && memneg_bad_buffer(0u, 1u, len); + ok = ok && memneg_rx_share_denied(); + ok = ok && memneg_rxtx_over_shared(); + + (void)memneg_build(); + g_memneg_desc[len - WT_FFA_MEM_CONSTITUENT_SIZE] = 1u; /* misaligned base */ + ok = ok && memneg_refused(len); + (void)memneg_build(); + g_memneg_desc[0] = 0x34u; /* sender is not the caller */ + g_memneg_desc[1] = 0x12u; + ok = ok && memneg_refused(len); + (void)memneg_build(); + g_memneg_desc[51] = 1u; /* MBZ access descriptor flags */ + ok = ok && memneg_refused(len); + (void)memneg_build(); + g_memneg_desc[len - WT_FFA_MEM_CONSTITUENT_SIZE - + WT_FFA_MEM_COMPOSITE_HDR_SIZE] = 9u; /* total != the sum */ + ok = ok && memneg_refused(len); + + ok = ok && (mem_reclaim_smc(handle) == WT_FFA_SUCCESS32); + ok = ok && (mem_reclaim_smc(handle) == WT_FFA_ERROR); /* dead handle */ + ok = ok && memneg_sbz_ignored(len); + ok = ok && memneg_donate(); + if (memneg_psa_owned() == 0) { + put_str("[NS] memneg psa BAD\r\n"); + ok = 0; + } + + put_str(ok ? "[NS] memneg ok\r\n" : "[NS] memneg BAD\r\n"); + ok = memfrag_share(); + ok = ok && memfrag_unmap_aborts(); + put_str(ok ? "[NS] memfrag ok\r\n" : "[NS] memfrag BAD\r\n"); +} +#endif + +#if defined(WT_NS_GUEST_FUZZ) +/* Sweep function ids the SPMD does not implement at the NS physical instance - + * unimplemented FF-A ids, unimplemented PSCI ids, and ids outside every served + * range - and confirm each is refused cleanly (no crash, no world change): FF-A + * ids answer FFA_ERROR/NOT_SUPPORTED, the rest answer the SMCCC unknown value. + * Built from macros so no raw FF-A id literal lives outside ffa_abi.h. */ +static const uint32_t g_fuzz_fids[] = { + WT_FFA_YIELD, WT_FFA_NORMAL_WORLD_RESUME, + WT_FFA_RX_ACQUIRE, + WT_FFA_CONSOLE_LOG32, WT_FFA_CONSOLE_LOG64, + WT_FFA_FID32_LAST, WT_FFA_FID64_LAST, + WT_PSCI_CPU_FREEZE, WT_PSCI_SYSTEM_SUSPEND64, WT_PSCI_FID32_LAST, + 0x80000002u /* SMCCC_ARCH_SOC_ID: not offered */, + 0x82000000u, 0x8F000000u, 0xC3000000u, + 0xC3000102u /* the ACS test timer: absent outside ACS builds */ +}; + +static int fuzz_refused(uint32_t fid, const uint64_t* o) +{ + if (wt_ffa_fid_in_range(fid)) { + return ((uint32_t)o[0] == WT_FFA_ERROR) && + ((int32_t)(uint32_t)o[2] == WT_FFA_NOT_SUPPORTED); + } + /* SMCCC 5.2: -1 sign-extended, so an SMC64 id reads all of x0 set. */ + if ((fid & 0x40000000u) != 0u) { + return o[0] == 0xFFFFFFFFFFFFFFFFull; + } + return (uint32_t)o[0] == 0xFFFFFFFFu; +} + +/* An SMC32 call carries W1-W7 only (SMCCC 3.1): an RXTX_MAP32 whose buffer + * addresses arrive with junk in the upper register halves still maps them. */ +static uint8_t g_fuzz_tx[4096] __attribute__((aligned(4096))); +static uint8_t g_fuzz_rx[4096] __attribute__((aligned(4096))); + +static void guest_smc32_upper(void) +{ + uint64_t x[5]; + uint32_t st; + + x[0] = WT_FFA_RXTX_MAP32; + x[1] = 0xA5A5A5A500000000ull | (uint64_t)(uintptr_t)g_fuzz_tx; + x[2] = 0x5A5A5A5A00000000ull | (uint64_t)(uintptr_t)g_fuzz_rx; + x[3] = 0xFFFFFFFF00000001ull; + x[4] = 0u; + smc5(x); + st = (uint32_t)x[0]; + x[0] = WT_FFA_RXTX_UNMAP; + x[1] = 0u; + x[2] = 0u; + x[3] = 0u; + x[4] = 0u; + smc5(x); + if ((st == WT_FFA_SUCCESS32) && ((uint32_t)x[0] == WT_FFA_SUCCESS32)) { + put_str("[NS] smc32 upper halves ignored\r\n"); + } + else { + put_str("[NS] smc32 upper halves BAD map=0x"); + put_hex(st); + put_str(" unmap=0x"); + put_hex((uint32_t)x[0]); + put_str("\r\n"); + } +} + +static void guest_fuzz(void) +{ + uint64_t o[4]; + unsigned int n = (unsigned int)(sizeof(g_fuzz_fids) / sizeof(g_fuzz_fids[0])); + unsigned int ok = 0u; + unsigned int i; + + for (i = 0u; i < n; i++) { + ffa_smc(g_fuzz_fids[i], 0u, o); + if (fuzz_refused(g_fuzz_fids[i], o)) { + ok++; + } + else { + put_str("[NS] smcfuzz BAD fid=0x"); + put_hex(g_fuzz_fids[i]); + put_str(" x0=0x"); + put_hex((uint32_t)(o[0] >> 32)); + put_str("_"); + put_hex((uint32_t)o[0]); + put_str("\r\n"); + } + } + if (ok == n) { + put_str("[NS] smcfuzz ok swept="); + put_dec(n); + put_str("\r\n"); + } + else { + put_str("[NS] smcfuzz FAIL ok="); + put_dec(ok); + put_str("\r\n"); + } + guest_smc32_upper(); +} +#endif + +#if defined(WT_NS_GUEST_RESET) +/* UARTIFLS: a machine reset restores it (0x12), a firmware warm re-entry + * keeps whatever the previous boot wrote. */ +#define UART_IFLS 0x34u +#define UART_IFLS_MARK 0x24u + +/* Ask the SPMD to reset the system through PSCI, marking a device register + * first so the next boot shows whether the machine was really reset. The + * reset does not return to the Normal world. */ +static void guest_reset(void) +{ + uint64_t o[4]; + + put_str("[NS] uart ifls=0x"); + put_hex(*uart_reg(UART_IFLS) & 0x3Fu); + put_str("\r\n"); + *uart_reg(UART_IFLS) = UART_IFLS_MARK; + put_str("[NS] psci system_reset\r\n"); + ffa_smc(WT_PSCI_SYSTEM_RESET, 0u, o); +} +#endif + +#if defined(WT_NS_GUEST_SECRAM) +extern char _ns_vectbl[]; +extern void ns_exit(int code); +extern uint32_t ns_secram_load(uintptr_t pa); + +/* The fence's refusal as the NS-EL1h guest takes it (vector slot 4): a data + * abort without a change in Exception level (EC 0x25) that is a synchronous + * external abort (DFSC 0x10) on a read, with FAR valid (FnV 0). */ +#define WT_NS_VEC_SYNC_CUR_SPX 4u +#define WT_NS_ESR_EC_DABT_CUR 0x25u +#define WT_NS_ESR_DFSC_EXT 0x10u +#define WT_NS_ESR_WNR (1u << 6) +#define WT_NS_ESR_FNV (1u << 10) + +/* The Normal world's own abort vector (ns.S) lands here when the Secure-RAM read + * faults. Only the fence's abort on the probe load and address is a refusal; + * any other exception, a hang (no handler), or a returned value (a leak) is a + * failure. */ +void ns_abort_report(uint64_t esr, uint64_t far, uint64_t elr, uint64_t slot) +{ + uint32_t ec = (uint32_t)(esr >> 26) & 0x3Fu; + uint32_t dfsc = (uint32_t)esr & 0x3Fu; + int ok = (slot == WT_NS_VEC_SYNC_CUR_SPX) && + (ec == WT_NS_ESR_EC_DABT_CUR) && (dfsc == WT_NS_ESR_DFSC_EXT) && + (((uint32_t)esr & (WT_NS_ESR_WNR | WT_NS_ESR_FNV)) == 0u) && + (far == (uint64_t)WT_NS_SECURE_PROBE_PA) && + (elr == (uint64_t)(uintptr_t)ns_secram_load); + + put_str((ok != 0) ? "[NS] secram refused ec=0x" : "[NS] secram BAD ec=0x"); + put_hex(ec); + put_str(" dfsc=0x"); + put_hex(dfsc); + put_str(" far=0x"); + put_hex((uint32_t)far); + put_str(" esr=0x"); + put_hex((uint32_t)esr); + put_str(" slot="); + put_dec((uint32_t)slot); + put_str(" elr=0x"); + put_hex((uint32_t)elr); + put_str("\r\n"); + ns_exit((ok != 0) ? 0 : 1); +} + +/* Attempt to read Secure RAM from the Normal world: the secure physical region + * must be unreachable from NS. The read is expected to fault into the guest's + * own abort vector (proving the world fence); if it ever returns data the fence + * is broken. */ +static void guest_secram(void) +{ + uint32_t v; + + __asm__ volatile("msr vbar_el1, %0\n\tisb" : : "r"(_ns_vectbl)); + put_str("[NS] secram read 0x"); + put_hex((uint32_t)WT_NS_SECURE_PROBE_PA); + put_str("\r\n"); + v = ns_secram_load((uintptr_t)WT_NS_SECURE_PROBE_PA); + put_str("[NS] secram LEAK 0x"); + put_hex(v); + put_str("\r\n"); +} +#endif + +/* Walk the NS physical instance: FEATURES(VERSION) is supported, ID_GET returns + * the caller's own id (the primary NS endpoint, 0), SPM_ID_GET returns the SPMC + * id (0x8000), and PARTITION_INFO_GET (forwarded to the SPMC) reports the + * partition count. Returns non-zero when all answer as expected; *count holds + * the reported partition count. */ +static int discover(uint32_t* count) +{ + uint64_t o[4]; + + ffa_smc(WT_FFA_FEATURES, WT_FFA_VERSION, o); + if ((uint32_t)o[0] != WT_FFA_SUCCESS32) { + return 0; + } + ffa_smc(WT_FFA_ID_GET, 0u, o); + if (((uint32_t)o[0] != WT_FFA_SUCCESS32) || + ((uint16_t)o[2] != WT_FFA_ID_NS_PRIMARY)) { + return 0; + } + ffa_smc(WT_FFA_SPM_ID_GET, 0u, o); + if (((uint32_t)o[0] != WT_FFA_SUCCESS32) || + ((uint16_t)o[2] != WT_FFA_ID_SPMC)) { + return 0; + } + *count = partition_count(); + if (*count == 0u) { + return 0; + } + return 1; +} + +#if defined(WT_NS_GUEST_PSCI) +/* Read the PSCI version from the SPMD, then power off through PSCI (WT-FFM-0067): + * SYSTEM_OFF does not return, so the SPMD ends the run. */ +static uint64_t psci_call(uint32_t fid, uint64_t a1, uint64_t a2) +{ + register uint64_t r0 __asm__("x0") = fid; + register uint64_t r1 __asm__("x1") = a1; + register uint64_t r2 __asm__("x2") = a2; + register uint64_t r3 __asm__("x3") = 0; + + __asm__ volatile("smc #0" + : "+r"(r0), "+r"(r1), "+r"(r2), "+r"(r3) + : + : "x4", "x5", "x6", "x7", "x8", "x9", "x10", "x11", "x12", + "x13", "x14", "x15", "x16", "x17", "memory"); + return r0; +} + +static int psci_expect(const char* what, uint64_t got, int32_t want) +{ + if ((int32_t)(uint32_t)got == want) { + return 1; + } + put_str("[NS] psci BAD "); + put_str(what); + put_str(" x0=0x"); + put_hex((uint32_t)got); + put_str("\r\n"); + return 0; +} + +/* An SMC64 call's int32 result must fill all of X0, sign-extended. */ +static int psci_expect64(const char* what, uint64_t got, int32_t want) +{ + if (got == (uint64_t)(int64_t)want) { + return 1; + } + put_str("[NS] psci BAD "); + put_str(what); + put_str(" x0=0x"); + put_hex((uint32_t)(got >> 32)); + put_str("_"); + put_hex((uint32_t)got); + put_str("\r\n"); + return 0; +} + +/* SMCCC 1.1 and later: a call that returns only x0 hands x4-x7 back + * unchanged, whether the SPMD answers it alone or after a power message. + * An SMC32 call carries only w4-w7 (SMCCC 3.1), so its markers are 32-bit. */ +static int psci_preserves_x4_x7(uint32_t fid, uint64_t a1, int32_t want) +{ + uint64_t m = ((fid & 0x40000000u) != 0u) ? ~0ull : 0xFFFFFFFFull; + register uint64_t r0 __asm__("x0") = fid; + register uint64_t r1 __asm__("x1") = a1; + register uint64_t r2 __asm__("x2") = 0; + register uint64_t r3 __asm__("x3") = 0; + register uint64_t r4 __asm__("x4") = 0x4444444444444444ull & m; + register uint64_t r5 __asm__("x5") = 0x5555555555555555ull & m; + register uint64_t r6 __asm__("x6") = 0x6666666666666666ull & m; + register uint64_t r7 __asm__("x7") = 0x7777777777777777ull & m; + + __asm__ volatile("smc #0" + : "+r"(r0), "+r"(r1), "+r"(r2), "+r"(r3), "+r"(r4), + "+r"(r5), "+r"(r6), "+r"(r7) + : + : "x8", "x9", "x10", "x11", "x12", "x13", "x14", + "x15", "x16", "x17", "memory"); + if (((int32_t)(uint32_t)r0 == want) && + (r4 == (0x4444444444444444ull & m)) && + (r5 == (0x5555555555555555ull & m)) && + (r6 == (0x6666666666666666ull & m)) && + (r7 == (0x7777777777777777ull & m))) { + return 1; + } + put_str("[NS] psci BAD x4-x7 not preserved\r\n"); + return 0; +} + +/* SMCCC_VERSION, discovered through PSCI_FEATURES (DEN0028 Appendix B), and + * SMCCC_ARCH_FEATURES, which must know itself and SMCCC_VERSION (7.3.6). */ +static int smccc_walk(uint64_t self) +{ + int ok = 1; + + ok &= psci_expect("features smccc_version", + psci_call(WT_PSCI_FEATURES, WT_SMCCC_VERSION, 0u), + WT_PSCI_SUCCESS); + ok &= psci_expect("smccc_version", psci_call(WT_SMCCC_VERSION, 0u, 0u), + (int32_t)WT_SMCCC_VERSION_1_2); + ok &= psci_expect("arch_features smccc_version", + psci_call(WT_SMCCC_ARCH_FEATURES, WT_SMCCC_VERSION, 0u), + 0); + ok &= psci_expect("arch_features arch_features", + psci_call(WT_SMCCC_ARCH_FEATURES, WT_SMCCC_ARCH_FEATURES, + 0u), 0); + ok &= psci_expect("arch_features workaround_1", + psci_call(WT_SMCCC_ARCH_FEATURES, 0x80008000u, 0u), + WT_SMCCC_NOT_SUPPORTED); + ok &= psci_preserves_x4_x7(WT_PSCI_AFFINITY_INFO64, self, + WT_PSCI_AFFINITY_ON); + ok &= psci_preserves_x4_x7(WT_PSCI_CPU_OFF, 0u, WT_PSCI_DENIED); + if (ok != 0) { + put_str("[NS] smccc version 1.2\r\n"); + } + return ok; +} + +/* With a GIC system-register interface, NS-EL1 reaches ICC_SRE_EL1 and finds + * SRE set: an implemented EL2 neither traps it nor forces the legacy one. */ +static void gic_sre_probe(void) +{ + uint64_t pfr0; + uint64_t sre; + + __asm__ volatile("mrs %0, id_aa64pfr0_el1" : "=r"(pfr0)); + if (((pfr0 >> 24) & 0xFu) == 0u) { + put_str("[NS] gic sysreg interface absent\r\n"); + return; + } + __asm__ volatile("mrs %0, icc_sre_el1" : "=r"(sre)); + if ((sre & 1u) != 0u) { + put_str("[NS] icc_sre_el1 sre=1\r\n"); + } + else { + put_str("[NS] icc_sre_el1 BAD 0x"); + put_hex((uint32_t)sre); + put_str("\r\n"); + } +} + +/* Run at NS-EL2, clear HCR_EL2.RW and drop to an AArch32 EL1 stub that issues + * one SMC with R0-R5 from regs[0..5] and returns through HVC (the vector at + * 0x600, lower EL in AArch32); R0-R3 come back in regs[0..3]. */ +void ns_a32_smc(uint64_t* regs); +__asm__( +" .pushsection .text.ns_a32, \"ax\"\n" +" .balign 0x800\n" +"ns_el2_vectors:\n" +" .rept 12\n" +" .balign 0x80\n" +" b .\n" +" .endr\n" +" .balign 0x80\n" +" b ns_a32_back\n" +" .rept 3\n" +" .balign 0x80\n" +" b .\n" +" .endr\n" +" .globl ns_a32_smc\n" +"ns_a32_smc:\n" +" stp x29, x30, [sp, #-112]!\n" +" stp x19, x20, [sp, #16]\n" +" stp x21, x22, [sp, #32]\n" +" stp x23, x24, [sp, #48]\n" +" stp x25, x26, [sp, #64]\n" +" stp x27, x28, [sp, #80]\n" +" mrs x9, hcr_el2\n" +" stp x0, x9, [sp, #96]\n" +" adr x10, ns_el2_vectors\n" +" msr vbar_el2, x10\n" +" bic x9, x9, #0x80000000\n" +" msr hcr_el2, x9\n" +" adr x10, ns_a32_stub\n" +" msr elr_el2, x10\n" +" mov x10, #0x1d3\n" +" msr spsr_el2, x10\n" +" ldp x4, x5, [x0, #32]\n" +" ldp x2, x3, [x0, #16]\n" +" ldp x0, x1, [x0]\n" +" isb\n" +" eret\n" +"ns_a32_back:\n" +" ldp x9, x10, [sp, #96]\n" +" msr hcr_el2, x10\n" +" isb\n" +" mov w0, w0\n" +" mov w1, w1\n" +" mov w2, w2\n" +" mov w3, w3\n" +" stp x0, x1, [x9]\n" +" stp x2, x3, [x9, #16]\n" +" ldp x19, x20, [sp, #16]\n" +" ldp x21, x22, [sp, #32]\n" +" ldp x23, x24, [sp, #48]\n" +" ldp x25, x26, [sp, #64]\n" +" ldp x27, x28, [sp, #80]\n" +" ldp x29, x30, [sp], #112\n" +" ret\n" +" .balign 4\n" +"ns_a32_stub:\n" +" .word 0xe1600070\n" /* A32 smc #0 */ +" .word 0xe1400070\n" /* A32 hvc #0 */ +" .popsection\n"); + +/* SMCCC 4 from an AArch32 caller: an SMC32 PSCI call and a forwarded FF-A + * call are served and return to AArch32, and an SMC64 id is unknown (5.2). */ +static int a32_walk(void) +{ + uint64_t r[6]; + uint64_t pfr0; + unsigned int i; + int ok = 1; + + __asm__ volatile("mrs %0, id_aa64pfr0_el1" : "=r"(pfr0)); + if (((pfr0 >> 4) & 0xFu) != 2u) { + put_str("[NS] a32 el1 absent\r\n"); + return 1; + } + for (i = 0u; i < 6u; i++) { + r[i] = 0u; + } + r[0] = WT_PSCI_VERSION; + ns_a32_smc(r); + ok &= psci_expect("a32 psci_version", r[0], (int32_t)WT_PSCI_VERSION_1_1); + r[0] = WT_PSCI_AFFINITY_INFO64; + ns_a32_smc(r); + ok &= psci_expect("a32 smc64 id", r[0], WT_PSCI_NOT_SUPPORTED); + r[0] = WT_FFA_PARTITION_INFO_GET; + r[1] = 0u; + r[2] = 0u; + r[3] = 0u; + r[4] = 0u; + r[5] = WT_FFA_PARTINFO_FLAG_COUNT; + ns_a32_smc(r); + if (((uint32_t)r[0] != WT_FFA_SUCCESS32) || (r[2] == 0u)) { + put_str("[NS] psci BAD a32 partinfo x0=0x"); + put_hex((uint32_t)r[0]); + put_str("\r\n"); + ok = 0; + } + if (ok != 0) { + put_str("[NS] a32 smc ok partinfo n="); + put_dec((uint32_t)r[2]); + put_str("\r\n"); + } + return ok; +} + +/* A target_cpu naming the boot core but with a Must-be-zero bit set (DEN0022 + * 5.1.4: bits[31:24] and, for SMC64, bits[63:40]) names no core. */ +static int psci_mbz_target(uint64_t self) +{ + static const uint32_t fids[] = { + WT_PSCI_CPU_ON64, WT_PSCI_AFFINITY_INFO64, WT_PSCI_MIGRATE64, + WT_PSCI_CPU_ON32, WT_PSCI_AFFINITY_INFO32, WT_PSCI_MIGRATE32 + }; + static const unsigned int bits[] = { 24u, 31u, 40u, 63u }; + uint64_t got; + unsigned int f; + unsigned int b; + int smc64; + int ok = 1; + + for (f = 0u; f < sizeof(fids) / sizeof(fids[0]); f++) { + smc64 = ((fids[f] & 0x40000000u) != 0u) ? 1 : 0; + for (b = 0u; b < sizeof(bits) / sizeof(bits[0]); b++) { + if ((smc64 == 0) && (bits[b] >= 32u)) { + continue; + } + got = psci_call(fids[f], self | (1ull << bits[b]), 0u); + if (((smc64 != 0) && + (got != (uint64_t)(int64_t)WT_PSCI_INVALID_PARAMS)) || + ((smc64 == 0) && + ((int32_t)(uint32_t)got != WT_PSCI_INVALID_PARAMS))) { + put_str("[NS] psci BAD mbz target fid=0x"); + put_hex(fids[f]); + put_str(" bit="); + put_dec(bits[b]); + put_str(" x0=0x"); + put_hex((uint32_t)got); + put_str("\r\n"); + ok = 0; + } + } + } + return ok; +} + +/* The mandatory PSCI 1.1 set as a boot-core-only Normal world sees it. */ +static void psci_walk(void) +{ + uint64_t o[4]; + uint64_t self; + uint64_t parked; + uint64_t el; + int ok = 1; + + ffa_smc(WT_PSCI_VERSION, 0u, o); + put_str("[NS] psci version "); + put_dec((uint32_t)((o[0] >> 16) & 0xFFFFu)); + put_char('.'); + put_dec((uint32_t)(o[0] & 0xFFFFu)); + put_str("\r\n"); + + __asm__ volatile("mrs %0, mpidr_el1" : "=r"(self)); + self &= 0x000000FF00FFFFFFull; + parked = self ^ 1u; + ok &= psci_expect64("cpu_on self", psci_call(WT_PSCI_CPU_ON64, self, 0u), + WT_PSCI_ALREADY_ON); + ok &= psci_expect64("cpu_on bogus", + psci_call(WT_PSCI_CPU_ON64, self | 0x00FF0000u, 0u), + WT_PSCI_INVALID_PARAMS); + ok &= psci_expect64("affinity self", + psci_call(WT_PSCI_AFFINITY_INFO64, self, 0u), + WT_PSCI_AFFINITY_ON); + /* The neighbour core, parked at EL3 or absent, is outside the Normal + * world's machine view: not an MPIDR it can query or turn on. */ + ok &= psci_expect64("affinity neighbour", + psci_call(WT_PSCI_AFFINITY_INFO64, parked, 0u), + WT_PSCI_INVALID_PARAMS); + ok &= psci_expect64("cpu_on neighbour", + psci_call(WT_PSCI_CPU_ON64, parked, 0u), + WT_PSCI_INVALID_PARAMS); + ok &= psci_expect64("affinity level1", + psci_call(WT_PSCI_AFFINITY_INFO64, self, 1u), + WT_PSCI_INVALID_PARAMS); + ok &= psci_expect("cpu_on32 self", + psci_call(WT_PSCI_CPU_ON32, self & 0xFFFFFFFFu, 0u), + WT_PSCI_ALREADY_ON); + ok &= psci_expect("affinity32 neighbour", + psci_call(WT_PSCI_AFFINITY_INFO32, + parked & 0xFFFFFFFFu, 0u), + WT_PSCI_INVALID_PARAMS); + ok &= psci_expect("cpu_off", psci_call(WT_PSCI_CPU_OFF, 0u, 0u), + WT_PSCI_DENIED); + ok &= psci_expect64("suspend powerdown", + psci_call(WT_PSCI_CPU_SUSPEND64, 0x00010000u, 0u), + WT_PSCI_INVALID_PARAMS); + ok &= psci_expect64("migrate", psci_call(WT_PSCI_MIGRATE64, self, 0u), + WT_PSCI_DENIED); + ok &= psci_expect64("migrate neighbour", + psci_call(WT_PSCI_MIGRATE64, parked, 0u), + WT_PSCI_INVALID_PARAMS); + ok &= psci_expect64("migrate bogus", + psci_call(WT_PSCI_MIGRATE64, self | 0x00FF0000u, 0u), + WT_PSCI_INVALID_PARAMS); + ok &= psci_mbz_target(self); + ok &= psci_expect("migrate_info_type", + psci_call(WT_PSCI_MIGRATE_INFO_TYPE, 0u, 0u), + (int32_t)WT_PSCI_TOS_UP_NOT_MIGRATABLE); + if (psci_call(WT_PSCI_MIGRATE_INFO_UP_CPU64, 0u, 0u) != self) { + put_str("[NS] psci BAD migrate_info_up_cpu\r\n"); + ok = 0; + } + ok &= psci_expect("features cpu_suspend", + psci_call(WT_PSCI_FEATURES, WT_PSCI_CPU_SUSPEND64, 0u), + WT_PSCI_SUCCESS); + ok &= psci_expect("features cpu_freeze", + psci_call(WT_PSCI_FEATURES, WT_PSCI_CPU_FREEZE, 0u), + WT_PSCI_NOT_SUPPORTED); + ok &= smccc_walk(self); + if (ok != 0) { + put_str("[NS] psci mandatory set ok\r\n"); + } + __asm__ volatile("mrs %0, CurrentEL" : "=r"(el)); + if (((el >> 2) & 0x3u) == 2u) { + (void)a32_walk(); + } + ffa_smc(WT_PSCI_SYSTEM_OFF, 0u, o); +} +#endif + +#if defined(WT_NS_PREEMPT) +/* Spin ~200ms of guest time so the Secure tick fires while the Normal world + * runs; the preemption is handled at EL3 and the loop resumes to completion. */ +static void ns_spin(void) +{ + uint64_t o[4]; + uint64_t freq; + uint64_t start; + uint64_t now; + + __asm__ volatile("mrs %0, cntfrq_el0" : "=r"(freq)); + __asm__ volatile("mrs %0, cntpct_el0" : "=r"(start)); + put_str("[NS] spinning\r\n"); + /* Core standby: the monitor's WFI wakes on the armed Secure tick. */ + ffa_smc(WT_PSCI_CPU_SUSPEND64, WT_PSCI_STATE_CORE_STANDBY, o); + if ((uint32_t)o[0] == (uint32_t)WT_PSCI_SUCCESS) { + put_str("[NS] psci standby woke\r\n"); + } + do { + __asm__ volatile("mrs %0, cntpct_el0" : "=r"(now)); + } while ((now - start) < (freq / 5u)); + put_str("[NS] resumed after preempt\r\n"); +} +#endif + +void ns_main(void) +{ + uint64_t current_el; + uint64_t o[4]; + uint32_t count = 0u; + + __asm__ volatile("mrs %0, CurrentEL" : "=r"(current_el)); + put_str("[NS] hello el="); + put_char((char)('0' + (char)((current_el >> 2) & 0x3u))); + put_str("\r\n"); + + ffa_smc(WT_FFA_VERSION, WT_FFA_VERSION_1_2, o); + if ((uint32_t)o[0] == WT_FFA_VERSION_1_2) { + put_str("[NS] ffa version 1.2\r\n"); + } + else { + put_str("[NS] ffa version BAD 0x"); + put_hex((uint32_t)o[0]); + put_str("\r\n"); + } + +#if defined(WT_NS_PREEMPT) + ns_spin(); + return; +#endif + +#if defined(WT_NS_GUEST_SECRAM) + guest_secram(); + return; +#endif + +#if defined(WT_NS_GUEST_RESET) + guest_reset(); + return; +#endif + +#if defined(WT_NS_GUEST_PSCI) + gic_sre_probe(); + psci_walk(); +#endif + + if (discover(&count)) { + put_str("[NS] discovery ok n="); + put_dec(count); + put_str("\r\n"); + } + else { + put_str("[NS] discovery BAD\r\n"); + } + notif_psa_denied(); + +#if defined(WT_NS_GUEST_ECHO) + guest_direct(); + guest_req2_refused(); + guest_direct_preempted(); + guest_idle_echo_run(); +#endif + +#if defined(WT_NS_GUEST_PSA) + guest_psa(); +#endif + +#if defined(WT_NS_GUEST_FUZZ) + guest_fuzz(); +#endif + +#if defined(WT_NS_GUEST_STORAGE) + guest_storage(); +#endif + +#if defined(WT_NS_GUEST_MEMNEG) + guest_memneg(); +#endif + +#if defined(WT_NS_GUEST_CONF) + guest_conformance(); +#endif +} diff --git a/tests/firmware/aarch64-ns-smoke/ns.ld b/tests/firmware/aarch64-ns-smoke/ns.ld new file mode 100644 index 00000000..02eeaa2e --- /dev/null +++ b/tests/firmware/aarch64-ns-smoke/ns.ld @@ -0,0 +1,48 @@ +/* The Normal-world payload runs from NS DRAM (WT_NS_BASE from the Makefile). + * The loaded image (code, rodata, the .data initializers) stays pristine in + * IMG; .data is copied into RAM at every entry, so a warm reset that re-enters + * the payload without reloading it starts from the initial values. */ +ENTRY(_start) + +MEMORY +{ + IMG (rx) : ORIGIN = WT_NS_BASE, LENGTH = WT_NS_IMG_SIZE + RAM (rw) : ORIGIN = WT_NS_BASE + WT_NS_IMG_SIZE, LENGTH = WT_NS_RAM_SIZE +} + +SECTIONS +{ + .text : { + KEEP(*(.text.entry)) + *(.text*) + } > IMG + .rodata : { + *(.rodata*) + . = ALIGN(16); + } > IMG + .data : ALIGN(16) { + __data_start = .; + *(.data*) + . = ALIGN(16); + __data_end = .; + } > RAM AT > IMG + __data_load = LOADADDR(.data); + .bss (NOLOAD) : { + __bss_start = .; + *(.bss*) + *(COMMON) + . = ALIGN(16); + __bss_end = .; + } > RAM + .stack (NOLOAD) : { + . = ALIGN(16); + . += WT_NS_STACK_SIZE; + __stack_top = .; + } > RAM + + /DISCARD/ : { + *(.note*) + *(.comment) + *(.eh_frame*) + } +} diff --git a/tests/firmware/aarch64-ns-smoke/wolfhsm_cfg.h b/tests/firmware/aarch64-ns-smoke/wolfhsm_cfg.h new file mode 100644 index 00000000..2017ea60 --- /dev/null +++ b/tests/firmware/aarch64-ns-smoke/wolfhsm_cfg.h @@ -0,0 +1,36 @@ +/* wolfhsm_cfg.h + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfTrust. + * + * wolfTrust 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. + * + * wolfTrust 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, see . + */ + +/* wolfHSM client configuration for the AArch64 Normal-world smoke guest: a + * crypto-free client whose only transport is the SPM-mediated psa_call. */ + +#ifndef WT_NS_SMOKE_WOLFHSM_CFG_H +#define WT_NS_SMOKE_WOLFHSM_CFG_H + +#define WOLFHSM_CFG_ENABLE_CLIENT +#define WOLFHSM_CFG_NO_CRYPTO + +/* Must match the secure side (src/services/wolfhsm/runner/wh_settings_local.h). */ +#define WOLFHSM_CFG_COMM_DATA_LEN 368 + +#define WOLFHSM_CFG_NO_SYS_TIME +#define WOLFHSM_CFG_HEXDUMP_DISABLE + +#endif /* WT_NS_SMOKE_WOLFHSM_CFG_H */ diff --git a/tests/firmware/aarch64-smoke/Makefile b/tests/firmware/aarch64-smoke/Makefile new file mode 100644 index 00000000..9345c3d9 --- /dev/null +++ b/tests/firmware/aarch64-smoke/Makefile @@ -0,0 +1,69 @@ +# Makefile +# +# Copyright (C) 2026 wolfSSL Inc. +# +# This file is part of wolfTrust. +# +# wolfTrust 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. +# +# wolfTrust 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 + +# EL3 smoke image for tests/target/run_qemu_a_scenario.sh. MACHINE picks the +# load address, the RAM band, and the console the emulated machine exposes. +TOOLPREFIX ?= aarch64-none-elf- +MACHINE ?= virt +WT_SMOKE_CPUS ?= 1 +BUILD_DIR ?= build/$(MACHINE) + +CC := $(TOOLPREFIX)gcc +OBJCOPY := $(TOOLPREFIX)objcopy + +ifeq ($(MACHINE),virt) +BASE := 0x00000000 +RAM := 0x0E000000 +UART := 0x09040000 +else ifeq ($(MACHINE),versal-virt) +BASE := 0xFFFC0000 +RAM := 0xFFFE0000 +UART := 0xFF000000 +else +$(error unsupported MACHINE=$(MACHINE) (virt or versal-virt)) +endif + +CFLAGS := -mcpu=generic -mgeneral-regs-only -mstrict-align -ffreestanding \ + -fno-builtin -fno-pic -fno-stack-protector -nostdlib -O2 -g \ + -Wall -Wextra -Werror \ + -DWT_SMOKE_UART=$(UART)u -DWT_SMOKE_MACHINE=\"$(MACHINE)\" \ + -DWT_SMOKE_CPUS=$(WT_SMOKE_CPUS)u +LDFLAGS := -nostartfiles -Wl,--build-id=none -T smoke.ld \ + -Wl,--defsym=WT_SMOKE_BASE=$(BASE) -Wl,--defsym=WT_SMOKE_RAM=$(RAM) + +SRCS := smoke.S smoke.c +ELF := $(BUILD_DIR)/smoke.elf +BIN := $(BUILD_DIR)/smoke.bin + +.PHONY: all clean + +all: $(ELF) $(BIN) + +$(BUILD_DIR): + mkdir -p $@ + +$(ELF): $(SRCS) smoke.ld | $(BUILD_DIR) + $(CC) $(CFLAGS) $(LDFLAGS) -o $@ $(SRCS) + +$(BIN): $(ELF) + $(OBJCOPY) -O binary $< $@ + +clean: + rm -rf build diff --git a/tests/firmware/aarch64-smoke/smoke.S b/tests/firmware/aarch64-smoke/smoke.S new file mode 100644 index 00000000..6b2bc173 --- /dev/null +++ b/tests/firmware/aarch64-smoke/smoke.S @@ -0,0 +1,87 @@ +/* smoke.S + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfTrust. + * + * wolfTrust 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. + * + * wolfTrust 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, see . + */ + +/* EL3 entry of the QEMU AArch64 smoke: refuse anything but EL3, park every + * core whose MPIDR affinity is not zero, and hand core 0 to smoke_main. */ + + .section .text.entry, "ax" + .globl _start +_start: + mrs x0, CurrentEL + lsr x0, x0, #2 + cmp x0, #3 + b.ne wrong_el + mrs x0, MPIDR_EL1 + and x1, x0, #0xffffff + cbz x1, primary + /* Report only after core 0 has cleared .bss, or the flag would be wiped. */ + ldr x2, =g_ready +5: ldr w3, [x2] + cbnz w3, 6f + wfe + b 5b +6: and x1, x0, #3 + ldr x2, =g_parked + mov w3, #1 + strb w3, [x2, x1] + dsb sy + sev +1: wfe + b 1b + +primary: + ldr x0, =__stack_top + mov sp, x0 + ldr x0, =__bss_start + ldr x1, =__bss_end +2: cmp x0, x1 + b.hs 3f + str xzr, [x0], #8 + b 2b +3: ldr x0, =g_ready + mov w1, #1 + str w1, [x0] + dsb sy + sev + bl smoke_main + mov x0, #0 + b smoke_exit + +wrong_el: + mov x0, #2 + b smoke_exit + +/* Semihosting SYS_EXIT: x0 = exit code, reported through QEMU's exit status. */ + .text + .globl smoke_exit +smoke_exit: + ldr x1, =g_exit_block + ldr x2, =0x20026 + str x2, [x1] + str x0, [x1, #8] + mov x0, #0x18 + hlt #0xf000 +4: wfi + b 4b + + .bss + .balign 16 +g_exit_block: + .skip 16 diff --git a/tests/firmware/aarch64-smoke/smoke.c b/tests/firmware/aarch64-smoke/smoke.c new file mode 100644 index 00000000..7ecf73e0 --- /dev/null +++ b/tests/firmware/aarch64-smoke/smoke.c @@ -0,0 +1,130 @@ +/* smoke.c + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfTrust. + * + * wolfTrust 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. + * + * wolfTrust 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, see . + */ + +/* EL3 smoke for the QEMU AArch64 scenario runner: prints the exception + * level, the machine, the generic timer frequency, and which secondary + * cores reached the park loop, then returns so the entry code exits. */ + +#include + +#define UART_DR 0x00u +#define UART_FR 0x18u +#define UART_FR_TXFF (1u << 5) +#define PARK_SLOTS 4u +#define PARK_WAIT_MS 200u + +volatile uint8_t g_parked[PARK_SLOTS]; +volatile uint32_t g_ready; + +static volatile uint32_t* uart_reg(uint32_t offset) +{ + return (volatile uint32_t*)(uintptr_t)(WT_SMOKE_UART + offset); +} + +/* QEMU's PL011 transmits without CR/IBRD setup; this smoke never runs on silicon. */ +static void put_char(char c) +{ + while ((*uart_reg(UART_FR) & UART_FR_TXFF) != 0u) { + } + *uart_reg(UART_DR) = (uint32_t)(uint8_t)c; +} + +static void put_str(const char* s) +{ + while (*s != '\0') { + put_char(*s); + s++; + } +} + +static void put_hex(uint64_t value) +{ + static const char digits[] = "0123456789abcdef"; + char buf[17]; + int i = 16; + + buf[i] = '\0'; + do { + i--; + buf[i] = digits[value & 0xFu]; + value >>= 4; + } while (value != 0u && i > 0); + put_str(&buf[i]); +} + +static void put_dec(uint64_t value) +{ + char buf[21]; + int i = 20; + + buf[i] = '\0'; + do { + i--; + buf[i] = (char)('0' + (value % 10u)); + value /= 10u; + } while (value != 0u && i > 0); + put_str(&buf[i]); +} + +static uint64_t cntpct(void) +{ + uint64_t value; + + __asm__ volatile("isb; mrs %0, CNTPCT_EL0" : "=r"(value)); + return value; +} + +static uint32_t parked_mask(void) +{ + uint32_t mask = 0u; + uint32_t i; + + for (i = 0u; i < PARK_SLOTS; i++) { + if (g_parked[i] != 0u) { + mask |= (1u << i); + } + } + return mask; +} + +void smoke_main(void) +{ + uint64_t current_el; + uint64_t cntfrq; + uint64_t deadline; + uint32_t expected = (uint32_t)((1u << WT_SMOKE_CPUS) - 2u); + uint32_t mask; + + __asm__ volatile("mrs %0, CurrentEL" : "=r"(current_el)); + __asm__ volatile("mrs %0, CNTFRQ_EL0" : "=r"(cntfrq)); + + deadline = cntpct() + ((cntfrq * PARK_WAIT_MS) / 1000u); + do { + mask = parked_mask(); + } while (mask != expected && cntpct() < deadline); + + put_str("[SMOKE] EL"); + put_dec(current_el >> 2); + put_str(" machine=" WT_SMOKE_MACHINE " cntfrq="); + put_dec(cntfrq); + put_str(" parked_mask=0x"); + put_hex(mask); + put_str("\r\n[SMOKE] exit 0\r\n"); +} diff --git a/tests/firmware/aarch64-smoke/smoke.ld b/tests/firmware/aarch64-smoke/smoke.ld new file mode 100644 index 00000000..d453000a --- /dev/null +++ b/tests/firmware/aarch64-smoke/smoke.ld @@ -0,0 +1,39 @@ +/* Code at WT_SMOKE_BASE (flash on virt, OCM on versal-virt), bss and the + * stack at WT_SMOKE_RAM; both come from the Makefile as --defsym values. */ +ENTRY(_start) + +SECTIONS +{ + . = WT_SMOKE_BASE; + .text : { + KEEP(*(.text.entry)) + *(.text*) + } + .rodata : { + *(.rodata*) + } + .data : { + *(.data*) + } + + . = WT_SMOKE_RAM; + .bss (NOLOAD) : { + __bss_start = .; + *(.bss*) + *(COMMON) + . = ALIGN(16); + __bss_end = .; + } + . = ALIGN(16); + . += 0x4000; + __stack_top = .; + + /DISCARD/ : { + *(.note*) + *(.comment) + *(.eh_frame*) + } +} + +/* .data would sit in read-only flash on virt; the smoke keeps state in .bss. */ +ASSERT(SIZEOF(.data) == 0, "aarch64-smoke must not use .data") diff --git a/tests/host/Makefile b/tests/host/Makefile index 4e82e96a..2e055747 100644 --- a/tests/host/Makefile +++ b/tests/host/Makefile @@ -38,12 +38,15 @@ UNIT_SUITES := domain manifest lifecycle guest_verify rollback sp_recovery \ keystore_isolation \ attestation_service attestation attestation_token \ attestation_golden attestation_negatives attestation_replay \ - attestation_iak ffm_domain sp_layout tasklet_priv port_binding silicon_rev boot_handoff \ - spm_gate \ + attestation_iak ffm_domain sp_layout tasklet_priv port_binding silicon_rev \ + boot_handoff ffa_abi \ + aarch64_esr aarch64_libc ffa_boot_info aarch64_tables aarch64_domain ffa_spmd \ + aarch64_profile \ + ffa_mem ffa_notif ffa_runtime spm_irq spm_gate \ flash_nvm ffm_veneer secure_layout \ vault_service storage_service ps_service keystore_ipc negatives qcbor_shim \ fwu_service runtime_verify psa_ffm_client psa_storage_client vnet_relay \ - psa_headers + psa_headers psa_ffa_transport acs_pal .PHONY: all test test-compilers test-sanitize test-valgrind print-suites clean diff --git a/tests/host/aarch64_domain/Makefile b/tests/host/aarch64_domain/Makefile new file mode 100644 index 00000000..6582aa02 --- /dev/null +++ b/tests/host/aarch64_domain/Makefile @@ -0,0 +1,62 @@ +# Makefile +# +# Copyright (C) 2026 wolfSSL Inc. +# +# This file is part of wolfTrust. +# +# wolfTrust 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. +# +# wolfTrust 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 + +ROOT := ../../.. + +CC ?= cc +BUILD_DIR ?= build + +CFLAGS := \ + -I$(ROOT)/include \ + -std=c99 -O0 -g -Wall -Wextra -Werror -pedantic +CFLAGS += $(EXTRA_CFLAGS) + +SRCS := $(ROOT)/src/arch/aarch64/spm/domain.c $(ROOT)/src/arch/aarch64/spm/tables.c main.c +TEST_BIN := $(BUILD_DIR)/test_aarch64_domain + +.PHONY: all run compilers sanitize valgrind clean + +all: $(TEST_BIN) + +$(BUILD_DIR): + mkdir -p $@ + +$(TEST_BIN): $(SRCS) | $(BUILD_DIR) + $(CC) $(CFLAGS) $(EXTRA_LDFLAGS) -o $@ $(SRCS) + +run: $(TEST_BIN) + $(TEST_BIN) + +compilers: + $(MAKE) clean run CC=gcc BUILD_DIR=$(BUILD_DIR)/gcc + $(MAKE) clean run CC=clang BUILD_DIR=$(BUILD_DIR)/clang + +sanitize: + $(MAKE) clean run CC=clang \ + BUILD_DIR=$(BUILD_DIR)/sanitize \ + EXTRA_CFLAGS="-fsanitize=address,undefined -fno-omit-frame-pointer" \ + EXTRA_LDFLAGS="-fsanitize=address,undefined" + +valgrind: clean $(TEST_BIN) + valgrind --error-exitcode=1 --leak-check=full --show-leak-kinds=all \ + $(TEST_BIN) + +clean: + rm -rf $(BUILD_DIR) diff --git a/tests/host/aarch64_domain/main.c b/tests/host/aarch64_domain/main.c new file mode 100644 index 00000000..9d02d199 --- /dev/null +++ b/tests/host/aarch64_domain/main.c @@ -0,0 +1,392 @@ +/* main.c + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfTrust. + * + * wolfTrust 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. + * + * wolfTrust 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, see . + */ + +/* WT-PORT-0014 (domain rows): the AArch64 domain operations switch TTBR0 + * to one prebuilt table per partition region set, cache by the stable + * regions pointer, restore the SPM-only table, and fail closed. */ + +#include "wolftrust/arch.h" +#include "wolftrust/arch/aarch64/domain.h" +#include "wolftrust/arch/aarch64/tables.h" +#include "wolftrust/sched/coroutine_internal.h" + +#include +#include +#include + +#define POOL_PAGES 256u +#define POOL_PA 0x0E041000ull + +static int checks; +static int failures; +static uint8_t g_pool_mem[POOL_PAGES * WT_TABLES_PAGE_SIZE] + __attribute__((aligned(4096))); +static uint64_t g_switched_to; +static unsigned int g_switches; +static int g_last_fail; +static unsigned int g_fails; + +void wt_mmu_switch_ttbr0(uint64_t ttbr0) +{ + g_switched_to = ttbr0; + g_switches++; +} + +static uint64_t g_tlbi_asid; +static unsigned int g_tlbis; + +void wt_mmu_tlbi_asid(uint64_t asid) +{ + g_tlbi_asid = asid; + g_tlbis++; +} + +static uint64_t g_sync_va; +static uint64_t g_sync_size; +static uint64_t g_sync_ttbr0; +static unsigned int g_syncs; + +void wt_mmu_sync_icache(uint64_t va, uint64_t size) +{ + g_sync_va = va; + g_sync_size = size; + g_sync_ttbr0 = g_switched_to; + g_syncs++; +} + +void wt_domain_fail(int code) +{ + g_last_fail = code; + g_fails++; +} + +static void check(int ok, const char* what) +{ + checks++; + if (ok) { + printf(" [check] PASS %s\n", what); + } + else { + failures++; + printf(" [check] FAIL %s\n", what); + } +} + +#define RW (WT_MEM_ATTR_READ | WT_MEM_ATTR_WRITE) +#define RX (WT_MEM_ATTR_READ | WT_MEM_ATTR_EXEC) + +static const wt_memory_region_t g_fill[] = { + { 0x00000000u, 0x20000u, RX | WT_DOMAIN_FILL_SHARED }, + { 0x0E000000u, 0x40000u, RW }, + { (uintptr_t)POOL_PA, POOL_PAGES * WT_TABLES_PAGE_SIZE, RW }, + { 0x09040000u, 0x1000u, RW | WT_MEM_ATTR_DEVICE }, + { 0x0E500000u, 0x2000u, RW | WT_DOMAIN_FILL_SHARED | WT_DOMAIN_FILL_OWNED } +}; + +/* Exactly the owned band: its owner's table takes it over. */ +static const wt_memory_region_t g_sp_owned_exact[] = { + { 0x0E500000u, 0x2000u, RW } +}; + +/* A wider region over the owned band is someone else claiming it. */ +static const wt_memory_region_t g_sp_owned_cover[] = { + { 0x0E4FF000u, 0x4000u, RW } +}; + +/* Public text, a band of domain 3, an SPMC band, and SPM RAM. */ +static const wt_memory_region_t g_own_fill[] = { + { 0x0E100000u, 0x10000u, RX | WT_DOMAIN_FILL_SHARED }, + { 0x0E240000u, 0x4000u, RW | WT_DOMAIN_FILL_SHARED | WT_DOMAIN_FILL_OWNED }, + { 0x0E2A0000u, 0x4000u, RW | WT_DOMAIN_FILL_SHARED | WT_DOMAIN_FILL_OWNED }, + { 0x0E200000u, 0x40000u, RW } +}; +static const uint32_t g_own_owner[] = { 0u, 3u, WT_DOMAIN_ID_INVALID, 0u }; + +static void owner_rows(void) +{ + const size_t n = sizeof(g_own_fill) / sizeof(g_own_fill[0]); + + check(wt_domain_fill_foreign(g_own_fill, g_own_owner, n, 3u, 0x0E240000u, + 0x4000u) == 0 && + wt_domain_fill_foreign(g_own_fill, g_own_owner, n, 3u, 0x0E100000u, + 0x100000u) == 0, + "a partition may name its own band and the public image"); + check(wt_domain_fill_foreign(g_own_fill, g_own_owner, n, 4u, 0x0E243000u, + 0x2000u) != 0 && + wt_domain_fill_foreign(g_own_fill, g_own_owner, n, 4u, 0x0E200000u, + 0x100000u) != 0, + "one page of, or a range covering, another partition's band is refused"); + check(wt_domain_fill_foreign(g_own_fill, g_own_owner, n, 3u, 0x0E2A0000u, + 0x4000u) != 0, + "no manifest partition may name an SPMC, echo, or native band, even exactly"); + check(wt_domain_fill_foreign(g_own_fill, g_own_owner, n, 3u, + (uintptr_t)UINTPTR_MAX - 0xFFFu, 0x2000u) != 0, + "a range that wraps is refused"); +} + + +/* Covers the shared text fill entry exactly: allowed, replaces it. */ +static const wt_memory_region_t g_sp_shared[] = { + { 0x00000000u, 0x20000u, RX }, + { 0x0E600000u, 0x1000u, RW } +}; + +/* Covers only part of the shared text fill entry: still an overlap. */ +static const wt_memory_region_t g_sp_partial[] = { + { 0x00000000u, 0x10000u, RX }, + { 0x0E601000u, 0x1000u, RW } +}; + +/* One private page each, distinct region sets: as many tables as the cache + * must hold for every partition plus the retained boot proofs and a borrower + * rebuild, and one more to prove the cap. */ +static wt_memory_region_t g_many[WT_DOMAIN_MAX_TABLES + 1u][1]; + +static void capacity_rows(void) +{ + size_t built = wt_domain_tables_built(); + size_t fails = g_fails; + size_t i; + int ok = 1; + + check(WT_DOMAIN_MAX_TABLES >= (WT_CO_MAX + WT_DOMAIN_PROOF_TABLES + 1u), + "the table cache holds every partition, the five boot proof domains, " + "and a borrower rebuild"); + for (i = 0u; (built + i) < WT_DOMAIN_MAX_TABLES; i++) { + g_many[i][0].base = 0x0E700000u + (i * 0x1000u); + g_many[i][0].size = 0x1000u; + g_many[i][0].attributes = RW; + wt_arch_program_sp_thread_domain(g_many[i], 1u); + ok = ok && (g_fails == fails) && + (wt_domain_tables_built() == built + i + 1u); + } + check(ok && wt_domain_tables_built() == WT_DOMAIN_MAX_TABLES, + "distinct region sets build up to the cache size without a failure"); + g_many[i][0].base = 0x0E700000u + (i * 0x1000u); + g_many[i][0].size = 0x1000u; + g_many[i][0].attributes = RW; + wt_arch_program_sp_thread_domain(g_many[i], 1u); + check(g_fails == fails + 1u && g_last_fail == WT_DOMAIN_FAIL_SLOTS && + wt_domain_tables_built() == WT_DOMAIN_MAX_TABLES, + "one region set past the cache size fails closed on slots"); +} + +static const wt_memory_region_t g_sp0[] = { + { 0x0E200000u, 0x2000u, RX }, + { 0x0E202000u, 0x2000u, RW } +}; + +static const wt_memory_region_t g_sp1[] = { + { 0x0E400000u, 0x2000u, RX }, + { 0x0E402000u, 0x1000u, RW } +}; + +static const wt_memory_region_t g_bad[] = { + { 0x0E000000u, 0x1000u, RW } +}; + +/* A partition whose declared stack is a strict subrange of its private + * resource, next to a shared band, a plain data band, a scrubbed data band, + * and a device. */ +#define SCRUB (RW | WT_MEMORY_ATTR_RESTART_CLEAR) +static const wt_memory_resource_t g_res[] = { + { 0x0E100000u, 0x100000u, RX | WT_MEMORY_ATTR_SHARED, 3u }, + { 0x0E240000u, 0x10000u, SCRUB, 0u }, + { 0x0E300000u, 0x40000u, RW | WT_MEMORY_ATTR_SHARED, 1u }, + { 0x0E260000u, 0x2000u, RW, 0u }, + { 0x0E270000u, 0x1000u, SCRUB, 0u }, + { 0x09000000u, 0x1000u, SCRUB | WT_MEM_ATTR_DEVICE, 0u } +}; + +static void stack_band_rows(void) +{ + wt_domain_descriptor_t d; + wt_memory_region_t band; + size_t i; + unsigned int mask = 0u; + + (void)memset(&d, 0, sizeof(d)); + d.domain_class = WT_DOMAIN_CLASS_SECURE_PARTITION; + d.memory_resources = g_res; + d.memory_resource_count = sizeof(g_res) / sizeof(g_res[0]); + d.stack_base = 0x0E244000u; + d.stack_size = 0x4000u; + check(wt_domain_stack_band(&d, &band) == 0 && band.base == 0x0E240000u && + band.size == 0x10000u && band.attributes == RW, + "a declared stack inside a larger private resource maps the whole resource the scheduler seeds"); + for (i = 0u; i < d.memory_resource_count; i++) { + if (wt_domain_spm_band(&d, i, &band) == 0) { + mask |= 1u << i; + } + } + check(mask == ((1u << 1) | (1u << 4)), + "the SPMC maps the stack resource and a scrubbed private band, never a shared, plain, or device one"); + d.stack_base = 0x0E300000u; + check(wt_domain_stack_band(&d, &band) == -1, + "a declared stack only a shared band holds has no stack band"); + d.stack_size = 0u; + check(wt_domain_stack_band(&d, &band) == 0 && band.base == 0x0E270000u, + "with no declared stack the last private writable resource is the stack"); +} + +int main(void) +{ + uint64_t spm; + uint64_t sp0; + uint64_t sp1; + size_t used; + unsigned int switches; + unsigned int fails; + size_t built; + unsigned int tlbis; + uint32_t attrs; + + printf("WT-PORT-0014 (AArch64 domain operations)\n"); + stack_band_rows(); + + wt_arch_program_sp_thread_domain(g_sp0, 2u); + check(g_fails == 1u && g_last_fail == WT_DOMAIN_FAIL_INIT && g_switches == 0u, + "a domain switch before init fails closed and never touches TTBR0"); + wt_arch_restore_spm_domain(); + check(g_switches == 0u, "restore before init is a no-op"); + + spm = wt_domain_init(g_fill, sizeof(g_fill) / sizeof(g_fill[0]), g_pool_mem, + POOL_PA, sizeof(g_pool_mem)); + check(spm == (POOL_PA | 0ull) && wt_domain_current_ttbr0() == spm && + wt_domain_tables_built() == 0u, + "init builds the SPM-only table with ASID 0 and no partition tables"); + used = wt_domain_pool_pages_used(); + + wt_arch_program_sp_thread_domain(g_sp0, 2u); + sp0 = wt_domain_current_ttbr0(); + check(g_switches == 1u && g_switched_to == sp0 && (sp0 >> 48) == 1u && + wt_domain_tables_built() == 1u && wt_domain_pool_pages_used() > used, + "the first partition gets ASID 1, its own L1, and TTBR0 switches to it"); + used = wt_domain_pool_pages_used(); + switches = g_switches; + wt_arch_program_sp_thread_domain(g_sp0, 2u); + check(g_switches == switches + 1u && g_switched_to == sp0 && + wt_domain_tables_built() == 1u && wt_domain_pool_pages_used() == used, + "the same regions pointer reuses the table (no pool growth)"); + + wt_arch_program_secure_partition_domain(g_sp1, 2u); + sp1 = wt_domain_current_ttbr0(); + check((sp1 >> 48) == 2u && sp1 != sp0 && wt_domain_tables_built() == 2u, + "a second partition gets ASID 2 through the privileged variant too"); + + wt_arch_restore_spm_domain(); + check(g_switched_to == spm && wt_domain_current_ttbr0() == spm, + "restore switches back to the SPM-only table"); + + switches = g_switches; + wt_arch_program_sp_thread_domain(g_bad, 1u); + check(g_fails == 2u && g_last_fail == WT_DOMAIN_FAIL_BUILD && + g_switches == switches && wt_domain_tables_built() == 2u, + "regions over the SPM fill fail the build and leave TTBR0 alone"); + + switches = g_switches; + wt_arch_program_sp_thread_domain(g_sp_shared, 2u); + check(g_fails == 2u && g_switches == switches + 1u && + (wt_domain_current_ttbr0() >> 48) == 3u && wt_domain_tables_built() == 3u, + "a partition region covering a shareable fill entry replaces it (ASID 3)"); + + switches = g_switches; + wt_arch_program_sp_thread_domain(g_sp_partial, 2u); + check(g_fails == 3u && g_last_fail == WT_DOMAIN_FAIL_BUILD && + g_switches == switches && wt_domain_tables_built() == 3u, + "partial cover of a shareable fill entry still fails the build"); + + attrs = 0u; + check(wt_domain_get_permissions(g_sp0, 2u, 0x0E201000u, &attrs) == + WT_TABLES_OK && attrs == RX, + "a partition reads back its code page as read-execute"); + check(wt_domain_set_permissions(g_sp0, 2u, 0x0E201000u, 1u, RW) == + WT_TABLES_OK && g_tlbis == 1u && g_tlbi_asid == 1u && + wt_domain_get_permissions(g_sp0, 2u, 0x0E201000u, &attrs) == + WT_TABLES_OK && attrs == RW, + "re-permissioning an owned page invalidates exactly that domain's ASID"); + check(wt_domain_set_permissions(g_sp0, 2u, 0x0E400000u, 1u, RW) == + WT_TABLES_ERROR_UNMAPPED && + wt_domain_set_permissions(g_sp0, 2u, 0x0E203000u, 2u, RW) == + WT_TABLES_ERROR_UNMAPPED && + wt_domain_set_permissions(g_sp0, 2u, 0x0E000000u, 1u, RW) == + WT_TABLES_ERROR_UNMAPPED && g_tlbis == 1u, + "another partition's page, a range past the region end, and SPM memory are refused"); + check(wt_domain_get_permissions(g_sp0, 2u, 0x0E201001u, &attrs) == + WT_TABLES_ERROR_ALIGN, + "a base address off its translation granule is refused (DEN0140 Table 2.37)"); + check(wt_domain_set_permissions(g_bad, 1u, 0x0E000000u, 1u, RW) == + WT_TABLES_ERROR_ARGUMENT && + wt_domain_get_permissions(g_sp0, 2u, 0x0E201000u, NULL) == + WT_TABLES_ERROR_ARGUMENT, + "a domain that was never built and a NULL result are refused"); + check(wt_domain_owner_hold(g_sp0, 2u, 0x0E201000u, 1u, 0) == WT_TABLES_OK && + wt_domain_get_permissions(g_sp0, 2u, 0x0E201000u, &attrs) == + WT_TABLES_OK && attrs == 0u && + wt_domain_owner_release(g_sp0, 2u, 0x0E201000u, 1u) == WT_TABLES_OK && + wt_domain_get_permissions(g_sp0, 2u, 0x0E201000u, &attrs) == + WT_TABLES_OK && attrs == RW, + "a page a transaction holds reads back as no access, and its own access once released"); + tlbis = g_tlbis; + check(wt_domain_owner_hold(g_sp0, 2u, 0x0E201000u, 1u, 1) == WT_TABLES_OK && + wt_domain_owner_withdraw(g_sp0, 2u, 0x0E201000u, 1u) == + WT_TABLES_OK && g_tlbis == tlbis + 2u && + wt_domain_get_permissions(g_sp0, 2u, 0x0E201000u, &attrs) == + WT_TABLES_OK && attrs == 0u && + wt_domain_owner_release(g_sp0, 2u, 0x0E201000u, 1u) == WT_TABLES_OK && + wt_domain_get_permissions(g_sp0, 2u, 0x0E201000u, &attrs) == + WT_TABLES_OK && attrs == RW, + "a page held for reading can be withdrawn to no access, invalidating the ASID, and is released as it was"); + switches = g_switches; + check(g_syncs == 0u && wt_domain_current_ttbr0() != sp0 && + wt_domain_set_permissions(g_sp0, 2u, 0x0E201000u, 1u, RX) == + WT_TABLES_OK && + g_syncs == 1u && g_sync_va == 0x0E201000u && + g_sync_size == WT_TABLES_PAGE_SIZE && g_sync_ttbr0 == sp0 && + g_switches == switches + 2u && + g_switched_to == wt_domain_current_ttbr0(), + "making a page executable syncs the instruction cache for it under its own table, then switches back"); + check(wt_domain_set_permissions(g_sp0, 2u, 0x0E201000u, 1u, RW) == + WT_TABLES_OK && + wt_domain_owner_hold(g_sp0, 2u, 0x0E202000u, 1u, 0) == WT_TABLES_OK && + wt_domain_owner_release(g_sp0, 2u, 0x0E202000u, 1u) == + WT_TABLES_OK && g_syncs == 1u, + "a data page made or given back execute-never needs no instruction cache sync"); + + fails = g_fails; + switches = g_switches; + built = wt_domain_tables_built(); + wt_arch_program_sp_thread_domain(g_sp_owned_cover, 1u); + check(g_fails == fails + 1u && g_last_fail == WT_DOMAIN_FAIL_BUILD && + g_switches == switches && wt_domain_tables_built() == built, + "a region wider than an owned band never takes it over: the build fails"); + wt_arch_program_sp_thread_domain(g_sp_owned_exact, 1u); + check(g_fails == fails + 1u && g_switches == switches + 1u && + wt_domain_tables_built() == built + 1u && + wt_domain_page_owned(g_sp_owned_exact, 1u, 0x0E501000u) != 0, + "the region that is exactly the owned band takes it over at EL0"); + owner_rows(); + capacity_rows(); + + check(wt_domain_pool_pages_used() <= POOL_PAGES, "pool accounting stays inside the pool"); + + printf("aarch64_domain: %d checks, %d failures\n", checks, failures); + return (failures == 0) ? 0 : 1; +} diff --git a/tests/host/aarch64_esr/Makefile b/tests/host/aarch64_esr/Makefile new file mode 100644 index 00000000..c87d2c9f --- /dev/null +++ b/tests/host/aarch64_esr/Makefile @@ -0,0 +1,62 @@ +# Makefile +# +# Copyright (C) 2026 wolfSSL Inc. +# +# This file is part of wolfTrust. +# +# wolfTrust 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. +# +# wolfTrust 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 + +ROOT := ../../.. + +CC ?= cc +BUILD_DIR ?= build + +CFLAGS := \ + -I$(ROOT)/include \ + -std=c99 -O0 -g -Wall -Wextra -Werror -pedantic +CFLAGS += $(EXTRA_CFLAGS) + +SRCS := $(ROOT)/src/arch/aarch64/el3/esr.c main.c +TEST_BIN := $(BUILD_DIR)/test_aarch64_esr + +.PHONY: all run compilers sanitize valgrind clean + +all: $(TEST_BIN) + +$(BUILD_DIR): + mkdir -p $@ + +$(TEST_BIN): $(SRCS) | $(BUILD_DIR) + $(CC) $(CFLAGS) $(EXTRA_LDFLAGS) -o $@ $(SRCS) + +run: $(TEST_BIN) + $(TEST_BIN) + +compilers: + $(MAKE) clean run CC=gcc BUILD_DIR=$(BUILD_DIR)/gcc + $(MAKE) clean run CC=clang BUILD_DIR=$(BUILD_DIR)/clang + +sanitize: + $(MAKE) clean run CC=clang \ + BUILD_DIR=$(BUILD_DIR)/sanitize \ + EXTRA_CFLAGS="-fsanitize=address,undefined -fno-omit-frame-pointer" \ + EXTRA_LDFLAGS="-fsanitize=address,undefined" + +valgrind: clean $(TEST_BIN) + valgrind --error-exitcode=1 --leak-check=full --show-leak-kinds=all \ + $(TEST_BIN) + +clean: + rm -rf $(BUILD_DIR) diff --git a/tests/host/aarch64_esr/main.c b/tests/host/aarch64_esr/main.c new file mode 100644 index 00000000..863dffda --- /dev/null +++ b/tests/host/aarch64_esr/main.c @@ -0,0 +1,228 @@ +/* main.c + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfTrust. + * + * wolfTrust 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. + * + * wolfTrust 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, see . + */ + +/* Host rows for the AArch64 exception-syndrome decoder: every exception + * class of the EL3 fault table maps to its wolfTrust fault reason, the + * stack-guard and external-abort promotions apply in the documented order, + * and the "[SYNC ...]" line is byte-exact. */ + +#include "wolftrust/arch/aarch64/esr.h" +#include "wolftrust/arch/aarch64/sysreg.h" + +#include +#include +#include + +#define GUARD_BASE 0x0E010000ull +#define GUARD_SIZE 0x1000ull + +static int checks; +static int failures; + +static void check(int ok, const char* what) +{ + checks++; + if (ok) { + printf(" [check] PASS %s\n", what); + } + else { + failures++; + printf(" [check] FAIL %s\n", what); + } +} + +static uint64_t esr_of(uint32_t ec, uint32_t iss) +{ + return ((uint64_t)ec << 26) | (uint64_t)(iss & 0x1FFFFFFu); +} + +static wt_fault_reason_t classify(uint32_t ec, uint32_t iss, uint64_t far, + int from_ns) +{ + return wt_esr_classify(esr_of(ec, iss), far, from_ns, GUARD_BASE, + GUARD_SIZE); +} + +static const uint32_t g_illegal_ecs[] = { + WT_ESR_EC_UNKNOWN, WT_ESR_EC_FP_ACCESS, WT_ESR_EC_ILLEGAL_STATE, + WT_ESR_EC_SYSREG, WT_ESR_EC_BRK +}; + +static const uint32_t g_external_fscs[] = { + WT_ESR_FSC_EXTERNAL, 0x14u, 0x15u, 0x16u, 0x17u, WT_ESR_FSC_PARITY, + 0x1Cu, 0x1Du, 0x1Eu, 0x1Fu +}; +static const uint32_t g_not_external_fscs[] = { + 0x11u, 0x12u, 0x13u, 0x19u, 0x1Au, 0x1Bu, 0x30u, 0x34u, 0x0Du +}; +static const uint32_t g_abort_ecs[] = { + WT_ESR_EC_IABT_LOWER, WT_ESR_EC_IABT_SAME, WT_ESR_EC_DABT_LOWER, + WT_ESR_EC_DABT_SAME +}; + +static const uint32_t g_align_ecs[] = { + WT_ESR_EC_PC_ALIGN, WT_ESR_EC_SP_ALIGN +}; + +static const uint32_t g_platform_ecs[] = { + WT_ESR_EC_SMC32, WT_ESR_EC_SMC64, WT_ESR_EC_SVC64, WT_ESR_EC_SERROR, + 0x3Fu +}; + +int main(void) +{ + char line[80]; + size_t i; + size_t n; + int ok; + uint64_t far_outside = 0x40001000ull; + uint64_t far_inside = GUARD_BASE + 0x10ull; + uint32_t translation = 0x07u; + + printf("aarch64 exception-syndrome decoder (EL3 fault table)\n"); + + ok = 1; + for (i = 0u; i < sizeof(g_illegal_ecs) / sizeof(g_illegal_ecs[0]); i++) { + ok = ok && (classify(g_illegal_ecs[i], 0u, 0u, 0) == + WT_FAULT_ILLEGAL_INSTRUCTION); + ok = ok && (classify(g_illegal_ecs[i], 0u, far_inside, 1) == + WT_FAULT_ILLEGAL_INSTRUCTION); + } + check(ok, "unknown, FP access, illegal state, sysreg trap, and BRK are illegal instructions"); + + ok = 1; + for (i = 0u; i < sizeof(g_abort_ecs) / sizeof(g_abort_ecs[0]); i++) { + ok = ok && (classify(g_abort_ecs[i], translation, far_outside, 0) == + WT_FAULT_MEMORY_VIOLATION); + ok = ok && (classify(g_abort_ecs[i], translation, far_outside, 1) == + WT_FAULT_MEMORY_VIOLATION); + } + check(ok, "instruction and data aborts outside the guard are memory violations"); + + ok = 1; + for (i = 0u; i < sizeof(g_abort_ecs) / sizeof(g_abort_ecs[0]); i++) { + ok = ok && (classify(g_abort_ecs[i], translation, far_inside, 0) == + WT_FAULT_STACK_OVERFLOW); + ok = ok && (classify(g_abort_ecs[i], translation, GUARD_BASE, 1) == + WT_FAULT_STACK_OVERFLOW); + } + check(ok, "aborts whose FAR lands in the stack guard are stack overflows"); + + check(classify(WT_ESR_EC_DABT_LOWER, translation, + GUARD_BASE + GUARD_SIZE, 0) == WT_FAULT_MEMORY_VIOLATION && + classify(WT_ESR_EC_DABT_LOWER, translation, + GUARD_BASE - 1ull, 0) == WT_FAULT_MEMORY_VIOLATION, + "the guard window is [base, base + size)"); + check(wt_esr_classify(esr_of(WT_ESR_EC_DABT_LOWER, translation), far_inside, + 0, GUARD_BASE, 0u) == WT_FAULT_MEMORY_VIOLATION, + "a zero-sized guard never promotes to stack overflow"); + + ok = 1; + for (i = 0u; i < sizeof(g_abort_ecs) / sizeof(g_abort_ecs[0]); i++) { + ok = ok && (classify(g_abort_ecs[i], WT_ESR_FSC_EXTERNAL, far_outside, + 1) == WT_FAULT_SECURE_ESCALATION); + ok = ok && (classify(g_abort_ecs[i], WT_ESR_FSC_EXTERNAL, far_outside, + 0) == WT_FAULT_PLATFORM); + } + check(ok, "synchronous external aborts escalate from NS and are platform faults from Secure"); + check(classify(WT_ESR_EC_DABT_LOWER, WT_ESR_FSC_EXTERNAL, far_inside, 1) == + WT_FAULT_SECURE_ESCALATION && + classify(WT_ESR_EC_DABT_SAME, WT_ESR_FSC_EXTERNAL, far_inside, 0) == + WT_FAULT_PLATFORM, + "an external abort inside the guard keeps its external classification"); + check(classify(WT_ESR_EC_DABT_LOWER, 0x1FFFFC0u | WT_ESR_FSC_EXTERNAL, + far_outside, 1) == WT_FAULT_SECURE_ESCALATION && + classify(WT_ESR_EC_DABT_LOWER, 0x1FFFFC0u | translation, far_outside, + 1) == WT_FAULT_MEMORY_VIOLATION, + "only the FSC bits of the ISS select the external abort"); + + ok = 1; + for (i = 0u; i < sizeof(g_external_fscs) / sizeof(g_external_fscs[0]); + i++) { + ok = ok && (classify(WT_ESR_EC_DABT_LOWER, g_external_fscs[i], + far_outside, 1) == WT_FAULT_SECURE_ESCALATION); + ok = ok && (classify(WT_ESR_EC_IABT_LOWER, g_external_fscs[i], + far_inside, 1) == WT_FAULT_SECURE_ESCALATION); + ok = ok && (classify(WT_ESR_EC_DABT_SAME, g_external_fscs[i], + far_inside, 0) == WT_FAULT_PLATFORM); + } + check(ok, "external aborts and parity errors on a translation-table walk at " + "levels 0-3, and on the access itself, are external, never " + "stack overflows"); + ok = 1; + for (i = 0u; i < sizeof(g_not_external_fscs) / sizeof(g_not_external_fscs[0]); + i++) { + ok = ok && (classify(WT_ESR_EC_DABT_LOWER, g_not_external_fscs[i], + far_outside, 1) == WT_FAULT_MEMORY_VIOLATION); + } + check(ok, "tag check, reserved, TLB conflict, and lockdown codes next to the " + "external encodings stay memory violations"); + + ok = 1; + for (i = 0u; i < sizeof(g_align_ecs) / sizeof(g_align_ecs[0]); i++) { + ok = ok && (classify(g_align_ecs[i], 0u, far_inside, 0) == + WT_FAULT_MEMORY_VIOLATION); + } + check(ok, "PC and SP alignment faults are memory violations, never stack overflows"); + + ok = 1; + for (i = 0u; i < sizeof(g_platform_ecs) / sizeof(g_platform_ecs[0]); i++) { + ok = ok && (classify(g_platform_ecs[i], 0u, far_inside, 1) == + WT_FAULT_PLATFORM); + } + check(ok, "SMC, SVC, SError, and unlisted classes fall to the platform reason"); + check(classify(WT_ESR_EC_DABT_LOWER, translation, far_outside, 0) != + WT_FAULT_NONE && + classify(0x3Fu, 0u, 0u, 0) != WT_FAULT_NONE, + "no syndrome decodes to no fault"); + + n = wt_esr_format(line, sizeof(line), 1u, + esr_of(WT_ESR_EC_DABT_LOWER, 0x000047u), 0x0E001000ull); + check(strcmp(line, "[SYNC EL=1 EC=0x24 ISS=0x0000047 FAR=0x000000000e001000]") == 0, + "data abort line is byte-exact"); + check(n == strlen(line), "the returned length is the string length"); + + n = wt_esr_format(line, sizeof(line), 3u, + esr_of(WT_ESR_EC_BRK, 0x1FFFFFFu) | (1ull << 25), + 0xFFFFFFFFFFFFFFFFull); + check(strcmp(line, "[SYNC EL=3 EC=0x3c ISS=0x1ffffff FAR=0xffffffffffffffff]") == 0, + "ISS prints all 25 bits (bit 24 included, IL excluded), FAR 16 digits, EL one digit"); + check(n == 56u, "the widest line is 56 characters"); + + n = wt_esr_format(line, sizeof(line), 2u, esr_of(WT_ESR_EC_SMC64, 0u), 0u); + check(strcmp(line, "[SYNC EL=2 EC=0x17 ISS=0x0000000 FAR=0x0000000000000000]") == 0, + "zero fields keep their fixed widths"); + + memset(line, 'x', sizeof(line)); + n = wt_esr_format(line, 12u, 1u, esr_of(WT_ESR_EC_DABT_LOWER, 0x47u), + 0x0E001000ull); + check(n == 11u && line[11] == '\0' && + strcmp(line, "[SYNC EL=1 ") == 0 && line[12] == 'x', + "a short buffer truncates at size-1 and stays NUL-terminated"); + memset(line, 'x', sizeof(line)); + n = wt_esr_format(line, 1u, 1u, 0u, 0u); + check(n == 0u && line[0] == '\0', "a one-byte buffer holds only the NUL"); + check(wt_esr_format(NULL, sizeof(line), 1u, 0u, 0u) == 0u && + wt_esr_format(line, 0u, 1u, 0u, 0u) == 0u, + "a NULL or empty buffer writes nothing"); + + printf("aarch64_esr: %d checks, %d failures\n", checks, failures); + return (failures == 0) ? 0 : 1; +} diff --git a/tests/host/aarch64_libc/Makefile b/tests/host/aarch64_libc/Makefile new file mode 100644 index 00000000..45f4616b --- /dev/null +++ b/tests/host/aarch64_libc/Makefile @@ -0,0 +1,69 @@ +# Makefile +# +# Copyright (C) 2026 wolfSSL Inc. +# +# This file is part of wolfTrust. +# +# wolfTrust 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. +# +# wolfTrust 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 + +ROOT := ../../.. + +CC ?= cc +BUILD_DIR ?= build + +CFLAGS := \ + -I$(ROOT)/include \ + -std=c99 -O0 -g -Wall -Wextra -Werror -pedantic +CFLAGS += $(EXTRA_CFLAGS) + +LIBC_OBJ := $(BUILD_DIR)/libc_min.o +LIBC_RENAME := -Dmemset=wt_min_memset -Dmemcpy=wt_min_memcpy \ + -Dmemmove=wt_min_memmove -Dmemcmp=wt_min_memcmp +TEST_BIN := $(BUILD_DIR)/test_aarch64_libc + +.PHONY: all run compilers sanitize valgrind clean + +all: $(TEST_BIN) + +$(BUILD_DIR): + mkdir -p $@ + +$(LIBC_OBJ): $(ROOT)/src/arch/aarch64/common/libc_min.c | $(BUILD_DIR) + $(CC) $(CFLAGS) -fno-builtin $(LIBC_RENAME) -c -o $@ $< + +$(TEST_BIN): main.c $(LIBC_OBJ) | $(BUILD_DIR) + $(CC) $(CFLAGS) $(EXTRA_LDFLAGS) -o $@ main.c $(LIBC_OBJ) + +run: $(TEST_BIN) + $(TEST_BIN) + +compilers: + $(MAKE) clean run CC=gcc BUILD_DIR=$(BUILD_DIR)/gcc + $(MAKE) clean run CC=clang BUILD_DIR=$(BUILD_DIR)/clang + +# pointer-compare flags a relational comparison of pointers into different +# objects, which memmove must not rely on. +sanitize: + ASAN_OPTIONS=detect_invalid_pointer_pairs=2 $(MAKE) clean run CC=clang \ + BUILD_DIR=$(BUILD_DIR)/sanitize \ + EXTRA_CFLAGS="-fsanitize=address,pointer-compare,undefined -fno-omit-frame-pointer" \ + EXTRA_LDFLAGS="-fsanitize=address,pointer-compare,undefined" + +valgrind: clean $(TEST_BIN) + valgrind --error-exitcode=1 --leak-check=full --show-leak-kinds=all \ + $(TEST_BIN) + +clean: + rm -rf $(BUILD_DIR) diff --git a/tests/host/aarch64_libc/main.c b/tests/host/aarch64_libc/main.c new file mode 100644 index 00000000..92bdee4e --- /dev/null +++ b/tests/host/aarch64_libc/main.c @@ -0,0 +1,109 @@ +/* main.c + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfTrust. + * + * wolfTrust 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. + * + * wolfTrust 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, see . + */ + +/* Host rows for the freestanding AArch64 memmove: overlap in both directions + * and separately allocated buffers, whose relative order it must not ask the + * compiler for (the build renames the routines so the host libc stays in + * use). */ + +#include +#include +#include +#include +#include + +void* wt_min_memmove(void* dest, const void* src, size_t count); + +static int checks; +static int failures; + +static void check(int ok, const char* what) +{ + checks++; + if (ok) { + printf(" [check] PASS %s\n", what); + } + else { + failures++; + printf(" [check] FAIL %s\n", what); + } +} + +static void fill(uint8_t* buf, size_t n) +{ + size_t i; + + for (i = 0u; i < n; i++) { + buf[i] = (uint8_t)(i + 1u); + } +} + +int main(void) +{ + static const uint8_t forward[16] = { + 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 15, 16 + }; + static const uint8_t backward[16] = { + 1, 2, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14 + }; + uint8_t buf[16]; + uint8_t* heap_src; + uint8_t* heap_dst; + uint8_t stack_dst[16]; + + fill(buf, sizeof(buf)); + check(wt_min_memmove(buf, buf + 2, 14u) == buf && + memcmp(buf, forward, sizeof(buf)) == 0, + "an overlapping move to a lower address copies forward intact"); + + fill(buf, sizeof(buf)); + check(wt_min_memmove(buf + 2, buf, 14u) == buf + 2 && + memcmp(buf, backward, sizeof(buf)) == 0, + "an overlapping move to a higher address copies backward intact"); + + heap_src = (uint8_t*)malloc(16u); + heap_dst = (uint8_t*)malloc(16u); + if ((heap_src == NULL) || (heap_dst == NULL)) { + free(heap_src); + free(heap_dst); + printf("aarch64_libc: allocation failed\n"); + return 1; + } + fill(heap_src, 16u); + (void)memset(heap_dst, 0, 16u); + (void)wt_min_memmove(heap_dst, heap_src, 16u); + check(memcmp(heap_dst, heap_src, 16u) == 0, + "a move between separately allocated buffers copies them"); + (void)memset(stack_dst, 0, sizeof(stack_dst)); + (void)wt_min_memmove(stack_dst, heap_src, sizeof(stack_dst)); + check(memcmp(stack_dst, heap_src, sizeof(stack_dst)) == 0, + "a move from the heap onto the stack copies it"); + (void)memset(heap_dst, 0, 16u); + (void)wt_min_memmove(heap_dst, stack_dst, 16u); + check(memcmp(heap_dst, stack_dst, 16u) == 0, + "a move from the stack onto the heap copies it"); + check(wt_min_memmove(heap_dst, heap_src, 0u) == heap_dst, + "a zero-length move returns the destination"); + free(heap_src); + free(heap_dst); + + printf("aarch64_libc: %d checks, %d failures\n", checks, failures); + return (failures == 0) ? 0 : 1; +} diff --git a/tests/host/aarch64_profile/Makefile b/tests/host/aarch64_profile/Makefile new file mode 100644 index 00000000..358cf870 --- /dev/null +++ b/tests/host/aarch64_profile/Makefile @@ -0,0 +1,116 @@ +# Makefile +# +# Copyright (C) 2026 wolfSSL Inc. +# +# This file is part of wolfTrust. +# +# wolfTrust 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. +# +# wolfTrust 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, see . + +ROOT := ../../.. + +CC ?= cc +BUILD_DIR ?= build + +CFLAGS := \ + -I$(ROOT)/include \ + -std=c99 -O0 -g -Wall -Wextra -Werror -pedantic \ + -DWT_SPM_TABLE_POOL_PAGES=128u +CFLAGS += $(EXTRA_CFLAGS) + +GENERATOR := $(ROOT)/tools/manifest/generate.py +GEN_OPTS := --supported-features 0x1 --supported-framework-version 0x100 \ + --address-bits 64 --mpu-granule 4096 --spm-table-pages 8 +SRCS := $(ROOT)/src/domain.c $(ROOT)/src/manifest.c $(ROOT)/src/spm.c \ + $(ROOT)/src/partition.c $(ROOT)/port/common/aarch64/partitions.c main.c + +# ::: +VARIANTS := qemuvirt:qemuvirt:manifest:1 \ + qemuvirt-conf:qemuvirt:manifest-conformance:1 \ + versal:versal:manifest:0 \ + versal-conf:versal:manifest-conformance:0 + +field = $(word $(2),$(subst :, ,$(1))) +BINS := $(foreach v,$(VARIANTS),$(BUILD_DIR)/test_$(call field,$(v),1)) + +.PHONY: all run compilers sanitize valgrind clean + +all: $(BINS) + +define variant_rule +$(BUILD_DIR)/$(call field,$(1),1)/wolftrust_manifest_generated.c: \ + $(GENERATOR) $(ROOT)/port/$(call field,$(1),2)/$(call field,$(1),3).json + python3 $(GENERATOR) $(ROOT)/port/$(call field,$(1),2)/$(call field,$(1),3).json \ + $(BUILD_DIR)/$(call field,$(1),1) $(GEN_OPTS) > /dev/null + +$(BUILD_DIR)/test_$(call field,$(1),1): $(SRCS) \ + $(ROOT)/port/$(call field,$(1),2)/memory_map.h \ + $(BUILD_DIR)/$(call field,$(1),1)/wolftrust_manifest_generated.c + $(CC) $(CFLAGS) -I$(ROOT)/port/$(call field,$(1),2) \ + -I$(BUILD_DIR)/$(call field,$(1),1) \ + -DEXPECT_NS_FENCE=$(call field,$(1),4) \ + -DVARIANT_NAME='"$(call field,$(1),1)"' $(EXTRA_LDFLAGS) -o $$@ \ + $(SRCS) $(BUILD_DIR)/$(call field,$(1),1)/wolftrust_manifest_generated.c +endef +$(foreach v,$(VARIANTS),$(eval $(call variant_rule,$(v)))) + +# DEN0022 5.11: the reset budget that ends an emulator run on SYSTEM_RESET +# builds only for a port marked emulated. +PSCI_SRC := $(ROOT)/src/arch/aarch64/el3/psci.c +# The EL3 parking state holds WT_EL3_MAX_CPUS cores; a port may declare no +# more, and no fewer than its boot core. +EL3_MAIN_CPP := $(CC) -E -I$(ROOT)/include -DWT_SPM_BOOT_INFO_PA=0x0E040000u \ + $(ROOT)/src/arch/aarch64/el3/el3_main.c + +run: $(BINS) + @set -e; for bin in $(BINS); do $$bin; done + @$(CC) -E -I$(ROOT)/include -DWT_EL3_RESET_LIMIT=1u -DWT_PORT_EMULATED=1 \ + $(PSCI_SRC) > /dev/null + @if $(CC) -E -I$(ROOT)/include -DWT_EL3_RESET_LIMIT=1u $(PSCI_SRC) 2>&1 | \ + grep -q "emulated targets only"; then \ + echo " [check] PASS a reset budget builds only for an emulated port"; \ + else \ + echo " [check] FAIL a reset budget builds for a port not marked emulated"; \ + exit 1; \ + fi + @for n in 1u 4u; do \ + $(EL3_MAIN_CPP) -DWT_PORT_BOOT_CPUS=$$n > /dev/null || exit 1; \ + done; \ + echo " [check] PASS a port declaring 1 to WT_EL3_MAX_CPUS boot cores builds" + @for n in 0u 5u 32u; do \ + if ! $(EL3_MAIN_CPP) -DWT_PORT_BOOT_CPUS=$$n 2>&1 | \ + grep -q "must be between"; then \ + echo " [check] FAIL WT_PORT_BOOT_CPUS=$$n builds"; \ + exit 1; \ + fi; \ + done; \ + echo " [check] PASS no boot cores, or more than the EL3 parking state holds, is refused" + +compilers: + $(MAKE) clean run CC=gcc BUILD_DIR=$(BUILD_DIR)/gcc + $(MAKE) clean run CC=clang BUILD_DIR=$(BUILD_DIR)/clang + +sanitize: + $(MAKE) clean run CC=clang \ + BUILD_DIR=$(BUILD_DIR)/sanitize \ + EXTRA_CFLAGS="-fsanitize=address,undefined -fno-omit-frame-pointer" \ + EXTRA_LDFLAGS="-fsanitize=address,undefined" + +valgrind: clean $(BINS) + @set -e; for bin in $(BINS); do \ + valgrind --error-exitcode=1 --leak-check=full \ + --show-leak-kinds=all $$bin; \ + done + +clean: + rm -rf $(BUILD_DIR) diff --git a/tests/host/aarch64_profile/main.c b/tests/host/aarch64_profile/main.c new file mode 100644 index 00000000..ce958d3c --- /dev/null +++ b/tests/host/aarch64_profile/main.c @@ -0,0 +1,104 @@ +/* main.c + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfTrust. + * + * wolfTrust 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. + * + * wolfTrust 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, see . + */ + +/* WT-PORT-0009 (AArch64 rows): a port claims security-state isolation, and + * so any isolation level, only when its memory map fences the Secure bands + * from the Normal world, and its own manifests validate against that claim. */ + +#include "wolftrust/spm.h" +#include "wolftrust/partition.h" +#include "wolftrust_manifest_generated.h" + +#include + +#ifndef EXPECT_NS_FENCE +#error "EXPECT_NS_FENCE must name the port's expected Normal-world fence" +#endif + +static int checks; +static int failures; + +static void check(int ok, const char* what) +{ + checks++; + if (ok) { + printf(" [check] PASS %s: %s\n", VARIANT_NAME, what); + } + else { + failures++; + printf(" [check] FAIL %s: %s\n", VARIANT_NAME, what); + } +} + +void wt_el3_puts(const char* s) +{ + (void)s; +} + +int main(void) +{ + const wt_system_manifest_t* manifest = wt_generated_manifest_get(); + const wt_profile_capabilities_t* platform = + wt_partitions_profile_capabilities(); + wt_system_manifest_t claim; + wt_profile_capabilities_t claim_caps; + wt_spm_t spm; + unsigned int level; + int ok; + + check(wt_spm_init(&spm, manifest, WT_MANIFEST_FEATURE_IPC, platform) == + WT_SPM_VALID && wt_spm_ready(&spm), + "the port's manifest validates against the port's own claim"); + + if (EXPECT_NS_FENCE == 1) { + check((platform->capabilities & WT_CAPABILITY_SECURITY_STATE) != 0U, + "a fenced port claims security-state isolation"); + check(manifest->isolation_profile == WT_ISOLATION_PROFILE_LEVEL_3, + "a fenced port's manifest declares isolation Level 3"); + } + else { + check((platform->capabilities & WT_CAPABILITY_SECURITY_STATE) == 0U, + "an unfenced port claims no security-state isolation"); + check(manifest->isolation_profile == + WT_ISOLATION_PROFILE_SERVICE_ONLY && + (manifest->profile_capabilities->capabilities & + WT_CAPABILITY_SECURITY_STATE) == 0U, + "an unfenced port's manifest declares no isolation level"); + + claim = *manifest; + claim_caps = *manifest->profile_capabilities; + claim_caps.capabilities |= WT_CAPABILITY_SECURITY_STATE; + claim.profile_capabilities = &claim_caps; + ok = 1; + for (level = (unsigned int)WT_ISOLATION_PROFILE_LEVEL_1; + level <= (unsigned int)WT_ISOLATION_PROFILE_LEVEL_3; level++) { + claim.isolation_profile = (wt_isolation_profile_t)level; + if (wt_spm_init(&spm, &claim, WT_MANIFEST_FEATURE_IPC, + platform) != WT_SPM_ERROR_VALIDATION || + wt_spm_ready(&spm)) { + ok = 0; + } + } + check(ok, "an isolation-level claim on an unfenced port fails closed"); + } + + printf("%s: %d checks, %d failures\n", VARIANT_NAME, checks, failures); + return (failures == 0) ? 0 : 1; +} diff --git a/tests/host/aarch64_tables/Makefile b/tests/host/aarch64_tables/Makefile new file mode 100644 index 00000000..b2cb6223 --- /dev/null +++ b/tests/host/aarch64_tables/Makefile @@ -0,0 +1,62 @@ +# Makefile +# +# Copyright (C) 2026 wolfSSL Inc. +# +# This file is part of wolfTrust. +# +# wolfTrust 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. +# +# wolfTrust 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 + +ROOT := ../../.. + +CC ?= cc +BUILD_DIR ?= build + +CFLAGS := \ + -I$(ROOT)/include \ + -std=c99 -O0 -g -Wall -Wextra -Werror -pedantic +CFLAGS += $(EXTRA_CFLAGS) + +SRCS := $(ROOT)/src/arch/aarch64/spm/tables.c main.c +TEST_BIN := $(BUILD_DIR)/test_aarch64_tables + +.PHONY: all run compilers sanitize valgrind clean + +all: $(TEST_BIN) + +$(BUILD_DIR): + mkdir -p $@ + +$(TEST_BIN): $(SRCS) | $(BUILD_DIR) + $(CC) $(CFLAGS) $(EXTRA_LDFLAGS) -o $@ $(SRCS) + +run: $(TEST_BIN) + $(TEST_BIN) + +compilers: + $(MAKE) clean run CC=gcc BUILD_DIR=$(BUILD_DIR)/gcc + $(MAKE) clean run CC=clang BUILD_DIR=$(BUILD_DIR)/clang + +sanitize: + $(MAKE) clean run CC=clang \ + BUILD_DIR=$(BUILD_DIR)/sanitize \ + EXTRA_CFLAGS="-fsanitize=address,undefined -fno-omit-frame-pointer" \ + EXTRA_LDFLAGS="-fsanitize=address,undefined" + +valgrind: clean $(TEST_BIN) + valgrind --error-exitcode=1 --leak-check=full --show-leak-kinds=all \ + $(TEST_BIN) + +clean: + rm -rf $(BUILD_DIR) diff --git a/tests/host/aarch64_tables/main.c b/tests/host/aarch64_tables/main.c new file mode 100644 index 00000000..ab9a1b1d --- /dev/null +++ b/tests/host/aarch64_tables/main.c @@ -0,0 +1,628 @@ +/* main.c + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfTrust. + * + * wolfTrust 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. + * + * wolfTrust 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, see . + */ + +/* WT-PORT-0014 (builder rows): the S-EL0 partition tables encode every + * attribute per the matrix, refuse W^X and overlaps, leave the guard page + * unmapped, keep partition pages non-global and Secure, and account for + * the pool. The manifest-driven full-RAM scan lands with the real manifests. */ + +#include "wolftrust/arch/aarch64/tables.h" + +#include +#include +#include + +#define POOL_PAGES 64u +#define POOL_PA 0x0E100000ull + +static int checks; +static int failures; +static uint8_t g_pool_mem[POOL_PAGES * WT_TABLES_PAGE_SIZE] + __attribute__((aligned(4096))); + +static void check(int ok, const char* what) +{ + checks++; + if (ok) { + printf(" [check] PASS %s\n", what); + } + else { + failures++; + printf(" [check] FAIL %s\n", what); + } +} + +#define RW (WT_MEM_ATTR_READ | WT_MEM_ATTR_WRITE) +#define RX (WT_MEM_ATTR_READ | WT_MEM_ATTR_EXEC) + +static const wt_memory_region_t g_el1[] = { + { 0x0E041000u, 0x4000u, RX }, + { 0x0E045000u, 0x2000u, RW | WT_TABLES_ATTR_NG }, + { (uintptr_t)POOL_PA, POOL_PAGES * WT_TABLES_PAGE_SIZE, RW } +}; + +static const wt_memory_region_t g_sp[] = { + { 0x0E200000u, 0x2000u, RX }, + { 0x0E202000u, 0x1000u, RW }, + { 0x0E203000u, 0x1000u, WT_MEM_ATTR_READ }, + { 0x0E205000u, 0x2000u, RW | WT_MEM_ATTR_RESTART_CLEAR }, + { 0x09040000u, 0x1000u, RW | WT_MEM_ATTR_DEVICE }, + { 0x0E300000u, 0u, RW } +}; + +static int build(wt_tables_t* t, uint16_t asid, const wt_memory_region_t* sp, + size_t n, wt_tables_pool_t* pool) +{ + return wt_tables_build(t, asid, sp, n, g_el1, + sizeof(g_el1) / sizeof(g_el1[0]), pool); +} + +static int walk_is(const wt_tables_t* t, const wt_tables_pool_t* pool, + uint64_t va, uint32_t attr, uint32_t ap, uint32_t uxn, + uint32_t pxn, uint32_t ng) +{ + wt_tables_walk_t w; + + if (wt_tables_walk(t, pool, va, &w) != WT_TABLES_OK) { + return 0; + } + return (w.pa == va) && (w.attr_index == attr) && (w.ap == ap) && + (w.uxn == uxn) && (w.pxn == pxn) && (w.ng == ng) && (w.ns == 0u); +} + +/* A Normal Non-secure page, non-global and PXN, with this AP and UXN. */ +static int walk_ns_is(const wt_tables_t* t, const wt_tables_pool_t* pool, + uint64_t va, uint32_t ap, uint32_t uxn) +{ + wt_tables_walk_t w; + + if (wt_tables_walk(t, pool, va, &w) != WT_TABLES_OK) { + return 0; + } + return (w.pa == va) && (w.attr_index == WT_TABLES_ATTR_NORMAL_WBWA) && + (w.ap == ap) && (w.uxn == uxn) && (w.pxn == 1u) && (w.ng == 1u) && + (w.ns == 1u); +} + +/* Full-RAM scan window covering the fill code/data, the whole table pool, and + * the partition regions, with unmapped gaps between them. */ +#define SCAN_LO 0x0E040000ull +#define SCAN_HI 0x0E208000ull + +static int va_in(const wt_memory_region_t* r, size_t n, uint64_t va) +{ + size_t i; + + for (i = 0u; i < n; i++) { + if ((r[i].size != 0u) && (va >= r[i].base) && + (va < (uint64_t)r[i].base + r[i].size)) { + return 1; + } + } + return 0; +} + +/* No page may be both writable and executable at either exception level. */ +static int wx_ok(const wt_tables_walk_t* w) +{ + int el0_w = (w->ap == WT_TABLES_AP_ALL_RW); + int el0_x = (w->uxn == 0u) && ((w->ap & 1u) != 0u); + int el1_w = (w->ap == WT_TABLES_AP_EL1_RW) || (w->ap == WT_TABLES_AP_ALL_RW); + int el1_x = (w->pxn == 0u); + + return !(el0_w && el0_x) && !(el1_w && el1_x); +} + +/* Walk every 4 KB of the RAM window in the SPM-only table and one partition + * table and assert the isolation invariants hold at every page: exact region + * coverage, NS=0, W^X, EL0 access only inside the partition's own regions and + * always non-global, the pool never EL0, and the SPM-only table free of EL0. */ +static void run_scan(void) +{ + wt_tables_pool_t pool; + wt_tables_t spmt; + wt_tables_t part; + wt_tables_walk_t w; + uint64_t va; + uint64_t pool_hi = POOL_PA + (uint64_t)POOL_PAGES * WT_TABLES_PAGE_SIZE; + size_t nsp = sizeof(g_sp) / sizeof(g_sp[0]); + size_t nel1 = sizeof(g_el1) / sizeof(g_el1[0]); + int cover_bad = 0; + int ns_bad = 0; + int wx_bad = 0; + int el0_out = 0; + int ng_bad = 0; + int pool_el0 = 0; + int spm_el0 = 0; + int spm_sp = 0; + int spm_pool = 0; + int in_sp; + int in_el1; + int in_pool; + int mapped; + + wt_tables_pool_init(&pool, g_pool_mem, POOL_PA, sizeof(g_pool_mem)); + if ((wt_tables_build(&spmt, 0u, NULL, 0u, g_el1, nel1, &pool) != WT_TABLES_OK) || + (wt_tables_build(&part, 3u, g_sp, nsp, g_el1, nel1, &pool) != WT_TABLES_OK)) { + check(0, "the scan tables build"); + return; + } + for (va = SCAN_LO; va < SCAN_HI; va += WT_TABLES_PAGE_SIZE) { + in_sp = va_in(g_sp, nsp, va); + in_el1 = va_in(g_el1, nel1, va); + in_pool = (va >= POOL_PA) && (va < pool_hi); + + mapped = (wt_tables_walk(&part, &pool, va, &w) == WT_TABLES_OK); + if (mapped != (in_sp || in_el1)) { + cover_bad++; + } + if (mapped) { + if (w.ns != 0u) { + ns_bad++; + } + if (!wx_ok(&w)) { + wx_bad++; + } + if ((w.ap & 1u) != 0u) { + if (!in_sp) { + el0_out++; + } + if (w.ng != 1u) { + ng_bad++; + } + if (in_pool) { + pool_el0++; + } + } + } + + mapped = (wt_tables_walk(&spmt, &pool, va, &w) == WT_TABLES_OK); + if (mapped && ((w.ap & 1u) != 0u)) { + spm_el0++; + } + if (in_sp && !in_el1 && mapped) { + spm_sp++; + } + if (in_pool && !mapped) { + spm_pool++; + } + } + check(cover_bad == 0, "partition table maps exactly the fill and SP regions, gaps unmapped"); + check(ns_bad == 0, "every mapped secure page has NS=0"); + check(wx_bad == 0, "no page is both writable and executable across the RAM window"); + check(el0_out == 0, "EL0 access appears only inside the partition's own regions"); + check(ng_bad == 0, "every EL0-accessible page is non-global"); + check(pool_el0 == 0, "the table pool is never EL0-accessible in a partition table"); + check(spm_el0 == 0, "the SPM-only table grants no EL0 access anywhere"); + check(spm_sp == 0, "the SPM-only table does not map the partition's EL0 bands"); + check(spm_pool == 0, "the SPM-only table maps the whole table pool"); +} + +/* EL1-only pages a partition does not own, one of each kind a grant can meet. */ +static const wt_memory_region_t g_el1_kinds[] = { + { 0x0E045000u, 0x1000u, RW | WT_TABLES_ATTR_NG }, + { 0x0E046000u, 0x1000u, WT_MEM_ATTR_READ | WT_TABLES_ATTR_NG }, + { 0x0E047000u, 0x1000u, RW | WT_TABLES_ATTR_NS | WT_TABLES_ATTR_NG }, + { 0x09041000u, 0x1000u, RW | WT_MEM_ATTR_DEVICE | WT_TABLES_ATTR_NG } +}; + +/* A revoke rebuilds the EL1-only entry, so a grant takes only a page whose + * entry is exactly the one the revoke rebuilds. */ +static void run_window_restore(void) +{ + wt_tables_pool_t pool; + wt_tables_t t; + int mapped = 0; + + wt_tables_pool_init(&pool, g_pool_mem, POOL_PA, sizeof(g_pool_mem)); + if (wt_tables_build(&t, 7u, NULL, 0u, g_el1_kinds, + sizeof(g_el1_kinds) / sizeof(g_el1_kinds[0]), + &pool) != WT_TABLES_OK) { + check(0, "the window table builds"); + return; + } + check(wt_tables_grant_el0(&t, &pool, 0x0E046000u, 1u, WT_MEM_ATTR_READ, + &mapped) == WT_TABLES_ERROR_OVERLAP && + walk_is(&t, &pool, 0x0E046000u, WT_TABLES_ATTR_NORMAL_WBWA, + WT_TABLES_AP_EL1_RO, 1u, 1u, 1u), + "window: a read-only EL1 page is never granted, and stays read-only"); + check(wt_tables_grant_el0(&t, &pool, 0x09041000u, 1u, WT_MEM_ATTR_READ, + &mapped) == WT_TABLES_ERROR_OVERLAP && + walk_is(&t, &pool, 0x09041000u, WT_TABLES_ATTR_DEVICE_NGNRE, + WT_TABLES_AP_EL1_RW, 1u, 1u, 1u), + "window: a Device EL1 page is never granted, and stays Device"); + check(wt_tables_grant_el0(&t, &pool, 0x0E045000u, 1u, + RW | WT_TABLES_ATTR_NS, &mapped) == + WT_TABLES_ERROR_OVERLAP && + walk_is(&t, &pool, 0x0E045000u, WT_TABLES_ATTR_NORMAL_WBWA, + WT_TABLES_AP_EL1_RW, 1u, 1u, 1u) && + wt_tables_grant_el0(&t, &pool, 0x0E047000u, 1u, RW, &mapped) == + WT_TABLES_ERROR_OVERLAP && + walk_ns_is(&t, &pool, 0x0E047000u, WT_TABLES_AP_EL1_RW, 1u), + "window: a page is never granted in the other security state"); + check(wt_tables_grant_el0(&t, &pool, 0x0E045000u, 1u, WT_MEM_ATTR_READ, + &mapped) == WT_TABLES_OK && + walk_is(&t, &pool, 0x0E045000u, WT_TABLES_ATTR_NORMAL_WBWA, + WT_TABLES_AP_ALL_RO, 1u, 1u, 1u) && + wt_tables_revoke_el0(&t, &pool, 0x0E045000u, 1u, 1) == WT_TABLES_OK && + walk_is(&t, &pool, 0x0E045000u, WT_TABLES_ATTR_NORMAL_WBWA, + WT_TABLES_AP_EL1_RW, 1u, 1u, 1u), + "window: a read-only grant of a read-write page revokes to the entry it replaced"); + check(wt_tables_grant_el0(&t, &pool, 0x0E047000u, 1u, + RW | WT_TABLES_ATTR_NS, &mapped) == WT_TABLES_OK && + wt_tables_set_el0_attributes(&t, &pool, 0x0E047000u, 1u, + WT_MEM_ATTR_READ) == WT_TABLES_OK && + walk_ns_is(&t, &pool, 0x0E047000u, WT_TABLES_AP_ALL_RO, 1u) && + wt_tables_set_el0_attributes(&t, &pool, 0x0E047000u, 1u, 0u) == + WT_TABLES_OK && + walk_ns_is(&t, &pool, 0x0E047000u, WT_TABLES_AP_EL1_RW, 1u) && + wt_tables_set_el0_attributes(&t, &pool, 0x0E047000u, 1u, RW) == + WT_TABLES_OK && + walk_ns_is(&t, &pool, 0x0E047000u, WT_TABLES_AP_ALL_RW, 1u), + "re-permission: a Non-secure EL0 page goes read-only, no access, and read-write, and stays Non-secure"); + check(wt_tables_set_el0_attributes(&t, &pool, 0x0E047000u, 1u, RX) == + WT_TABLES_ERROR_WX && + walk_ns_is(&t, &pool, 0x0E047000u, WT_TABLES_AP_ALL_RW, 1u) && + wt_tables_revoke_el0(&t, &pool, 0x0E047000u, 1u, 1) == WT_TABLES_OK && + walk_ns_is(&t, &pool, 0x0E047000u, WT_TABLES_AP_EL1_RW, 1u), + "re-permission: a Non-secure page is never made executable, and its revoke restores the Non-secure EL1 page"); +} + +/* Memory a partition donated comes back to it: its own entry for the page is + * the spent hold, which a grant takes and a revoke leaves EL1 read-write. */ +static void run_spent_hold(void) +{ + wt_tables_pool_t pool; + wt_tables_t t; + int mapped = 0; + + wt_tables_pool_init(&pool, g_pool_mem, POOL_PA, sizeof(g_pool_mem)); + if (build(&t, 8u, g_sp, sizeof(g_sp) / sizeof(g_sp[0]), &pool) != + WT_TABLES_OK) { + check(0, "the spent-hold table builds"); + return; + } + check(wt_tables_hold_el0(&t, &pool, 0x0E202000u, 1u, 0) == WT_TABLES_OK && + wt_tables_grant_el0(&t, &pool, 0x0E202000u, 1u, RW, &mapped) == + WT_TABLES_OK && + walk_is(&t, &pool, 0x0E202000u, WT_TABLES_ATTR_NORMAL_WBWA, + WT_TABLES_AP_ALL_RW, 1u, 1u, 1u) && + wt_tables_revoke_el0(&t, &pool, 0x0E202000u, 1u, 1) == WT_TABLES_OK && + walk_is(&t, &pool, 0x0E202000u, WT_TABLES_ATTR_NORMAL_WBWA, + WT_TABLES_AP_EL1_RW, 1u, 1u, 1u) && + wt_tables_release_el0(&t, &pool, 0x0E202000u, 1u) == + WT_TABLES_ERROR_UNMAPPED, + "window: a spent hold on donated read-write data is granted back, and its revoke leaves no hold to release"); + check(wt_tables_hold_el0(&t, &pool, 0x0E203000u, 1u, 0) == WT_TABLES_OK && + wt_tables_grant_el0(&t, &pool, 0x0E203000u, 1u, WT_MEM_ATTR_READ, + &mapped) == WT_TABLES_ERROR_OVERLAP && + walk_is(&t, &pool, 0x0E203000u, WT_TABLES_ATTR_NORMAL_WBWA, + WT_TABLES_AP_EL1_RO, 1u, 1u, 1u), + "window: a held read-only page is never granted, since a revoke could not put it back"); +} + +int main(void) +{ + wt_tables_pool_t pool; + wt_tables_t t; + wt_tables_t t2; + wt_tables_walk_t w; + wt_memory_region_t bad[2]; + size_t used; + int mapped = 0; + int ret; + + printf("WT-PORT-0014 (AArch64 stage-1 table builder)\n"); + + wt_tables_pool_init(&pool, g_pool_mem, POOL_PA, sizeof(g_pool_mem)); + ret = build(&t, 3u, g_sp, sizeof(g_sp) / sizeof(g_sp[0]), &pool); + check(ret == WT_TABLES_OK, "a partition table builds from EL1 fill + EL0 regions"); + used = wt_tables_pool_pages_used(&pool); + check(used == 5u, "one L1, one L2, three L3 pages for regions in three 2 MB blocks"); + check(wt_tables_ttbr0(&t) == (POOL_PA | (3ull << 48)), + "TTBR0 = L1 page address with the ASID in bits 63:48"); + + check(walk_is(&t, &pool, 0x0E200000u, WT_TABLES_ATTR_NORMAL_WBWA, + WT_TABLES_AP_ALL_RO, 0u, 0u, 1u) && + walk_is(&t, &pool, 0x0E201000u, WT_TABLES_ATTR_NORMAL_WBWA, + WT_TABLES_AP_ALL_RO, 0u, 0u, 1u), + "SP code: Normal WBWA, RO for EL0+EL1, executable, non-global"); + check(walk_is(&t, &pool, 0x0E202000u, WT_TABLES_ATTR_NORMAL_WBWA, + WT_TABLES_AP_ALL_RW, 1u, 1u, 1u), + "SP data: RW for EL0+EL1, UXN and PXN, non-global"); + check(walk_is(&t, &pool, 0x0E203000u, WT_TABLES_ATTR_NORMAL_WBWA, + WT_TABLES_AP_ALL_RO, 1u, 1u, 1u), + "SP read-only data: RO, never executable"); + check(walk_is(&t, &pool, 0x0E205000u, WT_TABLES_ATTR_NORMAL_WBWA, + WT_TABLES_AP_ALL_RW, 1u, 1u, 1u) && + walk_is(&t, &pool, 0x0E206000u, WT_TABLES_ATTR_NORMAL_WBWA, + WT_TABLES_AP_ALL_RW, 1u, 1u, 1u), + "SP stack: RW, restart-clear has no PTE effect"); + check(walk_is(&t, &pool, 0x09040000u, WT_TABLES_ATTR_DEVICE_NGNRE, + WT_TABLES_AP_ALL_RW, 1u, 1u, 1u), + "device region: Device-nGnRE, RW, execute-never"); + check(walk_is(&t, &pool, 0x0E041000u, WT_TABLES_ATTR_NORMAL_WBWA, + WT_TABLES_AP_EL1_RO, 1u, 0u, 0u), + "SPM code: EL1-only RO, PXN clear, UXN set, global"); + check(walk_is(&t, &pool, 0x0E045000u, WT_TABLES_ATTR_NORMAL_WBWA, + WT_TABLES_AP_EL1_RW, 1u, 1u, 1u), + "SPM data flagged shareable: EL1-only RW, XN, non-global (the NG hint)"); + check(walk_is(&t, &pool, (uint64_t)POOL_PA, WT_TABLES_ATTR_NORMAL_WBWA, + WT_TABLES_AP_EL1_RW, 1u, 1u, 0u), + "the table pool: EL1-only RW, never EL0, global"); + check(wt_tables_walk(&t, &pool, 0x0E204000u, &w) == WT_TABLES_ERROR_UNMAPPED && + wt_tables_walk(&t, &pool, 0x0E207000u, &w) == WT_TABLES_ERROR_UNMAPPED && + wt_tables_walk(&t, &pool, 0x0E040000u, &w) == WT_TABLES_ERROR_UNMAPPED && + wt_tables_walk(&t, &pool, 0x40000000u, &w) == WT_TABLES_ERROR_UNMAPPED, + "the stack guard page, the page past the stack, and untouched VAs are unmapped"); + check(wt_tables_walk(&t, &pool, 0x0E202ABCu, &w) == WT_TABLES_OK && w.pa == 0x0E202ABCu, + "a walk keeps the page offset"); + check(wt_tables_walk(&t, &pool, WT_TABLES_VA_LIMIT, &w) == WT_TABLES_ERROR_RANGE, + "a VA at or past 2^39 is out of range"); + + ret = build(&t2, 4u, g_sp, 4u, &pool); + check(ret == WT_TABLES_OK && wt_tables_pool_pages_used(&pool) == used + 4u && + wt_tables_ttbr0(&t2) != wt_tables_ttbr0(&t), + "a second partition table shares the pool and gets its own L1"); + check(walk_is(&t2, &pool, 0x0E041000u, WT_TABLES_ATTR_NORMAL_WBWA, + WT_TABLES_AP_EL1_RO, 1u, 0u, 0u) && + wt_tables_walk(&t2, &pool, 0x09040000u, &w) == WT_TABLES_ERROR_UNMAPPED, + "the EL1 fill is identical in every table, EL0 regions are per table"); + + memcpy(bad, g_sp, sizeof(bad)); + bad[0].attributes = RW | WT_MEM_ATTR_EXEC; + check(build(&t2, 5u, bad, 1u, &pool) == WT_TABLES_ERROR_WX, + "a writable and executable region fails closed"); + bad[0].attributes = RX | WT_MEM_ATTR_DEVICE; + check(build(&t2, 5u, bad, 1u, &pool) == WT_TABLES_ERROR_WX, + "an executable device region fails closed"); + bad[0].attributes = WT_MEM_ATTR_WRITE; + check(build(&t2, 5u, bad, 1u, &pool) == WT_TABLES_ERROR_ARGUMENT, + "write without read has no encoding and is refused"); + bad[0].attributes = RX; + bad[0].base = 0x0E200800u; + check(build(&t2, 5u, bad, 1u, &pool) == WT_TABLES_ERROR_ALIGN, + "a region base off the 4 KB granule is refused"); + bad[0].base = 0x0E200000u; + bad[0].size = 0x1800u; + check(build(&t2, 5u, bad, 1u, &pool) == WT_TABLES_ERROR_ALIGN, + "a region size off the 4 KB granule is refused"); + bad[0].size = 0x2000u; + bad[1].base = 0x0E201000u; + bad[1].size = 0x1000u; + bad[1].attributes = RW; + check(build(&t2, 5u, bad, 2u, &pool) == WT_TABLES_ERROR_OVERLAP, + "two EL0 regions sharing a page overlap"); + bad[1].base = 0x0E045000u; + check(build(&t2, 5u, bad, 2u, &pool) == WT_TABLES_ERROR_OVERLAP, + "an EL0 region over the SPM fill overlaps (no page differs across tables)"); + bad[1].base = (uintptr_t)(WT_TABLES_VA_LIMIT - 0x1000ull); + bad[1].size = 0x2000u; + check(build(&t2, 5u, bad, 2u, &pool) == WT_TABLES_ERROR_RANGE, + "a region past the 39-bit VA space is refused"); + check(wt_tables_build(&t2, 256u, g_sp, 1u, g_el1, 1u, &pool) == + WT_TABLES_ERROR_ARGUMENT && + wt_tables_build(NULL, 1u, g_sp, 1u, g_el1, 1u, &pool) == + WT_TABLES_ERROR_ARGUMENT, + "an ASID over 8 bits or a NULL table is refused"); + used = wt_tables_pool_pages_used(&pool); + check(wt_tables_set_el0_attributes(&t, &pool, 0x0E201000u, 1u, RW) == + WT_TABLES_OK && + walk_is(&t, &pool, 0x0E201000u, WT_TABLES_ATTR_NORMAL_WBWA, + WT_TABLES_AP_ALL_RW, 1u, 1u, 1u) && + walk_is(&t, &pool, 0x0E200000u, WT_TABLES_ATTR_NORMAL_WBWA, + WT_TABLES_AP_ALL_RO, 0u, 0u, 1u), + "re-permission: one owned code page becomes RW and execute-never, its neighbour is untouched"); + check(wt_tables_set_el0_attributes(&t, &pool, 0x0E201000u, 1u, + WT_MEM_ATTR_READ) == WT_TABLES_OK && + walk_is(&t, &pool, 0x0E201000u, WT_TABLES_ATTR_NORMAL_WBWA, + WT_TABLES_AP_ALL_RO, 1u, 1u, 1u), + "re-permission: the same page becomes read-only data"); + check(wt_tables_set_el0_attributes(&t, &pool, 0x0E201000u, 1u, + RW | WT_MEM_ATTR_EXEC) == + WT_TABLES_ERROR_WX && + walk_is(&t, &pool, 0x0E201000u, WT_TABLES_ATTR_NORMAL_WBWA, + WT_TABLES_AP_ALL_RO, 1u, 1u, 1u), + "re-permission: writable and executable is refused and changes nothing"); + check(wt_tables_set_el0_attributes(&t, &pool, 0x0E201000u, 1u, RX) == + WT_TABLES_OK && + walk_is(&t, &pool, 0x0E201000u, WT_TABLES_ATTR_NORMAL_WBWA, + WT_TABLES_AP_ALL_RO, 0u, 0u, 1u), + "re-permission: the page returns to read-execute"); + check(wt_tables_set_el0_attributes(&t, &pool, 0x0E201000u, 1u, 0u) == + WT_TABLES_OK && + walk_is(&t, &pool, 0x0E201000u, WT_TABLES_ATTR_NORMAL_WBWA, + WT_TABLES_AP_EL1_RW, 1u, 1u, 1u) && + wt_tables_grant_el0(&t, &pool, 0x0E201000u, 1u, RW, &mapped) == + WT_TABLES_ERROR_OVERLAP && + wt_tables_hold_el0(&t, &pool, 0x0E201000u, 1u, 0) == + WT_TABLES_ERROR_UNMAPPED, + "re-permission: no access leaves S-EL1 read-write only, never a window or a held page"); + check(wt_tables_set_el0_attributes(&t, &pool, 0x0E201000u, 1u, RX) == + WT_TABLES_OK && + walk_is(&t, &pool, 0x0E201000u, WT_TABLES_ATTR_NORMAL_WBWA, + WT_TABLES_AP_ALL_RO, 0u, 0u, 1u), + "re-permission: a no-access page is re-permissioned back to read-execute"); + check(wt_tables_set_el0_attributes(&t, &pool, 0x0E041000u, 1u, RW) == + WT_TABLES_ERROR_UNMAPPED && + walk_is(&t, &pool, 0x0E041000u, WT_TABLES_ATTR_NORMAL_WBWA, + WT_TABLES_AP_EL1_RO, 1u, 0u, 0u), + "re-permission: an EL1-only SPM page is never a partition's to change"); + check(wt_tables_set_el0_attributes(&t, &pool, 0x09040000u, 1u, + WT_MEM_ATTR_READ) == WT_TABLES_OK && + walk_is(&t, &pool, 0x09040000u, WT_TABLES_ATTR_DEVICE_NGNRE, + WT_TABLES_AP_ALL_RO, 1u, 1u, 1u) && + wt_tables_set_el0_attributes(&t, &pool, 0x09040000u, 1u, 0u) == + WT_TABLES_OK && + walk_is(&t, &pool, 0x09040000u, WT_TABLES_ATTR_DEVICE_NGNRE, + WT_TABLES_AP_EL1_RW, 1u, 1u, 1u) && + wt_tables_set_el0_attributes(&t, &pool, 0x09040000u, 1u, RW) == + WT_TABLES_OK && + walk_is(&t, &pool, 0x09040000u, WT_TABLES_ATTR_DEVICE_NGNRE, + WT_TABLES_AP_ALL_RW, 1u, 1u, 1u), + "re-permission: a device page changes data access and stays Device and execute-never"); + check(wt_tables_set_el0_attributes(&t, &pool, 0x09040000u, 1u, RX) == + WT_TABLES_ERROR_WX && + walk_is(&t, &pool, 0x09040000u, WT_TABLES_ATTR_DEVICE_NGNRE, + WT_TABLES_AP_ALL_RW, 1u, 1u, 1u), + "re-permission: an executable device page is refused and changes nothing"); + check(wt_tables_set_el0_attributes(&t, &pool, 0x0E203000u, 2u, RW) == + WT_TABLES_ERROR_UNMAPPED && + walk_is(&t, &pool, 0x0E203000u, WT_TABLES_ATTR_NORMAL_WBWA, + WT_TABLES_AP_ALL_RO, 1u, 1u, 1u), + "re-permission: a range running into a guard hole changes no page at all"); + check(wt_tables_set_el0_attributes(&t, &pool, 0x0E201800u, 1u, RW) == + WT_TABLES_ERROR_ALIGN && + wt_tables_set_el0_attributes(&t, &pool, 0x0E201000u, 0u, RW) == + WT_TABLES_ERROR_ARGUMENT && + wt_tables_set_el0_attributes(&t, &pool, 0x0E201000u, 1u, + RW | WT_MEM_ATTR_DEVICE) == + WT_TABLES_ERROR_ARGUMENT, + "re-permission: unaligned, empty, and device-typed requests are refused"); + check(wt_tables_pool_pages_used(&pool) == used, + "re-permission: rewrites entries in place, no pool growth"); + + check(wt_tables_grant_el0(&t, &pool, 0x0E045000u, 1u, RW, &mapped) == + WT_TABLES_OK && mapped == 1 && + walk_is(&t, &pool, 0x0E045000u, WT_TABLES_ATTR_NORMAL_WBWA, + WT_TABLES_AP_ALL_RW, 1u, 1u, 1u) && + walk_is(&t, &pool, 0x0E046000u, WT_TABLES_ATTR_NORMAL_WBWA, + WT_TABLES_AP_EL1_RW, 1u, 1u, 1u), + "window: one EL1-only page becomes EL0 data, its neighbour is untouched"); + check(wt_tables_grant_el0(&t, &pool, 0x0E045000u, 1u, RW, &mapped) == + WT_TABLES_ERROR_OVERLAP, + "window: a page EL0 already reaches is never granted over"); + check(wt_tables_grant_el0(&t, &pool, (uint64_t)POOL_PA, 1u, RW, &mapped) == + WT_TABLES_ERROR_OVERLAP && + walk_is(&t, &pool, (uint64_t)POOL_PA, WT_TABLES_ATTR_NORMAL_WBWA, + WT_TABLES_AP_EL1_RW, 1u, 1u, 0u), + "window: a global EL1-only page is never granted over, and stays as it was"); + check(wt_tables_revoke_el0(&t, &pool, 0x0E045000u, 1u, 1) == WT_TABLES_OK && + walk_is(&t, &pool, 0x0E045000u, WT_TABLES_ATTR_NORMAL_WBWA, + WT_TABLES_AP_EL1_RW, 1u, 1u, 1u), + "window: revoke puts the EL1-only mapping back"); + check(wt_tables_revoke_el0(&t, &pool, 0x0E045000u, 1u, 1) == + WT_TABLES_ERROR_UNMAPPED, + "window: nothing to revoke on an EL1-only page"); + used = wt_tables_pool_pages_used(&pool); + check(wt_tables_grant_el0(&t, &pool, 0x0E048000u, 2u, WT_MEM_ATTR_READ, + &mapped) == WT_TABLES_ERROR_UNMAPPED && + wt_tables_walk(&t, &pool, 0x0E048000u, &w) != WT_TABLES_OK && + wt_tables_pool_pages_used(&pool) == used, + "window: pages the table does not map are never granted, and no pool page is taken"); + check(wt_tables_grant_el0(&t, &pool, 0x0E046000u, 3u, RW, &mapped) == + WT_TABLES_ERROR_UNMAPPED && + walk_is(&t, &pool, 0x0E046000u, WT_TABLES_ATTR_NORMAL_WBWA, + WT_TABLES_AP_EL1_RW, 1u, 1u, 1u), + "window: a range mixing mapped and absent pages changes nothing"); + check(wt_tables_grant_el0(&t, &pool, 0x0E045000u, 1u, RX, &mapped) == + WT_TABLES_ERROR_ARGUMENT && + wt_tables_grant_el0(&t, &pool, 0x0E045800u, 1u, RW, &mapped) == + WT_TABLES_ERROR_ALIGN, + "window: executable and unaligned grants are refused"); + + check(wt_tables_hold_el0(&t, &pool, 0x0E200000u, 3u, 0) == WT_TABLES_OK && + walk_is(&t, &pool, 0x0E200000u, WT_TABLES_ATTR_NORMAL_WBWA, + WT_TABLES_AP_EL1_RO, 1u, 1u, 1u) && + walk_is(&t, &pool, 0x0E202000u, WT_TABLES_ATTR_NORMAL_WBWA, + WT_TABLES_AP_EL1_RW, 1u, 1u, 1u), + "hold: an owner's code and data pages lose EL0 access and execution"); + check(wt_tables_hold_el0(&t, &pool, 0x0E200000u, 1u, 0) == + WT_TABLES_ERROR_UNMAPPED && + wt_tables_revoke_el0(&t, &pool, 0x0E200000u, 1u, 1) == + WT_TABLES_ERROR_UNMAPPED, + "hold: a held page is neither held again nor revoked as a window"); + check(wt_tables_release_el0(&t, &pool, 0x0E200000u, 3u) == WT_TABLES_OK && + walk_is(&t, &pool, 0x0E200000u, WT_TABLES_ATTR_NORMAL_WBWA, + WT_TABLES_AP_ALL_RO, 0u, 0u, 1u) && + walk_is(&t, &pool, 0x0E201000u, WT_TABLES_ATTR_NORMAL_WBWA, + WT_TABLES_AP_ALL_RO, 0u, 0u, 1u) && + walk_is(&t, &pool, 0x0E202000u, WT_TABLES_ATTR_NORMAL_WBWA, + WT_TABLES_AP_ALL_RW, 1u, 1u, 1u), + "release: each page gets back exactly its own access and execution"); + check(wt_tables_hold_el0(&t, &pool, 0x0E202000u, 1u, 1) == WT_TABLES_OK && + walk_is(&t, &pool, 0x0E202000u, WT_TABLES_ATTR_NORMAL_WBWA, + WT_TABLES_AP_ALL_RO, 1u, 1u, 1u) && + wt_tables_set_el0_attributes(&t, &pool, 0x0E202000u, 1u, RW) == + WT_TABLES_ERROR_UNMAPPED && + wt_tables_release_el0(&t, &pool, 0x0E202000u, 1u) == WT_TABLES_OK && + walk_is(&t, &pool, 0x0E202000u, WT_TABLES_ATTR_NORMAL_WBWA, + WT_TABLES_AP_ALL_RW, 1u, 1u, 1u), + "hold: a page held for reading stays EL0 read-only, is never re-permissioned, and is released read-write"); + check(wt_tables_hold_el0(&t, &pool, 0x0E200000u, 3u, 1) == WT_TABLES_OK && + wt_tables_withdraw_el0(&t, &pool, 0x0E200000u, 3u) == WT_TABLES_OK && + walk_is(&t, &pool, 0x0E200000u, WT_TABLES_ATTR_NORMAL_WBWA, + WT_TABLES_AP_EL1_RO, 1u, 1u, 1u) && + walk_is(&t, &pool, 0x0E202000u, WT_TABLES_ATTR_NORMAL_WBWA, + WT_TABLES_AP_EL1_RW, 1u, 1u, 1u), + "withdraw: held pages lose the EL0 reading and execution left them, S-EL1 writing only what was writable"); + check(wt_tables_withdraw_el0(&t, &pool, 0x0E200000u, 3u) == WT_TABLES_OK && + wt_tables_release_el0(&t, &pool, 0x0E200000u, 3u) == WT_TABLES_OK && + walk_is(&t, &pool, 0x0E200000u, WT_TABLES_ATTR_NORMAL_WBWA, + WT_TABLES_AP_ALL_RO, 0u, 0u, 1u) && + walk_is(&t, &pool, 0x0E202000u, WT_TABLES_ATTR_NORMAL_WBWA, + WT_TABLES_AP_ALL_RW, 1u, 1u, 1u), + "withdraw: repeated, it changes nothing, and release still puts back exactly what the page had"); + check(wt_tables_hold_el0(&t, &pool, 0x0E202000u, 1u, 1) == WT_TABLES_OK && + wt_tables_withdraw_el0(&t, &pool, 0x0E202000u, 2u) == + WT_TABLES_ERROR_UNMAPPED && + walk_is(&t, &pool, 0x0E202000u, WT_TABLES_ATTR_NORMAL_WBWA, + WT_TABLES_AP_ALL_RO, 1u, 1u, 1u) && + wt_tables_withdraw_el0(&t, &pool, 0x0E045000u, 1u) == + WT_TABLES_ERROR_UNMAPPED && + wt_tables_release_el0(&t, &pool, 0x0E202000u, 1u) == WT_TABLES_OK, + "withdraw: a range reaching a page not held, or an EL1-only page, is refused and changes nothing"); + check(wt_tables_release_el0(&t, &pool, 0x0E202000u, 1u) == + WT_TABLES_ERROR_UNMAPPED && + wt_tables_hold_el0(&t, &pool, 0x0E044000u, 2u, 0) == + WT_TABLES_ERROR_UNMAPPED && + walk_is(&t, &pool, 0x0E045000u, WT_TABLES_ATTR_NORMAL_WBWA, + WT_TABLES_AP_EL1_RW, 1u, 1u, 1u), + "hold: a page never held is not released, and a range reaching an EL1-only page changes nothing"); + + wt_tables_pool_init(&pool, g_pool_mem, POOL_PA, 4u * WT_TABLES_PAGE_SIZE); + check(build(&t2, 6u, g_sp, 1u, &pool) == WT_TABLES_OK && + wt_tables_pool_pages_used(&pool) == 4u && + wt_tables_grant_el0(&t2, &pool, 0x0E3FF000u, 2u, RW, &mapped) == + WT_TABLES_ERROR_UNMAPPED && + wt_tables_pool_pages_used(&pool) == 4u && + wt_tables_walk(&t2, &pool, 0x0E3FF000u, &w) == + WT_TABLES_ERROR_UNMAPPED && + wt_tables_walk(&t2, &pool, 0x0E201000u, &w) == WT_TABLES_OK, + "window: a grant across subtrees the table never built grows no table and maps nothing"); + + wt_tables_pool_init(&pool, g_pool_mem, POOL_PA, 2u * WT_TABLES_PAGE_SIZE); + check(build(&t2, 6u, g_sp, 1u, &pool) == WT_TABLES_ERROR_POOL, + "pool exhaustion fails the build"); + wt_tables_pool_init(&pool, g_pool_mem + 8u, POOL_PA, 4u * WT_TABLES_PAGE_SIZE); + check(build(&t2, 6u, g_sp, 1u, &pool) == WT_TABLES_ERROR_ARGUMENT, + "an unaligned pool is refused"); + check(WT_TABLES_MAIR_EL1 == 0x44FF0400ull && + (WT_TABLES_TCR_EL1 & 0x3Fu) == 25u && ((WT_TABLES_TCR_EL1 >> 32) & 7u) == 2u && + ((WT_TABLES_TCR_EL1 >> 23) & 1u) == 1u, + "MAIR indexes and TCR (T0SZ 25, IPS 40-bit, EPD1) match the design"); + + run_window_restore(); + run_spent_hold(); + run_scan(); + + printf("aarch64_tables: %d checks, %d failures\n", checks, failures); + return (failures == 0) ? 0 : 1; +} diff --git a/tests/host/acs_pal/Makefile b/tests/host/acs_pal/Makefile new file mode 100644 index 00000000..4c64836b --- /dev/null +++ b/tests/host/acs_pal/Makefile @@ -0,0 +1,75 @@ +# Makefile +# +# Copyright (C) 2026 wolfSSL Inc. +# +# This file is part of wolfTrust. +# +# wolfTrust 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. +# +# wolfTrust 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 + +# Host rows for the FF-A ACS platform page pool (tests/conformance/ffa-acs/ +# tgt_wolftrust_qemu/src/pal_misc.c), built against stub PAL headers here. + +ROOT := ../../.. + +CC ?= cc +BUILD_DIR ?= build + +PAL_SRC := $(ROOT)/tests/conformance/ffa-acs/tgt_wolftrust_qemu/src/pal_misc.c + +CFLAGS := \ + -I. \ + -DSP1_COMPILE=1 \ + -std=c99 -O0 -g -Wall -Wextra -Werror -pedantic +CFLAGS += $(EXTRA_CFLAGS) + +TEST_BIN := $(BUILD_DIR)/test_acs_pal + +.PHONY: all run compilers sanitize valgrind clean + +all: $(TEST_BIN) + +$(BUILD_DIR): + mkdir -p $@ + +$(TEST_BIN): main.c $(PAL_SRC) pal_interfaces.h pal_misc_asm.h | $(BUILD_DIR) + $(CC) $(CFLAGS) $(EXTRA_LDFLAGS) -o $@ main.c $(PAL_SRC) + +# Each image's device map builds under ISO C: the dispatcher's, and a +# partition with no devices, whose map is empty. +PAL_CHECK := $(CC) -I. -std=c99 -Wall -Wextra -Werror -pedantic -c \ + -o /dev/null $(PAL_SRC) + +run: $(TEST_BIN) + $(TEST_BIN) + @$(PAL_CHECK) -DVM1_COMPILE=1 + @echo " [check] PASS the dispatcher's device map builds" + @$(PAL_CHECK) + @echo " [check] PASS a partition with no devices builds an empty device map" + +compilers: + $(MAKE) clean run CC=gcc BUILD_DIR=$(BUILD_DIR)/gcc + $(MAKE) clean run CC=clang BUILD_DIR=$(BUILD_DIR)/clang + +sanitize: + $(MAKE) clean run CC=clang BUILD_DIR=$(BUILD_DIR)/sanitize \ + EXTRA_CFLAGS="-fsanitize=address,undefined -fno-omit-frame-pointer" \ + EXTRA_LDFLAGS="-fsanitize=address,undefined" + +valgrind: clean $(TEST_BIN) + valgrind --error-exitcode=1 --leak-check=full --show-leak-kinds=all \ + $(TEST_BIN) + +clean: + rm -rf $(BUILD_DIR) diff --git a/tests/host/acs_pal/main.c b/tests/host/acs_pal/main.c new file mode 100644 index 00000000..23f8796a --- /dev/null +++ b/tests/host/acs_pal/main.c @@ -0,0 +1,137 @@ +/* main.c + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfTrust. + * + * wolfTrust 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. + * + * wolfTrust 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, see . + */ + +/* Host rows for the FF-A ACS platform page pool: an allocation is released + * only by its own base and span, so a free naming the wrong size, a tail + * page, or a page never handed out cannot release a neighbour's page. */ + +#include +#include +#include + +#include "pal_interfaces.h" + +static int checks; +static int failures; + +static void check(int ok, const char* what) +{ + checks++; + if (ok) { + printf("PASS: %s\n", what); + } + else { + failures++; + printf("FAIL: %s\n", what); + } +} + +#define PAGE ((uint64_t)PAGE_SIZE_4K) +#define PAGES 5 + +static int page_zeroed(const uint8_t* p) +{ + uint32_t i; + + for (i = 0u; i < PAGE_SIZE_4K; i++) { + if (p[i] != 0u) { + return 0; + } + } + return 1; +} + +int main(void) +{ + uint8_t* one; + uint8_t* two; + uint8_t* next; + uint8_t* all[PAGES]; + int i; + + one = (uint8_t*)pal_memory_alloc(PAGE); + two = (uint8_t*)pal_memory_alloc(2u * PAGE); + check((one != NULL) && (two != NULL) && (two == one + PAGE), + "a page and a page pair come out of the pool in order"); + check(pal_memory_free(one, 2u * PAGE) == PAL_ERROR, + "a page is not released as a pair"); + next = (uint8_t*)pal_memory_alloc(PAGE); + check((next != NULL) && (next != one) && (next != two) && + (next != two + PAGE), + "the refused free left the page and its neighbour allocated"); + check(pal_memory_free(next, PAGE) == PAL_SUCCESS, + "the extra page is released"); + check(pal_memory_free(two, PAGE) == PAL_ERROR, + "a pair is not released as a page"); + check(pal_memory_free(two + PAGE, PAGE) == PAL_ERROR, + "a pair's tail page is not released on its own"); + check(pal_memory_free(two + PAGE, 2u * PAGE) == PAL_ERROR, + "a pair is not released from its tail"); + check(pal_memory_free(one + 16u, PAGE) == PAL_ERROR, + "an address inside a page is not a base"); + check(pal_memory_free(one, 3u * PAGE) == PAL_ERROR, + "a size the pool never hands out is refused"); + check(pal_memory_free(one, 0u) == PAL_ERROR, "so is a zero size"); + next = (uint8_t*)pal_memory_alloc(PAGE); + check((next != NULL) && (next != one) && (next != two) && + (next != two + PAGE), + "every refused free left the pool as it was"); + check(pal_memory_free(next, PAGE) == PAL_SUCCESS, + "the extra page is released again"); + check(pal_memory_free(two, 2u * PAGE) == PAL_SUCCESS, + "the pair is released by its base and span"); + check(pal_memory_free(two, 2u * PAGE) == PAL_ERROR, + "and not a second time"); + check(pal_memory_free(one, PAGE) == PAL_SUCCESS, + "the page is released by its base and span"); + check(pal_memory_free(one, PAGE) == PAL_ERROR, "and not a second time"); + + check(pal_memory_alloc(3u * PAGE) == NULL, + "three pages are never handed out"); + for (i = 0; i < PAGES; i++) { + all[i] = (uint8_t*)pal_memory_alloc(PAGE); + if (all[i] != NULL) { + memset(all[i], 0xA5, PAGE_SIZE_4K); + } + } + check((all[PAGES - 1] != NULL) && (pal_memory_alloc(PAGE) == NULL), + "the pool holds five pages and no more"); + check(pal_memory_free(all[1], PAGE) == PAL_SUCCESS && + pal_memory_free(all[2], PAGE) == PAL_SUCCESS, + "two neighbours are released"); + two = (uint8_t*)pal_memory_alloc(2u * PAGE); + check((two == all[1]) && page_zeroed(two) && page_zeroed(two + PAGE), + "the pair reuses them, wiped"); + check(pal_memory_free(all[1], PAGE) == PAL_ERROR, + "a page inside the new pair is no longer a page allocation"); + check(pal_memory_free(two, 2u * PAGE) == PAL_SUCCESS && + pal_memory_free(all[0], PAGE) == PAL_SUCCESS && + pal_memory_free(all[3], PAGE) == PAL_SUCCESS && + pal_memory_free(all[4], PAGE) == PAL_SUCCESS, + "everything is released"); + for (i = 0; i < PAGES; i++) { + all[i] = (uint8_t*)pal_memory_alloc(PAGE); + } + check((all[PAGES - 1] != NULL) && (pal_memory_alloc(PAGE) == NULL), + "all five pages come back"); + + printf("%d checks, %d failures\n", checks, failures); + return (failures == 0) ? 0 : 1; +} diff --git a/tests/host/acs_pal/pal_interfaces.h b/tests/host/acs_pal/pal_interfaces.h new file mode 100644 index 00000000..ce3d81f5 --- /dev/null +++ b/tests/host/acs_pal/pal_interfaces.h @@ -0,0 +1,61 @@ +/* pal_interfaces.h + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfTrust. + * + * wolfTrust 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. + * + * wolfTrust 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, see . + */ + +/* The slice of the Arm FF-A ACS platform interface pal_misc.c uses, so the + * page pool builds on the host without the ACS tree. */ + +#ifndef WT_TEST_PAL_INTERFACES_H +#define WT_TEST_PAL_INTERFACES_H + +#include +#include + +#define PAGE_SIZE_4K 0x1000 +#define PAL_SUCCESS 0 +#define PAL_ERROR 1 + +#define PLATFORM_NVM_BASE 0x0E800000u +#define PLATFORM_NVM_SIZE 0x10000u +#define ATTR_DEVICE_RW_S 0x5u +#define ATTR_DEVICE_RW 0x4u +#define PLATFORM_NS_UART_BASE 0x09000000u +#define PLATFORM_NS_UART_SIZE 0x1000u +#define GICD_BASE 0x08000000u +#define GICD_SIZE 0x10000u +#define GICR_BASE 0x080A0000u +#define GICR_SIZE 0x200000u +#define GICC_BASE 0x08010000u +#define GICC_SIZE 0x10000u + +typedef struct { + uint64_t virtual_address; + uint64_t physical_address; + uint64_t length; + uint64_t attributes; +} memory_region_descriptor_t; + +uint32_t pal_get_endpoint_device_map(void **region_list, + size_t *no_of_mem_regions); +uint32_t pal_terminate_simulation(void); +void *pal_memory_alloc(uint64_t size); +uint32_t pal_memory_free(void *address, uint64_t size); +void *pal_mem_virt_to_phys(void *va); + +#endif /* WT_TEST_PAL_INTERFACES_H */ diff --git a/tests/host/acs_pal/pal_misc_asm.h b/tests/host/acs_pal/pal_misc_asm.h new file mode 100644 index 00000000..fe8b5ce8 --- /dev/null +++ b/tests/host/acs_pal/pal_misc_asm.h @@ -0,0 +1,32 @@ +/* pal_misc_asm.h + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfTrust. + * + * wolfTrust 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. + * + * wolfTrust 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, see . + */ + +/* pal_misc.c includes the assembly helpers' header. */ + +#ifndef WT_TEST_PAL_MISC_ASM_H +#define WT_TEST_PAL_MISC_ASM_H + +#include + +/* Only the dispatcher's build calls it; the device-map rows compile it. */ +uint64_t pal_syscall_for_psci(uint64_t fid, uint64_t x1, uint64_t x2, + uint64_t x3); + +#endif /* WT_TEST_PAL_MISC_ASM_H */ diff --git a/tests/host/ffa_abi/Makefile b/tests/host/ffa_abi/Makefile new file mode 100644 index 00000000..500cae1a --- /dev/null +++ b/tests/host/ffa_abi/Makefile @@ -0,0 +1,64 @@ +# Makefile +# +# Copyright (C) 2026 wolfSSL Inc. +# +# This file is part of wolfTrust. +# +# wolfTrust 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. +# +# wolfTrust 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 + +ROOT := ../../.. + +CC ?= cc +BUILD_DIR ?= build + +CFLAGS := \ + -I$(ROOT)/include \ + -std=c99 -O0 -g -Wall -Wextra -Werror -pedantic +CFLAGS += $(EXTRA_CFLAGS) + +SRCS := $(ROOT)/src/arch/aarch64/ffa/ffa_msg.c \ + $(ROOT)/src/arch/aarch64/ffa/ffa_mem.c \ + $(ROOT)/src/arch/aarch64/ffa/ffa_partinfo.c main.c +TEST_BIN := $(BUILD_DIR)/test_ffa_abi + +.PHONY: all run compilers sanitize valgrind clean + +all: $(TEST_BIN) + +$(BUILD_DIR): + mkdir -p $@ + +$(TEST_BIN): $(SRCS) | $(BUILD_DIR) + $(CC) $(CFLAGS) $(EXTRA_LDFLAGS) -o $@ $(SRCS) + +run: $(TEST_BIN) + $(TEST_BIN) + +compilers: + $(MAKE) clean run CC=gcc BUILD_DIR=$(BUILD_DIR)/gcc + $(MAKE) clean run CC=clang BUILD_DIR=$(BUILD_DIR)/clang + +sanitize: + $(MAKE) clean run CC=clang \ + BUILD_DIR=$(BUILD_DIR)/sanitize \ + EXTRA_CFLAGS="-fsanitize=address,undefined -fno-omit-frame-pointer" \ + EXTRA_LDFLAGS="-fsanitize=address,undefined" + +valgrind: clean $(TEST_BIN) + valgrind --error-exitcode=1 --leak-check=full --show-leak-kinds=all \ + $(TEST_BIN) + +clean: + rm -rf $(BUILD_DIR) diff --git a/tests/host/ffa_abi/main.c b/tests/host/ffa_abi/main.c new file mode 100644 index 00000000..dc4193c4 --- /dev/null +++ b/tests/host/ffa_abi/main.c @@ -0,0 +1,1246 @@ +/* main.c + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfTrust. + * + * wolfTrust 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. + * + * wolfTrust 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, see . + */ + +/* WT-FFA-0001 / WT-FFA-0002: the FF-A function-id table, status codes, id + * spaces, and the version negotiation rules of DEN0077A 13.2. */ + +#include "wolftrust/arch/aarch64/ffa_abi.h" +#include "wolftrust/arch/aarch64/ffa_manifest.h" +#include "wolftrust/arch/aarch64/ffa_mem.h" +#include "wolftrust/arch/aarch64/ffa_msg.h" +#include "wolftrust/arch/aarch64/ffa_partinfo.h" + +#include +#include +#include + +static int checks; +static int failures; + +static void check(int ok, const char* what) +{ + checks++; + if (ok) { + printf(" [check] PASS %s\n", what); + } + else { + failures++; + printf(" [check] FAIL %s\n", what); + } +} + +static const uint32_t g_fids[] = { + WT_FFA_ERROR, WT_FFA_SUCCESS32, WT_FFA_SUCCESS64, WT_FFA_INTERRUPT, + WT_FFA_VERSION, WT_FFA_FEATURES, WT_FFA_RX_RELEASE, WT_FFA_RXTX_MAP32, + WT_FFA_RXTX_MAP64, WT_FFA_RXTX_UNMAP, WT_FFA_PARTITION_INFO_GET, + WT_FFA_ID_GET, WT_FFA_MSG_WAIT, WT_FFA_YIELD, WT_FFA_RUN, + WT_FFA_MSG_SEND_DIRECT_REQ32, WT_FFA_MSG_SEND_DIRECT_REQ64, + WT_FFA_MSG_SEND_DIRECT_RESP32, WT_FFA_MSG_SEND_DIRECT_RESP64, + WT_FFA_NORMAL_WORLD_RESUME, WT_FFA_NOTIFICATION_BITMAP_CREATE, + WT_FFA_RX_ACQUIRE, WT_FFA_SPM_ID_GET, WT_FFA_MSG_SEND2, + WT_FFA_CONSOLE_LOG32, WT_FFA_CONSOLE_LOG64, + WT_FFA_PARTITION_INFO_GET_REGS, WT_FFA_MSG_SEND_DIRECT_REQ2, + WT_FFA_MSG_SEND_DIRECT_RESP2 +}; + +static const int32_t g_codes[] = { + WT_FFA_NOT_SUPPORTED, WT_FFA_INVALID_PARAMETERS, WT_FFA_NO_MEMORY, + WT_FFA_BUSY, WT_FFA_INTERRUPTED, WT_FFA_DENIED, WT_FFA_RETRY, + WT_FFA_ABORTED, WT_FFA_NO_DATA, WT_FFA_NOT_READY +}; + +/* WT-FFA-0005: direct-message register encodings (15.2/15.3) and the relayer + * checks of 7.4.2. */ +static void direct_message_rows(void) +{ + static const uint32_t payload[WT_FFA_DIRECT_PAYLOAD_WORDS] = { + 0x11111111u, 0x22222222u, 0x33333333u, 0x44444444u, 0x55555555u + }; + uint64_t x[8]; + unsigned int i; + int ok; + + wt_ffa_direct_build(x, WT_FFA_MSG_SEND_DIRECT_REQ32, WT_FFA_ID_NS_PRIMARY, + WT_FFA_ID_SP_FIRST, payload); + ok = ((uint32_t)x[0] == WT_FFA_MSG_SEND_DIRECT_REQ32) && + (wt_ffa_direct_sender(x[1]) == WT_FFA_ID_NS_PRIMARY) && + (wt_ffa_direct_receiver(x[1]) == WT_FFA_ID_SP_FIRST) && (x[2] == 0u); + for (i = 0u; i < WT_FFA_DIRECT_PAYLOAD_WORDS; i++) { + ok = ok && ((uint32_t)x[3u + i] == payload[i]); + } + check(ok, "a direct request packs ids in w1, keeps w2 zero, and carries the payload in x3-x7"); + + check(wt_ffa_direct_req_check(x, WT_FFA_INSTANCE_NS_PHYSICAL) == 0, + "the SPMD relays a Normal-world request to a Secure partition"); + check(wt_ffa_direct_req_check(x, WT_FFA_INSTANCE_SECURE_VIRTUAL) == + WT_FFA_INVALID_PARAMETERS, + "the SPMC does not relay a Normal-world sender between partitions"); + + x[2] = WT_FFA_DIRECT_FRAMEWORK_BIT; + check(wt_ffa_direct_req_check(x, WT_FFA_INSTANCE_NS_PHYSICAL) == + WT_FFA_INVALID_PARAMETERS, + "a request with the framework bit set is INVALID_PARAMETERS"); + x[2] = 0x1u; + check(wt_ffa_direct_req_check(x, WT_FFA_INSTANCE_NS_PHYSICAL) == + WT_FFA_INVALID_PARAMETERS, + "a partition request with w2 bits 7:0 set (MBZ) is INVALID_PARAMETERS"); + x[2] = 0x7FFFFF00u; + check(wt_ffa_direct_req_check(x, WT_FFA_INSTANCE_NS_PHYSICAL) == 0, + "the SBZ w2 bits 30:8 of a partition request are ignored"); + wt_ffa_direct_clear_sbz(x); + check(x[2] == 0u && (uint32_t)x[3] == payload[0], + "and cleared before the request reaches its receiver"); + wt_ffa_direct_build(x, WT_FFA_MSG_SEND_DIRECT_REQ64, + (uint16_t)(WT_FFA_ID_SP_FIRST + 1u), WT_FFA_ID_SP_FIRST, + payload); + x[2] = 0xFFFFFFFF7FFFFF00ull; + check(wt_ffa_direct_req_check(x, WT_FFA_INSTANCE_SECURE_VIRTUAL) == 0, + "a partition's REQ64 at the SVC conduit ignores the SBZ bits too"); + x[2] = 0x80u; + check(wt_ffa_direct_req_check(x, WT_FFA_INSTANCE_SECURE_VIRTUAL) == + WT_FFA_INVALID_PARAMETERS, + "and refuses its MBZ bits 7:0"); + wt_ffa_direct_build(x, WT_FFA_MSG_SEND_DIRECT_REQ32, WT_FFA_ID_NS_PRIMARY, + WT_FFA_ID_SP_FIRST, payload); + + wt_ffa_direct_build(x, WT_FFA_MSG_SEND_DIRECT_REQ32, WT_FFA_ID_SP_FIRST, + WT_FFA_ID_SP_FIRST, payload); + check(wt_ffa_direct_req_check(x, WT_FFA_INSTANCE_SECURE_VIRTUAL) == + WT_FFA_INVALID_PARAMETERS, + "a request whose sender equals its receiver is INVALID_PARAMETERS"); + + wt_ffa_direct_build(x, WT_FFA_MSG_SEND_DIRECT_REQ32, WT_FFA_ID_NS_PRIMARY, + 0x0001u, payload); + check(wt_ffa_direct_req_check(x, WT_FFA_INSTANCE_NS_PHYSICAL) == + WT_FFA_INVALID_PARAMETERS, + "a Normal-world request to a Normal-world receiver is INVALID_PARAMETERS"); + + wt_ffa_direct_build(x, WT_FFA_MSG_SEND_DIRECT_REQ32, + (uint16_t)(WT_FFA_ID_SP_FIRST + 1u), WT_FFA_ID_SP_FIRST, + payload); + check(wt_ffa_direct_req_check(x, WT_FFA_INSTANCE_SECURE_VIRTUAL) == 0, + "the SPMC relays a request between two Secure partitions"); + check(wt_ffa_direct_req_check(x, WT_FFA_INSTANCE_NS_PHYSICAL) == + WT_FFA_INVALID_PARAMETERS, + "the SPMD does not relay a Secure sender as a Normal-world request"); + + wt_ffa_direct_build(x, WT_FFA_MSG_SEND_DIRECT_RESP32, WT_FFA_ID_SP_FIRST, + WT_FFA_ID_NS_PRIMARY, payload); + check(wt_ffa_direct_resp_check(x, WT_FFA_INSTANCE_SECURE_VIRTUAL) == 0 && + wt_ffa_direct_resp_check(x, WT_FFA_INSTANCE_NS_PHYSICAL) == 0, + "a Secure partition's response returns to the Normal-world requester"); + check(wt_ffa_direct_req_check(x, WT_FFA_INSTANCE_SECURE_VIRTUAL) == + WT_FFA_INVALID_PARAMETERS, + "a response frame is not accepted as a request"); + + wt_ffa_direct_build(x, WT_FFA_MSG_SEND_DIRECT_RESP32, WT_FFA_ID_SP_FIRST, + WT_FFA_ID_NS_PRIMARY, payload); + x[2] = 0x7FFFFF00u; + check(wt_ffa_direct_resp_check(x, WT_FFA_INSTANCE_SECURE_VIRTUAL) == 0, + "the SBZ w2 bits 30:8 of a partition response are ignored"); + x[2] = 0xFFFFu; + check(wt_ffa_direct_resp_check(x, WT_FFA_INSTANCE_SECURE_VIRTUAL) == + WT_FFA_INVALID_PARAMETERS, + "but a response with w2 bits 7:0 set (MBZ) is INVALID_PARAMETERS"); + x[0] = WT_FFA_MSG_SEND_DIRECT_RESP64; + x[2] = 0x40000000u; + check(wt_ffa_direct_resp_check(x, WT_FFA_INSTANCE_NS_PHYSICAL) == 0, + "a RESP64 to the Normal world ignores the SBZ bits at the NS " + "physical instance"); + x[2] = WT_FFA_DIRECT_FRAMEWORK_BIT; + check(wt_ffa_direct_resp_check(x, WT_FFA_INSTANCE_NS_PHYSICAL) == + WT_FFA_INVALID_PARAMETERS, + "and refuses a partition response marked as a framework message"); + + wt_ffa_direct_build(x, WT_FFA_MSG_SEND_DIRECT_RESP32, WT_FFA_ID_NS_PRIMARY, + WT_FFA_ID_SP_FIRST, payload); + check(wt_ffa_direct_resp_check(x, WT_FFA_INSTANCE_SECURE_VIRTUAL) == + WT_FFA_INVALID_PARAMETERS, + "a response whose sender is not Secure is INVALID_PARAMETERS"); + + wt_ffa_direct_build(x, WT_FFA_MSG_SEND_DIRECT_REQ2, WT_FFA_ID_NS_PRIMARY, + WT_FFA_ID_SP_FIRST, payload); + x[2] = 0x1122334455667788ull; + check(wt_ffa_direct_req_check(x, WT_FFA_INSTANCE_NS_PHYSICAL) == 0, + "FFA_MSG_SEND_DIRECT_REQ2 carries a UUID in x2, not a reserved word"); + wt_ffa_direct_clear_sbz(x); + check(x[2] == 0x1122334455667788ull, + "and its UUID reaches the receiver intact"); + check(wt_ffa_msg_reg_count(x[0]) == WT_FFA_MSG_REGS_EXT && + wt_ffa_msg_reg_count(WT_FFA_MSG_SEND_DIRECT_REQ64) == + WT_FFA_MSG_REGS, + "only REQ2 and RESP2 relay x8-x17"); + wt_ffa_direct_build(x, WT_FFA_MSG_SEND_DIRECT_RESP2, WT_FFA_ID_SP_FIRST, + WT_FFA_ID_NS_PRIMARY, payload); + x[2] = 0x5555u; + check(wt_ffa_direct_resp_check(x, WT_FFA_INSTANCE_NS_PHYSICAL) == 0 && + wt_ffa_direct_resp_check(x, WT_FFA_INSTANCE_SECURE_VIRTUAL) == 0, + "a RESP2 with its SBZ x2 and x3 set returns to the Normal-world " + "requester, at either instance"); + wt_ffa_direct_clear_sbz(x); + check(x[2] == 0u && x[3] == 0u && (uint32_t)x[4] == payload[1], + "with x2 and x3 cleared"); +} + +static int ext_equal(const uint64_t* x, uint64_t v) +{ + unsigned int i; + + for (i = WT_FFA_MSG_REGS; i < WT_FFA_MSG_REGS_EXT; i++) { + if (x[i] != v) { + return 0; + } + } + return 1; +} + +/* 11.2 / SMCCC 2.6-2.7: a reply to an SMC64 call returns x8-x17 zero unless it + * carries results there; an SMC32 caller's x8-x17 are preserved. */ +static void reply_ext_rows(void) +{ + uint64_t x[WT_FFA_MSG_REGS_EXT]; + + memset(x, 0x77, sizeof(x)); + x[0] = WT_FFA_ERROR; + wt_ffa_reply_clear_ext(WT_FFA_MSG_SEND_DIRECT_REQ32, x); + check(ext_equal(x, 0x7777777777777777ull), + "a reply to an SMC32 call leaves x8-x17 as the caller had them"); + wt_ffa_reply_clear_ext(WT_FFA_MSG_SEND_DIRECT_REQ64, x); + check(ext_equal(x, 0u), "a reply to an SMC64 call returns x8-x17 zero"); + memset(x, 0x77, sizeof(x)); + x[0] = WT_FFA_SUCCESS64; + wt_ffa_reply_clear_ext(WT_FFA_PARTITION_INFO_GET_REGS, x); + check(ext_equal(x, 0x7777777777777777ull), + "FFA_PARTITION_INFO_GET_REGS keeps the descriptors it returns in x8-x17"); + x[0] = WT_FFA_MSG_SEND_DIRECT_RESP2; + wt_ffa_reply_clear_ext(WT_FFA_MSG_SEND_DIRECT_REQ2, x); + check(ext_equal(x, 0x7777777777777777ull), + "a RESP2 keeps its x8-x17 payload"); + x[0] = WT_FFA_ERROR; + wt_ffa_reply_clear_ext(WT_FFA_MSG_SEND_DIRECT_REQ2, x); + check(ext_equal(x, 0u), "an error answering a REQ2 returns x8-x17 zero"); +} + +/* 11.2: a message written into the saved registers of the call it answers + * (x[0] names that call) fills x8-x17 only for REQ2/RESP2; an SMC64 caller's + * other x8-x17 come back zero, an SMC32 caller's are its own. */ +static void msg_deliver_rows(void) +{ + uint64_t x[WT_FFA_MSG_REGS_EXT]; + uint64_t msg[WT_FFA_MSG_REGS_EXT]; + + memset(msg, 0x55, sizeof(msg)); + memset(x, 0x77, sizeof(x)); + x[0] = WT_FFA_MSG_SEND_DIRECT_REQ2; + msg[0] = WT_FFA_ERROR; + wt_ffa_msg_deliver(x, msg); + check(x[0] == WT_FFA_ERROR && x[7] == 0x5555555555555555ull && + ext_equal(x, 0u), + "an error answering a blocked REQ2 returns x8-x17 zero, not its payload"); + memset(x, 0x77, sizeof(x)); + x[0] = WT_FFA_MSG_SEND_DIRECT_REQ2; + msg[0] = WT_FFA_MSG_SEND_DIRECT_RESP2; + wt_ffa_msg_deliver(x, msg); + check(ext_equal(x, 0x5555555555555555ull), + "a RESP2 answering a blocked REQ2 delivers its own x8-x17"); + memset(x, 0x77, sizeof(x)); + x[0] = WT_FFA_MSG_SEND_DIRECT_REQ32; + msg[0] = WT_FFA_YIELD; + wt_ffa_msg_deliver(x, msg); + check(x[0] == WT_FFA_YIELD && ext_equal(x, 0x7777777777777777ull), + "a blocked SMC32 caller keeps its own x8-x17"); + memset(x, 0x77, sizeof(x)); + x[0] = WT_FFA_MSG_SEND_DIRECT_RESP2; + msg[0] = WT_FFA_MSG_SEND_DIRECT_REQ64; + wt_ffa_msg_deliver(x, msg); + check(x[0] == WT_FFA_MSG_SEND_DIRECT_REQ64 && ext_equal(x, 0u), + "a REQ64 to a partition waiting in RESP2 clears that response's x8-x17"); + memset(x, 0x77, sizeof(x)); + x[0] = WT_FFA_MSG_WAIT; + msg[0] = WT_FFA_MSG_SEND_DIRECT_REQ2; + wt_ffa_msg_deliver(x, msg); + check(ext_equal(x, 0x5555555555555555ull), + "a REQ2 to a partition waiting in FFA_MSG_WAIT carries its x8-x17"); +} + +/* FFA_RUN (14.3): w1 names the endpoint and the vCPU of it to run; each + * endpoint here has the single execution context 0. */ +static void run_target_rows(void) +{ + uint16_t id = 0u; + + check(wt_ffa_run_target(0x80020000u, &id) == 0 && id == 0x8002u, + "FFA_RUN names its target endpoint in w1 bits[31:16], vCPU 0"); + id = 0u; + check(wt_ffa_run_target(0x80020001u, &id) == WT_FFA_INVALID_PARAMETERS && + wt_ffa_run_target(0x8002FFFFu, &id) == WT_FFA_INVALID_PARAMETERS, + "a vCPU id other than the endpoint's one execution context is " + "INVALID_PARAMETERS (Table 14.14)"); + + check(wt_ffa_run_busy_check(0x8002u, 0x8002u, 1u, 0u) == 0, + "the requester resumes a callee that yielded to it (8.2)"); + check(wt_ffa_run_busy_check(0x8002u, 0x8002u, 0u, 1u) == 0, + "and one a Non-secure interrupt preempted mid-request (9.3.1.1)"); + check(wt_ffa_run_busy_check(0x8002u, 0x8003u, 0u, 1u) == WT_FFA_DENIED && + wt_ffa_run_busy_check(0x8002u, 0u, 1u, 0u) == WT_FFA_DENIED, + "no other endpoint may resume the callee of someone else's request"); + check(wt_ffa_run_busy_check(0x8002u, 0x8002u, 0u, 0u) == WT_FFA_DENIED, + "a callee still running its request is not resumed"); +} + +static uint32_t rd_u16(const uint8_t* p) +{ + return (uint32_t)p[0] | ((uint32_t)p[1] << 8); +} + +static uint32_t rd_u32(const uint8_t* p) +{ + return (uint32_t)p[0] | ((uint32_t)p[1] << 8) | + ((uint32_t)p[2] << 16) | ((uint32_t)p[3] << 24); +} + +/* WT-FFA-0003 (6.1/6.2 discovery descriptors, Nil-UUID lists all) and + * WT-FFA-0004 (7.2 RX buffer: producer zero-fills, NO_MEMORY when too small). */ +static void partition_info_rows(void) +{ + static const wt_ffa_partinfo_entry_t parts[3] = { + { 0x8002u, 1u, WT_FFA_PARTINFO_PROP_DIRECT_RECV | + WT_FFA_PARTINFO_PROP_DIRECT_SEND, + { 0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08, + 0x09,0x0A,0x0B,0x0C,0x0D,0x0E,0x0F,0x10 } }, + { 0x8003u, 1u, WT_FFA_PARTINFO_PROP_DIRECT_RECV | + WT_FFA_PARTINFO_PROP_DIRECT_SEND, + { 0x11,0x12,0x13,0x14,0x15,0x16,0x17,0x18, + 0x19,0x1A,0x1B,0x1C,0x1D,0x1E,0x1F,0x20 } }, + { 0x8004u, 2u, WT_FFA_PARTINFO_PROP_INDIRECT, + { 0x21,0x22,0x23,0x24,0x25,0x26,0x27,0x28, + 0x29,0x2A,0x2B,0x2C,0x2D,0x2E,0x2F,0x30 } } + }; + static const uint8_t nil[16] = { 0 }; + uint8_t rx[128]; + uint64_t regs[18]; + uint64_t x[4]; + uint32_t count; + uint32_t size; + int ret; + int ok; + unsigned int i; + + check(wt_ffa_partinfo_desc_size(WT_FFA_VERSION_MAKE(1u, 0u)) == + WT_FFA_PARTINFO_DESC_V10 && + wt_ffa_partinfo_desc_size(WT_FFA_VERSION_1_2) == + WT_FFA_PARTINFO_DESC_V11, + "a 1.0 caller gets 8-byte descriptors, a 1.1+ caller 24-byte with the UUID"); + + for (i = 0u; i < sizeof(rx); i++) { + rx[i] = 0xEEu; + } + ret = wt_ffa_partinfo_write(rx, sizeof(rx), WT_FFA_VERSION_1_2, parts, 3u, + nil, 0u, &count, &size); + check(ret == 0 && count == 3u && size == WT_FFA_PARTINFO_DESC_V11, + "a Nil UUID lists every partition with the 1.2 descriptor size"); + check(rd_u16(&rx[0]) == 0x8002u && rd_u16(&rx[2]) == 1u && + rd_u32(&rx[4]) == (WT_FFA_PARTINFO_PROP_DIRECT_RECV | + WT_FFA_PARTINFO_PROP_DIRECT_SEND) && + rx[8] == 0x01u && rx[23] == 0x10u, + "the first descriptor decodes id, context count, properties, and UUID"); + check(rx[5] == 0u && rx[6] == 0u && rx[7] == 0u, + "the producer zeroes descriptor bytes it does not fill"); + check(rd_u16(&rx[WT_FFA_PARTINFO_DESC_V11]) == 0x8003u && + rd_u16(&rx[2u * WT_FFA_PARTINFO_DESC_V11]) == 0x8004u, + "descriptors pack back to back at the descriptor size"); + + for (i = 0u; i < sizeof(rx); i++) { + rx[i] = 0xEEu; + } + ret = wt_ffa_partinfo_write(rx, sizeof(rx), WT_FFA_VERSION_1_2, parts, 3u, + parts[1].uuid, 0u, &count, &size); + check(ret == 0 && count == 1u && rd_u16(&rx[0]) == 0x8003u, + "a specific UUID returns only the matching partition"); + ok = 1; + for (i = 8u; i < WT_FFA_PARTINFO_DESC_V11; i++) { + ok = ok && (rx[i] == 0u); + } + check(ok, "a specific-UUID query leaves the descriptor UUID field zero (MBZ)"); + + ret = wt_ffa_partinfo_write(NULL, 0u, WT_FFA_VERSION_1_2, parts, 3u, nil, + WT_FFA_PARTINFO_FLAG_COUNT, &count, &size); + check(ret == 0 && count == 3u && size == 0u, + "the count-only flag returns the count without writing descriptors"); + + ret = wt_ffa_partinfo_write(rx, sizeof(rx), WT_FFA_VERSION_1_2, parts, 3u, + nil, 0xFFFFFFFEu, &count, &size); + check(ret == 0 && count == 3u && size == WT_FFA_PARTINFO_DESC_V11, + "the SBZ flag bits 31:1 are ignored"); + + ret = wt_ffa_partinfo_write(rx, WT_FFA_PARTINFO_DESC_V11 + 1u, + WT_FFA_VERSION_1_2, parts, 3u, nil, 0u, &count, + &size); + check(ret == WT_FFA_NO_MEMORY, + "an RX buffer too small for the matches is NO_MEMORY"); + + ret = wt_ffa_partinfo_regs(parts, 3u, nil, 0u, 0u, regs); + check(ret == 0 && (uint32_t)regs[0] == WT_FFA_SUCCESS64 && + (regs[2] & 0xFFFFu) == 2u && ((regs[2] >> 16) & 0xFFFFu) == 2u && + (regs[2] >> 48) == WT_FFA_PARTINFO_DESC_V11 && + (regs[3] & 0xFFFFu) == parts[0].id && + (regs[9] & 0xFFFFu) == parts[2].id && regs[12] == 0u, + "FFA_PARTITION_INFO_GET_REGS packs every match from index 0"); + ret = wt_ffa_partinfo_regs(parts, 3u, nil, 2u, 0u, regs); + check(ret == 0 && (regs[3] & 0xFFFFu) == parts[2].id && + ((regs[2] >> 16) & 0xFFFFu) == 2u && regs[6] == 0u, + "a start index resumes the listing there"); + ret = wt_ffa_partinfo_regs(parts, 3u, nil, 0u, 0u, regs); + check(ret == 0 && (uint8_t)regs[4] == parts[0].uuid[0] && + (uint8_t)(regs[5] >> 56) == parts[0].uuid[15] && + (uint8_t)regs[10] == parts[2].uuid[0], + "a Nil-UUID query returns each UUID in two registers"); + ret = wt_ffa_partinfo_regs(parts, 3u, parts[1].uuid, 0u, 0u, regs); + check(ret == 0 && (regs[2] & 0xFFFFu) == 0u && + (regs[3] & 0xFFFFu) == parts[1].id && + regs[4] == 0u && regs[5] == 0u, + "a specific UUID selects its partition with the UUID registers zero (MBZ)"); + check(wt_ffa_partinfo_regs(parts, 3u, nil, 3u, 0u, regs) == + WT_FFA_INVALID_PARAMETERS && + wt_ffa_partinfo_regs(parts, 3u, rx, 0u, 0u, regs) == + WT_FFA_INVALID_PARAMETERS, + "a start past the end or an unknown UUID is refused"); + check(wt_ffa_partinfo_regs(parts, 3u, nil, 0u, 1u, regs) == + WT_FFA_INVALID_PARAMETERS, + "a nonzero tag at start index 0 is INVALID_PARAMETERS (MBZ)"); + check(wt_ffa_partinfo_regs(parts, 3u, nil, 1u, 1u, regs) == WT_FFA_RETRY && + wt_ffa_partinfo_regs(parts, 3u, nil, 1u, 0u, regs) == 0, + "a continuation with a tag the callee did not hand out is RETRY"); + + memset(x, 0, sizeof(x)); + x[0] = WT_FFA_PARTITION_INFO_GET_REGS; + x[3] = 0xFFFFFFFF00000001ull; + check(wt_ffa_partinfo_regs_call(parts, 3u, x, regs) == 0 && + (regs[3] & 0xFFFFu) == parts[1].id, + "the SBZ x3 bits 63:32 of FFA_PARTITION_INFO_GET_REGS are ignored"); + x[3] = 0x00010000u; + check(wt_ffa_partinfo_regs_call(parts, 3u, x, regs) == + WT_FFA_INVALID_PARAMETERS, + "but the tag in x3 bits 31:16 is still read"); +} + +/* Discovery records from the manifest: the id is the live endpoint id of the + * partition running in the domain, and a domain nothing runs in is omitted. */ +static void manifest_record_rows(void) +{ + static const wt_ffa_uuid_t uuids[1] = { + { { 0x4fu, 0xd3u, 0xdau, 0x63u, 0x10u, 0x2cu, 0x5eu, 0xf8u, + 0x9eu, 0xadu, 0x37u, 0x6bu, 0xd7u, 0x22u, 0xc3u, 0x37u } } + }; + static const wt_ffa_partition_manifest_t part = { + uuids, 4u, 1u, 1u, WT_FFA_RUNTIME_EL_SEL0, WT_FFA_MESSAGING_NONE, + WT_FFA_NS_INTERRUPT_QUEUED, WT_FFA_BOOT_INFO_NONE, + WT_FFA_VERSION_1_2 + }; + static const wt_ffa_uuid_t uuids2[2] = { + { { 0x10u, 0x11u, 0x12u, 0x13u, 0x14u, 0x15u, 0x16u, 0x17u, + 0x18u, 0x19u, 0x1au, 0x1bu, 0x1cu, 0x1du, 0x1eu, 0x1fu } }, + { { 0x20u, 0x21u, 0x22u, 0x23u, 0x24u, 0x25u, 0x26u, 0x27u, + 0x28u, 0x29u, 0x2au, 0x2bu, 0x2cu, 0x2du, 0x2eu, 0x2fu } } + }; + static const wt_ffa_partition_manifest_t two = { + uuids2, 7u, 2u, 1u, WT_FFA_RUNTIME_EL_SEL0, WT_FFA_MESSAGING_NONE, + WT_FFA_NS_INTERRUPT_QUEUED, WT_FFA_BOOT_INFO_NONE, + WT_FFA_VERSION_1_2 + }; + static const wt_ffa_partition_manifest_t none = { + uuids2, 8u, 0u, 1u, WT_FFA_RUNTIME_EL_SEL0, WT_FFA_MESSAGING_NONE, + WT_FFA_NS_INTERRUPT_QUEUED, WT_FFA_BOOT_INFO_NONE, + WT_FFA_VERSION_1_2 + }; + static const uint8_t nil[16] = { 0 }; + wt_ffa_partinfo_entry_t out[2]; + uint32_t count = 0u; + uint32_t size = 0u; + size_t n = 99u; + int ret; + + ret = wt_ffa_partinfo_from_manifest(&part, 0x8002u, out, 2u, &n); + check(ret == 0 && n == 1u && out[0].id == 0x8002u && + out[0].exec_contexts == 1u && + memcmp(out[0].uuid, uuids[0].bytes, 16u) == 0, + "a manifest partition is listed under its live id with its UUID"); + check((out[0].properties & WT_FFA_PARTINFO_PROP_AARCH64) != 0u, + "a manifest partition reports the AArch64 execution state (bit 8)"); + check(out[0].properties == WT_FFA_PARTINFO_PROP_AARCH64, + "a manifest partition declaring direct messaging advertises no FF-A " + "messaging, since its services are reached through the PSA endpoint"); + check(wt_ffa_partinfo_write(NULL, 0u, WT_FFA_VERSION_1_2, out, n, + uuids[0].bytes, WT_FFA_PARTINFO_FLAG_COUNT, + &count, &size) == 0 && count == 1u, + "its UUID finds that record"); + n = 99u; + ret = wt_ffa_partinfo_from_manifest(&part, 0u, out, 2u, &n); + check(ret == 0 && n == 0u, + "a domain no live partition runs in is omitted"); + n = 99u; + ret = wt_ffa_partinfo_from_manifest(&part, 0x8007u, out, 0u, &n); + check(ret == WT_FFA_NO_MEMORY && n == 0u, + "no room for the record is NO_MEMORY"); + check(wt_ffa_partinfo_write(NULL, 0u, WT_FFA_VERSION_1_2, out, 0u, nil, + WT_FFA_PARTINFO_FLAG_COUNT, &count, &size) == + 0 && count == 0u, + "an empty listing counts zero"); + + n = 99u; + ret = wt_ffa_partinfo_from_manifest(&two, 0x8005u, out, 2u, &n); + check(ret == 0 && n == 2u && out[0].id == 0x8005u && + out[1].id == 0x8005u && + memcmp(out[0].uuid, uuids2[0].bytes, 16u) == 0 && + memcmp(out[1].uuid, uuids2[1].bytes, 16u) == 0, + "a partition exporting two UUIDs gets a record per UUID under one id"); + check(wt_ffa_partinfo_write(NULL, 0u, WT_FFA_VERSION_1_2, out, n, nil, + WT_FFA_PARTINFO_FLAG_COUNT, &count, &size) == + 0 && count == 2u, + "a Nil-UUID count covers every exported UUID"); + check(wt_ffa_partinfo_write(NULL, 0u, WT_FFA_VERSION_1_2, out, n, + uuids2[1].bytes, WT_FFA_PARTINFO_FLAG_COUNT, + &count, &size) == 0 && count == 1u, + "the second UUID finds the partition too"); + n = 99u; + check(wt_ffa_partinfo_from_manifest(&two, 0x8005u, out, 1u, &n) == + WT_FFA_NO_MEMORY && n == 0u, + "room for fewer records than UUIDs is NO_MEMORY"); + check(wt_ffa_partinfo_from_manifest(&none, 0x8006u, out, 2u, &n) == + WT_FFA_INVALID_PARAMETERS, + "a partition exporting no UUID is refused"); +} + +/* Tables 6.2, 15.8 and 15.16: a direct request goes only to an endpoint that + * takes that kind (DENIED otherwise), and an id no partition has is + * INVALID_PARAMETERS. */ +static void direct_permission_rows(void) +{ + static const wt_ffa_partinfo_entry_t parts[3] = { + { 0x8008u, 1u, 0x70Fu, { 0x01 } }, + { 0x8002u, 1u, WT_FFA_PARTINFO_PROP_AARCH64, { 0x02 } }, + { 0x8002u, 1u, WT_FFA_PARTINFO_PROP_NOTIF, { 0x03 } } + }; + const uint32_t native = 0x70Fu; + const uint32_t manifest = WT_FFA_PARTINFO_PROP_AARCH64; + const uint32_t req_only = WT_FFA_PARTINFO_PROP_DIRECT_RECV; + uint32_t props = 99u; + + check(wt_ffa_partinfo_props_of(parts, 3u, 0x8008u, &props) == 0 && + props == 0x70Fu, + "a listed partition's properties are found by its id"); + check(wt_ffa_partinfo_props_of(parts, 3u, 0x8002u, &props) == 0 && + props == (WT_FFA_PARTINFO_PROP_AARCH64 | + WT_FFA_PARTINFO_PROP_NOTIF), + "a partition listed once per UUID has the union of its records"); + check(wt_ffa_partinfo_props_of(parts, 3u, 0x8009u, &props) == + WT_FFA_INVALID_PARAMETERS && + wt_ffa_partinfo_props_of(parts, 0u, 0x8008u, &props) == + WT_FFA_INVALID_PARAMETERS, + "an id no partition has is INVALID_PARAMETERS"); + + check(wt_ffa_direct_req_allowed(native, WT_FFA_MSG_SEND_DIRECT_REQ32, 1) == + 0 && + wt_ffa_direct_req_allowed(native, WT_FFA_MSG_SEND_DIRECT_REQ64, 1) == + 0 && + wt_ffa_direct_req_allowed(native, WT_FFA_MSG_SEND_DIRECT_REQ2, 1) == + 0 && + wt_ffa_direct_req_allowed(native, WT_FFA_MSG_SEND_DIRECT_REQ2, 0) == + 0, + "an endpoint advertising both kinds takes and sends both"); + check(wt_ffa_direct_req_allowed(manifest, WT_FFA_MSG_SEND_DIRECT_REQ32, + 1) == WT_FFA_DENIED && + wt_ffa_direct_req_allowed(manifest, WT_FFA_MSG_SEND_DIRECT_REQ64, + 1) == WT_FFA_DENIED && + wt_ffa_direct_req_allowed(manifest, WT_FFA_MSG_SEND_DIRECT_REQ2, + 1) == WT_FFA_DENIED, + "a request to an endpoint that takes none is DENIED"); + check(wt_ffa_direct_req_allowed(req_only, WT_FFA_MSG_SEND_DIRECT_REQ32, + 1) == 0 && + wt_ffa_direct_req_allowed(req_only, WT_FFA_MSG_SEND_DIRECT_REQ2, + 1) == WT_FFA_DENIED, + "FFA_MSG_SEND_DIRECT_REQ receipt does not imply REQ2 receipt"); + check(wt_ffa_direct_req_allowed(req_only, WT_FFA_MSG_SEND_DIRECT_REQ32, + 0) == WT_FFA_DENIED && + wt_ffa_direct_req_allowed(WT_FFA_PARTINFO_PROP_REQ2_SEND, + WT_FFA_MSG_SEND_DIRECT_REQ2, 0) == 0, + "sending is judged on the send bits, not the receive bits"); + + check(wt_ffa_direct_req_authorize(parts, 3u, 0x8008u, 0x8002u, + WT_FFA_MSG_SEND_DIRECT_REQ32) == + WT_FFA_DENIED, + "a listed sender may not reach a receiver that takes nothing"); + check(wt_ffa_direct_req_authorize(parts, 3u, 0x8002u, 0x8008u, + WT_FFA_MSG_SEND_DIRECT_REQ32) == + WT_FFA_DENIED, + "a listed sender that advertises no sending may not send"); + check(wt_ffa_direct_req_authorize(parts, 3u, 0x8009u, 0x8008u, + WT_FFA_MSG_SEND_DIRECT_REQ32) == + WT_FFA_DENIED && + wt_ffa_direct_req_authorize(parts, 3u, 0x8009u, 0x8008u, + WT_FFA_MSG_SEND_DIRECT_REQ2) == + WT_FFA_DENIED, + "a sender discovery does not list may send nothing, even to an " + "endpoint that takes both kinds"); + check(wt_ffa_direct_req_authorize(parts, 3u, 0x8008u, 0x8009u, + WT_FFA_MSG_SEND_DIRECT_REQ32) == + WT_FFA_INVALID_PARAMETERS, + "a receiver no partition has is INVALID_PARAMETERS"); + + check(wt_ffa_msg2_sender_allowed(parts, 3u, 0x8008u) == 0, + "a partition advertising indirect messaging may send FFA_MSG_SEND2"); + check(wt_ffa_msg2_sender_allowed(parts, 3u, 0x8002u) == WT_FFA_DENIED, + "one that does not advertise it may not"); + check(wt_ffa_msg2_sender_allowed(parts, 3u, 0x8009u) == WT_FFA_DENIED, + "nor may a partition discovery does not list"); + check(wt_ffa_msg2_sender_allowed(parts, 3u, WT_FFA_ID_NS_PRIMARY) == 0, + "the Normal world may send one at the NS physical instance"); +} + +/* FFA_PARTITION_INFO_GET through the caller's mailbox (13.8, Table 13.36): + * descriptors need its RX buffer mapped and free, a count needs none. */ +static void partition_info_mailbox_rows(void) +{ + static const wt_ffa_partinfo_entry_t parts[2] = { + { 0x8002u, 1u, 0u, + { 0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08, + 0x09,0x0A,0x0B,0x0C,0x0D,0x0E,0x0F,0x10 } }, + { 0x8003u, 1u, 0u, + { 0x11,0x12,0x13,0x14,0x15,0x16,0x17,0x18, + 0x19,0x1A,0x1B,0x1C,0x1D,0x1E,0x1F,0x20 } } + }; + static uint8_t pair[2][4096] __attribute__((aligned(4096))); + const uint32_t v12 = WT_FFA_VERSION_1_2; + wt_ffa_mailbox_t mb; + uint64_t x[8] = { WT_FFA_PARTITION_INFO_GET, 0u, 0u, 0u, 0u, 0u, 0u, 0u }; + uint32_t count = 0u; + uint32_t size = 0u; + int ret; + + memset(&mb, 0, sizeof(mb)); + memset(pair, 0xEE, sizeof(pair)); + x[5] = WT_FFA_PARTINFO_FLAG_COUNT; + ret = wt_ffa_partinfo_get(x, v12, NULL, parts, 2u, &count, &size); + check(ret == 0 && count == 2u && size == 0u, + "a count-only query needs no RX buffer"); + x[5] = 0xFFFFFFFFu; + ret = wt_ffa_partinfo_get(x, v12, NULL, parts, 2u, &count, &size); + check(ret == 0 && count == 2u && size == 0u, + "the SBZ flag bits 31:1 of FFA_PARTITION_INFO_GET are ignored"); + x[5] = 0u; + check(wt_ffa_partinfo_get(x, v12, NULL, parts, 2u, &count, &size) == + WT_FFA_BUSY && + wt_ffa_partinfo_get(x, v12, &mb, parts, 2u, &count, &size) == + WT_FFA_BUSY && pair[1][0] == 0xEEu, + "descriptors with no RX buffer mapped are BUSY and written nowhere"); + + check(wt_ffa_mailbox_map(&mb, (uint64_t)(uintptr_t)pair[0], + (uint64_t)(uintptr_t)pair[1], 1u) == 0, + "the caller maps its RX/TX pair"); + ret = wt_ffa_partinfo_get(x, v12, &mb, parts, 2u, &count, &size); + check(ret == 0 && count == 2u && size == WT_FFA_PARTINFO_DESC_V11 && + rd_u16(&pair[1][0]) == 0x8002u && + rd_u16(&pair[1][WT_FFA_PARTINFO_DESC_V11]) == 0x8003u && + mb.rx_full != 0u, + "descriptors land in the mapped RX buffer, which the caller now owns"); + check(wt_ffa_partinfo_get(x, v12, &mb, parts, 2u, &count, &size) == + WT_FFA_BUSY, + "a second query before RX_RELEASE is BUSY"); + check(wt_ffa_mailbox_rx_release(&mb) == 0 && + wt_ffa_partinfo_get(x, v12, &mb, parts, 2u, &count, &size) == 0, + "after RX_RELEASE the buffer takes descriptors again"); + check(wt_ffa_mailbox_rx_release(&mb) == 0, + "the caller hands RX back after reading"); + + x[1] = 0xDEADBEEFu; + check(wt_ffa_partinfo_get(x, v12, &mb, parts, 2u, &count, &size) == + WT_FFA_INVALID_PARAMETERS && mb.rx_full == 0u, + "an unknown UUID is refused before the RX buffer changes hands"); + x[1] = 0x14131211u; + x[2] = 0x18171615u; + x[3] = 0x1C1B1A19u; + x[4] = 0x201F1E1Du; + ret = wt_ffa_partinfo_get(x, v12, &mb, parts, 2u, &count, &size); + check(ret == 0 && count == 1u && rd_u16(&pair[1][0]) == 0x8003u, + "a UUID in w1-w4 selects its partition"); + check(wt_ffa_mailbox_rx_release(&mb) == 0 && + wt_ffa_mailbox_unmap(&mb) == 0 && + wt_ffa_partinfo_get(x, v12, &mb, parts, 2u, &count, &size) == + WT_FFA_BUSY, + "once the pair is unmapped, descriptors are BUSY again"); +} + +/* 18.5.3: a caller that negotiated FF-A 1.0 gets the v1.0 partition + * information descriptor (Table 18.22), a 1.1+ caller the 24-byte one. */ +static void partition_info_version_rows(void) +{ + static const wt_ffa_partinfo_entry_t parts[2] = { + { 0x8002u, 1u, 0x0000070Fu, + { 0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08, + 0x09,0x0A,0x0B,0x0C,0x0D,0x0E,0x0F,0x10 } }, + { 0x8003u, 1u, 0x00000105u, + { 0x11,0x12,0x13,0x14,0x15,0x16,0x17,0x18, + 0x19,0x1A,0x1B,0x1C,0x1D,0x1E,0x1F,0x20 } } + }; + static uint8_t pair[2][4096] __attribute__((aligned(4096))); + wt_ffa_mailbox_t mb; + uint64_t x[8] = { WT_FFA_PARTITION_INFO_GET, 0u, 0u, 0u, 0u, 0u, 0u, 0u }; + uint32_t count = 0u; + uint32_t size = 0u; + unsigned int i; + int ok; + int ret; + + memset(&mb, 0, sizeof(mb)); + memset(pair, 0xEE, sizeof(pair)); + check(wt_ffa_mailbox_map(&mb, (uint64_t)(uintptr_t)pair[0], + (uint64_t)(uintptr_t)pair[1], 1u) == 0, + "the v1.0 caller maps its RX/TX pair"); + ret = wt_ffa_partinfo_get(x, WT_FFA_VERSION_MAKE(1u, 0u), &mb, parts, 2u, + &count, &size); + check(ret == 0 && count == 2u && size == WT_FFA_PARTINFO_DESC_V10 && + rd_u16(&pair[1][0]) == 0x8002u && + rd_u16(&pair[1][WT_FFA_PARTINFO_DESC_V10]) == 0x8003u, + "a 1.0 caller gets 8-byte descriptors packed back to back"); + check(rd_u32(&pair[1][4]) == 0x7u && + rd_u32(&pair[1][WT_FFA_PARTINFO_DESC_V10 + 4u]) == 0x5u, + "and only the property bits the v1.0 descriptor defines (2:0)"); + ok = 1; + for (i = 2u * WT_FFA_PARTINFO_DESC_V10; + i < (2u * WT_FFA_PARTINFO_DESC_V11); i++) { + ok = ok && (pair[1][i] == 0xEEu); + } + check(ok, "nothing is written past the two v1.0 descriptors"); + check(wt_ffa_mailbox_rx_release(&mb) == 0, "the 1.0 caller hands RX back"); + + ret = wt_ffa_partinfo_get(x, WT_FFA_VERSION_MAKE(1u, 1u), &mb, parts, 2u, + &count, &size); + check(ret == 0 && count == 2u && size == WT_FFA_PARTINFO_DESC_V11 && + rd_u32(&pair[1][4]) == 0x70Fu && pair[1][8] == 0x01u && + rd_u16(&pair[1][WT_FFA_PARTINFO_DESC_V11]) == 0x8003u, + "a 1.1 caller gets the 24-byte descriptor with every property and the UUID"); + check(wt_ffa_mailbox_rx_release(&mb) == 0 && wt_ffa_mailbox_unmap(&mb) == 0, + "the caller releases and unmaps its pair"); +} + +/* WT-FFA-0002 (version renegotiation, 13.2). */ +static void version_state_rows(void) +{ + wt_ffa_version_state_t st = { 0u, 0u }; + + check(wt_ffa_version_negotiate(&st, WT_FFA_VERSION_MAKE(1u, 1u), + WT_FFA_VERSION_1_2) == + (int32_t)WT_FFA_VERSION_1_2 && + wt_ffa_version_negotiate(&st, WT_FFA_VERSION_1_2, + WT_FFA_VERSION_1_2) == + (int32_t)WT_FFA_VERSION_1_2, + "a caller may renegotiate before its first other call"); + wt_ffa_version_lock(&st, WT_FFA_VERSION_1_2); + check(wt_ffa_version_negotiate(&st, WT_FFA_VERSION_MAKE(1u, 1u), + WT_FFA_VERSION_1_2) == WT_FFA_NOT_SUPPORTED && + wt_ffa_version_negotiate(&st, WT_FFA_VERSION_1_2, + WT_FFA_VERSION_1_2) == + (int32_t)WT_FFA_VERSION_1_2, + "after it, only the settled version is accepted"); + st.version = 0u; + st.locked = 0u; + wt_ffa_version_lock(&st, WT_FFA_VERSION_1_2); + check(wt_ffa_version_negotiate(&st, WT_FFA_VERSION_MAKE(1u, 1u), + WT_FFA_VERSION_1_2) == WT_FFA_NOT_SUPPORTED, + "a caller that never negotiated is held to the callee version"); + + st.version = 0u; + st.locked = 0u; + check(wt_ffa_version_negotiate(&st, WT_FFA_VERSION_MAKE(1u, 0u), + WT_FFA_VERSION_1_2) == + (int32_t)WT_FFA_VERSION_1_2 && + wt_ffa_version_of(&st, WT_FFA_VERSION_1_2) == + WT_FFA_VERSION_MAKE(1u, 0u), + "a 1.0 caller is told 1.2 before the lock and settles on 1.0"); + wt_ffa_version_lock(&st, WT_FFA_VERSION_1_2); + check(wt_ffa_version_negotiate(&st, WT_FFA_VERSION_MAKE(1u, 0u), + WT_FFA_VERSION_1_2) == + (int32_t)WT_FFA_VERSION_MAKE(1u, 0u) && + wt_ffa_version_negotiate(&st, WT_FFA_VERSION_1_2, + WT_FFA_VERSION_1_2) == + (int32_t)WT_FFA_VERSION_MAKE(1u, 0u), + "once locked at 1.0 that is the only version it is told, for 1.0 " + "and for a later 1.2 alike (13.2.2)"); + + st.version = 0u; + st.locked = 0u; + check(wt_ffa_version_negotiate(&st, WT_FFA_VERSION_MAKE(1u, 4u), + WT_FFA_VERSION_1_2) == + (int32_t)WT_FFA_VERSION_1_2 && + wt_ffa_version_of(&st, WT_FFA_VERSION_1_2) == WT_FFA_VERSION_1_2, + "a caller asking 1.4 is told 1.2 and settles on 1.2, not 1.4"); + wt_ffa_version_lock(&st, WT_FFA_VERSION_1_2); + check(wt_ffa_version_negotiate(&st, WT_FFA_VERSION_1_2, + WT_FFA_VERSION_1_2) == + (int32_t)WT_FFA_VERSION_1_2 && + wt_ffa_version_negotiate(&st, WT_FFA_VERSION_MAKE(1u, 4u), + WT_FFA_VERSION_1_2) == + (int32_t)WT_FFA_VERSION_1_2 && + wt_ffa_version_negotiate(&st, WT_FFA_VERSION_MAKE(2u, 0u), + WT_FFA_VERSION_1_2) == + (int32_t)WT_FFA_VERSION_1_2, + "once locked it may repeat 1.2, and a later version is told 1.2"); + + st.version = 0u; + st.locked = 0u; + (void)wt_ffa_version_negotiate(&st, WT_FFA_VERSION_MAKE(1u, 0u), + WT_FFA_VERSION_1_2); + wt_ffa_version_lock(&st, WT_FFA_VERSION_1_2); + check(wt_ffa_version_negotiate(&st, WT_FFA_VERSION_MAKE(1u, 0u), + WT_FFA_VERSION_1_2) == + (int32_t)WT_FFA_VERSION_MAKE(1u, 0u) && + wt_ffa_version_negotiate(&st, WT_FFA_VERSION_1_2, + WT_FFA_VERSION_1_2) == + (int32_t)WT_FFA_VERSION_MAKE(1u, 0u) && + wt_ffa_version_negotiate(&st, 0u, WT_FFA_VERSION_1_2) == + WT_FFA_NOT_SUPPORTED, + "a caller locked at 1.0 asking a later version is told 1.0, the only " + "version it may use"); +} + + +#define V12 WT_FFA_VERSION_1_2 +#define V11 WT_FFA_VERSION_MAKE(1u, 1u) + +/* The v1.2 partition message header of FFA_MSG_SEND2 (15.1) and the w1/w2 + * rules of Table 15.3 per instance, as its relayer validates them. */ +static void msg2_rows(void) +{ + uint8_t tx[4096]; + wt_ffa_msg2_t m; + const wt_ffa_instance_t ns = WT_FFA_INSTANCE_NS_PHYSICAL; + const wt_ffa_instance_t sv = WT_FFA_INSTANCE_SECURE_VIRTUAL; + static const uint8_t ep_uuid[16] = { + 1u, 2u, 3u, 4u, 5u, 6u, 7u, 8u, + 9u, 10u, 11u, 12u, 13u, 14u, 15u, 16u + }; + unsigned int i; + + memset(tx, 0, sizeof(tx)); + /* offset 40, sender 0 receiver 0x8002, size 32, uuid = the endpoint's */ + tx[8] = 40u; + tx[12] = 0x02u; tx[13] = 0x80u; + tx[16] = 32u; + for (i = 0u; i < 16u; i++) { + tx[24u + i] = ep_uuid[i]; + } + + check(wt_ffa_msg2_parse(tx, sizeof(tx), 0u, V12, ns, 0u, 0u, &m) == 0, + "a well-formed header parses"); + check((m.receiver == 0x8002u) && (m.offset == 40u) && (m.size == 32u), + "and yields receiver, offset and size"); + check(wt_ffa_msg2_uuid_ok(m.uuid, ep_uuid) == 1, + "the receiver's own UUID is accepted"); + tx[24] = 0xAAu; + check(wt_ffa_msg2_uuid_ok(&tx[24], ep_uuid) == 0, + "a foreign UUID is refused"); + memset(&tx[24], 0, 16u); + check(wt_ffa_msg2_uuid_ok(&tx[24], ep_uuid) == 1, + "a Nil UUID is accepted"); + + check(wt_ffa_msg2_parse(tx, 39u, 0u, V12, ns, 0u, 0u, &m) == + WT_FFA_INVALID_PARAMETERS, "a TX smaller than the header is refused"); + check(wt_ffa_msg2_parse(tx, sizeof(tx), 0u, V12, ns, 0xFFFFu, 0u, &m) == 0, + "the SBZ w1 bits 15:0 are ignored"); + check(wt_ffa_msg2_parse(tx, sizeof(tx), 0u, V12, ns, 0u, 0xFFFFFFFDu, + &m) == 0, + "at the NS physical instance the SBZ w2 flags are ignored"); + check(wt_ffa_msg2_parse(tx, sizeof(tx), 0u, V12, ns, 0u, + WT_FFA_MSG2_FLAG_DELAY_SRI, &m) == + WT_FFA_INVALID_PARAMETERS, + "and so is the delay-SRI hint, Secure virtual only"); + check(wt_ffa_msg2_parse(tx, sizeof(tx), 0x8003u, V12, sv, 0u, 0u, &m) == + WT_FFA_INVALID_PARAMETERS, + "a header sender other than the caller is refused"); + tx[14] = 0x03u; tx[15] = 0x80u; + check(wt_ffa_msg2_parse(tx, sizeof(tx), 0x8003u, V12, sv, 0u, 0u, &m) == 0, + "a secure caller leaves the w1 sender zero"); + check(wt_ffa_msg2_parse(tx, sizeof(tx), 0x8003u, V12, sv, 0x80030000u, 0u, + &m) == WT_FFA_INVALID_PARAMETERS && + wt_ffa_msg2_parse(tx, sizeof(tx), 0x8003u, V12, sv, 0x00010000u, 0u, + &m) == WT_FFA_INVALID_PARAMETERS, + "at the SVC conduit the w1 sender is MBZ, even the caller's own id"); + check(wt_ffa_msg2_parse(tx, sizeof(tx), 0x8003u, V12, sv, 0u, 0xFFFFFFFFu, + &m) == 0, + "at the SVC conduit w2 is ignored"); + tx[14] = 0x01u; tx[15] = 0u; + check(wt_ffa_msg2_parse(tx, sizeof(tx), 1u, V12, ns, 0x00010000u, 0u, + &m) == 0, + "at the NS physical instance w1 may name the sender VM"); + check(wt_ffa_msg2_parse(tx, sizeof(tx), 1u, V12, ns, 0x00020000u, 0u, &m) == + WT_FFA_INVALID_PARAMETERS, + "at the NS physical instance a w1 sender other than the caller is refused"); + tx[14] = 0u; tx[15] = 0u; + tx[0] = 1u; tx[4] = 1u; tx[20] = 1u; + check(wt_ffa_msg2_parse(tx, sizeof(tx), 0u, V12, ns, 0u, 0u, &m) == 0, + "the SBZ header flags and reserved words are ignored"); + tx[0] = 0u; tx[4] = 0u; tx[20] = 0u; + tx[8] = 39u; + check(wt_ffa_msg2_parse(tx, sizeof(tx), 0u, V12, ns, 0u, 0u, &m) == + WT_FFA_INVALID_PARAMETERS, "an offset inside the header is refused"); + tx[8] = 40u; + tx[16] = 0xFFu; tx[17] = 0xFFu; tx[18] = 0xFFu; tx[19] = 0xFFu; + check(wt_ffa_msg2_parse(tx, sizeof(tx), 0u, V12, ns, 0u, 0u, &m) == + WT_FFA_INVALID_PARAMETERS, "a payload past the TX end is refused"); + tx[16] = 32u; tx[17] = 0u; tx[18] = 0u; tx[19] = 0u; + tx[12] = 0u; tx[13] = 0u; + check(wt_ffa_msg2_parse(tx, sizeof(tx), 0u, V12, ns, 0u, 0u, &m) == + WT_FFA_INVALID_PARAMETERS, "a receiver equal to the sender is refused"); +} + +/* 7.2.2.3.2: the relayer produces the receiver's RX, so a partition message + * lands with every byte it does not populate cleared. */ +static void msg2_copy_rows(void) +{ + uint8_t tx[256]; + uint8_t rx[256]; + wt_ffa_msg2_t m; + unsigned int i; + int ok = 1; + + memset(tx, 0xBB, sizeof(tx)); + memset(tx, 0, WT_FFA_MSG2_HEADER_SIZE); + tx[8] = 64u; + tx[12] = 0x02u; tx[13] = 0x80u; + tx[16] = 16u; + for (i = 64u; i < 80u; i++) { + tx[i] = (uint8_t)i; + } + memset(rx, 0xAA, sizeof(rx)); + check(wt_ffa_msg2_parse(tx, sizeof(tx), 0u, V12, + WT_FFA_INSTANCE_NS_PHYSICAL, 0u, 0u, &m) == 0, + "a message with a gap between header and payload parses"); + wt_ffa_msg2_copy(rx, sizeof(rx), V12, tx, &m); + check(memcmp(rx, tx, WT_FFA_MSG2_HEADER_SIZE) == 0 && + memcmp(&rx[64], &tx[64], 16u) == 0, + "the header and payload reach the receiver's RX"); + for (i = WT_FFA_MSG2_HEADER_SIZE; i < 64u; i++) { + ok = ok && (rx[i] == 0u); + } + check(ok != 0, "the sender's bytes between header and payload do not"); + ok = 1; + for (i = 80u; i < sizeof(rx); i++) { + ok = ok && (rx[i] == 0u); + } + check(ok != 0, "nor does anything left in the RX past the payload"); + + tx[0] = 0x11u; tx[5] = 0x22u; tx[23] = 0x33u; + check(wt_ffa_msg2_parse(tx, sizeof(tx), 0u, V12, + WT_FFA_INSTANCE_NS_PHYSICAL, 0u, 0u, &m) == 0, + "a header with its SBZ words set parses"); + wt_ffa_msg2_copy(rx, sizeof(rx), V12, tx, &m); + check(rx[0] == 0u && rx[5] == 0u && rx[23] == 0u, + "and reaches the receiver with them cleared"); + + /* The sender rewrites its TX header between the parse and the copy. */ + tx[8] = 41u; + tx[12] = 0x09u; tx[13] = 0x80u; tx[14] = 0x05u; tx[15] = 0x80u; + tx[16] = 0xF0u; + tx[24] = 0x77u; + memset(rx, 0xAA, sizeof(rx)); + wt_ffa_msg2_copy(rx, sizeof(rx), V12, tx, &m); + check(rx[8] == 64u && rx[12] == 0x02u && rx[13] == 0x80u && + rx[14] == 0u && rx[15] == 0u && rx[16] == 16u && rx[24] == 0u, + "a TX header rewritten after the parse cannot change the sender, " + "receiver, offset, size or UUID the receiver is handed"); + + /* The same at the SVC conduit: a partition 0x8003 sends to 0x8002. */ + memset(tx, 0, WT_FFA_MSG2_HEADER_SIZE); + tx[8] = 64u; + tx[12] = 0x02u; tx[13] = 0x80u; tx[14] = 0x03u; tx[15] = 0x80u; + tx[16] = 16u; + check(wt_ffa_msg2_parse(tx, sizeof(tx), 0x8003u, V12, + WT_FFA_INSTANCE_SECURE_VIRTUAL, 0u, + WT_FFA_MSG2_FLAG_DELAY_SRI, &m) == 0, + "a partition's message parses at the SVC conduit"); + tx[14] = 0x04u; + tx[16] = 0xF0u; + wt_ffa_msg2_copy(rx, sizeof(rx), V12, tx, &m); + check(rx[14] == 0x03u && rx[15] == 0x80u && rx[16] == 16u, + "and its rewritten TX header cannot spoof the sender or size either"); +} + +/* Table 7.2's 20-byte header is the one a v1.0 or v1.1 endpoint uses: a + * sender's header is read, and a receiver's written, in the layout of the + * version each negotiated. */ +static void msg2_version_rows(void) +{ + const wt_ffa_instance_t ns = WT_FFA_INSTANCE_NS_PHYSICAL; + uint8_t tx[256]; + uint8_t rx[256]; + wt_ffa_msg2_t m; + unsigned int i; + int ok = 1; + + check(wt_ffa_msg2_header_size(WT_FFA_VERSION_MAKE(1u, 0u)) == 20u && + wt_ffa_msg2_header_size(V11) == 20u && + wt_ffa_msg2_header_size(V12) == 40u, + "a v1.0 or v1.1 header is 20 bytes, a v1.2 one 40"); + memset(tx, 0, sizeof(tx)); + tx[8] = 20u; + tx[12] = 0x02u; tx[13] = 0x80u; + tx[16] = 16u; + for (i = 20u; i < 36u; i++) { + tx[i] = (uint8_t)(0xC0u + i); + } + check(wt_ffa_msg2_parse(tx, sizeof(tx), 0u, V11, ns, 0u, 0u, &m) == 0 && + m.offset == 20u && m.size == 16u && m.receiver == 0x8002u, + "a v1.1 sender's payload right behind its 20-byte header parses"); + for (i = 0u; i < 16u; i++) { + ok = ok && (m.uuid[i] == 0u); + } + check(ok != 0, "and its payload is not read as a UUID it has no field for"); + check(wt_ffa_msg2_parse(tx, sizeof(tx), 0u, V12, ns, 0u, 0u, &m) == + WT_FFA_INVALID_PARAMETERS, + "the same offset is inside a v1.2 sender's header"); + check(wt_ffa_msg2_parse(tx, 19u, 0u, V11, ns, 0u, 0u, &m) == + WT_FFA_INVALID_PARAMETERS, + "a TX smaller than a v1.1 header is refused"); + tx[8] = 19u; + check(wt_ffa_msg2_parse(tx, sizeof(tx), 0u, V11, ns, 0u, 0u, &m) == + WT_FFA_INVALID_PARAMETERS, + "an offset inside a v1.1 header is refused"); + tx[8] = 20u; + check(wt_ffa_msg2_parse(tx, sizeof(tx), 0u, V11, ns, 0u, 0u, &m) == 0 && + wt_ffa_msg2_rx_offset(&m, V12) == 40u && + wt_ffa_msg2_rx_offset(&m, V11) == 20u, + "a v1.1 payload moves behind a v1.2 receiver's header only"); + memset(rx, 0xAA, sizeof(rx)); + wt_ffa_msg2_copy(rx, sizeof(rx), V12, tx, &m); + ok = (rx[8] == 40u) && (rx[16] == 16u) && + (memcmp(&rx[40], &tx[20], 16u) == 0); + for (i = 20u; i < 40u; i++) { + ok = ok && (rx[i] == 0u); + } + check(ok != 0, "a v1.2 receiver gets a Nil UUID and the payload at 40"); + memset(rx, 0xAA, sizeof(rx)); + wt_ffa_msg2_copy(rx, sizeof(rx), V11, tx, &m); + check(rx[8] == 20u && rx[12] == 0x02u && rx[13] == 0x80u && + rx[16] == 16u && memcmp(&rx[20], &tx[20], 16u) == 0 && + rx[36] == 0u, + "a v1.1 receiver gets the 20-byte header with the payload behind it"); + + memset(tx, 0, sizeof(tx)); + tx[8] = 40u; + tx[12] = 0x02u; tx[13] = 0x80u; + tx[16] = 4u; + for (i = 24u; i < 40u; i++) { + tx[i] = 0x5Au; + } + tx[40] = 0x11u; + check(wt_ffa_msg2_parse(tx, sizeof(tx), 0u, V12, ns, 0u, 0u, &m) == 0, + "a v1.2 sender's header with a UUID parses"); + memset(rx, 0xAA, sizeof(rx)); + wt_ffa_msg2_copy(rx, sizeof(rx), V11, tx, &m); + ok = (rx[8] == 40u) && (rx[40] == 0x11u); + for (i = 20u; i < 40u; i++) { + ok = ok && (rx[i] == 0u); + } + check(ok != 0, "a v1.1 receiver of it gets no UUID, the payload at 40"); +} + +/* 13.12: the one count rule the SPMD and the SPMC both apply. */ +static void console_count_rows(void) +{ + check(wt_ffa_console_count(24u, 0u) == 24u && + wt_ffa_console_count(128u, 1u) == 128u, + "console: the largest count each convention carries is taken"); + check(wt_ffa_console_count(0u, 0u) == 0u && + wt_ffa_console_count(25u, 0u) == 0u && + wt_ffa_console_count(129u, 1u) == 0u, + "console: a count of 0 or past the convention's registers is refused"); + check(wt_ffa_console_count(0xFFFFFF03u, 0u) == 3u && + wt_ffa_console_count(0xDEADBEEF00000180ull, 1u) == 128u, + "console: the SBZ bits above bits 7:0 are ignored at SMC32 and SMC64"); + check(wt_ffa_console_count(0x100u, 1u) == 0u, + "console: bits 7:0 alone count, so 0x100 counts nothing"); +} + +int main(void) +{ + size_t n = sizeof(g_fids) / sizeof(g_fids[0]); + size_t i; + size_t j; + int ok; + uint64_t x32[8] = { WT_FFA_MSG_SEND_DIRECT_REQ32, 0x1111111100008002ull, 0u, + 0x1125534411255344ull, 0xFFEEDDCC88776655ull, 0u, 0u, + 0xBBAA9988CCBBAA99ull }; + uint64_t x64[8] = { WT_FFA_MSG_SEND_DIRECT_REQ64, 0x1111111100008002ull, 0u, + 0x1125534411255344ull, 0u, 0u, 0u, 0u }; + + printf("WT-FFA-0001 / WT-FFA-0002 (function ids, status codes, version)\n"); + + ok = 1; + for (i = 0u; i < n; i++) { + ok = ok && wt_ffa_fid_in_range(g_fids[i]); + ok = ok && ((g_fids[i] & 0x80000000u) != 0u); + } + check(ok, "every function id sits in the reserved FF-A fast-call ranges"); + + ok = 1; + for (i = 0u; i < n; i++) { + for (j = i + 1u; j < n; j++) { + ok = ok && (g_fids[i] != g_fids[j]); + } + } + check(ok, "function ids are unique"); + + check((WT_FFA_SUCCESS64 & 0x40000000u) != 0u && + (WT_FFA_SUCCESS32 & 0x40000000u) == 0u && + (WT_FFA_SUCCESS64 & 0xFFFFu) == (WT_FFA_SUCCESS32 & 0xFFFFu), + "SMC64 variants differ from SMC32 only in bit 30"); + check(!wt_ffa_fid_in_range(WT_FFA_FID32_FIRST - 1u) && + !wt_ffa_fid_in_range(WT_FFA_FID32_LAST + 1u) && + !wt_ffa_fid_in_range(WT_FFA_FID64_FIRST - 1u) && + !wt_ffa_fid_in_range(WT_FFA_FID64_LAST + 1u) && + !wt_ffa_fid_in_range(0xC3000004u) && + wt_ffa_fid_in_range(WT_FFA_FID32_LAST) && + wt_ffa_fid_in_range(WT_FFA_FID64_FIRST), + "range check excludes neighbours and the OEM test calls"); + + ok = 1; + for (i = 0u; i < sizeof(g_codes) / sizeof(g_codes[0]); i++) { + ok = ok && (g_codes[i] == -(int32_t)(i + 1u)); + } + check(ok, "status codes are -1..-10 in specification order"); + + check(WT_FFA_VERSION_1_2 == WT_FFA_VERSION_MAKE(1u, 2u) && + WT_FFA_VERSION_MAJOR_OF(WT_FFA_VERSION_1_2) == 1u && + WT_FFA_VERSION_MINOR_OF(WT_FFA_VERSION_1_2) == 2u, + "version 1.2 encodes as major 1 minor 2"); + check(WT_FFA_FEATURES_RXTX_MAX_PAGES(1u) == 0x00010000u && + (WT_FFA_FEATURES_RXTX_MAX_PAGES(1u) & 0x3u) == 0u && + WT_FFA_FEATURES_RXTX_MAX_PAGES(0u) == 0u, + "FFA_FEATURES(FFA_RXTX_MAP) puts the page limit in w2 bits[31:16] " + "with a 4K minimum in bits[1:0]"); + check(wt_ffa_features_retrieve_check(WT_FFA_VERSION_MAKE(1u, 0u), 0u) == 0 && + wt_ffa_features_retrieve_check(WT_FFA_VERSION_MAKE(1u, 0u), + WT_FFA_FEATURES_RETRIEVE_NS_BIT) == 0, + "a v1.0 partition may query FFA_MEM_RETRIEVE_REQ with or without " + "requesting the NS bit (DEN0140 Table 1.19)"); + check(wt_ffa_features_retrieve_check(WT_FFA_VERSION_MAKE(1u, 1u), 0u) == + WT_FFA_NOT_SUPPORTED && + wt_ffa_features_retrieve_check(WT_FFA_VERSION_1_2, 0u) == + WT_FFA_NOT_SUPPORTED && + wt_ffa_features_retrieve_check(WT_FFA_VERSION_1_2, + WT_FFA_FEATURES_RETRIEVE_NS_BIT) == 0, + "a v1.1+ partition must request the NS bit (DEN0140 1.10.4.1.1), or " + "the query is NOT_SUPPORTED (13.3)"); + check(wt_ffa_fid_min_version(WT_FFA_VERSION) == WT_FFA_VERSION_MAKE(1u, 0u) && + wt_ffa_fid_min_version(WT_FFA_MSG_SEND_DIRECT_REQ64) == + WT_FFA_VERSION_MAKE(1u, 0u) && + wt_ffa_fid_min_version(WT_FFA_MEM_FRAG_TX) == + WT_FFA_VERSION_MAKE(1u, 0u) && + wt_ffa_fid_min_version(WT_FFA_NOTIFICATION_SET) == + WT_FFA_VERSION_MAKE(1u, 1u) && + wt_ffa_fid_min_version(WT_FFA_MSG_SEND2) == + WT_FFA_VERSION_MAKE(1u, 1u) && + wt_ffa_fid_min_version(WT_FFA_SPM_ID_GET) == + WT_FFA_VERSION_MAKE(1u, 1u) && + wt_ffa_fid_min_version(WT_FFA_MSG_SEND_DIRECT_REQ2) == + WT_FFA_VERSION_1_2 && + wt_ffa_fid_min_version(WT_FFA_MSG_SEND_DIRECT_RESP2) == + WT_FFA_VERSION_1_2 && + wt_ffa_fid_min_version(WT_FFA_PARTITION_INFO_GET_REGS) == + WT_FFA_VERSION_1_2 && + wt_ffa_fid_min_version(WT_FFA_CONSOLE_LOG64) == WT_FFA_VERSION_1_2, + "each ABI carries the Framework version it appeared in (revision " + "history: notifications, MSG_SEND2, SPM_ID_GET 1.1; DIRECT_REQ2, " + "PARTITION_INFO_GET_REGS, CONSOLE_LOG 1.2)"); + check(wt_ffa_fid_available(WT_FFA_MSG_SEND_DIRECT_REQ32, + WT_FFA_VERSION_MAKE(1u, 0u)) && + !wt_ffa_fid_available(WT_FFA_NOTIFICATION_SET, + WT_FFA_VERSION_MAKE(1u, 0u)) && + wt_ffa_fid_available(WT_FFA_NOTIFICATION_SET, + WT_FFA_VERSION_MAKE(1u, 1u)) && + !wt_ffa_fid_available(WT_FFA_MSG_SEND_DIRECT_REQ2, + WT_FFA_VERSION_MAKE(1u, 1u)) && + wt_ffa_fid_available(WT_FFA_MSG_SEND_DIRECT_REQ2, WT_FFA_VERSION_1_2), + "an ABI is available only from the version it appeared in (13.2.2: " + "the negotiated version is the only one supported for the caller)"); + check(!wt_ffa_ns_bit_used(WT_FFA_VERSION_MAKE(1u, 0u), 0) && + wt_ffa_ns_bit_used(WT_FFA_VERSION_MAKE(1u, 0u), 1) && + wt_ffa_ns_bit_used(WT_FFA_VERSION_MAKE(1u, 1u), 0) && + wt_ffa_ns_bit_used(WT_FFA_VERSION_1_2, 0), + "a retrieve response tells a v1.0 partition the NS bit only if it " + "asked, a v1.1+ one always (DEN0140 Table 1.19)"); + check(wt_ffa_version_reply(WT_FFA_VERSION_1_2, WT_FFA_VERSION_1_2) == + (int32_t)WT_FFA_VERSION_1_2, + "a 1.2 caller is told 1.2"); + check(wt_ffa_version_reply(WT_FFA_VERSION_MAKE(1u, 0u), WT_FFA_VERSION_1_2) == + (int32_t)WT_FFA_VERSION_1_2, + "a 1.0 caller is told the callee version 1.2"); + check(wt_ffa_version_reply(WT_FFA_VERSION_MAKE(2u, 0u), WT_FFA_VERSION_1_2) == + (int32_t)WT_FFA_VERSION_1_2 && + wt_ffa_version_reply(WT_FFA_VERSION_MAKE(1u, 4u), WT_FFA_VERSION_1_2) == + (int32_t)WT_FFA_VERSION_1_2, + "a caller at a later major or minor is told the callee's highest " + "version, 1.2 (13.2.2)"); + check(wt_ffa_version_reply(0u, WT_FFA_VERSION_1_2) == WT_FFA_NOT_SUPPORTED && + wt_ffa_version_reply(WT_FFA_VERSION_MAKE(0u, 9u), WT_FFA_VERSION_1_2) == + WT_FFA_NOT_SUPPORTED, + "a caller below the callee's major is refused"); + wt_ffa_regs_normalize(x32); + wt_ffa_regs_normalize(x64); + check(x32[1] == 0x00008002ull && x32[3] == 0x11255344ull && + x32[4] == 0x88776655ull && x32[7] == 0xCCBBAA99ull, + "a 32-bit function id is relayed with w1-w7 only"); + check(x64[1] == 0x1111111100008002ull && x64[3] == 0x1125534411255344ull, + "a 64-bit function id keeps its full registers"); + check(wt_ffa_version_reply(0x80010002u, WT_FFA_VERSION_1_2) == + WT_FFA_NOT_SUPPORTED, + "bit 31 set in the input version is NOT_SUPPORTED"); + check(wt_ffa_version_compatible(WT_FFA_VERSION_MAKE(1u, 0u), WT_FFA_VERSION_1_2) && + wt_ffa_version_compatible(WT_FFA_VERSION_1_2, WT_FFA_VERSION_1_2), + "same major and caller minor <= callee minor is compatible"); + check(!wt_ffa_version_compatible(WT_FFA_VERSION_MAKE(1u, 3u), WT_FFA_VERSION_1_2) && + !wt_ffa_version_compatible(WT_FFA_VERSION_MAKE(2u, 0u), WT_FFA_VERSION_1_2), + "greater minor or different major is incompatible"); + + check((WT_FFA_ID_SPMC & 0x8000u) != 0u && (WT_FFA_ID_SPMD & 0x8000u) != 0u && + (WT_FFA_ID_SP_FIRST & 0x8000u) != 0u && WT_FFA_ID_SPMC != WT_FFA_ID_SPMD && + WT_FFA_ID_SP_FIRST > WT_FFA_ID_SPMD && WT_FFA_ID_NS_PRIMARY == 0u, + "SPM-allocated ids carry bit 15, the primary NS endpoint is id 0"); + check(WT_FFA_FEATURES_IS_FID(WT_FFA_VERSION) && !WT_FFA_FEATURES_IS_FID(0x3u), + "FFA_FEATURES tells function ids from feature ids by bit 31"); + + direct_message_rows(); + run_target_rows(); + reply_ext_rows(); + msg_deliver_rows(); + msg2_rows(); + msg2_copy_rows(); + msg2_version_rows(); + console_count_rows(); + partition_info_rows(); + manifest_record_rows(); + partition_info_mailbox_rows(); + partition_info_version_rows(); + direct_permission_rows(); + version_state_rows(); + + printf("ffa_abi: %d checks, %d failures\n", checks, failures); + return (failures == 0) ? 0 : 1; +} diff --git a/tests/host/ffa_boot_info/Makefile b/tests/host/ffa_boot_info/Makefile new file mode 100644 index 00000000..ed6026e5 --- /dev/null +++ b/tests/host/ffa_boot_info/Makefile @@ -0,0 +1,62 @@ +# Makefile +# +# Copyright (C) 2026 wolfSSL Inc. +# +# This file is part of wolfTrust. +# +# wolfTrust 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. +# +# wolfTrust 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 + +ROOT := ../../.. + +CC ?= cc +BUILD_DIR ?= build + +CFLAGS := \ + -I$(ROOT)/include \ + -std=c99 -O0 -g -Wall -Wextra -Werror -pedantic +CFLAGS += $(EXTRA_CFLAGS) + +SRCS := $(ROOT)/src/arch/aarch64/ffa/ffa_boot_info.c main.c +TEST_BIN := $(BUILD_DIR)/test_ffa_boot_info + +.PHONY: all run compilers sanitize valgrind clean + +all: $(TEST_BIN) + +$(BUILD_DIR): + mkdir -p $@ + +$(TEST_BIN): $(SRCS) | $(BUILD_DIR) + $(CC) $(CFLAGS) $(EXTRA_LDFLAGS) -o $@ $(SRCS) + +run: $(TEST_BIN) + $(TEST_BIN) + +compilers: + $(MAKE) clean run CC=gcc BUILD_DIR=$(BUILD_DIR)/gcc + $(MAKE) clean run CC=clang BUILD_DIR=$(BUILD_DIR)/clang + +sanitize: + $(MAKE) clean run CC=clang \ + BUILD_DIR=$(BUILD_DIR)/sanitize \ + EXTRA_CFLAGS="-fsanitize=address,undefined -fno-omit-frame-pointer" \ + EXTRA_LDFLAGS="-fsanitize=address,undefined" + +valgrind: clean $(TEST_BIN) + valgrind --error-exitcode=1 --leak-check=full --show-leak-kinds=all \ + $(TEST_BIN) + +clean: + rm -rf $(BUILD_DIR) diff --git a/tests/host/ffa_boot_info/main.c b/tests/host/ffa_boot_info/main.c new file mode 100644 index 00000000..efce051f --- /dev/null +++ b/tests/host/ffa_boot_info/main.c @@ -0,0 +1,257 @@ +/* main.c + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfTrust. + * + * wolfTrust 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. + * + * wolfTrust 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, see . + */ + +/* WT-FFA-0008: the FF-A boot information blob (DEN0077A 1.2 section 5.4) + * round-trips between the SPMD producer and the SPMC consumer, and every + * malformed header or descriptor is refused before any descriptor is used. */ + +#include "wolftrust/arch/aarch64/ffa_abi.h" +#include "wolftrust/arch/aarch64/ffa_boot_info.h" + +#include +#include +#include + +#define BLOB_PA 0x0E040000ull +#define BLOB_LIMIT 4096u + +static int checks; +static int failures; +static uint8_t g_blob[BLOB_LIMIT]; + +static void check(int ok, const char* what) +{ + checks++; + if (ok) { + printf(" [check] PASS %s\n", what); + } + else { + failures++; + printf(" [check] FAIL %s\n", what); + } +} + +static const uint8_t g_record[20] = { + 0x57, 0x54, 0x48, 0x4F, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 +}; + +static const uint8_t g_uuid[16] = { + 0xA0, 0xA1, 0xA2, 0xA3, 0xB0, 0xB1, 0xB2, 0xB3, + 0xC0, 0xC1, 0xC2, 0xC3, 0xD0, 0xD1, 0xD2, 0xD3 +}; + +static uint32_t build_handoff(uint32_t version) +{ + wt_ffa_boot_info_item_t item; + uint32_t size = 0u; + int ret; + + memset(&item, 0, sizeof(item)); + item.name = WT_FFA_BOOT_INFO_NAME_WT_HANDOFF; + item.type = WT_FFA_BOOT_INFO_TYPE_WT_HANDOFF; + item.name_format = WT_FFA_BOOT_INFO_NAME_STRING; + item.contents_format = WT_FFA_BOOT_INFO_CONTENTS_ADDRESS; + item.source = g_record; + item.size = (uint32_t)sizeof(g_record); + memset(g_blob, 0xEE, sizeof(g_blob)); + ret = wt_ffa_boot_info_build(g_blob, BLOB_PA, BLOB_LIMIT, version, &item, 1u, + &size); + return (ret == WT_FFA_BOOT_INFO_OK) ? size : 0u; +} + +static void put32(uint32_t off, uint32_t v) +{ + g_blob[off] = (uint8_t)v; + g_blob[off + 1u] = (uint8_t)(v >> 8); + g_blob[off + 2u] = (uint8_t)(v >> 16); + g_blob[off + 3u] = (uint8_t)(v >> 24); +} + +static int parse_now(wt_ffa_boot_info_t* info) +{ + return wt_ffa_boot_info_parse(g_blob, BLOB_PA, BLOB_LIMIT, info); +} + +int main(void) +{ + wt_ffa_boot_info_t info; + wt_ffa_boot_info_desc_t desc; + wt_ffa_boot_info_item_t items[3]; + uint32_t size; + int ret; + + printf("WT-FFA-0008 (FF-A boot information protocol)\n"); + + size = build_handoff(WT_FFA_VERSION_1_2); + check(size == 88u, "header + one descriptor + 20-byte record padded to 8 = 88 bytes"); + check(g_blob[0] == 0xFA && g_blob[1] == 0x0F && g_blob[2] == 0 && g_blob[3] == 0, + "signature 0x0FFA is little-endian at offset 0"); + check(parse_now(&info) == WT_FFA_BOOT_INFO_OK && info.version == WT_FFA_VERSION_1_2 && + info.blob_size == 88u && info.desc_count == 1u && info.desc_offset == 32u, + "the consumer parses the header back"); + ret = wt_ffa_boot_info_find(g_blob, &info, WT_FFA_BOOT_INFO_TYPE_WT_HANDOFF, &desc); + check(ret == WT_FFA_BOOT_INFO_OK && strcmp(desc.name, "wt.handoff") == 0 && + desc.type == 0x81u && desc.flags == 0u && desc.size == 20u && + desc.contents == BLOB_PA + 64u, + "the handoff descriptor is IMPDEF type 0x81, string name, address form, at +64"); + check(memcmp(g_blob + 64u, g_record, sizeof(g_record)) == 0 && + g_blob[84] == 0 && g_blob[87] == 0, + "address-form contents are copied into the blob and padded with zeros"); + check(wt_ffa_boot_info_find(g_blob, &info, WT_FFA_BOOT_INFO_TYPE_FDT, &desc) == + WT_FFA_BOOT_INFO_ERROR_NOT_FOUND && + wt_ffa_boot_info_desc(g_blob, &info, 1u, &desc) == + WT_FFA_BOOT_INFO_ERROR_NOT_FOUND, + "a missing type or an index past the array is NOT_FOUND"); + + memset(items, 0, sizeof(items)); + items[0].name = "fdt"; + items[0].type = WT_FFA_BOOT_INFO_TYPE_FDT; + items[0].contents_format = WT_FFA_BOOT_INFO_CONTENTS_VALUE; + items[0].value = 0x4000000000ull; + items[0].size = 8u; + items[1].name = (const char*)g_uuid; + items[1].name_format = WT_FFA_BOOT_INFO_NAME_UUID; + items[1].type = WT_FFA_BOOT_INFO_TYPE_IMPDEF | 0x7Fu; + items[1].contents_format = WT_FFA_BOOT_INFO_CONTENTS_ADDRESS; + items[1].source = g_record; + items[1].size = 3u; + items[2].name = "second"; + items[2].type = WT_FFA_BOOT_INFO_TYPE_HOB; + items[2].contents_format = WT_FFA_BOOT_INFO_CONTENTS_ADDRESS; + items[2].source = g_record + 4; + items[2].size = 9u; + ret = wt_ffa_boot_info_build(g_blob, BLOB_PA, BLOB_LIMIT, WT_FFA_VERSION_1_2, items, + 3u, &size); + check(ret == WT_FFA_BOOT_INFO_OK && size == 32u + 96u + 8u + 16u, + "three descriptors: value form takes no space, address forms are 8-aligned"); + check(parse_now(&info) == WT_FFA_BOOT_INFO_OK && info.desc_count == 3u, + "the three-descriptor blob parses"); + check(wt_ffa_boot_info_desc(g_blob, &info, 0u, &desc) == WT_FFA_BOOT_INFO_OK && + desc.flags == 0x4u && desc.contents == 0x4000000000ull && desc.size == 8u, + "value form keeps the value in Contents with flags bits 3:2 = 1"); + items[0].size = 0u; + check(wt_ffa_boot_info_build(g_blob, BLOB_PA, BLOB_LIMIT, WT_FFA_VERSION_1_2, items, 1u, + &size) == WT_FFA_BOOT_INFO_ERROR_DESC, + "a value-form descriptor with size 0 is refused"); + items[0].size = 9u; + check(wt_ffa_boot_info_build(g_blob, BLOB_PA, BLOB_LIMIT, WT_FFA_VERSION_1_2, items, 1u, + &size) == WT_FFA_BOOT_INFO_ERROR_DESC, + "a value-form descriptor with size 9 is refused"); + items[0].size = 8u; + items[0].type = 0x02u; + check(wt_ffa_boot_info_build(g_blob, BLOB_PA, BLOB_LIMIT, WT_FFA_VERSION_1_2, items, 1u, + &size) == WT_FFA_BOOT_INFO_ERROR_DESC, + "a reserved standard type (not FDT or HOB) is refused"); + items[0].type = WT_FFA_BOOT_INFO_TYPE_FDT; + check(wt_ffa_boot_info_desc(g_blob, &info, 1u, &desc) == WT_FFA_BOOT_INFO_OK && + desc.flags == 0x1u && memcmp(desc.name, g_uuid, 16) == 0 && + desc.contents == BLOB_PA + 128u && desc.size == 3u, + "UUID names are 16 raw bytes with flags bits 1:0 = 1"); + check(wt_ffa_boot_info_desc(g_blob, &info, 2u, &desc) == WT_FFA_BOOT_INFO_OK && + desc.contents == BLOB_PA + 136u && + memcmp(g_blob + 136u, g_record + 4, 9) == 0, + "the second address-form item follows the first at the next 8-byte boundary"); + + ret = wt_ffa_boot_info_build(g_blob, BLOB_PA, BLOB_LIMIT, WT_FFA_VERSION_1_2, NULL, 0u, + &size); + check(ret == WT_FFA_BOOT_INFO_OK && size == 32u && parse_now(&info) == WT_FFA_BOOT_INFO_OK && + info.desc_count == 0u, + "an empty blob is the 32-byte header and parses"); + + items[0].name = "0123456789abcdef"; + items[0].name_format = WT_FFA_BOOT_INFO_NAME_STRING; + check(wt_ffa_boot_info_build(g_blob, BLOB_PA, BLOB_LIMIT, WT_FFA_VERSION_1_2, items, 1u, + &size) == WT_FFA_BOOT_INFO_ERROR_DESC, + "a 16-character string name has no room for its NUL"); + items[0].name = "fdt"; + check(wt_ffa_boot_info_build(g_blob, BLOB_PA, 100u, WT_FFA_VERSION_1_2, items, 3u, + &size) == WT_FFA_BOOT_INFO_ERROR_SPACE, + "a blob too small for the array and contents is refused"); + check(wt_ffa_boot_info_build(g_blob, BLOB_PA + 4u, BLOB_LIMIT, WT_FFA_VERSION_1_2, items, + 1u, &size) == WT_FFA_BOOT_INFO_ERROR_ARGUMENT && + wt_ffa_boot_info_build(NULL, BLOB_PA, BLOB_LIMIT, WT_FFA_VERSION_1_2, items, 1u, + &size) == WT_FFA_BOOT_INFO_ERROR_ARGUMENT, + "an unaligned blob address or a NULL blob is refused"); + + size = build_handoff(WT_FFA_VERSION_1_2); + put32(0u, 0x0FFBu); + check(parse_now(&info) == WT_FFA_BOOT_INFO_ERROR_SIGNATURE, "wrong signature"); + size = build_handoff(WT_FFA_VERSION_MAKE(2u, 0u)); + check(parse_now(&info) == WT_FFA_BOOT_INFO_ERROR_VERSION, "major version 2 is refused"); + size = build_handoff(WT_FFA_VERSION_MAKE(1u, 0u)); + check(parse_now(&info) == WT_FFA_BOOT_INFO_OK, "version 1.0 blobs are accepted"); + size = build_handoff(WT_FFA_VERSION_1_2); + put32(12u, 16u); + check(parse_now(&info) == WT_FFA_BOOT_INFO_ERROR_LAYOUT, "descriptor size other than 32"); + size = build_handoff(WT_FFA_VERSION_1_2); + put32(20u, 36u); + check(parse_now(&info) == WT_FFA_BOOT_INFO_ERROR_LAYOUT, "descriptor offset not 8-aligned"); + size = build_handoff(WT_FFA_VERSION_1_2); + put32(16u, 3u); + check(parse_now(&info) == WT_FFA_BOOT_INFO_ERROR_LAYOUT, + "descriptor array running past the blob size"); + size = build_handoff(WT_FFA_VERSION_1_2); + put32(8u, BLOB_LIMIT + 8u); + check(parse_now(&info) == WT_FFA_BOOT_INFO_ERROR_LAYOUT, "blob size past the memory limit"); + size = build_handoff(WT_FFA_VERSION_1_2); + put32(28u, 1u); + check(parse_now(&info) == WT_FFA_BOOT_INFO_ERROR_RESERVED, "header reserved bytes not zero"); + size = build_handoff(WT_FFA_VERSION_1_2); + g_blob[32u + 17u] = 1u; + check(parse_now(&info) == WT_FFA_BOOT_INFO_ERROR_RESERVED, + "descriptor reserved byte not zero"); + size = build_handoff(WT_FFA_VERSION_1_2); + g_blob[32u + 18u] = 0x10u; + check(parse_now(&info) == WT_FFA_BOOT_INFO_ERROR_DESC, "descriptor flags bit 4 set"); + size = build_handoff(WT_FFA_VERSION_1_2); + g_blob[32u + 18u] = 0x08u; + check(parse_now(&info) == WT_FFA_BOOT_INFO_ERROR_DESC, "contents format b'10 is reserved"); + size = build_handoff(WT_FFA_VERSION_1_2); + g_blob[32u + 18u] = 0x02u; + check(parse_now(&info) == WT_FFA_BOOT_INFO_ERROR_DESC, "name format b'10 is reserved"); + size = build_handoff(WT_FFA_VERSION_1_2); + memset(g_blob + 32u, 'x', 16u); + check(parse_now(&info) == WT_FFA_BOOT_INFO_ERROR_DESC, + "a string name without a NUL inside 16 bytes"); + size = build_handoff(WT_FFA_VERSION_1_2); + g_blob[32u + 18u] = 0x04u; + check(parse_now(&info) == WT_FFA_BOOT_INFO_ERROR_DESC, + "the consumer refuses a value-form descriptor whose size is not 1 to 8"); + size = build_handoff(WT_FFA_VERSION_1_2); + g_blob[32u + 16u] = 0x02u; + check(parse_now(&info) == WT_FFA_BOOT_INFO_ERROR_DESC, + "the consumer refuses a reserved standard type"); + size = build_handoff(WT_FFA_VERSION_1_2); + put32(32u + 24u, (uint32_t)(BLOB_PA + 80u)); + check(parse_now(&info) == WT_FFA_BOOT_INFO_ERROR_LAYOUT, + "address-form contents running past the blob"); + size = build_handoff(WT_FFA_VERSION_1_2); + put32(32u + 24u, (uint32_t)(BLOB_PA - 8u)); + check(parse_now(&info) == WT_FFA_BOOT_INFO_ERROR_LAYOUT, + "address-form contents before the blob"); + check(wt_ffa_boot_info_parse(g_blob, BLOB_PA, 16u, &info) == WT_FFA_BOOT_INFO_ERROR_LAYOUT && + wt_ffa_boot_info_parse(NULL, BLOB_PA, BLOB_LIMIT, &info) == + WT_FFA_BOOT_INFO_ERROR_ARGUMENT, + "a memory limit under the header or a NULL blob is refused"); + (void)size; + + printf("ffa_boot_info: %d checks, %d failures\n", checks, failures); + return (failures == 0) ? 0 : 1; +} diff --git a/tests/host/ffa_mem/Makefile b/tests/host/ffa_mem/Makefile new file mode 100644 index 00000000..56da9457 --- /dev/null +++ b/tests/host/ffa_mem/Makefile @@ -0,0 +1,65 @@ +# Makefile +# +# Copyright (C) 2026 wolfSSL Inc. +# +# This file is part of wolfTrust. +# +# wolfTrust 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. +# +# wolfTrust 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 + +ROOT := ../../.. + +CC ?= cc +BUILD_DIR ?= build + +CFLAGS := \ + -I$(ROOT)/include \ + -std=c99 -O0 -g -Wall -Wextra -Werror -pedantic +CFLAGS += $(EXTRA_CFLAGS) + +SRCS := $(ROOT)/src/arch/aarch64/ffa/ffa_mem.c \ + $(ROOT)/src/arch/aarch64/spm/spm_mem.c \ + $(ROOT)/src/arch/aarch64/spm/domain.c \ + $(ROOT)/src/arch/aarch64/spm/tables.c main.c +TEST_BIN := $(BUILD_DIR)/test_ffa_mem + +.PHONY: all run compilers sanitize valgrind clean + +all: $(TEST_BIN) + +$(BUILD_DIR): + mkdir -p $@ + +$(TEST_BIN): $(SRCS) | $(BUILD_DIR) + $(CC) $(CFLAGS) $(EXTRA_LDFLAGS) -o $@ $(SRCS) + +run: $(TEST_BIN) + $(TEST_BIN) + +compilers: + $(MAKE) clean run CC=gcc BUILD_DIR=$(BUILD_DIR)/gcc + $(MAKE) clean run CC=clang BUILD_DIR=$(BUILD_DIR)/clang + +sanitize: + $(MAKE) clean run CC=clang \ + BUILD_DIR=$(BUILD_DIR)/sanitize \ + EXTRA_CFLAGS="-fsanitize=address,undefined -fno-omit-frame-pointer" \ + EXTRA_LDFLAGS="-fsanitize=address,undefined" + +valgrind: clean $(TEST_BIN) + valgrind --error-exitcode=1 --leak-check=full --show-leak-kinds=all \ + $(TEST_BIN) + +clean: + rm -rf $(BUILD_DIR) diff --git a/tests/host/ffa_mem/main.c b/tests/host/ffa_mem/main.c new file mode 100644 index 00000000..97d60298 --- /dev/null +++ b/tests/host/ffa_mem/main.c @@ -0,0 +1,4154 @@ +/* main.c + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfTrust. + * + * wolfTrust 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. + * + * wolfTrust 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, see . + */ + +/* WT-FFA-0009: the DEN0140 memory transaction descriptor (lend/donate/share), + * its composite and constituent sub-descriptors, the relayer validation, the + * RX/TX buffer geometry, the memory handle lifetime state machine, and the + * SPMC relayer driving real partition tables. */ + +#define _DEFAULT_SOURCE +#define _DARWIN_C_SOURCE + +#include "wolftrust/arch.h" +#include "wolftrust/arch/aarch64/domain.h" +#include "wolftrust/arch/aarch64/ffa_abi.h" +#include "wolftrust/arch/aarch64/ffa_mem.h" +#include "wolftrust/arch/aarch64/ffa_notif.h" +#include "wolftrust/arch/aarch64/spm_mem.h" +#include "wolftrust/arch/aarch64/spm_svc.h" +#include "wolftrust/arch/aarch64/tables.h" + +#include +#include +#include +#include +#include + +static int checks; +static int failures; + +static void check(int ok, const char* what) +{ + checks++; + if (ok) { + printf(" [check] PASS %s\n", what); + } + else { + failures++; + printf(" [check] FAIL %s\n", what); + } +} + +static void put32(uint8_t* p, uint32_t v) +{ + p[0] = (uint8_t)(v & 0xFFu); + p[1] = (uint8_t)((v >> 8) & 0xFFu); + p[2] = (uint8_t)((v >> 16) & 0xFFu); + p[3] = (uint8_t)((v >> 24) & 0xFFu); +} + +static void put64(uint8_t* p, uint64_t v) +{ + put32(p, (uint32_t)(v & 0xFFFFFFFFu)); + put32(&p[4], (uint32_t)((v >> 32) & 0xFFFFFFFFu)); +} + +#define V10 WT_FFA_VERSION_MAKE(1u, 0u) + +/* Encode a canonical two-constituent transaction for op with the given flags: + * sender 0, borrower 0x8002, normal NS memory, read-write no-execute. The + * constituents are page-aligned, adjacent, and total five pages. */ +static size_t make_txn(uint8_t* buf, size_t cap, wt_ffa_mem_op_t op, + uint32_t flags) +{ + static const wt_ffa_mem_constituent_t cons[2] = { + { 0x40000000ull, 2u }, + { 0x40002000ull, 3u } + }; + wt_ffa_mem_build_t in; + size_t out = 0u; + + memset(&in, 0, sizeof(in)); + in.constituents = cons; + in.constituent_count = 2u; + in.op = op; + in.sender = 0u; + in.receiver = 0x8002u; + in.attributes = 0x2Fu; + in.permissions = 0x06u; + in.flags = flags; + in.tag = 0x1122334455667788ull; + in.access_desc_size = (uint8_t)WT_FFA_MEM_ACCESS_SIZE; + if (wt_ffa_mem_txn_build(buf, cap, &in, &out) != 0) { + return 0u; + } + return out; +} + +/* WT-FFA-0009 (memory-management function ids). */ +static void fid_rows(void) +{ + static const uint32_t mem32[] = { + WT_FFA_MEM_DONATE32, WT_FFA_MEM_LEND32, WT_FFA_MEM_SHARE32, + WT_FFA_MEM_RETRIEVE_REQ32, WT_FFA_MEM_RETRIEVE_RESP, + WT_FFA_MEM_RELINQUISH, WT_FFA_MEM_RECLAIM, + WT_FFA_MEM_FRAG_RX, WT_FFA_MEM_FRAG_TX + }; + size_t n = sizeof(mem32) / sizeof(mem32[0]); + size_t i; + size_t j; + int ok; + + ok = 1; + for (i = 0u; i < n; i++) { + ok = ok && wt_ffa_fid_in_range(mem32[i]); + ok = ok && ((mem32[i] & 0x80000000u) != 0u); + } + check(ok, "every memory-management function id sits in the FF-A fast-call ranges"); + + ok = 1; + for (i = 0u; i < n; i++) { + for (j = i + 1u; j < n; j++) { + ok = ok && (mem32[i] != mem32[j]); + } + } + check(ok, "memory-management function ids are unique"); + + check(WT_FFA_MEM_DONATE64 == (WT_FFA_MEM_DONATE32 | 0x40000000u) && + WT_FFA_MEM_LEND64 == (WT_FFA_MEM_LEND32 | 0x40000000u) && + WT_FFA_MEM_SHARE64 == (WT_FFA_MEM_SHARE32 | 0x40000000u) && + WT_FFA_MEM_RETRIEVE_REQ64 == (WT_FFA_MEM_RETRIEVE_REQ32 | 0x40000000u), + "donate, lend, share, and retrieve-request have paired SMC32 and SMC64 ids"); +} + +/* WT-FFA-0009 (descriptor round-trip). */ +static void txn_rows(void) +{ + uint8_t buf[256]; + wt_ffa_mem_txn_t txn; + wt_ffa_mem_constituent_t c; + uint16_t rid; + uint8_t perms; + size_t len; + + len = make_txn(buf, sizeof(buf), WT_FFA_MEM_OP_SHARE, 0u); + check(len == 112u, + "a two-constituent share encodes header, one access descriptor, composite, and constituents"); + check(wt_ffa_mem_txn_validate(buf, len, WT_FFA_MEM_OP_SHARE, 0u, &txn) == 0, + "the relayer accepts a well-formed share transaction"); + check(txn.sender == 0u && txn.receiver_count == 1u && + txn.composite_offset == 64u && txn.constituent_count == 2u && + txn.total_page_count == 5u && txn.tag == 0x1122334455667788ull, + "the parsed header carries the sender, composite location, and page total"); + check(wt_ffa_mem_receiver(buf, len, &txn, 0u, &rid, &perms) == 0 && + rid == 0x8002u && perms == 0x06u, + "the endpoint access descriptor names the borrower and its permissions"); + check(wt_ffa_mem_constituent(buf, len, &txn, 0u, &c) == 0 && + c.address == 0x40000000ull && c.page_count == 2u && + wt_ffa_mem_constituent(buf, len, &txn, 1u, &c) == 0 && + c.address == 0x40002000ull && c.page_count == 3u, + "each constituent decodes its page-aligned base and page count"); + check(wt_ffa_mem_receiver(buf, len, &txn, 1u, &rid, &perms) == + WT_FFA_INVALID_PARAMETERS && + wt_ffa_mem_constituent(buf, len, &txn, 2u, &c) == WT_FFA_INVALID_PARAMETERS, + "out-of-range receiver and constituent indices are refused"); + + check(wt_ffa_mem_txn_validate(buf, len, WT_FFA_MEM_OP_DONATE, 0u, &txn) == 0, + "a single-receiver donate is well formed"); + len = make_txn(buf, sizeof(buf), WT_FFA_MEM_OP_LEND, WT_FFA_MEM_FLAG_ZERO); + check(wt_ffa_mem_txn_validate(buf, len, WT_FFA_MEM_OP_LEND, 0u, &txn) == 0, + "a lend may set the zero-memory flag"); +} + +/* WT-FFA-0009 (relayer rejections). Each row rebuilds a valid descriptor and + * corrupts one field. */ +static void reject_rows(void) +{ + uint8_t buf[256]; + wt_ffa_mem_txn_t txn; + uint16_t rid; + uint8_t perms; + size_t len; + + len = make_txn(buf, sizeof(buf), WT_FFA_MEM_OP_SHARE, 0u); + check(wt_ffa_mem_txn_validate(buf, WT_FFA_MEM_TXN_HDR_SIZE - 1u, + WT_FFA_MEM_OP_SHARE, 0u, &txn) == + WT_FFA_INVALID_PARAMETERS, + "a buffer shorter than the header is INVALID_PARAMETERS"); + + len = make_txn(buf, sizeof(buf), WT_FFA_MEM_OP_SHARE, 0u); + check(wt_ffa_mem_txn_validate(buf, len, WT_FFA_MEM_OP_SHARE, 0x9999u, &txn) == + WT_FFA_DENIED, + "a sender that is not the caller is DENIED"); + + len = make_txn(buf, sizeof(buf), WT_FFA_MEM_OP_SHARE, 0u); + buf[24] = 24u; + check(wt_ffa_mem_txn_validate(buf, len, WT_FFA_MEM_OP_SHARE, 0u, &txn) == + WT_FFA_NOT_SUPPORTED, + "an access descriptor size this SPMC cannot parse is NOT_SUPPORTED"); + + len = make_txn(buf, sizeof(buf), WT_FFA_MEM_OP_SHARE, 0u); + buf[28] = 2u; + check(wt_ffa_mem_txn_validate(buf, len, WT_FFA_MEM_OP_DONATE, 0u, &txn) == + WT_FFA_INVALID_PARAMETERS, + "a donate to more than one receiver is INVALID_PARAMETERS"); + + len = make_txn(buf, sizeof(buf), WT_FFA_MEM_OP_SHARE, 0u); + buf[36] = 1u; + buf[47] = 0xFFu; + check(wt_ffa_mem_txn_validate(buf, len, WT_FFA_MEM_OP_SHARE, 0u, &txn) == 0, + "the SBZ header bytes [36, 48) are ignored (Table 1.20)"); + + len = make_txn(buf, sizeof(buf), WT_FFA_MEM_OP_SHARE, 0u); + buf[2] = (uint8_t)(buf[2] | 0x80u); + buf[3] = 0xFFu; + check(wt_ffa_mem_txn_validate(buf, len, WT_FFA_MEM_OP_SHARE, 0u, &txn) == 0 && + txn.attributes == 0x2Fu, + "the SBZ memory-attribute bits[15:7] are ignored and dropped (Table 1.18)"); + + len = make_txn(buf, sizeof(buf), WT_FFA_MEM_OP_SHARE, 0u); + buf[2] = (uint8_t)((buf[2] & 0xCFu) | 0x30u); + check(wt_ffa_mem_txn_validate(buf, len, WT_FFA_MEM_OP_SHARE, 0u, &txn) == + WT_FFA_INVALID_PARAMETERS, + "a reserved memory-type encoding is INVALID_PARAMETERS"); + + len = make_txn(buf, sizeof(buf), WT_FFA_MEM_OP_SHARE, 0u); + buf[50] = 0x03u; + check(wt_ffa_mem_txn_validate(buf, len, WT_FFA_MEM_OP_SHARE, 0u, &txn) == + WT_FFA_INVALID_PARAMETERS, + "a reserved data-access permission is INVALID_PARAMETERS"); + + len = make_txn(buf, sizeof(buf), WT_FFA_MEM_OP_SHARE, 0u); + buf[50] = 0xF6u; + buf[56] = 1u; + buf[63] = 0xFFu; + check(wt_ffa_mem_txn_validate(buf, len, WT_FFA_MEM_OP_SHARE, 0u, &txn) == 0 && + wt_ffa_mem_receiver(buf, len, &txn, 0u, &rid, &perms) == 0 && + perms == 0x06u, + "the SBZ permission bits[7:4] and access descriptor tail are ignored and dropped (Tables 1.15, 1.16)"); + + len = make_txn(buf, sizeof(buf), WT_FFA_MEM_OP_SHARE, 0u); + buf[72] = 1u; + buf[79] = 0xFFu; + buf[92] = 1u; + buf[111] = 0xFFu; + check(wt_ffa_mem_txn_validate(buf, len, WT_FFA_MEM_OP_SHARE, 0u, &txn) == 0, + "the SBZ composite bytes [8, 16) and constituent bytes [12, 16) are ignored (Tables 1.13, 1.14)"); + + len = make_txn(buf, sizeof(buf), WT_FFA_MEM_OP_SHARE, 0u); + put32(&buf[52], 0u); + check(wt_ffa_mem_txn_validate(buf, len, WT_FFA_MEM_OP_SHARE, 0u, &txn) == + WT_FFA_INVALID_PARAMETERS, + "a receiver with no composite offset is INVALID_PARAMETERS"); + + len = make_txn(buf, sizeof(buf), WT_FFA_MEM_OP_SHARE, 0u); + put32(&buf[52], (uint32_t)len); + check(wt_ffa_mem_txn_validate(buf, len, WT_FFA_MEM_OP_SHARE, 0u, &txn) == + WT_FFA_INVALID_PARAMETERS, + "a composite offset past the buffer is INVALID_PARAMETERS"); + + len = make_txn(buf, sizeof(buf), WT_FFA_MEM_OP_SHARE, 0u); + buf[80] = 1u; + check(wt_ffa_mem_txn_validate(buf, len, WT_FFA_MEM_OP_SHARE, 0u, &txn) == + WT_FFA_INVALID_PARAMETERS, + "a misaligned constituent base is INVALID_PARAMETERS"); + + len = make_txn(buf, sizeof(buf), WT_FFA_MEM_OP_SHARE, 0u); + put32(&buf[88], 0u); + check(wt_ffa_mem_txn_validate(buf, len, WT_FFA_MEM_OP_SHARE, 0u, &txn) == + WT_FFA_INVALID_PARAMETERS, + "a zero-length constituent is INVALID_PARAMETERS"); + + len = make_txn(buf, sizeof(buf), WT_FFA_MEM_OP_SHARE, 0u); + put32(&buf[64], 6u); + check(wt_ffa_mem_txn_validate(buf, len, WT_FFA_MEM_OP_SHARE, 0u, &txn) == + WT_FFA_INVALID_PARAMETERS, + "a total page count that does not match the constituents is INVALID_PARAMETERS"); + + len = make_txn(buf, sizeof(buf), WT_FFA_MEM_OP_SHARE, 0u); + put64(&buf[96], 0x40001000ull); + check(wt_ffa_mem_txn_validate(buf, len, WT_FFA_MEM_OP_SHARE, 0u, &txn) == + WT_FFA_INVALID_PARAMETERS, + "overlapping constituents are INVALID_PARAMETERS"); + + len = make_txn(buf, sizeof(buf), WT_FFA_MEM_OP_SHARE, 0u); + put32(&buf[4], WT_FFA_MEM_FLAG_ZERO); + check(wt_ffa_mem_txn_validate(buf, len, WT_FFA_MEM_OP_SHARE, 0u, &txn) == + WT_FFA_INVALID_PARAMETERS, + "a share that requests zeroing is INVALID_PARAMETERS"); + + len = make_txn(buf, sizeof(buf), WT_FFA_MEM_OP_LEND, 0u); + put32(&buf[4], 0xFFFFFFFCu | WT_FFA_MEM_FLAG_ZERO); + check(wt_ffa_mem_txn_validate(buf, len, WT_FFA_MEM_OP_LEND, 0u, &txn) == 0 && + txn.flags == WT_FFA_MEM_FLAG_ZERO, + "the SBZ transaction flag bits[31:2] are ignored and never kept (Table 1.21)"); +} + +/* Move everything after the header of a descriptor in buf by bytes, fixing up + * the access array offset and, for a send, its composite offset. */ +static size_t shift_access(uint8_t* buf, size_t len, uint32_t by, + int composite) +{ + size_t i; + + for (i = len; i > WT_FFA_MEM_TXN_HDR_SIZE; i--) { + buf[i - 1u + by] = buf[i - 1u]; + } + memset(&buf[WT_FFA_MEM_TXN_HDR_SIZE], 0, by); + put32(&buf[WT_FFA_MEM_TXN_OFF_ACC_OFFSET], WT_FFA_MEM_TXN_HDR_SIZE + by); + if (composite != 0) { + put32(&buf[WT_FFA_MEM_TXN_HDR_SIZE + by + WT_FFA_MEM_ACC_OFF_COMP_OFF], + WT_FFA_MEM_TXN_HDR_SIZE + WT_FFA_MEM_ACCESS_SIZE + by); + } + return len + by; +} + +/* WT-FFA-0009 (the access descriptor array sits at a 16-byte aligned offset, + * Table 1.20). */ +static void access_offset_rows(void) +{ + uint8_t buf[256]; + wt_ffa_mem_retrieve_req_t rq; + wt_ffa_mem_txn_t txn; + size_t len = 0u; + + len = shift_access(buf, make_txn(buf, sizeof(buf), WT_FFA_MEM_OP_SHARE, 0u), + 16u, 1); + check(wt_ffa_mem_txn_validate(buf, len, WT_FFA_MEM_OP_SHARE, 0u, &txn) == 0 && + txn.access_offset == 64u && txn.composite_offset == 80u, + "a share whose access array sits at a later 16-byte aligned offset is accepted"); + len = shift_access(buf, make_txn(buf, sizeof(buf), WT_FFA_MEM_OP_SHARE, 0u), + 1u, 1); + check(wt_ffa_mem_txn_validate(buf, len, WT_FFA_MEM_OP_SHARE, 0u, &txn) == + WT_FFA_INVALID_PARAMETERS, + "a share whose access array offset is 49 is INVALID_PARAMETERS"); + len = shift_access(buf, make_txn(buf, sizeof(buf), WT_FFA_MEM_OP_LEND, 0u), + 8u, 1); + check(wt_ffa_mem_txn_validate(buf, len, WT_FFA_MEM_OP_LEND, 0u, &txn) == + WT_FFA_INVALID_PARAMETERS, + "a lend whose access array is only 8-byte aligned is INVALID_PARAMETERS"); + + (void)wt_ffa_mem_retrieve_req_build(buf, sizeof(buf), 0x77ull, 0u, 0x8002u, + 0x02u, &len); + len = shift_access(buf, len, 16u, 0); + check(wt_ffa_mem_retrieve_req_parse_ex(buf, len, &rq) == 0 && + rq.receivers[0] == 0x8002u, + "a retrieve request whose access array sits at offset 64 is accepted"); + (void)wt_ffa_mem_retrieve_req_build(buf, sizeof(buf), 0x77ull, 0u, 0x8002u, + 0x02u, &len); + len = shift_access(buf, len, 1u, 0); + check(wt_ffa_mem_retrieve_req_parse_ex(buf, len, &rq) == + WT_FFA_INVALID_PARAMETERS, + "a retrieve request whose access array offset is 49 is INVALID_PARAMETERS"); +} + +/* WT-FFA-0009 (RX/TX buffer geometry, 7.2.1). */ +static void rxtx_rows(void) +{ + check(wt_ffa_rxtx_validate(0x1000ull, 0x2000ull, 1u) == 0, + "a page-aligned, non-overlapping RX/TX pair is valid"); + check(wt_ffa_rxtx_validate(0x1001ull, 0x2000ull, 1u) == + WT_FFA_INVALID_PARAMETERS, + "a misaligned TX base is INVALID_PARAMETERS"); + check(wt_ffa_rxtx_validate(0x1000ull, 0x1000ull, 1u) == + WT_FFA_INVALID_PARAMETERS, + "identical RX and TX bases are INVALID_PARAMETERS"); + check(wt_ffa_rxtx_validate(0x1000ull, 0x2000ull, 2u) == + WT_FFA_INVALID_PARAMETERS, + "overlapping RX and TX ranges are INVALID_PARAMETERS"); + check(wt_ffa_rxtx_validate(0x1000ull, 0x2000ull, 0u) == + WT_FFA_INVALID_PARAMETERS, + "a zero page count is INVALID_PARAMETERS"); + check(WT_FFA_RXTX_MAX_PAGES == 63u && + WT_FFA_RXTX_PAGE_COUNT(WT_FFA_RXTX_MAX_PAGES) == 63u && + WT_FFA_RXTX_PAGE_COUNT(64u) == 0u && + wt_ffa_rxtx_validate(0x1000ull, 0x40000ull, 63u) == 0 && + wt_ffa_rxtx_validate(0x1000ull, 0x41000ull, 64u) == + WT_FFA_INVALID_PARAMETERS, + "the largest pair is the 63 pages w3[5:0] can name; 64 is not " + "encodable and not valid"); + check(wt_ffa_rxtx_validate(0x1000ull, 0x2000ull, WT_FFA_RXTX_MAX_PAGES + 1u) == + WT_FFA_INVALID_PARAMETERS, + "more than the maximum page count is INVALID_PARAMETERS"); +} + +/* WT-FFA-0009 (RX/TX registration and RX buffer ownership, 7.2.2). */ +static void mailbox_rows(void) +{ + wt_ffa_mailbox_t mb; + + memset(&mb, 0, sizeof(mb)); + check(wt_ffa_mailbox_unmap(&mb) == WT_FFA_INVALID_PARAMETERS, + "FFA_RXTX_UNMAP with nothing mapped is INVALID_PARAMETERS"); + check(wt_ffa_mailbox_rx_release(&mb) == WT_FFA_DENIED, + "FFA_RX_RELEASE by an endpoint with no registered pair is DENIED: it " + "owns no RX buffer (Table 13.22, ACS ffa_rx_release)"); + check(wt_ffa_mailbox_rx_acquire(&mb) == WT_FFA_DENIED, + "an unmapped RX buffer cannot be acquired"); + check(wt_ffa_mailbox_map(&mb, 0x1000ull, 0x3000ull, 0xFFFFFFC0u | 1u) == + 0 && mb.pages == 1u && wt_ffa_mailbox_unmap(&mb) == 0, + "reserved SBZ bits above the page count are ignored (Table 13.25)"); + check(wt_ffa_mailbox_map(&mb, 0x1001ull, 0x3000ull, 1u) == + WT_FFA_INVALID_PARAMETERS && mb.mapped == 0u, + "bad geometry maps nothing"); + check(wt_ffa_mailbox_map(&mb, 0x1000ull, 0x3000ull, 1u) == 0 && + mb.mapped == 1u && mb.tx == 0x1000ull && mb.rx == 0x3000ull, + "a valid pair is recorded"); + check(wt_ffa_mailbox_map(&mb, 0x5000ull, 0x7000ull, 1u) == WT_FFA_DENIED, + "a second FFA_RXTX_MAP before an unmap is DENIED"); + check(wt_ffa_mailbox_overlaps(&mb, 0x1000ull, 0x1000ull) != 0 && + wt_ffa_mailbox_overlaps(&mb, 0x2000ull, 0x2000ull) != 0 && + wt_ffa_mailbox_overlaps(&mb, 0x2000ull, 0x1000ull) == 0 && + wt_ffa_mailbox_overlaps(&mb, 0x4000ull, 0x1000ull) == 0, + "a range holding a page of the mapped TX or RX buffer overlaps the pair"); + check(wt_ffa_mailbox_rx_release(&mb) == WT_FFA_DENIED, + "releasing an RX buffer the endpoint does not own is DENIED"); + check(wt_ffa_mailbox_rx_acquire(&mb) == 0 && + wt_ffa_mailbox_rx_acquire(&mb) == WT_FFA_BUSY, + "a full RX buffer is BUSY until released"); + check(wt_ffa_mailbox_rx_release(&mb) == 0 && + wt_ffa_mailbox_rx_acquire(&mb) == 0, + "FFA_RX_RELEASE hands the buffer back to the producer"); + check(wt_ffa_mailbox_rx_release(&mb) == 0 && + wt_ffa_mailbox_rx_post(&mb) == 0 && + wt_ffa_mailbox_rx_post(&mb) == WT_FFA_BUSY && + wt_ffa_mailbox_rx_acquire(&mb) == WT_FFA_BUSY, + "a partition message posted to RX makes the next one BUSY"); + check(wt_ffa_mailbox_rx_release(&mb) == WT_FFA_DENIED && + mb.rx_full == WT_FFA_RX_POSTED, + "RX_RELEASE before the RX-full notification is retrieved is DENIED " + "and the message stays (7.2.2.4.2 rule 2.1.1, Table 13.22)"); + wt_ffa_mailbox_rx_claim(&mb, WT_FFA_NOTIF_FW_SPM_MASK & + ~WT_FFA_NOTIF_FW_SPM_RX_FULL); + check(mb.rx_full == WT_FFA_RX_POSTED, + "a GET that returns no RX-full notification hands nothing over"); + wt_ffa_mailbox_rx_claim(&mb, WT_FFA_NOTIF_FW_NS_RX_FULL); + check(mb.rx_full == WT_FFA_RX_OWNED && wt_ffa_mailbox_rx_release(&mb) == 0, + "retrieving the RX-full notification hands RX to the endpoint, " + "which releases it"); + wt_ffa_mailbox_rx_claim(&mb, WT_FFA_NOTIF_FW_SPM_RX_FULL); + check(mb.rx_full == WT_FFA_RX_EMPTY && wt_ffa_mailbox_rx_acquire(&mb) == 0, + "an RX-full bit with no message posted claims nothing"); + check(wt_ffa_mailbox_unmap(&mb) == 0 && mb.mapped == 0u && + mb.rx_full == 0u && + wt_ffa_mailbox_overlaps(&mb, 0x1000ull, 0x1000ull) == 0, + "FFA_RXTX_UNMAP forgets the pair and its ownership"); +} + +/* WT-FFA-0009 (a memory management descriptor rides in the caller's mapped TX + * buffer, DEN0140 2.1.1.2 items 1-2, 2.4.1.2 items 1-2). */ +static void tx_buffer_rows(void) +{ + wt_ffa_mailbox_t mb; + uint64_t tx = 0u; + + memset(&mb, 0, sizeof(mb)); + check(wt_ffa_mem_tx_buffer(&mb, 0u, 0u, 64u, &tx) == + WT_FFA_INVALID_PARAMETERS && + wt_ffa_mem_tx_buffer(NULL, 0u, 0u, 64u, &tx) == + WT_FFA_INVALID_PARAMETERS, + "a caller with no RX/TX pair mapped is INVALID_PARAMETERS"); + (void)wt_ffa_mailbox_map(&mb, 0x5000ull, 0x7000ull, 1u); + check(wt_ffa_mem_tx_buffer(&mb, 0u, 0u, 64u, &tx) == 0 && tx == 0x5000ull, + "a mapped caller's descriptor is read from its TX buffer"); + check(wt_ffa_mem_tx_buffer(&mb, 0u, 0u, WT_FFA_MEM_PAGE_SIZE + 1u, &tx) == + WT_FFA_INVALID_PARAMETERS, + "a descriptor longer than the TX buffer is INVALID_PARAMETERS"); + check(wt_ffa_mem_tx_buffer(&mb, 0x9000ull, 0u, 64u, &tx) == + WT_FFA_INVALID_PARAMETERS && + wt_ffa_mem_tx_buffer(&mb, 0u, 1u, 64u, &tx) == + WT_FFA_INVALID_PARAMETERS && + wt_ffa_mem_tx_buffer(&mb, 0x9000ull, 1u, 64u, &tx) == + WT_FFA_INVALID_PARAMETERS, + "a dynamically allocated buffer address or page count is INVALID_PARAMETERS (4.1.1.3)"); +} + +/* WT-FFA-0009 (handle lifetime state). */ +static void registry_rows(void) +{ + wt_ffa_mem_registry_t reg; + const wt_ffa_mem_handle_entry_t* e; + uint64_t h[WT_FFA_MEM_MAX_HANDLES]; + uint64_t extra = 0u; + unsigned int i; + unsigned int j; + int ok; + + wt_ffa_mem_registry_init(®); + ok = 1; + for (i = 0u; i < WT_FFA_MEM_MAX_HANDLES; i++) { + ok = ok && (wt_ffa_mem_handle_alloc(®, WT_FFA_MEM_OP_SHARE, 0u, + 0x8002u, &h[i]) == 0); + } + check(ok, "the registry allocates a handle for every slot"); + + ok = 1; + for (i = 0u; i < WT_FFA_MEM_MAX_HANDLES; i++) { + for (j = i + 1u; j < WT_FFA_MEM_MAX_HANDLES; j++) { + ok = ok && (h[i] != h[j]); + } + ok = ok && ((h[i] & 0x8000000000000000ull) == 0u); + } + check(ok, "allocated handles are unique with the SPMC allocator bit clear"); + check(wt_ffa_mem_handle_alloc(®, WT_FFA_MEM_OP_SHARE, 0u, 0x8002u, + &extra) == WT_FFA_NO_MEMORY, + "a full registry refuses a further allocation"); + + check(wt_ffa_mem_handle_lookup(®, h[0], &e) == 0 && + e->borrower == 0x8002u && + e->state == (uint8_t)WT_FFA_MEM_STATE_SHARED, + "a live handle looks up its borrower and state"); + check(wt_ffa_mem_handle_lookup(®, 0xDEADBEEFull, &e) == + WT_FFA_INVALID_PARAMETERS, + "an unknown handle does not look up"); + + check(wt_ffa_mem_handle_retrieve(®, h[0], 0x9999u) == + WT_FFA_INVALID_PARAMETERS, + "a handle not sent to the caller cannot be retrieved: " + "INVALID_PARAMETERS (DEN0140 1.11.1)"); + check(wt_ffa_mem_handle_retrieve(®, h[0], 0x8002u) == 0, + "the declared borrower retrieves the handle"); + check(wt_ffa_mem_handle_retrieve(®, h[0], 0x8002u) == WT_FFA_DENIED, + "a handle cannot be retrieved twice"); + check(wt_ffa_mem_handle_reclaim(®, h[0], 0u) == WT_FFA_DENIED, + "the owner cannot reclaim a handle the borrower still holds"); + check(wt_ffa_mem_handle_relinquish(®, h[0], 0x9999u) == + WT_FFA_INVALID_PARAMETERS, + "a caller the handle was not sent to cannot relinquish it: " + "INVALID_PARAMETERS (DEN0140 2.6.1.2 rules 1-2)"); + check(wt_ffa_mem_handle_relinquish(®, h[0], 0x8002u) == 0, + "the borrower relinquishes the handle"); + check(wt_ffa_mem_handle_reclaim(®, h[0], 0x9999u) == + WT_FFA_INVALID_PARAMETERS, + "a caller that does not own the handle cannot reclaim it: " + "INVALID_PARAMETERS (DEN0140 2.7.1.2 rule 1)"); + check(wt_ffa_mem_handle_reclaim(®, h[0], 0u) == 0, + "the owner reclaims the relinquished handle"); + check(wt_ffa_mem_handle_lookup(®, h[0], &e) == WT_FFA_INVALID_PARAMETERS, + "a reclaimed handle is no longer known"); + check(wt_ffa_mem_handle_alloc(®, WT_FFA_MEM_OP_LEND, 0u, 0x8003u, + &extra) == 0 && extra != h[0], + "reclaiming a slot frees it for a new, distinct handle"); +} + +/* WT-FFA-0009 (share lifecycle: capture regions, map on retrieve, free on + * reclaim). */ +static void share_rows(void) +{ + uint8_t buf[256]; + wt_ffa_mem_txn_t txn; + wt_ffa_mem_region_t regs[WT_FFA_MEM_MAX_REGIONS]; + wt_ffa_mem_registry_t reg; + const wt_ffa_mem_handle_entry_t* e = NULL; + uint32_t n = 0u; + uint64_t h = 0u; + size_t len; + + len = make_txn(buf, sizeof(buf), WT_FFA_MEM_OP_SHARE, 0u); + (void)wt_ffa_mem_txn_validate(buf, len, WT_FFA_MEM_OP_SHARE, 0u, &txn); + + check(wt_ffa_mem_regions_from_txn(buf, len, &txn, 0u, regs, + WT_FFA_MEM_MAX_REGIONS, &n) == 0 && + n == 2u && regs[0].base == 0x40000000ull && + regs[0].page_count == 2u && regs[0].permissions == 0x06u && + regs[0].ns == 0u && regs[1].base == 0x40002000ull && + regs[1].page_count == 3u, + "the mapping list carries each constituent with the borrower permissions; the relayer decides the security state"); + buf[2] = (uint8_t)(buf[2] | 0x40u); + check(wt_ffa_mem_txn_validate(buf, len, WT_FFA_MEM_OP_SHARE, 0u, &txn) == + WT_FFA_INVALID_PARAMETERS, + "a sender that sets the NS bit is INVALID_PARAMETERS"); + buf[2] = (uint8_t)(buf[2] & ~0x40u); + check(wt_ffa_mem_regions_from_txn(buf, len, &txn, 0u, regs, 1u, &n) == + WT_FFA_NO_MEMORY, + "a mapping list smaller than the constituent count is NO_MEMORY"); + + wt_ffa_mem_registry_init(®); + h = 0u; + check(wt_ffa_mem_share_register(®, (wt_ffa_mem_op_t)7, 0u, 0x8002u, + regs, 2u, &h) == WT_FFA_INVALID_PARAMETERS && + h == 0u && wt_ffa_mem_handle_lookup(®, 1u, &e) == + WT_FFA_INVALID_PARAMETERS && + reg.entries[0].state == (uint8_t)WT_FFA_MEM_STATE_FREE, + "an operation that is not share, lend, or donate registers nothing " + "and hands out no handle"); + check(wt_ffa_mem_share_register(®, WT_FFA_MEM_OP_SHARE, 0u, 0x8002u, + regs, 2u, &h) == 0, + "a share registers a handle with its captured regions"); + check(wt_ffa_mem_handle_regions(®, h, regs, WT_FFA_MEM_MAX_REGIONS, + &n) == 0 && n == 2u && + regs[0].base == 0x40000000ull && regs[1].page_count == 3u, + "the relayer reads back the captured regions to map into the borrower"); + check(wt_ffa_mem_handle_retrieve(®, h, 0x8002u) == 0 && + wt_ffa_mem_handle_regions(®, h, regs, WT_FFA_MEM_MAX_REGIONS, + &n) == 0, + "the regions are still available after the borrower retrieves"); + check(wt_ffa_mem_handle_relinquish(®, h, 0x8002u) == 0 && + wt_ffa_mem_handle_reclaim(®, h, 0u) == 0 && + wt_ffa_mem_handle_regions(®, h, regs, WT_FFA_MEM_MAX_REGIONS, + &n) == WT_FFA_INVALID_PARAMETERS, + "a reclaimed handle exposes no regions"); + check(wt_ffa_mem_share_register(®, WT_FFA_MEM_OP_SHARE, 0u, 0x8002u, + regs, WT_FFA_MEM_MAX_REGIONS + 1u, &h) == + WT_FFA_INVALID_PARAMETERS, + "a share with more regions than the cap is refused"); +} + +/* WT-FFA-0009 (the constituent count is held to what a handle captures + * before any pairwise overlap scan runs). */ +static void constituent_limit_rows(void) +{ + static wt_ffa_mem_constituent_t cons[4096]; + static uint8_t buf[WT_FFA_MEM_TXN_HDR_SIZE + WT_FFA_MEM_ACCESS_SIZE + + WT_FFA_MEM_COMPOSITE_HDR_SIZE + + (4096u * WT_FFA_MEM_CONSTITUENT_SIZE)]; + wt_ffa_mem_build_t in; + wt_ffa_mem_txn_t txn; + size_t len = 0u; + uint32_t i; + + for (i = 0u; i < 4096u; i++) { + cons[i].address = 0x40000000ull + ((uint64_t)i * WT_FFA_MEM_PAGE_SIZE); + cons[i].page_count = 1u; + } + memset(&in, 0, sizeof(in)); + in.constituents = cons; + in.op = WT_FFA_MEM_OP_SHARE; + in.receiver = 0x8002u; + in.attributes = 0x2Fu; + in.permissions = 0x06u; + in.access_desc_size = (uint8_t)WT_FFA_MEM_ACCESS_SIZE; + in.constituent_count = WT_FFA_MEM_MAX_REGIONS; + check(wt_ffa_mem_txn_build(buf, sizeof(buf), &in, &len) == 0 && + wt_ffa_mem_txn_validate(buf, len, WT_FFA_MEM_OP_SHARE, 0u, &txn) == 0, + "a descriptor with as many constituents as a handle holds is valid"); + in.constituent_count = WT_FFA_MEM_MAX_REGIONS + 1u; + check(wt_ffa_mem_txn_build(buf, sizeof(buf), &in, &len) == 0 && + wt_ffa_mem_txn_validate(buf, len, WT_FFA_MEM_OP_SHARE, 0u, &txn) == + WT_FFA_NO_MEMORY, + "one constituent more than a handle holds is NO_MEMORY"); + for (i = 0u; i < 4096u; i++) { + cons[i].address = 0x40000000ull; + } + in.constituent_count = 4096u; + check(wt_ffa_mem_txn_build(buf, sizeof(buf), &in, &len) == 0 && + wt_ffa_mem_txn_validate(buf, len, WT_FFA_MEM_OP_SHARE, 0u, &txn) == + WT_FFA_NO_MEMORY, + "thousands of overlapping constituents are NO_MEMORY before any pair is compared"); +} + +/* WT-FFA-0009 (retrieve request and relinquish descriptors, and the handle a + * retrieve response carries). */ +static void retrieve_rows(void) +{ + uint8_t buf[256]; + wt_ffa_mem_txn_t txn; + uint64_t h = 0u; + uint32_t flags = 0u; + uint16_t sender = 0u; + uint16_t receiver = 0u; + size_t len = 0u; + + check(wt_ffa_mem_retrieve_req_build(buf, sizeof(buf), 0x1234ull, 0x8000u, + 0x80FBu, 0x06u, &len) == 0 && + len == 64u, + "a retrieve request is a header plus one access descriptor"); + check(wt_ffa_mem_retrieve_req_parse(buf, len, &h, &sender, &receiver) == 0 && + h == 0x1234ull && sender == 0x8000u && receiver == 0x80FBu, + "the retrieve request round-trips the handle, owner, and borrower"); + check(wt_ffa_mem_retrieve_req_parse(buf, WT_FFA_MEM_TXN_HDR_SIZE, &h, + &sender, &receiver) == + WT_FFA_INVALID_PARAMETERS, + "a retrieve request without its access descriptor is INVALID_PARAMETERS"); + + (void)wt_ffa_mem_retrieve_req_build(buf, sizeof(buf), 0x1234ull, 0x8000u, + 0x80FBu, 0x06u, &len); + buf[28] = (uint8_t)(WT_FFA_MEM_MAX_BORROWERS + 1u); + check(wt_ffa_mem_retrieve_req_parse(buf, len, &h, &sender, &receiver) == + WT_FFA_NOT_SUPPORTED, + "a retrieve request naming more receivers than a transaction holds is NOT_SUPPORTED"); + (void)wt_ffa_mem_retrieve_req_build(buf, sizeof(buf), 0x1234ull, 0x8000u, + 0x80FBu, 0x06u, &len); + buf[24] = 24u; + check(wt_ffa_mem_retrieve_req_parse(buf, len, &h, &sender, &receiver) == + WT_FFA_NOT_SUPPORTED, + "a retrieve request with an unknown access descriptor size is NOT_SUPPORTED"); + (void)wt_ffa_mem_retrieve_req_build(buf, sizeof(buf), 0x1234ull, 0x8000u, + 0x80FBu, 0x06u, &len); + put32(&buf[52], 64u); + check(wt_ffa_mem_retrieve_req_parse(buf, len, &h, &sender, &receiver) == + WT_FFA_INVALID_PARAMETERS, + "a retrieve request whose composite offset runs past it is INVALID_PARAMETERS"); + + check(wt_ffa_mem_relinquish_build(buf, sizeof(buf), 0x1234ull, 0u, 0x80FBu, + &len) == 0 && len == 18u, + "a relinquish descriptor names one endpoint after its header"); + check(wt_ffa_mem_relinquish_parse(buf, len, &h, &receiver) == 0 && + h == 0x1234ull && receiver == 0x80FBu, + "the relinquish descriptor round-trips the handle and endpoint"); + check(wt_ffa_mem_relinquish_parse(buf, WT_FFA_MEM_RELINQ_HDR_SIZE, &h, + &receiver) == WT_FFA_INVALID_PARAMETERS, + "a relinquish descriptor cut before its endpoint is INVALID_PARAMETERS"); + put32(&buf[12], 2u); + check(wt_ffa_mem_relinquish_parse(buf, len, &h, &receiver) == + WT_FFA_NOT_SUPPORTED, + "a relinquish naming more than one endpoint is NOT_SUPPORTED"); + put32(&buf[12], 1u); + put32(&buf[8], 0xFFFFFFFCu | WT_FFA_MEM_RELINQ_FLAG_ZERO); + check(wt_ffa_mem_relinquish_parse_ex(buf, len, &h, &receiver, &flags) == 0 && + flags == WT_FFA_MEM_RELINQ_FLAG_ZERO, + "the SBZ relinquish flag bits[31:2] are ignored and dropped (Table 2.25)"); + check(wt_ffa_mem_relinquish_build(buf, sizeof(buf), 0x1234ull, 0x4u, + 0x80FBu, &len) == WT_FFA_INVALID_PARAMETERS, + "the relinquish builder refuses a reserved flag"); + + len = make_txn(buf, sizeof(buf), WT_FFA_MEM_OP_SHARE, 0u); + put64(&buf[8], 0x55AAull); + check(wt_ffa_mem_txn_validate(buf, len, WT_FFA_MEM_OP_SHARE, 0u, &txn) == 0 && + txn.handle == 0x55AAull, + "a descriptor carrying a handle (a retrieve response) parses it"); +} + +/* WT-FFA-0009 (several borrowers, the v1.2 access descriptor, send flags). */ +static void borrower_rows(void) +{ + static const wt_ffa_mem_constituent_t cons[1] = { { 0x40000000ull, 2u } }; + wt_ffa_mem_registry_t reg; + wt_ffa_mem_region_t region = { 0x40000000ull, 2u, 0x06u, 1u }; + wt_ffa_mem_retrieve_req_t rq; + wt_ffa_mem_build_t in; + wt_ffa_mem_txn_t txn; + uint8_t buf[256]; + uint64_t h = 0u; + uint64_t parsed = 0u; + uint32_t flags = 0u; + uint16_t ep = 0u; + size_t len = 0u; + + wt_ffa_mem_registry_init(®); + check(wt_ffa_mem_share_register(®, WT_FFA_MEM_OP_SHARE, 0u, 0x8002u, + ®ion, 1u, &h) == 0 && + wt_ffa_mem_handle_add_borrower(®, h, 0x8003u, 0x05u) == 0, + "a transaction names a second borrower with its own permissions"); + check(wt_ffa_mem_handle_add_borrower(®, h, 0x8003u, 0x05u) == + WT_FFA_INVALID_PARAMETERS && + wt_ffa_mem_handle_add_borrower(®, h, 0u, 0x05u) == + WT_FFA_INVALID_PARAMETERS, + "a repeated borrower or the owner itself is refused"); + check(wt_ffa_mem_handle_add_borrower(®, h, 0x8004u, 0x05u) == 0 && + wt_ffa_mem_handle_add_borrower(®, h, 0x8005u, 0x05u) == + WT_FFA_NO_MEMORY, + "borrowers past the per-transaction limit are NO_MEMORY"); + check(wt_ffa_mem_handle_borrower(®, h, 0x8003u) != NULL && + wt_ffa_mem_handle_borrower(®, h, 0x8003u)->permissions == 0x05u && + wt_ffa_mem_handle_borrower(®, h, 0x8009u) == NULL, + "each borrower keeps what it was granted"); + check(wt_ffa_mem_handle_retrieve(®, h, 0x8002u) == 0 && + wt_ffa_mem_handle_retrieve(®, h, 0x8003u) == 0 && + wt_ffa_mem_handle_relinquish(®, h, 0x8002u) == 0 && + wt_ffa_mem_handle_reclaim(®, h, 0u) == WT_FFA_DENIED, + "the owner cannot reclaim while any borrower still holds the region"); + check(wt_ffa_mem_handle_add_borrower(®, h, 0x8006u, 0x05u) == + WT_FFA_INVALID_PARAMETERS, + "no borrower joins a transaction already retrieved"); + check(wt_ffa_mem_registry_overlaps(®, 0x40001000ull, 1u) != 0 && + wt_ffa_mem_registry_overlaps(®, 0x40002000ull, 4u) == 0, + "pages a live handle holds are found, its neighbours are not"); + check(wt_ffa_mem_handle_relinquish(®, h, 0x8003u) == 0 && + wt_ffa_mem_handle_reclaim(®, h, 0u) == 0 && + wt_ffa_mem_registry_overlaps(®, 0x40001000ull, 1u) == 0, + "reclaim frees the pages for a later transaction"); + + memset(&in, 0, sizeof(in)); + in.constituents = cons; + in.constituent_count = 1u; + in.op = WT_FFA_MEM_OP_LEND; + in.receiver = 0x8002u; + in.attributes = 0x2Fu; + in.permissions = 0x06u; + in.access_desc_size = (uint8_t)WT_FFA_MEM_ACCESS_SIZE_V12; + check(wt_ffa_mem_txn_build(buf, sizeof(buf), &in, &len) == 0 && + len == 112u && + wt_ffa_mem_txn_validate(buf, len, WT_FFA_MEM_OP_LEND, 0u, &txn) == 0 && + txn.access_desc_size == WT_FFA_MEM_ACCESS_SIZE_V12 && + txn.composite_offset == 80u, + "the 32-byte FF-A 1.2 access descriptor round-trips"); + buf[48u + 31u] = 1u; + check(wt_ffa_mem_txn_validate(buf, len, WT_FFA_MEM_OP_LEND, 0u, &txn) == 0, + "its SBZ reserved tail is ignored"); + buf[48u + 31u] = 0u; + buf[48u + 12u] = 0x5Au; + check(wt_ffa_mem_txn_validate(buf, len, WT_FFA_MEM_OP_LEND, 0u, &txn) == 0, + "its implementation-defined bytes are the sender's to use"); + in.access_desc_size = 24u; + check(wt_ffa_mem_txn_build(buf, sizeof(buf), &in, &len) == + WT_FFA_INVALID_PARAMETERS, + "no other access descriptor size is built"); + + (void)wt_ffa_mem_retrieve_req_build(buf, sizeof(buf), 0x77ull, 0u, 0x8002u, + 0x06u, &len); + put64(&buf[16], 0xABCDull); + put32(&buf[4], WT_FFA_MEM_FLAG_TYPE_LEND); + check(wt_ffa_mem_retrieve_req_parse_ex(buf, len, &rq) == 0 && + rq.handle == 0x77ull && rq.tag == 0xABCDull && + rq.flags == WT_FFA_MEM_FLAG_TYPE_LEND && rq.receiver_count == 1u && + rq.receivers[0] == 0x8002u && rq.permissions[0] == 0x06u && + rq.access_desc_size == WT_FFA_MEM_ACCESS_SIZE, + "a retrieve request yields its tag, flags, and asked-for permissions"); + buf[WT_FFA_MEM_TXN_HDR_SIZE + WT_FFA_MEM_ACC_OFF_PERMS] = 0xF6u; + buf[WT_FFA_MEM_TXN_HDR_SIZE + 8u] = 1u; + buf[WT_FFA_MEM_TXN_OFF_ATTRS + 1u] = 0x80u; + buf[40] = 0xFFu; + check(wt_ffa_mem_retrieve_req_parse_ex(buf, len, &rq) == 0 && + rq.permissions[0] == 0x06u && rq.attributes == 0u, + "a retrieve request's SBZ permission bits, access tail, attribute bits, and header bytes are ignored and dropped"); + put32(&buf[WT_FFA_MEM_TXN_OFF_FLAGS], 0xFFFFF800u | WT_FFA_MEM_FLAG_TYPE_LEND); + check(wt_ffa_mem_retrieve_req_parse_ex(buf, len, &rq) == 0 && + rq.flags == WT_FFA_MEM_FLAG_TYPE_LEND, + "a retrieve request's SBZ flag bits[31:11] are ignored and dropped (Table 1.22)"); + + (void)wt_ffa_mem_relinquish_build(buf, sizeof(buf), 0x77ull, + WT_FFA_MEM_RELINQ_FLAG_ZERO, 0x8002u, + &len); + check(wt_ffa_mem_relinquish_parse_ex(buf, len, &parsed, &ep, &flags) == 0 && + parsed == 0x77ull && ep == 0x8002u && + flags == WT_FFA_MEM_RELINQ_FLAG_ZERO, + "a relinquish descriptor yields its zero-memory flag"); +} + +/* WT-FFA-0009 (fragmented transmission, DEN0140 4.1.2): a descriptor sent + * in pieces reassembles byte for byte at every split point, and only the + * transaction's own sender and handle can add to it. */ +static wt_ffa_mem_frag_t g_frag_row; + +static void frag_rows(void) +{ + static uint8_t buf[256]; + wt_ffa_mem_registry_t reg; + wt_ffa_mem_txn_t txn; + size_t len = make_txn(buf, sizeof(buf), WT_FFA_MEM_OP_SHARE, 0u); + uint32_t split; + uint32_t off; + uint32_t piece; + uint64_t h1; + uint64_t h2; + const wt_ffa_mem_handle_entry_t* e = NULL; + static uint8_t rbuf[128]; + size_t rlen = 0u; + uint64_t size = 0u; + int done = 0; + int ok = 1; + int ret; + + check(len > 32u, "the canonical descriptor spans several fragments"); + for (split = 1u; split < (uint32_t)len; split++) { + ret = wt_ffa_mem_frag_begin(&g_frag_row, 7u, 0u, 1u, buf, split, + (uint32_t)len); + done = 0; + for (off = split; (ret == 0) && (off < (uint32_t)len); off += piece) { + piece = ((uint32_t)len - off > 5u) ? 5u : (uint32_t)len - off; + ret = wt_ffa_mem_frag_add(&g_frag_row, 7u, 0u, &buf[off], piece, + &done); + } + ok = ok && (ret == 0) && (done == 1) && + (g_frag_row.received == (uint32_t)len) && + (memcmp(g_frag_row.buf, buf, len) == 0); + wt_ffa_mem_frag_reset(&g_frag_row); + } + check(ok, "a descriptor split at every offset reassembles byte for byte"); + + check(wt_ffa_mem_frag_begin(&g_frag_row, 7u, 0u, 1u, buf, 0u, 64u) == + WT_FFA_INVALID_PARAMETERS, + "an empty first fragment is refused"); + check(wt_ffa_mem_frag_begin(&g_frag_row, 7u, 0u, 1u, buf, 64u, 64u) == + WT_FFA_INVALID_PARAMETERS, + "a first fragment that is the whole descriptor is not a fragmented send"); + check(wt_ffa_mem_frag_begin(&g_frag_row, 7u, 0u, 1u, buf, 16u, + WT_FFA_MEM_FRAG_MAX + 1u) == WT_FFA_NO_MEMORY, + "a descriptor past the reassembly bound is NO_MEMORY"); + + (void)wt_ffa_mem_frag_begin(&g_frag_row, 7u, 0u, 1u, buf, 16u, 64u); + check(wt_ffa_mem_frag_add(&g_frag_row, 8u, 0u, &buf[16], 16u, &done) == + WT_FFA_INVALID_PARAMETERS, + "a fragment for another handle is refused"); + check(wt_ffa_mem_frag_add(&g_frag_row, 7u, 0x8002u, &buf[16], 16u, &done) == + WT_FFA_INVALID_PARAMETERS, + "a fragment from another sender is refused"); + check(wt_ffa_mem_frag_add(&g_frag_row, 7u, 0u, &buf[16], 0u, &done) == + WT_FFA_INVALID_PARAMETERS, + "an empty fragment is refused"); + check(wt_ffa_mem_frag_add(&g_frag_row, 7u, 0u, &buf[16], 49u, &done) == + WT_FFA_INVALID_PARAMETERS, + "a fragment past the declared total is refused"); + check(g_frag_row.received == 16u, + "a refused fragment leaves the reassembly where it was"); + wt_ffa_mem_frag_reset(&g_frag_row); + check(wt_ffa_mem_frag_add(&g_frag_row, 7u, 0u, buf, 16u, &done) == + WT_FFA_INVALID_PARAMETERS, + "a fragment with no transfer in progress is refused"); + + wt_ffa_mem_registry_init(®); + h1 = wt_ffa_mem_handle_reserve(®); + (void)wt_ffa_mem_share_register(®, WT_FFA_MEM_OP_SHARE, 0u, 0x8002u, + NULL, 0u, &h2); + check((h1 != 0u) && (h2 != h1), + "a reserved handle is never handed to another transaction"); + check((wt_ffa_mem_share_register_as(®, WT_FFA_MEM_OP_SHARE, 0u, 0x8002u, + NULL, 0u, h1) == 0) && + (wt_ffa_mem_handle_lookup(®, h1, &e) == 0) && (e->handle == h1), + "the reserved handle names the region once the descriptor is whole"); + + (void)wt_ffa_mem_frag_begin(&g_frag_row, h1, 0u, 1u, buf, 40u, + (uint32_t)len); + (void)wt_ffa_mem_frag_add(&g_frag_row, h1, 0u, &buf[40], + (uint32_t)len - 40u, &done); + check((done == 1) && + (wt_ffa_mem_txn_validate(g_frag_row.buf, g_frag_row.total, + WT_FFA_MEM_OP_SHARE, 0u, &txn) == 0), + "a reassembled descriptor passes the relayer checks"); + wt_ffa_mem_frag_reset(&g_frag_row); + + check((wt_ffa_mem_frag_expected(buf, (uint32_t)len, 0, &size) == 1) && + (size == (uint64_t)len), + "a whole first fragment states the descriptor's exact length"); + check((wt_ffa_mem_frag_expected(buf, (uint32_t)len - 16u, 0, &size) == 1) && + (size == (uint64_t)len), + "the length is known before the last constituent has arrived"); + check(wt_ffa_mem_frag_expected(buf, WT_FFA_MEM_TXN_HDR_SIZE, 0, &size) == 0, + "a fragment that stops before the composite header cannot tell"); + check((wt_ffa_mem_frag_expected(buf, (uint32_t)len, 0, &size) == 1) && + (size != (uint64_t)len + 0x10u), + "a total longer than the descriptor it heads is caught"); + rlen = 0u; + check((wt_ffa_mem_retrieve_req_build(rbuf, sizeof(rbuf), 0x1234u, 0u, + 0x8002u, 0x06u, &rlen) == 0) && + (wt_ffa_mem_frag_expected(rbuf, (uint32_t)rlen, 1, &size) == 1) && + (size == (uint64_t)rlen) && (size != WT_FFA_MEM_PAGE_SIZE + 1u), + "a retrieve request states its length from its access descriptors"); + put32(&rbuf[WT_FFA_MEM_TXN_OFF_ACC_SIZE], 0u); + put32(&rbuf[WT_FFA_MEM_TXN_OFF_ACC_COUNT], 0xFFFFFFFFu); + check(wt_ffa_mem_frag_expected(rbuf, (uint32_t)rlen, 1, &size) == 0, + "a retrieve request with a zero access descriptor size cannot tell, and is never walked"); + put32(&rbuf[WT_FFA_MEM_TXN_OFF_ACC_SIZE], WT_FFA_MEM_ACCESS_SIZE); + put32(&rbuf[WT_FFA_MEM_TXN_OFF_ACC_OFFSET], 8u); + check(wt_ffa_mem_frag_expected(rbuf, (uint32_t)rlen, 1, &size) == 0, + "nor is one whose access array starts inside the header"); +} + +/* A transaction from owner 0 to borrowers 0x8002 onwards with tag 0x77. */ +static const wt_ffa_mem_handle_entry_t* make_entry(wt_ffa_mem_registry_t* reg, + wt_ffa_mem_op_t op, + uint32_t borrowers) +{ + wt_ffa_mem_region_t region = { 0x40000000ull, 1u, 0x02u, 1u }; + const wt_ffa_mem_handle_entry_t* e = NULL; + uint64_t h = 0u; + uint32_t i; + + wt_ffa_mem_registry_init(reg); + if (wt_ffa_mem_share_register(reg, op, 0u, 0x8002u, ®ion, 1u, &h) != 0) { + return NULL; + } + for (i = 1u; i < borrowers; i++) { + if (wt_ffa_mem_handle_add_borrower(reg, h, (uint16_t)(0x8002u + i), + 0x02u) != 0) { + return NULL; + } + } + wt_ffa_mem_handle_set_meta(reg, h, 0x77ull, 0u); + wt_ffa_mem_handle_set_attributes(reg, h, + (uint16_t)WT_FFA_MEM_ATTR_RELAYER); + if (wt_ffa_mem_handle_lookup(reg, h, &e) != 0) { + return NULL; + } + return e; +} + +/* A retrieve request from self for e naming its first n borrowers in order, + * each other borrower marked a non-retrieval borrower. */ +static void make_rq_as(wt_ffa_mem_retrieve_req_t* rq, + const wt_ffa_mem_handle_entry_t* e, uint32_t n, + uint16_t self) +{ + uint32_t i; + + memset(rq, 0, sizeof(*rq)); + rq->handle = e->handle; + rq->tag = 0x77ull; + rq->receiver_count = n; + rq->access_desc_size = WT_FFA_MEM_ACCESS_SIZE; + for (i = 0u; i < n; i++) { + rq->receivers[i] = (uint16_t)(0x8002u + i); + rq->permissions[i] = 0x02u; + if (rq->receivers[i] != self) { + rq->access_flags[i] = WT_FFA_MEM_ACC_FLAG_NON_RETRIEVAL; + } + } +} + +/* make_rq_as from the first borrower. */ +static void make_rq(wt_ffa_mem_retrieve_req_t* rq, + const wt_ffa_mem_handle_entry_t* e, uint32_t n) +{ + make_rq_as(rq, e, n, 0x8002u); +} + +/* WT-FFA-0009 (a retrieve request held against its transaction, 2.4.1.2). */ +static void retrieve_check_rows(void) +{ + static wt_ffa_mem_registry_t reg; + const wt_ffa_mem_handle_entry_t* e; + wt_ffa_mem_retrieve_req_t rq; + + check(wt_ffa_mem_type_flag((uint8_t)WT_FFA_MEM_STATE_SHARED) == + WT_FFA_MEM_FLAG_TYPE_SHARE && + wt_ffa_mem_type_flag((uint8_t)WT_FFA_MEM_STATE_LENT) == + WT_FFA_MEM_FLAG_TYPE_LEND && + wt_ffa_mem_type_flag((uint8_t)WT_FFA_MEM_STATE_DONATED) == + WT_FFA_MEM_FLAG_TYPE_DONATE, + "each live handle state reports its transaction type"); + + e = make_entry(®, WT_FFA_MEM_OP_LEND, 1u); + check(e != NULL, "a single-borrower lend registers"); + if (e == NULL) { + return; + } + make_rq(&rq, e, 1u); + check(wt_ffa_mem_retrieve_req_check(e, &rq, 0x8002u) == 0, + "a request naming the borrower with the owner's tag is accepted"); + rq.flags = WT_FFA_MEM_FLAG_TYPE_LEND; + check(wt_ffa_mem_retrieve_req_check(e, &rq, 0x8002u) == 0, + "a request stating the transaction's own type is accepted"); + rq.flags = WT_FFA_MEM_FLAG_TYPE_SHARE; + check(wt_ffa_mem_retrieve_req_check(e, &rq, 0x8002u) == WT_FFA_INVALID_PARAMETERS, + "a request stating another transaction type is INVALID_PARAMETERS"); + make_rq(&rq, e, 1u); + rq.tag = 0x78ull; + check(wt_ffa_mem_retrieve_req_check(e, &rq, 0x8002u) == WT_FFA_INVALID_PARAMETERS, + "a request with another tag is INVALID_PARAMETERS"); + make_rq(&rq, e, 1u); + rq.flags = WT_FFA_MEM_FLAG_TIME_SLICE; + check(wt_ffa_mem_retrieve_req_check(e, &rq, 0x8002u) == WT_FFA_INVALID_PARAMETERS, + "a retrieve flag the relayer does not implement is INVALID_PARAMETERS"); + make_rq(&rq, e, 1u); + rq.flags = WT_FFA_MEM_FLAG_BYPASS_BORROWERS; + check(wt_ffa_mem_retrieve_req_check(e, &rq, 0x8002u) == WT_FFA_INVALID_PARAMETERS, + "the bypass flag with a single borrower is INVALID_PARAMETERS"); + make_rq(&rq, e, 1u); + rq.attributes = (uint16_t)(WT_FFA_MEM_ATTR_TYPE_NORMAL | + WT_FFA_MEM_ATTR_CACHE_MASK | + WT_FFA_MEM_ATTR_SHARE_INNER | WT_FFA_MEM_ATTR_NS); + check(wt_ffa_mem_retrieve_req_check(e, &rq, 0x8002u) == WT_FFA_INVALID_PARAMETERS, + "a request that sets the NS bit is INVALID_PARAMETERS"); + rq.attributes = (uint16_t)WT_FFA_MEM_ATTR_TYPE_DEVICE; + check(wt_ffa_mem_retrieve_req_check(e, &rq, 0x8002u) == WT_FFA_DENIED, + "a request for Device memory is DENIED"); + make_rq(&rq, e, 1u); + rq.receivers[0] = 0x8009u; + check(wt_ffa_mem_retrieve_req_check(e, &rq, 0x8002u) == WT_FFA_INVALID_PARAMETERS, + "a request naming an endpoint that is not a borrower is INVALID_PARAMETERS"); + make_rq(&rq, e, 1u); + rq.impdef[0][3] = 0x5Au; + check(wt_ffa_mem_retrieve_req_check(e, &rq, 0x8002u) == WT_FFA_INVALID_PARAMETERS, + "a request that does not repeat the implementation-defined bytes is INVALID_PARAMETERS"); + + e = make_entry(®, WT_FFA_MEM_OP_SHARE, 2u); + check(e != NULL, "a two-borrower share registers"); + if (e == NULL) { + return; + } + make_rq(&rq, e, 1u); + rq.flags = WT_FFA_MEM_FLAG_BYPASS_BORROWERS; + check(wt_ffa_mem_retrieve_req_check(e, &rq, 0x8002u) == 0, + "with several borrowers the bypass flag lets the caller name only itself"); +} + +/* WT-FFA-0009 (time slicing, DEN0140 4.1.3, is not implemented, so its flag + * is INVALID_PARAMETERS in every call that carries it). */ +static void time_slice_rows(void) +{ + static const wt_ffa_mem_op_t ops[3] = { + WT_FFA_MEM_OP_SHARE, WT_FFA_MEM_OP_LEND, WT_FFA_MEM_OP_DONATE + }; + static wt_ffa_mem_registry_t reg; + const wt_ffa_mem_handle_entry_t* e; + wt_ffa_mem_retrieve_req_t rq; + wt_ffa_mem_txn_t txn; + uint8_t buf[256]; + uint64_t h = 0u; + uint16_t ep = 0u; + size_t len = 0u; + uint32_t i; + int ok = 1; + + for (i = 0u; i < 3u; i++) { + len = make_txn(buf, sizeof(buf), ops[i], WT_FFA_MEM_FLAG_TIME_SLICE); + ok = ok && (len != 0u) && + (wt_ffa_mem_txn_validate(buf, len, ops[i], 0u, &txn) == + WT_FFA_INVALID_PARAMETERS); + } + check(ok, "a share, lend, or donate that asks for time slicing is INVALID_PARAMETERS"); + + e = make_entry(®, WT_FFA_MEM_OP_LEND, 1u); + check(e != NULL, "a lend to retrieve registers"); + if (e != NULL) { + make_rq(&rq, e, 1u); + rq.flags = WT_FFA_MEM_FLAG_TIME_SLICE; + check(wt_ffa_mem_retrieve_req_check(e, &rq, 0x8002u) == + WT_FFA_INVALID_PARAMETERS, + "a retrieve request that asks for time slicing is INVALID_PARAMETERS"); + } + + (void)wt_ffa_mem_relinquish_build(buf, sizeof(buf), 0x1234ull, 0u, 0x8002u, + &len); + put32(&buf[8], WT_FFA_MEM_FLAG_TIME_SLICE); + check(wt_ffa_mem_relinquish_parse(buf, len, &h, &ep) == + WT_FFA_INVALID_PARAMETERS, + "a relinquish that asks for time slicing is INVALID_PARAMETERS"); + check(wt_ffa_mem_relinquish_build(buf, sizeof(buf), 0x1234ull, + WT_FFA_MEM_FLAG_TIME_SLICE, 0x8002u, + &len) == WT_FFA_INVALID_PARAMETERS, + "the relinquish builder refuses the time-slicing flag"); + check(wt_ffa_mem_reclaim_flags_check(WT_FFA_MEM_FLAG_TIME_SLICE) == + WT_FFA_INVALID_PARAMETERS && + wt_ffa_mem_reclaim_flags_check(WT_FFA_MEM_FLAG_TIME_SLICE | + WT_FFA_MEM_RELINQ_FLAG_ZERO) == + WT_FFA_INVALID_PARAMETERS, + "a reclaim that asks for time slicing is INVALID_PARAMETERS"); + check(wt_ffa_mem_reclaim_flags_check(0u) == 0 && + wt_ffa_mem_reclaim_flags_check(WT_FFA_MEM_RELINQ_FLAG_ZERO) == 0, + "a reclaim may still ask for the memory to be zeroed"); + check(wt_ffa_mem_reclaim_flags_check(0xFFFFFFFCu) == 0, + "the SBZ reclaim flag bits[31:2] are ignored (Table 2.31)"); +} + +/* WT-FFA-0009 (the Handle field of a lend/donate/share, 1.11.1: this SPMC + * allocates every handle, so a sender leaves it zero). */ +static void send_handle_rows(void) +{ + static const wt_ffa_mem_op_t ops[3] = { + WT_FFA_MEM_OP_SHARE, WT_FFA_MEM_OP_LEND, WT_FFA_MEM_OP_DONATE + }; + static uint8_t buf[256]; + static wt_ffa_mem_registry_t reg; + wt_ffa_mem_txn_t txn; + uint64_t h; + size_t len = 0u; + uint32_t i; + int done = 0; + int ok = 1; + + for (i = 0u; i < 3u; i++) { + len = make_txn(buf, sizeof(buf), ops[i], 0u); + ok = ok && (len != 0u) && + (wt_ffa_mem_send_validate(buf, len, ops[i], 0u, &txn) == 0); + } + check(ok, "a share, lend, or donate with a zero Handle field is accepted"); + + len = make_txn(buf, sizeof(buf), WT_FFA_MEM_OP_SHARE, 0u); + put64(&buf[8], 0x55AAull); + check(wt_ffa_mem_send_validate(buf, len, WT_FFA_MEM_OP_SHARE, 0u, &txn) == + WT_FFA_INVALID_PARAMETERS, + "a send naming a handle of its own is INVALID_PARAMETERS"); + put64(&buf[8], 0x8000000000000001ull); + check(wt_ffa_mem_send_validate(buf, len, WT_FFA_MEM_OP_SHARE, 0u, &txn) == + WT_FFA_INVALID_PARAMETERS, + "a send carrying a Hypervisor-allocated handle is INVALID_PARAMETERS"); + check(wt_ffa_mem_send_validate(buf, len, WT_FFA_MEM_OP_SHARE, 0x9999u, + &txn) == WT_FFA_DENIED, + "a send from the wrong sender is still DENIED first"); + + len = make_txn(buf, sizeof(buf), WT_FFA_MEM_OP_LEND, 0u); + wt_ffa_mem_registry_init(®); + h = wt_ffa_mem_handle_reserve(®); + ok = (wt_ffa_mem_frag_begin(&g_frag_row, h, 0u, (uint8_t)WT_FFA_MEM_OP_LEND, + buf, 40u, (uint32_t)len) == 0) && + (wt_ffa_mem_frag_add(&g_frag_row, h, 0u, &buf[40], + (uint32_t)len - 40u, &done) == 0) && + (done == 1); + check(ok && (h != 0u) && + (wt_ffa_mem_send_validate(g_frag_row.buf, g_frag_row.total, + WT_FFA_MEM_OP_LEND, 0u, &txn) == 0), + "a fragmented send passes with its reserved handle held outside the descriptor"); + wt_ffa_mem_frag_reset(&g_frag_row); +} + +/* WT-FFA-0009 (memory region attribute encodings, Table 1.18). */ +static void attribute_rows(void) +{ + static const uint16_t valid[] = { + 0x00u, 0x24u, 0x26u, 0x27u, 0x2Cu, 0x2Eu, 0x2Fu, + 0x10u, 0x14u, 0x18u, 0x1Cu + }; + static const uint16_t invalid[] = { + 0x20u, 0x23u, 0x28u, 0x2Bu, 0x25u, 0x2Du, + 0x11u, 0x1Fu, 0x04u, 0x01u, 0x30u + }; + static wt_ffa_mem_registry_t reg; + const wt_ffa_mem_handle_entry_t* e; + wt_ffa_mem_retrieve_req_t rq; + wt_ffa_mem_txn_t txn; + uint8_t buf[256]; + size_t len; + size_t i; + int ok; + + ok = 1; + for (i = 0u; i < sizeof(valid) / sizeof(valid[0]); i++) { + ok = ok && (wt_ffa_mem_attributes_check(valid[i]) == 0); + } + check(ok, "Normal non-cacheable or write-back memory of any defined shareability, Device memory, and an unspecified type are valid"); + ok = 1; + for (i = 0u; i < sizeof(invalid) / sizeof(invalid[0]); i++) { + ok = ok && (wt_ffa_mem_attributes_check(invalid[i]) == + WT_FFA_INVALID_PARAMETERS); + } + check(ok, "reserved cacheability, reserved shareability, non-zero reserved bits, and the reserved type are INVALID_PARAMETERS"); + + len = make_txn(buf, sizeof(buf), WT_FFA_MEM_OP_SHARE, 0u); + buf[2] = 0x2Bu; + check(wt_ffa_mem_txn_validate(buf, len, WT_FFA_MEM_OP_SHARE, 0u, &txn) == + WT_FFA_INVALID_PARAMETERS, + "a share of Normal memory with a reserved cacheability is INVALID_PARAMETERS"); + buf[2] = 0x2Du; + check(wt_ffa_mem_txn_validate(buf, len, WT_FFA_MEM_OP_SHARE, 0u, &txn) == + WT_FFA_INVALID_PARAMETERS, + "a share of Normal memory with the reserved shareability is INVALID_PARAMETERS"); + buf[2] = 0x13u; + check(wt_ffa_mem_txn_validate(buf, len, WT_FFA_MEM_OP_SHARE, 0u, &txn) == + WT_FFA_INVALID_PARAMETERS, + "a share of Device memory with shareability bits set is INVALID_PARAMETERS"); + buf[2] = 0x1Cu; + check(wt_ffa_mem_txn_validate(buf, len, WT_FFA_MEM_OP_SHARE, 0u, &txn) == 0, + "a share of Device-GRE memory is well formed"); + len = make_txn(buf, sizeof(buf), WT_FFA_MEM_OP_LEND, 0u); + buf[2] = 0x0Fu; + check(wt_ffa_mem_txn_validate(buf, len, WT_FFA_MEM_OP_LEND, 0u, &txn) == + WT_FFA_INVALID_PARAMETERS, + "an unspecified type with cacheability or shareability bits set is INVALID_PARAMETERS"); + buf[2] = 0x00u; + check(wt_ffa_mem_txn_validate(buf, len, WT_FFA_MEM_OP_LEND, 0u, &txn) == 0, + "an unspecified type with its reserved bits clear is well formed"); + + e = make_entry(®, WT_FFA_MEM_OP_SHARE, 1u); + check(e != NULL, "a share to retrieve registers"); + if (e == NULL) { + return; + } + make_rq(&rq, e, 1u); + rq.attributes = 0x2Fu; + check(wt_ffa_mem_retrieve_req_check(e, &rq, 0x8002u) == 0, + "a retrieve request for Normal write-back inner-shareable memory is accepted"); + rq.attributes = 0x30u; + check(wt_ffa_mem_retrieve_req_check(e, &rq, 0x8002u) == WT_FFA_INVALID_PARAMETERS, + "a retrieve request for the reserved memory type is INVALID_PARAMETERS"); + rq.attributes = 0x2Bu; + check(wt_ffa_mem_retrieve_req_check(e, &rq, 0x8002u) == WT_FFA_INVALID_PARAMETERS, + "a retrieve request with a reserved Normal cacheability is INVALID_PARAMETERS"); + rq.attributes = 0x0Cu; + check(wt_ffa_mem_retrieve_req_check(e, &rq, 0x8002u) == WT_FFA_INVALID_PARAMETERS, + "a retrieve request with an unspecified type and cacheability bits set is INVALID_PARAMETERS"); + rq.attributes = 0x1Fu; + check(wt_ffa_mem_retrieve_req_check(e, &rq, 0x8002u) == WT_FFA_DENIED, + "a retrieve request for Device memory is DENIED before its reserved bits are read"); + rq.attributes = 0x2Eu; + check(wt_ffa_mem_retrieve_req_check(e, &rq, 0x8002u) == WT_FFA_DENIED, + "a retrieve request for outer-shareable memory the lender made inner-shareable is DENIED"); + rq.attributes = 0x27u; + check(wt_ffa_mem_retrieve_req_check(e, &rq, 0x8002u) == + WT_FFA_INVALID_PARAMETERS, + "a retrieve request for non-cacheable memory, which the relayer cannot map, is INVALID_PARAMETERS"); + rq.attributes = 0x2Cu; + check(wt_ffa_mem_retrieve_req_check(e, &rq, 0x8002u) == + WT_FFA_INVALID_PARAMETERS, + "a retrieve request for non-shareable memory, which the relayer cannot map, is INVALID_PARAMETERS"); + rq.attributes = 0x00u; + check(wt_ffa_mem_retrieve_req_check(e, &rq, 0x8002u) == 0, + "a retrieve request that leaves the attributes unspecified is accepted"); + wt_ffa_mem_handle_set_attributes(®, e->handle, 0u); + rq.attributes = 0x2Fu; + check(wt_ffa_mem_retrieve_req_check(e, &rq, 0x8002u) == WT_FFA_DENIED, + "a transaction with no recorded attributes is held to none"); +} + +/* WT-FFA-0009 (the attributes a lend or share names are those every borrower + * maps with, DEN0140 1.10.4.2). */ +static void send_attribute_rows(void) +{ + static const uint16_t denied[] = { 0x2Eu, 0x26u }; + static const uint16_t unmappable[] = { + 0x27u, 0x24u, 0x2Cu, 0x10u, 0x14u, 0x18u, 0x1Cu + }; + uint16_t out = 0u; + size_t i; + int ok; + + check(wt_ffa_mem_send_attributes(0x00u, &out) == 0 && + out == (uint16_t)WT_FFA_MEM_ATTR_RELAYER, + "a send that leaves the attributes unspecified gets the relayer's Normal write-back inner-shareable"); + out = 0u; + check(wt_ffa_mem_send_attributes(0x2Fu, &out) == 0 && out == 0x2Fu, + "a send of Normal write-back inner-shareable memory keeps its attributes"); + ok = 1; + for (i = 0u; i < sizeof(denied) / sizeof(denied[0]); i++) { + ok = ok && (wt_ffa_mem_send_attributes(denied[i], &out) == WT_FFA_DENIED); + } + check(ok, "a send of outer-shareable memory, more permissive than the relayer maps, is DENIED"); + ok = 1; + for (i = 0u; i < sizeof(unmappable) / sizeof(unmappable[0]); i++) { + ok = ok && (wt_ffa_mem_send_attributes(unmappable[i], &out) == + WT_FFA_INVALID_PARAMETERS); + } + check(ok, "a send of non-cacheable, non-shareable, or Device memory, which the relayer cannot map, is INVALID_PARAMETERS"); + check(wt_ffa_mem_send_attributes(0x2Bu, &out) == WT_FFA_INVALID_PARAMETERS && + wt_ffa_mem_send_attributes(0x6Fu, &out) == WT_FFA_INVALID_PARAMETERS && + wt_ffa_mem_send_attributes(0x2Fu, NULL) == WT_FFA_INVALID_PARAMETERS, + "a reserved encoding, the NS bit, or no output is INVALID_PARAMETERS"); +} + +/* WT-FFA-0009 (the endpoint access descriptor flags byte, 1.10.1). */ +static void access_flag_rows(void) +{ + static wt_ffa_mem_registry_t reg; + const wt_ffa_mem_handle_entry_t* e; + wt_ffa_mem_retrieve_req_t rq; + wt_ffa_mem_txn_t txn; + uint8_t buf[256]; + size_t len = 0u; + + len = make_txn(buf, sizeof(buf), WT_FFA_MEM_OP_SHARE, 0u); + buf[48u + WT_FFA_MEM_ACC_OFF_FLAGS] = WT_FFA_MEM_ACC_FLAG_NON_RETRIEVAL; + check(wt_ffa_mem_txn_validate(buf, len, WT_FFA_MEM_OP_SHARE, 0u, &txn) == + WT_FFA_INVALID_PARAMETERS, + "a share whose access descriptor sets a flag is INVALID_PARAMETERS"); + len = make_txn(buf, sizeof(buf), WT_FFA_MEM_OP_LEND, 0u); + buf[48u + WT_FFA_MEM_ACC_OFF_FLAGS] = 0x80u; + check(wt_ffa_mem_txn_validate(buf, len, WT_FFA_MEM_OP_LEND, 0u, &txn) == + WT_FFA_INVALID_PARAMETERS, + "every bit of a lend's access descriptor flags is MBZ"); + len = make_txn(buf, sizeof(buf), WT_FFA_MEM_OP_DONATE, 0u); + buf[48u + WT_FFA_MEM_ACC_OFF_FLAGS] = 0x02u; + check(wt_ffa_mem_txn_validate(buf, len, WT_FFA_MEM_OP_DONATE, 0u, &txn) == + WT_FFA_INVALID_PARAMETERS, + "every bit of a donate's access descriptor flags is MBZ"); + + (void)wt_ffa_mem_retrieve_req_build(buf, sizeof(buf), 0x77ull, 0u, 0x8002u, + 0x02u, &len); + buf[48u + WT_FFA_MEM_ACC_OFF_FLAGS] = 0x81u; + check(wt_ffa_mem_retrieve_req_parse_ex(buf, len, &rq) == 0 && + rq.access_flags[0] == 0x81u, + "a retrieve request yields each access descriptor's flags"); + + e = make_entry(®, WT_FFA_MEM_OP_LEND, 1u); + check(e != NULL, "a single-borrower lend registers"); + if (e == NULL) { + return; + } + make_rq(&rq, e, 1u); + rq.access_flags[0] = WT_FFA_MEM_ACC_FLAG_NON_RETRIEVAL; + check(wt_ffa_mem_retrieve_req_check(e, &rq, 0x8002u) == + WT_FFA_INVALID_PARAMETERS, + "the only borrower marking itself a non-retrieval borrower is INVALID_PARAMETERS"); + rq.access_flags[0] = 0xFEu; + check(wt_ffa_mem_retrieve_req_check(e, &rq, 0x8002u) == 0, + "the SBZ flag bits of a retrieve request are ignored"); + + e = make_entry(®, WT_FFA_MEM_OP_SHARE, 2u); + check(e != NULL, "a two-borrower share registers"); + if (e == NULL) { + return; + } + make_rq(&rq, e, 2u); + rq.access_flags[1] = 0u; + check(wt_ffa_mem_retrieve_req_check(e, &rq, 0x8002u) == + WT_FFA_INVALID_PARAMETERS, + "naming the other borrower without the non-retrieval flag is INVALID_PARAMETERS"); + rq.access_flags[1] = WT_FFA_MEM_ACC_FLAG_NON_RETRIEVAL; + check(wt_ffa_mem_retrieve_req_check(e, &rq, 0x8002u) == 0, + "naming the other borrower as a non-retrieval borrower is accepted"); + rq.access_flags[0] = WT_FFA_MEM_ACC_FLAG_NON_RETRIEVAL; + check(wt_ffa_mem_retrieve_req_check(e, &rq, 0x8002u) == + WT_FFA_INVALID_PARAMETERS, + "the caller's own entry marked a non-retrieval borrower is INVALID_PARAMETERS"); + check(wt_ffa_mem_retrieve_req_check(e, &rq, 0x8003u) == + WT_FFA_INVALID_PARAMETERS, + "the rule follows the caller, whichever entry is its own"); + rq.access_flags[1] = 0u; + check(wt_ffa_mem_retrieve_req_check(e, &rq, 0x8003u) == 0, + "the second borrower retrieves with the first marked non-retrieval"); + make_rq(&rq, e, 1u); + rq.flags = WT_FFA_MEM_FLAG_BYPASS_BORROWERS; + rq.receiver_count = 2u; + rq.receivers[1] = 0x8003u; + check(wt_ffa_mem_retrieve_req_check(e, &rq, 0x8002u) == + WT_FFA_INVALID_PARAMETERS, + "the bypass flag does not excuse another borrower's clear flag"); + make_rq_as(&rq, e, 1u, 0x8003u); + rq.flags = WT_FFA_MEM_FLAG_BYPASS_BORROWERS; + rq.receivers[0] = 0x8003u; + rq.access_flags[0] = 0u; + check(wt_ffa_mem_retrieve_req_check(e, &rq, 0x8002u) == + WT_FFA_INVALID_PARAMETERS, + "a bypass request whose only entry is another borrower is " + "INVALID_PARAMETERS even with its flags in order"); +} + +/* WT-FFA-0009 (without the bypass flag a retrieve request names the lender's + * whole borrower list, 1.11.3.3 and Table 1.22 bit[10]). */ +static void borrower_list_rows(void) +{ + static wt_ffa_mem_registry_t reg; + const wt_ffa_mem_handle_entry_t* e; + wt_ffa_mem_retrieve_req_t rq; + + e = make_entry(®, WT_FFA_MEM_OP_LEND, 2u); + check(e != NULL, "a two-borrower lend registers"); + if (e == NULL) { + return; + } + make_rq(&rq, e, 2u); + check(wt_ffa_mem_retrieve_req_check(e, &rq, 0x8002u) == 0, + "the first borrower naming both borrowers is accepted"); + make_rq_as(&rq, e, 2u, 0x8003u); + check(wt_ffa_mem_retrieve_req_check(e, &rq, 0x8003u) == 0, + "the second borrower naming both borrowers is accepted"); + make_rq(&rq, e, 2u); + rq.receivers[0] = 0x8003u; + rq.receivers[1] = 0x8002u; + rq.access_flags[0] = WT_FFA_MEM_ACC_FLAG_NON_RETRIEVAL; + rq.access_flags[1] = 0u; + check(wt_ffa_mem_retrieve_req_check(e, &rq, 0x8002u) == 0, + "the borrowers may be named in any order"); + make_rq(&rq, e, 2u); + rq.permissions[1] = WT_FFA_MEM_PERM_DATA_RO; + check(wt_ffa_mem_retrieve_req_check(e, &rq, 0x8002u) == WT_FFA_DENIED, + "naming the other borrower with access the lender did not give it is DENIED"); + rq.permissions[1] = WT_FFA_MEM_PERM_DATA_RW; + rq.permissions[0] = WT_FFA_MEM_PERM_DATA_RO; + check(wt_ffa_mem_retrieve_req_check(e, &rq, 0x8002u) == 0, + "the caller's own entry may ask for less; the other's data access matches"); + rq.permissions[1] = WT_FFA_MEM_PERM_DATA_RW | WT_FFA_MEM_PERM_INSTR_NX; + check(wt_ffa_mem_retrieve_req_check(e, &rq, 0x8002u) == + WT_FFA_INVALID_PARAMETERS, + "naming the other borrower's instruction access is INVALID_PARAMETERS (1.10.3 item 1)"); + rq.permissions[1] = WT_FFA_MEM_PERM_DATA_RW | WT_FFA_MEM_PERM_INSTR_X; + check(wt_ffa_mem_retrieve_req_check(e, &rq, 0x8002u) == + WT_FFA_INVALID_PARAMETERS, + "so is naming it executable"); + rq.permissions[1] = WT_FFA_MEM_PERM_DATA_RSVD; + check(wt_ffa_mem_retrieve_req_check(e, &rq, 0x8002u) == + WT_FFA_INVALID_PARAMETERS, + "and a reserved data access for the other borrower"); + make_rq(&rq, e, 1u); + check(wt_ffa_mem_retrieve_req_check(e, &rq, 0x8002u) == + WT_FFA_INVALID_PARAMETERS, + "naming only itself without the bypass flag is INVALID_PARAMETERS"); + make_rq(&rq, e, 2u); + rq.receivers[1] = 0x8002u; + rq.access_flags[1] = 0u; + check(wt_ffa_mem_retrieve_req_check(e, &rq, 0x8002u) == + WT_FFA_INVALID_PARAMETERS, + "naming one borrower twice in place of the other is INVALID_PARAMETERS"); + make_rq(&rq, e, 3u); + rq.receivers[2] = 0x8004u; + check(wt_ffa_mem_retrieve_req_check(e, &rq, 0x8002u) == + WT_FFA_INVALID_PARAMETERS, + "naming an endpoint the lender did not name is INVALID_PARAMETERS"); + make_rq(&rq, e, 1u); + rq.flags = WT_FFA_MEM_FLAG_BYPASS_BORROWERS; + check(wt_ffa_mem_retrieve_req_check(e, &rq, 0x8002u) == 0, + "with the bypass flag the caller may still name only itself"); + + e = make_entry(®, WT_FFA_MEM_OP_SHARE, 3u); + check(e != NULL, "a three-borrower share registers"); + if (e == NULL) { + return; + } + make_rq_as(&rq, e, 3u, 0x8003u); + check(wt_ffa_mem_retrieve_req_check(e, &rq, 0x8003u) == 0, + "naming all three borrowers is accepted"); + make_rq_as(&rq, e, 2u, 0x8003u); + check(wt_ffa_mem_retrieve_req_check(e, &rq, 0x8003u) == + WT_FFA_INVALID_PARAMETERS, + "leaving a borrower out is INVALID_PARAMETERS"); + make_rq_as(&rq, e, 3u, 0x8003u); + rq.receivers[2] = 0x8003u; + rq.access_flags[2] = 0u; + check(wt_ffa_mem_retrieve_req_check(e, &rq, 0x8003u) == + WT_FFA_INVALID_PARAMETERS, + "a repeat that hides a missing borrower is INVALID_PARAMETERS"); + make_rq_as(&rq, e, 2u, 0x8003u); + rq.flags = WT_FFA_MEM_FLAG_BYPASS_BORROWERS; + check(wt_ffa_mem_retrieve_req_check(e, &rq, 0x8003u) == + WT_FFA_INVALID_PARAMETERS, + "with the bypass flag, naming itself and one other borrower is INVALID_PARAMETERS"); + make_rq_as(&rq, e, 3u, 0x8003u); + rq.flags = WT_FFA_MEM_FLAG_BYPASS_BORROWERS; + check(wt_ffa_mem_retrieve_req_check(e, &rq, 0x8003u) == + WT_FFA_INVALID_PARAMETERS, + "with the bypass flag, naming every borrower is INVALID_PARAMETERS too"); + make_rq_as(&rq, e, 1u, 0x8003u); + rq.receivers[0] = 0x8003u; + rq.access_flags[0] = 0u; + rq.flags = WT_FFA_MEM_FLAG_BYPASS_BORROWERS; + check(wt_ffa_mem_retrieve_req_check(e, &rq, 0x8003u) == 0, + "with the bypass flag the second borrower names only itself"); + + e = make_entry(®, WT_FFA_MEM_OP_LEND, 1u); + check(e != NULL, "a single-borrower lend registers"); + if (e == NULL) { + return; + } + make_rq(&rq, e, 2u); + rq.receivers[1] = 0x8002u; + rq.access_flags[1] = 0u; + check(wt_ffa_mem_retrieve_req_check(e, &rq, 0x8002u) == + WT_FFA_INVALID_PARAMETERS, + "a single borrower naming itself twice is INVALID_PARAMETERS"); +} + +static uint32_t get32(const uint8_t* p) +{ + return (uint32_t)p[0] | ((uint32_t)p[1] << 8) | ((uint32_t)p[2] << 16) | + ((uint32_t)p[3] << 24); +} + +/* A single-receiver retrieve request in the FF-A v1.0 layout (DEN0140 Table + * 4.17): a 32-byte header, then one 16-byte access descriptor. */ +static size_t v10_retrieve_req(uint8_t* buf, uint64_t handle, uint16_t owner, + uint16_t receiver, uint8_t perms) +{ + memset(buf, 0, 48u); + buf[WT_FFA_MEM_TXN_OFF_SENDER] = (uint8_t)(owner & 0xFFu); + buf[WT_FFA_MEM_TXN_OFF_SENDER + 1u] = (uint8_t)(owner >> 8); + put64(&buf[WT_FFA_MEM_TXN_OFF_HANDLE], handle); + put32(&buf[WT_FFA_MEM_TXN_OFF_ACC_COUNT], 1u); + buf[32] = (uint8_t)(receiver & 0xFFu); + buf[33] = (uint8_t)(receiver >> 8); + buf[32u + WT_FFA_MEM_ACC_OFF_PERMS] = perms; + return 48u; +} + +/* WT-FFA-0009 (a v1.0 caller's memory transaction descriptors use the v1.0 + * layout, DEN0077A 18.5.3 and DEN0140 4.2.1). */ +static void v10_rows(void) +{ + static const wt_ffa_mem_constituent_t cons[1] = { { 0x40000000ull, 2u } }; + wt_ffa_mem_build_t in; + wt_ffa_mem_txn_t txn; + wt_ffa_mem_retrieve_req_t rq; + uint8_t buf[128]; + uint8_t req[64]; + uint64_t size = 0u; + size_t len = 0u; + + memset(&in, 0, sizeof(in)); + in.constituents = cons; + in.constituent_count = 1u; + in.op = WT_FFA_MEM_OP_LEND; + in.sender = 0x8002u; + in.receiver = 0x8003u; + in.permissions = WT_FFA_MEM_PERM_DATA_RW; + in.version = V10; + check(wt_ffa_mem_txn_build(buf, sizeof(buf), &in, &len) == 0 && + len == 80u && get32(&buf[24]) == 0u && get32(&buf[28]) == 1u && + buf[32] == 0x03u && buf[33] == 0x80u && + get32(&buf[32u + WT_FFA_MEM_ACC_OFF_COMP_OFF]) == 48u && + get32(&buf[48]) == 2u && get32(&buf[64]) == 0x40000000u, + "v1.0: a descriptor for a v1.0 reader has no access descriptor size " + "or offset, and its access descriptors start at 32 (Table 4.17)"); + check(wt_ffa_mem_txn_validate_at(buf, len, WT_FFA_MEM_OP_LEND, 0x8002u, + V10, &txn) == 0 && + txn.access_offset == 32u && txn.access_desc_size == 16u && + txn.composite_offset == 48u && txn.total_page_count == 2u, + "v1.0: a v1.0 caller's lend is read in its own layout (18.5.3)"); + check(wt_ffa_mem_txn_validate(buf, len, WT_FFA_MEM_OP_LEND, 0x8002u, + &txn) != 0, + "v1.0: the same bytes are no Table 1.20 descriptor"); + check((wt_ffa_mem_frag_expected_at(buf, 64u, 0, V10, &size) == 1) && + (size == (uint64_t)len), + "v1.0: a first fragment in the v1.0 layout names the whole length"); + buf[24] = 1u; + check(wt_ffa_mem_txn_validate_at(buf, len, WT_FFA_MEM_OP_LEND, 0x8002u, + V10, &txn) == WT_FFA_INVALID_PARAMETERS, + "v1.0: the reserved word at offset 24 is MBZ"); + buf[24] = 0u; + buf[3] = 1u; + check(wt_ffa_mem_txn_validate_at(buf, len, WT_FFA_MEM_OP_LEND, 0x8002u, + V10, &txn) == WT_FFA_INVALID_PARAMETERS, + "v1.0: the reserved byte after the one-byte attributes is MBZ"); + in.version = 0u; + check(wt_ffa_mem_txn_build(buf, sizeof(buf), &in, &len) == 0 && + wt_ffa_mem_txn_validate_at(buf, len, WT_FFA_MEM_OP_LEND, 0x8002u, + V10, &txn) == + WT_FFA_INVALID_PARAMETERS, + "v1.0: a Table 1.20 descriptor from a v1.0 caller is refused"); + + len = v10_retrieve_req(req, 0x1234ull, 0x8002u, 0x8003u, + WT_FFA_MEM_PERM_DATA_RW); + check(wt_ffa_mem_retrieve_req_parse_at(req, len, V10, &rq) == 0 && + rq.receiver_count == 1u && rq.receivers[0] == 0x8003u && + rq.access_desc_size == 16u && rq.handle == 0x1234ull && + rq.sender == 0x8002u && + rq.permissions[0] == WT_FFA_MEM_PERM_DATA_RW, + "v1.0: a v1.0 retrieve request is read in its own layout"); + check(wt_ffa_mem_retrieve_req_parse_ex(req, len, &rq) == + WT_FFA_NOT_SUPPORTED, + "v1.0: the same request read as Table 1.20 names no access " + "descriptor size"); + check((wt_ffa_mem_frag_expected_at(req, 40u, 1, V10, &size) == 1) && + (size == (uint64_t)len), + "v1.0: a v1.0 retrieve request's first fragment names its length"); + (void)memset(req, 0xA5, sizeof(req)); + check(wt_ffa_mem_retrieve_req_build_at(req, sizeof(req), 0x1234ull, + 0x8002u, 0x8003u, + WT_FFA_MEM_PERM_DATA_RW, V10, + &len) == 0 && + len == 48u && get32(&req[24]) == 0u && get32(&req[28]) == 1u && + req[32] == 0x03u && req[33] == 0x80u && + wt_ffa_mem_retrieve_req_parse_at(req, len, V10, &rq) == 0 && + rq.receiver_count == 1u && rq.receivers[0] == 0x8003u && + rq.handle == 0x1234ull && rq.sender == 0x8002u && + rq.permissions[0] == WT_FFA_MEM_PERM_DATA_RW, + "v1.0: a retrieve request built for a v1.0 reader has the 32-byte " + "header and parses in the v1.0 layout (Table 4.17)"); +} + +/* The relayer maps and zeroes memory at its own address (VA == PA), so the + * host backs the partitions' pages with memory below the table VA limit. */ +#define RELAY_MEM_PAGES 16u +#define RELAY_POOL_PAGES 64u +#define RELAY_POOL_PA 0x0E100000ull +#define RELAY_ID_A 0x8002u +#define RELAY_ID_B 0x8003u +#define RELAY_ID_C 0x8004u +#define RELAY_RW (WT_MEM_ATTR_READ | WT_MEM_ATTR_WRITE) +/* Pages of the host backing: A's manifest-shared page, the image page every + * partition maps, A's read-write pages 2-5, A's read-only page, A's own code + * page, B's and C's own pages, the Normal world's window (10-11), A's Device + * page, and A's page no fill entry names. Every other table holds each + * partition's pages and the window as SPMC EL1-only fill entries, except + * PG_GAP, which only A's table maps. */ +#define PG_SHARED 0u +#define PG_IMAGE 1u +#define PG_FILL 2u +#define PG_RW 3u +#define PG_FILL2 5u +#define PG_RO 6u +#define PG_RX 7u +#define PG_B 8u +#define PG_DEV 12u +#define PG_C 9u +#define PG_NS 10u +#define PG_GAP 13u + +static uint8_t g_relay_pool[RELAY_POOL_PAGES * WT_TABLES_PAGE_SIZE] + __attribute__((aligned(4096))); +static uint8_t* g_mem; +static wt_memory_region_t g_relay_fill[8]; +static wt_secure_domain_t g_dom_a; +static wt_secure_domain_t g_dom_b; +static wt_secure_domain_t g_dom_c; +static int g_co_a; +static int g_co_b; +static int g_co_c; +static unsigned int g_domain_fails; +static unsigned int g_cleans; +static uint64_t g_clean_va; +static uint64_t g_clean_size; +static int g_clean_zeroed; +static int g_clean_a_access; + +#define CO_A ((struct wt_co*)(void*)&g_co_a) +#define CO_B ((struct wt_co*)(void*)&g_co_b) +#define CO_C ((struct wt_co*)(void*)&g_co_c) + +void wt_mmu_switch_ttbr0(uint64_t ttbr0) +{ + (void)ttbr0; +} + +void wt_mmu_tlbi_asid(uint64_t asid) +{ + (void)asid; +} + +/* Records the last range the relayer cleaned, and whether it still held the + * zeros when it did. */ +void wt_mmu_dcache_clean_inval(uint64_t va, uint64_t size) +{ + const uint8_t* p = (const uint8_t*)(uintptr_t)va; + uint64_t i; + + g_cleans++; + g_clean_va = va; + g_clean_size = size; + g_clean_a_access = wt_domain_page_access(g_dom_a.regions, + g_dom_a.region_count, + (uintptr_t)va); + g_clean_zeroed = 1; + for (i = 0u; i < size; i++) { + if (p[i] != 0u) { + g_clean_zeroed = 0; + } + } +} + +static unsigned int g_syncs; +static uint64_t g_sync_va; +static uint64_t g_sync_size; + +/* Records the last range made fetchable after EL0 was let execute it. */ +void wt_mmu_sync_icache(uint64_t va, uint64_t size) +{ + g_syncs++; + g_sync_va = va; + g_sync_size = size; +} + +void wt_domain_fail(int code) +{ + (void)code; + g_domain_fails++; +} + +struct wt_co* wt_spm_sp_by_ffa_id(uint16_t id) +{ + (void)id; + return NULL; +} + +/* The partition the rows have taken out of service, if any. */ +static const struct wt_co* g_aborted_co; + +int32_t wt_spm_sp_unavailable(const struct wt_co* co) +{ + return ((co != NULL) && (co == g_aborted_co)) ? WT_FFA_ABORTED : 0; +} + +/* The one RX/TX pair the rows map, standing in for every endpoint's. */ +static wt_ffa_mailbox_t g_relay_mailbox; + +/* A byte a racing Normal world rewrites in its TX buffer, landing at the + * relayer's first mailbox check: after validation, before registration. */ +static uint8_t* g_race_at; +static uint8_t g_race_to; + +int wt_spm_mailbox_overlaps(uint64_t base, uint64_t size) +{ + if (g_race_at != NULL) { + *g_race_at = g_race_to; + g_race_at = NULL; + } + return wt_ffa_mailbox_overlaps(&g_relay_mailbox, base, size); +} + +/* The versions A, B, and the Normal world negotiated, and whether B asked for + * the NS bit, as the SVC gate and the SPMC report them. */ +static uint32_t g_ver_a = WT_FFA_VERSION_1_2; +static uint32_t g_ver_b = WT_FFA_VERSION_1_2; +static uint32_t g_ver_ns = WT_FFA_VERSION_1_2; +static int g_ns_bit_b; + +uint32_t wt_spm_sp_ffa_version(const struct wt_co* co) +{ + if (co == CO_A) { + return g_ver_a; + } + return (co == CO_B) ? g_ver_b : WT_FFA_VERSION_1_2; +} + +uint32_t wt_spm_ns_ffa_version(void) +{ + return g_ver_ns; +} + +int wt_spm_sp_ffa_ns_bit(const struct wt_co* co) +{ + return wt_ffa_ns_bit_used(wt_spm_sp_ffa_version(co), + (co == CO_B) ? g_ns_bit_b : 0); +} + +static uintptr_t page(unsigned int i); +static int cleaned(unsigned int pg); + +size_t wt_platform_sp_shared_regions(wt_memory_region_t* regions, size_t max) +{ + if ((regions == NULL) || (max < 1u)) { + return 0u; + } + regions[0].base = page(PG_IMAGE); + regions[0].size = WT_TABLES_PAGE_SIZE; + regions[0].attributes = WT_MEM_ATTR_READ | WT_MEM_ATTR_EXEC; + return 1u; +} + +static uint8_t* low_pages(size_t size) +{ + static const uint64_t hints[] = { + 0x10000000ull, 0x800000000ull, 0x2000000000ull, 0x40000000ull + }; + void* p; + size_t i; + + for (i = 0u; i < sizeof(hints) / sizeof(hints[0]); i++) { + p = mmap((void*)(uintptr_t)hints[i], size, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANON, -1, 0); + if ((p != MAP_FAILED) && + (((uint64_t)(uintptr_t)p + size) <= WT_TABLES_VA_LIMIT)) { + return (uint8_t*)p; + } + if (p != MAP_FAILED) { + (void)munmap(p, size); + } + } + return NULL; +} + +static uintptr_t page(unsigned int i) +{ + return (uintptr_t)g_mem + ((uintptr_t)i * WT_TABLES_PAGE_SIZE); +} + +static void set_region(wt_memory_region_t* r, unsigned int first, + unsigned int pages, uint32_t attributes) +{ + r->base = page(first); + r->size = (size_t)pages * WT_TABLES_PAGE_SIZE; + r->attributes = attributes; +} + +static int relay_reset(void) +{ + (void)memset(g_mem, 0, (size_t)RELAY_MEM_PAGES * WT_TABLES_PAGE_SIZE); + (void)memset(&g_dom_a, 0, sizeof(g_dom_a)); + (void)memset(&g_dom_b, 0, sizeof(g_dom_b)); + (void)memset(&g_dom_c, 0, sizeof(g_dom_c)); + set_region(&g_dom_a.regions[0], PG_FILL, 4u, RELAY_RW); + set_region(&g_dom_a.regions[1], PG_RO, 1u, WT_MEM_ATTR_READ); + set_region(&g_dom_a.regions[2], PG_SHARED, 1u, + RELAY_RW | WT_MEMORY_ATTR_SHARED); + set_region(&g_dom_a.regions[3], PG_IMAGE, 1u, + WT_MEM_ATTR_READ | WT_MEM_ATTR_EXEC); + set_region(&g_dom_a.regions[4], PG_RX, 1u, + WT_MEM_ATTR_READ | WT_MEM_ATTR_EXEC); + set_region(&g_dom_a.regions[5], PG_DEV, 1u, + RELAY_RW | WT_MEM_ATTR_DEVICE); + set_region(&g_dom_a.regions[6], PG_GAP, 1u, RELAY_RW); + g_dom_a.region_count = 7u; + set_region(&g_dom_b.regions[0], PG_B, 1u, RELAY_RW); + g_dom_b.region_count = 1u; + set_region(&g_dom_c.regions[0], PG_C, 1u, RELAY_RW); + g_dom_c.region_count = 1u; + set_region(&g_relay_fill[0], PG_FILL, 1u, + RELAY_RW | WT_DOMAIN_FILL_SHARED); + set_region(&g_relay_fill[1], PG_FILL2, 1u, + RELAY_RW | WT_DOMAIN_FILL_SHARED); + set_region(&g_relay_fill[2], PG_RW, 2u, RELAY_RW | WT_DOMAIN_FILL_SHARED); + set_region(&g_relay_fill[3], PG_RO, 1u, RELAY_RW | WT_DOMAIN_FILL_SHARED); + set_region(&g_relay_fill[4], PG_RX, 1u, RELAY_RW | WT_DOMAIN_FILL_SHARED); + set_region(&g_relay_fill[5], PG_B, 1u, RELAY_RW | WT_DOMAIN_FILL_SHARED); + set_region(&g_relay_fill[6], PG_C, 1u, RELAY_RW | WT_DOMAIN_FILL_SHARED); + set_region(&g_relay_fill[7], PG_NS, 2u, + RELAY_RW | WT_TABLES_ATTR_NS | WT_TABLES_ATTR_NG); + g_domain_fails = 0u; + if (wt_domain_init(g_relay_fill, 8u, g_relay_pool, RELAY_POOL_PA, + sizeof(g_relay_pool)) == 0u) { + return 0; + } + wt_arch_program_sp_thread_domain(g_dom_a.regions, g_dom_a.region_count); + wt_arch_program_sp_thread_domain(g_dom_b.regions, g_dom_b.region_count); + wt_arch_program_sp_thread_domain(g_dom_c.regions, g_dom_c.region_count); + wt_spm_mem_init(); + return (wt_spm_mem_bind(RELAY_ID_A, CO_A, &g_dom_a) == 0) && + (wt_spm_mem_bind(RELAY_ID_B, CO_B, &g_dom_b) == 0) && + (wt_spm_mem_bind(RELAY_ID_C, CO_C, &g_dom_c) == 0) && + (g_domain_fails == 0u); +} + +/* A descriptor sending n constituents from A to B with the given memory + * region attributes. */ +static int relay_build_attrs(uint8_t* desc, size_t cap, wt_ffa_mem_op_t op, + const wt_ffa_mem_constituent_t* c, uint32_t n, + uint8_t perms, uint32_t flags, uint16_t attributes, + size_t* len) +{ + wt_ffa_mem_build_t in; + + (void)memset(&in, 0, sizeof(in)); + in.constituents = c; + in.constituent_count = n; + in.op = op; + in.sender = RELAY_ID_A; + in.receiver = RELAY_ID_B; + in.permissions = perms; + in.flags = flags; + in.attributes = attributes; + in.access_desc_size = (uint8_t)WT_FFA_MEM_ACCESS_SIZE; + return wt_ffa_mem_txn_build(desc, cap, &in, len); +} + +/* A descriptor sending n constituents from A to B. */ +static int relay_build(uint8_t* desc, size_t cap, wt_ffa_mem_op_t op, + const wt_ffa_mem_constituent_t* c, uint32_t n, + uint8_t perms, uint32_t flags, size_t* len) +{ + return relay_build_attrs(desc, cap, op, c, n, perms, flags, 0u, len); +} + +/* Send n constituents from A to B; *ret gets the relayer's answer. */ +/* What a receiver states when the memory becomes its alone (a donate, or a + * lend to one borrower): its data access and, DEN0140 1.10.3 item 2, the + * instruction access it wants, which this relayer only ever grants as + * not-executable. A share's or a multi-borrower lend's borrower leaves the + * instruction access unspecified (item 1). */ +#define OWN_RW (uint8_t)(WT_FFA_MEM_PERM_DATA_RW | WT_FFA_MEM_PERM_INSTR_NX) +#define OWN_RO (uint8_t)(WT_FFA_MEM_PERM_DATA_RO | WT_FFA_MEM_PERM_INSTR_NX) + +static uint64_t relay_send(wt_ffa_mem_op_t op, const wt_ffa_mem_constituent_t* c, + uint32_t n, uint8_t perms, uint32_t flags, int* ret) +{ + uint8_t desc[256]; + uint64_t h = 0u; + size_t len = 0u; + + *ret = relay_build(desc, sizeof(desc), op, c, n, perms, flags, &len); + if (*ret == 0) { + *ret = wt_spm_mem_share(desc, len, op, RELAY_ID_A, &h); + } + return h; +} + +static int relay_retrieve(uint64_t h, uint8_t perms, uint32_t flags) +{ + uint8_t req[128]; + uint8_t resp[256]; + size_t len = 0u; + size_t resp_len = 0u; + int ret; + + ret = wt_ffa_mem_retrieve_req_build(req, sizeof(req), h, RELAY_ID_A, + RELAY_ID_B, perms, &len); + if (ret == 0) { + put32(&req[WT_FFA_MEM_TXN_OFF_FLAGS], flags); + ret = wt_spm_mem_retrieve(req, len, RELAY_ID_B, resp, sizeof(resp), + &resp_len); + } + return ret; +} + +static int relay_relinquish(uint64_t h, uint32_t flags) +{ + uint8_t rel[32]; + size_t len = 0u; + int ret; + + ret = wt_ffa_mem_relinquish_build(rel, sizeof(rel), h, flags, RELAY_ID_B, + &len); + if (ret == 0) { + ret = wt_spm_mem_relinquish(rel, len, RELAY_ID_B); + } + return ret; +} + +static int access_of(const wt_secure_domain_t* d, unsigned int pg) +{ + return wt_domain_page_access(d->regions, d->region_count, page(pg)); +} + +/* What B's table holds at a page it does not reach: 1 for an SPMC EL1-only + * entry, -1 for anything else. Probed by a grant and its undo. */ +static int b_entry(unsigned int pg) +{ + int was = -1; + + if (wt_domain_grant(g_dom_b.regions, g_dom_b.region_count, page(pg), 1u, + WT_MEM_ATTR_READ, &was) != WT_TABLES_OK) { + return -1; + } + if (wt_domain_revoke(g_dom_b.regions, g_dom_b.region_count, page(pg), 1u, + was) != WT_TABLES_OK) { + return -1; + } + return was; +} + +/* WT-FFA-0009 (the SPMC relayer: a lend maps into the borrower on retrieve, + * a relinquish unmaps it, a reclaim hands it back to the owner). */ +static void relay_rows(void) +{ + wt_ffa_mem_constituent_t c[2]; + uint64_t h; + int ret = 0; + + g_mem = low_pages((size_t)RELAY_MEM_PAGES * WT_TABLES_PAGE_SIZE); + check(g_mem != NULL, "the host backs the relayer's pages below the table VA limit"); + if (g_mem == NULL) { + return; + } + check(relay_reset(), "two bound partitions over real tables"); + + c[0].address = page(PG_RW); + c[0].page_count = 2u; + h = relay_send(WT_FFA_MEM_OP_LEND, c, 1u, WT_FFA_MEM_PERM_DATA_RW, 0u, + &ret); + check(ret == 0 && access_of(&g_dom_a, PG_RW) == WT_DOMAIN_ACCESS_NONE && + access_of(&g_dom_a, PG_RW + 2u) == WT_DOMAIN_ACCESS_RW, + "relayer: a lend takes the lent pages, and only those, from the owner"); + check(relay_retrieve(h, OWN_RW, 0u) == 0 && + access_of(&g_dom_b, PG_RW) == WT_DOMAIN_ACCESS_RW && + access_of(&g_dom_b, PG_RW + 1u) == WT_DOMAIN_ACCESS_RW, + "relayer: a retrieve maps the lent pages into the borrower"); + check(relay_relinquish(h, 0u) == 0 && + access_of(&g_dom_b, PG_RW) == WT_DOMAIN_ACCESS_NONE && + b_entry(PG_RW) == 1, + "relayer: a relinquish unmaps them from the borrower"); + c[1].address = page(PG_RW + 2u); + c[1].page_count = 1u; + g_aborted_co = CO_B; + (void)relay_send(WT_FFA_MEM_OP_LEND, &c[1], 1u, WT_FFA_MEM_PERM_DATA_RW, + 0u, &ret); + g_aborted_co = NULL; + check(ret == WT_FFA_ABORTED && access_of(&g_dom_a, PG_RW + 2u) == + WT_DOMAIN_ACCESS_RW, + "relayer: a transaction naming a borrower that has aborted is ABORTED and takes nothing"); + check(wt_spm_mem_reclaim(h, RELAY_ID_A, 0u) == 0 && + access_of(&g_dom_a, PG_RW) == WT_DOMAIN_ACCESS_RW && + access_of(&g_dom_a, PG_RW + 1u) == WT_DOMAIN_ACCESS_RW, + "relayer: a reclaim gives the owner its access back"); + check(relay_retrieve(h, OWN_RW, 0u) != 0, + "relayer: a reclaimed handle cannot be retrieved"); + check(g_domain_fails == 0u, "relayer: no domain operation failed closed"); +} + +/* WT-FFA-0009 (only memory the sender owns outright may be sent, 10.10). */ +static void relay_owner_rows(void) +{ + wt_ffa_mem_constituent_t c[1]; + clock_t t0; + int ret = 0; + + if ((g_mem == NULL) || !relay_reset()) { + check(0, "owner: fixture"); + return; + } + c[0].address = page(PG_IMAGE); + c[0].page_count = 1u; + (void)relay_send(WT_FFA_MEM_OP_SHARE, c, 1u, WT_FFA_MEM_PERM_DATA_RO, 0u, + &ret); + check(ret == WT_FFA_DENIED, + "owner: the image every partition maps is DENIED, though the sender reaches it"); + c[0].address = page(PG_SHARED); + (void)relay_send(WT_FFA_MEM_OP_LEND, c, 1u, WT_FFA_MEM_PERM_DATA_RW, 0u, + &ret); + check(ret == WT_FFA_DENIED && + access_of(&g_dom_a, PG_SHARED) == WT_DOMAIN_ACCESS_RW, + "owner: a region the manifest marks shared is DENIED and stays mapped"); + c[0].address = page(PG_DEV); + c[0].page_count = 1u; + (void)relay_send(WT_FFA_MEM_OP_LEND, c, 1u, WT_FFA_MEM_PERM_DATA_RW, 0u, + &ret); + check(ret == WT_FFA_DENIED && + wt_spm_mem_in_transaction(page(PG_DEV), WT_TABLES_PAGE_SIZE) == 0, + "owner: a lend of the sender's own Device page is DENIED"); + (void)relay_send(WT_FFA_MEM_OP_DONATE, c, 1u, + WT_FFA_MEM_PERM_DATA_NOT_SPEC, 0u, &ret); + check(ret == WT_FFA_DENIED && + wt_spm_mem_in_transaction(page(PG_DEV), WT_TABLES_PAGE_SIZE) == 0, + "owner: so is a donate of it"); + c[0].address = page(PG_RW + 2u); + c[0].page_count = 0xFFFFFFFFu; + t0 = clock(); + (void)relay_send(WT_FFA_MEM_OP_LEND, c, 1u, WT_FFA_MEM_PERM_DATA_RO, 0u, + &ret); + check(ret == WT_FFA_DENIED && ((clock() - t0) < CLOCKS_PER_SEC), + "owner: a range running past the sender's pages is DENIED at the first page it lacks"); +} + +/* B's retrieve of h asking for the given attributes; *resp_attrs gets the + * attributes the response reports. */ +static int relay_retrieve_attrs(uint64_t h, uint16_t attributes, uint8_t perms, + uint16_t* resp_attrs) +{ + uint8_t req[128]; + uint8_t resp[256]; + size_t len = 0u; + size_t resp_len = 0u; + int ret; + + ret = wt_ffa_mem_retrieve_req_build(req, sizeof(req), h, RELAY_ID_A, + RELAY_ID_B, perms, &len); + if (ret == 0) { + req[WT_FFA_MEM_TXN_OFF_ATTRS] = (uint8_t)(attributes & 0xFFu); + req[WT_FFA_MEM_TXN_OFF_ATTRS + 1u] = (uint8_t)(attributes >> 8); + ret = wt_spm_mem_retrieve(req, len, RELAY_ID_B, resp, sizeof(resp), + &resp_len); + } + if (ret == 0) { + *resp_attrs = (uint16_t)(resp[WT_FFA_MEM_TXN_OFF_ATTRS] | + (resp[WT_FFA_MEM_TXN_OFF_ATTRS + 1u] << 8)); + } + return ret; +} + +/* WT-FFA-0009 (a lend or share keeps the attributes its borrowers map with, + * holds each retrieve request to them, and reports them, 1.10.4.2). */ +static void relay_attr_rows(void) +{ + wt_ffa_mem_constituent_t c[1]; + uint8_t desc[256]; + uint64_t h = 0u; + uint16_t got = 0u; + size_t len = 0u; + int ret = 0; + + if ((g_mem == NULL) || !relay_reset()) { + check(0, "attr: fixture"); + return; + } + c[0].address = page(PG_RW); + c[0].page_count = 1u; + (void)relay_build_attrs(desc, sizeof(desc), WT_FFA_MEM_OP_SHARE, c, 1u, + WT_FFA_MEM_PERM_DATA_RW, 0u, 0x27u, &len); + check(wt_spm_mem_share(desc, len, WT_FFA_MEM_OP_SHARE, RELAY_ID_A, &h) == + WT_FFA_INVALID_PARAMETERS, + "attr: a share of non-cacheable memory, which the relayer cannot map, is INVALID_PARAMETERS"); + (void)relay_build_attrs(desc, sizeof(desc), WT_FFA_MEM_OP_SHARE, c, 1u, + WT_FFA_MEM_PERM_DATA_RW, 0u, 0x2Eu, &len); + check(wt_spm_mem_share(desc, len, WT_FFA_MEM_OP_SHARE, RELAY_ID_A, &h) == + WT_FFA_DENIED, + "attr: a share of outer-shareable memory is DENIED"); + (void)relay_build_attrs(desc, sizeof(desc), WT_FFA_MEM_OP_SHARE, c, 1u, + WT_FFA_MEM_PERM_DATA_RW, 0u, 0x2Fu, &len); + ret = wt_spm_mem_share(desc, len, WT_FFA_MEM_OP_SHARE, RELAY_ID_A, &h); + check(ret == 0, + "attr: neither refused share held the page, and a Normal write-back inner-shareable share of it is accepted"); + check(relay_retrieve_attrs(h, 0x27u, WT_FFA_MEM_PERM_DATA_RW, &got) == WT_FFA_INVALID_PARAMETERS && + access_of(&g_dom_b, PG_RW) == WT_DOMAIN_ACCESS_NONE, + "attr: a retrieve asking for non-cacheable memory is INVALID_PARAMETERS and maps nothing"); + check(relay_retrieve_attrs(h, 0x2Eu, WT_FFA_MEM_PERM_DATA_RW, &got) == WT_FFA_DENIED && + access_of(&g_dom_b, PG_RW) == WT_DOMAIN_ACCESS_NONE, + "attr: a retrieve asking for more than the lender gave is DENIED and maps nothing"); + check(relay_retrieve_attrs(h, 0x2Fu, WT_FFA_MEM_PERM_DATA_RW, &got) == 0 && got == 0x2Fu && + access_of(&g_dom_b, PG_RW) == WT_DOMAIN_ACCESS_RW, + "attr: a retrieve asking for the lender's attributes maps them and reports them"); + check(relay_relinquish(h, 0u) == 0 && + wt_spm_mem_reclaim(h, RELAY_ID_A, 0u) == 0, + "attr: the share ends"); + h = relay_send(WT_FFA_MEM_OP_LEND, c, 1u, WT_FFA_MEM_PERM_DATA_RW, 0u, + &ret); + got = 0u; + check(ret == 0 && relay_retrieve_attrs(h, 0u, OWN_RW, &got) == 0 && got == 0x2Fu && + relay_relinquish(h, 0u) == 0 && + wt_spm_mem_reclaim(h, RELAY_ID_A, 0u) == 0, + "attr: a lend to one borrower reports the Normal write-back inner-shareable mapping the relayer chose"); + check(g_domain_fails == 0u, "attr: no domain operation failed closed"); +} + +/* WT-FFA-0009 (relinquish holds the zero flag against the access the borrower + * was given at retrieve, Table 2.25 bit[0]). */ +static void relay_perm_rows(void) +{ + wt_ffa_mem_constituent_t c[1]; + uint8_t* p; + uint64_t h; + int ret = 0; + + if ((g_mem == NULL) || !relay_reset()) { + check(0, "perm: fixture"); + return; + } + p = (uint8_t*)page(PG_RW); + c[0].address = page(PG_RW); + c[0].page_count = 1u; + h = relay_send(WT_FFA_MEM_OP_LEND, c, 1u, WT_FFA_MEM_PERM_DATA_RW, 0u, + &ret); + p[0] = 0xA5u; + check(ret == 0 && relay_retrieve(h, OWN_RO, 0u) == 0 && + access_of(&g_dom_b, PG_RW) == WT_DOMAIN_ACCESS_RO, + "perm: a borrower granted read-write may retrieve read-only"); + check(relay_relinquish(h, WT_FFA_MEM_RELINQ_FLAG_ZERO) == WT_FFA_DENIED && + p[0] == 0xA5u && access_of(&g_dom_b, PG_RW) == WT_DOMAIN_ACCESS_RO, + "perm: a borrower that retrieved read-only is DENIED the zero flag at relinquish"); + check(relay_relinquish(h, 0u) == 0 && + wt_spm_mem_reclaim(h, RELAY_ID_A, 0u) == 0 && p[0] == 0xA5u, + "perm: it relinquishes without the flag and the owner reclaims the memory intact"); +} + +/* WT-FFA-0009 (each region goes back to exactly the entry it replaced, in the + * borrower's table and in the owner's). */ +static void relay_region_rows(void) +{ + wt_ffa_mem_constituent_t c[3]; + uint64_t h; + uint32_t attrs = 0u; + size_t used; + int ret = 0; + + if ((g_mem == NULL) || !relay_reset()) { + check(0, "region: fixture"); + return; + } + c[0].address = page(PG_FILL); + c[0].page_count = 1u; + c[1].address = page(PG_RW); + c[1].page_count = 1u; + h = relay_send(WT_FFA_MEM_OP_LEND, c, 2u, WT_FFA_MEM_PERM_DATA_RW, 0u, + &ret); + check(ret == 0 && relay_retrieve(h, OWN_RW, 0u) == 0 && + relay_relinquish(h, 0u) == 0 && b_entry(PG_FILL) == 1 && + b_entry(PG_RW) == 1 && + wt_spm_mem_reclaim(h, RELAY_ID_A, 0u) == 0, + "region: a relinquish puts back the SPMC's entry for every region"); + + c[0].address = page(PG_GAP); + used = wt_domain_pool_pages_used(); + h = relay_send(WT_FFA_MEM_OP_LEND, c, 1u, WT_FFA_MEM_PERM_DATA_RW, 0u, + &ret); + check(ret == 0 && + relay_retrieve(h, OWN_RW, 0u) == WT_FFA_NO_MEMORY && + access_of(&g_dom_b, PG_GAP) == WT_DOMAIN_ACCESS_NONE && + wt_domain_pool_pages_used() == used && + wt_spm_mem_reclaim(h, RELAY_ID_A, 0u) == 0, + "region: a page the borrower's table does not map is never retrieved, and no table page is taken for it"); + + c[0].address = page(PG_FILL); + c[1].address = page(PG_RW); + c[2].address = page(PG_GAP); + c[2].page_count = 1u; + h = relay_send(WT_FFA_MEM_OP_LEND, c, 3u, WT_FFA_MEM_PERM_DATA_RW, 0u, + &ret); + check(ret == 0 && + relay_retrieve(h, OWN_RW, 0u) == WT_FFA_NO_MEMORY && + b_entry(PG_FILL) == 1 && b_entry(PG_RW) == 1 && + access_of(&g_dom_b, PG_GAP) == WT_DOMAIN_ACCESS_NONE && + wt_spm_mem_reclaim(h, RELAY_ID_A, 0u) == 0, + "region: a retrieve that cannot map its last region puts back each earlier one as it was"); + + c[0].address = page(PG_RW); + c[0].page_count = 1u; + c[1].address = page(PG_RO); + c[1].page_count = 1u; + h = relay_send(WT_FFA_MEM_OP_LEND, c, 2u, WT_FFA_MEM_PERM_DATA_RO, 0u, + &ret); + check(ret == 0 && access_of(&g_dom_a, PG_RW) == WT_DOMAIN_ACCESS_NONE && + access_of(&g_dom_a, PG_RO) == WT_DOMAIN_ACCESS_NONE && + wt_spm_mem_reclaim(h, RELAY_ID_A, 0u) == 0 && + access_of(&g_dom_a, PG_RW) == WT_DOMAIN_ACCESS_RW && + access_of(&g_dom_a, PG_RO) == WT_DOMAIN_ACCESS_RO, + "region: a reclaim gives the owner back each region's own access"); + c[0].address = page(PG_RX); + h = relay_send(WT_FFA_MEM_OP_LEND, c, 1u, WT_FFA_MEM_PERM_DATA_RO, 0u, + &ret); + check(ret == 0 && access_of(&g_dom_a, PG_RX) == WT_DOMAIN_ACCESS_NONE && + relay_retrieve(h, OWN_RO, 0u) == 0 && + relay_relinquish(h, 0u) == 0 && + wt_spm_mem_reclaim(h, RELAY_ID_A, 0u) == 0 && + wt_domain_get_permissions(g_dom_a.regions, g_dom_a.region_count, + page(PG_RX), &attrs) == WT_TABLES_OK && + attrs == (WT_MEM_ATTR_READ | WT_MEM_ATTR_EXEC), + "region: a reclaim gives the owner back its own code page executable"); + check(g_domain_fails == 0u, "region: no domain operation failed closed"); +} + +/* WT-FFA-0009 (a mapped RX/TX pair is never sent in a memory transaction, and + * memory a transaction covers is never mapped as one, DEN0077A 7.2.2.2). */ +static void relay_mailbox_rows(void) +{ + wt_ffa_mem_constituent_t c[1]; + uint64_t h; + int ret = 0; + + if ((g_mem == NULL) || !relay_reset()) { + check(0, "mailbox: fixture"); + return; + } + (void)memset(&g_relay_mailbox, 0, sizeof(g_relay_mailbox)); + (void)wt_ffa_mailbox_map(&g_relay_mailbox, page(PG_RW), page(PG_RW + 1u), + 1u); + c[0].address = page(PG_RW + 1u); + c[0].page_count = 1u; + (void)relay_send(WT_FFA_MEM_OP_LEND, c, 1u, WT_FFA_MEM_PERM_DATA_RW, 0u, + &ret); + check(ret == WT_FFA_DENIED && + access_of(&g_dom_a, PG_RW + 1u) == WT_DOMAIN_ACCESS_RW, + "mailbox: a lend of a mapped RX buffer is DENIED and the owner keeps it"); + c[0].address = page(PG_RW); + (void)relay_send(WT_FFA_MEM_OP_DONATE, c, 1u, + WT_FFA_MEM_PERM_DATA_NOT_SPEC, 0u, &ret); + check(ret == WT_FFA_DENIED, + "mailbox: a donate of a mapped TX buffer is DENIED"); + (void)relay_send(WT_FFA_MEM_OP_SHARE, c, 1u, WT_FFA_MEM_PERM_DATA_RO, 0u, + &ret); + check(ret == WT_FFA_DENIED, "mailbox: so is a share of it"); + (void)wt_ffa_mailbox_unmap(&g_relay_mailbox); + h = relay_send(WT_FFA_MEM_OP_LEND, c, 1u, WT_FFA_MEM_PERM_DATA_RW, 0u, + &ret); + check(ret == 0 && + wt_spm_mem_in_transaction(page(PG_RW), WT_TABLES_PAGE_SIZE) != 0 && + wt_spm_mem_in_transaction(page(PG_RW + 1u), WT_TABLES_PAGE_SIZE) == 0, + "mailbox: once unmapped the page is lent, and the lend keeps it from any pair"); + check(wt_spm_mem_reclaim(h, RELAY_ID_A, 0u) == 0 && + wt_spm_mem_in_transaction(page(PG_RW), WT_TABLES_PAGE_SIZE) == 0, + "mailbox: a reclaim frees it for one again"); +} + +/* WT-FFA-0009 (a donate hands over no more data access than the owner had on + * every page of it, whatever order its regions come in, 1.10.2 item 2). */ +static void relay_donate_rows(void) +{ + wt_ffa_mem_constituent_t c[2]; + uint64_t h; + int ret = 0; + int order; + + for (order = 0; order < 2; order++) { + if ((g_mem == NULL) || !relay_reset()) { + check(0, "donate: fixture"); + return; + } + c[order].address = page(PG_RW); + c[order].page_count = 1u; + c[1 - order].address = page(PG_RO); + c[1 - order].page_count = 1u; + h = relay_send(WT_FFA_MEM_OP_DONATE, c, 2u, + WT_FFA_MEM_PERM_DATA_NOT_SPEC, 0u, &ret); + check(ret == 0 && + relay_retrieve(h, OWN_RW, 0u) == WT_FFA_DENIED && + access_of(&g_dom_b, PG_RW) == WT_DOMAIN_ACCESS_NONE && + access_of(&g_dom_b, PG_RO) == WT_DOMAIN_ACCESS_NONE, + (order == 0) + ? "donate: a read-write then read-only donate is DENIED to a read-write retrieve" + : "donate: a read-only then read-write donate is DENIED to a read-write retrieve"); + check(relay_retrieve(h, OWN_RO, 0u) == 0 && + access_of(&g_dom_b, PG_RW) == WT_DOMAIN_ACCESS_RO && + access_of(&g_dom_b, PG_RO) == WT_DOMAIN_ACCESS_RO, + "donate: the receiver maps every page read-only"); + } + check(g_domain_fails == 0u, "donate: no domain operation failed closed"); +} + +/* Append C as a second receiver, with B's permissions, to the send descriptor + * relay_build laid out in desc; returns the new length. */ +static size_t add_receiver_c(uint8_t* desc, size_t len) +{ + const size_t acc = WT_FFA_MEM_TXN_HDR_SIZE; + const size_t comp = acc + WT_FFA_MEM_ACCESS_SIZE; + size_t i; + + for (i = len; i > comp; i--) { + desc[i - 1u + WT_FFA_MEM_ACCESS_SIZE] = desc[i - 1u]; + } + memcpy(&desc[comp], &desc[acc], WT_FFA_MEM_ACCESS_SIZE); + desc[comp + WT_FFA_MEM_ACC_OFF_RECEIVER] = (uint8_t)(RELAY_ID_C & 0xFFu); + desc[comp + WT_FFA_MEM_ACC_OFF_RECEIVER + 1u] = (uint8_t)(RELAY_ID_C >> 8); + put32(&desc[acc + WT_FFA_MEM_ACC_OFF_COMP_OFF], + (uint32_t)(comp + WT_FFA_MEM_ACCESS_SIZE)); + put32(&desc[comp + WT_FFA_MEM_ACC_OFF_COMP_OFF], + (uint32_t)(comp + WT_FFA_MEM_ACCESS_SIZE)); + put32(&desc[WT_FFA_MEM_TXN_OFF_ACC_COUNT], 2u); + return len + WT_FFA_MEM_ACCESS_SIZE; +} + +/* Append a second endpoint memory access descriptor naming id with perms. */ +static size_t add_receiver_as(uint8_t* desc, size_t len, uint16_t id, + uint8_t perms) +{ + const size_t second = WT_FFA_MEM_TXN_HDR_SIZE + WT_FFA_MEM_ACCESS_SIZE; + + len = add_receiver_c(desc, len); + desc[second + WT_FFA_MEM_ACC_OFF_RECEIVER] = (uint8_t)(id & 0xFFu); + desc[second + WT_FFA_MEM_ACC_OFF_RECEIVER + 1u] = (uint8_t)(id >> 8); + desc[second + WT_FFA_MEM_ACC_OFF_PERMS] = perms; + return len; +} + +/* A sends page pg to B with op, also naming itself with self_perms: first in + * the list when self_first, else second. self_perms of 0xFF names only A. */ +static int relay_self_send(wt_ffa_mem_op_t op, int self_first, + uint8_t self_perms, uint8_t b_perms, + unsigned int pg, uint64_t* h) +{ + wt_ffa_mem_constituent_t c[1]; + wt_ffa_mem_build_t in; + uint8_t desc[256]; + size_t len = 0u; + int ret; + + c[0].address = page(pg); + c[0].page_count = 1u; + (void)memset(&in, 0, sizeof(in)); + in.constituents = c; + in.constituent_count = 1u; + in.op = op; + in.sender = RELAY_ID_A; + in.receiver = (self_first != 0) ? RELAY_ID_A : RELAY_ID_B; + in.permissions = (self_first != 0) ? self_perms : b_perms; + in.attributes = (op == WT_FFA_MEM_OP_SHARE) ? 0x2Fu : 0u; + in.access_desc_size = (uint8_t)WT_FFA_MEM_ACCESS_SIZE; + ret = wt_ffa_mem_txn_build(desc, sizeof(desc), &in, &len); + if ((ret == 0) && (b_perms != 0xFFu)) { + len = add_receiver_as(desc, len, + (self_first != 0) ? RELAY_ID_B : RELAY_ID_A, + (self_first != 0) ? b_perms : self_perms); + } + if (ret == 0) { + ret = wt_spm_mem_share(desc, len, op, RELAY_ID_A, h); + } + return ret; +} + +/* WT-FFA-0009 (a share may name the lender itself with the lower data access + * it keeps meanwhile, 1.11.3.1 and 2.3.1.2 item 10). */ +static void relay_self_rows(void) +{ + uint64_t h = 0u; + + if ((g_mem == NULL) || !relay_reset()) { + check(0, "self: fixture"); + return; + } + check(relay_self_send(WT_FFA_MEM_OP_SHARE, 0, WT_FFA_MEM_PERM_DATA_RO, + WT_FFA_MEM_PERM_DATA_RW, PG_RW, &h) == 0 && + access_of(&g_dom_a, PG_RW) == WT_DOMAIN_ACCESS_RO && + relay_retrieve(h, WT_FFA_MEM_PERM_DATA_RW, 0u) == 0 && + access_of(&g_dom_b, PG_RW) == WT_DOMAIN_ACCESS_RW, + "self: a share naming the lender read-only leaves it reading and the borrower writing"); + check(wt_domain_set_permissions(g_dom_a.regions, g_dom_a.region_count, + page(PG_RW), 1u, RELAY_RW) != + WT_TABLES_OK && + access_of(&g_dom_a, PG_RW) == WT_DOMAIN_ACCESS_RO, + "self: the lender cannot re-permission the page while it is shared"); + check(relay_relinquish(h, 0u) == 0 && + wt_spm_mem_reclaim(h, RELAY_ID_A, 0u) == 0 && + access_of(&g_dom_a, PG_RW) == WT_DOMAIN_ACCESS_RW, + "self: a reclaim gives the lender its write access back"); + check(relay_self_send(WT_FFA_MEM_OP_SHARE, 1, WT_FFA_MEM_PERM_DATA_RO, + WT_FFA_MEM_PERM_DATA_RW, PG_RW, &h) == 0 && + relay_retrieve(h, WT_FFA_MEM_PERM_DATA_RW, 0u) == 0 && + relay_relinquish(h, 0u) == 0 && + wt_spm_mem_reclaim(h, RELAY_ID_A, 0u) == 0 && + access_of(&g_dom_a, PG_RW) == WT_DOMAIN_ACCESS_RW, + "self: the borrower keeps the access it was given when the lender comes first"); + g_cleans = 0u; + check(relay_self_send(WT_FFA_MEM_OP_SHARE, 0, WT_FFA_MEM_PERM_DATA_RO, + WT_FFA_MEM_PERM_DATA_RW, PG_RW, &h) == 0 && + wt_spm_mem_reclaim(h, RELAY_ID_A, WT_FFA_MEM_RELINQ_FLAG_ZERO) == 0 && + cleaned(PG_RW) && g_clean_a_access == WT_DOMAIN_ACCESS_NONE && + access_of(&g_dom_a, PG_RW) == WT_DOMAIN_ACCESS_RW, + "self: a reclaim that zeroes a page the lender kept reading wipes it with no EL0 access left, then gives write access back"); + check(relay_self_send(WT_FFA_MEM_OP_SHARE, 0, WT_FFA_MEM_PERM_DATA_RW, + WT_FFA_MEM_PERM_DATA_RO, PG_RO, &h) == + WT_FFA_DENIED && + access_of(&g_dom_a, PG_RO) == WT_DOMAIN_ACCESS_RO, + "self: a lender cannot name itself more access than it has"); + check(relay_self_send(WT_FFA_MEM_OP_SHARE, 0, + (uint8_t)(WT_FFA_MEM_PERM_DATA_RO | + WT_FFA_MEM_PERM_INSTR_NX), + WT_FFA_MEM_PERM_DATA_RW, PG_RW, &h) == + WT_FFA_INVALID_PARAMETERS, + "self: its instruction access is the relayer's and stays unspecified"); + check(relay_self_send(WT_FFA_MEM_OP_LEND, 0, WT_FFA_MEM_PERM_DATA_RO, + WT_FFA_MEM_PERM_DATA_RW, PG_RW, &h) == + WT_FFA_INVALID_PARAMETERS && + access_of(&g_dom_a, PG_RW) == WT_DOMAIN_ACCESS_RW, + "self: only a share names the lender"); + check(relay_self_send(WT_FFA_MEM_OP_SHARE, 1, WT_FFA_MEM_PERM_DATA_RO, + 0xFFu, PG_RW, &h) == WT_FFA_INVALID_PARAMETERS, + "self: a share naming only the lender has no borrower"); + check(g_domain_fails == 0u, "self: no domain operation failed closed"); +} + +/* WT-FFA-0009 (FFA_MEM_PERM_GET/SET, DEN0140 2.8 and 2.9: a partition + * re-permissions its own pages, never ones the SPMC writes at S-EL1). */ +static void relay_perm_set_rows(void) +{ + uint32_t perm = 0u; + + if ((g_mem == NULL) || !relay_reset()) { + check(0, "perm set: fixture"); + return; + } + (void)memset(&g_relay_mailbox, 0, sizeof(g_relay_mailbox)); + check(wt_spm_mem_perm_set(&g_dom_a, &g_relay_mailbox, page(PG_IMAGE), 1u, + WT_FFA_PERM_DATA_RO) == WT_FFA_INVALID_PARAMETERS, + "perm set: code every partition runs is INVALID_PARAMETERS (Table 2.41)"); + check(wt_spm_mem_rxtx_ok(&g_dom_a, page(PG_SHARED)) == 0, + "rxtx: memory its manifest shares with another partition is never its RX/TX buffer"); + check(wt_spm_mem_perm_set(&g_dom_a, &g_relay_mailbox, page(PG_SHARED), 1u, + WT_FFA_PERM_DATA_NONE) == + WT_FFA_INVALID_PARAMETERS && + wt_spm_mem_perm_set(&g_dom_a, &g_relay_mailbox, page(PG_SHARED), 1u, + WT_FFA_PERM_DATA_RO) == + WT_FFA_INVALID_PARAMETERS && + access_of(&g_dom_a, PG_SHARED) == WT_DOMAIN_ACCESS_RW, + "perm set: nor is it the partition's own to re-permission (INVALID_PARAMETERS)"); + check(wt_spm_mem_perm_get(&g_dom_a, page(PG_SHARED), &perm) == 0 && + perm == (WT_FFA_PERM_DATA_RW | WT_FFA_PERM_XN), + "perm get: it still reads back its access to memory it shares (Table 2.37)"); + check(wt_spm_mem_perm_set(&g_dom_a, &g_relay_mailbox, page(PG_RW), 1u, + WT_FFA_PERM_DATA_RO | WT_FFA_PERM_XN) == 0 && + access_of(&g_dom_a, PG_RW) == WT_DOMAIN_ACCESS_RO && + wt_spm_mem_perm_get(&g_dom_a, page(PG_RW), &perm) == 0 && + perm == (WT_FFA_PERM_DATA_RO | WT_FFA_PERM_XN) && + wt_spm_mem_perm_set(&g_dom_a, &g_relay_mailbox, page(PG_RW), 1u, + WT_FFA_PERM_DATA_RW | WT_FFA_PERM_XN) == 0, + "perm set: a relayer endpoint makes memory its manifest makes writable read-only, and back"); + wt_spm_mem_unbind(CO_C); + check(wt_spm_mem_perm_set(&g_dom_c, &g_relay_mailbox, page(PG_C), 1u, + WT_FFA_PERM_DATA_RO | WT_FFA_PERM_XN) == + WT_FFA_INVALID_PARAMETERS && + access_of(&g_dom_c, PG_C) == WT_DOMAIN_ACCESS_RW && + wt_spm_mem_perm_set(&g_dom_c, &g_relay_mailbox, page(PG_C), 1u, + WT_FFA_PERM_DATA_NONE) == 0 && + wt_spm_mem_perm_set(&g_dom_c, &g_relay_mailbox, page(PG_C), 1u, + WT_FFA_PERM_DATA_RW | WT_FFA_PERM_XN) == 0, + "perm set: an FF-M partition's writable manifest memory, which the gate writes at S-EL1, never becomes read-only (INVALID_PARAMETERS) but may go no-access"); + check(wt_spm_mem_perm_set(&g_dom_a, &g_relay_mailbox, page(PG_RX), 1u, + WT_FFA_PERM_DATA_RW | WT_FFA_PERM_XN) == 0 && + access_of(&g_dom_a, PG_RX) == WT_DOMAIN_ACCESS_RW && + wt_spm_mem_perm_get(&g_dom_a, page(PG_RX), &perm) == 0 && + perm == (WT_FFA_PERM_DATA_RW | WT_FFA_PERM_XN), + "perm set: an image page the manifest leaves read-execute may become read-write"); + check(wt_spm_mem_perm_set(&g_dom_a, &g_relay_mailbox, page(PG_RX), 1u, + 0xFFFFFFF8u | WT_FFA_PERM_DATA_RW | + WT_FFA_PERM_XN) == 0 && + access_of(&g_dom_a, PG_RX) == WT_DOMAIN_ACCESS_RW, + "perm set: the SBZ bits above the permissions are ignored (Table 2.40)"); + (void)wt_ffa_mailbox_map(&g_relay_mailbox, page(PG_RX), page(PG_GAP), 1u); + check(wt_spm_mem_perm_set(&g_dom_a, &g_relay_mailbox, page(PG_RX), 1u, + WT_FFA_PERM_DATA_RO | WT_FFA_PERM_XN) == + WT_FFA_INVALID_PARAMETERS && + access_of(&g_dom_a, PG_RX) == WT_DOMAIN_ACCESS_RW, + "perm set: a page of the mapped RX/TX pair keeps its permissions (INVALID_PARAMETERS)"); + check(wt_spm_mem_perm_get(&g_dom_a, page(PG_RX), &perm) == 0 && + perm == (WT_FFA_PERM_DATA_RW | WT_FFA_PERM_XN) && + wt_spm_mem_perm_get(&g_dom_a, page(PG_IMAGE), &perm) == 0 && + perm == WT_FFA_PERM_DATA_RO, + "perm get: a page of the mapped pair and the partition's own code page read back (Table 2.37)"); + (void)wt_ffa_mailbox_unmap(&g_relay_mailbox); + check(wt_spm_mem_perm_set(&g_dom_a, &g_relay_mailbox, page(PG_RX), 1u, + WT_FFA_PERM_DATA_RO) == 0 && + wt_spm_mem_perm_get(&g_dom_a, page(PG_RX), &perm) == 0 && + perm == WT_FFA_PERM_DATA_RO, + "perm set: once the pair is unmapped the page goes back to read-execute"); + check(wt_spm_mem_perm_set(&g_dom_a, &g_relay_mailbox, page(PG_RX), 0u, + WT_FFA_PERM_DATA_RO) == + WT_FFA_INVALID_PARAMETERS && + wt_spm_mem_perm_set(&g_dom_a, &g_relay_mailbox, page(PG_RX) + 8u, 1u, + WT_FFA_PERM_DATA_RO) == + WT_FFA_INVALID_PARAMETERS && + wt_spm_mem_perm_set(&g_dom_a, &g_relay_mailbox, page(PG_B), 1u, + WT_FFA_PERM_DATA_RO) == + WT_FFA_INVALID_PARAMETERS && + wt_spm_mem_perm_set(&g_dom_a, &g_relay_mailbox, page(PG_RX), 1u, + WT_FFA_PERM_DATA_RW) == + WT_FFA_INVALID_PARAMETERS, + "perm set: no pages, an unaligned base, another partition's page, and read-write-execute are INVALID_PARAMETERS"); + check(wt_spm_mem_perm_set(&g_dom_a, &g_relay_mailbox, page(PG_RX), 1u, + WT_FFA_PERM_DATA_NONE) == 0 && + access_of(&g_dom_a, PG_RX) == WT_DOMAIN_ACCESS_NONE && + wt_spm_mem_perm_get(&g_dom_a, page(PG_RX), &perm) == 0 && + perm == (WT_FFA_PERM_DATA_NONE | WT_FFA_PERM_XN), + "perm set: no access takes the page from EL0 and reads back as no access, execute-never"); + check(wt_spm_mem_perm_set(&g_dom_a, &g_relay_mailbox, page(PG_RX), 1u, + WT_FFA_PERM_DATA_RW | WT_FFA_PERM_XN) == 0 && + access_of(&g_dom_a, PG_RX) == WT_DOMAIN_ACCESS_RW, + "perm set: a page made no-access is still the partition's to open again"); + check(wt_spm_mem_perm_set(&g_dom_a, &g_relay_mailbox, page(PG_RW), 1u, + WT_FFA_PERM_DATA_NONE | WT_FFA_PERM_XN) == 0 && + access_of(&g_dom_a, PG_RW) == WT_DOMAIN_ACCESS_NONE && + b_entry(PG_RW) == 1 && + wt_spm_mem_perm_set(&g_dom_a, &g_relay_mailbox, page(PG_RW), 1u, + WT_FFA_PERM_DATA_RW | WT_FFA_PERM_XN) == 0, + "perm set: manifest-writable memory may go no-access, which S-EL1 still writes"); + check(wt_spm_mem_perm_set(&g_dom_c, &g_relay_mailbox, page(PG_C), 1u, + WT_FFA_PERM_DATA_RO) == + WT_FFA_INVALID_PARAMETERS && + wt_spm_mem_perm_set(&g_dom_a, &g_relay_mailbox, page(PG_RW), 1u, + WT_FFA_PERM_DATA_RO) == 0 && + wt_spm_mem_perm_get(&g_dom_a, page(PG_RW), &perm) == 0 && + perm == WT_FFA_PERM_DATA_RO && + wt_spm_mem_perm_set(&g_dom_a, &g_relay_mailbox, page(PG_RW), 1u, + WT_FFA_PERM_DATA_RW | WT_FFA_PERM_XN) == 0, + "perm set: read-only and executable follows the same rule"); + check(wt_spm_mem_perm_get(&g_dom_a, page(PG_DEV), &perm) == 0 && + perm == (WT_FFA_PERM_DATA_RW | WT_FFA_PERM_XN) && + wt_spm_mem_perm_set(&g_dom_a, &g_relay_mailbox, page(PG_DEV), 1u, + WT_FFA_PERM_DATA_NONE | WT_FFA_PERM_XN) == 0 && + wt_spm_mem_perm_get(&g_dom_a, page(PG_DEV), &perm) == 0 && + perm == (WT_FFA_PERM_DATA_NONE | WT_FFA_PERM_XN) && + wt_spm_mem_perm_set(&g_dom_a, &g_relay_mailbox, page(PG_DEV), 1u, + WT_FFA_PERM_DATA_RW | WT_FFA_PERM_XN) == 0, + "perm set: the partition's own Device page reads back and changes its data access"); + check(wt_spm_mem_perm_set(&g_dom_a, &g_relay_mailbox, page(PG_DEV), 1u, + WT_FFA_PERM_DATA_RO) != 0 && + wt_spm_mem_perm_get(&g_dom_a, page(PG_DEV), &perm) == 0 && + perm == (WT_FFA_PERM_DATA_RW | WT_FFA_PERM_XN), + "perm set: a Device page is never made executable (2.9.0.0.1 rule 2)"); + check(g_domain_fails == 0u, "perm set: no domain operation failed closed"); +} + +/* Non-zero when the last instruction cache sync covered exactly page pg. */ +static int synced(unsigned int pg) +{ + return (g_syncs != 0u) && (g_sync_va == (uint64_t)page(pg)) && + (g_sync_size == WT_TABLES_PAGE_SIZE); +} + +/* WT-FFA-0009 (a page EL0 may execute again after it could be written has its + * instructions made fetchable: FFA_MEM_PERM_SET and a reclaim that restores an + * executable page; a retrieve never grants execution). */ +static void relay_icache_rows(void) +{ + wt_ffa_mem_constituent_t c[1]; + uint64_t h; + int ret = 0; + + if ((g_mem == NULL) || !relay_reset()) { + check(0, "icache: fixture"); + return; + } + (void)memset(&g_relay_mailbox, 0, sizeof(g_relay_mailbox)); + g_syncs = 0u; + check(wt_spm_mem_perm_set(&g_dom_a, &g_relay_mailbox, page(PG_RX), 1u, + WT_FFA_PERM_DATA_RW | WT_FFA_PERM_XN) == 0 && + wt_spm_mem_perm_set(&g_dom_a, &g_relay_mailbox, page(PG_RW), 1u, + WT_FFA_PERM_DATA_RO | WT_FFA_PERM_XN) == 0 && + g_syncs == 0u, + "icache: a page made writable or read-only data needs no sync"); + *(uint8_t*)page(PG_RX) = 0xD5u; + check(wt_spm_mem_perm_set(&g_dom_a, &g_relay_mailbox, page(PG_RX), 1u, + WT_FFA_PERM_DATA_RO) == 0 && + synced(PG_RX) && g_syncs == 1u, + "icache: a page written as data and made executable is synced for fetch"); + c[0].address = page(PG_RX); + c[0].page_count = 1u; + h = relay_send(WT_FFA_MEM_OP_LEND, c, 1u, WT_FFA_MEM_PERM_DATA_RO, 0u, + &ret); + g_syncs = 0u; + check(ret == 0 && access_of(&g_dom_a, PG_RX) == WT_DOMAIN_ACCESS_NONE && + relay_retrieve(h, OWN_RO, 0u) == 0 && + relay_relinquish(h, 0u) == 0 && g_syncs == 0u, + "icache: lending the executable page, and a borrower mapping it execute-never, need no sync"); + check(wt_spm_mem_reclaim(h, RELAY_ID_A, 0u) == 0 && + access_of(&g_dom_a, PG_RX) == WT_DOMAIN_ACCESS_RO && + synced(PG_RX) && g_syncs == 1u, + "icache: a reclaim that gives the owner back an executable page syncs it"); + c[0].address = page(PG_FILL2); + h = relay_send(WT_FFA_MEM_OP_LEND, c, 1u, WT_FFA_MEM_PERM_DATA_RW, 0u, + &ret); + check(ret == 0 && relay_retrieve(h, OWN_RW, 0u) == 0 && + relay_relinquish(h, 0u) == 0 && + wt_spm_mem_reclaim(h, RELAY_ID_A, 0u) == 0 && g_syncs == 1u, + "icache: a reclaim that gives back execute-never data needs none"); + check(g_domain_fails == 0u, "icache: no domain operation failed closed"); +} + +/* who (B or C) retrieves h asking for perms, naming the other as a + * non-retrieval borrower with the read-write access the lender gave it. */ +static int relay_retrieve_of_two_as(uint64_t h, uint16_t who, uint8_t perms) +{ + uint8_t req[128]; + uint8_t resp[256]; + uint16_t other = (who == RELAY_ID_B) ? RELAY_ID_C : RELAY_ID_B; + size_t len = 0u; + size_t resp_len = 0u; + int ret; + + ret = wt_ffa_mem_retrieve_req_build(req, sizeof(req), h, RELAY_ID_A, who, + perms, &len); + if (ret == 0) { + memcpy(&req[len], &req[WT_FFA_MEM_TXN_HDR_SIZE], WT_FFA_MEM_ACCESS_SIZE); + req[len + WT_FFA_MEM_ACC_OFF_RECEIVER] = (uint8_t)(other & 0xFFu); + req[len + WT_FFA_MEM_ACC_OFF_RECEIVER + 1u] = (uint8_t)(other >> 8); + req[len + WT_FFA_MEM_ACC_OFF_PERMS] = (uint8_t)WT_FFA_MEM_PERM_DATA_RW; + req[len + WT_FFA_MEM_ACC_OFF_FLAGS] = + (uint8_t)WT_FFA_MEM_ACC_FLAG_NON_RETRIEVAL; + put32(&req[WT_FFA_MEM_TXN_OFF_ACC_COUNT], 2u); + ret = wt_spm_mem_retrieve(req, len + WT_FFA_MEM_ACCESS_SIZE, who, resp, + sizeof(resp), &resp_len); + } + return ret; +} + +/* who (B or C) retrieves h read-write, naming the other as a non-retrieval + * borrower. */ +static int relay_retrieve_of_two(uint64_t h, uint16_t who) +{ + return relay_retrieve_of_two_as(h, who, WT_FFA_MEM_PERM_DATA_RW); +} + +static int relay_relinquish_as(uint64_t h, uint16_t who) +{ + uint8_t rel[32]; + size_t len = 0u; + int ret; + + ret = wt_ffa_mem_relinquish_build(rel, sizeof(rel), h, 0u, who, &len); + if (ret == 0) { + ret = wt_spm_mem_relinquish(rel, len, who); + } + return ret; +} + +/* B retrieves h, a lend to it alone, from A asking for flags; *resp_flags + * gets the response's. */ +static int relay_retrieve_flags(uint64_t h, uint32_t flags, + uint32_t* resp_flags) +{ + uint8_t req[128]; + uint8_t resp[256]; + size_t len = 0u; + size_t resp_len = 0u; + int ret; + + *resp_flags = 0xFFFFFFFFu; + ret = wt_ffa_mem_retrieve_req_build(req, sizeof(req), h, RELAY_ID_A, + RELAY_ID_B, OWN_RW, &len); + if (ret == 0) { + put32(&req[WT_FFA_MEM_TXN_OFF_FLAGS], flags); + ret = wt_spm_mem_retrieve(req, len, RELAY_ID_B, resp, sizeof(resp), + &resp_len); + } + if (ret == 0) { + *resp_flags = get32(&resp[WT_FFA_MEM_TXN_OFF_FLAGS]); + } + return ret; +} + +/* WT-FFA-0009 (memory the owner asked to be zeroed is zeroed once, after the + * owner's access is gone and before any borrower maps it, Table 1.21 bit[0] + * and 1.11.4.1). */ +static void relay_zero_rows(void) +{ + wt_ffa_mem_constituent_t c[1]; + uint8_t desc[256]; + uint8_t* p; + uint64_t h = 0u; + size_t len = 0u; + uint32_t resp_flags = 0u; + int ret; + + if ((g_mem == NULL) || !relay_reset()) { + check(0, "zero: fixture"); + return; + } + p = (uint8_t*)page(PG_RW); + p[0] = 0x5Au; + c[0].address = page(PG_RW); + c[0].page_count = 1u; + ret = relay_build(desc, sizeof(desc), WT_FFA_MEM_OP_LEND, c, 1u, + WT_FFA_MEM_PERM_DATA_RW, WT_FFA_MEM_FLAG_ZERO, &len); + if (ret == 0) { + len = add_receiver_c(desc, len); + ret = wt_spm_mem_share(desc, len, WT_FFA_MEM_OP_LEND, RELAY_ID_A, &h); + } + check(ret == 0 && relay_retrieve_of_two(h, RELAY_ID_B) == 0 && p[0] == 0u, + "zero: the owner's data is gone before the first borrower maps the lent page"); + p[0] = 0xB0u; + check(relay_retrieve_of_two(h, RELAY_ID_C) == 0 && p[0] == 0xB0u && + access_of(&g_dom_c, PG_RW) == WT_DOMAIN_ACCESS_RW, + "zero: a second borrower's retrieve leaves what the first one wrote"); + check(relay_relinquish_as(h, RELAY_ID_B) == 0 && + relay_relinquish_as(h, RELAY_ID_C) == 0 && + wt_spm_mem_reclaim(h, RELAY_ID_A, 0u) == 0 && p[0] == 0xB0u, + "zero: nothing wipes the page again when the borrowers let go"); + h = relay_send(WT_FFA_MEM_OP_LEND, c, 1u, WT_FFA_MEM_PERM_DATA_RW, + 0xFFFFFFFCu, &ret); + p[0] = 0xC1u; + check(ret == 0 && relay_retrieve(h, OWN_RW, 0u) == 0 && + relay_relinquish(h, 0u) == 0 && + wt_spm_mem_reclaim(h, RELAY_ID_A, 0u) == 0 && p[0] == 0xC1u, + "zero: a lend's SBZ flag bits are ignored and never ask the relayer for a wipe"); + h = relay_send(WT_FFA_MEM_OP_LEND, c, 1u, WT_FFA_MEM_PERM_DATA_RW, + WT_FFA_MEM_FLAG_ZERO, &ret); + check(ret == 0 && + relay_retrieve_flags(h, WT_FFA_MEM_FLAG_ZERO, &resp_flags) == 0 && + (resp_flags & WT_FFA_MEM_FLAG_ZERO) != 0u && + relay_relinquish(h, 0u) == 0, + "zero: a borrower's first retrieval may ask for the wipe, and is told it ran"); + p[0] = 0xD2u; + check(relay_retrieve_flags(h, WT_FFA_MEM_FLAG_ZERO, &resp_flags) == + WT_FFA_INVALID_PARAMETERS && + access_of(&g_dom_b, PG_RW) == WT_DOMAIN_ACCESS_NONE, + "zero: a repeated retrieval asking for it is INVALID_PARAMETERS (Table 1.22 bit[0])"); + check(relay_retrieve_flags(h, 0u, &resp_flags) == 0 && + (resp_flags & WT_FFA_MEM_FLAG_ZERO) == 0u && p[0] == 0xD2u && + relay_relinquish(h, 0u) == 0 && + wt_spm_mem_reclaim(h, RELAY_ID_A, 0u) == 0, + "zero: one that does not is mapped and told no wipe ran before it (Table 1.23 bit[0])"); + check(g_domain_fails == 0u, "zero: no domain operation failed closed"); +} + +/* who (B or C) relinquishes h asking for it to be zeroed after. */ +static int relay_relinquish_zero_as(uint64_t h, uint16_t who) +{ + uint8_t rel[32]; + size_t len = 0u; + int ret; + + ret = wt_ffa_mem_relinquish_build(rel, sizeof(rel), h, + WT_FFA_MEM_RELINQ_FLAG_ZERO, who, &len); + if (ret == 0) { + ret = wt_spm_mem_relinquish(rel, len, who); + } + return ret; +} + +/* WT-FFA-0009 (with several borrowers, a wipe any of them asks for runs once + * the last is unmapped: the relayer zeroes memory only once no other component + * maps it, DEN0140 1.11.4.1 and Table 2.25 bit[0]). */ +static void relay_multi_zero_rows(void) +{ + wt_ffa_mem_constituent_t c[1]; + uint8_t desc[256]; + uint8_t* p; + uint64_t h = 0u; + size_t len = 0u; + int ret; + + if ((g_mem == NULL) || !relay_reset()) { + check(0, "multi zero: fixture"); + return; + } + p = (uint8_t*)page(PG_RW); + c[0].address = page(PG_RW); + c[0].page_count = 1u; + ret = relay_build(desc, sizeof(desc), WT_FFA_MEM_OP_LEND, c, 1u, + WT_FFA_MEM_PERM_DATA_RW, 0u, &len); + if (ret == 0) { + len = add_receiver_c(desc, len); + ret = wt_spm_mem_share(desc, len, WT_FFA_MEM_OP_LEND, RELAY_ID_A, &h); + } + check(ret == 0 && relay_retrieve_of_two(h, RELAY_ID_B) == 0 && + relay_retrieve_of_two(h, RELAY_ID_C) == 0, + "multi zero: two borrowers map one lent page"); + p[0] = 0x77u; + g_cleans = 0u; + check(relay_relinquish_zero_as(h, RELAY_ID_B) == 0 && p[0] == 0x77u && + g_cleans == 0u && access_of(&g_dom_c, PG_RW) == WT_DOMAIN_ACCESS_RW, + "multi zero: the first to let go asking for a wipe leaves the page intact for the borrower still mapping it"); + check(relay_relinquish_as(h, RELAY_ID_C) == 0 && p[0] == 0u && + cleaned(PG_RW), + "multi zero: the wipe runs when the last borrower is unmapped, though it did not ask"); + check(wt_spm_mem_reclaim(h, RELAY_ID_A, 0u) == 0, + "multi zero: the owner reclaims the wiped page"); + ret = relay_build(desc, sizeof(desc), WT_FFA_MEM_OP_LEND, c, 1u, + WT_FFA_MEM_PERM_DATA_RW, 0u, &len); + if (ret == 0) { + len = add_receiver_c(desc, len); + ret = wt_spm_mem_share(desc, len, WT_FFA_MEM_OP_LEND, RELAY_ID_A, &h); + } + check(ret == 0 && relay_retrieve_of_two(h, RELAY_ID_B) == 0 && + relay_retrieve_of_two(h, RELAY_ID_C) == 0, + "multi zero: the two borrowers map it again"); + p[0] = 0x66u; + g_cleans = 0u; + check(relay_relinquish_as(h, RELAY_ID_C) == 0 && p[0] == 0x66u && + g_cleans == 0u && + relay_relinquish_zero_as(h, RELAY_ID_B) == 0 && p[0] == 0u && + cleaned(PG_RW), + "multi zero: in the other order the last to let go asks for the wipe, which runs at once"); + check(wt_spm_mem_reclaim(h, RELAY_ID_A, 0u) == 0 && + g_domain_fails == 0u, + "multi zero: the owner reclaims it and no domain operation failed closed"); +} + +/* WT-FFA-0009 (a lend or share borrower states the data access it wants in + * its retrieve request, DEN0140 1.10.2 item 1; a donate's receiver only should, + * item 2, and one that does not is given the owner's). */ +static void relay_own_access_rows(void) +{ + static const wt_ffa_mem_op_t ops[2] = { + WT_FFA_MEM_OP_LEND, WT_FFA_MEM_OP_SHARE + }; + wt_ffa_mem_constituent_t c[1]; + uint8_t desc[256]; + uint64_t h = 0u; + size_t len = 0u; + unsigned int i; + uint8_t instr; + int ret = 0; + + if ((g_mem == NULL) || !relay_reset()) { + check(0, "own access: fixture"); + return; + } + c[0].address = page(PG_RW); + c[0].page_count = 1u; + for (i = 0u; i < 2u; i++) { + instr = (ops[i] == WT_FFA_MEM_OP_LEND) ? WT_FFA_MEM_PERM_INSTR_NX + : WT_FFA_MEM_PERM_INSTR_NOT_SPEC; + h = relay_send(ops[i], c, 1u, WT_FFA_MEM_PERM_DATA_RW, 0u, &ret); + check(ret == 0 && + relay_retrieve(h, (uint8_t)(WT_FFA_MEM_PERM_DATA_NOT_SPEC | instr), + 0u) == WT_FFA_INVALID_PARAMETERS && + access_of(&g_dom_b, PG_RW) == WT_DOMAIN_ACCESS_NONE, + (i == 0u) + ? "own access: a lend borrower leaving its data access unspecified is INVALID_PARAMETERS" + : "own access: so is a share borrower"); + check(relay_retrieve(h, (uint8_t)(WT_FFA_MEM_PERM_DATA_RSVD | instr), + 0u) == WT_FFA_INVALID_PARAMETERS && + relay_retrieve(h, (uint8_t)(WT_FFA_MEM_PERM_DATA_RW | instr), + 0u) == 0 && + access_of(&g_dom_b, PG_RW) == WT_DOMAIN_ACCESS_RW && + relay_relinquish(h, 0u) == 0 && + wt_spm_mem_reclaim(h, RELAY_ID_A, 0u) == 0, + "own access: the reserved encoding too; stating it, the borrower retrieves"); + } + /* 1.10.3: the one borrower of a lend states its instruction access, a + * share's leaves it unspecified, and neither is ever mapped executable. */ + h = relay_send(WT_FFA_MEM_OP_LEND, c, 1u, WT_FFA_MEM_PERM_DATA_RW, 0u, &ret); + check(ret == 0 && + relay_retrieve(h, WT_FFA_MEM_PERM_DATA_RW, 0u) == + WT_FFA_INVALID_PARAMETERS && + access_of(&g_dom_b, PG_RW) == WT_DOMAIN_ACCESS_NONE, + "own access: a lend's one borrower leaving its instruction access unspecified is INVALID_PARAMETERS (1.10.3 item 2)"); + check(relay_retrieve(h, (uint8_t)(WT_FFA_MEM_PERM_DATA_RW | + WT_FFA_MEM_PERM_INSTR_X), 0u) == + WT_FFA_DENIED && + relay_retrieve(h, (uint8_t)(WT_FFA_MEM_PERM_DATA_RW | + WT_FFA_MEM_PERM_INSTR_MASK), 0u) == + WT_FFA_INVALID_PARAMETERS && + access_of(&g_dom_b, PG_RW) == WT_DOMAIN_ACCESS_NONE && + relay_retrieve(h, OWN_RW, 0u) == 0 && + access_of(&g_dom_b, PG_RW) == WT_DOMAIN_ACCESS_RW && + relay_relinquish(h, 0u) == 0 && + wt_spm_mem_reclaim(h, RELAY_ID_A, 0u) == 0, + "own access: asking for executable is DENIED and the reserved encoding INVALID_PARAMETERS; stating not-executable, it retrieves"); + h = relay_send(WT_FFA_MEM_OP_SHARE, c, 1u, WT_FFA_MEM_PERM_DATA_RW, 0u, &ret); + check(ret == 0 && + relay_retrieve(h, OWN_RW, 0u) == WT_FFA_INVALID_PARAMETERS && + access_of(&g_dom_b, PG_RW) == WT_DOMAIN_ACCESS_NONE && + relay_retrieve(h, WT_FFA_MEM_PERM_DATA_RW, 0u) == 0 && + relay_relinquish(h, 0u) == 0 && + wt_spm_mem_reclaim(h, RELAY_ID_A, 0u) == 0, + "own access: a share borrower stating an instruction access is INVALID_PARAMETERS (item 1)"); + ret = relay_build(desc, sizeof(desc), WT_FFA_MEM_OP_LEND, c, 1u, + WT_FFA_MEM_PERM_DATA_RW, 0u, &len); + if (ret == 0) { + len = add_receiver_c(desc, len); + ret = wt_spm_mem_share(desc, len, WT_FFA_MEM_OP_LEND, RELAY_ID_A, &h); + } + check(ret == 0 && + relay_retrieve_of_two_as(h, RELAY_ID_B, + WT_FFA_MEM_PERM_DATA_NOT_SPEC) == + WT_FFA_INVALID_PARAMETERS && + access_of(&g_dom_b, PG_RW) == WT_DOMAIN_ACCESS_NONE && + relay_retrieve_of_two_as(h, RELAY_ID_B, WT_FFA_MEM_PERM_DATA_RO) == 0 && + access_of(&g_dom_b, PG_RW) == WT_DOMAIN_ACCESS_RO, + "own access: so is one of two lend borrowers, which retrieves once it states it"); + check(relay_relinquish_as(h, RELAY_ID_B) == 0 && + wt_spm_mem_reclaim(h, RELAY_ID_A, 0u) == 0, + "own access: the two-borrower lend is reclaimed"); + h = relay_send(WT_FFA_MEM_OP_DONATE, c, 1u, WT_FFA_MEM_PERM_DATA_NOT_SPEC, + 0u, &ret); + check(ret == 0 && + relay_retrieve(h, WT_FFA_MEM_PERM_DATA_NOT_SPEC, 0u) == + WT_FFA_INVALID_PARAMETERS && + relay_retrieve(h, (uint8_t)(WT_FFA_MEM_PERM_DATA_NOT_SPEC | + WT_FFA_MEM_PERM_INSTR_X), 0u) == + WT_FFA_DENIED && + access_of(&g_dom_b, PG_RW) == WT_DOMAIN_ACCESS_NONE, + "own access: a donate's receiver states its instruction access too, and executable is DENIED"); + check(relay_retrieve(h, (uint8_t)(WT_FFA_MEM_PERM_DATA_NOT_SPEC | + WT_FFA_MEM_PERM_INSTR_NX), 0u) == 0 && + access_of(&g_dom_b, PG_RW) == WT_DOMAIN_ACCESS_RW, + "own access: a donate's receiver that leaves its data access unspecified gets the owner's read-write access"); + check(g_domain_fails == 0u, "own access: no domain operation failed closed"); +} + +/* Non-zero when the last clean covered exactly page pg, after it was zeroed. */ +static int cleaned(unsigned int pg) +{ + return (g_cleans != 0u) && (g_clean_va == (uint64_t)page(pg)) && + (g_clean_size == WT_TABLES_PAGE_SIZE) && (g_clean_zeroed != 0); +} + +/* WT-FFA-0009 (each wipe is cleaned to the point of coherency so memory, not + * just the SPMC's cache, holds the zeros, 1.11.4.1). */ +static void relay_clean_rows(void) +{ + wt_ffa_mem_constituent_t c[1]; + uint64_t h; + int ret = 0; + + if ((g_mem == NULL) || !relay_reset()) { + check(0, "clean: fixture"); + return; + } + c[0].address = page(PG_RW); + c[0].page_count = 1u; + g_cleans = 0u; + h = relay_send(WT_FFA_MEM_OP_LEND, c, 1u, WT_FFA_MEM_PERM_DATA_RW, + WT_FFA_MEM_FLAG_ZERO, &ret); + check(ret == 0 && cleaned(PG_RW), + "clean: a lend that asks for zeroing cleans the zeroed page"); + g_cleans = 0u; + check(relay_retrieve(h, OWN_RW, 0u) == 0 && + g_cleans == 0u, + "clean: the retrieve that follows neither wipes nor cleans it again"); + check(relay_relinquish(h, WT_FFA_MEM_RELINQ_FLAG_ZERO) == 0 && + cleaned(PG_RW), + "clean: a relinquish that asks for zeroing cleans the zeroed page"); + g_cleans = 0u; + check(wt_spm_mem_reclaim(h, RELAY_ID_A, WT_FFA_MEM_RELINQ_FLAG_ZERO) == 0 && + cleaned(PG_RW), + "clean: a reclaim that asks for zeroing cleans the zeroed page"); + check(g_clean_a_access == WT_DOMAIN_ACCESS_NONE && + access_of(&g_dom_a, PG_RW) == WT_DOMAIN_ACCESS_RW, + "clean: that wipe runs before the owner's mapping comes back (Table 2.31 bit[0])"); +} + +/* WT-FFA-0009 (a partition that faults gives up what it borrowed, zeroed if + * its retrieve asked, Table 1.22 bit[2], and what it owned goes to the SPM, + * 1.3.1 rule 9). */ +static void relay_teardown_rows(void) +{ + wt_ffa_mem_constituent_t c[1]; + uint8_t desc[256]; + uint8_t* wiped; + uint8_t* kept; + uint64_t h1; + uint64_t h2; + uint64_t fh = 0u; + uint32_t offset = 0u; + size_t len = 0u; + int done = 0; + int ret = 0; + int ret2 = 0; + + if ((g_mem == NULL) || !relay_reset()) { + check(0, "teardown: fixture"); + return; + } + wiped = (uint8_t*)page(PG_RW); + kept = (uint8_t*)page(PG_RW + 1u); + c[0].address = page(PG_RW); + c[0].page_count = 1u; + h1 = relay_send(WT_FFA_MEM_OP_LEND, c, 1u, WT_FFA_MEM_PERM_DATA_RW, 0u, + &ret); + c[0].address = page(PG_RW + 1u); + h2 = relay_send(WT_FFA_MEM_OP_LEND, c, 1u, WT_FFA_MEM_PERM_DATA_RW, 0u, + &ret2); + wiped[0] = 0x5Au; + kept[0] = 0x5Au; + check(ret == 0 && ret2 == 0 && + relay_retrieve(h1, OWN_RW, + WT_FFA_MEM_FLAG_ZERO_AFTER) == 0 && + relay_retrieve(h2, OWN_RW, 0u) == 0, + "teardown: the borrower holds two lent pages, one to be zeroed after"); + wt_spm_mem_endpoint_teardown(CO_B); + check(access_of(&g_dom_b, PG_RW) == WT_DOMAIN_ACCESS_NONE && + access_of(&g_dom_b, PG_RW + 1u) == WT_DOMAIN_ACCESS_NONE && + b_entry(PG_RW) == 1 && b_entry(PG_RW + 1u) == 1, + "teardown: a faulted borrower's table loses every page it retrieved"); + check(wiped[0] == 0u && kept[0] == 0x5Au, + "teardown: only the page its retrieve asked to be zeroed is wiped"); + check(wt_spm_mem_reclaim(h1, RELAY_ID_A, 0u) == 0 && + wt_spm_mem_reclaim(h2, RELAY_ID_A, 0u) == 0 && + access_of(&g_dom_a, PG_RW) == WT_DOMAIN_ACCESS_RW, + "teardown: the owner reclaims what the faulted borrower held"); + + c[0].address = page(PG_RW); + h1 = relay_send(WT_FFA_MEM_OP_LEND, c, 1u, WT_FFA_MEM_PERM_DATA_RW, 0u, + &ret); + c[0].address = page(PG_FILL2); + h2 = relay_send(WT_FFA_MEM_OP_LEND, c, 1u, WT_FFA_MEM_PERM_DATA_RW, 0u, + &ret2); + check(ret == 0 && ret2 == 0 && + relay_retrieve(h2, OWN_RW, 0u) == 0, + "teardown: the owner lends two pages, the borrower retrieves one"); + (void)relay_build(desc, sizeof(desc), WT_FFA_MEM_OP_LEND, c, 1u, + WT_FFA_MEM_PERM_DATA_RW, 0u, &len); + check(wt_spm_mem_frag_begin((uint8_t)WT_FFA_MEM_OP_LEND, RELAY_ID_A, desc, + 40u, (uint32_t)len, &fh) == 0, + "teardown: the owner starts a descriptor in fragments"); + wt_spm_mem_endpoint_teardown(CO_A); + check(access_of(&g_dom_a, PG_RW) == WT_DOMAIN_ACCESS_NONE && + wt_spm_mem_reclaim(h1, RELAY_ID_A, 0u) == WT_FFA_INVALID_PARAMETERS, + "teardown: what a faulted owner lent and nobody retrieved ends, its access left to the SPM"); + check(wt_spm_mem_frag_next(fh, RELAY_ID_A, &desc[40], (uint32_t)len - 40u, + &offset, &done) == WT_FFA_INVALID_PARAMETERS, + "teardown: its unfinished fragmented descriptor is dropped"); + check(access_of(&g_dom_b, PG_FILL2) == WT_DOMAIN_ACCESS_RW && + access_of(&g_dom_a, PG_FILL2) == WT_DOMAIN_ACCESS_NONE, + "teardown: what a borrower still maps stays mapped"); + check(relay_relinquish(h2, 0u) == 0 && b_entry(PG_FILL2) == 1 && + access_of(&g_dom_a, PG_FILL2) == WT_DOMAIN_ACCESS_NONE && + wt_spm_mem_reclaim(h2, RELAY_ID_A, 0u) == WT_FFA_INVALID_PARAMETERS, + "teardown: the last borrower to relinquish ends it, and the faulted owner never gets it back"); + check(g_domain_fails == 0u, "teardown: no domain operation failed closed"); +} + +/* B retrieves h from owner with a v1.0 retrieve request. */ +static int relay_retrieve_v10(uint64_t h, uint16_t owner, uint8_t perms, + uint8_t* resp, size_t* resp_len) +{ + uint8_t req[64]; + size_t len = v10_retrieve_req(req, h, owner, RELAY_ID_B, perms); + + return wt_spm_mem_retrieve(req, len, RELAY_ID_B, resp, 256u, resp_len); +} + +static uint8_t ns_bit_told(uint64_t h, int* ret) +{ + uint8_t resp[256]; + size_t resp_len = 0u; + + memset(resp, 0, sizeof(resp)); + *ret = relay_retrieve_v10(h, WT_FFA_ID_NS_PRIMARY, WT_FFA_MEM_PERM_DATA_RW, + resp, &resp_len); + if (*ret == 0) { + *ret = relay_relinquish(h, 0u); + } + return (uint8_t)(resp[WT_FFA_MEM_TXN_OFF_ATTRS] & WT_FFA_MEM_ATTR_NS); +} + +/* WT-FFA-0009 (the relayer reads and answers each endpoint in the layout of + * the version it negotiated, DEN0077A 18.5.3, and tells a v1.0 borrower the + * NS bit only if it asked, DEN0140 Table 1.19). */ +static void relay_v10_rows(void) +{ + wt_ffa_mem_constituent_t c[1]; + wt_ffa_mem_build_t in; + uint8_t desc[256]; + uint8_t resp[256]; + uint64_t h = 0u; + uint64_t fh = 0u; + size_t len = 0u; + size_t resp_len = 0u; + uint32_t offset = 0u; + uint8_t told; + int done = 0; + int ret = 0; + + if ((g_mem == NULL) || !relay_reset()) { + check(0, "v1.0: fixture"); + return; + } + c[0].address = page(PG_RW); + c[0].page_count = 1u; + memset(&in, 0, sizeof(in)); + in.constituents = c; + in.constituent_count = 1u; + in.op = WT_FFA_MEM_OP_LEND; + in.sender = RELAY_ID_A; + in.receiver = RELAY_ID_B; + in.permissions = WT_FFA_MEM_PERM_DATA_RW; + in.version = V10; + g_ver_a = V10; + g_ver_b = V10; + (void)wt_ffa_mem_txn_build(desc, sizeof(desc), &in, &len); + check(wt_spm_mem_share(desc, len, WT_FFA_MEM_OP_LEND, RELAY_ID_A, &h) == 0, + "v1.0: the relayer takes a lend from a v1.0 owner in its layout"); + memset(resp, 0xA5, sizeof(resp)); + check(relay_retrieve_v10(h, RELAY_ID_A, OWN_RW, resp, &resp_len) == 0 && + resp_len == 80u && get32(&resp[24]) == 0u && + get32(&resp[28]) == 1u && resp[32] == (RELAY_ID_B & 0xFFu) && + get32(&resp[32u + WT_FFA_MEM_ACC_OFF_COMP_OFF]) == 48u && + get32(&resp[WT_FFA_MEM_TXN_OFF_HANDLE]) == (uint32_t)h && + get32(&resp[64]) == (uint32_t)page(PG_RW) && + access_of(&g_dom_b, PG_RW) == WT_DOMAIN_ACCESS_RW, + "v1.0: a v1.0 borrower's retrieve is mapped and answered in the v1.0 " + "layout"); + check(relay_relinquish(h, 0u) == 0 && + wt_spm_mem_reclaim(h, RELAY_ID_A, 0u) == 0, + "v1.0: the lend ends"); + + (void)wt_ffa_mem_txn_build(desc, sizeof(desc), &in, &len); + check(wt_spm_mem_frag_begin((uint8_t)WT_FFA_MEM_OP_LEND, RELAY_ID_A, desc, + 64u, (uint32_t)len, &fh) == 0 && + wt_spm_mem_frag_next(fh, RELAY_ID_A, &desc[64], + (uint32_t)len - 64u, &offset, &done) == 0 && + done == 1 && wt_spm_mem_frag_share(fh, RELAY_ID_A) == 0 && + wt_spm_mem_reclaim(fh, RELAY_ID_A, 0u) == 0, + "v1.0: a v1.0 owner's lend sent in fragments is taken in its layout"); + + wt_spm_mem_ns_window(page(10u), 2u * WT_TABLES_PAGE_SIZE); + g_ver_ns = V10; + c[0].address = page(10u); + in.op = WT_FFA_MEM_OP_SHARE; + in.sender = WT_FFA_ID_NS_PRIMARY; + (void)wt_ffa_mem_txn_build(desc, sizeof(desc), &in, &len); + check(wt_spm_mem_share(desc, len, WT_FFA_MEM_OP_SHARE, + WT_FFA_ID_NS_PRIMARY, &h) == 0, + "v1.0: a v1.0 Normal world shares its memory in the v1.0 layout"); + told = ns_bit_told(h, &ret); + check(ret == 0 && told == 0u, + "v1.0: a v1.0 borrower that never asked for the NS bit is not told " + "it (Table 1.19 row 5)"); + g_ns_bit_b = 1; + told = ns_bit_told(h, &ret); + check(ret == 0 && told != 0u, + "v1.0: one that asked through FFA_FEATURES is told it (row 6)"); + g_ns_bit_b = 0; + g_ver_b = WT_FFA_VERSION_1_2; + memset(resp, 0, sizeof(resp)); + ret = wt_ffa_mem_retrieve_req_build(desc, sizeof(desc), h, + WT_FFA_ID_NS_PRIMARY, RELAY_ID_B, + WT_FFA_MEM_PERM_DATA_RW, &len); + if (ret == 0) { + ret = wt_spm_mem_retrieve(desc, len, RELAY_ID_B, resp, sizeof(resp), + &resp_len); + } + check(ret == 0 && + (resp[WT_FFA_MEM_TXN_OFF_ATTRS] & WT_FFA_MEM_ATTR_NS) != 0u && + relay_relinquish(h, 0u) == 0, + "v1.0: a v1.1+ borrower is always told it (row 7)"); + check(wt_spm_mem_reclaim(h, WT_FFA_ID_NS_PRIMARY, 0u) == 0, + "v1.0: the Normal world reclaims its share"); + g_ver_a = WT_FFA_VERSION_1_2; + g_ver_b = WT_FFA_VERSION_1_2; + g_ver_ns = WT_FFA_VERSION_1_2; + g_ns_bit_b = 0; + check(g_domain_fails == 0u, "v1.0: no domain operation failed closed"); +} + +/* WT-FFA-0009 (a binding the SPMC made for a boot self-test is dropped with + * what it holds, so a partition reusing the coroutine starts unbound). */ +static void relay_unbind_rows(void) +{ + wt_ffa_mem_constituent_t c[1]; + uint64_t h; + int ret = 0; + + if ((g_mem == NULL) || !relay_reset()) { + check(0, "unbind: fixture"); + return; + } + c[0].address = page(PG_RW); + c[0].page_count = 1u; + h = relay_send(WT_FFA_MEM_OP_LEND, c, 1u, WT_FFA_MEM_PERM_DATA_RW, 0u, + &ret); + check(ret == 0 && relay_retrieve(h, OWN_RW, 0u) == 0 && + wt_spm_mem_binding(CO_B) != NULL, + "unbind: a bound borrower holds a lent page"); + wt_spm_mem_unbind(CO_B); + check(wt_spm_mem_binding(CO_B) == NULL && + access_of(&g_dom_b, PG_RW) == WT_DOMAIN_ACCESS_NONE && + wt_spm_mem_binding(CO_A) != NULL, + "unbind: the coroutine loses its binding and the page it held, no other"); + check(relay_retrieve(h, OWN_RW, 0u) == WT_FFA_DENIED && + wt_spm_mem_reclaim(h, RELAY_ID_A, 0u) == 0, + "unbind: the unbound endpoint cannot retrieve and the owner reclaims"); +} + +/* Send n constituents from sender to receiver; *ret gets the relayer's + * answer. */ +static uint64_t relay_send_from(wt_ffa_mem_op_t op, + const wt_ffa_mem_constituent_t* c, uint32_t n, + uint16_t sender, uint16_t receiver, + uint8_t perms, int* ret) +{ + wt_ffa_mem_build_t in; + uint8_t desc[256]; + uint64_t h = 0u; + size_t len = 0u; + + (void)memset(&in, 0, sizeof(in)); + in.constituents = c; + in.constituent_count = n; + in.op = op; + in.sender = sender; + in.receiver = receiver; + in.permissions = perms; + in.access_desc_size = (uint8_t)WT_FFA_MEM_ACCESS_SIZE; + *ret = wt_ffa_mem_txn_build(desc, sizeof(desc), &in, &len); + if (*ret == 0) { + *ret = wt_spm_mem_share(desc, len, op, sender, &h); + } + return h; +} + +static int relay_retrieve_by(uint64_t h, uint16_t sender, uint16_t receiver, + uint8_t perms) +{ + uint8_t req[128]; + uint8_t resp[256]; + size_t len = 0u; + size_t resp_len = 0u; + int ret; + + ret = wt_ffa_mem_retrieve_req_build(req, sizeof(req), h, sender, receiver, + perms, &len); + if (ret == 0) { + ret = wt_spm_mem_retrieve(req, len, receiver, resp, sizeof(resp), + &resp_len); + } + return ret; +} + +static int ns_of(const wt_secure_domain_t* d, unsigned int pg) +{ + return wt_domain_page_ns(d->regions, d->region_count, page(pg)); +} + +/* WT-FFA-0009 (the Normal world donates its memory to a partition, which then + * owns it: the Normal world cannot send it again, and the partition sends it + * on as Non-secure memory, Table 1.24 and 1.3.1 rules 5 and 6). */ +static void relay_ns_donate_rows(void) +{ + wt_ffa_mem_constituent_t c[2]; + uint64_t h; + int ret = 0; + int ok; + + if ((g_mem == NULL) || !relay_reset()) { + check(0, "ns donate: fixture"); + return; + } + wt_spm_mem_ns_window(page(10u), 2u * WT_TABLES_PAGE_SIZE); + c[0].address = page(10u); + c[0].page_count = 1u; + h = relay_send_from(WT_FFA_MEM_OP_DONATE, c, 1u, WT_FFA_ID_NS_PRIMARY, + RELAY_ID_B, WT_FFA_MEM_PERM_DATA_NOT_SPEC, &ret); + check(ret == 0 && + relay_retrieve_by(h, WT_FFA_ID_NS_PRIMARY, RELAY_ID_B, + OWN_RW) == 0 && + access_of(&g_dom_b, 10u) == WT_DOMAIN_ACCESS_RW && + ns_of(&g_dom_b, 10u) != 0, + "ns donate: the Normal world donates a page and the receiver maps it Non-secure"); + (void)relay_send_from(WT_FFA_MEM_OP_LEND, c, 1u, WT_FFA_ID_NS_PRIMARY, + RELAY_ID_C, WT_FFA_MEM_PERM_DATA_RW, &ret); + check(ret == WT_FFA_DENIED && + wt_spm_mem_ns_owns(page(10u), WT_TABLES_PAGE_SIZE) == 0 && + wt_spm_mem_ns_owns(page(11u), WT_TABLES_PAGE_SIZE) != 0, + "ns donate: the donated page is no longer the Normal world's to send"); + h = relay_send_from(WT_FFA_MEM_OP_LEND, c, 1u, RELAY_ID_B, RELAY_ID_C, + WT_FFA_MEM_PERM_DATA_RW, &ret); + check(ret == 0 && access_of(&g_dom_b, 10u) == WT_DOMAIN_ACCESS_NONE && + relay_retrieve_by(h, RELAY_ID_B, RELAY_ID_C, + OWN_RW) == 0 && + ns_of(&g_dom_c, 10u) != 0, + "ns donate: its new owner lends it on, and the borrower maps it Non-secure too"); + check(relay_relinquish_as(h, RELAY_ID_C) == 0 && + wt_spm_mem_reclaim(h, RELAY_ID_B, 0u) == 0 && + access_of(&g_dom_b, 10u) == WT_DOMAIN_ACCESS_RW && + ns_of(&g_dom_b, 10u) != 0, + "ns donate: a reclaim gives the owner the page back Non-secure"); + (void)relay_send_from(WT_FFA_MEM_OP_DONATE, c, 1u, RELAY_ID_B, + WT_FFA_ID_NS_PRIMARY, WT_FFA_MEM_PERM_DATA_NOT_SPEC, + &ret); + ok = (ret == WT_FFA_DENIED) ? 1 : 0; + (void)relay_send_from(WT_FFA_MEM_OP_LEND, c, 1u, RELAY_ID_B, + WT_FFA_ID_NS_PRIMARY, WT_FFA_MEM_PERM_DATA_RW, &ret); + ok = ok && (ret == WT_FFA_DENIED); + (void)relay_send_from(WT_FFA_MEM_OP_SHARE, c, 1u, RELAY_ID_B, + WT_FFA_ID_NS_PRIMARY, WT_FFA_MEM_PERM_DATA_RW, &ret); + check(ok && (ret == WT_FFA_DENIED) && + wt_spm_mem_in_transaction(page(10u), WT_TABLES_PAGE_SIZE) == 0 && + access_of(&g_dom_b, 10u) == WT_DOMAIN_ACCESS_RW, + "ns donate: its new owner cannot send it to the Normal world, no SP-to-NS-Endpoint send being a Table 1.7 combination"); + c[1].address = page(PG_B); + c[1].page_count = 1u; + (void)relay_send_from(WT_FFA_MEM_OP_LEND, c, 2u, RELAY_ID_B, RELAY_ID_C, + WT_FFA_MEM_PERM_DATA_RW, &ret); + check(ret == WT_FFA_DENIED && + access_of(&g_dom_b, PG_B) == WT_DOMAIN_ACCESS_RW, + "ns donate: a region mixing Non-secure and Secure pages is DENIED"); + check(g_domain_fails == 0u, "ns donate: no domain operation failed closed"); +} + +/* B retrieves h with a request laid out for req_version; + * *resp_size gets the size the response's descriptors use. */ +static int relay_retrieve_sized(uint64_t h, uint32_t req_version, + uint32_t* resp_size) +{ + uint8_t req[128]; + uint8_t resp[256]; + size_t len = 0u; + size_t resp_len = 0u; + int ret; + + ret = wt_ffa_mem_retrieve_req_build_at(req, sizeof(req), h, RELAY_ID_A, + RELAY_ID_B, OWN_RW, req_version, + &len); + if (ret == 0) { + ret = wt_spm_mem_retrieve(req, len, RELAY_ID_B, resp, sizeof(resp), + &resp_len); + } + if (ret == 0) { + *resp_size = get32(&resp[WT_FFA_MEM_TXN_OFF_ACC_SIZE]); + ret = ((size_t)(WT_FFA_MEM_TXN_HDR_SIZE + *resp_size + + WT_FFA_MEM_COMPOSITE_HDR_SIZE + + WT_FFA_MEM_CONSTITUENT_SIZE) == resp_len) ? 0 : -1; + } + return ret; +} + +/* WT-FFA-0009 (each endpoint gets the endpoint memory access descriptor of + * the version it negotiated, whatever size it sent, DEN0077A 18.5 and DEN0140 + * Table 1.16). */ +static void relay_access_size_rows(void) +{ + wt_ffa_mem_constituent_t c[1]; + wt_ffa_mem_build_t in; + uint8_t desc[256]; + uint32_t size = 0u; + size_t len = 0u; + uint64_t h; + int ret = 0; + + (void)memset(&in, 0, sizeof(in)); + c[0].address = 0x40000000ull; + c[0].page_count = 1u; + in.constituents = c; + in.constituent_count = 1u; + in.op = WT_FFA_MEM_OP_LEND; + in.receiver = 0x8002u; + in.permissions = WT_FFA_MEM_PERM_DATA_RW; + check(wt_ffa_mem_txn_build(desc, sizeof(desc), &in, &len) == 0 && + get32(&desc[WT_FFA_MEM_TXN_OFF_ACC_SIZE]) == WT_FFA_MEM_ACCESS_SIZE_V12 && + len == 112u, + "access size: a descriptor for an FF-A 1.2 reader uses the 32-byte access descriptor"); + in.version = WT_FFA_VERSION_MAKE(1u, 1u); + check(wt_ffa_mem_txn_build(desc, sizeof(desc), &in, &len) == 0 && + get32(&desc[WT_FFA_MEM_TXN_OFF_ACC_SIZE]) == WT_FFA_MEM_ACCESS_SIZE && + len == 96u, + "access size: one for an FF-A 1.1 reader keeps the 16-byte one"); + + if ((g_mem == NULL) || !relay_reset()) { + check(0, "access size: fixture"); + return; + } + c[0].address = page(PG_RW); + h = relay_send(WT_FFA_MEM_OP_LEND, c, 1u, WT_FFA_MEM_PERM_DATA_RW, 0u, + &ret); + check(ret == 0 && + relay_retrieve_sized(h, WT_FFA_VERSION_MAKE(1u, 1u), &size) == 0 && + size == WT_FFA_MEM_ACCESS_SIZE_V12 && relay_relinquish(h, 0u) == 0, + "access size: a 1.2 borrower's 16-byte request is answered in the 32-byte layout"); + g_ver_b = WT_FFA_VERSION_MAKE(1u, 1u); + check(relay_retrieve_sized(h, WT_FFA_VERSION_1_2, &size) == 0 && + size == WT_FFA_MEM_ACCESS_SIZE && relay_relinquish(h, 0u) == 0 && + wt_spm_mem_reclaim(h, RELAY_ID_A, 0u) == 0, + "access size: a 1.1 borrower's 32-byte request is answered in the 16-byte layout"); + g_ver_b = WT_FFA_VERSION_1_2; +} + +/* B's retrieve of h naming n address ranges of its own (1.11.3.2) in a + * composite after its access descriptor; *resp_len and *resp_comp get the + * response length and the composite offset it states. */ +static int relay_retrieve_ranges(uint64_t h, const wt_ffa_mem_constituent_t* r, + uint32_t n, uint32_t flags, size_t* resp_len, + uint32_t* resp_comp) +{ + uint8_t req[256]; + uint8_t resp[256]; + size_t len = 0u; + uint32_t comp; + uint32_t total = 0u; + uint32_t i; + int ret; + + ret = wt_ffa_mem_retrieve_req_build(req, sizeof(req), h, RELAY_ID_A, + RELAY_ID_B, OWN_RW, &len); + if (ret != 0) { + return ret; + } + comp = (uint32_t)len; + put32(&req[WT_FFA_MEM_TXN_HDR_SIZE + WT_FFA_MEM_ACC_OFF_COMP_OFF], comp); + put32(&req[WT_FFA_MEM_TXN_OFF_FLAGS], flags); + (void)memset(&req[comp], 0, WT_FFA_MEM_COMPOSITE_HDR_SIZE + + (n * WT_FFA_MEM_CONSTITUENT_SIZE)); + for (i = 0u; i < n; i++) { + put64(&req[comp + WT_FFA_MEM_COMPOSITE_HDR_SIZE + + (i * WT_FFA_MEM_CONSTITUENT_SIZE)], r[i].address); + put32(&req[comp + WT_FFA_MEM_COMPOSITE_HDR_SIZE + + (i * WT_FFA_MEM_CONSTITUENT_SIZE) + 8u], r[i].page_count); + total += r[i].page_count; + } + put32(&req[comp + WT_FFA_MEM_COMP_OFF_PAGES], total); + put32(&req[comp + WT_FFA_MEM_COMP_OFF_COUNT], n); + len = comp + WT_FFA_MEM_COMPOSITE_HDR_SIZE + (n * WT_FFA_MEM_CONSTITUENT_SIZE); + ret = wt_spm_mem_retrieve(req, len, RELAY_ID_B, resp, sizeof(resp), + resp_len); + if (ret == 0) { + *resp_comp = get32(&resp[WT_FFA_MEM_TXN_HDR_SIZE + + WT_FFA_MEM_ACC_OFF_COMP_OFF]); + } + return ret; +} + +/* WT-FFA-0009 (a receiver may name the address ranges its mapping uses, + * 1.11.3.2; the relayer maps S-EL0 memory only at its own address and answers + * with no composite, 1.11.3.3). */ +static void relay_range_rows(void) +{ + wt_ffa_mem_constituent_t c[2]; + wt_ffa_mem_constituent_t r[2]; + wt_ffa_mem_retrieve_req_t rq; + const uint8_t* whole; + uint8_t req[256]; + uint8_t resp[256]; + uint64_t size = 0u; + uint64_t fh = 0u; + uint64_t h; + size_t len = 0u; + size_t resp_len = 0u; + uint32_t comp = 1u; + uint32_t offset = 0u; + uint32_t total = 0u; + uint32_t split; + uint8_t op = 0u; + int done = 0; + int ret = 0; + + if ((g_mem == NULL) || !relay_reset()) { + check(0, "ranges: fixture"); + return; + } + c[0].address = page(PG_RW); + c[0].page_count = 2u; + h = relay_send(WT_FFA_MEM_OP_LEND, c, 1u, WT_FFA_MEM_PERM_DATA_RW, 0u, + &ret); + r[0].address = page(PG_RW); + r[0].page_count = 2u; + check(ret == 0 && + relay_retrieve_ranges(h, r, 1u, 0u, &resp_len, &comp) == 0 && + comp == 0u && + resp_len == (WT_FFA_MEM_TXN_HDR_SIZE + WT_FFA_MEM_ACCESS_SIZE_V12) && + access_of(&g_dom_b, PG_RW + 1u) == WT_DOMAIN_ACCESS_RW && + relay_relinquish(h, 0u) == 0, + "ranges: a borrower naming the region's own addresses is mapped there and answered with no composite"); + r[0].page_count = 1u; + r[1].address = page(PG_RW + 1u); + r[1].page_count = 1u; + check(relay_retrieve_ranges(h, r, 2u, 0u, &resp_len, &comp) == 0 && + access_of(&g_dom_b, PG_RW) == WT_DOMAIN_ACCESS_RW && + relay_relinquish(h, 0u) == 0, + "ranges: the same pages split into two ranges are mapped too"); + r[0].page_count = 3u; + check(relay_retrieve_ranges(h, r, 1u, 0u, &resp_len, &comp) == + WT_FFA_INVALID_PARAMETERS && + access_of(&g_dom_b, PG_RW) == WT_DOMAIN_ACCESS_NONE, + "ranges: a size other than the sender's is INVALID_PARAMETERS (1.11.3.3)"); + r[0].address = page(PG_B); + r[0].page_count = 2u; + check(relay_retrieve_ranges(h, r, 1u, 0u, &resp_len, &comp) == + WT_FFA_INVALID_PARAMETERS && + access_of(&g_dom_b, PG_RW) == WT_DOMAIN_ACCESS_NONE, + "ranges: addresses the relayer cannot map the region at are INVALID_PARAMETERS"); + r[0].address = page(PG_RW); + check(relay_retrieve_ranges(h, r, 1u, 1u << 9, &resp_len, &comp) == + WT_FFA_INVALID_PARAMETERS && + access_of(&g_dom_b, PG_RW) == WT_DOMAIN_ACCESS_NONE, + "ranges: an alignment hint with them is INVALID_PARAMETERS (Table 1.22)"); + check(wt_spm_mem_reclaim(h, RELAY_ID_A, 0u) == 0, "ranges: the lend ends"); + + (void)wt_ffa_mem_retrieve_req_build(req, sizeof(req), 0x77ull, RELAY_ID_A, + RELAY_ID_B, WT_FFA_MEM_PERM_DATA_RW, + &len); + put32(&req[WT_FFA_MEM_TXN_HDR_SIZE + WT_FFA_MEM_ACC_OFF_COMP_OFF], + (uint32_t)len); + (void)memset(&req[len], 0, 32u); + put32(&req[len + WT_FFA_MEM_COMP_OFF_PAGES], 1u); + put32(&req[len + WT_FFA_MEM_COMP_OFF_COUNT], 1u); + put64(&req[len + 16u], 0x40000000ull); + put32(&req[len + 24u], 1u); + check(wt_ffa_mem_retrieve_req_parse_ex(req, len + 32u, &rq) == 0 && + rq.range_count == 1u && rq.range_index == 0u && + rq.ranges[0].address == 0x40000000ull && + rq.ranges[0].page_count == 1u && + wt_ffa_mem_frag_expected(req, (uint32_t)len + 20u, 1, &size) == 1 && + size == (uint64_t)(len + 32u), + "ranges: a retrieve request's own composite is parsed and names its whole length to a first fragment"); + ret = 0; + for (split = WT_FFA_MEM_TXN_HDR_SIZE; split < (uint32_t)len + 32u; split++) { + size = 0u; + if ((wt_ffa_mem_frag_expected(req, split, 1, &size) != 0) && + (size != (uint64_t)len + 32u)) { + ret = -1; + } + done = 0; + if ((wt_spm_mem_frag_begin(WT_SPM_MEM_FRAG_OP_RETRIEVE, RELAY_ID_B, + req, split, (uint32_t)len + 32u, &fh) != 0) || + (wt_spm_mem_frag_next(fh, RELAY_ID_B, &req[split], + (uint32_t)len + 32u - split, &offset, + &done) != 0) || + (done != 1)) { + ret = -1; + } + wt_spm_mem_frag_release(fh, RELAY_ID_B); + } + check(ret == 0, + "ranges: a retrieve request naming its ranges is taken at every first-fragment boundary, a header-only one included"); + put32(&req[len + WT_FFA_MEM_COMP_OFF_PAGES], 2u); + check(wt_ffa_mem_retrieve_req_parse_ex(req, len + 32u, &rq) == + WT_FFA_INVALID_PARAMETERS, + "ranges: a composite whose page total is not its ranges' is INVALID_PARAMETERS"); + put32(&req[WT_FFA_MEM_TXN_OFF_ACC_SIZE], 0u); + put32(&req[WT_FFA_MEM_TXN_OFF_ACC_COUNT], 0xFFFFFFFFu); + put32(&req[WT_FFA_MEM_TXN_HDR_SIZE + WT_FFA_MEM_ACC_OFF_COMP_OFF], 0u); + check(wt_spm_mem_frag_begin(WT_SPM_MEM_FRAG_OP_RETRIEVE, RELAY_ID_B, req, + (uint32_t)len, (uint32_t)len + 32u, &fh) == 0 && + wt_spm_mem_frag_next(fh, RELAY_ID_B, &req[len], 32u, &offset, + &done) == 0 && done == 1, + "ranges: a fragmented retrieve request with a zero access descriptor size is taken without walking it"); + whole = wt_spm_mem_frag_desc(fh, RELAY_ID_B, &total, &op); + check(whole != NULL && + wt_spm_mem_retrieve(whole, total, RELAY_ID_B, resp, sizeof(resp), + &resp_len) == WT_FFA_NOT_SUPPORTED, + "ranges: and the whole request is then refused (NOT_SUPPORTED)"); + wt_spm_mem_frag_release(fh, RELAY_ID_B); + check(g_domain_fails == 0u, "ranges: no domain operation failed closed"); +} + +/* A window holding a 128 MB boundary (the largest a hint asks for) with room + * for the fixture's pages around it; mapped lazily, so only those are backed. */ +#define ALIGN_WINDOW (0x8000000ull + 0x80000ull) +#define ALIGN_128M 0x8000000ull +#define ALIGN_VALID (1u << 9) + +/* With the fixture moved so A's first lendable page sits at base, A lends it + * to B and B retrieves it asking for alignment hint n: the retrieve's answer, + * or -99 if the fixture fails. The lend ends either way. */ +static int relay_align_at(uint64_t base, uint32_t n) +{ + wt_ffa_mem_constituent_t c[1]; + uint64_t h = 0u; + int sent = -1; + int ret = -99; + + g_mem = (uint8_t*)(uintptr_t)(base - ((uint64_t)PG_FILL * + WT_TABLES_PAGE_SIZE)); + c[0].address = base; + c[0].page_count = 1u; + if (relay_reset()) { + h = relay_send(WT_FFA_MEM_OP_LEND, c, 1u, WT_FFA_MEM_PERM_DATA_RW, 0u, + &sent); + } + if (sent == 0) { + ret = relay_retrieve(h, OWN_RW, ALIGN_VALID | (n << 5)); + if ((ret == 0) && (relay_relinquish(h, 0u) != 0)) { + ret = -99; + } + if (wt_spm_mem_reclaim(h, RELAY_ID_A, 0u) != 0) { + ret = -99; + } + } + return ret; +} + +/* WT-FFA-0009 (a retrieve's alignment hint n asks for a 2^n x 4 KB boundary, + * DEN0140 Table 1.22 bits[8:5], and a region off it is DENIED). */ +static void relay_align_rows(void) +{ + uint8_t* saved = g_mem; + uint8_t* win; + uint64_t x; + uint64_t d; + uint64_t y = 0u; + uint64_t z = 0u; + + if (g_mem == NULL) { + check(0, "align: fixture"); + return; + } + win = low_pages((size_t)ALIGN_WINDOW); + check(win != NULL, + "align: the host backs a window holding a 128 MB boundary below the table VA limit"); + if (win == NULL) { + return; + } + x = ((uint64_t)(uintptr_t)win + (2u * WT_TABLES_PAGE_SIZE) + ALIGN_128M - + 1u) & ~(ALIGN_128M - 1u); + /* y sits on a 24 KB boundary but not a 32 KB one, z on a 120 KB one. */ + for (d = WT_TABLES_PAGE_SIZE; d < 0x40000u; d += WT_TABLES_PAGE_SIZE) { + if ((y == 0u) && (((x + d) % 0x6000u) == 0u) && + (((x + d) % 0x8000u) != 0u)) { + y = x + d; + } + if ((z == 0u) && (((x + d) % 0x1E000u) == 0u)) { + z = x + d; + } + } + check(relay_align_at(x, 0u) == 0 && relay_align_at(x, 1u) == 0 && + relay_align_at(x, 3u) == 0 && relay_align_at(x, 15u) == 0, + "align: a region on a 128 MB boundary is mapped for hints 0, 1, 3, and 15"); + check(relay_align_at(x + WT_TABLES_PAGE_SIZE, 0u) == 0 && + relay_align_at(x + WT_TABLES_PAGE_SIZE, 1u) == WT_FFA_DENIED, + "align: hint 0 asks for 4 KB, which every page meets, and hint 1 for 8 KB"); + check(y != 0u && relay_align_at(y, 1u) == 0 && + relay_align_at(y, 3u) == WT_FFA_DENIED, + "align: hint 3 asks for 32 KB, not 24 KB, so a page on a 24 KB boundary only is DENIED"); + check(z != 0u && relay_align_at(z, 15u) == WT_FFA_DENIED, + "align: hint 15 asks for 128 MB, not 120 KB, so a page on a 120 KB boundary is DENIED"); + (void)munmap(win, (size_t)ALIGN_WINDOW); + g_mem = saved; + check(relay_reset() && g_domain_fails == 0u, + "align: the fixture is back on its own pages"); +} + +/* WT-FFA-0009 (a donate makes the receiver the owner, Owner-EA, 2.4.1.2 item + * 12: its permission and RX/TX calls treat the page as its own; an owner + * cannot change the access of memory a transaction covers, 1.3.1 rule 7). */ +static void relay_donated_perm_rows(void) +{ + wt_ffa_mem_constituent_t c[1]; + uint32_t perm = 0u; + uint64_t h; + int ret = 0; + + if ((g_mem == NULL) || !relay_reset()) { + check(0, "donated perm: fixture"); + return; + } + (void)memset(&g_relay_mailbox, 0, sizeof(g_relay_mailbox)); + c[0].address = page(PG_RW); + c[0].page_count = 1u; + h = relay_send_from(WT_FFA_MEM_OP_DONATE, c, 1u, RELAY_ID_A, RELAY_ID_B, + WT_FFA_MEM_PERM_DATA_NOT_SPEC, &ret); + check(ret == 0 && + relay_retrieve_by(h, RELAY_ID_A, RELAY_ID_B, + OWN_RW) == 0 && + wt_spm_mem_perm_get(&g_dom_b, page(PG_RW), &perm) == 0 && + perm == (WT_FFA_PERM_DATA_RW | WT_FFA_PERM_XN), + "donated perm: the receiver reads back the page it was donated"); + check(wt_spm_mem_perm_set(&g_dom_b, &g_relay_mailbox, page(PG_RW), 1u, + WT_FFA_PERM_DATA_RO | WT_FFA_PERM_XN) == 0 && + access_of(&g_dom_b, PG_RW) == WT_DOMAIN_ACCESS_RO && + wt_spm_mem_perm_set(&g_dom_b, &g_relay_mailbox, page(PG_RW), 1u, + WT_FFA_PERM_DATA_RW | WT_FFA_PERM_XN) == 0 && + access_of(&g_dom_b, PG_RW) == WT_DOMAIN_ACCESS_RW, + "donated perm: and re-permissions it as its own"); + check(wt_spm_mem_rxtx_ok(&g_dom_b, page(PG_RW)) != 0 && + wt_spm_mem_rxtx_ok(&g_dom_b, page(PG_B)) != 0, + "donated perm: it may map the donated page, like its own, as an RX/TX buffer"); + check(wt_spm_mem_perm_get(&g_dom_a, page(PG_RW), &perm) == 0 && + perm == (WT_FFA_PERM_DATA_NONE | WT_FFA_PERM_XN) && + wt_spm_mem_perm_set(&g_dom_a, &g_relay_mailbox, page(PG_RW), 1u, + WT_FFA_PERM_DATA_RW | WT_FFA_PERM_XN) == + WT_FFA_INVALID_PARAMETERS && + wt_spm_mem_rxtx_ok(&g_dom_a, page(PG_RW)) == 0, + "donated perm: the donor has no access left to read, set, or map"); + c[0].address = page(PG_FILL2); + h = relay_send_from(WT_FFA_MEM_OP_LEND, c, 1u, RELAY_ID_A, RELAY_ID_B, + WT_FFA_MEM_PERM_DATA_RW, &ret); + check(ret == 0 && + relay_retrieve_by(h, RELAY_ID_A, RELAY_ID_B, + OWN_RW) == 0 && + wt_spm_mem_perm_get(&g_dom_b, page(PG_FILL2), &perm) == + WT_FFA_INVALID_PARAMETERS && + wt_spm_mem_perm_set(&g_dom_b, &g_relay_mailbox, page(PG_FILL2), 1u, + WT_FFA_PERM_DATA_RO | WT_FFA_PERM_XN) == + WT_FFA_INVALID_PARAMETERS && + wt_spm_mem_rxtx_ok(&g_dom_b, page(PG_FILL2)) == 0, + "donated perm: a page it only borrows is never its own"); + check(relay_relinquish_as(h, RELAY_ID_B) == 0 && + wt_spm_mem_reclaim(h, RELAY_ID_A, 0u) == 0, + "donated perm: the lend ends"); + c[0].address = page(PG_RX); + check(wt_spm_mem_perm_set(&g_dom_a, &g_relay_mailbox, page(PG_RX), 1u, + WT_FFA_PERM_DATA_RW | WT_FFA_PERM_XN) == 0, + "donated perm: the owner opens its image page for writing"); + h = relay_send_from(WT_FFA_MEM_OP_SHARE, c, 1u, RELAY_ID_A, RELAY_ID_B, + WT_FFA_MEM_PERM_DATA_RO, &ret); + check(ret == 0 && + wt_spm_mem_perm_set(&g_dom_a, &g_relay_mailbox, page(PG_RX), 1u, + WT_FFA_PERM_DATA_NONE) == + WT_FFA_INVALID_PARAMETERS && + access_of(&g_dom_a, PG_RX) == WT_DOMAIN_ACCESS_RW && + wt_spm_mem_reclaim(h, RELAY_ID_A, 0u) == 0, + "donated perm: an owner cannot change the access of memory it shares until it reclaims it (1.3.1 rule 7)"); + wt_spm_mem_ns_window(page(PG_NS), 2u * WT_TABLES_PAGE_SIZE); + c[0].address = page(PG_NS); + h = relay_send_from(WT_FFA_MEM_OP_DONATE, c, 1u, WT_FFA_ID_NS_PRIMARY, + RELAY_ID_B, WT_FFA_MEM_PERM_DATA_NOT_SPEC, &ret); + check(ret == 0 && + relay_retrieve_by(h, WT_FFA_ID_NS_PRIMARY, RELAY_ID_B, + OWN_RW) == 0 && + wt_spm_mem_perm_get(&g_dom_b, page(PG_NS), &perm) == 0 && + perm == (WT_FFA_PERM_DATA_RW | WT_FFA_PERM_XN) && + wt_spm_mem_rxtx_ok(&g_dom_b, page(PG_NS)) == 0, + "donated perm: Non-secure memory donated to it is its own but never an RX/TX buffer (DEN0077A 7.2.2.2 rule 3)"); + check(wt_spm_mem_perm_set(&g_dom_b, &g_relay_mailbox, page(PG_NS), 1u, + WT_FFA_PERM_DATA_RO | WT_FFA_PERM_XN) == 0 && + access_of(&g_dom_b, PG_NS) == WT_DOMAIN_ACCESS_RO && + wt_spm_mem_perm_set(&g_dom_b, &g_relay_mailbox, page(PG_NS), 1u, + WT_FFA_PERM_DATA_NONE) == 0 && + access_of(&g_dom_b, PG_NS) == WT_DOMAIN_ACCESS_NONE && + wt_spm_mem_ns_owns(page(PG_NS), WT_TABLES_PAGE_SIZE) == 0 && + wt_spm_mem_perm_get(&g_dom_b, page(PG_NS), &perm) == 0 && + perm == (WT_FFA_PERM_DATA_NONE | WT_FFA_PERM_XN) && + wt_spm_mem_perm_set(&g_dom_b, &g_relay_mailbox, page(PG_NS), 1u, + WT_FFA_PERM_DATA_RW | WT_FFA_PERM_XN) == 0 && + access_of(&g_dom_b, PG_NS) == WT_DOMAIN_ACCESS_RW && + ns_of(&g_dom_b, PG_NS) != 0 && + wt_spm_mem_rxtx_ok(&g_dom_b, page(PG_NS)) == 0, + "donated perm: it re-permissions donated Non-secure memory read-only, no access (still not the Normal world's), and read-write, still Non-secure and never an RX/TX buffer"); + check(wt_spm_mem_perm_set(&g_dom_b, &g_relay_mailbox, page(PG_NS), 1u, + WT_FFA_PERM_DATA_RO) == + WT_FFA_INVALID_PARAMETERS && + access_of(&g_dom_b, PG_NS) == WT_DOMAIN_ACCESS_RW && + wt_spm_mem_perm_get(&g_dom_b, page(PG_NS), &perm) == 0 && + perm == (WT_FFA_PERM_DATA_RW | WT_FFA_PERM_XN), + "donated perm: but never makes it executable, and a refused change leaves it as it was"); + check(g_domain_fails == 0u, "donated perm: no domain operation failed closed"); +} + +/* WT-FFA-0009 (later fragments come through the buffer the first one did, + * DEN0140 4.1.2 rule 6: a sender that unmaps it has its transfer aborted and + * told so, rule 8). */ +static void relay_frag_abort_rows(void) +{ + wt_ffa_mem_constituent_t c[1]; + uint8_t desc[256]; + uint64_t fh = 0u; + uint64_t other = 0u; + size_t len = 0u; + uint32_t offset = 0u; + uint32_t total = 0u; + uint8_t op = 0u; + int done = 0; + + if ((g_mem == NULL) || !relay_reset()) { + check(0, "frag abort: fixture"); + return; + } + c[0].address = page(PG_RW); + c[0].page_count = 1u; + (void)relay_build(desc, sizeof(desc), WT_FFA_MEM_OP_LEND, c, 1u, + WT_FFA_MEM_PERM_DATA_RW, 0u, &len); + check(wt_spm_mem_frag_begin((uint8_t)WT_FFA_MEM_OP_LEND, RELAY_ID_A, desc, + 40u, (uint32_t)len, &fh) == 0 && + wt_spm_mem_frag_begin((uint8_t)WT_FFA_MEM_OP_LEND, RELAY_ID_B, desc, + 40u, (uint32_t)len, &other) == 0, + "frag abort: two senders each start a descriptor in fragments"); + wt_spm_mem_frag_abort(RELAY_ID_A); + check(wt_spm_mem_frag_next(fh, RELAY_ID_A, &desc[40], (uint32_t)len - 40u, + &offset, &done) == WT_FFA_ABORTED && + done == 0 && + wt_spm_mem_frag_next(fh, RELAY_ID_A, &desc[40], (uint32_t)len - 40u, + &offset, &done) == WT_FFA_INVALID_PARAMETERS && + wt_spm_mem_in_transaction(page(PG_RW), WT_TABLES_PAGE_SIZE) == 0 && + access_of(&g_dom_a, PG_RW) == WT_DOMAIN_ACCESS_RW, + "frag abort: the sender that unmapped its TX buffer is told ABORTED once, and nothing was sent"); + check(wt_spm_mem_frag_next(fh, RELAY_ID_A, NULL, 0u, &offset, &done) == + WT_FFA_INVALID_PARAMETERS, + "frag abort: the aborted handle names no transfer afterwards"); + check(wt_spm_mem_frag_next(other, RELAY_ID_B, &desc[40], + (uint32_t)len - 40u, &offset, &done) == 0 && + done == 1 && + wt_spm_mem_frag_desc(other, RELAY_ID_B, &total, &op) != NULL, + "frag abort: another sender's transfer goes on"); + wt_spm_mem_frag_release(other, RELAY_ID_B); + check(wt_spm_mem_frag_begin((uint8_t)WT_FFA_MEM_OP_LEND, RELAY_ID_A, desc, + 40u, (uint32_t)len, &fh) == 0, + "frag abort: the sender may start over"); + wt_spm_mem_frag_abort(RELAY_ID_A); + check(wt_spm_mem_frag_next(fh, RELAY_ID_A, NULL, (uint32_t)len - 40u, + &offset, &done) == WT_FFA_ABORTED && + wt_spm_mem_frag_desc(fh, RELAY_ID_A, &total, &op) == NULL, + "frag abort: ABORTED even when no TX buffer is mapped to read the fragment from"); + check(g_domain_fails == 0u, "frag abort: no domain operation failed closed"); +} + +/* DEN0140 4.1.2 rules 6 and 9: a sender's unfinished transfer keeps its TX + * buffer busy and is never dropped for a new one; the Normal world keeps a + * slot no partition can take. */ +static void relay_frag_busy_rows(void) +{ + wt_ffa_mem_constituent_t c[1]; + uint8_t desc[256]; + uint64_t fh = 0u; + uint64_t again = 0u; + uint64_t fb = 0u; + uint64_t fc = 0u; + uint64_t fns = 0u; + size_t len = 0u; + uint32_t offset = 0u; + uint32_t total = 0u; + uint8_t op = 0u; + int done = 0; + + if ((g_mem == NULL) || !relay_reset()) { + check(0, "frag busy: fixture"); + return; + } + c[0].address = page(PG_RW); + c[0].page_count = 1u; + (void)relay_build(desc, sizeof(desc), WT_FFA_MEM_OP_LEND, c, 1u, + WT_FFA_MEM_PERM_DATA_RW, 0u, &len); + check(wt_spm_mem_frag_begin((uint8_t)WT_FFA_MEM_OP_LEND, RELAY_ID_A, desc, + 40u, (uint32_t)len, &fh) == 0 && + wt_spm_mem_frag_begin((uint8_t)WT_FFA_MEM_OP_LEND, RELAY_ID_A, desc, + 40u, (uint32_t)len, &again) == WT_FFA_BUSY, + "frag busy: a second first fragment from a sender mid-transfer is BUSY"); + check(wt_spm_mem_frag_next(fh, RELAY_ID_A, &desc[40], (uint32_t)len - 40u, + &offset, &done) == 0 && done == 1 && + wt_spm_mem_frag_desc(fh, RELAY_ID_A, &total, &op) != NULL, + "frag busy: and the transfer it began goes on to the end"); + wt_spm_mem_frag_release(fh, RELAY_ID_A); + check(wt_spm_mem_frag_begin((uint8_t)WT_FFA_MEM_OP_LEND, RELAY_ID_A, desc, + 40u, (uint32_t)len, &fh) == 0 && + wt_spm_mem_frag_begin((uint8_t)WT_FFA_MEM_OP_LEND, RELAY_ID_B, desc, + 40u, (uint32_t)len, &fb) == 0 && + wt_spm_mem_frag_begin((uint8_t)WT_FFA_MEM_OP_LEND, RELAY_ID_C, desc, + 40u, (uint32_t)len, &fc) == WT_FFA_NO_MEMORY, + "frag busy: partitions share their own slots, a third is NO_MEMORY"); + check(wt_spm_mem_frag_begin((uint8_t)WT_FFA_MEM_OP_LEND, + WT_FFA_ID_NS_PRIMARY, desc, 40u, + (uint32_t)len, &fns) == 0 && + wt_spm_mem_frag_begin((uint8_t)WT_FFA_MEM_OP_LEND, + WT_FFA_ID_NS_PRIMARY, desc, 40u, + (uint32_t)len, &again) == WT_FFA_BUSY, + "frag busy: the Normal world still starts one, and is BUSY for a second"); + wt_spm_mem_frag_release(fh, RELAY_ID_A); + wt_spm_mem_frag_release(fb, RELAY_ID_B); + wt_spm_mem_frag_release(fns, WT_FFA_ID_NS_PRIMARY); + check(g_domain_fails == 0u, "frag busy: no domain operation failed closed"); +} + +/* WT-FFA-0009 (a copy the SPMC makes for the Normal world reaches only memory + * it still has: none it lent or donated, or that a partition now owns, DEN0140 + * Table 1.3; a page it shares keeps its access, read-only where the share + * named it so, 1.11.3.1). */ +static void relay_ns_access_rows(void) +{ + const uint64_t pg = WT_TABLES_PAGE_SIZE; + wt_ffa_mem_constituent_t c[1]; + wt_ffa_mem_build_t in; + uint8_t desc[256]; + uint64_t base; + uint64_t h; + size_t len = 0u; + int ret = 0; + + if ((g_mem == NULL) || !relay_reset()) { + check(0, "ns access: fixture"); + return; + } + wt_spm_mem_ns_window(page(PG_NS), 2u * WT_TABLES_PAGE_SIZE); + base = (uint64_t)page(PG_NS); + check(wt_spm_mem_ns_access(base + 16u, (2u * pg) - 32u, 0) == 1 && + wt_spm_mem_ns_access(base, 2u * pg, 1) == 1 && + wt_spm_mem_ns_access(base - 16u, 32u, 0) == 0 && + wt_spm_mem_ns_access(base + pg, pg + 1u, 1) == 0 && + wt_spm_mem_ns_access(0u, 0u, 1) == 1, + "ns access: the Normal world reads and writes its own window, nothing past it"); + c[0].address = page(PG_NS); + c[0].page_count = 1u; + h = relay_send_from(WT_FFA_MEM_OP_LEND, c, 1u, WT_FFA_ID_NS_PRIMARY, + RELAY_ID_B, WT_FFA_MEM_PERM_DATA_RW, &ret); + check(ret == 0 && wt_spm_mem_ns_access(base + 100u, 8u, 0) == 0 && + wt_spm_mem_ns_access(base + 100u, 8u, 1) == 0 && + wt_spm_mem_ns_access(base + pg - 4u, 8u, 0) == 0 && + wt_spm_mem_ns_access(base + pg, 8u, 1) == 1, + "ns access: a page it lent is neither read nor written for it, even by a span that only reaches into it"); + check(relay_retrieve_by(h, WT_FFA_ID_NS_PRIMARY, RELAY_ID_B, + OWN_RW) == 0 && + wt_spm_mem_ns_access(base, 8u, 0) == 0 && + relay_relinquish_as(h, RELAY_ID_B) == 0 && + wt_spm_mem_ns_access(base, 8u, 0) == 0 && + wt_spm_mem_reclaim(h, WT_FFA_ID_NS_PRIMARY, 0u) == 0 && + wt_spm_mem_ns_access(base, pg, 1) == 1, + "ns access: nor while a borrower maps it or may yet retrieve it; the reclaim gives it back"); + h = relay_send_from(WT_FFA_MEM_OP_SHARE, c, 1u, WT_FFA_ID_NS_PRIMARY, + RELAY_ID_B, WT_FFA_MEM_PERM_DATA_RW, &ret); + check(ret == 0 && wt_spm_mem_ns_access(base, 8u, 1) == 1 && + relay_retrieve_by(h, WT_FFA_ID_NS_PRIMARY, RELAY_ID_B, + WT_FFA_MEM_PERM_DATA_RW) == 0 && + wt_spm_mem_ns_access(base, 8u, 0) == 1 && + wt_spm_mem_ns_access(base, 8u, 1) == 1 && + relay_relinquish_as(h, RELAY_ID_B) == 0 && + wt_spm_mem_reclaim(h, WT_FFA_ID_NS_PRIMARY, 0u) == 0, + "ns access: a page it shares stays its to read and write, a borrower mapping it or not"); + (void)memset(&in, 0, sizeof(in)); + in.constituents = c; + in.constituent_count = 1u; + in.op = WT_FFA_MEM_OP_SHARE; + in.sender = WT_FFA_ID_NS_PRIMARY; + in.receiver = RELAY_ID_B; + in.permissions = WT_FFA_MEM_PERM_DATA_RW; + in.access_desc_size = (uint8_t)WT_FFA_MEM_ACCESS_SIZE; + ret = wt_ffa_mem_txn_build(desc, sizeof(desc), &in, &len); + if (ret == 0) { + len = add_receiver_as(desc, len, WT_FFA_ID_NS_PRIMARY, + WT_FFA_MEM_PERM_DATA_RO); + ret = wt_spm_mem_share(desc, len, WT_FFA_MEM_OP_SHARE, + WT_FFA_ID_NS_PRIMARY, &h); + } + check(ret == 0 && wt_spm_mem_ns_access(base, 8u, 0) == 1 && + wt_spm_mem_ns_access(base, 8u, 1) == 0 && + wt_spm_mem_reclaim(h, WT_FFA_ID_NS_PRIMARY, 0u) == 0 && + wt_spm_mem_ns_access(base, 8u, 1) == 1, + "ns access: a share naming it read-only leaves it reading until it reclaims"); + h = relay_send_from(WT_FFA_MEM_OP_DONATE, c, 1u, WT_FFA_ID_NS_PRIMARY, + RELAY_ID_B, WT_FFA_MEM_PERM_DATA_NOT_SPEC, &ret); + check(ret == 0 && wt_spm_mem_ns_access(base, 8u, 0) == 0 && + relay_retrieve_by(h, WT_FFA_ID_NS_PRIMARY, RELAY_ID_B, + OWN_RW) == 0 && + wt_spm_mem_in_transaction(base, pg) == 0 && + wt_spm_mem_ns_access(base, 8u, 0) == 0 && + wt_spm_mem_ns_access(base, 8u, 1) == 0 && + wt_spm_mem_ns_access(base + pg, 8u, 1) == 1, + "ns access: a page it donated is never its again, before or after the receiver takes it"); + check(wt_spm_mem_perm_set(&g_dom_b, &g_relay_mailbox, page(PG_NS), 1u, + WT_FFA_PERM_DATA_NONE) == 0 && + wt_spm_mem_ns_access(base, 8u, 0) == 0, + "ns access: nor once its new owner makes it no-access"); + check(g_domain_fails == 0u, "ns access: no domain operation failed closed"); +} + +/* WT-FFA-0009 (the Normal world can rewrite its TX buffer while the relayer + * reads it: what is registered is the descriptor that was validated, whole or + * reassembled from fragments, and one too large to copy is NO_MEMORY). */ +static void relay_ns_snapshot_rows(void) +{ + static uint8_t big[WT_FFA_MEM_FRAG_MAX + 16u]; + wt_ffa_mem_constituent_t c[1]; + wt_ffa_mem_build_t in; + uint8_t* recv; + uint8_t tx[256]; + uint64_t h = 0u; + uint32_t offset = 0u; + size_t len = 0u; + int done = 0; + int ret; + + if ((g_mem == NULL) || !relay_reset()) { + check(0, "ns snapshot: fixture"); + return; + } + wt_spm_mem_ns_window(page(PG_NS), 2u * WT_TABLES_PAGE_SIZE); + c[0].address = page(PG_NS); + c[0].page_count = 1u; + (void)memset(&in, 0, sizeof(in)); + in.constituents = c; + in.constituent_count = 1u; + in.op = WT_FFA_MEM_OP_LEND; + in.sender = WT_FFA_ID_NS_PRIMARY; + in.receiver = RELAY_ID_B; + in.permissions = WT_FFA_MEM_PERM_DATA_RW; + in.access_desc_size = (uint8_t)WT_FFA_MEM_ACCESS_SIZE; + ret = wt_ffa_mem_txn_build(tx, sizeof(tx), &in, &len); + recv = &tx[WT_FFA_MEM_TXN_HDR_SIZE + WT_FFA_MEM_ACC_OFF_RECEIVER]; + g_race_at = recv; + g_race_to = (uint8_t)(RELAY_ID_C & 0xFFu); + if (ret == 0) { + ret = wt_spm_mem_ns_send(WT_FFA_MEM_OP_LEND, tx, (uint32_t)len, + (uint32_t)len, &h); + } + check(ret == 0 && g_race_at == NULL && + relay_retrieve_by(h, WT_FFA_ID_NS_PRIMARY, RELAY_ID_C, + OWN_RW) != 0 && + relay_retrieve_by(h, WT_FFA_ID_NS_PRIMARY, RELAY_ID_B, + OWN_RW) == 0, + "ns snapshot: a descriptor rewritten mid-send lends to the borrower it was validated with"); + check(relay_relinquish_as(h, RELAY_ID_B) == 0 && + wt_spm_mem_reclaim(h, WT_FFA_ID_NS_PRIMARY, 0u) == 0, + "ns snapshot: and that borrower hands it back to the Normal world"); + *recv = (uint8_t)(RELAY_ID_B & 0xFFu); + ret = wt_spm_mem_ns_send(WT_FFA_MEM_OP_LEND, tx, 40u, (uint32_t)len, &h); + if (ret == 0) { + ret = wt_spm_mem_frag_next(h, WT_FFA_ID_NS_PRIMARY, &tx[40], + (uint32_t)len - 40u, &offset, &done); + } + g_race_at = recv; + if ((ret == 0) && (done == 1)) { + ret = wt_spm_mem_frag_share(h, WT_FFA_ID_NS_PRIMARY); + } + check(ret == 0 && g_race_at == NULL && + relay_retrieve_by(h, WT_FFA_ID_NS_PRIMARY, RELAY_ID_C, + OWN_RW) != 0 && + relay_retrieve_by(h, WT_FFA_ID_NS_PRIMARY, RELAY_ID_B, + OWN_RW) == 0 && + relay_relinquish_as(h, RELAY_ID_B) == 0 && + wt_spm_mem_reclaim(h, WT_FFA_ID_NS_PRIMARY, 0u) == 0, + "ns snapshot: so does one sent in fragments and rewritten once they are in"); + g_race_at = NULL; + (void)memcpy(big, tx, len); + check(wt_spm_mem_ns_send(WT_FFA_MEM_OP_LEND, big, (uint32_t)sizeof(big), + (uint32_t)sizeof(big), &h) == WT_FFA_NO_MEMORY && + wt_spm_mem_ns_send(WT_FFA_MEM_OP_LEND, big, + WT_FFA_MEM_FRAG_MAX + 1u, + WT_FFA_MEM_FRAG_MAX + 2u, &h) == + WT_FFA_NO_MEMORY && + wt_spm_mem_ns_send(WT_FFA_MEM_OP_LEND, tx, (uint32_t)len, + (uint32_t)len - 1u, &h) == + WT_FFA_INVALID_PARAMETERS && + wt_spm_mem_ns_send(WT_FFA_MEM_OP_LEND, tx, 0u, (uint32_t)len, &h) == + WT_FFA_INVALID_PARAMETERS && + wt_spm_mem_in_transaction(page(PG_NS), WT_TABLES_PAGE_SIZE) == 0, + "ns snapshot: past the Secure copy is NO_MEMORY, a bad length INVALID_PARAMETERS, nothing registered"); + check(g_domain_fails == 0u, "ns snapshot: no domain operation failed closed"); +} + +int main(void) +{ + printf("WT-FFA-0009 (FF-A memory transaction descriptors and handle state)\n"); + + fid_rows(); + txn_rows(); + reject_rows(); + access_offset_rows(); + rxtx_rows(); + mailbox_rows(); + tx_buffer_rows(); + registry_rows(); + share_rows(); + constituent_limit_rows(); + retrieve_rows(); + borrower_rows(); + frag_rows(); + retrieve_check_rows(); + time_slice_rows(); + send_handle_rows(); + attribute_rows(); + send_attribute_rows(); + access_flag_rows(); + borrower_list_rows(); + v10_rows(); + relay_rows(); + relay_owner_rows(); + relay_attr_rows(); + relay_perm_rows(); + relay_zero_rows(); + relay_multi_zero_rows(); + relay_own_access_rows(); + relay_clean_rows(); + relay_region_rows(); + relay_donate_rows(); + relay_mailbox_rows(); + relay_self_rows(); + relay_perm_set_rows(); + relay_icache_rows(); + relay_teardown_rows(); + relay_v10_rows(); + relay_unbind_rows(); + relay_ns_donate_rows(); + relay_donated_perm_rows(); + relay_access_size_rows(); + relay_range_rows(); + relay_align_rows(); + relay_frag_abort_rows(); + relay_frag_busy_rows(); + relay_ns_access_rows(); + relay_ns_snapshot_rows(); + + if (g_mem != NULL) { + (void)munmap(g_mem, (size_t)RELAY_MEM_PAGES * WT_TABLES_PAGE_SIZE); + } + printf("ffa_mem: %d checks, %d failures\n", checks, failures); + return (failures == 0) ? 0 : 1; +} diff --git a/tests/host/ffa_notif/Makefile b/tests/host/ffa_notif/Makefile new file mode 100644 index 00000000..197f9678 --- /dev/null +++ b/tests/host/ffa_notif/Makefile @@ -0,0 +1,62 @@ +# Makefile +# +# Copyright (C) 2026 wolfSSL Inc. +# +# This file is part of wolfTrust. +# +# wolfTrust 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. +# +# wolfTrust 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 + +ROOT := ../../.. + +CC ?= cc +BUILD_DIR ?= build + +CFLAGS := \ + -I$(ROOT)/include \ + -std=c99 -O0 -g -Wall -Wextra -Werror -pedantic +CFLAGS += $(EXTRA_CFLAGS) + +SRCS := $(ROOT)/src/arch/aarch64/ffa/ffa_notif.c main.c +TEST_BIN := $(BUILD_DIR)/test_ffa_notif + +.PHONY: all run compilers sanitize valgrind clean + +all: $(TEST_BIN) + +$(BUILD_DIR): + mkdir -p $@ + +$(TEST_BIN): $(SRCS) | $(BUILD_DIR) + $(CC) $(CFLAGS) $(EXTRA_LDFLAGS) -o $@ $(SRCS) + +run: $(TEST_BIN) + $(TEST_BIN) + +compilers: + $(MAKE) clean run CC=gcc BUILD_DIR=$(BUILD_DIR)/gcc + $(MAKE) clean run CC=clang BUILD_DIR=$(BUILD_DIR)/clang + +sanitize: + $(MAKE) clean run CC=clang \ + BUILD_DIR=$(BUILD_DIR)/sanitize \ + EXTRA_CFLAGS="-fsanitize=address,undefined -fno-omit-frame-pointer" \ + EXTRA_LDFLAGS="-fsanitize=address,undefined" + +valgrind: clean $(TEST_BIN) + valgrind --error-exitcode=1 --leak-check=full --show-leak-kinds=all \ + $(TEST_BIN) + +clean: + rm -rf $(BUILD_DIR) diff --git a/tests/host/ffa_notif/main.c b/tests/host/ffa_notif/main.c new file mode 100644 index 00000000..294aa201 --- /dev/null +++ b/tests/host/ffa_notif/main.c @@ -0,0 +1,687 @@ +/* main.c + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfTrust. + * + * wolfTrust 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. + * + * wolfTrust 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, see . + */ + +/* WT-FFA-0013: the FF-A v1.2 notification state machine (DEN0077A Ch.10) - + * bitmap lifecycle, bind/unbind, set/get by source class, info-get list + * encoding, and the schedule-receiver latch. The refusal rows transcribe the + * FF-A ACS notifications group error-path tests verbatim. */ + +#include "wolftrust/arch/aarch64/ffa_abi.h" +#include "wolftrust/arch/aarch64/ffa_notif.h" + +#include +#include + +#define VM0 0x0000u +#define SP1 0x8002u +#define SP2 0x8003u +#define SP3 0x8004u +#define BAD_ID 0xFFFFu + +#define IDS(sender, receiver) \ + ((uint32_t)(((uint32_t)(sender) << 16) | (uint32_t)(receiver))) +#define BIT(n) (1ull << (n)) + +static int checks; +static int failures; + +static void check(int ok, const char* what) +{ + checks++; + if (ok) { + printf(" [check] PASS %s\n", what); + } + else { + failures++; + printf(" [check] FAIL %s\n", what); + } +} + +/* The B5 partition shape: one Normal-world VM and three partitions. */ +static void fixture(void) +{ + wt_ffa_notif_reset(); + check(wt_ffa_notif_register(VM0, 0) == 0, "register the NS endpoint"); + check(wt_ffa_notif_register(SP1, 1) == 0, "register SP1"); + check(wt_ffa_notif_register(SP2, 1) == 0, "register SP2"); + check(wt_ffa_notif_register(SP3, 1) == 0, "register SP3"); +} + +/* The id values themselves live only in ffa_abi.h (the FID rule); these rows + * hold the shape: all in the FF-A range, destroy through info-get contiguous + * after bitmap-create, and the info-get pair differing only in convention. */ +static void fid_rows(void) +{ + printf("[suite] function ids\n"); + check(wt_ffa_fid_in_range(WT_FFA_NOTIFICATION_BITMAP_CREATE) != 0, + "BITMAP_CREATE is in range"); + check(WT_FFA_NOTIFICATION_BITMAP_DESTROY == + WT_FFA_NOTIFICATION_BITMAP_CREATE + 1u, "BITMAP_DESTROY follows"); + check(WT_FFA_NOTIFICATION_BIND == + WT_FFA_NOTIFICATION_BITMAP_CREATE + 2u, "BIND follows"); + check(WT_FFA_NOTIFICATION_UNBIND == + WT_FFA_NOTIFICATION_BITMAP_CREATE + 3u, "UNBIND follows"); + check(WT_FFA_NOTIFICATION_SET == + WT_FFA_NOTIFICATION_BITMAP_CREATE + 4u, "SET follows"); + check(WT_FFA_NOTIFICATION_GET == + WT_FFA_NOTIFICATION_BITMAP_CREATE + 5u, "GET follows"); + check(WT_FFA_NOTIFICATION_INFO_GET32 == + WT_FFA_NOTIFICATION_BITMAP_CREATE + 6u, "INFO_GET32 follows"); + check(WT_FFA_NOTIFICATION_INFO_GET64 == + (WT_FFA_NOTIFICATION_INFO_GET32 + 0x40000000u), + "INFO_GET64 is the SMC64 form"); +} + +static void register_rows(void) +{ + printf("[suite] endpoint registration\n"); + fixture(); + check(wt_ffa_notif_register(SP1, 1) != 0, "a duplicate id is refused"); +} + +static void bitmap_rows(void) +{ + wt_ffa_notif_get_result_t got; + + printf("[suite] bitmap lifecycle\n"); + fixture(); + check(wt_ffa_notif_bitmap_create(SP1, BAD_ID, 1u) == WT_FFA_NOT_SUPPORTED, + "create from a partition is NOT_SUPPORTED before anything else"); + check(wt_ffa_notif_bitmap_destroy(SP1, BAD_ID) == WT_FFA_NOT_SUPPORTED, + "destroy from a partition is NOT_SUPPORTED"); + check(wt_ffa_notif_bitmap_create(VM0, BAD_ID, 1u) == + WT_FFA_INVALID_PARAMETERS, "create with an unknown VM id is refused"); + check(wt_ffa_notif_bitmap_create(VM0, SP2, 1u) == + WT_FFA_INVALID_PARAMETERS, "create naming a partition is refused"); + check(wt_ffa_notif_bitmap_create(VM0, VM0, 0u) == + WT_FFA_INVALID_PARAMETERS, "create with zero contexts is refused"); + check(wt_ffa_notif_bitmap_destroy(VM0, BAD_ID) == + WT_FFA_INVALID_PARAMETERS, "destroy with an unknown VM id is refused"); + check(wt_ffa_notif_bitmap_destroy(VM0, VM0) == WT_FFA_DENIED, + "destroy before any create is DENIED"); + check(wt_ffa_notif_bitmap_create(VM0, VM0, 2u) == WT_FFA_NO_MEMORY, + "create for more vCPUs than the one context held is refused"); + check(wt_ffa_notif_bitmap_create(VM0, VM0, 0xFFFFFFFFu) == + WT_FFA_NO_MEMORY, "and so is the largest count"); + check(wt_ffa_notif_bind(VM0, IDS(SP1, VM0), 0u, BIT(3)) == WT_FFA_DENIED, + "the refused create left no bitmap to bind into"); + check(wt_ffa_notif_bitmap_create(VM0, VM0, 1u) == 0, "create succeeds"); + check(wt_ffa_notif_bitmap_create(VM0, VM0, 1u) == WT_FFA_DENIED, + "a second create is DENIED"); + check(wt_ffa_notif_bind(VM0, IDS(SP1, VM0), 0u, BIT(3)) == 0, + "the VM binds an id from SP1"); + check(wt_ffa_notif_set(SP1, IDS(SP1, VM0), 0u, BIT(3)) == 0, + "SP1 signals it"); + check(wt_ffa_notif_bitmap_destroy(VM0, VM0) == WT_FFA_DENIED, + "destroy with a pending notification is DENIED"); + check(wt_ffa_notif_get(VM0, VM0, WT_FFA_NOTIF_GET_FLAG_SP, &got) == 0, + "the VM drains the pending bit"); + check(got.from_sp == BIT(3), "the drained bitmap is the signaled bit"); + check(wt_ffa_notif_bitmap_destroy(VM0, VM0) == WT_FFA_DENIED, + "destroy with a bound notification is DENIED"); + check(wt_ffa_notif_unbind(VM0, IDS(SP1, VM0), 0u, BIT(3)) == 0, + "the refused destroy left the binding in place to unbind"); + check(wt_ffa_notif_bitmap_create(VM0, 0x10000u | VM0, 1u) == + WT_FFA_INVALID_PARAMETERS, + "create refuses w1 bits 31:16, which are MBZ for it"); + check(wt_ffa_notif_bitmap_destroy(VM0, 0xFFFF0000u | VM0) == 0, + "destroy succeeds once unbound and drained, ignoring the SBZ w1 " + "bits 31:16"); + check(wt_ffa_notif_bitmap_destroy(VM0, VM0) == WT_FFA_DENIED, + "destroy again is DENIED"); +} + +static void bind_rows(void) +{ + printf("[suite] bind\n"); + fixture(); + check(wt_ffa_notif_bind(VM0, IDS(BAD_ID, VM0), 0u, BIT(0)) == + WT_FFA_INVALID_PARAMETERS, "an unknown sender half is refused"); + check(wt_ffa_notif_bind(VM0, IDS(SP3, BAD_ID), 0u, BIT(0)) == + WT_FFA_INVALID_PARAMETERS, "an unknown receiver half is refused"); + check(wt_ffa_notif_bind(VM0, IDS(SP3, VM0), 0u, 0u) == + WT_FFA_INVALID_PARAMETERS, "an empty bitmap is refused"); + check(wt_ffa_notif_bind(VM0, IDS(VM0, VM0), 0u, BIT(0)) == + WT_FFA_INVALID_PARAMETERS, "sender equal to receiver is refused"); + check(wt_ffa_notif_bind(VM0, IDS(VM0, SP2), 0u, BIT(0)) == WT_FFA_DENIED, + "binding another endpoint's ids is DENIED"); + check(wt_ffa_notif_bind(VM0, IDS(SP1, VM0), 0u, BIT(0)) == WT_FFA_DENIED, + "a VM without a bitmap cannot bind"); + check(wt_ffa_notif_bitmap_create(VM0, VM0, 1u) == 0, "create the bitmap"); + check(wt_ffa_notif_bind(VM0, IDS(SP1, VM0), 0u, BIT(0) | BIT(1)) == 0, + "the VM binds two global ids from SP1"); + check(wt_ffa_notif_bind(VM0, IDS(SP3, VM0), 0u, BIT(1)) == WT_FFA_DENIED, + "an already-bound id is DENIED"); + check(wt_ffa_notif_bind(SP2, IDS(VM0, SP2), 0u, BIT(12)) == 0, + "a partition binds a global id from the VM"); + check(wt_ffa_notif_bind(SP2, IDS(VM0, SP2), WT_FFA_NOTIF_FLAG_PER_VCPU, + BIT(13)) == 0, + "a partition binds a per-vCPU id from the VM"); + check(wt_ffa_notif_bind(VM0, IDS(SP1, VM0), 0xFFFFFFFEu, BIT(20)) == 0, + "the Normal world's SBZ flag bits 31:1 are ignored"); + check(wt_ffa_notif_bind(SP2, IDS(VM0, SP2), 0xFFFFFFFEu, BIT(14)) == 0, + "a partition's SBZ flag bits 31:1 are ignored"); + check(wt_ffa_notif_set(VM0, IDS(VM0, SP2), 0u, BIT(14)) == 0, + "and the id is bound as the global one flag bit 0 asked for"); +} + +static void unbind_rows(void) +{ + wt_ffa_notif_get_result_t got; + + printf("[suite] unbind\n"); + fixture(); + check(wt_ffa_notif_bitmap_create(VM0, VM0, 1u) == 0, "create the bitmap"); + check(wt_ffa_notif_unbind(VM0, IDS(BAD_ID, VM0), 0u, BIT(0)) == + WT_FFA_INVALID_PARAMETERS, "an unknown sender half is refused"); + check(wt_ffa_notif_unbind(VM0, IDS(SP3, BAD_ID), 0u, BIT(0)) == + WT_FFA_INVALID_PARAMETERS, "an unknown receiver half is refused"); + check(wt_ffa_notif_unbind(VM0, IDS(SP3, VM0), 0x10u, 0u) == + WT_FFA_INVALID_PARAMETERS, + "a nonzero reserved word with an empty bitmap is refused"); + check(wt_ffa_notif_unbind(VM0, IDS(SP3, VM0), 0u, 0u) == + WT_FFA_INVALID_PARAMETERS, "an empty bitmap is refused"); + check(wt_ffa_notif_unbind(VM0, IDS(SP3, VM0), 0u, BIT(5)) == + WT_FFA_INVALID_PARAMETERS, "an unbound id is refused"); + check(wt_ffa_notif_bind(VM0, IDS(SP1, VM0), 0u, BIT(5)) == 0, + "bind an id from SP1"); + check(wt_ffa_notif_unbind(VM0, IDS(SP3, VM0), 0u, BIT(5)) == + WT_FFA_DENIED, "unbinding an id bound to another sender is DENIED"); + check(wt_ffa_notif_unbind(SP2, IDS(SP1, VM0), 0u, BIT(5)) == + WT_FFA_DENIED, "unbinding another endpoint's ids is DENIED"); + check(wt_ffa_notif_set(SP1, IDS(SP1, VM0), 0u, BIT(5)) == 0, + "SP1 signals the id"); + check(wt_ffa_notif_unbind(VM0, IDS(SP1, VM0), 0u, BIT(5)) == + WT_FFA_DENIED, "unbinding a pending id is DENIED"); + check(wt_ffa_notif_get(VM0, VM0, WT_FFA_NOTIF_GET_FLAG_SP, &got) == 0, + "the VM drains it"); + check(got.from_sp == BIT(5), "the refused unbind left it signaled"); + check(wt_ffa_notif_unbind(VM0, IDS(SP1, VM0), 0x10u, BIT(5)) == 0, + "the drained id unbinds and the reserved word is ignored"); + check(wt_ffa_notif_bind(VM0, IDS(SP3, VM0), 0u, BIT(5)) == 0, + "the freed id can be bound to a new sender"); + check(wt_ffa_notif_bind(SP2, IDS(VM0, SP2), 0u, BIT(6) | BIT(7)) == 0, + "SP2 binds two ids from the VM"); + check(wt_ffa_notif_set(VM0, IDS(VM0, SP2), 0u, BIT(7)) == 0, + "the VM signals one of them"); + check(wt_ffa_notif_unbind(SP2, IDS(VM0, SP2), 0u, BIT(6) | BIT(7)) == + WT_FFA_DENIED, "one pending id refuses the whole unbind"); + check(wt_ffa_notif_unbind(SP2, IDS(VM0, SP2), 0u, BIT(6)) == 0, + "the idle id was left bound and unbinds alone"); +} + +/* The notification_set error rows: SP2 holds bit 12 global and bit 13 + * per-vCPU, both from the VM, as its server half arranges. */ +static void set_rows(void) +{ + printf("[suite] set\n"); + fixture(); + check(wt_ffa_notif_bitmap_create(VM0, VM0, 1u) == 0, "create the bitmap"); + check(wt_ffa_notif_bind(SP2, IDS(VM0, SP2), 0u, BIT(12)) == 0, + "SP2 binds bit 12 global from the VM"); + check(wt_ffa_notif_bind(SP2, IDS(VM0, SP2), WT_FFA_NOTIF_FLAG_PER_VCPU, + BIT(13)) == 0, + "SP2 binds bit 13 per-vCPU from the VM"); + check(wt_ffa_notif_bind(VM0, IDS(SP1, VM0), 0u, BIT(0) | BIT(1)) == 0, + "the VM binds bits 0 and 1 global from SP1"); + check(wt_ffa_notif_set(VM0, IDS(VM0, BAD_ID), 0u, BIT(12)) == + WT_FFA_INVALID_PARAMETERS, "an unknown receiver is refused"); + check(wt_ffa_notif_set(VM0, IDS(BAD_ID, SP2), 0u, BIT(12)) == + WT_FFA_INVALID_PARAMETERS, "an unknown sender is refused"); + check(wt_ffa_notif_set(VM0, IDS(VM0, SP2), (2u << 16), BIT(13)) == + WT_FFA_INVALID_PARAMETERS, + "a vCPU id without the per-vCPU flag is refused"); + check(wt_ffa_notif_set(VM0, IDS(VM0, SP2), 0u, BIT(13)) == + WT_FFA_INVALID_PARAMETERS, + "a per-vCPU id signaled as global is refused"); + check(wt_ffa_notif_set(VM0, IDS(VM0, SP2), WT_FFA_NOTIF_FLAG_PER_VCPU, + BIT(12)) == WT_FFA_INVALID_PARAMETERS, + "a global id signaled as per-vCPU is refused"); + check(wt_ffa_notif_set(VM0, IDS(VM0, SP2), 0u, BIT(16)) == WT_FFA_DENIED, + "an unbound id is DENIED"); + check(wt_ffa_notif_set(VM0, IDS(VM0, SP2), 0x4u, BIT(12)) == + WT_FFA_INVALID_PARAMETERS, "reserved flag bits are refused"); + check(wt_ffa_notif_set(VM0, IDS(VM0, SP2), WT_FFA_NOTIF_FLAG_DELAY_SRI, + BIT(12)) == WT_FFA_INVALID_PARAMETERS, + "the delay-SRI hint from the Normal world is refused"); + check(wt_ffa_notif_set(VM0, IDS(VM0, SP2), + WT_FFA_NOTIF_FLAG_PER_VCPU | (1u << 16), BIT(13)) == + WT_FFA_INVALID_PARAMETERS, + "a vCPU beyond the single context is refused"); + check(wt_ffa_notif_set(SP1, IDS(VM0, SP2), 0u, BIT(12)) == WT_FFA_DENIED, + "a sender other than the caller is DENIED"); + check(wt_ffa_notif_set(SP3, IDS(SP3, VM0), 0u, BIT(0)) == WT_FFA_DENIED, + "an id bound to a different sender is DENIED"); + check(wt_ffa_notif_sri_take() == 0, "no schedule-receiver work yet"); + check(wt_ffa_notif_set(VM0, IDS(VM0, SP2), 0u, BIT(12)) == 0, + "the VM signals SP2's global id"); + check(wt_ffa_notif_sri_pending() == 1, "the signal latches the SRI"); + check(wt_ffa_notif_sri_take() == 1, "the latch reads once"); + check(wt_ffa_notif_sri_take() == 0, "and clears"); + check(wt_ffa_notif_set(VM0, IDS(VM0, SP2), 0u, BIT(12)) == 0, + "the VM signals the still-pending id again"); + check(wt_ffa_notif_sri_pending() == 0, + "which has no effect: no second SRI (10.5 rule 3)"); + check(wt_ffa_notif_set(VM0, IDS(VM0, SP2), + WT_FFA_NOTIF_FLAG_PER_VCPU, BIT(13)) == 0, + "the VM signals SP2's per-vCPU id on vCPU 0"); + check(wt_ffa_notif_sri_take() == 1, "the VM's signals latch the SRI"); + check(wt_ffa_notif_sri_take_now() == 0, + "a Normal-world signal never asks for it at once"); + check(wt_ffa_notif_set(SP1, IDS(SP1, VM0), WT_FFA_NOTIF_FLAG_DELAY_SRI, + BIT(0)) == 0, + "SP1 signals the VM with a delayed SRI"); + check(wt_ffa_notif_sri_take_now() == 0, + "a delayed signal does not assert the SRI as the call completes"); + check(wt_ffa_notif_sri_pending() == 1, + "it waits for the next Normal-world entry"); + check(wt_ffa_notif_set(SP1, IDS(SP1, VM0), 0u, BIT(0)) == 0, + "SP1 signals the still-pending id again without the delay hint"); + check(wt_ffa_notif_sri_take_now() == 0, + "which has no effect, so it asserts no SRI at once"); + check(wt_ffa_notif_set(SP1, IDS(SP1, VM0), 0u, BIT(1)) == 0, + "SP1 signals a new id without the delay hint"); + check(wt_ffa_notif_sri_take_now() == 1, + "the SRI is asserted as that call completes"); + check(wt_ffa_notif_sri_take_now() == 0, "once"); + check(wt_ffa_notif_sri_take() == 1, + "and the earlier delayed latch is still pending"); + check(wt_ffa_notif_sri_take() == 0, + "until the Normal-world entry takes it"); +} + +static void get_rows(void) +{ + wt_ffa_notif_get_result_t got; + + printf("[suite] get\n"); + fixture(); + check(wt_ffa_notif_bitmap_create(VM0, VM0, 1u) == 0, "create the bitmap"); + check(wt_ffa_notif_bind(VM0, IDS(SP1, VM0), 0u, BIT(0)) == 0, + "the VM binds bit 0 from SP1"); + check(wt_ffa_notif_bind(SP2, IDS(VM0, SP2), 0u, BIT(12)) == 0, + "SP2 binds bit 12 from the VM"); + check(wt_ffa_notif_get(VM0, BAD_ID, WT_FFA_NOTIF_GET_FLAG_VM, &got) == + WT_FFA_INVALID_PARAMETERS, "an unknown receiver is refused"); + check(wt_ffa_notif_get(VM0, IDS(BAD_ID, VM0), WT_FFA_NOTIF_GET_FLAG_VM, + &got) == WT_FFA_INVALID_PARAMETERS, + "a vCPU beyond the single context is refused"); + check(wt_ffa_notif_get(VM0, VM0, 0x10000u, &got) == + WT_FFA_INVALID_PARAMETERS, "reserved flag bits are refused"); + check(wt_ffa_notif_get(VM0, SP2, WT_FFA_NOTIF_GET_FLAG_SP, &got) == + WT_FFA_DENIED, "reading another endpoint's bitmap is DENIED"); + check(wt_ffa_notif_set(SP1, IDS(SP1, VM0), 0u, BIT(0)) == 0, + "SP1 signals the VM"); + check(wt_ffa_notif_set(VM0, IDS(VM0, SP2), 0u, BIT(12)) == 0, + "the VM signals SP2"); + check(wt_ffa_notif_get(VM0, VM0, WT_FFA_NOTIF_GET_FLAG_VM, &got) == 0, + "the VM-class flag is SBZ, so ignored, at the NS physical instance"); + check((got.from_vm == 0u) && (got.from_sp == 0u), + "and gets nothing without disturbing the pending bit"); + check((wt_ffa_notif_frame_rx_full(VM0, 1) == 0) && + (wt_ffa_notif_frame_rx_full(VM0, 0) == 0), + "both framework halves pend for the VM"); + check(wt_ffa_notif_get(VM0, VM0, WT_FFA_NOTIF_GET_FLAG_HYP | + WT_FFA_NOTIF_GET_FLAG_SPM, &got) == 0, + "the Hypervisor flag is SBZ there too"); + check(got.framework == WT_FFA_NOTIF_FW_SPM_RX_FULL, + "so only the SPM framework half comes back"); + check(wt_ffa_notif_get(VM0, VM0, WT_FFA_NOTIF_GET_FLAG_SP, &got) == 0, + "the VM drains the partition class"); + check(got.from_sp == BIT(0), "the signaled bit comes back"); + check(wt_ffa_notif_get(VM0, VM0, WT_FFA_NOTIF_GET_FLAG_SP, &got) == 0, + "a second drain succeeds"); + check(got.from_sp == 0u, "and is empty"); + check(wt_ffa_notif_get(SP2, SP2, WT_FFA_NOTIF_GET_FLAG_VM, &got) == 0, + "SP2 drains the VM class"); + check(got.from_vm == BIT(12), "the signaled bit comes back"); + check(wt_ffa_notif_frame_rx_full(BAD_ID, 1) == WT_FFA_INVALID_PARAMETERS, + "a framework signal to an unknown receiver is refused"); + check(wt_ffa_notif_frame_rx_full(SP2, 1) == 0, + "a Secure sender's message pends RX-full for SP2"); + check(wt_ffa_notif_get(SP2, SP2, WT_FFA_NOTIF_GET_FLAG_SPM, &got) == 0, + "the framework bitmap drains"); + check(got.framework == WT_FFA_NOTIF_FW_SPM_RX_FULL, + "a Secure sender's RX-full is bit 0"); + check(wt_ffa_notif_frame_rx_full(SP2, 0) == 0, + "a Normal-world message pends RX-full for SP2"); + check(wt_ffa_notif_get(SP2, SP2, WT_FFA_NOTIF_GET_FLAG_HYP, &got) == 0, + "the Hypervisor framework flag drains it"); + check(got.framework == WT_FFA_NOTIF_FW_NS_RX_FULL, + "a Normal-world sender's RX-full is bit 32"); + check(wt_ffa_notif_get(SP2, SP2, WT_FFA_NOTIF_GET_FLAG_SPM, &got) == 0, + "a second drain succeeds"); + check(got.framework == 0u, "and is empty"); + check((wt_ffa_notif_frame_rx_full(SP2, 1) == 0) && + (wt_ffa_notif_frame_rx_full(SP2, 0) == 0), + "both framework halves pend for SP2"); + check(wt_ffa_notif_get(SP2, SP2, WT_FFA_NOTIF_GET_FLAG_SPM, &got) == 0, + "the SPM framework flag alone drains"); + check(got.framework == WT_FFA_NOTIF_FW_SPM_RX_FULL, + "only the SPM half comes back"); + check(wt_ffa_notif_get(SP2, SP2, WT_FFA_NOTIF_GET_FLAG_HYP, &got) == 0, + "the Hypervisor framework flag alone drains"); + check(got.framework == WT_FFA_NOTIF_FW_NS_RX_FULL, + "the Hypervisor half was left pending for it"); + check(wt_ffa_notif_get(SP2, SP2, WT_FFA_NOTIF_GET_FLAG_SPM | + WT_FFA_NOTIF_GET_FLAG_HYP, &got) == 0, + "both framework flags drain"); + check(got.framework == 0u, "and both halves are empty"); + check(wt_ffa_notif_frame_rx_full(VM0, 1) == 0, + "a partition's message pends RX-full for the Normal world"); + check(wt_ffa_notif_get(VM0, VM0, WT_FFA_NOTIF_GET_FLAG_SPM, &got) == 0, + "the Normal world asks for the SPM framework half"); + check(got.framework == WT_FFA_NOTIF_FW_SPM_RX_FULL, + "and reads the partition's RX-full in w6"); +} + +static void info_rows(void) +{ + wt_ffa_notif_info_result_t info; + wt_ffa_notif_get_result_t got; + + printf("[suite] info-get\n"); + fixture(); + check(wt_ffa_notif_info_get(SP1, 1, &info) == WT_FFA_NOT_SUPPORTED, + "a partition may not ask"); + check(wt_ffa_notif_info_get(VM0, 1, &info) == WT_FFA_NO_DATA, + "nothing pending is NO_DATA"); + check(wt_ffa_notif_bind(SP2, IDS(VM0, SP2), 0u, BIT(12)) == 0, + "SP2 binds a global id"); + check(wt_ffa_notif_set(VM0, IDS(VM0, SP2), 0u, BIT(12)) == 0, + "the VM signals it"); + check(wt_ffa_notif_info_get(VM0, 1, &info) == 0, "the scheduler asks"); + check(info.w2 == WT_FFA_NOTIF_INFO_COUNT(1u), + "one all-global list and no more pending"); + check(info.regs[0] == SP2, "the list is the bare endpoint id"); + check(wt_ffa_notif_info_get(VM0, 1, &info) == WT_FFA_NO_DATA, + "a list already returned is not returned again"); + check(wt_ffa_notif_info_get(VM0, 0, &info) == WT_FFA_NO_DATA, + "nor through the other convention"); + check(wt_ffa_notif_set(VM0, IDS(VM0, SP2), 0u, BIT(12)) == 0, + "the VM signals the still-pending id again"); + check(wt_ffa_notif_info_get(VM0, 1, &info) == WT_FFA_NO_DATA, + "re-signaling a pending id adds no new list"); + check(wt_ffa_notif_get(SP2, SP2, WT_FFA_NOTIF_GET_FLAG_VM, &got) == 0, + "SP2 drains"); + check(wt_ffa_notif_info_get(VM0, 1, &info) == WT_FFA_NO_DATA, + "drained work disappears from the list"); + check(wt_ffa_notif_set(VM0, IDS(VM0, SP2), 0u, BIT(12)) == 0, + "the VM signals the drained id afresh"); + check(wt_ffa_notif_info_get(VM0, 1, &info) == 0, "the scheduler asks"); + check((info.w2 == WT_FFA_NOTIF_INFO_COUNT(1u)) && (info.regs[0] == SP2), + "a drain then a new signal re-arms the list"); + check(wt_ffa_notif_bind(SP2, IDS(VM0, SP2), WT_FFA_NOTIF_FLAG_PER_VCPU, + BIT(13)) == 0, "SP2 binds a per-vCPU id"); + check(wt_ffa_notif_set(VM0, IDS(VM0, SP2), WT_FFA_NOTIF_FLAG_PER_VCPU, + BIT(13)) == 0, + "the VM signals it while the global list is out"); + check(wt_ffa_notif_info_get(VM0, 1, &info) == 0, "the scheduler asks"); + check(info.w2 == (WT_FFA_NOTIF_INFO_COUNT(1u) | (1u << 12)), + "a newly pended id re-arms the list, now with one vCPU id"); + check(info.regs[0] == SP2, "endpoint id then vCPU 0"); + check(wt_ffa_notif_info_get(VM0, 1, &info) == WT_FFA_NO_DATA, + "and it too goes out once"); + check(wt_ffa_notif_frame_rx_full(SP2, 0) == 0, + "a message pends RX-full for SP2"); + check(wt_ffa_notif_info_get(VM0, 1, &info) == 0, "the scheduler asks"); + check((info.w2 == (WT_FFA_NOTIF_INFO_COUNT(1u) | (1u << 12))) && + (info.regs[0] == SP2), "every message re-arms its receiver"); + check(wt_ffa_notif_get(SP2, SP2, WT_FFA_NOTIF_GET_FLAG_ALL, &got) == 0, + "SP2 drains every class"); + check(wt_ffa_notif_info_get(VM0, 1, &info) == WT_FFA_NO_DATA, + "nothing is left to report"); + check(wt_ffa_notif_bind(SP3, IDS(VM0, SP3), WT_FFA_NOTIF_FLAG_PER_VCPU, + BIT(0)) == 0, + "SP3 binds per-vCPU the id RX-full shares"); + check(wt_ffa_notif_frame_rx_full(SP3, 1) == 0, + "a Secure sender's message pends RX-full for SP3"); + check(wt_ffa_notif_info_get(VM0, 1, &info) == 0, "the scheduler asks"); + check((info.w2 == WT_FFA_NOTIF_INFO_COUNT(1u)) && (info.regs[0] == SP3), + "a framework notification is global"); + check(wt_ffa_notif_get(SP3, SP3, WT_FFA_NOTIF_GET_FLAG_SPM, &got) == 0, + "SP3 drains it"); + check(wt_ffa_notif_bind(SP3, IDS(VM0, SP3), 0u, BIT(1)) == 0, + "SP3 binds a global id"); + check(wt_ffa_notif_bind(SP1, IDS(VM0, SP1), 0u, BIT(2)) == 0, + "SP1 binds a global id"); + check(wt_ffa_notif_set(VM0, IDS(VM0, SP3), 0u, BIT(1)) == 0 && + wt_ffa_notif_set(VM0, IDS(VM0, SP1), 0u, BIT(2)) == 0, + "the VM signals both"); + check(wt_ffa_notif_info_get(VM0, 0, &info) == 0, "the 32-bit form asks"); + check(info.w2 == WT_FFA_NOTIF_INFO_COUNT(2u), "two all-global lists"); + check((info.regs[0] == ((uint64_t)SP3 << 16 | SP1)) && + (info.regs[1] == 0u), "ids pack two to a word in table order"); +} + +/* n partitions behind the NS endpoint, each holding one pending id from the + * VM; returns how many were set up. */ +static unsigned int pend_many(unsigned int n, uint32_t flags) +{ + unsigned int i; + uint16_t id; + + wt_ffa_notif_reset(); + if (wt_ffa_notif_register(VM0, 0) != 0) { + return 0u; + } + for (i = 0u; i < n; i++) { + id = (uint16_t)(WT_FFA_ID_SP_FIRST + i); + if (wt_ffa_notif_register(id, 1) != 0) { + break; + } + if (wt_ffa_notif_bind(id, IDS(VM0, id), flags, BIT(0)) != 0) { + break; + } + if (wt_ffa_notif_set(VM0, IDS(VM0, id), flags, BIT(0)) != 0) { + break; + } + } + return i; +} + +static uint64_t pack(unsigned int first, unsigned int count) +{ + uint64_t v = 0u; + unsigned int i; + + for (i = 0u; i < count; i++) { + v |= (uint64_t)(WT_FFA_ID_SP_FIRST + first + i) << (16u * i); + } + return v; +} + +static void info_page_rows(void) +{ + wt_ffa_notif_info_result_t info; + uint64_t sizes = 0u; + unsigned int i; + + printf("[suite] info-get pagination\n"); + check(pend_many(11u, 0u) == 11u, + "eleven partitions each hold a global id"); + check(wt_ffa_notif_info_get(VM0, 0, &info) == 0, "the 32-bit form asks"); + check(info.w2 == (WT_FFA_NOTIF_INFO_COUNT(10u) | WT_FFA_NOTIF_INFO_MORE), + "ten lists fit and more remain"); + check((info.regs[0] == pack(0u, 2u)) && (info.regs[4] == pack(8u, 2u)), + "the ten ids fill w3 through w7"); + check(wt_ffa_notif_info_get(VM0, 0, &info) == 0, + "the scheduler asks again"); + check((info.w2 == WT_FFA_NOTIF_INFO_COUNT(1u)) && + (info.regs[0] == pack(10u, 1u)), + "the next call resumes at the eleventh list and clears more"); + check(wt_ffa_notif_info_get(VM0, 0, &info) == WT_FFA_NO_DATA, + "every list has gone out once"); + + check(pend_many(11u, 0u) == 11u, "the same eleven afresh"); + check(wt_ffa_notif_info_get(VM0, 1, &info) == 0, "the 64-bit form asks"); + check(info.w2 == WT_FFA_NOTIF_INFO_COUNT(11u), "all eleven fit"); + check((info.regs[0] == pack(0u, 4u)) && (info.regs[2] == pack(8u, 3u)), + "ids pack four to a doubleword"); + + check(pend_many(6u, WT_FFA_NOTIF_FLAG_PER_VCPU) == 6u, + "six partitions each hold a per-vCPU id"); + for (i = 0u; i < 5u; i++) { + sizes |= 1ull << (12u + (2u * i)); + } + check(wt_ffa_notif_info_get(VM0, 0, &info) == 0, "the 32-bit form asks"); + check(info.w2 == (WT_FFA_NOTIF_INFO_COUNT(5u) | sizes | + WT_FFA_NOTIF_INFO_MORE), + "five two-id lists fill the ten slots and more remain"); + check(wt_ffa_notif_info_get(VM0, 0, &info) == 0, + "the scheduler asks again"); + check((info.w2 == (WT_FFA_NOTIF_INFO_COUNT(1u) | (1u << 12))) && + (info.regs[0] == pack(5u, 1u)), "the sixth list follows alone"); +} + +/* An endpoint out of service: its own bindings and pending state go, and so + * does every binding naming it the sender; its id stays recognized, and a + * BIND/UNBIND naming it the sender or a SET to it answers the code it was + * retired with, ABORTED for one that aborted (Tables 16.12, 16.16, 16.20). */ +static void abort_rows(void) +{ + wt_ffa_notif_get_result_t got; + wt_ffa_notif_info_result_t info; + + printf("[suite] abort\n"); + fixture(); + check(wt_ffa_notif_bitmap_create(VM0, VM0, 1u) == 0 && + wt_ffa_notif_bind(VM0, IDS(SP1, VM0), 0u, BIT(0)) == 0 && + wt_ffa_notif_bind(SP1, IDS(VM0, SP1), 0u, BIT(3)) == 0 && + wt_ffa_notif_bind(SP2, IDS(SP3, SP2), 0u, BIT(4)) == 0 && + wt_ffa_notif_set(VM0, IDS(VM0, SP1), 0u, BIT(3)) == 0, + "SP1 binds from the VM and the VM from SP1, and the VM signals SP1"); + wt_ffa_notif_retire(SP1, WT_FFA_ABORTED); + check(wt_ffa_notif_set(VM0, IDS(VM0, SP1), 0u, BIT(3)) == WT_FFA_ABORTED, + "a SET to an endpoint that has aborted is ABORTED"); + check(wt_ffa_notif_bind(VM0, IDS(SP1, VM0), 0u, BIT(1)) == WT_FFA_ABORTED && + wt_ffa_notif_unbind(VM0, IDS(SP1, VM0), 0u, BIT(0)) == + WT_FFA_ABORTED, + "a BIND or UNBIND naming it the sender is ABORTED"); + check(wt_ffa_notif_bind(VM0, IDS(SP2, VM0), 0u, BIT(0)) == 0, + "the id it had bound at the VM is free for another sender"); + check(wt_ffa_notif_info_get(VM0, 1, &info) == WT_FFA_NO_DATA, + "nothing it had pending is listed for the scheduler"); + check(wt_ffa_notif_set(SP3, IDS(SP3, SP2), 0u, BIT(4)) == 0 && + wt_ffa_notif_get(SP2, IDS(0u, SP2), WT_FFA_NOTIF_GET_FLAG_SP, + &got) == 0 && got.from_sp == BIT(4), + "other endpoints' bindings go on"); + wt_ffa_notif_retire(SP2, WT_FFA_DENIED); + check(wt_ffa_notif_set(SP3, IDS(SP3, SP2), 0u, BIT(4)) == WT_FFA_DENIED, + "one that failed initialization answers DENIED instead"); +} + +/* What a retired sender had pended goes with the binding it rode on, in the + * class its world pends to, so no receiver later drains an unbound id. */ +static void abort_sender_rows(void) +{ + wt_ffa_notif_get_result_t got; + wt_ffa_notif_info_result_t info; + + printf("[suite] abort (sender)\n"); + fixture(); + check(wt_ffa_notif_bitmap_create(VM0, VM0, 1u) == 0 && + wt_ffa_notif_bind(VM0, IDS(SP1, VM0), 0u, BIT(0)) == 0 && + wt_ffa_notif_bind(VM0, IDS(SP2, VM0), 0u, BIT(1)) == 0 && + wt_ffa_notif_bind(SP3, IDS(SP1, SP3), 0u, BIT(2)) == 0 && + wt_ffa_notif_bind(SP3, IDS(VM0, SP3), 0u, BIT(3)) == 0, + "the VM and SP3 bind ids from SP1, SP2, and the VM"); + check(wt_ffa_notif_set(SP1, IDS(SP1, VM0), 0u, BIT(0)) == 0 && + wt_ffa_notif_set(SP1, IDS(SP1, SP3), 0u, BIT(2)) == 0 && + wt_ffa_notif_set(VM0, IDS(VM0, SP3), 0u, BIT(3)) == 0, + "SP1 signals the VM and SP3, and the VM signals SP3"); + wt_ffa_notif_retire(SP1, WT_FFA_ABORTED); + check(wt_ffa_notif_get(VM0, VM0, WT_FFA_NOTIF_GET_FLAG_SP, &got) == 0 && + got.from_sp == 0u, + "the VM drains nothing SP1 pended before it aborted"); + check(wt_ffa_notif_get(SP3, SP3, WT_FFA_NOTIF_GET_FLAG_SP, &got) == 0 && + got.from_sp == 0u, "nor does a partition"); + check(wt_ffa_notif_info_get(VM0, 1, &info) == 0 && + info.w2 == WT_FFA_NOTIF_INFO_COUNT(1u) && info.regs[0] == SP3, + "the scheduler is sent only to SP3, for the VM's id"); + check(wt_ffa_notif_get(SP3, SP3, WT_FFA_NOTIF_GET_FLAG_VM, &got) == 0 && + got.from_vm == BIT(3), "which another sender's binding kept pending"); + check(wt_ffa_notif_bitmap_destroy(VM0, VM0) == WT_FFA_DENIED && + wt_ffa_notif_unbind(VM0, IDS(SP2, VM0), 0u, BIT(1)) == 0 && + wt_ffa_notif_bitmap_destroy(VM0, VM0) == 0, + "the VM's bitmap is masked and non-pending once SP2 is unbound"); + check(wt_ffa_notif_bind(SP2, IDS(VM0, SP2), 0u, BIT(5)) == 0 && + wt_ffa_notif_set(VM0, IDS(VM0, SP2), 0u, BIT(5)) == 0, + "SP2 binds an id from the VM, which signals it"); + wt_ffa_notif_retire(VM0, WT_FFA_ABORTED); + check(wt_ffa_notif_get(SP2, SP2, WT_FFA_NOTIF_GET_FLAG_VM, &got) == 0 && + got.from_vm == 0u, "a retired VM's signal goes with it too"); +} + +/* RX-full lives in the receiver's framework bitmap: a VM has one only between + * its create and destroy (10.3), and nothing pends for it outside that. */ +static void frame_rows(void) +{ + wt_ffa_notif_get_result_t got; + wt_ffa_notif_info_result_t info; + + printf("[suite] framework bitmap\n"); + fixture(); + check(wt_ffa_notif_frame_ready(BAD_ID) == WT_FFA_INVALID_PARAMETERS, + "an unknown receiver is refused"); + check(wt_ffa_notif_frame_ready(VM0) == WT_FFA_DENIED && + wt_ffa_notif_frame_rx_full(VM0, 1) == WT_FFA_DENIED, + "a VM that never created its bitmap takes no RX-full"); + check(wt_ffa_notif_bitmap_create(VM0, VM0, 1u) == 0 && + wt_ffa_notif_frame_ready(VM0) == 0, "its create makes one"); + check(wt_ffa_notif_bitmap_destroy(VM0, VM0) == 0 && + wt_ffa_notif_frame_ready(VM0) == WT_FFA_DENIED, + "its destroy takes it away"); + (void)wt_ffa_notif_sri_take(); + check(wt_ffa_notif_frame_rx_full(VM0, 1) == WT_FFA_DENIED, + "so a partition's message after the destroy pends nothing"); + check(wt_ffa_notif_sri_take() == 0, "and raises no SRI"); + check(wt_ffa_notif_info_get(VM0, 1, &info) == WT_FFA_NO_DATA, + "nor names the VM to its scheduler"); + check(wt_ffa_notif_get(VM0, VM0, WT_FFA_NOTIF_GET_FLAG_SPM, &got) == 0 && + got.framework == 0u, "nor comes back from a GET"); + wt_ffa_notif_retire(SP1, WT_FFA_ABORTED); + check(wt_ffa_notif_frame_ready(SP1) == WT_FFA_DENIED && + wt_ffa_notif_frame_rx_full(SP1, 0) == WT_FFA_DENIED, + "a partition out of service takes none either"); + check(wt_ffa_notif_frame_ready(SP2) == 0, + "one in service has its bitmap from creation"); +} + +int main(void) +{ + printf("WT-FFA-0013 (FF-A notification bitmaps, binding, signaling)\n"); + + fid_rows(); + register_rows(); + bitmap_rows(); + bind_rows(); + unbind_rows(); + set_rows(); + get_rows(); + info_rows(); + info_page_rows(); + abort_rows(); + abort_sender_rows(); + frame_rows(); + + printf("ffa_notif: %d checks, %d failures\n", checks, failures); + return (failures == 0) ? 0 : 1; +} diff --git a/tests/host/ffa_runtime/Makefile b/tests/host/ffa_runtime/Makefile new file mode 100644 index 00000000..bc45016d --- /dev/null +++ b/tests/host/ffa_runtime/Makefile @@ -0,0 +1,62 @@ +# Makefile +# +# Copyright (C) 2026 wolfSSL Inc. +# +# This file is part of wolfTrust. +# +# wolfTrust 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. +# +# wolfTrust 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 + +ROOT := ../../.. + +CC ?= cc +BUILD_DIR ?= build + +CFLAGS := \ + -I$(ROOT)/include \ + -std=c99 -O0 -g -Wall -Wextra -Werror -pedantic +CFLAGS += $(EXTRA_CFLAGS) + +SRCS := $(ROOT)/src/arch/aarch64/ffa/ffa_runtime.c main.c +TEST_BIN := $(BUILD_DIR)/test_ffa_runtime + +.PHONY: all run compilers sanitize valgrind clean + +all: $(TEST_BIN) + +$(BUILD_DIR): + mkdir -p $@ + +$(TEST_BIN): $(SRCS) | $(BUILD_DIR) + $(CC) $(CFLAGS) $(EXTRA_LDFLAGS) -o $@ $(SRCS) + +run: $(TEST_BIN) + $(TEST_BIN) + +compilers: + $(MAKE) clean run CC=gcc BUILD_DIR=$(BUILD_DIR)/gcc + $(MAKE) clean run CC=clang BUILD_DIR=$(BUILD_DIR)/clang + +sanitize: + $(MAKE) clean run CC=clang \ + BUILD_DIR=$(BUILD_DIR)/sanitize \ + EXTRA_CFLAGS="-fsanitize=address,undefined -fno-omit-frame-pointer" \ + EXTRA_LDFLAGS="-fsanitize=address,undefined" + +valgrind: clean $(TEST_BIN) + valgrind --error-exitcode=1 --leak-check=full --show-leak-kinds=all \ + $(TEST_BIN) + +clean: + rm -rf $(BUILD_DIR) diff --git a/tests/host/ffa_runtime/main.c b/tests/host/ffa_runtime/main.c new file mode 100644 index 00000000..617ecfbf --- /dev/null +++ b/tests/host/ffa_runtime/main.c @@ -0,0 +1,300 @@ +/* main.c + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfTrust. + * + * wolfTrust 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. + * + * wolfTrust 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, see . + */ + +/* WT-FFA-0006 (runtime models): the FF-A partition runtime state machine of + * DEN0077A Ch.8. Every legal transition moves the partition to the expected + * state; every illegal one leaves the state untouched and reports DENIED + * (or BUSY for a direct request to a non-waiting receiver, §7.4). */ + +#include "wolftrust/arch/aarch64/ffa_abi.h" +#include "wolftrust/arch/aarch64/ffa_runtime.h" + +#include +#include + +static int checks; +static int failures; + +static void check(int ok, const char* what) +{ + checks++; + if (ok) { + printf(" [check] PASS %s\n", what); + } + else { + failures++; + printf(" [check] FAIL %s\n", what); + } +} + +/* One transition expectation: from `state` on `event`, expect return code + * `rc` and the state left at `next` (for an error, next == state). */ +struct row { + wt_ffa_rt_state_t state; + wt_ffa_rt_event_t event; + int rc; + wt_ffa_rt_state_t next; + const char* what; +}; + +static const struct row g_rows[] = { + /* FFA_RUN allocates cycles to a waiting, blocked, or preempted context. */ + { WT_FFA_RT_WAITING, WT_FFA_RT_EV_RUN, 0, WT_FFA_RT_RUNNING, + "RUN wakes a waiting partition into running" }, + { WT_FFA_RT_BLOCKED, WT_FFA_RT_EV_RUN, 0, WT_FFA_RT_RUNNING, + "RUN resumes a blocked partition" }, + { WT_FFA_RT_PREEMPTED, WT_FFA_RT_EV_RUN, 0, WT_FFA_RT_RUNNING, + "RUN resumes a preempted partition" }, + { WT_FFA_RT_RUNNING, WT_FFA_RT_EV_RUN, WT_FFA_DENIED, WT_FFA_RT_RUNNING, + "RUN on a running partition is DENIED" }, + + /* A direct request lands only on a waiting receiver; else BUSY. */ + { WT_FFA_RT_WAITING, WT_FFA_RT_EV_DIRECT_REQ, 0, WT_FFA_RT_RUNNING, + "a direct request enters a waiting receiver" }, + { WT_FFA_RT_RUNNING, WT_FFA_RT_EV_DIRECT_REQ, WT_FFA_BUSY, WT_FFA_RT_RUNNING, + "a direct request to a running receiver is BUSY" }, + { WT_FFA_RT_BLOCKED, WT_FFA_RT_EV_DIRECT_REQ, WT_FFA_BUSY, WT_FFA_RT_BLOCKED, + "a direct request to a blocked receiver is BUSY" }, + { WT_FFA_RT_PREEMPTED, WT_FFA_RT_EV_DIRECT_REQ, WT_FFA_BUSY, WT_FFA_RT_PREEMPTED, + "a direct request to a preempted receiver is BUSY" }, + + /* FFA_MSG_WAIT returns a running partition to waiting. */ + { WT_FFA_RT_RUNNING, WT_FFA_RT_EV_MSG_WAIT, 0, WT_FFA_RT_WAITING, + "MSG_WAIT returns a running partition to waiting" }, + { WT_FFA_RT_WAITING, WT_FFA_RT_EV_MSG_WAIT, WT_FFA_DENIED, WT_FFA_RT_WAITING, + "MSG_WAIT from waiting is DENIED" }, + { WT_FFA_RT_BLOCKED, WT_FFA_RT_EV_MSG_WAIT, WT_FFA_DENIED, WT_FFA_RT_BLOCKED, + "MSG_WAIT from blocked is DENIED" }, + { WT_FFA_RT_PREEMPTED, WT_FFA_RT_EV_MSG_WAIT, WT_FFA_DENIED, WT_FFA_RT_PREEMPTED, + "MSG_WAIT from preempted is DENIED" }, + + /* A direct response completes a request and returns to waiting. */ + { WT_FFA_RT_RUNNING, WT_FFA_RT_EV_DIRECT_RESP, 0, WT_FFA_RT_WAITING, + "a direct response returns a running partition to waiting" }, + { WT_FFA_RT_WAITING, WT_FFA_RT_EV_DIRECT_RESP, WT_FFA_DENIED, WT_FFA_RT_WAITING, + "a direct response from waiting is DENIED" }, + { WT_FFA_RT_BLOCKED, WT_FFA_RT_EV_DIRECT_RESP, WT_FFA_DENIED, WT_FFA_RT_BLOCKED, + "a direct response from blocked is DENIED" }, + + /* FFA_YIELD blocks a running partition. */ + { WT_FFA_RT_RUNNING, WT_FFA_RT_EV_YIELD, 0, WT_FFA_RT_BLOCKED, + "YIELD blocks a running partition" }, + { WT_FFA_RT_WAITING, WT_FFA_RT_EV_YIELD, WT_FFA_DENIED, WT_FFA_RT_WAITING, + "YIELD from waiting is DENIED" }, + { WT_FFA_RT_BLOCKED, WT_FFA_RT_EV_YIELD, WT_FFA_DENIED, WT_FFA_RT_BLOCKED, + "YIELD from blocked is DENIED" }, + { WT_FFA_RT_PREEMPTED, WT_FFA_RT_EV_YIELD, WT_FFA_DENIED, WT_FFA_RT_PREEMPTED, + "YIELD from preempted is DENIED" }, + + /* A physical interrupt preempts only a running partition. */ + { WT_FFA_RT_RUNNING, WT_FFA_RT_EV_INTERRUPT, 0, WT_FFA_RT_PREEMPTED, + "an interrupt preempts a running partition" }, + { WT_FFA_RT_WAITING, WT_FFA_RT_EV_INTERRUPT, WT_FFA_DENIED, WT_FFA_RT_WAITING, + "an interrupt event from waiting is DENIED" }, + { WT_FFA_RT_BLOCKED, WT_FFA_RT_EV_INTERRUPT, WT_FFA_DENIED, WT_FFA_RT_BLOCKED, + "an interrupt event from blocked is DENIED" }, + { WT_FFA_RT_PREEMPTED, WT_FFA_RT_EV_INTERRUPT, WT_FFA_DENIED, WT_FFA_RT_PREEMPTED, + "an interrupt event from preempted is DENIED" } +}; + +static void run_table(void) +{ + wt_ffa_rt_state_t s; + unsigned int i; + int rc; + + for (i = 0u; i < sizeof(g_rows) / sizeof(g_rows[0]); i++) { + s = g_rows[i].state; + rc = wt_ffa_rt_transition(&s, g_rows[i].event); + check(rc == g_rows[i].rc && s == g_rows[i].next, g_rows[i].what); + } +} + +/* A direct-request call chain threads the states end to end: a waiting SP is + * entered by a request, blocks on an outbound call, is resumed by RUN, + * responds, and lands back in waiting. */ +static void run_chain(void) +{ + wt_ffa_rt_state_t s = WT_FFA_RT_WAITING; + + check(wt_ffa_rt_transition(&s, WT_FFA_RT_EV_DIRECT_REQ) == 0 && + s == WT_FFA_RT_RUNNING, "chain: request enters the SP"); + check(wt_ffa_rt_transition(&s, WT_FFA_RT_EV_YIELD) == 0 && + s == WT_FFA_RT_BLOCKED, "chain: SP blocks on an outbound call"); + check(wt_ffa_rt_transition(&s, WT_FFA_RT_EV_DIRECT_REQ) == WT_FFA_BUSY && + s == WT_FFA_RT_BLOCKED, "chain: a request to the blocked SP is BUSY"); + check(wt_ffa_rt_transition(&s, WT_FFA_RT_EV_RUN) == 0 && + s == WT_FFA_RT_RUNNING, "chain: RUN resumes the blocked SP"); + check(wt_ffa_rt_transition(&s, WT_FFA_RT_EV_DIRECT_RESP) == 0 && + s == WT_FFA_RT_WAITING, "chain: the response returns it to waiting"); + + /* Preemption and resume of the same SP. */ + check(wt_ffa_rt_transition(&s, WT_FFA_RT_EV_RUN) == 0 && + s == WT_FFA_RT_RUNNING, "chain: RUN wakes it again"); + check(wt_ffa_rt_transition(&s, WT_FFA_RT_EV_INTERRUPT) == 0 && + s == WT_FFA_RT_PREEMPTED, "chain: an interrupt preempts it"); + check(wt_ffa_rt_transition(&s, WT_FFA_RT_EV_RUN) == 0 && + s == WT_FFA_RT_RUNNING, "chain: RUN resumes the preempted SP"); + check(wt_ffa_rt_transition(&s, WT_FFA_RT_EV_MSG_WAIT) == 0 && + s == WT_FFA_RT_WAITING, "chain: MSG_WAIT parks it back at waiting"); +} + +/* 8.5: an SP still initializing may message an SP that has initialized, and + * nothing that hands its cycles to another endpoint. */ +static void init_model_rows(void) +{ + check(wt_ffa_rt_init_call(WT_FFA_MSG_SEND_DIRECT_REQ32, 1) == 0 && + wt_ffa_rt_init_call(WT_FFA_MSG_SEND_DIRECT_REQ64, 1) == 0 && + wt_ffa_rt_init_call(WT_FFA_MSG_SEND_DIRECT_REQ2, 1) == 0, + "init: a direct request to an initialized SP is allowed (rule 1)"); + check(wt_ffa_rt_init_call(WT_FFA_MSG_SEND_DIRECT_REQ32, 0) == + WT_FFA_DENIED, + "init: a direct request to an SP not yet initialized is DENIED"); + check(wt_ffa_rt_init_call(WT_FFA_YIELD, 1) == WT_FFA_DENIED, + "init: FFA_YIELD is DENIED (rule 4)"); + check(wt_ffa_rt_init_call(WT_FFA_MSG_SEND_DIRECT_RESP32, 1) == + WT_FFA_DENIED && + wt_ffa_rt_init_call(WT_FFA_MSG_SEND_DIRECT_RESP2, 1) == + WT_FFA_DENIED, + "init: a direct response is DENIED (rule 5)"); + check(wt_ffa_rt_init_call(WT_FFA_RUN, 1) == WT_FFA_DENIED, + "init: FFA_RUN is DENIED (rule 6)"); + check(wt_ffa_rt_init_call(WT_FFA_MSG_WAIT, 0) == 0 && + wt_ffa_rt_init_call(WT_FFA_ERROR, 0) == 0 && + wt_ffa_rt_init_call(WT_FFA_PARTITION_INFO_GET, 0) == 0, + "init: FFA_MSG_WAIT, FFA_ERROR and setup calls are served"); +} + +/* The encodings a partition's FFA_SUCCESS (completing a direct request) and + * FFA_ERROR (failing its initialization) must carry. */ +static void status_encoding_rows(void) +{ + uint64_t x[18] = { 0 }; + + x[0] = WT_FFA_SUCCESS32; + x[4] = 0xFFFFFFFF00000000ull; + x[9] = 1u; + check(wt_ffa_rt_success_check(x) == 0, + "FFA_SUCCESS32 is judged on w1-w7 alone"); + x[3] = 1u; + check(wt_ffa_rt_success_check(x) == WT_FFA_INVALID_PARAMETERS, + "FFA_SUCCESS32 with a nonzero w3 is INVALID_PARAMETERS"); + x[0] = WT_FFA_SUCCESS64; + x[3] = 0u; + x[4] = 0u; + check(wt_ffa_rt_success_check(x) == WT_FFA_INVALID_PARAMETERS, + "FFA_SUCCESS64 with a nonzero x9 is INVALID_PARAMETERS"); + x[9] = 0u; + x[17] = 0x100000000ull; + check(wt_ffa_rt_success_check(x) == WT_FFA_INVALID_PARAMETERS, + "and so is one with a bit set in the upper half of x17"); + x[17] = 0u; + check(wt_ffa_rt_success_check(x) == 0, "a clean FFA_SUCCESS64 is accepted"); + + x[0] = WT_FFA_ERROR; + x[2] = (uint32_t)WT_FFA_NO_MEMORY; + check(wt_ffa_rt_error_check(x) == 0, "FFA_ERROR with an error code is accepted"); + x[1] = 0x8003u; + check(wt_ffa_rt_error_check(x) == WT_FFA_INVALID_PARAMETERS, + "FFA_ERROR naming a target in w1 (MBZ here) is INVALID_PARAMETERS"); + x[1] = 0u; + x[2] = 0u; + check(wt_ffa_rt_error_check(x) == WT_FFA_INVALID_PARAMETERS, + "FFA_ERROR without an error code is INVALID_PARAMETERS"); + + x[0] = WT_FFA_YIELD; + x[4] = 0xFFFFFFFFu; + x[7] = 1u; + x[1] = 0xFFFFFFFF00000000ull; + check(wt_ffa_rt_yield_check(x) == 0, + "FFA_YIELD ignores its SBZ w4-w7 and reads w1-w3 alone"); + x[1] = 0x80030000u; + check(wt_ffa_rt_yield_check(x) == WT_FFA_INVALID_PARAMETERS, + "a partition's FFA_YIELD naming an endpoint in w1 (MBZ) is refused"); + x[1] = 0u; + x[2] = 1000u; + check(wt_ffa_rt_yield_check(x) == WT_FFA_INVALID_PARAMETERS, + "and so is one asking for a timeout in w2, the Hypervisor's alone"); + x[2] = 0u; + x[3] = 1u; + check(wt_ffa_rt_yield_check(x) == WT_FFA_INVALID_PARAMETERS, + "or in w3"); + x[3] = 0u; +} + +/* FFA_MSG_WAIT's Retain RX Buffer Ownership flag (DEN0077A 1.2 REL0 Table + * 14.3): w2 bit 0 keeps the buffer for a v1.2 caller; w2 was SBZ before. */ +static void msg_wait_rx_rows(void) +{ + uint64_t x[8] = { 0 }; + + x[0] = WT_FFA_MSG_WAIT; + check(wt_ffa_rt_msg_wait_releases_rx(WT_FFA_VERSION_1_2, x) == 1, + "MSG_WAIT with the retain flag clear hands the RX buffer back"); + x[2] = WT_FFA_MSG_WAIT_RETAIN_RX; + check(wt_ffa_rt_msg_wait_releases_rx(WT_FFA_VERSION_1_2, x) == 0, + "MSG_WAIT with w2 bit 0 set keeps a v1.2 caller's RX buffer"); + x[2] = 0xFFFFFFFFull; + check(wt_ffa_rt_msg_wait_releases_rx(WT_FFA_VERSION_1_2, x) == 0, + "SBZ w2 bits[31:1] do not cancel the retain flag"); + x[2] = 0xFFFFFFFEull | 0xFFFFFFFF00000000ull; + check(wt_ffa_rt_msg_wait_releases_rx(WT_FFA_VERSION_1_2, x) == 1, + "only w2 bit 0 retains: SBZ bits and the upper half are ignored"); + x[2] = WT_FFA_MSG_WAIT_RETAIN_RX; + x[1] = 0xFFFFFFFFull; + x[3] = 0xFFFFFFFFull; + x[7] = 0xFFFFFFFFull; + check(wt_ffa_rt_msg_wait_releases_rx(WT_FFA_VERSION_1_2, x) == 0, + "SBZ w1 and w3-w7 do not change the retain decision"); + check(wt_ffa_rt_msg_wait_releases_rx(0x00010001u, x) == 1 && + wt_ffa_rt_msg_wait_releases_rx(0x00010000u, x) == 1, + "a v1.1 or v1.0 caller's SBZ w2 never keeps the RX buffer"); +} + +int main(void) +{ + wt_ffa_rt_state_t s = WT_FFA_RT_RUNNING; + + printf("WT-FFA-0006 (FF-A partition runtime state machine)\n"); + + run_table(); + run_chain(); + init_model_rows(); + status_encoding_rows(); + msg_wait_rx_rows(); + + /* A NULL state pointer and an out-of-range event are rejected without a + * side effect. */ + check(wt_ffa_rt_transition(NULL, WT_FFA_RT_EV_RUN) == WT_FFA_INVALID_PARAMETERS, + "a NULL state pointer is INVALID_PARAMETERS"); + check(wt_ffa_rt_transition(&s, (wt_ffa_rt_event_t)0x7F) == + WT_FFA_INVALID_PARAMETERS && s == WT_FFA_RT_RUNNING, + "an unknown event is INVALID_PARAMETERS and does not move the state"); + + check(wt_ffa_rt_state_name(WT_FFA_RT_WAITING)[0] == 'w' && + wt_ffa_rt_state_name(WT_FFA_RT_RUNNING)[0] == 'r' && + wt_ffa_rt_state_name(WT_FFA_RT_PREEMPTED)[0] == 'p' && + wt_ffa_rt_state_name(WT_FFA_RT_BLOCKED)[0] == 'b' && + wt_ffa_rt_state_name((wt_ffa_rt_state_t)0x7F)[0] == 'i', + "state names round-trip and an out-of-range state reads invalid"); + + printf("ffa_runtime: %d checks, %d failures\n", checks, failures); + return (failures == 0) ? 0 : 1; +} diff --git a/tests/host/ffa_spmd/Makefile b/tests/host/ffa_spmd/Makefile new file mode 100644 index 00000000..31521e61 --- /dev/null +++ b/tests/host/ffa_spmd/Makefile @@ -0,0 +1,64 @@ +# Makefile +# +# Copyright (C) 2026 wolfSSL Inc. +# +# This file is part of wolfTrust. +# +# wolfTrust 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. +# +# wolfTrust 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 + +ROOT := ../../.. + +CC ?= cc +BUILD_DIR ?= build + +CFLAGS := \ + -I$(ROOT)/include \ + -std=c99 -O0 -g -Wall -Wextra -Werror -pedantic +CFLAGS += $(EXTRA_CFLAGS) + +SRCS := $(ROOT)/src/arch/aarch64/ffa/ffa_spmd.c \ + $(ROOT)/src/arch/aarch64/ffa/ffa_mem.c \ + $(ROOT)/src/arch/aarch64/ffa/ffa_msg.c main.c +TEST_BIN := $(BUILD_DIR)/test_ffa_spmd + +.PHONY: all run compilers sanitize valgrind clean + +all: $(TEST_BIN) + +$(BUILD_DIR): + mkdir -p $@ + +$(TEST_BIN): $(SRCS) | $(BUILD_DIR) + $(CC) $(CFLAGS) $(EXTRA_LDFLAGS) -o $@ $(SRCS) + +run: $(TEST_BIN) + $(TEST_BIN) + +compilers: + $(MAKE) clean run CC=gcc BUILD_DIR=$(BUILD_DIR)/gcc + $(MAKE) clean run CC=clang BUILD_DIR=$(BUILD_DIR)/clang + +sanitize: + $(MAKE) clean run CC=clang \ + BUILD_DIR=$(BUILD_DIR)/sanitize \ + EXTRA_CFLAGS="-fsanitize=address,undefined -fno-omit-frame-pointer" \ + EXTRA_LDFLAGS="-fsanitize=address,undefined" + +valgrind: clean $(TEST_BIN) + valgrind --error-exitcode=1 --leak-check=full --show-leak-kinds=all \ + $(TEST_BIN) + +clean: + rm -rf $(BUILD_DIR) diff --git a/tests/host/ffa_spmd/main.c b/tests/host/ffa_spmd/main.c new file mode 100644 index 00000000..4ec515a9 --- /dev/null +++ b/tests/host/ffa_spmd/main.c @@ -0,0 +1,639 @@ +/* main.c + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfTrust. + * + * wolfTrust 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. + * + * wolfTrust 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, see . + */ + +/* WT-FFA-0001 / WT-FFA-0002 (SPMD rows): the Secure physical instance + * dispatcher replies per 13.2 (version, locked after the SPMC's first other + * call), 13.3 (features), 13.10/13.11 (ids), 13.12 (console log, both + * conventions), zeroes every unused result register, and answers unknown + * function ids with NOT_SUPPORTED. */ + +#include "wolftrust/arch/aarch64/el3.h" +#include "wolftrust/arch/aarch64/ffa.h" +#include "wolftrust/arch/aarch64/ffa_abi.h" +#include "wolftrust/arch/aarch64/ffa_msg.h" +#include "wolftrust/arch/aarch64/psci.h" + +#include +#include +#include +#include + +static int checks; +static int failures; +static char g_console[256]; +static size_t g_console_len; + +void wt_platform_console_putc(char c) +{ + if (g_console_len < sizeof(g_console) - 1u) { + g_console[g_console_len++] = c; + g_console[g_console_len] = '\0'; + } +} + +static void check(int ok, const char* what) +{ + checks++; + if (ok) { + printf(" [check] PASS %s\n", what); + } + else { + failures++; + printf(" [check] FAIL %s\n", what); + } +} + +static void call(wt_ffa_regs_t* r, uint32_t fid, uint64_t x1) +{ + memset(r, 0, sizeof(*r)); + r->x[0] = fid; + r->x[1] = x1; + wt_ffa_spmd_secure_call(r); +} + +static int rest_zero(const uint64_t* x, unsigned int from, unsigned int to) +{ + unsigned int i; + + for (i = from; i <= to; i++) { + if (x[i] != 0u) { + return 0; + } + } + return 1; +} + +static int is_error(const wt_ffa_regs_t* r, int32_t code) +{ + return ((uint32_t)r->x[0] == WT_FFA_ERROR) && + ((int32_t)(uint32_t)r->x[2] == code) && (r->x[1] == 0u) && + rest_zero(r->x, 3u, 7u); +} + +static void reset_console(void) +{ + g_console_len = 0u; + g_console[0] = '\0'; +} + +/* The Normal world negotiates a version: its FFA_VERSION is forwarded to the + * SPMC as the Table 13.7 message and the SPMC's answer settles it. */ +static void ns_settle(uint32_t version) +{ + uint64_t x[18]; + + memset(x, 0, sizeof(x)); + x[0] = WT_FFA_VERSION; + x[1] = version; + (void)wt_ffa_spmd_ns_forward(x); + wt_ffa_fwk_version_resp(x, (int32_t)version); + wt_ffa_spmd_ns_reply(x); +} + +static void ns_call(wt_ffa_regs_t* r, uint32_t fid) +{ + memset(r, 0, sizeof(*r)); + r->x[0] = fid; + wt_ffa_spmd_ns_call(r); +} + +/* The NS physical instance answers these function ids directly; every other id + * in the FF-A ranges is NOT_SUPPORTED (discovery and direct messaging forward to + * the SPMC, a separate concern from the SPMD's own dispatch). A call only a + * message receiver makes is DENIED: the primary endpoint is never one. */ +static int ns_defined_reply(uint32_t fid) +{ + switch (fid) { + case WT_FFA_VERSION: + case WT_FFA_ID_GET: + case WT_FFA_SPM_ID_GET: + case WT_FFA_MSG_WAIT: + case WT_FFA_MSG_SEND_DIRECT_RESP32: + case WT_FFA_MSG_SEND_DIRECT_RESP64: + case WT_FFA_MSG_SEND_DIRECT_RESP2: + return 1; + default: + return 0; + } +} + +static int ns_range_total(uint32_t first, uint32_t last) +{ + wt_ffa_regs_t r; + uint32_t fid; + + for (fid = first; fid <= last; fid++) { + ns_call(&r, fid); + if (ns_defined_reply(fid)) { + /* An implemented id is handled, never left to the catch-all + * NOT_SUPPORTED; it may still reject bad arguments with a + * specific error. */ + if (is_error(&r, WT_FFA_NOT_SUPPORTED)) { + return 0; + } + } + else if (!is_error(&r, WT_FFA_NOT_SUPPORTED)) { + return 0; + } + } + return 1; +} + +static void fill_ext(uint64_t* x) +{ + unsigned int i; + + memset(x, 0, 18u * sizeof(x[0])); + for (i = 8u; i < 18u; i++) { + x[i] = 0x5A5A0000u + i; + } +} + +static int ext_filled(const uint64_t* x) +{ + unsigned int i; + + for (i = 8u; i < 18u; i++) { + if (x[i] != (0x5A5A0000u + i)) { + return 0; + } + } + return 1; +} + +/* The SPMD relays a Normal-world direct request only after the NS-physical + * checks of 7.4.2, so no forwarded request speaks with a Secure sender id. */ +static void ns_forward_rows(void) +{ + uint64_t x[18]; + + memset(x, 0, sizeof(x)); + x[0] = WT_FFA_MSG_SEND_DIRECT_REQ32; + x[1] = ((uint64_t)WT_FFA_ID_NS_PRIMARY << 16) | WT_FFA_ID_SP_FIRST; + x[3] = 0x1234u; + check(wt_ffa_spmd_ns_forward(x) == 1 && + (uint32_t)x[0] == WT_FFA_MSG_SEND_DIRECT_REQ32 && x[3] == 0x1234u, + "a well-formed Normal-world direct request is forwarded unchanged"); + + memset(x, 0, sizeof(x)); + x[0] = WT_FFA_MSG_SEND_DIRECT_REQ32; + x[1] = ((uint64_t)WT_FFA_ID_SPMD << 16) | WT_FFA_ID_SPMC; + x[2] = 0x80000008u; + x[3] = WT_FFA_VERSION_MAKE(1u, 0u); + check(wt_ffa_spmd_ns_forward(x) == 0 && + is_error((const wt_ffa_regs_t*)x, WT_FFA_INVALID_PARAMETERS), + "a Normal-world request claiming the SPMD's id is refused, not forwarded"); + + memset(x, 0, sizeof(x)); + x[0] = WT_FFA_MSG_SEND_DIRECT_REQ2; + x[1] = ((uint64_t)WT_FFA_ID_SP_FIRST << 16) | 0x8003u; + check(wt_ffa_spmd_ns_forward(x) == 0 && + is_error((const wt_ffa_regs_t*)x, WT_FFA_INVALID_PARAMETERS), + "a Normal-world REQ2 with a Secure sender is refused too"); + + memset(x, 0, sizeof(x)); + x[0] = WT_FFA_RXTX_MAP64; + x[3] = 1u; + check(wt_ffa_spmd_ns_forward(x) == 1 && (uint32_t)x[0] == WT_FFA_RXTX_MAP64, + "a forwarded call that is not a direct request passes through"); + + fill_ext(x); + x[0] = WT_FFA_MSG_SEND_DIRECT_REQ64; + x[1] = ((uint64_t)WT_FFA_ID_SP_FIRST << 16) | 0x8003u; + check(wt_ffa_spmd_ns_forward(x) == 0 && rest_zero(x, 8u, 17u), + "a refused SMC64 request comes back with x8-x17 zero"); + fill_ext(x); + x[0] = WT_FFA_MEM_SHARE64; + check(wt_ffa_spmd_ns_forward(x) == 1 && rest_zero(x, 8u, 17u), + "a forwarded SMC64 call's x8-x17 are cleared, as its reply carries " + "only x0-x7"); + fill_ext(x); + x[0] = WT_FFA_MEM_SHARE32; + check(wt_ffa_spmd_ns_forward(x) == 1 && ext_filled(x), + "a forwarded SMC32 call keeps x8-x17, which SMCCC preserves"); + fill_ext(x); + x[0] = WT_FFA_MSG_SEND_DIRECT_REQ2; + x[1] = ((uint64_t)WT_FFA_ID_NS_PRIMARY << 16) | WT_FFA_ID_SP_FIRST; + check(wt_ffa_spmd_ns_forward(x) == 1 && ext_filled(x), + "a forwarded REQ2 keeps its x8-x17 payload"); + fill_ext(x); + x[0] = 0xC3000102u; + check(wt_ffa_spmd_ns_forward(x) == 1 && ext_filled(x), + "a forwarded call outside the FF-A ranges keeps x8-x17"); +} + +static uint32_t ns_version_call(uint32_t asked) +{ + wt_ffa_regs_t r; + + memset(&r, 0, sizeof(r)); + r.x[0] = WT_FFA_VERSION; + r.x[1] = asked; + wt_ffa_spmd_ns_call(&r); + return (uint32_t)r.x[0]; +} + +/* 13.2.3.2: until the Normal world locks its version, each FFA_VERSION it + * makes reaches the SPMC as the Table 13.7 message, and the SPMC's Table 13.8 + * answer is what the Normal world gets back. */ +static void ns_version_rows(void) +{ + uint64_t x[18]; + wt_ffa_regs_t r; + unsigned int i; + int ok; + + check(wt_ffa_spmd_ns_forwards(WT_FFA_VERSION) == 1, + "a Normal-world FFA_VERSION is forwarded while its version is open"); + memset(x, 0, sizeof(x)); + x[0] = WT_FFA_VERSION; + x[1] = WT_FFA_VERSION_MAKE(1u, 0u); + x[5] = 0x55u; + check(wt_ffa_spmd_ns_forward(x) == 1 && + (uint32_t)x[0] == WT_FFA_MSG_SEND_DIRECT_REQ32 && + (uint32_t)x[1] == 0x80018000u && (uint32_t)x[2] == 0x80000008u && + (uint32_t)x[3] == WT_FFA_VERSION_MAKE(1u, 0u) && + rest_zero(x, 4u, 7u) && wt_ffa_fwk_version_is_req(x), + "it goes to the SPMC as the Table 13.7 framework message"); + + wt_ffa_fwk_version_resp(x, (int32_t)WT_FFA_VERSION_1_2); + check((uint32_t)x[0] == WT_FFA_MSG_SEND_DIRECT_RESP32 && + (uint32_t)x[1] == 0x80008001u && (uint32_t)x[2] == 0x80000009u && + (uint32_t)x[3] == WT_FFA_VERSION_1_2 && rest_zero(x, 4u, 7u), + "the SPMC answers with the Table 13.8 framework message"); + wt_ffa_spmd_ns_reply(x); + check((uint32_t)x[0] == WT_FFA_VERSION_1_2 && rest_zero(x, 1u, 7u), + "the Normal world gets the SPMC's answer in w0 with x1-x7 zero"); + + for (i = 0u; i < 8u; i++) { + x[i] = 0xA0u + i; + } + wt_ffa_spmd_ns_reply(x); + ok = 1; + for (i = 0u; i < 8u; i++) { + ok = ok && (x[i] == (0xA0u + i)); + } + check(ok, "a reply to any other forwarded call is returned untouched"); + + memset(x, 0, sizeof(x)); + x[0] = WT_FFA_VERSION; + x[1] = WT_FFA_VERSION_MAKE(2u, 0u); + (void)wt_ffa_spmd_ns_forward(x); + wt_ffa_fwk_version_resp(x, (int32_t)WT_FFA_VERSION_1_2); + wt_ffa_spmd_ns_reply(x); + check((uint32_t)x[0] == WT_FFA_VERSION_1_2, + "a later version is answered with the SPMC's 1.2 (13.2.2)"); + + memset(x, 0, sizeof(x)); + x[0] = WT_FFA_VERSION; + x[1] = WT_FFA_VERSION_MAKE(0u, 1u); + (void)wt_ffa_spmd_ns_forward(x); + wt_ffa_fwk_version_resp(x, WT_FFA_NOT_SUPPORTED); + wt_ffa_spmd_ns_reply(x); + check((int32_t)(uint32_t)x[0] == WT_FFA_NOT_SUPPORTED, + "a version the SPMC refuses is NOT_SUPPORTED for the Normal world"); + + memset(x, 0, sizeof(x)); + x[0] = WT_FFA_VERSION; + x[1] = WT_FFA_VERSION_1_2; + (void)wt_ffa_spmd_ns_forward(x); + memset(x, 0, sizeof(x)); + x[0] = WT_FFA_SUCCESS32; + wt_ffa_spmd_ns_reply(x); + check((int32_t)(uint32_t)x[0] == WT_FFA_NOT_SUPPORTED, + "an answer that is not a Table 13.8 message is NOT_SUPPORTED"); + + memset(x, 0, sizeof(x)); + x[0] = WT_FFA_MSG_SEND_DIRECT_REQ32; + x[1] = 0x80018000u; + check(!wt_ffa_fwk_version_is_req(x), + "a partition message between the same ids is not the version message"); + + ns_settle(WT_FFA_VERSION_MAKE(1u, 0u)); + memset(&r, 0, sizeof(r)); + r.x[0] = WT_FFA_FEATURES; + r.x[1] = WT_FFA_MSG_SEND_DIRECT_REQ2; + wt_ffa_spmd_ns_call(&r); + check(is_error(&r, WT_FFA_NOT_SUPPORTED) && + wt_ffa_spmd_ns_forwards(WT_FFA_MSG_SEND_DIRECT_REQ2) == 0 && + wt_ffa_spmd_ns_forwards(WT_FFA_NOTIFICATION_SET) == 0, + "a Normal world settled at 1.0 is told DIRECT_REQ2 and NOTIFICATION_SET " + "are NOT_SUPPORTED and neither is forwarded (13.2.2)"); + memset(&r, 0, sizeof(r)); + r.x[0] = WT_FFA_MSG_SEND_DIRECT_REQ2; + r.x[1] = 0x00008002u; + wt_ffa_spmd_ns_call(&r); + check(is_error(&r, WT_FFA_NOT_SUPPORTED), + "and a DIRECT_REQ2 it sends anyway is NOT_SUPPORTED, not DENIED"); + memset(&r, 0, sizeof(r)); + r.x[0] = WT_FFA_FEATURES; + r.x[1] = WT_FFA_MSG_SEND_DIRECT_REQ32; + wt_ffa_spmd_ns_call(&r); + check((uint32_t)r.x[0] == WT_FFA_SUCCESS32 && + wt_ffa_spmd_ns_forwards(WT_FFA_MSG_SEND_DIRECT_REQ32) == 1, + "while a 1.0 ABI is still implemented and forwarded for it"); + ns_settle(WT_FFA_VERSION_MAKE(1u, 1u)); + memset(&r, 0, sizeof(r)); + r.x[0] = WT_FFA_FEATURES; + r.x[1] = WT_FFA_NOTIFICATION_SET; + wt_ffa_spmd_ns_call(&r); + check((uint32_t)r.x[0] == WT_FFA_SUCCESS32 && + wt_ffa_spmd_ns_forwards(WT_FFA_NOTIFICATION_SET) == 1 && + wt_ffa_spmd_ns_forwards(WT_FFA_MSG_SEND_DIRECT_REQ2) == 0, + "settled at 1.1 the notifications are back and DIRECT_REQ2 still not"); + ns_settle(WT_FFA_VERSION_1_2); + check(wt_ffa_spmd_ns_forwards(WT_FFA_MSG_SEND_DIRECT_REQ2) == 1, + "settled at 1.2 every implemented ABI is forwarded"); + + wt_ffa_spmd_ns_note(WT_FFA_ID_GET); + check(wt_ffa_spmd_ns_forwards(WT_FFA_VERSION) == 0, + "after its first other call the SPMD stops forwarding FFA_VERSION"); + check(ns_version_call(WT_FFA_VERSION_1_2) == WT_FFA_VERSION_1_2 && + ns_version_call(WT_FFA_VERSION_MAKE(2u, 0u)) == WT_FFA_VERSION_1_2 && + (int32_t)ns_version_call(WT_FFA_VERSION_MAKE(1u, 0u)) == + WT_FFA_NOT_SUPPORTED, + "and holds the Normal world to the 1.2 it settled on for 2.0, not a " + "refused one"); +} + +/* 18.2.4: the Table 18.6 request the SPMD sends for a PSCI power operation, + * the Table 18.8 answer, and what the SPMD makes of each SMC the SPMC issues + * while the request is outstanding. */ +static void pm_rows(void) +{ + uint64_t x[18]; + + memset(x, 0x5A, sizeof(x)); + wt_ffa_fwk_pm_req(x, WT_PSCI_CPU_SUSPEND64, 0x1122334455667788ull, + 0x99ull, 0xAAull); + check((uint32_t)x[0] == WT_FFA_MSG_SEND_DIRECT_REQ64 && + x[1] == (((uint64_t)WT_FFA_ID_SPMD << 16) | WT_FFA_ID_SPMC) && + x[2] == 0x80000000u && x[3] == WT_PSCI_CPU_SUSPEND64 && + x[4] == 0x1122334455667788ull && x[5] == 0x99u && x[6] == 0xAAu && + rest_zero(x, 7u, 17u) && wt_ffa_fwk_pm_is_req(x), + "an SMC64 power call is a REQ64 framework message (Table 18.6), " + "x4-x6 its x1-x3 and x7-x17 zero"); + wt_ffa_fwk_pm_req(x, WT_PSCI_SYSTEM_OFF, 0xFFFFFFFF00000001ull, 0u, 0u); + check((uint32_t)x[0] == WT_FFA_MSG_SEND_DIRECT_REQ32 && + x[3] == WT_PSCI_SYSTEM_OFF && x[4] == 1u && wt_ffa_fwk_pm_is_req(x), + "an SMC32 power call is a REQ32 with 32-bit parameters"); + check(!wt_ffa_fwk_version_is_req(x), + "a power message is not taken for the Table 13.7 version message"); + x[2] = WT_FFA_FWK_VERSION_REQ; + check(!wt_ffa_fwk_pm_is_req(x), + "nor a version message for a power message"); + wt_ffa_fwk_pm_req(x, WT_PSCI_SYSTEM_OFF, 0u, 0u, 0u); + x[1] = ((uint64_t)WT_FFA_ID_NS_PRIMARY << 16) | WT_FFA_ID_SPMC; + check(!wt_ffa_fwk_pm_is_req(x), + "only the SPMD sends a power message to the SPMC"); + + wt_ffa_fwk_pm_resp(x, 0); + check((uint32_t)x[0] == WT_FFA_MSG_SEND_DIRECT_RESP32 && + x[1] == (((uint64_t)WT_FFA_ID_SPMC << 16) | WT_FFA_ID_SPMD) && + x[2] == 0x80000002u && x[3] == 0u && rest_zero(x, 4u, 7u) && + wt_ffa_fwk_pm_granted(x), + "the SPMC's SUCCESS is a Table 18.8 response that grants the call"); + check(wt_ffa_spmd_pm_answer(x) == WT_SPMD_PM_GRANTED, + "and the SPMD completes the operation on it"); + wt_ffa_fwk_pm_resp(x, WT_FFA_DENIED); + check(!wt_ffa_fwk_pm_granted(x) && + wt_ffa_spmd_pm_answer(x) == WT_SPMD_PM_DENIED, + "a Table 18.8 DENIED denies the operation"); + wt_ffa_fwk_pm_resp(x, 0); + x[2] = WT_FFA_FWK_VERSION_RESP; + check(wt_ffa_spmd_pm_answer(x) == WT_SPMD_PM_DENIED, + "a SUCCESS in any other framework response is no grant"); + wt_ffa_fwk_pm_resp(x, 0); + x[1] = ((uint64_t)0x8002u << 16) | WT_FFA_ID_SPMD; + check(wt_ffa_spmd_pm_answer(x) == WT_SPMD_PM_DENIED, + "nor is one naming a sender other than the SPMC"); + memset(x, 0, sizeof(x)); + x[0] = WT_FFA_ERROR; + x[2] = (uint64_t)(uint32_t)WT_FFA_NOT_SUPPORTED; + check(wt_ffa_spmd_pm_answer(x) == WT_SPMD_PM_DENIED, + "an FFA_ERROR answer denies the operation"); + memset(x, 0, sizeof(x)); + x[0] = WT_FFA_MSG_WAIT; + check(wt_ffa_spmd_pm_answer(x) == WT_SPMD_PM_REFUSED && + is_error((const wt_ffa_regs_t*)x, WT_FFA_DENIED), + "the SPMC's FFA_MSG_WAIT cannot switch to the Normal world " + "mid-message: DENIED"); + memset(x, 0, sizeof(x)); + x[0] = WT_FFA_YIELD; + check(wt_ffa_spmd_pm_answer(x) == WT_SPMD_PM_REFUSED, + "nor can its FFA_YIELD"); + memset(x, 0, sizeof(x)); + x[0] = WT_FFA_NORMAL_WORLD_RESUME; + check(wt_ffa_spmd_pm_answer(x) == WT_SPMD_PM_REFUSED, + "nor its FFA_NORMAL_WORLD_RESUME"); + memset(x, 0, sizeof(x)); + x[0] = WT_FFA_CONSOLE_LOG32; + check(wt_ffa_spmd_pm_answer(x) == WT_SPMD_PM_NONE, + "a call that does not leave the Secure world is served as usual"); +} + +int main(void) +{ + wt_ffa_regs_t r; + uint64_t frame[19]; + unsigned int i; + char expect[129]; + + printf("WT-FFA-0001 / WT-FFA-0002 (SPMD Secure physical instance)\n"); + + call(&r, WT_FFA_VERSION, WT_FFA_VERSION_1_2); + check((uint32_t)r.x[0] == WT_FFA_VERSION_1_2 && rest_zero(r.x, 1u, 7u), + "FFA_VERSION replies 1.2 in w0 with every other register zero"); + call(&r, WT_FFA_VERSION, 0x80010002u); + check((int32_t)(uint32_t)r.x[0] == WT_FFA_NOT_SUPPORTED, + "FFA_VERSION with bit 31 set is NOT_SUPPORTED"); + call(&r, WT_FFA_VERSION, WT_FFA_VERSION_MAKE(1u, 1u)); + check((uint32_t)r.x[0] == WT_FFA_VERSION_1_2, + "the SPMC may renegotiate before its first other call"); + call(&r, WT_FFA_FEATURES, WT_FFA_CONSOLE_LOG32); + check(is_error(&r, WT_FFA_NOT_SUPPORTED), + "an SPMC settled at 1.1 is told the 1.2 FFA_CONSOLE_LOG is " + "NOT_SUPPORTED (13.2.2)"); + call(&r, WT_FFA_CONSOLE_LOG32, 1u); + check(is_error(&r, WT_FFA_NOT_SUPPORTED) && g_console_len == 0u, + "and an FFA_CONSOLE_LOG it sends anyway logs nothing"); + call(&r, WT_FFA_FEATURES, WT_FFA_SPM_ID_GET); + check((uint32_t)r.x[0] == WT_FFA_SUCCESS32, + "while the 1.1 FFA_SPM_ID_GET stays implemented for it"); + check(wt_ffa_spmd_secure_available(WT_FFA_CONSOLE_LOG64) == 0 && + wt_ffa_spmd_secure_available(WT_FFA_CONSOLE_LOG32) == 0 && + wt_ffa_spmd_secure_available(WT_FFA_SPM_ID_GET) == 1 && + wt_ffa_spmd_secure_available(WT_FFA_MSG_WAIT) == 1, + "the monitor's own extended-register console path asks the same " + "question: no FFA_CONSOLE_LOG64 for an SPMC settled at 1.1"); + wt_ffa_spmd_secure_note(WT_FFA_VERSION); + call(&r, WT_FFA_VERSION, WT_FFA_VERSION_1_2); + check((uint32_t)r.x[0] == WT_FFA_VERSION_1_2, + "FFA_VERSION itself does not end the SPMC's negotiation"); + wt_ffa_spmd_secure_note(WT_FFA_ID_GET); + call(&r, WT_FFA_VERSION, WT_FFA_VERSION_MAKE(1u, 1u)); + check((int32_t)(uint32_t)r.x[0] == WT_FFA_NOT_SUPPORTED && + rest_zero(r.x, 1u, 7u), + "after the SPMC's first other call a different version is NOT_SUPPORTED"); + call(&r, WT_FFA_VERSION, WT_FFA_VERSION_1_2); + check((uint32_t)r.x[0] == WT_FFA_VERSION_1_2, + "and the version it settled on is still accepted"); + call(&r, WT_FFA_VERSION, WT_FFA_VERSION_MAKE(1u, 3u)); + check((uint32_t)r.x[0] == WT_FFA_VERSION_1_2 && rest_zero(r.x, 1u, 7u), + "while a later version is told the settled 1.2 (13.2.2)"); + + call(&r, WT_FFA_FEATURES, WT_FFA_VERSION); + check((uint32_t)r.x[0] == WT_FFA_SUCCESS32 && rest_zero(r.x, 1u, 7u), + "FFA_FEATURES reports an implemented function id with zero properties"); + call(&r, WT_FFA_FEATURES, WT_FFA_CONSOLE_LOG64); + check((uint32_t)r.x[0] == WT_FFA_SUCCESS32, "FFA_FEATURES knows FFA_CONSOLE_LOG SMC64"); + call(&r, WT_FFA_FEATURES, WT_FFA_RXTX_MAP32); + check(is_error(&r, WT_FFA_NOT_SUPPORTED), + "FFA_FEATURES refuses a function the SPMD does not implement yet"); + call(&r, WT_FFA_FEATURES, 0x1u); + check(is_error(&r, WT_FFA_NOT_SUPPORTED), "FFA_FEATURES refuses feature ids"); + memset(&r, 0, sizeof(r)); + r.x[0] = WT_FFA_FEATURES; + r.x[1] = WT_FFA_FEATURE_SRI; + r.x[2] = 1u; + wt_ffa_spmd_ns_call(&r); + check(is_error(&r, WT_FFA_NOT_SUPPORTED), + "an SRI feature query with the MBZ input properties set is " + "NOT_SUPPORTED (Table 13.11)"); + memset(&r, 0, sizeof(r)); + r.x[0] = WT_FFA_FEATURES; + r.x[1] = WT_FFA_FEATURE_SRI; + wt_ffa_spmd_ns_call(&r); + check((uint32_t)r.x[0] == WT_FFA_SUCCESS32 && r.x[2] == WT_FFA_SRI_INTID, + "and with w2 zero it reports the SRI"); + call(&r, WT_FFA_FEATURES, WT_FFA_NORMAL_WORLD_RESUME); + check((uint32_t)r.x[0] == WT_FFA_SUCCESS32 && rest_zero(r.x, 1u, 7u), + "FFA_FEATURES reports FFA_NORMAL_WORLD_RESUME, which the SPMD serves"); + + check(wt_ffa_spmd_is_ns_resume(WT_FFA_NORMAL_WORLD_RESUME) == 1, + "FFA_NORMAL_WORLD_RESUME resumes a preempted Normal world"); + check(wt_ffa_spmd_is_ns_resume(WT_FFA_RUN) == 0, + "FFA_RUN at the Secure physical instance is no alias for the resume (14.4)"); + call(&r, WT_FFA_NORMAL_WORLD_RESUME, 0u); + check(is_error(&r, WT_FFA_DENIED), + "FFA_NORMAL_WORLD_RESUME with no preempted Normal world is DENIED (14.4.1)"); + call(&r, WT_FFA_RUN, 0u); + check(is_error(&r, WT_FFA_NOT_SUPPORTED), + "FFA_RUN from the SPMC is NOT_SUPPORTED at the Secure physical instance"); + + call(&r, WT_FFA_ID_GET, 0u); + check((uint32_t)r.x[0] == WT_FFA_SUCCESS32 && r.x[2] == WT_FFA_ID_SPMC && + r.x[1] == 0u && rest_zero(r.x, 3u, 7u), + "FFA_ID_GET at the Secure physical instance returns the SPMC id"); + call(&r, WT_FFA_SPM_ID_GET, 0u); + check((uint32_t)r.x[0] == WT_FFA_SUCCESS32 && r.x[2] == WT_FFA_ID_SPMD && + rest_zero(r.x, 3u, 7u), + "FFA_SPM_ID_GET returns the SPMD id"); + call(&r, WT_FFA_FID32_LAST - 0xFu, 0x1234u); + check(is_error(&r, WT_FFA_NOT_SUPPORTED), + "an unknown function id in the FF-A range is NOT_SUPPORTED with clean registers"); + call(&r, WT_FFA_MSG_SEND_DIRECT_REQ32, 0u); + check(is_error(&r, WT_FFA_NOT_SUPPORTED), + "a known but unimplemented function id is NOT_SUPPORTED"); + + reset_console(); + memset(&r, 0, sizeof(r)); + r.x[0] = WT_FFA_CONSOLE_LOG32; + r.x[1] = 24u; + for (i = 0u; i < 24u; i++) { + r.x[2u + (i / 4u)] |= (uint64_t)(uint8_t)('A' + (char)i) << (8u * (i % 4u)); + } + for (i = 2u; i < 8u; i++) { + r.x[i] |= 0xDEADBEEF00000000ull; + } + wt_ffa_spmd_secure_call(&r); + check((uint32_t)r.x[0] == WT_FFA_SUCCESS32 && rest_zero(r.x, 1u, 7u) && + strcmp(g_console, "ABCDEFGHIJKLMNOPQRSTUVWX") == 0, + "SMC32 console log prints 24 characters from w2-w7 and ignores the upper halves"); + + reset_console(); + memset(&r, 0, sizeof(r)); + r.x[0] = WT_FFA_CONSOLE_LOG32; + r.x[1] = 3u; + r.x[2] = 0x00434241u; + wt_ffa_spmd_secure_call(&r); + check((uint32_t)r.x[0] == WT_FFA_SUCCESS32 && strcmp(g_console, "ABC") == 0, + "a short SMC32 log prints only the counted characters"); + + reset_console(); + call(&r, WT_FFA_CONSOLE_LOG32, 0u); + check(is_error(&r, WT_FFA_INVALID_PARAMETERS) && g_console_len == 0u, + "count 0 is INVALID_PARAMETERS and prints nothing"); + call(&r, WT_FFA_CONSOLE_LOG32, 25u); + check(is_error(&r, WT_FFA_INVALID_PARAMETERS) && g_console_len == 0u, + "count 25 on SMC32 is INVALID_PARAMETERS"); + call(&r, WT_FFA_CONSOLE_LOG32, 0x100u); + check(is_error(&r, WT_FFA_INVALID_PARAMETERS) && g_console_len == 0u, + "the count is bits 7:0 alone, so 0x100 counts no character"); + memset(&r, 0, sizeof(r)); + r.x[0] = WT_FFA_CONSOLE_LOG32; + r.x[1] = 0xFFFFFF03u; + r.x[2] = 0x00434241u; + wt_ffa_spmd_secure_call(&r); + check((uint32_t)r.x[0] == WT_FFA_SUCCESS32 && strcmp(g_console, "ABC") == 0, + "the SBZ bits 31:8 of the count are ignored"); + + reset_console(); + memset(frame, 0, sizeof(frame)); + frame[0] = WT_FFA_CONSOLE_LOG64; + frame[1] = 128u; + for (i = 0u; i < 128u; i++) { + expect[i] = (char)('0' + (i % 10u)); + frame[2u + (i / 8u)] |= (uint64_t)(uint8_t)expect[i] << (8u * (i % 8u)); + } + expect[128] = '\0'; + frame[18] = 0x5555u; + wt_ffa_spmd_console_call(frame, 1u); + check((uint32_t)frame[0] == WT_FFA_SUCCESS32 && rest_zero(frame, 1u, 7u) && + strcmp(g_console, expect) == 0 && frame[18] == 0x5555u, + "SMC64 console log prints 128 characters from x2-x17 and leaves x18 alone"); + check(rest_zero(frame, 8u, 17u), + "the SMC64 reply returns x8-x17 zero, not the logged characters (11.2)"); + + reset_console(); + memset(frame, 0, sizeof(frame)); + frame[0] = WT_FFA_CONSOLE_LOG64; + frame[1] = 129u; + for (i = 8u; i < 18u; i++) { + frame[i] = 0x4141414141414141ull; + } + wt_ffa_spmd_console_call(frame, 1u); + check((uint32_t)frame[0] == WT_FFA_ERROR && + (int32_t)(uint32_t)frame[2] == WT_FFA_INVALID_PARAMETERS && + g_console_len == 0u && rest_zero(frame, 8u, 17u), + "count 129 on SMC64 is INVALID_PARAMETERS, with x8-x17 zero"); + + ns_forward_rows(); + ns_version_rows(); + pm_rows(); + + check(ns_range_total(WT_FFA_FID32_FIRST, WT_FFA_FID32_LAST) && + ns_range_total(WT_FFA_FID64_FIRST, WT_FFA_FID64_LAST), + "the NS dispatch is total across the FF-A ranges: every unimplemented " + "function id is NOT_SUPPORTED and no id is left unanswered"); + + printf("ffa_spmd: %d checks, %d failures\n", checks, failures); + return (failures == 0) ? 0 : 1; +} diff --git a/tests/host/flash_nvm/Makefile b/tests/host/flash_nvm/Makefile index 2a173d1f..87753308 100644 --- a/tests/host/flash_nvm/Makefile +++ b/tests/host/flash_nvm/Makefile @@ -22,25 +22,42 @@ CC ?= cc BUILD_DIR ?= build CFLAGS := -std=c99 -O0 -g -Wall -Wextra -Werror -pedantic \ - -DWT_CONF_NVM_HOST_TEST \ - -I$(ROOT)/include -I$(ROOT)/port/stm32h563/conformance $(EXTRA_CFLAGS) + -DWT_CONF_NVM_HOST_TEST -DWT_CONFORMANCE=1 -I$(ROOT)/include \ + $(EXTRA_CFLAGS) LDFLAGS := $(EXTRA_LDFLAGS) +H5 := $(ROOT)/port/stm32h563 +A64 := $(ROOT)/port/common/aarch64 TEST := $(BUILD_DIR)/test_flash_nvm -SRCS := $(ROOT)/port/stm32h563/conformance/pal_driver_intf.c main.c +TEST_A64 := $(BUILD_DIR)/test_flash_nvm_aarch64 +WRAP := $(BUILD_DIR)/test_conf_wrap +WRAP_A64 := $(BUILD_DIR)/test_conf_wrap_aarch64 +TESTS := $(TEST) $(TEST_A64) $(WRAP) $(WRAP_A64) .PHONY: all run compilers compiler-gcc compiler-clang sanitize valgrind \ run-valgrind clean -all: $(TEST) +all: $(TESTS) $(BUILD_DIR): mkdir -p $@ -$(TEST): $(SRCS) | $(BUILD_DIR) - $(CC) $(CFLAGS) $(LDFLAGS) -o $@ $(SRCS) +$(TEST): $(H5)/conformance/pal_driver_intf.c main.c | $(BUILD_DIR) + $(CC) $(CFLAGS) -I$(H5)/conformance $(LDFLAGS) -o $@ $^ -run: $(TEST) +$(TEST_A64): $(A64)/conformance/pal_driver_intf.c main.c | $(BUILD_DIR) + $(CC) $(CFLAGS) -I$(A64)/conformance $(LDFLAGS) -o $@ $^ + +$(WRAP): $(H5)/conformance/conf_nvm_sync.c wrap.c | $(BUILD_DIR) + $(CC) $(CFLAGS) -I$(H5)/conformance -I$(H5) $(LDFLAGS) -o $@ $^ + +$(WRAP_A64): $(A64)/conformance/conf_nvm_sync.c wrap.c | $(BUILD_DIR) + $(CC) $(CFLAGS) -I$(A64)/conformance $(LDFLAGS) -o $@ $^ + +run: $(TESTS) $(TEST) + $(TEST_A64) + $(WRAP) + $(WRAP_A64) compiler-gcc: $(MAKE) run CC=gcc BUILD_DIR=$(BUILD_DIR)/gcc @@ -58,9 +75,11 @@ sanitize: valgrind: $(MAKE) run-valgrind BUILD_DIR=$(BUILD_DIR)/valgrind -run-valgrind: $(TEST) - valgrind --error-exitcode=1 --leak-check=full --show-leak-kinds=all \ - $(TEST) +run-valgrind: $(TESTS) + for t in $(TESTS); do \ + valgrind --error-exitcode=1 --leak-check=full \ + --show-leak-kinds=all $$t || exit 1; \ + done clean: rm -rf $(BUILD_DIR) diff --git a/tests/host/flash_nvm/main.c b/tests/host/flash_nvm/main.c index 04c40242..9e771274 100644 --- a/tests/host/flash_nvm/main.c +++ b/tests/host/flash_nvm/main.c @@ -22,13 +22,16 @@ * conformance DRIVER PAL. The real flash controller code (hsm_flash.c) is * target-only, so this drives the genuine pal_driver_intf.c shadow logic * against a faithful flash model that keeps its contents across a simulated - * reset — proving load-on-boot, write-through, and reload semantics. */ + * reset — proving load-on-boot, write-through, and reload semantics. Built + * once per port, each with its own PAL and pal_config.h. */ + +#include "pal_config.h" #include #include #include -/* PAL entry points under test (port/stm32h563/conformance/pal_driver_intf.c). */ +/* PAL entry points under test (the port's conformance/pal_driver_intf.c). */ int pal_nvmem_write(uintptr_t base, uint32_t offset, void *buffer, int size); int pal_nvmem_read(uintptr_t base, uint32_t offset, void *buffer, int size); void wt_conf_drv_nvm_test_reset(void); @@ -47,6 +50,13 @@ static void flash_power_on(void) g_flash_powered = 1; } +/* The backend refusing every access (a rejected trap or a dead controller) + * with the sector's contents intact. */ +static void flash_backend_down(int down) +{ + g_flash_powered = (down != 0) ? 0 : 1; +} + /* The PAL interrupt hooks are irrelevant to the NVM logic under test. */ int wt_conf_irq_set(int on) { @@ -88,11 +98,14 @@ static void check(int cond, const char *name) } } +#define NVM_BYTES ((uint32_t)(NVMEM_0_END - NVMEM_0_START + 1u)) + int main(void) { uint8_t boot[4]; uint8_t data[8]; uint8_t readback[8]; + uint8_t edge = 0x5Au; flash_power_on(); @@ -125,6 +138,18 @@ int main(void) check(pal_nvmem_read(0u, 16u, readback, 8) == 1, "flag read after reset"); check(memcmp(readback, "FLAGDATA", 8) == 0, "flag field survives reset"); + /* Every byte pal_config.h advertises is backed, and none past it. */ + check(pal_nvmem_write(0u, NVM_BYTES - 1u, &edge, 1) == 1, + "the last advertised NVMEM byte is writable"); + wt_conf_drv_nvm_test_reset(); + readback[0] = 0u; + check(pal_nvmem_read(0u, NVM_BYTES - 1u, readback, 1) == 1 && + readback[0] == 0x5Au, "and reads back after a reset"); + check(pal_nvmem_write(0u, NVM_BYTES, &edge, 1) == 0, + "the first byte past the advertised range is refused"); + check(pal_nvmem_read(0u, NVM_BYTES, readback, 1) == 0, + "and cannot be read"); + /* A fresh power-on with a blank sector reads back the 0xFF erased state, * matching the pre-flash RAM store's power-on behaviour. */ flash_power_on(); @@ -134,6 +159,33 @@ int main(void) check(readback[0] == 0xFFu && readback[3] == 0xFFu, "blank sector reads erased 0xFF"); + /* A backend that refuses the load must not fabricate a blank sector, and + * a store it refuses must not show through the shadow. */ + check(pal_nvmem_write(0u, 0u, boot, 4) == 1, "write before the backend fails"); + flash_backend_down(1); + wt_conf_drv_nvm_test_reset(); + (void)memset(readback, 0x55, sizeof(readback)); + check(pal_nvmem_read(0u, 0u, readback, 4) == 0 && readback[0] == 0x55u, + "a read whose reload the backend refuses fails and returns nothing"); + check(pal_nvmem_write(0u, 0u, data, 4) == 0, + "a write whose reload the backend refuses fails"); + flash_backend_down(0); + check(pal_nvmem_read(0u, 0u, readback, 4) == 1 && + memcmp(readback, "BOOT", 4) == 0, + "once the backend answers, the next access reloads the real contents"); + flash_backend_down(1); + check(pal_nvmem_write(0u, 0u, data, 4) == 0, + "a write whose store the backend refuses fails"); + flash_backend_down(0); + check(pal_nvmem_read(0u, 0u, readback, 4) == 1 && + memcmp(readback, "BOOT", 4) == 0, + "and the shadow still reads the last committed value, not the " + "rejected one"); + wt_conf_drv_nvm_test_reset(); + check(pal_nvmem_read(0u, 0u, readback, 4) == 1 && + memcmp(readback, "BOOT", 4) == 0, + "which is also what flash holds after a reset"); + if (g_failures == 0) { printf("PASS: flash_nvm survive-reset NVM\n"); return 0; diff --git a/tests/host/flash_nvm/wrap.c b/tests/host/flash_nvm/wrap.c new file mode 100644 index 00000000..85e40911 --- /dev/null +++ b/tests/host/flash_nvm/wrap.c @@ -0,0 +1,82 @@ +/* wrap.c + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfTrust. + * + * wolfTrust 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. + * + * wolfTrust 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, see . + */ + +/* The conformance DRIVER partition's trap wrappers (conf_nvm_sync.c) against + * a gate stand-in: a trap the gate refuses must never read as success. */ + +#include "conf_nvm.h" + +#include "wolftrust/spm_transport.h" +#include "wolftrust/spm_gate.h" + +#include +#include + +static int g_gate_status; +static int g_gate_ret; +static int g_failures; + +int wt_spm_sp_call(struct wt_spm_call* call) +{ + if (g_gate_status == WT_FFM_SUCCESS) { + call->ret_int = g_gate_ret; + } + return g_gate_status; +} + +static void check(int cond, const char *name) +{ + printf("%s: %s\n", (cond != 0) ? "PASS" : "FAIL", name); + if (cond == 0) { + g_failures++; + } +} + +int main(void) +{ + uint8_t buf[4] = { 0 }; + + g_gate_status = WT_FFM_SUCCESS; + g_gate_ret = 0; + check(wt_conf_nvm_sync(buf, sizeof(buf), 1) == 0, + "an NVM sync the gate runs returns its result"); + check(wt_conf_irq_set(1) == 0, + "an interrupt poke the gate runs returns its result"); + g_gate_ret = -1; + check(wt_conf_nvm_sync(buf, sizeof(buf), 0) == -1, + "a failed NVM sync reads as a failure"); + g_gate_status = WT_FFM_ERROR_ARGUMENT; + g_gate_ret = 0; + check(wt_conf_nvm_sync(buf, sizeof(buf), 1) != 0, + "an NVM store the gate refuses is not a success"); + check(wt_conf_nvm_sync(buf, sizeof(buf), 0) != 0, + "an NVM load the gate refuses is not a success"); + check(wt_conf_irq_set(1) != 0, + "an interrupt raise the gate refuses is not a success"); + check(wt_conf_irq_set(0) != 0, + "an interrupt quiesce the gate refuses is not a success"); + + if (g_failures == 0) { + printf("PASS: conformance trap wrappers\n"); + return 0; + } + printf("FAIL: conformance trap wrappers (%d failures)\n", g_failures); + return 1; +} diff --git a/tests/host/manifest/test_generator.py b/tests/host/manifest/test_generator.py index c3f56b5b..1be97800 100644 --- a/tests/host/manifest/test_generator.py +++ b/tests/host/manifest/test_generator.py @@ -42,15 +42,17 @@ def run_generator(self, source, output, supported_features="0x5"): "--address-bits", "32"], check=False, capture_output=True, text=True) - def compile_generated(self, output, executable): + def compile_generated(self, output, executable, defines=(), + main=TEST_DIR / "generated_main.c"): command = shlex.split(os.environ.get("CC", "cc")) + command.extend(defines) command.extend([ "-std=c99", "-Wall", "-Wextra", "-Werror", "-pedantic", "-I" + str(ROOT / "include"), "-I" + str(output), str(ROOT / "src" / "domain.c"), str(ROOT / "src" / "manifest.c"), str(output / "wolftrust_manifest_generated.c"), - str(TEST_DIR / "generated_main.c"), "-o", str(executable), + str(main), "-o", str(executable), ]) return subprocess.run(command, check=False, capture_output=True, text=True) @@ -310,6 +312,219 @@ def test_interrupt_ownership_is_rejected_before_output(self): self.assertNotEqual(result.returncode, 0) self.assertIn("partition interrupt", result.stderr) + def run_generator_64(self, source, output, extra=()): + return subprocess.run( + [sys.executable, str(GENERATOR), str(source), str(output), + "--supported-features", "0x5", "--address-bits", "64", *extra], + check=False, capture_output=True, text=True) + + def test_64_bit_header_sizes_the_table_pool(self): + with tempfile.TemporaryDirectory() as temporary: + root = Path(temporary) + source = root / "pool.json" + output = root / "output" + manifest = json.loads(FIXTURE.read_text(encoding="utf-8")) + domain = manifest["domains"][2] + domain["memory_resources"][1].update(base=0x1FF000, size=0x2000) + domain["stack_base"] = 0x1FF800 + source.write_text(json.dumps(manifest), encoding="utf-8") + + result = self.run_generator_64(source, output, + ("--spm-table-pages", "4")) + self.assertEqual(result.returncode, 0, result.stderr) + header = (output / "wolftrust_manifest_generated.h").read_text( + encoding="utf-8") + self.assertIn("#define WT_GENERATED_TABLE_POOL_PAGES 14U", header) + + def test_64_bit_source_refuses_a_table_pool_below_the_manifest(self): + with tempfile.TemporaryDirectory() as temporary: + root = Path(temporary) + source = root / "pool.json" + output = root / "output" + source.write_text(json.dumps(self.ffa_manifest()), encoding="utf-8") + + result = self.run_generator_64(source, output, + ("--spm-table-pages", "4")) + self.assertEqual(result.returncode, 0, result.stderr) + header = (output / "wolftrust_manifest_generated.h").read_text( + encoding="utf-8") + need = int(header.split("WT_GENERATED_TABLE_POOL_PAGES ")[1] + .split("U")[0]) + for defines, ok in ((("-DWT_SPM_TABLE_POOL_PAGES={}U".format(need),), + True), + (("-DWT_SPM_TABLE_POOL_PAGES={}U".format( + need - 1),), False), + ((), False)): + compiled = self.compile_generated(output, root / "generated", + defines) + if ok: + self.assertEqual(compiled.returncode, 0, compiled.stderr) + else: + self.assertNotEqual(compiled.returncode, 0, defines) + self.assertIn("WT_GENERATED_TABLE_POOL_PAGES", + compiled.stderr, defines) + + def test_32_bit_header_has_no_table_pool(self): + with tempfile.TemporaryDirectory() as temporary: + root = Path(temporary) + output = root / "output" + self.assertEqual(self.run_generator(FIXTURE, output).returncode, 0) + header = (output / "wolftrust_manifest_generated.h").read_text( + encoding="utf-8") + self.assertNotIn("TABLE_POOL", header) + + def ffa_manifest(self): + manifest = json.loads(FIXTURE.read_text(encoding="utf-8")) + entries = [] + for index, partition in enumerate(manifest["partitions"]): + entries.append({ + "domain_id": partition["domain_id"], + "ffa_version": "1.2", + "uuids": ["b4b5671e-4a90-4fe1-b81f-fb13dae1dacb", + "01234567-0123-4567-89ab-0123456789ab"] + if index == 0 else + ["0f0e0d0c-0b0a-4908-8706-050403020100"], + "execution_contexts": 1, + "runtime_el": "S-EL0", + "messaging": "none", + "ns_interrupt_action": "signaled", + "boot_info_register": "none", + }) + manifest["ffa"] = {"partitions": entries} + return manifest + + def test_ffa_section_emits_a_separate_partition_table(self): + with tempfile.TemporaryDirectory() as temporary: + root = Path(temporary) + source = root / "ffa.json" + output = root / "output" + source.write_text(json.dumps(self.ffa_manifest()), encoding="utf-8") + + result = self.run_generator_64(source, output) + self.assertEqual(result.returncode, 0, result.stderr) + generated = (output / "wolftrust_manifest_generated.c").read_text( + encoding="utf-8") + self.assertIn('#include "wolftrust/arch/aarch64/ffa_manifest.h"', + generated) + self.assertIn("wt_generated_ffa_partitions[{}]".format( + len(self.ffa_manifest()["ffa"]["partitions"])), generated) + self.assertIn(".uuid_count = 2U", generated) + self.assertIn("0xb4U, 0xb5U, 0x67U, 0x1eU", generated) + self.assertIn(".messaging = 0U", generated) + self.assertIn(".ffa_version = 65538U", generated) + self.assertIn(".boot_info_register = 4294967295U", generated) + self.assertIn("wt_generated_ffa_partitions_get(size_t* count)", + generated) + compiled = self.compile_generated( + output, root / "generated", ("-DWT_SPM_TABLE_POOL_PAGES=4096U",)) + self.assertEqual(compiled.returncode, 0, compiled.stderr) + + def test_64_bit_manifest_without_ffa_links_an_empty_table(self): + with tempfile.TemporaryDirectory() as temporary: + root = Path(temporary) + output = root / "output" + main = root / "ffa_main.c" + main.write_text( + "#include \"wolftrust_manifest_generated.h\"\n" + "#include \"wolftrust/arch/aarch64/ffa_manifest.h\"\n" + "int main(void)\n" + "{\n" + " size_t count = 1U;\n" + " const wt_ffa_partition_manifest_t* parts =\n" + " wt_generated_ffa_partitions_get(&count);\n" + " return ((parts != NULL) && (count == 0U)) ? 0 : 1;\n" + "}\n", encoding="utf-8") + self.assertEqual(self.run_generator_64(FIXTURE, output).returncode, 0) + generated = (output / "wolftrust_manifest_generated.c").read_text( + encoding="utf-8") + self.assertIn("wt_generated_ffa_partitions_get(size_t* count)", + generated) + compiled = self.compile_generated( + output, root / "ffa_empty", ("-DWT_SPM_TABLE_POOL_PAGES=4096U",), + main) + self.assertEqual(compiled.returncode, 0, compiled.stderr) + ran = subprocess.run([str(root / "ffa_empty")], check=False) + self.assertEqual(ran.returncode, 0) + + def test_32_bit_manifest_emits_no_ffa_symbols(self): + with tempfile.TemporaryDirectory() as temporary: + root = Path(temporary) + output = root / "output" + self.assertEqual(self.run_generator(FIXTURE, output).returncode, 0) + generated = (output / "wolftrust_manifest_generated.c").read_text( + encoding="utf-8") + self.assertNotIn("ffa", generated) + + def test_ffa_section_needs_a_64_bit_target(self): + with tempfile.TemporaryDirectory() as temporary: + root = Path(temporary) + source = root / "ffa32.json" + source.write_text(json.dumps(self.ffa_manifest()), encoding="utf-8") + + result = self.run_generator(source, root / "output") + self.assertNotEqual(result.returncode, 0) + self.assertIn("--address-bits 64", result.stderr) + + def test_ffa_section_missing_a_partition_is_rejected_before_output(self): + with tempfile.TemporaryDirectory() as temporary: + root = Path(temporary) + source = root / "partial.json" + output = root / "output" + manifest = self.ffa_manifest() + manifest["ffa"]["partitions"].pop() + source.write_text(json.dumps(manifest), encoding="utf-8") + + result = self.run_generator_64(source, output) + self.assertNotEqual(result.returncode, 0) + self.assertIn("lacks an entry for partition domain", result.stderr) + self.assertFalse(output.exists()) + + def test_ffa_null_section_is_rejected_before_output(self): + with tempfile.TemporaryDirectory() as temporary: + root = Path(temporary) + source = root / "null.json" + output = root / "output" + manifest = json.loads(FIXTURE.read_text(encoding="utf-8")) + manifest["ffa"] = None + source.write_text(json.dumps(manifest), encoding="utf-8") + + result = self.run_generator_64(source, output) + self.assertNotEqual(result.returncode, 0) + self.assertIn("manifest.ffa", result.stderr) + self.assertFalse(output.exists()) + + def test_ffa_policy_is_rejected_before_output(self): + cases = ( + ("uuids", ["B4B5671E-4A90-4FE1-B81F-FB13DAE1DACB"], "canonical"), + ("uuids", [], "1 to 4 UUIDs"), + ("domain_id", 250, "unknown domain"), + ("execution_contexts", 2, "one execution context"), + ("ffa_version", "1.1", "ffa_version must be 1.2"), + ("ffa_version", 0x10002, "ffa_version must be"), + ("runtime_el", "EL2", "runtime_el"), + ("runtime_el", "S-EL1", "runtime_el must be S-EL0"), + ("messaging", "smoke", "messaging"), + ("messaging", "indirect", "messaging must be none"), + ("messaging", "direct", "messaging must be none"), + ("ns_interrupt_action", "drop", "ns_interrupt_action"), + ("ns_interrupt_action", "queued", "must be signaled"), + ("boot_info_register", 4, "boot_info_register"), + ("boot_info_register", "x0", "boot_info_register must be none"), + ) + for field, value, message in cases: + with tempfile.TemporaryDirectory() as temporary: + root = Path(temporary) + source = root / "bad.json" + output = root / "output" + manifest = self.ffa_manifest() + manifest["ffa"]["partitions"][0][field] = value + source.write_text(json.dumps(manifest), encoding="utf-8") + + result = self.run_generator_64(source, output) + self.assertNotEqual(result.returncode, 0, field) + self.assertIn(message, result.stderr, field) + self.assertFalse(output.exists(), field) + def assert_rejected(self, manifest, message): with tempfile.TemporaryDirectory() as temporary: root = Path(temporary) diff --git a/tests/host/psa_ffa_transport/Makefile b/tests/host/psa_ffa_transport/Makefile new file mode 100644 index 00000000..b49a3621 --- /dev/null +++ b/tests/host/psa_ffa_transport/Makefile @@ -0,0 +1,130 @@ +# Makefile +# +# Copyright (C) 2026 wolfSSL Inc. +# +# This file is part of wolfTrust. +# +# wolfTrust 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. +# +# wolfTrust 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 + +ROOT := ../../.. +WOLFSSL := $(ROOT)/lib/wolfSSL + +CC ?= cc +BUILD_DIR ?= build + +CFLAGS := \ + -DWOLFSSL_USER_SETTINGS \ + -UHAVE_CONFIG_H \ + -I. \ + -I$(ROOT)/tests/host/wolfhsm_loopback \ + -I$(ROOT)/include \ + -I$(WOLFSSL) \ + -std=c99 -O0 -g -Wall -Wextra -Werror -pedantic \ + -Wno-unused-function -Wno-unused-parameter +CFLAGS += $(EXTRA_CFLAGS) +include ../vendor_c99.mk + +# The neutral FF-M core and services (the ffm_veneer fixture set), the neutral +# gateway, the OS-neutral PSA client, and the AArch64 FF-A binding + SPMC +# front-end that replace the Armv8-M CMSE veneers. +_WT_SRCS := \ + $(ROOT)/src/ffm.c \ + $(ROOT)/src/ffm_boot.c \ + $(ROOT)/src/ffm_domain.c \ + $(ROOT)/src/ipc.c \ + $(ROOT)/src/spm_gate.c \ + $(ROOT)/src/services/hsm_relay_service.c \ + $(ROOT)/src/services/storage_service.c \ + $(ROOT)/src/services/vault_service.c \ + $(ROOT)/src/client/psa_ffm_client.c \ + $(ROOT)/src/arch/common/ffm_gateway.c \ + $(ROOT)/src/arch/aarch64/ffa/psa_ffa_transport_arch.c \ + $(ROOT)/src/arch/aarch64/spm/psa_service.c + +_WC_SRCS := \ + $(WOLFSSL)/wolfcrypt/src/sha256.c \ + $(WOLFSSL)/wolfcrypt/src/random.c \ + $(WOLFSSL)/wolfcrypt/src/tfm.c \ + $(WOLFSSL)/wolfcrypt/src/wolfmath.c \ + $(WOLFSSL)/wolfcrypt/src/memory.c \ + $(WOLFSSL)/wolfcrypt/src/error.c \ + $(WOLFSSL)/wolfcrypt/src/logging.c \ + $(WOLFSSL)/wolfcrypt/src/hash.c \ + $(WOLFSSL)/wolfcrypt/src/wc_port.c \ + $(WOLFSSL)/wolfcrypt/src/cryptocb.c \ + $(WOLFSSL)/wolfcrypt/src/misc.c + +_TEST_SRCS := main.c + +_WT_OBJS := $(patsubst %.c,wt_%.o,$(notdir $(_WT_SRCS))) +_WC_OBJS := $(patsubst %.c,wc_%.o,$(notdir $(_WC_SRCS))) +_TEST_OBJS := $(patsubst %.c,%.o,$(notdir $(_TEST_SRCS))) + +ALL_OBJS := $(addprefix $(BUILD_DIR)/,$(_WT_OBJS) $(_WC_OBJS) $(_TEST_OBJS)) +TEST_BIN := $(BUILD_DIR)/test_psa_ffa_transport + +.PHONY: all run compilers sanitize valgrind clean + +all: $(TEST_BIN) + +$(BUILD_DIR): + mkdir -p $@ + +$(BUILD_DIR)/wt_%.o: $(ROOT)/src/%.c | $(BUILD_DIR) + $(CC) $(CFLAGS) -c -o $@ $< + +$(BUILD_DIR)/wt_%.o: $(ROOT)/src/services/%.c | $(BUILD_DIR) + $(CC) $(CFLAGS) -c -o $@ $< + +$(BUILD_DIR)/wt_%.o: $(ROOT)/src/client/%.c | $(BUILD_DIR) + $(CC) $(CFLAGS) -c -o $@ $< + +$(BUILD_DIR)/wt_%.o: $(ROOT)/src/arch/common/%.c | $(BUILD_DIR) + $(CC) $(CFLAGS) -c -o $@ $< + +$(BUILD_DIR)/wt_%.o: $(ROOT)/src/arch/aarch64/ffa/%.c | $(BUILD_DIR) + $(CC) $(CFLAGS) -c -o $@ $< + +$(BUILD_DIR)/wt_%.o: $(ROOT)/src/arch/aarch64/spm/%.c | $(BUILD_DIR) + $(CC) $(CFLAGS) -c -o $@ $< + +$(BUILD_DIR)/wc_%.o: $(WOLFSSL)/wolfcrypt/src/%.c | $(BUILD_DIR) + $(CC) $(CFLAGS) -c -o $@ $< + +$(BUILD_DIR)/%.o: %.c | $(BUILD_DIR) + $(CC) $(CFLAGS) -c -o $@ $< + +$(TEST_BIN): $(ALL_OBJS) | $(BUILD_DIR) + $(CC) $(CFLAGS) $(EXTRA_LDFLAGS) -o $@ $(ALL_OBJS) -lm + +run: $(TEST_BIN) + $(TEST_BIN) + +compilers: + $(MAKE) clean run CC=gcc BUILD_DIR=$(BUILD_DIR)/gcc + $(MAKE) clean run CC=clang BUILD_DIR=$(BUILD_DIR)/clang + +sanitize: + $(MAKE) clean run CC=clang \ + BUILD_DIR=$(BUILD_DIR)/sanitize \ + EXTRA_CFLAGS="-fsanitize=address,undefined -fno-omit-frame-pointer" \ + EXTRA_LDFLAGS="-fsanitize=address,undefined" + +valgrind: clean $(TEST_BIN) + valgrind --error-exitcode=1 --leak-check=full --show-leak-kinds=all \ + $(TEST_BIN) + +clean: + rm -rf $(BUILD_DIR) diff --git a/tests/host/psa_ffa_transport/main.c b/tests/host/psa_ffa_transport/main.c new file mode 100644 index 00000000..c1ad378f --- /dev/null +++ b/tests/host/psa_ffa_transport/main.c @@ -0,0 +1,443 @@ +/* main.c + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfTrust. + * + * wolfTrust 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. + * + * wolfTrust 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, see . + */ + +/* WT-FFA-0012 / WT-FFM-0066: the operating-system-neutral PSA client + * (src/client/psa_ffm_client.c, the one every Armv8-M guest links) over the + * AArch64 FF-A binding of its WolfTrust_FFM_* entry points, through the SMC seam + * into the SPMC front-end, into the neutral FF-M gateway and core - the same + * gateway the CMSE veneers feed, so the same services answer. The host stands in + * for the port's caller identity and Non-secure window. */ + +#include "psa/client.h" +#include "psa_manifest/pid.h" + +#include "wolftrust/arch.h" +#include "wolftrust/arch/aarch64/ffa.h" +#include "wolftrust/arch/aarch64/ffa_abi.h" +#include "wolftrust/arch/aarch64/ffa_msg.h" +#include "wolftrust/arch/aarch64/gic.h" +#include "wolftrust/arch/aarch64/psa_ffa.h" +#include "wolftrust/arch/aarch64/spm_mem.h" +#include "wolftrust/ffm_boot.h" +#include "wolftrust/ffm_gateway.h" +#include "wolftrust/ffm_veneer.h" +#include "wolftrust/services/hsm_relay.h" +#include "wolftrust/spm_sched.h" + +#include +#include +#include +#include + +#include + +#define TEST_HSM_SID 4102U + +static int checks; +static int failures; + +/* A panic anywhere in these rows is a failure: report it and end the run. */ +void wt_platform_panic(void) +{ + printf(" [check] FAIL the core panicked\n"); + exit(1); +} + +int wt_spm_sched_add(wt_ffm_runtime_t* runtime, int32_t partition_id, + wt_spm_sp_entry_fn entry, void* arg) +{ + (void)runtime; (void)partition_id; (void)entry; (void)arg; + return WT_FFM_SUCCESS; +} + +int wt_spm_sched_validate(void) +{ + return WT_FFM_SUCCESS; +} + +int wt_spm_hsm_start(wt_ffm_runtime_t* runtime, int32_t partition_id) +{ + (void)runtime; (void)partition_id; + return WT_FFM_SUCCESS; +} + +int wt_spm_vault_start(wt_ffm_runtime_t* runtime, int32_t partition_id) +{ + (void)runtime; (void)partition_id; + return WT_FFM_SUCCESS; +} + +int wt_spm_its_start(wt_ffm_runtime_t* runtime, int32_t partition_id) +{ + (void)runtime; (void)partition_id; + return WT_FFM_SUCCESS; +} + +int wt_spm_ps_start(wt_ffm_runtime_t* runtime, int32_t partition_id) +{ + (void)runtime; (void)partition_id; + return WT_FFM_SUCCESS; +} + +/* The relayer's ownership answer, host-side: [g_gone_lo, g_gone_hi) stands in + * for pages the guest lent or donated away. */ +static uint64_t g_gone_lo; +static uint64_t g_gone_hi; + +int wt_spm_mem_ns_access(uint64_t base, uint64_t size, int write) +{ + (void)write; + return (size == 0u) || ((base + size) <= g_gone_lo) || (base >= g_gone_hi); +} + +/* The AArch64 port's caller identity and Non-secure window checks, host-side: + * the primary guest is 0 and the window is whatever wt_spm_psa_init recorded, + * less what the guest no longer owns. */ +uint32_t wt_arch_active_guest_id(void) +{ + return 0u; +} + +int wt_arch_ns_check_read(wt_guest_id_t guest_id, const void* address, + size_t size) +{ + return (guest_id == (wt_guest_id_t)0) && + wt_spm_ns_window_ok((uintptr_t)address, size) && + wt_spm_mem_ns_access((uint64_t)(uintptr_t)address, (uint64_t)size, 0); +} + +int wt_arch_ns_check_write(wt_guest_id_t guest_id, void* address, size_t size) +{ + return (guest_id == (wt_guest_id_t)0) && + wt_spm_ns_window_ok((uintptr_t)address, size) && + wt_spm_mem_ns_access((uint64_t)(uintptr_t)address, (uint64_t)size, 1); +} + +int wt_arch_ns_check_writable(const void* address, size_t size) +{ + return wt_spm_ns_window_ok((uintptr_t)address, size) && + wt_spm_mem_ns_access((uint64_t)(uintptr_t)address, (uint64_t)size, 1); +} + +/* The CPU interface priority mask, and what it was while a service ran. */ +static uint32_t g_pmr = 0xFFu; +static uint32_t g_pmr_in_service; + +static uint32_t stub_swap_pmr(uint32_t pmr) +{ + uint32_t prev = g_pmr; + + g_pmr = pmr; + return prev; +} + +static const struct wt_gic_ops g_stub_gic = { + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, stub_swap_pmr, NULL, + NULL, NULL, NULL, 3u +}; +const struct wt_gic_ops* const wt_gic = &g_stub_gic; + +static uint32_t g_req_fid; +static uint32_t g_resp_fid; + +/* The SMC seam: a direct request to the PSA endpoint lands in the SPMC + * front-end; anything else is refused as the SPMD would. */ +void wt_ffa_transport_smc(wt_ffa_regs_t* r) +{ + g_req_fid = (uint32_t)r->x[0]; + if ((((uint32_t)r->x[0] == WT_FFA_MSG_SEND_DIRECT_REQ64) || + ((uint32_t)r->x[0] == WT_FFA_MSG_SEND_DIRECT_REQ32)) && + (wt_ffa_direct_receiver(r->x[1]) == WT_FFA_ID_PSA)) { + (void)wt_spm_psa_framework(r); + g_resp_fid = (uint32_t)r->x[0]; + return; + } + r->x[0] = WT_FFA_ERROR; + r->x[2] = (uint64_t)(uint32_t)WT_FFA_NOT_SUPPORTED; +} + +/* SHA-256 submit hook: hashing the relayed request keeps the KAT round trip + * byte-exact without a wolfHSM server in this fixture. */ +static int test_sha_submit(void* submit_ctx, int32_t client_id, + const uint8_t* req, size_t req_len, + uint8_t* resp, size_t resp_cap, size_t* resp_len) +{ + wc_Sha256 sha; + int rc; + + (void)submit_ctx; + (void)client_id; + g_pmr_in_service = g_pmr; + if (resp_cap < WC_SHA256_DIGEST_SIZE) { + return -1; + } + rc = wc_InitSha256(&sha); + if (rc == 0) { + rc = wc_Sha256Update(&sha, req, (word32)req_len); + } + if (rc == 0) { + rc = wc_Sha256Final(&sha, resp); + } + wc_Sha256Free(&sha); + if (rc != 0) { + return -1; + } + *resp_len = WC_SHA256_DIGEST_SIZE; + return 0; +} + +static const wt_service_descriptor_t g_services[] = { + { + "SERVICE_HSM", TEST_HSM_SID, 1U, WT_SERVICE_VERSION_RELAXED, + 0x10U, 0U, 1U, 1U + } +}; + +static const wt_partition_manifest_t g_partitions[] = { + { + "PARTITION_HSM", PARTITION_HSM_ID, WT_FFM_VERSION_1_0, + WT_PARTITION_MODEL_IPC, WT_PARTITION_PRIORITY_NORMAL, + g_services, sizeof(g_services) / sizeof(g_services[0]), + NULL, 0U, NULL, 0U + } +}; + +static const wt_system_manifest_t g_manifest = { + .format_version = WT_MANIFEST_FORMAT_VERSION, + .generator_version = "psa-ffa-transport-test", + .features = WT_MANIFEST_FEATURE_IPC, + .partitions = g_partitions, + .partition_count = sizeof(g_partitions) / sizeof(g_partitions[0]) +}; + +static void check(int ok, const char* what) +{ + checks++; + if (ok) { + printf(" [check] PASS %s\n", what); + } + else { + failures++; + printf(" [check] FAIL %s\n", what); + } +} + +/* Hand one crafted direct request of the given width to the SPMC front-end. */ +static void crafted(wt_ffa_regs_t* r, uint32_t fid, uint32_t op, uint64_t x4, + uint64_t x5) +{ + memset(r, 0, sizeof(*r)); + r->x[0] = fid; + r->x[1] = ((uint64_t)WT_FFA_ID_NS_PRIMARY << 16) | WT_FFA_ID_PSA; + r->x[3] = op; + r->x[4] = x4; + r->x[5] = x5; + (void)wt_spm_psa_framework(r); +} + +/* Drive one crafted Call through the SPMC front-end with the given vector block + * address and handle; returns the psa_status_t the guest would see. */ +static psa_status_t crafted_call(uint64_t block, psa_handle_t handle) +{ + wt_ffa_regs_t r; + + crafted(&r, WT_FFA_MSG_SEND_DIRECT_REQ64, WT_PSA_FFA_OP_CALL, block, + ((uint64_t)(uint32_t)PSA_IPC_CALL << 32) | (uint64_t)(uint32_t)handle); + return (psa_status_t)(int32_t)(uint32_t)r.x[3]; +} + +int main(void) +{ + /* SHA-256("wolfTrust FF-M SERVICE_CRYPTO dispatch test") */ + static const uint8_t input[] = + "wolfTrust FF-M SERVICE_CRYPTO dispatch test"; + static const uint8_t expected[32] = { + 0x20, 0x03, 0xdf, 0x15, 0x2a, 0x52, 0x8a, 0x06, + 0xc8, 0xd3, 0x48, 0xb8, 0xfa, 0x8b, 0x2f, 0x87, + 0xf7, 0x1f, 0xae, 0xc6, 0x24, 0x6c, 0x7e, 0x72, + 0x8e, 0x27, 0xa4, 0xb5, 0x0a, 0x49, 0x84, 0x66 + }; + static uint8_t area[1024] __attribute__((aligned(16))); + wt_ffa_regs_t r; + wt_ffm_veneer_iovec_t* block; + wt_ffm_veneer_iovec_t local; + psa_handle_t handle; + psa_handle_t refused; + psa_handle_t again; + uint8_t digest[32]; + psa_invec in_vec; + psa_outvec out_vec; + psa_status_t st; + + printf("WT-FFA-0012 / WT-FFM-0066 (PSA client over FF-A into the FF-M gateway)\n"); + + check(wt_ffm_boot_init(&g_manifest) == WT_FFM_SUCCESS, + "the neutral boot core initializes from the manifest"); + wt_ffm_gateway_install(); + wt_hsm_relay_set_submit(test_sha_submit, NULL); + + in_vec.base = input; + in_vec.len = sizeof(input) - 1u; + out_vec.base = digest; + out_vec.len = sizeof(digest); + + /* Fail closed: with no Non-secure window recorded, a data-carrying call is + * refused before any vector is read. */ + wt_spm_psa_init(0u, 0u); + handle = psa_connect(TEST_HSM_SID, 1u); + check(PSA_HANDLE_IS_VALID(handle), + "psa_connect over FF-A reaches the gateway and returns a core handle"); + st = psa_call(handle, PSA_IPC_CALL, &in_vec, 1u, &out_vec, 1u); + check(st == PSA_ERROR_PROGRAMMER_ERROR, + "with no Non-secure window recorded a call is refused (fail closed)"); + psa_close(handle); + + wt_spm_psa_init(0u, ~(uint64_t)0); + check(psa_framework_version() == PSA_FRAMEWORK_VERSION, + "psa_framework_version comes from the core over the transport"); + check((g_req_fid == WT_FFA_MSG_SEND_DIRECT_REQ64) && + (g_resp_fid == WT_FFA_MSG_SEND_DIRECT_RESP64), + "the client sends FFA_MSG_SEND_DIRECT_REQ64 and is answered with RESP64"); + check(psa_version(TEST_HSM_SID) == 1u, + "psa_version of SERVICE_HSM is 1 through the gateway"); + check(psa_version(0x9999u) == PSA_VERSION_NONE, + "psa_version of an unknown service is PSA_VERSION_NONE"); + + handle = psa_connect(TEST_HSM_SID, 1u); + check(PSA_HANDLE_IS_VALID(handle), "psa_connect to SERVICE_HSM succeeds"); + refused = psa_connect(0x9999u, 1u); + check(!PSA_HANDLE_IS_VALID(refused), "psa_connect to an unknown service is refused"); + + memset(digest, 0, sizeof(digest)); + out_vec.len = sizeof(digest); + st = psa_call(handle, PSA_IPC_CALL, &in_vec, 1u, &out_vec, 1u); + check(st == PSA_SUCCESS && memcmp(digest, expected, sizeof(expected)) == 0, + "psa_call round-trips client -> FF-A -> front-end -> gateway -> core -> relay: SHA-256 KAT matches"); + check(out_vec.len == sizeof(digest), + "the out-vec length is written back through the guest's vector block"); + check(g_pmr_in_service == WT_GIC_PMR_MASK_NS && g_pmr == 0xFFu, + "the service ran with Normal-world interrupts queued behind the " + "priority mask, put back once it answered (9.3.1.3)"); + + st = psa_call((psa_handle_t)0x7777, PSA_IPC_CALL, &in_vec, 1u, &out_vec, 1u); + check(st == PSA_ERROR_PROGRAMMER_ERROR, + "a call on a handle the core never issued is a PROGRAMMER_ERROR"); + + psa_close(handle); + again = psa_connect(TEST_HSM_SID, 1u); + check(PSA_HANDLE_IS_VALID(again), "a closed connection can be reopened"); + psa_close(again); + + /* Window-bounded rejections: the block sits inside the window, one vector + * does not; then the block itself is outside. */ + wt_spm_psa_init((uint64_t)(uintptr_t)area, + (uint64_t)(uintptr_t)area + (uint64_t)sizeof(area)); + handle = psa_connect(TEST_HSM_SID, 1u); + check(PSA_HANDLE_IS_VALID(handle), "connect needs no vectors and succeeds under a bounded window"); + block = (wt_ffm_veneer_iovec_t*)(void*)&area[0]; + memset(block, 0, sizeof(*block)); + block->in[0].base = input; + block->in[0].len = (uint32_t)(sizeof(input) - 1u); + block->out[0].base = &area[512]; + block->out[0].len = 32u; + block->in_count = 1u; + block->out_count = 1u; + check(crafted_call((uint64_t)(uintptr_t)block, handle) == PSA_ERROR_PROGRAMMER_ERROR, + "an in-vec outside the Non-secure window is refused by the core's memcheck"); + handle = psa_connect(TEST_HSM_SID, 1u); + memset(&local, 0, sizeof(local)); + local.in[0].base = &area[256]; + local.in[0].len = 8u; + local.in_count = 1u; + check(crafted_call((uint64_t)(uintptr_t)&local, handle) == PSA_ERROR_PROGRAMMER_ERROR, + "a vector block outside the Non-secure window is refused before it is read"); + wt_spm_psa_init(0u, ~(uint64_t)0); + + /* 7.2.1 / 11 rule 3: an SMC32 message is w0-w7, so the upper halves of + * x4/x5 are ignored and a Call's 64-bit block address and type cannot ride + * it. */ + crafted(&r, WT_FFA_MSG_SEND_DIRECT_REQ32, WT_PSA_FFA_OP_CONNECT, + 0xDEADBEEF00000000ull | TEST_HSM_SID, 0xFFFFFFFF00000001ull); + handle = (psa_handle_t)(int32_t)(uint32_t)r.x[3]; + check(((uint32_t)r.x[0] == WT_FFA_MSG_SEND_DIRECT_RESP32) && + PSA_HANDLE_IS_VALID(handle), + "an SMC32 Connect reads w4/w5 only and is answered with RESP32"); + block->out[0].len = 32u; + crafted(&r, WT_FFA_MSG_SEND_DIRECT_REQ32, WT_PSA_FFA_OP_CALL, + (uint64_t)(uintptr_t)block, + ((uint64_t)(uint32_t)PSA_IPC_CALL << 32) | (uint64_t)(uint32_t)handle); + check(((uint32_t)r.x[0] == WT_FFA_ERROR) && + ((int32_t)(uint32_t)r.x[2] == WT_FFA_INVALID_PARAMETERS) && + (r.x[3] == 0u), + "an SMC32 Call is INVALID_PARAMETERS: it cannot carry the block address and type"); + check(crafted_call((uint64_t)(uintptr_t)block, handle) == PSA_SUCCESS, + "the connection an SMC32 Connect opened serves an SMC64 Call"); + crafted(&r, WT_FFA_MSG_SEND_DIRECT_REQ32, WT_PSA_FFA_OP_CLOSE, + 0xA5A5A5A500000000ull | (uint64_t)(uint32_t)handle, 0u); + check(((uint32_t)r.x[0] == WT_FFA_MSG_SEND_DIRECT_RESP32) && + ((psa_status_t)(int32_t)(uint32_t)r.x[3] == PSA_SUCCESS) && + (crafted_call((uint64_t)(uintptr_t)block, handle) == + PSA_ERROR_PROGRAMMER_ERROR), + "an SMC32 Close takes the handle from w4 and closes the connection"); + + /* DEN0140 Table 1.3: memory the guest lent or donated is no longer its + * own, so no vector or vector block there is read or written for it. */ + memset(block, 0, sizeof(*block)); + block->in[0].base = input; + block->in[0].len = (uint32_t)(sizeof(input) - 1u); + block->out[0].base = &area[512]; + block->out[0].len = 32u; + block->in_count = 1u; + block->out_count = 1u; + memset(&area[512], 0xA5, 32u); + g_gone_lo = (uint64_t)(uintptr_t)&area[512]; + g_gone_hi = g_gone_lo + 32u; + handle = psa_connect(TEST_HSM_SID, 1u); + check(crafted_call((uint64_t)(uintptr_t)block, handle) == + PSA_ERROR_PROGRAMMER_ERROR && + area[512] == 0xA5u && area[543] == 0xA5u, + "an out-vec in memory the guest lent away is refused and never written"); + psa_close(handle); + g_gone_lo = (uint64_t)(uintptr_t)input; + g_gone_hi = g_gone_lo + 1u; + handle = psa_connect(TEST_HSM_SID, 1u); + check(crafted_call((uint64_t)(uintptr_t)block, handle) == + PSA_ERROR_PROGRAMMER_ERROR, + "an in-vec reaching into memory the guest lent away is refused"); + psa_close(handle); + g_gone_lo = (uint64_t)(uintptr_t)&block->out[0]; + g_gone_hi = g_gone_lo + 1u; + handle = psa_connect(TEST_HSM_SID, 1u); + check(crafted_call((uint64_t)(uintptr_t)block, handle) == + PSA_ERROR_PROGRAMMER_ERROR && + block->out[0].len == 32u, + "a vector block in memory the guest lent away is refused before it is read"); + psa_close(handle); + g_gone_lo = 0u; + g_gone_hi = 0u; + handle = psa_connect(TEST_HSM_SID, 1u); + check(crafted_call((uint64_t)(uintptr_t)block, handle) == PSA_SUCCESS && + memcmp(&area[512], expected, sizeof(expected)) == 0, + "the same call is served once the memory is the guest's again"); + psa_close(handle); + + printf("psa_ffa_transport: %d checks, %d failures\n", checks, failures); + return (failures == 0) ? 0 : 1; +} diff --git a/tests/host/psa_ffa_transport/psa_manifest/pid.h b/tests/host/psa_ffa_transport/psa_manifest/pid.h new file mode 100644 index 00000000..d9198167 --- /dev/null +++ b/tests/host/psa_ffa_transport/psa_manifest/pid.h @@ -0,0 +1,40 @@ +/* pid.h + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfTrust. + * + * wolfTrust 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. + * + * wolfTrust 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, see . + */ + +/* Host stand-in for the generated psa_manifest/pid.h; ids mirror the + * production manifest (tests/host/spm production-generated). */ + +#ifndef PSA_MANIFEST_PID_H +#define PSA_MANIFEST_PID_H + +#define PARTITION_ATTEST_ID 3 +#define PARTITION_CRYPTO_ID 4 +#define PARTITION_HSM_ID 4 +#define PARTITION_VAULT_ID 5 +#define PARTITION_ITS_ID 6 +#define PARTITION_PS_ID 7 +#define PARTITION_ATTEST PARTITION_ATTEST_ID +#define PARTITION_CRYPTO PARTITION_CRYPTO_ID +#define PARTITION_HSM PARTITION_HSM_ID +#define PARTITION_VAULT PARTITION_VAULT_ID +#define PARTITION_ITS PARTITION_ITS_ID +#define PARTITION_PS PARTITION_PS_ID + +#endif /* PSA_MANIFEST_PID_H */ diff --git a/tests/host/psa_ffm_client/main.c b/tests/host/psa_ffm_client/main.c index b4ac5403..7e2b7d65 100644 --- a/tests/host/psa_ffm_client/main.c +++ b/tests/host/psa_ffm_client/main.c @@ -123,6 +123,8 @@ void WolfTrust_FFM_Close(int32_t handle) (void)wt_ffm_close(wt_ffm_boot_runtime_mut(), TEST_NS_CLIENT, handle); } +static uint32_t g_veneer_in_len_seen; + int32_t WolfTrust_FFM_Call(int32_t handle, int32_t type, wt_ffm_veneer_iovec_t* iv) { @@ -138,6 +140,9 @@ int32_t WolfTrust_FFM_Call(int32_t handle, int32_t type, } memset(in, 0, sizeof(in)); memset(out, 0, sizeof(out)); + if (iv->in_count != 0u) { + g_veneer_in_len_seen = iv->in[0].len; + } for (i = 0u; i < iv->in_count; i++) { in[i].base = iv->in[i].base; in[i].len = iv->in[i].len; @@ -312,6 +317,7 @@ int main(void) psa_fwu_component_info_t fwu_info; psa_handle_t handle; psa_invec in_vec; + psa_invec big_vec; psa_outvec out_vec; psa_status_t status; @@ -352,6 +358,28 @@ int main(void) status = psa_call(handle, PSA_IPC_CALL, &in_vec, 5U, &out_vec, 1U); check(status == PSA_ERROR_PROGRAMMER_ERROR, "P7-S1 psa_call rejects an over-count invec (PROGRAMMER_ERROR)"); +#if SIZE_MAX > UINT32_MAX + /* LP64: a count that would wrap to a valid 32-bit one must stay an + * over-count at the veneer, and a length above 32 bits must not wrap. */ + g_veneer_in_len_seen = 0u; + status = psa_call(handle, PSA_IPC_CALL, &in_vec, + (size_t)1u << 32 | 1u, &out_vec, 1U); + check(status == PSA_ERROR_PROGRAMMER_ERROR, + "P7-S1 psa_call keeps a 2^32+1 invec count a PROGRAMMER_ERROR on LP64"); + status = psa_call(handle, PSA_IPC_CALL, &in_vec, 1U, &out_vec, + (size_t)1u << 32 | 1u); + check(status == PSA_ERROR_PROGRAMMER_ERROR, + "P7-S1 psa_call keeps a 2^32+1 outvec count a PROGRAMMER_ERROR on LP64"); + check(g_veneer_in_len_seen == 0u, + "P7-S1 an over-count never marshals a vector"); + big_vec.base = in_vec.base; + big_vec.len = (size_t)1u << 32 | 16u; + g_veneer_in_len_seen = 0u; + (void)psa_call(handle, PSA_IPC_CALL, &big_vec, 1U, &out_vec, 1U); + check(g_veneer_in_len_seen == UINT32_MAX, + "P7-S1 a 2^32+16 invec length saturates at the veneer instead of " + "wrapping to 16"); +#endif /* ---- PSA FWU 1.0 through the public client (SRC-PSA-FWU) ---- */ (void)memset(&g_fwu_ctx, 0, sizeof(g_fwu_ctx)); diff --git a/tests/host/spm_irq/Makefile b/tests/host/spm_irq/Makefile new file mode 100644 index 00000000..5d25afa3 --- /dev/null +++ b/tests/host/spm_irq/Makefile @@ -0,0 +1,64 @@ +# Makefile +# +# Copyright (C) 2026 wolfSSL Inc. +# +# This file is part of wolfTrust. +# +# wolfTrust 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. +# +# wolfTrust 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 + +ROOT := ../../.. + +CC ?= cc +BUILD_DIR ?= build + +# The shim's sysreg.h replaces the system-register accessors, which are +# AArch64 instructions, with host variables the suite drives. +CFLAGS := \ + -Ishim -I$(ROOT)/include \ + -std=c99 -O0 -g -Wall -Wextra -Werror -pedantic +CFLAGS += $(EXTRA_CFLAGS) + +SRCS := $(ROOT)/src/arch/aarch64/spm/spm_irq.c main.c +TEST_BIN := $(BUILD_DIR)/test_spm_irq + +.PHONY: all run compilers sanitize valgrind clean + +all: $(TEST_BIN) + +$(BUILD_DIR): + mkdir -p $@ + +$(TEST_BIN): $(SRCS) | $(BUILD_DIR) + $(CC) $(CFLAGS) $(EXTRA_LDFLAGS) -o $@ $(SRCS) + +run: $(TEST_BIN) + $(TEST_BIN) + +compilers: + $(MAKE) clean run CC=gcc BUILD_DIR=$(BUILD_DIR)/gcc + $(MAKE) clean run CC=clang BUILD_DIR=$(BUILD_DIR)/clang + +sanitize: + $(MAKE) clean run CC=clang \ + BUILD_DIR=$(BUILD_DIR)/sanitize \ + EXTRA_CFLAGS="-fsanitize=address,undefined -fno-omit-frame-pointer" \ + EXTRA_LDFLAGS="-fsanitize=address,undefined" + +valgrind: clean $(TEST_BIN) + valgrind --error-exitcode=1 --leak-check=full --show-leak-kinds=all \ + $(TEST_BIN) + +clean: + rm -rf $(BUILD_DIR) diff --git a/tests/host/spm_irq/main.c b/tests/host/spm_irq/main.c new file mode 100644 index 00000000..f5933a84 --- /dev/null +++ b/tests/host/spm_irq/main.c @@ -0,0 +1,390 @@ +/* main.c + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfTrust. + * + * wolfTrust 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. + * + * wolfTrust 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, see . + */ + +/* S-EL1 Group 0 interrupt routing (DEN0077A Ch.9, Table 9.1): a Secure + * interrupt a partition declared is queued (and its waiting owner signaled) + * whether the FIQ is taken from an S-EL0 partition or at S-EL1 itself, and + * the tick the boot proofs wait on is still recorded. */ + +#include "wolftrust/arch/aarch64/context.h" +#include "wolftrust/arch/aarch64/el3.h" +#include "wolftrust/arch/aarch64/gic.h" +#include "wolftrust/arch/aarch64/spm_svc.h" + +#include +#include +#include + +#define OWNED_SPI 41u +#define STRAY_SPI 45u +#define CLAIMABLE_SPI 56u +#define NS_TEST_SPI 59u + +uint64_t g_host_cntpct; +unsigned int g_host_fiq_masked; +volatile uint32_t g_wt_spm_sint_queued; +extern volatile uint32_t g_wt_spm_tick_intid; + +void wt_spm_fiq(void); +void wt_spm_lower_fiq(wt_trap_frame_t* frame); + +static int checks; +static int failures; +static int g_owner_token; +static uint32_t g_next_intid; +static uint32_t g_eoi; +static struct wt_co* g_queued_for; +static uint32_t g_queued_for_id; +static uint32_t g_queued_any; +static int g_signal_needed; +static int g_signal_asked; +static wt_trap_frame_t* g_preempted; + +#define OWNER ((struct wt_co*)(void*)&g_owner_token) +static int g_other_token; +#define OTHER ((struct wt_co*)(void*)&g_other_token) + +static void check(int ok, const char* what) +{ + checks++; + if (ok) { + printf(" [check] PASS %s\n", what); + } + else { + failures++; + printf(" [check] FAIL %s\n", what); + } +} + +static void gic_none(void) +{ +} + +static void gic_id(uint32_t intid) +{ + (void)intid; +} + +static void gic_prio(uint32_t intid, uint8_t priority) +{ + (void)intid; + (void)priority; +} + +static uint32_t gic_ack(void) +{ + return g_next_intid; +} + +static void gic_eoi(uint32_t intid) +{ + g_eoi = intid; +} + +static uint32_t gic_pmr(uint32_t pmr) +{ + return pmr; +} + +static uint32_t g_pended; + +static void gic_pend(uint32_t intid) +{ + g_pended = intid; +} + +static uint32_t gic_word(uint32_t word) +{ + (void)word; + return 0u; +} + +static uint32_t gic_lines(void) +{ + return 0u; +} + +static const struct wt_gic_ops g_host_gic = { + gic_none, gic_id, gic_id, gic_id, gic_prio, gic_ack, gic_eoi, gic_pend, + gic_id, gic_pmr, gic_id, gic_word, gic_word, gic_lines, 2u +}; +const struct wt_gic_ops* const wt_gic = &g_host_gic; + +void wt_el3_timer_arm_ms(uint32_t ms) +{ + (void)ms; +} + +void wt_el3_timer_disable(void) +{ +} + +static struct wt_co* g_owned_by = OWNER; +static int g_partition_running; + +struct wt_co* wt_spm_sint_owner(uint32_t intid) +{ + return (intid == OWNED_SPI) ? g_owned_by : NULL; +} + +void wt_spm_sint_queue_for(struct wt_co* co, uint32_t intid) +{ + g_queued_for = co; + g_queued_for_id = intid; +} + +void wt_spm_sint_queue(uint32_t intid) +{ + g_queued_any = intid; +} + +int wt_spm_sint_signal_needed(struct wt_co* owner) +{ + g_signal_asked = (owner == OWNER) ? 1 : 0; + return g_signal_needed; +} + +void wt_spm_preempt_from_fiq(wt_trap_frame_t* frame) +{ + g_preempted = frame; +} + +void wt_spm_preempt_from_irq(wt_trap_frame_t* frame) +{ + (void)frame; +} + +int wt_spm_current_is_partition(void) +{ + return g_partition_running; +} + +int wt_spm_sint_declared_any(uint32_t intid) +{ + return ((intid == OWNED_SPI) || (intid == CLAIMABLE_SPI)) ? 1 : 0; +} + +static void reset(uint32_t intid, int signal_needed) +{ + g_next_intid = intid; + g_eoi = 0u; + g_queued_for = NULL; + g_queued_for_id = 0u; + g_queued_any = 0u; + g_signal_needed = signal_needed; + g_signal_asked = 0; + g_preempted = NULL; + g_wt_spm_tick_intid = 0u; +} + +/* Two different interrupts queued for one partition before it next waits are + * both delivered, oldest first; neither overwrites the other (9.2.1). */ +static void fifo_rows(void) +{ + wt_spm_sint_fifo_t q; + uint32_t i; + int ok = 1; + + memset(&q, 0, sizeof(q)); + check(wt_spm_sint_fifo_pop(&q) == 0u, "an empty queue delivers nothing"); + check(wt_spm_sint_fifo_push(&q, OWNED_SPI) == 0 && + wt_spm_sint_fifo_push(&q, STRAY_SPI) == 0, + "two different interrupts queue for one partition"); + check(wt_spm_sint_fifo_pop(&q) == OWNED_SPI && + wt_spm_sint_fifo_pop(&q) == STRAY_SPI && + wt_spm_sint_fifo_pop(&q) == 0u, + "both are delivered, oldest first, and the queue drains"); + check(wt_spm_sint_fifo_push(&q, OWNED_SPI) == 0 && + wt_spm_sint_fifo_push(&q, OWNED_SPI) == 0 && + wt_spm_sint_fifo_pop(&q) == OWNED_SPI && + wt_spm_sint_fifo_pop(&q) == 0u, + "an id already queued is delivered once, as one GIC pending state"); + check(wt_spm_sint_fifo_push(&q, 0u) == -1 && q.count == 0u, + "id 0 is never queued, as it reads as none"); + for (i = 0u; i < WT_SPM_SINT_QUEUE_MAX; i++) { + ok = ok && (wt_spm_sint_fifo_push(&q, 32u + i) == 0); + } + check(ok != 0 && wt_spm_sint_fifo_push(&q, 100u) == -1 && + wt_spm_sint_fifo_pop(&q) == 32u, + "a full queue refuses another id and keeps what it holds"); +} + +/* The ACS test partitions' para-virtual interrupt controls: a partition may + * claim only an interrupt the platform declares for it, and the test timer + * raises only an interrupt its caller owns (the Normal world: only an SPI no + * partition owns or may claim). */ +static void authorization_rows(void) +{ + wt_ffa_native_sp_t sp; + + memset(&sp, 0, sizeof(sp)); + sp.intids[0] = CLAIMABLE_SPI; + sp.intid_count = 1u; + check(wt_spm_native_declares(&sp, CLAIMABLE_SPI) == 1, + "a partition may claim the interrupt its platform declares"); + check(wt_spm_native_declares(&sp, STRAY_SPI) == 0 && + wt_spm_native_declares(NULL, CLAIMABLE_SPI) == 0, + "but no other, and nothing without a declaration"); + sp.intid_count = WT_FFA_NATIVE_SP_INTIDS + 1u; + check(wt_spm_native_declares(&sp, CLAIMABLE_SPI) == 0, + "a declaration past its bound declares nothing"); + + check(wt_spm_twdog_arm(OWNER, OWNED_SPI, 1u) == 0, + "the owner arms the timer for its own interrupt"); + wt_spm_twdog_stop(OWNER); + check(wt_spm_twdog_arm(OTHER, OWNED_SPI, 1u) == -1, + "another partition cannot raise that interrupt"); + check(wt_spm_twdog_arm(OWNER, STRAY_SPI, 1u) == -1, + "nor can the owner raise an interrupt it does not own"); + check(wt_spm_twdog_arm(NULL, OWNED_SPI, 1u) == -1 && + wt_spm_twdog_arm(NULL, CLAIMABLE_SPI, 1u) == -1, + "the Normal world cannot raise a partition's interrupt, claimed or not"); + check(wt_spm_twdog_arm(NULL, WT_GIC_INTID_SECURE_TIMER, 1u) == -1, + "nor a private interrupt"); + check(wt_spm_twdog_arm(NULL, NS_TEST_SPI, 1u) == 0, + "the Normal world arms its own test interrupt"); + wt_spm_twdog_stop(NULL); +} + +/* A partition's timer stays bound to the partition that armed it: it is never + * raised once another partition owns its interrupt, nor once none does, and + * only its arming owner can stop it. */ +static void timer_owner_rows(void) +{ + g_owned_by = OWNER; + g_host_cntpct = 1000u; + check(wt_spm_twdog_arm(OWNER, OWNED_SPI, 5u) == 0, + "the owner arms a timer for its interrupt"); + g_owned_by = OTHER; + g_host_cntpct = 2000u; + g_pended = 0u; + wt_spm_twdog_tick(); + check(g_pended == 0u, + "once another partition owns the interrupt the expired timer does " + "not raise it for that partition"); + + g_owned_by = OWNER; + g_host_cntpct = 1000u; + check(wt_spm_twdog_arm(OWNER, OWNED_SPI, 5u) == 0, "the owner re-arms it"); + g_owned_by = NULL; + g_partition_running = 1; + g_host_cntpct = 1001u; + g_pended = 0u; + wt_spm_twdog_tick(); + check(g_pended == 0u, + "a released interrupt's timer is not taken for the Normal world's"); + g_partition_running = 0; + + g_owned_by = OWNER; + g_host_cntpct = 1000u; + check(wt_spm_twdog_arm(OWNER, OWNED_SPI, 5u) == 0, "the owner arms it again"); + wt_spm_twdog_stop(OTHER); + g_host_cntpct = 2000u; + g_pended = 0u; + wt_spm_twdog_tick(); + check(g_pended == OWNED_SPI, + "another partition cannot stop it, and it fires for its owner at " + "its deadline"); +} + +/* A Normal-world timer waits for its deadline and for a partition to run. */ +static void ns_timer_rows(void) +{ + g_host_cntpct = 1000u; + check(wt_spm_twdog_arm(NULL, NS_TEST_SPI, 5u) == 0, + "the Normal world arms its test interrupt for 5 ms"); + g_partition_running = 1; + g_host_cntpct = 1003u; + g_pended = 0u; + wt_spm_twdog_tick(); + check(g_pended == 0u, + "a partition running before the deadline is not interrupted yet"); + g_partition_running = 0; + g_host_cntpct = 1010u; + wt_spm_twdog_tick(); + check(g_pended == 0u, + "past the deadline it still waits for a partition to run"); + g_partition_running = 1; + wt_spm_twdog_tick(); + check(g_pended == NS_TEST_SPI, + "and is raised on the first tick past it that lands on a partition"); + g_partition_running = 0; +} + +int main(void) +{ + wt_trap_frame_t frame; + + printf("spm_irq: Secure interrupt routing at S-EL1\n"); + memset(&frame, 0, sizeof(frame)); + + reset(OWNED_SPI, 1); + wt_spm_lower_fiq(&frame); + check(g_queued_for == OWNER && g_queued_for_id == OWNED_SPI && + g_signal_asked == 1 && g_preempted == &frame && + g_eoi == OWNED_SPI, + "from S-EL0 a declared interrupt is queued for its owner and the " + "running partition preempted to signal it"); + + reset(OWNED_SPI, 1); + wt_spm_fiq(); + check(g_queued_for == OWNER && g_queued_for_id == OWNED_SPI && + g_signal_asked == 1 && g_eoi == OWNED_SPI, + "at S-EL1 a declared interrupt is queued and its waiting owner " + "signaled too, never dropped"); + check(g_preempted == NULL, + "with no partition running at S-EL1 nothing is preempted"); + check(g_wt_spm_tick_intid == OWNED_SPI, + "the id taken is still recorded for the boot proofs"); + + reset(STRAY_SPI, 0); + wt_spm_fiq(); + check(g_queued_any == STRAY_SPI && g_queued_for == NULL, + "an undeclared interrupt takes the same queue path as from S-EL0"); + + reset(WT_GIC_INTID_SECURE_TIMER, 0); + wt_spm_fiq(); + check(g_wt_spm_tick_intid == WT_GIC_INTID_SECURE_TIMER && + g_eoi == WT_GIC_INTID_SECURE_TIMER && g_queued_any == 0u && + g_queued_for == NULL, + "the secure tick at S-EL1 is recorded for the tick proof, not routed"); + + reset(WT_GIC_INTID_SPURIOUS, 0); + wt_spm_fiq(); + check(g_wt_spm_tick_intid == 0u && g_eoi == 0u && g_queued_any == 0u, + "a spurious acknowledge is neither ended nor routed"); + + reset(OWNED_SPI, 0); + check(wt_spm_ns_sint_take() == OWNED_SPI && g_eoi == OWNED_SPI && + g_queued_for == NULL && g_queued_any == 0u, + "the SPMC acknowledges and ends the interrupt that preempted the " + "Normal world itself, leaving the routing to its caller"); + reset(WT_GIC_INTID_SPURIOUS, 0); + check(wt_spm_ns_sint_take() == WT_GIC_INTID_SPURIOUS && g_eoi == 0u, + "with nothing pending it reports spurious and ends nothing"); + + fifo_rows(); + authorization_rows(); + timer_owner_rows(); + ns_timer_rows(); + + printf("spm_irq: %d checks, %d failures\n", checks, failures); + return (failures == 0) ? 0 : 1; +} diff --git a/tests/host/spm_irq/shim/wolftrust/arch/aarch64/sysreg.h b/tests/host/spm_irq/shim/wolftrust/arch/aarch64/sysreg.h new file mode 100644 index 00000000..d35060ca --- /dev/null +++ b/tests/host/spm_irq/shim/wolftrust/arch/aarch64/sysreg.h @@ -0,0 +1,50 @@ +/* sysreg.h + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfTrust. + * + * wolfTrust 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. + * + * wolfTrust 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, see . + */ + +#ifndef WOLFTRUST_ARCH_AARCH64_SYSREG_H +#define WOLFTRUST_ARCH_AARCH64_SYSREG_H + +#include + +/* Host stand-ins for the accessors spm_irq.c uses. */ +extern uint64_t g_host_cntpct; +extern unsigned int g_host_fiq_masked; + +static inline uint64_t wt_read_cntpct_el0(void) +{ + return g_host_cntpct++; +} + +static inline uint64_t wt_read_cntfrq_el0(void) +{ + return 1000u; +} + +static inline void wt_daif_clear_fiq(void) +{ + g_host_fiq_masked = 0u; +} + +static inline void wt_daif_set_fiq(void) +{ + g_host_fiq_masked = 1u; +} + +#endif /* WOLFTRUST_ARCH_AARCH64_SYSREG_H */ diff --git a/tests/target/detect_qemu_a.sh b/tests/target/detect_qemu_a.sh new file mode 100755 index 00000000..c4a3893c --- /dev/null +++ b/tests/target/detect_qemu_a.sh @@ -0,0 +1,19 @@ +#!/usr/bin/env bash +# Single source of truth for "can the AArch64 QEMU scenarios run here?", the +# twin of detect_m33mu.sh. Exit 0 when qemu-system-aarch64 and the +# aarch64-none-elf toolchain are reachable (or the user forced a target run), +# non-zero with a one-line reason on stdout otherwise. +set -u + +if [ "${WT_TARGET_SCENARIOS:-0}" = "1" ]; then + exit 0 +fi +if command -v "${QEMU:-qemu-system-aarch64}" >/dev/null 2>&1 && \ + command -v "${TOOLPREFIX:-aarch64-none-elf-}gcc" >/dev/null 2>&1 && \ + command -v timeout >/dev/null 2>&1 && \ + command -v truncate >/dev/null 2>&1; then + exit 0 +fi + +echo "qemu-system-aarch64 + aarch64-none-elf-gcc + timeout + truncate not detected — run inside ghcr.io/wolfssl/wolfboot-ci-aarch64 or set WT_TARGET_SCENARIOS=1" +exit 1 diff --git a/tests/target/lib/expect.sh b/tests/target/lib/expect.sh new file mode 100755 index 00000000..a08de9dc --- /dev/null +++ b/tests/target/lib/expect.sh @@ -0,0 +1,233 @@ +#!/usr/bin/env bash +# Assertion helpers shared by the target scenario runners (M33MU, STM32H5, +# QEMU). Source it, then point WT_EXPECT_LOG at the capture file; every helper +# reads it at call time. The Makefile and the suite drivers grep the +# " [check] " lines, so that format is fixed. +# +# WT_EXPECT_LOG= capture file the helpers assert on +# WT_EXPECT_GAP=1 `expect` also accepts the marker with bounded +# interleaved fragments between its words (boards +# where two guests raw-write one UART) +# +# tests/target/lib/expect.sh --selftest + +check_pass() { printf ' [check] PASS %s\n' "$1"; } +check_fail() { printf ' [check] FAIL %s (%s)\n' "$1" "$2"; exit 1; } + +# expect