Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 37 additions & 0 deletions ports/cortex_r52/gnu/example_build/s32z280_evb/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -127,3 +127,40 @@ if(TX_R52_ENABLE_VFP)
${EVB_LINK_QUIET_RWX}
)
endif()

# Nested IRQ handling on silicon. Needs the library built with
# TX_R52_ENABLE_IRQ_NESTING, since entry.S only emits the nesting_start and
# nesting_end pairing under that macro, so the image exists in that
# configuration only -- as s32z280_vfp.elf does for the floating-point ABI.
if(TX_R52_ENABLE_IRQ_NESTING)
add_executable(s32z280_nesting.elf EXCLUDE_FROM_ALL
${EVB_DIR}/entry.S
${EVB_DIR}/tx_initialize_low_level.S
${EVB_DIR}/linflexd.c
${EVB_DIR}/timer.c
${EVB_DIR}/mpu.c
${EVB_DIR}/gicv3.c
${EVB_DIR}/irq_dispatch.c
${EVB_DIR}/gic_probe.c
${EVB_DIR}/cache.c
${EVB_DIR}/demo_nesting_s32z280.c
)

target_compile_definitions(s32z280_nesting.elf PRIVATE TX_R52_USE_THREADX_IRQ=1)
target_compile_options(s32z280_nesting.elf PRIVATE -g)

target_link_libraries(s32z280_nesting.elf PRIVATE threadx)

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

target_link_options(s32z280_nesting.elf PRIVATE
-T${EVB_DIR}/link.lds
-nostartfiles
-Wl,-Map=s32z280_nesting.map
${EVB_LINK_QUIET_RWX}
)
endif()
22 changes: 22 additions & 0 deletions ports/cortex_r52/gnu/example_build/s32z280_evb/board.h
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,13 @@ unsigned int read_sctlr_after_mpu(void);

void board_irq_handler(void);

/* The nesting path splits the handler in three: the acknowledge happens in IRQ
mode before nesting starts, and the end-of-interrupt after it finishes.
board_irq_service does the middle part on an already-acknowledged INTID and
neither acknowledges nor EOIs. */

void board_irq_service(unsigned long intid);

/* Called from _tx_initialize_low_level when the kernel is linked. */

void board_init(void);
Expand All @@ -102,4 +109,19 @@ extern volatile unsigned long board_spurious_count;
extern volatile unsigned long board_unexpected_intid;
extern volatile unsigned long board_first_intid;

/* Nesting observability. Inert unless the image was built with
TX_ENABLE_IRQ_NESTING: without it the handler runs in IRQ mode with
interrupts masked and can never be re-entered. */

extern volatile unsigned long board_nest_depth;
extern volatile unsigned long board_nest_max;
extern volatile unsigned long board_sgi_count;
extern volatile unsigned long board_sgi_nested_count;
extern volatile unsigned long board_nest_provoke;
extern volatile unsigned long board_priority_bits;
extern volatile unsigned long board_timer_priority;
extern volatile unsigned long board_sgi_priority;

#define BOARD_NEST_SGI_INTID 8U

#endif /* BOARD_H */
240 changes: 240 additions & 0 deletions ports/cortex_r52/gnu/example_build/s32z280_evb/demo_nesting_s32z280.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,240 @@
/***************************************************************************
* Copyright (c) 2026 Eclipse ThreadX contributors
*
* This program and the accompanying materials are made available under the
* terms of the MIT License which is available at
* https://opensource.org/licenses/MIT.
*
* AI Disclosure: This file was largely AI-generated by Claude Code (Opus 5).
* The AI-generated portions may be considered public domain (CC0-1.0)
* and not subject to the project's licence. The human contributor has
* reviewed and verified that the code is correct.
*
* SPDX-License-Identifier: MIT and CC0-1.0
**************************************************************************/

