# Copyright (c) 2026 Eclipse ThreadX contributors
# SPDX-License-Identifier: MIT
# Some portions generated by Claude Code (Opus 5).
#
# Example builds for Cortex-R52 on the Armv8-R AEM FVP (FVP_BaseR_AEMv8R).

set(FVP_DIR ${CMAKE_CURRENT_LIST_DIR})

# A bare-metal image has one flat DRAM region and no OS page permissions; access
# control belongs to the MPU, so an RWX segment is expected here rather than a
# mistake. GNU ld has warned about them since binutils 2.39 and takes this flag
# to stay quiet. ld.lld does not warn and rejects the flag outright, failing the
# link with "unknown argument", so the suppression is chosen by toolchain
# instead of being spelled into every target.
if(CMAKE_C_COMPILER_ID STREQUAL "GNU")
    set(R52_LINK_QUIET_RWX -Wl,--no-warn-rwx-segments)
else()
    set(R52_LINK_QUIET_RWX)
endif()

# Console sources.  Both backends are always compiled; console.c selects one at
# compile time and --gc-sections drops the unused one, which keeps every image
# building in either configuration without per-target source juggling.
set(R52_CONSOLE_SOURCES
    ${FVP_DIR}/console.c
    ${FVP_DIR}/uart_pl011.c
    ${FVP_DIR}/mpu.c
)

# AR1/M1 -- boot check.  Verifies the EL2 configuration, the drop to EL1 and
# the EL2 HVC seam.  Standalone by design: it does not link ThreadX, so a
# failure here is unambiguously a boot problem rather than a kernel problem.
add_executable(boot_check.elf EXCLUDE_FROM_ALL
    ${FVP_DIR}/entry.S
    ${R52_CONSOLE_SOURCES}
    ${FVP_DIR}/bsp_boot.c
)

target_include_directories(boot_check.elf PRIVATE ${FVP_DIR})

target_link_options(boot_check.elf PRIVATE
    -T${FVP_DIR}/link.lds
    -nostartfiles
    -Wl,-Map=boot_check.map
    ${R52_LINK_QUIET_RWX}
)

# AR1/M2 -- cooperative two-thread demo.  Links ThreadX and exercises the
# ported context-switch assembly with no interrupts and no timer tick.
add_executable(demo_m2.elf EXCLUDE_FROM_ALL
    ${FVP_DIR}/entry.S
    ${R52_CONSOLE_SOURCES}
    ${FVP_DIR}/tx_initialize_low_level.S
    ${FVP_DIR}/demo_m2.c
)

target_link_libraries(demo_m2.elf PRIVATE threadx)

target_include_directories(demo_m2.elf PRIVATE
    ${FVP_DIR}
    ${CMAKE_SOURCE_DIR}/common/inc
    ${CMAKE_SOURCE_DIR}/ports/${THREADX_ARCH}/${THREADX_TOOLCHAIN}/inc
)

target_link_options(demo_m2.elf PRIVATE
    -T${FVP_DIR}/link.lds
    -nostartfiles
    -Wl,-Map=demo_m2.map
    ${R52_LINK_QUIET_RWX}
)

# AR1/M3 -- periodic tick and preemptive scheduling.  Adds GICv3, the generic
# timer and the ThreadX IRQ path.  TX_R52_USE_THREADX_IRQ routes the EL1 IRQ
# vector into _tx_thread_context_save and has _tx_initialize_low_level bring up
# the interrupt controller; images without it keep the fault reporter.
add_executable(demo_m3.elf EXCLUDE_FROM_ALL
    ${FVP_DIR}/entry.S
    ${R52_CONSOLE_SOURCES}
    ${FVP_DIR}/gicv3.c
    ${FVP_DIR}/timer.c
    ${FVP_DIR}/irq_dispatch.c
    ${FVP_DIR}/tx_initialize_low_level.S
    ${FVP_DIR}/demo_m3.c
)

target_compile_definitions(demo_m3.elf PRIVATE TX_R52_USE_THREADX_IRQ)

target_link_libraries(demo_m3.elf PRIVATE threadx)

target_include_directories(demo_m3.elf PRIVATE
    ${FVP_DIR}
    ${CMAKE_SOURCE_DIR}/common/inc
    ${CMAKE_SOURCE_DIR}/ports/${THREADX_ARCH}/${THREADX_TOOLCHAIN}/inc
)

