name: r52_fvp # Builds every Cortex-R52 CMake image in both supported configurations and # EXECUTES them on the Armv8-R AEM FVP, asserting each image's self-reported # result. # # Why this exists: until this workflow landed, nothing in CI ran a single # instruction of any ThreadX port. gcc_check compiles and links -- its own # header says so, "it compiles and links and **executes nothing**" -- and its # CMake stage covers exactly one R52 configuration, the base FVP example with # -DTX_R52_ENABLE_MPU=ON. So a change that assembles cleanly and links cleanly # and then hangs on the first context switch passes every required check. # # That gap matters more for cortex_r52 than for the other ports, because the # R52 is the foundation the partitioning work is sequenced behind: the EL2 boot # path in entry.S, the MPU programming, and the generic-timer plumbing are all # things later work attaches to rather than replaces. A regression in any of # them has to be caught by an execution test; there is no static check for # "the scheduler still schedules". # # What it covers: # # - the default configuration -- the images gcc_check's CMake stage builds, # but run rather than merely linked, plus the FVP module manager example, # whose two images are the module isolation tests; # - the feature configuration -- the same images with VFP and the hard float # ABI, FIQ, IRQ nesting and FIQ nesting. Those four options gate whole # assembly blocks (the VMRS/VSTMDB/VLDMIA sequences, the FIQ context paths, # the nesting start/end pairs) that the default configuration never # assembles, let alone runs. # # Both configurations also build the S32Z280 kernel and module examples. Those # need silicon, which a hosted runner does not have, so they are built and # never run: CTest registers only the FVP images. on: # push as well as pull_request, for the reason gcc_check.yml states at # length: dev needs its own baseline so a bad squash-merge is caught then # rather than by whoever opens the next pull request. push: branches: [ master, dev ] paths: - ".github/workflows/r52_fvp.yml" - "CMakeLists.txt" - "cmake/cortex_r52*.cmake" - "common/**" - "ports/cortex_r52/**" - "ports_module/cortex_r52/**" pull_request: branches: [ master, dev ] # Duplicated rather than shared through a YAML anchor. GitHub Actions' # parser does not dependably honour anchors and the failure mode is the # whole workflow refusing to parse -- a job that dies before it does # anything, which is the exact defect gcc_check.yml carries a comment # about. **Edit both.** paths: - ".github/workflows/r52_fvp.yml" - "CMakeLists.txt" - "cmake/cortex_r52*.cmake" - "common/**" - "ports/cortex_r52/**" - "ports_module/cortex_r52/**" jobs: # One job rather than one per configuration, for the reason gcc_check.yml # gives for folding its two toolchains together: the checks list stays short, # and a single run sees every configuration so a cross-configuration # regression shows up in one log instead of two. r52: runs-on: ubuntu-24.04 env: # Pinned deliberately. Matches gcc_check.yml and ci_cortex_m.yml so all # three share one cache entry rather than each holding its own copy of # the same archive; change them together or the sharing silently stops. # 14.3.rel1 is the project's GCC 14 default. GCC_VERSION: 14.3.rel1 # The Armv8-R AEM FVP, from Arm's unauthenticated download permalink. # Only the _Linux_x86.tar.gz name resolves; the _Linux64.tgz name of # older releases returns 404. The archive holds Arm's installer, which # refuses to run until its EULA is accepted, so the install step accepts # it. The model is fetched from Arm on every cache miss and never # re-hosted. # # Pinned with its checksum so that a change of model is a reviewed diff. # Keep the version in step with the find_program hint in # ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/CMakeLists.txt. FVP_URL: https://developer.arm.com/-/cdn-downloads/permalink/FVPs-Architecture/FM-11.32/FVP_Base_AEMv8R_11.32_19_Linux_x86.tar.gz FVP_SHA256: e6d046946529db86129ee6d8e7a2d5c3667d6c4c98365ad815586db8bca06613 steps: # Actions are pinned to a commit SHA with the version in the trailing # comment. A tag can be moved; a SHA cannot. Dependabot moves these pins # and rewrites the comments -- see .github/dependabot.yml. - name: Check out the repository uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 # Path and key match gcc_check.yml's AArch32 entry exactly, so the two # workflows share one cache entry. Only the AArch32 toolchain is needed # here: cortex_r52 is Armv8-R AArch32, and nothing this workflow builds # targets aarch64. - name: Cache the AArch32 Arm GNU toolchain id: cache-arm32 uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 with: path: toolchain key: arm-gnu-toolchain-${{ env.GCC_VERSION }}-x86_64-arm-none-eabi # The checksum suffix is .sha256asc and not .sha256. Arm publishes both, # and for arm-none-eabi the .sha256 file contains a 32-character MD5, so # sha256sum -c on it fails with "no properly formatted checksum lines # found". Do not "simplify" the suffix. (Same note as gcc_check.yml, which # is where it was first paid for.) - name: Install the AArch32 Arm GNU toolchain if: steps.cache-arm32.outputs.cache-hit != 'true' run: | set -eu base="https://developer.arm.com/-/media/Files/downloads/gnu/${GCC_VERSION}/binrel" archive="arm-gnu-toolchain-${GCC_VERSION}-x86_64-arm-none-eabi.tar.xz" mkdir -p toolchain && cd toolchain curl -fsSLO "$base/$archive" curl -fsSLO "$base/$archive.sha256asc" sha256sum -c "$archive.sha256asc" tar xf "$archive" rm -f "$archive" # Only reaches apt if the runner image has stopped shipping ninja. This # repository has already paid for unguarded apt calls -- scripts/install.sh # carries a long comment about apt-get update stalling for over two hours # and taking whole regression runs with it. Do not make this # unconditional. - name: Ensure ninja is available run: | set -eu if command -v ninja >/dev/null 2>&1; then ninja --version else sudo apt-get update sudo apt-get install -y --no-install-recommends ninja-build fi # Keyed on the archive's checksum, which changes exactly when the model # does. - name: Cache the Armv8-R AEM FVP id: cache-fvp uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 with: path: fvp key: fvp-aemv8r-${{ env.FVP_SHA256 }} - name: Install the Armv8-R AEM FVP if: steps.cache-fvp.outputs.cache-hit != 'true' run: | set -eu mkdir -p fvp-download curl -fsSL --retry 3 -o fvp-download/fvp.tar.gz "$FVP_URL" echo "$FVP_SHA256 fvp-download/fvp.tar.gz" | sha256sum -c - tar xzf fvp-download/fvp.tar.gz -C fvp-download sh fvp-download/FVP_Base_AEMv8R_*_Linux_x86.sh \ --i-agree-to-the-contained-eula --no-interactive \ -d "$GITHUB_WORKSPACE/fvp" rm -rf fvp-download # The model links against libatomic. Everything else it needs # (libstdc++, libgcc_s, librt, libpthread) is already on the runner image, # and its own libscxframework/libsystemc ship with the model. The # runner usually has libatomic1 by way of gcc, so this is guarded for the # same reason the ninja step is. - name: Ensure the FVP's runtime dependencies are present run: | set -eu if ldconfig -p | grep -q 'libatomic\.so\.1'; then echo "libatomic1 present" else sudo apt-get update sudo apt-get install -y --no-install-recommends libatomic1 fi # Resolve the model once and hand its absolute path to CMake below. # # ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/CMakeLists.txt finds # it with find_program(... HINTS $ENV{HOME}/FVP_Base_AEMv8R_11.32_19/bin), # a hint that is right on a developer's machine and meaningless here, so # the path is passed explicitly. find_program caches into FVP_BASER_AEMV8R, # which means -DFVP_BASER_AEMV8R= overrides it with no change to the # CMake files. # # A missing binary fails here, rather than leaving CMake without a model # and CTest with no tests to run. - name: Locate the FVP id: fvp run: | set -eu binary="$(find fvp -type f -name FVP_BaseR_AEMv8R -perm -u+x | head -1)" if [ -z "$binary" ]; then echo "::error::The FVP install produced no FVP_BaseR_AEMv8R binary." find fvp -maxdepth 3 -type d | sed 's/^/ /' >&2 exit 1 fi binary="$(cd "$(dirname "$binary")" && pwd)/$(basename "$binary")" echo "binary=$binary" >>"$GITHUB_OUTPUT" "$binary" --version # Both configurations are configured, built and run by one script, so that adding a configuration is one line rather # than three near-identical blocks of YAML. # # Every image is named explicitly from the generated ninja graph rather # than from a list kept here. The images are EXCLUDE_FROM_ALL -- a bare # "cmake --build" reports "no work to do" and would compare nothing -- # and reading the graph means a target added to CMakeLists.txt cannot # escape this check by nobody remembering to list it. This is the same # technique, and the same reasoning, as scripts/check_gcc.sh's CMake stage. # # TX_R52_FLOAT_ABI=hard is not optional alongside TX_R52_ENABLE_VFP: the # port's CMakeLists.txt refuses the combination outright (#686), because # TX_ENABLE_VFP_SUPPORT enables VMRS/VSTMDB/VLDMIA blocks that a soft # float ABI leaves the assembler unable to accept. - name: Build and run the Cortex-R52 images run: | set -eu export PATH="$GITHUB_WORKSPACE/toolchain/arm-gnu-toolchain-${GCC_VERSION}-x86_64-arm-none-eabi/bin:$PATH" arm-none-eabi-gcc --version | head -1 fvp_binary='${{ steps.fvp.outputs.binary }}' status=0 run_configuration() { name="$1"; shift build="build/$name" echo "" echo "==============================================================" echo "== $name" echo "==============================================================" if ! cmake -S . -B "$build" -G Ninja \ -DCMAKE_TOOLCHAIN_FILE=cmake/cortex_r52.cmake \ -DFVP_BASER_AEMV8R="$fvp_binary" "$@"; then echo "::error::$name: CMake configure failed" status=1 return fi images="$(ninja -C "$build" -t targets all 2>/dev/null \ | grep -oE '^[A-Za-z0-9_]+\.elf' \ | grep -v '^cmake_' | sort -u)" if [ -z "$images" ]; then echo "::error::$name: no .elf targets found in the CMake graph" status=1 return fi echo "$name: building $(echo "$images" | wc -l) images" # shellcheck disable=SC2086 if ! cmake --build "$build" --target $images; then echo "::error::$name: one or more images did not build" status=1 return fi # --no-tests=error, because ctest's default is to report success # when it finds nothing to run. The example registers its tests # inside if(FVP found) and if(Python3_FOUND), so either one going # unmet would leave this stage green having executed nothing -- the # very hole this workflow exists to close. if ! ctest --test-dir "$build" --output-on-failure --no-tests=error; then echo "::error::$name: one or more images failed on the FVP, or no tests were registered" status=1 fi } # Every example option defaults to OFF, so each one a configuration # leaves out is an example that no check builds. run_configuration default \ -DTX_R52_BUILD_FVP_EXAMPLE=ON \ -DTX_R52_BUILD_FVP_MODULE_EXAMPLE=ON \ -DTX_R52_BUILD_S32Z280_EXAMPLE=ON \ -DTX_R52_BUILD_S32Z280_MODULE_EXAMPLE=ON run_configuration feature \ -DTX_R52_BUILD_FVP_EXAMPLE=ON \ -DTX_R52_BUILD_FVP_MODULE_EXAMPLE=ON \ -DTX_R52_BUILD_S32Z280_EXAMPLE=ON \ -DTX_R52_BUILD_S32Z280_MODULE_EXAMPLE=ON \ -DTX_R52_ENABLE_VFP=ON \ -DTX_R52_FLOAT_ABI=hard \ -DTX_R52_ENABLE_FIQ=ON \ -DTX_R52_ENABLE_IRQ_NESTING=ON \ -DTX_R52_ENABLE_FIQ_NESTING=ON exit $status