/**************************************************************************/
/* */
/* BOARD SUPPORT RELEASE */
/* */
/* demo_nesting_s32z280.c Cortex-R52/GNU */
/* 6.5.2 */
/* AUTHOR */
/* */
/* Frederic Desbiens, Eclipse Foundation */
/* */
/* DESCRIPTION */
/* */
/* Nested IRQ handling on S32Z280 silicon. */
/* */
/* The FVP established that the nesting routines work and, more */
/* usefully, what breaks them: the interrupt has to be acknowledged */
/* before nesting starts, or the still-pending level-asserted timer is */
/* retaken the moment IRQ is enabled and recurses until the stacks are */
/* gone. entry.S here has the same ordering for the same reason. */
/* */
/* What the model could not answer is whether a real GIC-600 agrees. */
/* Two things differ from a model and both are visible here: */
/* */
/* P1 how many priority bits the GIC implements. Equal priorities */
/* do not preempt, and it is the low bits that vanish, so if the */
/* timer and the SGI collapse to the same value after truncation */
/* nesting cannot happen at all. board_init discovers the count */
/* by write and readback rather than assuming the model's five, */
/* and this demo reports it. */
/* */
/* P2 whether an SGI raised on real silicon is delivered at all. */
/* ICC_SGI1R is a 64-bit AArch32 CP15 register and its encoding */
/* does not transcribe from the AArch64 alias, so N1 tests SGI */
/* delivery on its own before nesting is involved. */
/* */
/**************************************************************************/

#include "tx_api.h"
#include "linflexd.h"
#include "board.h"
#include "gicv3.h"

#define DEMO_STACK_SIZE 2048

static TX_THREAD thread_check;
static TX_THREAD thread_spin;

static ULONG thread_check_stack[DEMO_STACK_SIZE / sizeof(ULONG)];
static ULONG thread_spin_stack[DEMO_STACK_SIZE / sizeof(ULONG)];

static volatile ULONG spin_runs;


/* Breakpoint target for tools/read_identity.gdb. Not static and not empty so
an optimising build cannot discard it. */

void bsp_done(void)
{
__asm volatile ("nop");
}


static UINT report(const char *label_ptr, UINT passed)
{
linflexd_puts(label_ptr);
linflexd_puts((passed != 0U) ? "PASS\n" : "FAIL\n");
return (passed != 0U) ? 0U : 1U;
}


static void report_hex(const char *label_ptr, unsigned long value)
{
linflexd_puts(label_ptr);
linflexd_puts(" = 0x");
linflexd_put_hex32((unsigned int) value);
linflexd_putc('\n');
}


static void thread_spin_entry(ULONG thread_input)
{
(void) thread_input;

for (;;)
{
spin_runs++;
}
}


static void thread_check_entry(ULONG thread_input)
{
UINT failures = 0U;
ULONG sgi_before;
ULONG ticks_start;
ULONG ticks_end;
ULONG spin_before;

(void) thread_input;

/* P1: what the GIC actually implements, and whether the two priorities this
test depends on survive truncation. */

report_hex("priority bits", board_priority_bits);
report_hex("timer effective", board_timer_priority);
report_hex("sgi effective", board_sgi_priority);

failures += report("P1 SGI outranks timer after truncation ",
(board_sgi_priority < board_timer_priority) ? 1U : 0U);

/* N1: SGI delivery on its own, before nesting is involved. */

linflexd_puts("N1 raising an SGI from thread context\n");

sgi_before = board_sgi_count;
gicv3_send_sgi(BOARD_NEST_SGI_INTID);
tx_thread_sleep(2);

failures += report("N1 SGI delivered and dispatched ",
(board_sgi_count > sgi_before) ? 1U : 0U);

if (board_sgi_count == sgi_before)
{
/* Everything below needs the SGI, so reporting four more failures with
the same cause would only obscure it. */

linflexd_puts("SGI never arrived; the nesting checks would only\n"
"restate that, so they are skipped.\n");
linflexd_puts("FAIL nested IRQ handling\n");
linflexd_puts("=== nesting demo complete ===\n");
bsp_done();
for (;;)
{
tx_thread_sleep(100UL);
}
}

/* N2 onwards: let the timer handler provoke a nested SGI. */

linflexd_puts("N2 enabling nested provocation in the timer handler\n");

board_nest_provoke = 1UL;
ticks_start = tx_time_get();
spin_before = spin_runs;
tx_thread_sleep(50);
ticks_end = tx_time_get();
board_nest_provoke = 0UL;

report_hex("max depth", board_nest_max);
report_hex("nested SGIs", board_sgi_nested_count);
report_hex("depth now", board_nest_depth);

failures += report("N2 SGI nested inside another handler ",
((board_sgi_nested_count > 0UL) &&
(board_nest_max >= 2UL)) ? 1U : 0U);

failures += report("N3 nesting unwound to depth zero ",
(board_nest_depth == 0UL) ? 1U : 0U);

failures += report("N4 tick still advancing after nesting ",
((ticks_end - ticks_start) >= 40UL) ? 1U : 0U);

failures += report("N5 lower-priority thread still scheduled ",
(spin_runs > spin_before) ? 1U : 0U);

report_hex("spurious", board_spurious_count);
report_hex("unexpected", board_unexpected_intid);

failures += report("N6 no spurious or unexpected interrupts ",
((board_spurious_count == 0UL) &&
(board_unexpected_intid == 0UL)) ? 1U : 0U);

if (failures == 0U)
{
linflexd_puts("PASS nested IRQ handling verified on silicon\n");
}
else
{
linflexd_puts("FAIL nested IRQ handling\n");
}

linflexd_puts("=== nesting demo complete ===\n");

bsp_done();

for (;;)
{
tx_thread_sleep(100UL);
}
}