target_link_options(demo_m3.elf PRIVATE
    -T${FVP_DIR}/link.lds
    -nostartfiles
    -Wl,-Map=demo_m3.map
    ${R52_LINK_QUIET_RWX}
)

# AR1/M4 -- the standard eight-thread ThreadX demo.  demo_threadx.c is the
# shipped sample kept byte-identical apart from its header note and one call
# into the verification harness, which lives in demo_verify.c.
add_executable(demo_threadx.elf EXCLUDE_FROM_ALL
    ${FVP_DIR}/entry.S
    ${R52_CONSOLE_SOURCES}
    ${FVP_DIR}/gicv3.c
    ${FVP_DIR}/timer.c
    ${FVP_DIR}/irq_dispatch.c
    ${FVP_DIR}/tx_initialize_low_level.S
    ${FVP_DIR}/demo_threadx.c
    ${FVP_DIR}/demo_verify.c
)

target_compile_definitions(demo_threadx.elf PRIVATE TX_R52_USE_THREADX_IRQ)

target_link_libraries(demo_threadx.elf PRIVATE threadx)

target_include_directories(demo_threadx.elf PRIVATE
    ${FVP_DIR}
    ${CMAKE_SOURCE_DIR}/common/inc
    ${CMAKE_SOURCE_DIR}/ports/${THREADX_ARCH}/${THREADX_TOOLCHAIN}/inc
)

target_link_options(demo_threadx.elf PRIVATE
    -T${FVP_DIR}/link.lds
    -nostartfiles
    -Wl,-Map=demo_threadx.map
    ${R52_LINK_QUIET_RWX}
)

# AR1/M5 -- lazy VFP context save and restore.  Only meaningful when the
# library was built with TX_R52_ENABLE_VFP and a floating-point ABI, so the
# target exists only in that configuration.
if(TX_R52_ENABLE_VFP)
    add_executable(demo_m5.elf EXCLUDE_FROM_ALL
        ${FVP_DIR}/entry.S
        ${R52_CONSOLE_SOURCES}
        ${FVP_DIR}/gicv3.c
        ${FVP_DIR}/timer.c
        ${FVP_DIR}/irq_dispatch.c
        ${FVP_DIR}/tx_initialize_low_level.S
        ${FVP_DIR}/demo_m5.c
    )

    target_compile_definitions(demo_m5.elf PRIVATE TX_R52_USE_THREADX_IRQ)

    target_link_libraries(demo_m5.elf PRIVATE threadx)

    target_include_directories(demo_m5.elf PRIVATE
        ${FVP_DIR}
        ${CMAKE_SOURCE_DIR}/common/inc
        ${CMAKE_SOURCE_DIR}/ports/${THREADX_ARCH}/${THREADX_TOOLCHAIN}/inc
    )

    target_link_options(demo_m5.elf PRIVATE
        -T${FVP_DIR}/link.lds
        -nostartfiles
        -Wl,-Map=demo_m5.map
        ${R52_LINK_QUIET_RWX}
    )
endif()

# AR1/M5 -- PMSAv8-R protection and caches.  Verifies enforcement, so it needs
# the recoverable data-abort path in entry.S (TX_R52_MPU_FAULT_TEST) and the
# protection itself (TX_R52_ENABLE_MPU), independently of the global option.
add_executable(demo_mpu.elf EXCLUDE_FROM_ALL
    ${FVP_DIR}/entry.S
    ${R52_CONSOLE_SOURCES}
    ${FVP_DIR}/gicv3.c
    ${FVP_DIR}/timer.c
    ${FVP_DIR}/irq_dispatch.c
    ${FVP_DIR}/tx_initialize_low_level.S
    ${FVP_DIR}/demo_mpu.c
)

target_compile_definitions(demo_mpu.elf PRIVATE
    TX_R52_USE_THREADX_IRQ
    TX_R52_ENABLE_MPU
    TX_R52_MPU_FAULT_TEST
)

target_link_libraries(demo_mpu.elf PRIVATE threadx)

target_include_directories(demo_mpu.elf PRIVATE
    ${FVP_DIR}
    ${CMAKE_SOURCE_DIR}/common/inc
    ${CMAKE_SOURCE_DIR}/ports/${THREADX_ARCH}/${THREADX_TOOLCHAIN}/inc
)

