summaryrefslogtreecommitdiff
path: root/.github/workflows/r52_fvp.yml
blob: 2293e1bddebb0538b3622984d7baa52e1cda07b9 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
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=<path> 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