void tx_application_define(void *first_unused_memory)
{
(void) first_unused_memory;

(void) tx_thread_create(&thread_check, "nest check", thread_check_entry,
0UL, thread_check_stack, sizeof(thread_check_stack),
10U, 10U, TX_NO_TIME_SLICE, TX_AUTO_START);

(void) tx_thread_create(&thread_spin, "spinner", thread_spin_entry,
0UL, thread_spin_stack, sizeof(thread_spin_stack),
20U, 20U, TX_NO_TIME_SLICE, TX_AUTO_START);
}


void bsp_main(void)
{
unsigned int console_status = linflexd_init();

linflexd_puts("\n=== ThreadX Cortex-R52 :: S32Z280-594EVB nested IRQ ===\n");
report_hex("console", (unsigned long) console_status);
linflexd_puts("entering kernel\n");

tx_kernel_enter();

/* Not reached. */

linflexd_puts("FAIL tx_kernel_enter returned\n");

for (;;)
{
__asm volatile ("nop");
}
}
32 changes: 32 additions & 0 deletions ports/cortex_r52/gnu/example_build/s32z280_evb/entry.S
Original file line number Diff line number Diff line change
Expand Up @@ -501,7 +501,39 @@ el1_irq_entry:

.global __tx_irq_processing_return
__tx_irq_processing_return:
#ifdef TX_ENABLE_IRQ_NESTING

/* Nested IRQ handling, the same shape as the FVP example, and the ordering
* matters as much here. The interrupt is acknowledged in IRQ mode with
* interrupts still masked, BEFORE nesting starts: reading ICC_IAR1 raises
* the GIC running priority to this interrupt's own, which masks it and
* everything of equal or lower priority, and only then is re-enabling IRQ
* safe. Start nesting first and the still-pending, still-level-asserted
* timer is retaken the instant IRQ is enabled, recursing until the IRQ and
* System stacks are gone -- a hang with no fault to point at.
*
* The INTID rides in r4, which survives the mode switch because only SP and
* LR are banked, and is pushed on the IRQ stack as well so a nested level
* reusing r4 cannot lose this level's value. End-of-interrupt waits for
* IRQ mode with interrupts masked again, so dropping the running priority
* cannot re-admit this same interrupt.
*/

bl gicv3_acknowledge
mov r4, r0
stmdb sp!, {r4, r5} /* r5 keeps 8-byte alignment */

bl _tx_thread_irq_nesting_start
mov r0, r4
bl board_irq_service
bl _tx_thread_irq_nesting_end

ldmia sp!, {r4, r5}
mov r0, r4
bl gicv3_end_of_interrupt
#else
bl board_irq_handler
#endif
b _tx_thread_context_restore

#else
Expand Down
Loading
Loading