target_link_options(demo_mpu.elf PRIVATE
    -T${FVP_DIR}/link.lds
    -nostartfiles
    -Wl,-Map=demo_mpu.map
    ${R52_LINK_QUIET_RWX}
)

# Nested IRQ handling.  Needs the library built with TX_R52_ENABLE_IRQ_NESTING,
# because _tx_thread_irq_nesting_start only re-enables IRQ usefully when
# tx_port.h agrees the configuration supports it, and entry.S only emits the
# start/end pairing under the same macro.  The image therefore exists in that
# configuration only, as demo_m5.elf does for VFP.
if(TX_R52_ENABLE_IRQ_NESTING)
    add_executable(demo_nesting.elf EXCLUDE_FROM_ALL
        ${FVP_DIR}/entry.S
        ${R52_CONSOLE_SOURCES}
        ${FVP_DIR}/gicv3.c
        ${FVP_DIR}/timer.c
        ${FVP_DIR}/irq_dispatch.c
        ${FVP_DIR}/tx_initialize_low_level.S
        ${FVP_DIR}/demo_nesting.c
    )

    target_compile_definitions(demo_nesting.elf PRIVATE TX_R52_USE_THREADX_IRQ)

    target_link_libraries(demo_nesting.elf PRIVATE threadx)

    target_include_directories(demo_nesting.elf PRIVATE
        ${FVP_DIR}
        ${CMAKE_SOURCE_DIR}/common/inc
        ${CMAKE_SOURCE_DIR}/ports/${THREADX_ARCH}/${THREADX_TOOLCHAIN}/inc
    )

    target_link_options(demo_nesting.elf PRIVATE
        -T${FVP_DIR}/link.lds
        -nostartfiles
        -Wl,-Map=demo_nesting.map
        ${R52_LINK_QUIET_RWX}
    )
endif()

# Nested FIQ handling.  Needs TX_R52_ENABLE_FIQ_NESTING, which the port-level
# CMakeLists already requires TX_R52_ENABLE_FIQ alongside, so the image exists
# only where both the FIQ vector path and the nesting pairing are compiled in.
if(TX_R52_ENABLE_FIQ_NESTING)
    add_executable(demo_fiq.elf EXCLUDE_FROM_ALL
        ${FVP_DIR}/entry.S
        ${R52_CONSOLE_SOURCES}
        ${FVP_DIR}/gicv3.c
        ${FVP_DIR}/timer.c
        ${FVP_DIR}/irq_dispatch.c
        ${FVP_DIR}/tx_initialize_low_level.S
        ${FVP_DIR}/demo_fiq.c
    )

    target_compile_definitions(demo_fiq.elf PRIVATE TX_R52_USE_THREADX_IRQ)

    target_link_libraries(demo_fiq.elf PRIVATE threadx)

    target_include_directories(demo_fiq.elf PRIVATE
        ${FVP_DIR}
        ${CMAKE_SOURCE_DIR}/common/inc
        ${CMAKE_SOURCE_DIR}/ports/${THREADX_ARCH}/${THREADX_TOOLCHAIN}/inc
    )

    target_link_options(demo_fiq.elf PRIVATE
        -T${FVP_DIR}/link.lds
        -nostartfiles
        -Wl,-Map=demo_fiq.map
        ${R52_LINK_QUIET_RWX}
    )
endif()

# Every image built here, in the order they should be exercised.
set(R52_IMAGES boot_check.elf demo_m2.elf demo_m3.elf demo_threadx.elf demo_mpu.elf)

# The linker script is passed with -T, which CMake does not treat as a
# dependency, so editing it would not trigger a relink and stale images would
# be tested against new region boundaries.  Declare it explicitly.
foreach(image IN LISTS R52_IMAGES)
    set_target_properties(${image} PROPERTIES LINK_DEPENDS ${FVP_DIR}/link.lds)
endforeach()
if(TX_R52_ENABLE_VFP)
    list(APPEND R52_IMAGES demo_m5.elf)
endif()
if(TX_R52_ENABLE_IRQ_NESTING)
    list(APPEND R52_IMAGES demo_nesting.elf)
endif()
if(TX_R52_ENABLE_FIQ_NESTING)
    list(APPEND R52_IMAGES demo_fiq.elf)
endif()

