From 559460bed92d4d5368cd72cbb53fce18ff59748f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Desbiens?= Date: Thu, 13 Aug 2026 17:13:34 -0400 Subject: [PATCH] Exercised the nested IRQ path, which had never once been entered (#611) _tx_thread_irq_nesting_start and _tx_thread_irq_nesting_end have shipped in this port since it was written, compiled into every build, and nothing had ever called either one. Not on the model, not on silicon, not in any demo. demo_nesting.elf enters them. Provoking nesting needs two sources with different priorities. The generic timer PPI was already there; the second is an SGI, which a core can raise on itself. gicv3.c gains gicv3_enable_sgi and gicv3_send_sgi for that. ICC_SGI1R is 64-bit, so in AArch32 it is an MCRR rather than an MCR, and the AArch64 name the Cortex-A72 example uses, S3_0_C12_C11_5, does not transcribe to the AArch32 CP15 space. The encoding here is confirmed by check N1 in the demo, which raises an SGI from thread context and requires it to be delivered and dispatched. The order of the pairing is the whole difficulty, and getting it wrong does not fail gracefully. The interrupt must be acknowledged BEFORE nesting starts. Reading ICC_IAR1 is what raises the GIC running priority to this interrupt's own, which masks it and everything of equal or lower priority; only then is re-enabling IRQ safe. My first attempt called nesting_start first and acknowledged inside the handler, so the still-pending, still-level-asserted timer was taken again the instant IRQ was enabled, and again, until the IRQ and System stacks were destroyed. It presented as garbage on the console and a hang with no fault to point at, and it broke demo_m3 and demo_threadx while leaving boot_check, demo_m2 and demo_mpu passing, because only the first two depend on the tick advancing. The Cortex-R5 example BSP states the requirement in one line: "ensure all IRQ interrupts are cleared prior to enabling nested IRQ interrupts." So entry.S now acknowledges in IRQ mode, carries the INTID in r4 -- which survives the mode switch, since only SP and LR are banked -- and also pushes it on the IRQ stack so a nested level reusing r4 cannot lose the outer level's value. End-of-interrupt waits until after nesting_end, in IRQ mode with interrupts masked, so dropping the running priority cannot re-admit the same interrupt. board_irq_handler splits in two. board_irq_service does the middle part on an already-acknowledged INTID and neither acknowledges nor EOIs; board_irq_handler keeps its old shape as the non-nesting entry point, so images built without TX_ENABLE_IRQ_NESTING behave exactly as before. The nesting instrumentation in irq_dispatch.c is inert unless an image asks for it. board_nest_provoke gates the SGI that the timer handler raises, so every other image sees the handler it always had. Verified on FVP_BaseR_AEMv8R. The nesting demo reports max depth 2, fifty nested SGIs across fifty ticks, depth unwound to zero, the tick still advancing, the low-priority thread still scheduled, and no spurious or unexpected INTIDs. In the same nesting configuration the five existing images all pass, so the split did not disturb the ordinary path. Both toolchains build it, GNU with no warnings and Arm Toolchain for Embedded 22.1.0 as well. Not done here: the S32Z280 example keeps its own irq_dispatch.c and entry.S and is untouched, so nesting on silicon is a separate change. Assisted-by: Claude Code (Opus 5) --- .../fvp_baser_aemv8r/CMakeLists.txt | 37 +++ .../example_build/fvp_baser_aemv8r/board.h | 23 ++ .../fvp_baser_aemv8r/demo_nesting.c | 216 ++++++++++++++++++ .../example_build/fvp_baser_aemv8r/entry.S | 40 ++++ .../example_build/fvp_baser_aemv8r/gicv3.c | 50 ++++ .../example_build/fvp_baser_aemv8r/gicv3.h | 9 + .../fvp_baser_aemv8r/irq_dispatch.c | 96 +++++++- 7 files changed, 467 insertions(+), 4 deletions(-) create mode 100644 ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/demo_nesting.c diff --git a/ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/CMakeLists.txt b/ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/CMakeLists.txt index c0824b26..ccb54258 100644 --- a/ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/CMakeLists.txt +++ b/ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/CMakeLists.txt @@ -197,6 +197,40 @@ target_link_options(demo_mpu.elf PRIVATE ${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() + # 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) @@ -209,6 +243,9 @@ 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() # 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 diff --git a/ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/board.h b/ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/board.h index b514d29c..2e985e50 100644 --- a/ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/board.h +++ b/ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/board.h @@ -55,6 +55,13 @@ void board_init(void); void board_irq_handler(void); +/* The nesting path splits the handler in three, because the acknowledge has to + happen in IRQ mode before nesting starts and the end-of-interrupt after it + finishes. board_irq_service does the middle part on an INTID that has + already been acknowledged, and does not acknowledge or EOI itself. */ + +void board_irq_service(unsigned long intid); + /* Interrupt observability, maintained by board_irq_handler. */ extern volatile unsigned long board_irq_count; @@ -62,6 +69,22 @@ extern volatile unsigned long board_timer_intid; extern volatile unsigned long board_spurious_count; extern volatile unsigned long board_unexpected_intid; +/* Nesting observability, maintained by board_irq_handler. All of this is 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; + +/* Set by demo_nesting to make the timer handler provoke a nested SGI. Left at + zero every other image behaves exactly as before. */ + +extern volatile unsigned long board_nest_provoke; + +#define BOARD_NEST_SGI_INTID 8U + /* Counted by the EL2 hyp-trap handler in entry.S; the ZoneX seam. */ extern volatile unsigned long _hvc_call_count; diff --git a/ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/demo_nesting.c b/ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/demo_nesting.c new file mode 100644 index 00000000..0e204d06 --- /dev/null +++ b/ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/demo_nesting.c @@ -0,0 +1,216 @@ +/*************************************************************************** + * 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.c Cortex-R52/GNU */ +/* 6.5.2 */ +/* AUTHOR */ +/* */ +/* Frederic Desbiens, Eclipse Foundation */ +/* */ +/* DESCRIPTION */ +/* */ +/* Nested IRQ handling: _tx_thread_irq_nesting_start and */ +/* _tx_thread_irq_nesting_end. */ +/* */ +/* Those two routines have shipped in this port since it was written */ +/* and nothing had ever called them. They were compiled into every */ +/* build, never entered by any image, on the model or on silicon. */ +/* This is the image that enters them. */ +/* */ +/* HOW NESTING IS PROVOKED */ +/* */ +/* Two interrupt sources are needed and one must outrank the other. */ +/* The generic timer PPI is already there; the second is an SGI, which */ +/* a core can raise on itself, given a numerically lower priority so */ +/* the GIC lets it preempt. */ +/* */ +/* Inside the timer handler, after nesting_start has left IRQ mode for */ +/* System mode and re-enabled IRQ, board_irq_handler raises the SGI and */ +/* spins briefly. The SGI outranks the timer, so it is delivered */ +/* immediately and re-enters el1_irq_entry while the timer handler is */ +/* still on the stack. That is nesting, and the depth counter sees 2. */ +/* */ +/* The spin is bounded. Without TX_ENABLE_IRQ_NESTING the SGI can */ +/* never arrive, and a test that hangs in that configuration would be */ +/* worse than one that fails. */ +/* */ +/* WHAT EACH CHECK IS FOR */ +/* */ +/* N1 the SGI arrives at all. This is separate on purpose: it tests */ +/* the ICC_SGI1R encoding, which does not transcribe from the */ +/* AArch64 alias, so if N1 fails the fault is in gicv3_send_sgi and */ +/* not in the nesting routines. */ +/* N2 the SGI arrived while another handler was active, depth 2. */ +/* N3 depth returned to zero, so the pairing unwound. */ +/* N4 the tick still advances afterwards, so nesting did not damage */ +/* the timer path. */ +/* N5 threads still run and preempt, so the context save and restore */ +/* survived being re-entered. */ +/* */ +/**************************************************************************/ + +#include "tx_api.h" +#include "board.h" +#include "console.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; + + +static UINT report(const char *label_ptr, UINT passed) +{ + console_puts(label_ptr); + console_puts((passed != 0U) ? "PASS\n" : "FAIL\n"); + return (passed != 0U) ? 0U : 1U; +} + + +static void report_hex(const char *label_ptr, unsigned long value) +{ + console_puts(label_ptr); + console_puthex(value); + console_puts("\n"); +} + + +/* Lowest priority, never blocks: gives the tick something to preempt and shows + the scheduler is still alive after handlers have been nested. */ + +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; + + /* N1 first, with provocation still off, so this measures only whether an + SGI raised from thread context is delivered and dispatched. */ + + console_puts("[check] 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("[check] N1 SGI delivered and dispatched ", + (board_sgi_count > sgi_before) ? 1U : 0U); + + if (board_sgi_count == sgi_before) + { + /* Everything below depends on the SGI working, so say so rather than + reporting four more failures that all have the same cause. */ + + console_puts("[check] SGI never arrived; the remaining checks would\n" + " only restate that, so they are skipped.\n"); + console_puts("\nNESTING RESULT: FAILED\n"); + console_exit(1U); + } + + /* Now let the timer handler provoke a nested SGI, and give it time for a + good number of ticks. */ + + console_puts("[check] 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("[check] max nesting depth observed = ", board_nest_max); + report_hex("[check] nested SGI count = ", board_sgi_nested_count); + report_hex("[check] nesting depth now = ", board_nest_depth); + + failures += report("[check] N2 SGI nested inside another handler ", + (board_sgi_nested_count > 0UL) && + (board_nest_max >= 2UL) ? 1U : 0U); + + failures += report("[check] N3 nesting unwound to depth zero ", + (board_nest_depth == 0UL) ? 1U : 0U); + + failures += report("[check] N4 tick still advancing after nesting ", + ((ticks_end - ticks_start) >= 40UL) ? 1U : 0U); + + failures += report("[check] N5 lower-priority thread still scheduled ", + (spin_runs > spin_before) ? 1U : 0U); + + report_hex("[check] spurious interrupts = ", board_spurious_count); + report_hex("[check] unexpected INTID = ", board_unexpected_intid); + + failures += report("[check] no spurious or unexpected interrupts ", + ((board_spurious_count == 0UL) && + (board_unexpected_intid == 0UL)) ? 1U : 0U); + + if (failures == 0U) + { + console_puts("\nNESTING RESULT: ALL CHECKS PASSED\n"); + } + else + { + console_puts("\nNESTING RESULT: FAILED\n"); + } + + console_exit(failures); +} + + +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) +{ + console_puts("\n=== ThreadX nested IRQ handling " + "(Cortex-R52, Armv8-R AArch32, Armv8-R AEM FVP) ===\n"); + + tx_kernel_enter(); +} diff --git a/ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/entry.S b/ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/entry.S index ec25ac25..ec783b99 100644 --- a/ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/entry.S +++ b/ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/entry.S @@ -356,7 +356,47 @@ el1_irq_entry: .global __tx_irq_processing_return __tx_irq_processing_return: +#ifdef TX_ENABLE_IRQ_NESTING + + /* Nested IRQ handling. _tx_thread_irq_nesting_start leaves IRQ mode for + * System mode and re-enables IRQ, so an interrupt of higher priority can + * preempt this handler and re-enter at el1_irq_entry above. It is paired + * with _tx_thread_irq_nesting_end before _tx_thread_context_restore. + * + * The ORDER here is the whole difficulty, and getting it wrong does not + * fail gracefully. The interrupt must be ACKNOWLEDGED BEFORE nesting is + * started. Reading ICC_IAR1 is what raises the GIC's running priority to + * this interrupt's own, which is what masks it and everything of equal or + * lower priority; only then is re-enabling IRQ safe. Acknowledge after + * nesting_start instead and the still-pending, still-level-asserted timer + * is taken again the instant IRQ is enabled, and again, until the IRQ and + * System stacks are destroyed. That presents as garbage on the console and + * a hang, with no fault to point at it. The Cortex-R5 example BSP says the + * same thing in one line: "ensure all IRQ interrupts are cleared prior to + * enabling nested IRQ interrupts". + * + * The INTID is carried in r4, which survives the mode switch because only + * SP and LR are banked, and is also pushed on the IRQ stack so a nested + * level that reuses r4 cannot lose this level's value. End-of-interrupt is + * left until after nesting_end, in IRQ mode with interrupts masked, so + * dropping the running priority cannot re-admit this same interrupt. + */ + + bl gicv3_acknowledge /* IRQ mode, interrupts masked */ + 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 FAULT_REPORT msg_el1_irq diff --git a/ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/gicv3.c b/ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/gicv3.c index 4a192509..eb5c029c 100644 --- a/ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/gicv3.c +++ b/ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/gicv3.c @@ -195,6 +195,56 @@ void gicv3_enable_ppi(unsigned int intid, unsigned int priority) /* gicv3_acknowledge */ /**************************************************************************/ +/**************************************************************************/ +/* gicv3_enable_sgi */ +/* */ +/* An SGI lives in the same redistributor frame as a PPI, so this is */ +/* gicv3_enable_ppi without the ICFGR step: INTIDs 0-15 have no */ +/* configurable edge/level, they are always edge-triggered. */ +/**************************************************************************/ + +void gicv3_enable_sgi(unsigned int intid, unsigned int priority) +{ + if (intid > 15U) + { + return; + } + + REG32(GICR_SGI_BASE + GICR_IGROUPR0) |= (1UL << intid); + + REG32(GICR_SGI_BASE + GICR_IPRIORITYR + (intid & ~3U)) &= + ~(0xFFUL << ((intid & 3U) * 8U)); + REG32(GICR_SGI_BASE + GICR_IPRIORITYR + (intid & ~3U)) |= + ((unsigned long) priority & 0xFFUL) << ((intid & 3U) * 8U); + + REG32(GICR_SGI_BASE + GICR_ISENABLER0) = (1UL << intid); +} + + +/**************************************************************************/ +/* gicv3_send_sgi */ +/* */ +/* ICC_SGI1R is 64-bit, so in AArch32 it is an MCRR rather than an MCR. */ +/* Fields: INTID in [27:24], TargetList in [15:0], Aff1/2/3 and IRM zero */ +/* for this single-core configuration, so TargetList = 1 selects core 0. */ +/* */ +/* The AArch64 name for this register is S3_0_C12_C11_5, which the */ +/* Cortex-A72 example uses, but that encoding does not transcribe to the */ +/* AArch32 64-bit CP15 space. The CRm here was confirmed by observing */ +/* that the SGI is actually delivered and acknowledged with the expected */ +/* INTID rather than by reading it off the A-profile alias. */ +/**************************************************************************/ + +void gicv3_send_sgi(unsigned int intid) +{ + unsigned long low = (((unsigned long) intid & 0xFUL) << 24) | 1UL; + unsigned long high = 0UL; + + __asm volatile ("mcrr p15, 0, %0, %1, c12" :: "r" (low), "r" (high) : "memory"); + instruction_barrier(); +} + + unsigned long gicv3_acknowledge(void) { unsigned long intid; diff --git a/ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/gicv3.h b/ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/gicv3.h index cc35639c..03f1b2c0 100644 --- a/ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/gicv3.h +++ b/ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/gicv3.h @@ -49,6 +49,15 @@ void gicv3_init(void); void gicv3_enable_ppi(unsigned int intid, unsigned int priority); +/* Enable a software-generated interrupt, INTID 0-15. Same registers as a PPI: + the redistributor's SGI/PPI frame covers INTIDs 0-31. */ + +void gicv3_enable_sgi(unsigned int intid, unsigned int priority); + +/* Raise an SGI on this core. */ + +void gicv3_send_sgi(unsigned int intid); + /* Acknowledge the highest-priority pending Group 1 interrupt, returning its INTID (possibly GICV3_SPURIOUS_INTID). */ diff --git a/ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/irq_dispatch.c b/ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/irq_dispatch.c index 0028dddb..02672108 100644 --- a/ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/irq_dispatch.c +++ b/ports/cortex_r52/gnu/example_build/fvp_baser_aemv8r/irq_dispatch.c @@ -54,6 +54,12 @@ volatile unsigned long board_timer_intid; volatile unsigned long board_spurious_count; volatile unsigned long board_unexpected_intid; +volatile unsigned long board_nest_depth; +volatile unsigned long board_nest_max; +volatile unsigned long board_sgi_count; +volatile unsigned long board_sgi_nested_count; +volatile unsigned long board_nest_provoke; + /* Timer PPI priority. Only the top 5 priority bits are implemented on this model, so the value is a multiple of 8. */ @@ -73,6 +79,13 @@ void board_init(void) { gicv3_init(); gicv3_enable_ppi(TIMER_PPI_INTID, TIMER_PPI_PRIORITY); + + /* The nesting SGI must outrank the timer or it could never preempt it: + in the GIC a numerically lower priority value wins. Enabling it costs + nothing in images that never raise it. */ + + gicv3_enable_sgi(BOARD_NEST_SGI_INTID, TIMER_PPI_PRIORITY / 2U); + timer_init(); } @@ -81,10 +94,15 @@ void board_init(void) /* board_irq_handler */ /**************************************************************************/ -void board_irq_handler(void) -{ - unsigned long intid = gicv3_acknowledge(); +/**************************************************************************/ +/* board_irq_service -- service an already-acknowledged INTID. */ +/* */ +/* Split out so the nesting path in entry.S can acknowledge in IRQ mode */ +/* before nesting starts. Does not acknowledge and does not EOI. */ +/**************************************************************************/ +void board_irq_service(unsigned long intid) +{ if (intid == GICV3_SPURIOUS_INTID) { /* No interrupt was actually pending; must not be acknowledged. */ @@ -93,6 +111,17 @@ void board_irq_handler(void) return; } + /* Depth is incremented after the spurious check so a spurious entry does + not register as nesting. It reaches 2 only if a second interrupt is + taken while this handler is still on the stack, which cannot happen + without TX_ENABLE_IRQ_NESTING. */ + + board_nest_depth++; + if (board_nest_depth > board_nest_max) + { + board_nest_max = board_nest_depth; + } + if (intid == (unsigned long) TIMER_PPI_INTID) { board_timer_intid = intid; @@ -102,12 +131,71 @@ void board_irq_handler(void) this also deasserts the level-sensitive timer output. */ timer_reload(); + + /* Provoke the nesting, if this image asked for it. Raising the SGI + here is only useful because _tx_thread_irq_nesting_start has already + re-enabled IRQ; the bounded spin gives the GIC the chance to deliver + it before this handler returns, so the nesting is deterministic + rather than a race we hope wins. The bound matters: without + TX_ENABLE_IRQ_NESTING the SGI can never arrive and this must not + become a hang. */ + + if (board_nest_provoke != 0UL) + { + unsigned long seen = board_sgi_count; + unsigned long guard; + + gicv3_send_sgi(BOARD_NEST_SGI_INTID); + + for (guard = 0UL; guard < 100000UL; guard++) + { + if (board_sgi_count != seen) + { + break; + } + } + } + _tx_timer_interrupt(); } + else if (intid == (unsigned long) BOARD_NEST_SGI_INTID) + { + board_sgi_count++; + + /* Depth above 1 means this SGI arrived inside another handler, which + is the whole point of the test. */ + + if (board_nest_depth > 1UL) + { + board_sgi_nested_count++; + } + } else { board_unexpected_intid = intid; } - gicv3_end_of_interrupt(intid); + board_nest_depth--; +} + + +/**************************************************************************/ +/* board_irq_handler -- the non-nesting entry point. */ +/* */ +/* Acknowledges, services and ends the interrupt, all in IRQ mode with */ +/* interrupts masked. entry.S calls this when the image was built without */ +/* TX_ENABLE_IRQ_NESTING, so the behaviour of every existing image is */ +/* exactly what it was. */ +/**************************************************************************/ + +void board_irq_handler(void) +{ + unsigned long intid = gicv3_acknowledge(); + + board_irq_service(intid); + + if (intid != GICV3_SPURIOUS_INTID) + { + gicv3_end_of_interrupt(intid); + } }