# Console backend.  Semihosting is the default because it needs no peripheral
# and so cannot be broken by a wrong memory map; the PL011 path is what real
# silicon will use.
# Protection and caches for every image.  Off by default so a bring-up failure
# can always be reproduced with the simplest possible memory configuration.
option(TX_R52_ENABLE_MPU
       "Enable the PMSAv8-R MPU and caches in every image" OFF)
if(TX_R52_ENABLE_MPU)
    foreach(image IN LISTS R52_IMAGES)
        target_compile_definitions(${image} PRIVATE TX_R52_ENABLE_MPU)
    endforeach()
endif()

option(TX_R52_CONSOLE_PL011
       "Use the PL011 UART for console output instead of semihosting" OFF)
if(TX_R52_CONSOLE_PL011)
    foreach(image IN LISTS R52_IMAGES)
        target_compile_definitions(${image} PRIVATE TX_R52_CONSOLE_PL011)
    endforeach()
endif()

# Run a target on the FVP.  Each image exits by itself through the
# semihosting SYS_EXIT call, so no host-side timeout is needed.  UART0 is
# routed to stdout unconditionally so that PL011-console images are visible
# too; it is harmless for semihosting images.
find_program(FVP_BASER_AEMV8R FVP_BaseR_AEMv8R
    HINTS $ENV{HOME}/FVP_Base_AEMv8R_11.32_19/bin
)
if(FVP_BASER_AEMV8R)
    function(threadx_r52_add_fvp_run target_name run_target description)
        add_custom_target(${run_target}
            COMMAND ${FVP_BASER_AEMV8R}
                    -C cluster0.NUM_CORES=1
                    -C bp.vis.disable_visualisation=1
                    -C bp.terminal_0.start_telnet=0
                    -C bp.pl011_uart0.out_file=-
                    -C bp.pl011_uart0.unbuffered_output=1
                    -a $<TARGET_FILE:${target_name}>
            DEPENDS ${target_name}
            USES_TERMINAL
            COMMENT "${description}"
        )
    endfunction()

    threadx_r52_add_fvp_run(boot_check.elf run-boot-check-r52
        "Running AR1/M1 boot check on FVP_BaseR_AEMv8R...")
    threadx_r52_add_fvp_run(demo_m2.elf run-demo-m2-r52
        "Running AR1/M2 cooperative switch demo on FVP_BaseR_AEMv8R...")
    threadx_r52_add_fvp_run(demo_m3.elf run-demo-m3-r52
        "Running AR1/M3 tick and preemption demo on FVP_BaseR_AEMv8R...")
    threadx_r52_add_fvp_run(demo_threadx.elf run-demo-threadx-r52
        "Running the standard ThreadX demo on FVP_BaseR_AEMv8R...")

    # Automated checks.  The runner judges each image by its self-reported
    # result and treats a missing result line as failure, so a hang cannot
    # pass.  Registered with CTest when Python is available, plus a plain
    # target so the suite is runnable without CTest.
    find_package(Python3 COMPONENTS Interpreter)
    if(Python3_FOUND)
        set(R52_RUNNER ${FVP_DIR}/test/run_fvp_test.py)
        set(R52_TEST_IMAGES ${R52_IMAGES})

        enable_testing()
        foreach(image IN LISTS R52_TEST_IMAGES)
            add_test(NAME r52-fvp-${image}
                     COMMAND ${Python3_EXECUTABLE} ${R52_RUNNER}
                             --elf $<TARGET_FILE:${image}>
                             --fvp ${FVP_BASER_AEMV8R})
        endforeach()

        add_custom_target(check-r52-fvp
            COMMENT "Running every Cortex-R52 FVP image through the test runner..."
        )
        foreach(image IN LISTS R52_TEST_IMAGES)
            add_custom_command(TARGET check-r52-fvp POST_BUILD
                COMMAND ${Python3_EXECUTABLE} ${R52_RUNNER}
                        --elf $<TARGET_FILE:${image}>
                        --fvp ${FVP_BASER_AEMV8R}
            )
            add_dependencies(check-r52-fvp ${image})
        endforeach()
    else()
        message(STATUS "Python3 not found; check-r52-fvp unavailable.")
    endif()
else()
    message(STATUS "FVP_BaseR_AEMv8R not found; FVP run targets unavailable.")
endif()
