mirror of
https://github.com/eclipse-threadx/threadx.git
synced 2026-10-06 06:59:08 +08:00
Verified nested FIQ handling on S32Z280 silicon (#614)
#613 exercised the FIQ nesting routines on the FVP. What the model could not show is whether a real GIC-600 routes Group 0 to FIQ the same way, which is the reason to run it here. It does, and the counts match the model exactly. The Group 0 support ports across unchanged: IGRPEN0 and BPR0 on the CPU interface, Group 0 in the distributor, gicv3_enable_sgi_group0, gicv3_send_sgi_group0 through ICC_SGI0R, and the separate Group 0 acknowledge and end-of-interrupt pair. entry.S routes the EL1 FIQ vector into _tx_thread_fiq_context_save with the acknowledge before nesting starts, and leaves FIQ unmasked on the drop to EL1 when FIQ support is compiled in, for the same reason as on the FVP: tx_thread_stack_build only clears a thread's F bit in that configuration. One structural difference from the FVP cost a link. This example reports faults through FAULT_TAIL rather than FAULT_REPORT, so the vector table needed a new el1_fiq_entry label that falls back to fault_el1_fiq. Placing that label inside the TX_R52_USE_THREADX_IRQ guard broke s32z280_boot.elf, which does not define it: the vector reference is unconditional, so the label has to be too. It now sits outside the guard and carries its own, the same shape the demo_m2 link break in #613 forced on the FVP side. Verified on S32Z280 silicon: F1 FIQ delivered and dispatched PASS low-priority FIQ count = 0x00000015 21 high-priority FIQ count = 0x00000014 20 nested FIQ count = 0x00000014 20 of 20 nested max FIQ depth = 0x00000002 FIQ depth now = 0x00000000 F2 FIQ nested inside an FIQ handler PASS F3 FIQ nesting unwound to depth zero PASS F4 IRQ tick undisturbed by FIQ work PASS F5 lower-priority thread still scheduled PASS F6 no unexpected Group 0 INTID PASS No regression, both configurations checked on the board. In the FIQ build the ThreadX demo still reports 100 ticks with 20 preemptions and the boot image still passes its cache and protection checks. In the default build the demo is unchanged and the image links no FIQ or Group 0 symbol at all, so the work is absent rather than dormant where it is not wanted -- worth confirming on hardware rather than reasoning about, because the default build now reaches the FIQ vector through a new label even though that label only branches to the fault reporter. entry.S assembles in all four combinations of TX_R52_USE_THREADX_IRQ and FIQ support, on both toolchains, and every file builds with GNU without warnings and with Arm Toolchain for Embedded 22.1.0. Assisted-by: Claude Code (Opus 5) <noreply@anthropic.com>
This commit is contained in:
@@ -164,3 +164,40 @@ if(TX_R52_ENABLE_IRQ_NESTING)
|
||||
${EVB_LINK_QUIET_RWX}
|
||||
)
|
||||
endif()
|
||||
|
||||
# Nested FIQ handling on silicon. 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 -- as s32z280_nesting.elf does for the IRQ side.
|
||||
if(TX_R52_ENABLE_FIQ_NESTING)
|
||||
add_executable(s32z280_fiq.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_fiq_s32z280.c
|
||||
)
|
||||
|
||||
target_compile_definitions(s32z280_fiq.elf PRIVATE TX_R52_USE_THREADX_IRQ=1)
|
||||
target_compile_options(s32z280_fiq.elf PRIVATE -g)
|
||||
|
||||
target_link_libraries(s32z280_fiq.elf PRIVATE threadx)
|
||||
|
||||
target_include_directories(s32z280_fiq.elf PRIVATE
|
||||
${EVB_DIR}
|
||||
${CMAKE_SOURCE_DIR}/common/inc
|
||||
${CMAKE_SOURCE_DIR}/ports/${THREADX_ARCH}/${THREADX_TOOLCHAIN}/inc
|
||||
)
|
||||
|
||||
target_link_options(s32z280_fiq.elf PRIVATE
|
||||
-T${EVB_DIR}/link.lds
|
||||
-nostartfiles
|
||||
-Wl,-Map=s32z280_fiq.map
|
||||
${EVB_LINK_QUIET_RWX}
|
||||
)
|
||||
endif()
|
||||
|
||||
@@ -124,4 +124,22 @@ extern volatile unsigned long board_sgi_priority;
|
||||
|
||||
#define BOARD_NEST_SGI_INTID 8U
|
||||
|
||||
/* FIQ observability. Two Group 0 SGIs, because FIQ nesting means an FIQ taken
|
||||
while an FIQ handler is running, so a single source cannot show it. The
|
||||
higher-priority one is raised from inside the lower one's handler. */
|
||||
|
||||
void board_fiq_handler(void);
|
||||
void board_fiq_service(unsigned long intid);
|
||||
|
||||
extern volatile unsigned long board_fiq_count;
|
||||
extern volatile unsigned long board_fiq_high_count;
|
||||
extern volatile unsigned long board_fiq_nested_count;
|
||||
extern volatile unsigned long board_fiq_depth;
|
||||
extern volatile unsigned long board_fiq_max_depth;
|
||||
extern volatile unsigned long board_fiq_unexpected;
|
||||
extern volatile unsigned long board_fiq_provoke;
|
||||
|
||||
#define BOARD_FIQ_LOW_INTID 10U
|
||||
#define BOARD_FIQ_HIGH_INTID 11U
|
||||
|
||||
#endif /* BOARD_H */
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -189,7 +189,7 @@ el1_vectors:
|
||||
b fault_el1_dabt /* 0x10 */
|
||||
b fault_el1_reserved /* 0x14 */
|
||||
b el1_irq_entry /* 0x18 IRQ */
|
||||
b fault_el1_fiq /* 0x1C */
|
||||
b el1_fiq_entry /* 0x1C FIQ */
|
||||
|
||||
@/**************************************************************************/
|
||||
@/* _start -- reset entry. Entered in THUMB state, Hyp mode (EL2). */
|
||||
@@ -330,7 +330,19 @@ start_a32:
|
||||
|
||||
ldr r0, =el1_entry
|
||||
msr ELR_hyp, r0
|
||||
#ifdef TX_ENABLE_FIQ_SUPPORT
|
||||
|
||||
/* FIQ left unmasked, IRQ still masked. tx_thread_stack_build only clears a
|
||||
* thread's F bit when the library was built with FIQ support and nothing
|
||||
* else ever clears it, so leaving PSR_F set here would mask FIQ until the
|
||||
* first thread ran and an FIQ raised from bsp_main would silently do
|
||||
* nothing.
|
||||
*/
|
||||
|
||||
ldr r0, =(PSR_A | PSR_I | MODE_SVC)
|
||||
#else
|
||||
ldr r0, =(PSR_A | PSR_I | PSR_F | MODE_SVC)
|
||||
#endif
|
||||
msr SPSR_hyp, r0
|
||||
isb
|
||||
eret
|
||||
@@ -536,6 +548,7 @@ __tx_irq_processing_return:
|
||||
#endif
|
||||
b _tx_thread_context_restore
|
||||
|
||||
|
||||
#else
|
||||
|
||||
/* Standalone: the classic A32 form. lr is adjusted by 4 because an IRQ
|
||||
@@ -549,6 +562,52 @@ __tx_irq_processing_return:
|
||||
|
||||
#endif
|
||||
|
||||
@/**************************************************************************/
|
||||
@/* el1_fiq_entry -- FIQ vector at EL1. */
|
||||
@/**************************************************************************/
|
||||
|
||||
el1_fiq_entry:
|
||||
#if defined(TX_ENABLE_FIQ_SUPPORT) && defined(TX_R52_USE_THREADX_IRQ)
|
||||
|
||||
/* Both guards are needed. TX_ENABLE_FIQ_SUPPORT is PUBLIC on the threadx
|
||||
* target, so it reaches every image once the library is built with FIQ,
|
||||
* including images that link no interrupt controller; referencing the GIC
|
||||
* and board symbols below from those breaks the link outright. That is why
|
||||
* the IRQ vector is gated the same way.
|
||||
*
|
||||
* Group 0 is what the GIC delivers as FIQ, and it has its own acknowledge
|
||||
* and end-of-interrupt registers -- the Group 1 pair returns the spurious
|
||||
* INTID for a Group 0 interrupt and leaves it pending, which looks like a
|
||||
* storm rather than a mistake. As on the IRQ path, the acknowledge happens
|
||||
* before nesting starts, so the running priority masks this interrupt before
|
||||
* FIQ is re-enabled.
|
||||
*/
|
||||
|
||||
b _tx_thread_fiq_context_save
|
||||
|
||||
.global __tx_fiq_processing_return
|
||||
__tx_fiq_processing_return:
|
||||
#ifdef TX_ENABLE_FIQ_NESTING
|
||||
bl gicv3_acknowledge_group0
|
||||
mov r4, r0
|
||||
stmdb sp!, {r4, r5}
|
||||
|
||||
bl _tx_thread_fiq_nesting_start
|
||||
mov r0, r4
|
||||
bl board_fiq_service
|
||||
bl _tx_thread_fiq_nesting_end
|
||||
|
||||
ldmia sp!, {r4, r5}
|
||||
mov r0, r4
|
||||
bl gicv3_end_of_interrupt_group0
|
||||
#else
|
||||
bl board_fiq_handler
|
||||
#endif
|
||||
b _tx_thread_fiq_context_restore
|
||||
#else
|
||||
b fault_el1_fiq
|
||||
#endif
|
||||
|
||||
@/**************************************************************************/
|
||||
@/* try_execute_data -- branch to a non-executable address on purpose. */
|
||||
@/* */
|
||||
|
||||
@@ -53,6 +53,7 @@
|
||||
/* Distributor registers. */
|
||||
|
||||
#define GICD_CTLR 0x0000U
|
||||
#define GICD_CTLR_ENABLE_GRP0 (1UL << 0)
|
||||
#define GICD_CTLR_ENABLE_GRP1 (1UL << 1)
|
||||
#define GICD_CTLR_ARE (1UL << 4)
|
||||
|
||||
@@ -102,6 +103,18 @@ static void write_icc_bpr1(unsigned long value)
|
||||
__asm__ volatile("mcr p15, 0, %0, c12, c12, 3" : : "r"(value) : "memory");
|
||||
}
|
||||
|
||||
static void write_icc_igrpen0(unsigned long value)
|
||||
{
|
||||
/* ICC_IGRPEN0 sits at c12, c12, 6, beside IGRPEN1 at opc2 7. */
|
||||
|
||||
__asm__ volatile("mcr p15, 0, %0, c12, c12, 6" : : "r"(value) : "memory");
|
||||
}
|
||||
|
||||
static void write_icc_bpr0(unsigned long value)
|
||||
{
|
||||
__asm__ volatile("mcr p15, 0, %0, c12, c8, 3" : : "r"(value) : "memory");
|
||||
}
|
||||
|
||||
static void write_icc_igrpen1(unsigned long value)
|
||||
{
|
||||
__asm__ volatile("mcr p15, 0, %0, c12, c12, 7" : : "r"(value) : "memory");
|
||||
@@ -127,6 +140,12 @@ void gicv3_init(void)
|
||||
REG32(GICD_BASE + GICD_CTLR) |= GICD_CTLR_ARE;
|
||||
REG32(GICD_BASE + GICD_CTLR) |= GICD_CTLR_ENABLE_GRP1;
|
||||
|
||||
/* Group 0 as well, which is what the GIC delivers as FIQ. Enabling it costs
|
||||
nothing while no interrupt is assigned to that group, and images that never
|
||||
use FIQ see no change: assignment is per interrupt, in GICR_IGROUPR0. */
|
||||
|
||||
REG32(GICD_BASE + GICD_CTLR) |= GICD_CTLR_ENABLE_GRP0;
|
||||
|
||||
/* Redistributor: clear ProcessorSleep and wait for the redistributor to
|
||||
report that its children are awake, otherwise no interrupt can be
|
||||
delivered to this core. */
|
||||
@@ -150,6 +169,14 @@ void gicv3_init(void)
|
||||
write_icc_pmr(ICC_PMR_UNMASK_ALL);
|
||||
write_icc_bpr1(0UL);
|
||||
write_icc_igrpen1(ICC_IGRPEN1_ENABLE);
|
||||
|
||||
/* The CPU interface half of Group 0: a Group 0 interrupt reaches the core
|
||||
only if both the distributor and this are enabled. BPR0 at zero gives no
|
||||
subpriority grouping, so priority alone decides preemption, which is what
|
||||
the FIQ nesting test depends on. */
|
||||
|
||||
write_icc_bpr0(0UL);
|
||||
write_icc_igrpen0(ICC_IGRPEN1_ENABLE);
|
||||
instruction_barrier();
|
||||
}
|
||||
|
||||
@@ -294,6 +321,74 @@ unsigned int gicv3_priority_bits(unsigned int scratch_intid)
|
||||
}
|
||||
|
||||
|
||||
/**************************************************************************/
|
||||
/* gicv3_enable_sgi_group0 */
|
||||
/* */
|
||||
/* As gicv3_enable_sgi, but leaves the interrupt in Group 0 so it arrives */
|
||||
/* as an FIQ. The group is chosen by clearing the GICR_IGROUPR0 bit; the */
|
||||
/* Group 1 version sets it. */
|
||||
/**************************************************************************/
|
||||
|
||||
void gicv3_enable_sgi_group0(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_group0 */
|
||||
/* */
|
||||
/* ICC_SGI0R rather than ICC_SGI1R: the two differ only in opc1, 2 against */
|
||||
/* 0, and each raises the SGI into its own group. Raising a Group 0 */
|
||||
/* interrupt through the Group 1 register does not deliver it as an FIQ. */
|
||||
/**************************************************************************/
|
||||
|
||||
void gicv3_send_sgi_group0(unsigned int intid)
|
||||
{
|
||||
unsigned long low = (((unsigned long) intid & 0xFUL) << 24) | 1UL;
|
||||
unsigned long high = 0UL;
|
||||
|
||||
__asm volatile ("mcrr p15, 2, %0, %1, c12" :: "r" (low), "r" (high) : "memory");
|
||||
instruction_barrier();
|
||||
}
|
||||
|
||||
|
||||
/**************************************************************************/
|
||||
/* gicv3_acknowledge_group0 / gicv3_end_of_interrupt_group0 */
|
||||
/* */
|
||||
/* Group 0 has its own pair at c12, c8, where Group 1 uses c12, c12. */
|
||||
/* Acknowledging a Group 0 interrupt through IAR1 returns the spurious */
|
||||
/* INTID and leaves the interrupt pending, which presents as an FIQ storm. */
|
||||
/**************************************************************************/
|
||||
|
||||
unsigned long gicv3_acknowledge_group0(void)
|
||||
{
|
||||
unsigned long intid;
|
||||
|
||||
__asm__ volatile("mrc p15, 0, %0, c12, c8, 0" : "=r"(intid));
|
||||
return intid & 0xFFFFFFUL;
|
||||
}
|
||||
|
||||
|
||||
void gicv3_end_of_interrupt_group0(unsigned long intid)
|
||||
{
|
||||
instruction_barrier();
|
||||
__asm__ volatile("mcr p15, 0, %0, c12, c8, 1" : : "r"(intid) : "memory");
|
||||
}
|
||||
|
||||
|
||||
unsigned long gicv3_acknowledge(void)
|
||||
{
|
||||
unsigned long intid;
|
||||
|
||||
@@ -58,6 +58,17 @@ void gicv3_enable_sgi(unsigned int intid, unsigned int priority);
|
||||
|
||||
void gicv3_send_sgi(unsigned int intid);
|
||||
|
||||
/* Group 0 support, which is what produces FIQ rather than IRQ. With a single
|
||||
security state the GIC routes Group 0 to FIQ and Group 1 to IRQ, so an
|
||||
interrupt has to be moved into Group 0 to arrive as an FIQ at all. Group 0
|
||||
has its own acknowledge and end-of-interrupt registers; using the Group 1 ones
|
||||
on a Group 0 interrupt does not work. */
|
||||
|
||||
void gicv3_enable_sgi_group0(unsigned int intid, unsigned int priority);
|
||||
void gicv3_send_sgi_group0(unsigned int intid);
|
||||
unsigned long gicv3_acknowledge_group0(void);
|
||||
void gicv3_end_of_interrupt_group0(unsigned long intid);
|
||||
|
||||
/* Implemented priority bits, discovered by write and readback. Leaves the
|
||||
scratch INTID's priority byte at zero. */
|
||||
|
||||
|
||||
@@ -66,6 +66,14 @@ 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;
|
||||
|
||||
volatile unsigned long board_fiq_count;
|
||||
volatile unsigned long board_fiq_high_count;
|
||||
volatile unsigned long board_fiq_nested_count;
|
||||
volatile unsigned long board_fiq_depth;
|
||||
volatile unsigned long board_fiq_max_depth;
|
||||
volatile unsigned long board_fiq_unexpected;
|
||||
volatile unsigned long board_fiq_provoke;
|
||||
volatile unsigned long board_priority_bits;
|
||||
volatile unsigned long board_timer_priority;
|
||||
volatile unsigned long board_sgi_priority;
|
||||
@@ -116,6 +124,17 @@ void board_init(void)
|
||||
|
||||
gicv3_enable_sgi(BOARD_NEST_SGI_INTID, TIMER_PPI_PRIORITY / 2U);
|
||||
|
||||
#ifdef TX_ENABLE_FIQ_SUPPORT
|
||||
|
||||
/* Two Group 0 SGIs for the FIQ path. Group 0 is what the GIC delivers as
|
||||
FIQ; the high one takes a numerically lower priority so it can preempt the
|
||||
low one, which is what FIQ nesting means. Both sit above the timer so the
|
||||
IRQ and FIQ paths do not interfere. */
|
||||
|
||||
gicv3_enable_sgi_group0(BOARD_FIQ_LOW_INTID, 0x40U);
|
||||
gicv3_enable_sgi_group0(BOARD_FIQ_HIGH_INTID, 0x20U);
|
||||
#endif
|
||||
|
||||
/* The priorities as the GIC will actually compare them. Only the top
|
||||
board_priority_bits survive; the low ones read back as zero, so two values
|
||||
that differ only there would collapse together and never preempt. Recorded
|
||||
@@ -249,3 +268,89 @@ void board_irq_handler(void)
|
||||
gicv3_end_of_interrupt(intid);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#ifdef TX_ENABLE_FIQ_SUPPORT
|
||||
/**************************************************************************/
|
||||
/* board_fiq_service -- service an already-acknowledged Group 0 INTID. */
|
||||
/* */
|
||||
/* The FIQ counterpart of board_irq_service, and split for the same */
|
||||
/* reason: the acknowledge has to happen before nesting starts. Does not */
|
||||
/* acknowledge and does not EOI. */
|
||||
/**************************************************************************/
|
||||
|
||||
void board_fiq_service(unsigned long intid)
|
||||
{
|
||||
if (intid == GICV3_SPURIOUS_INTID)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
board_fiq_depth++;
|
||||
if (board_fiq_depth > board_fiq_max_depth)
|
||||
{
|
||||
board_fiq_max_depth = board_fiq_depth;
|
||||
}
|
||||
|
||||
if (intid == (unsigned long) BOARD_FIQ_LOW_INTID)
|
||||
{
|
||||
board_fiq_count++;
|
||||
|
||||
/* Provoke the nested FIQ from inside this handler. Only reachable
|
||||
because _tx_thread_fiq_nesting_start has re-enabled FIQ; the bounded
|
||||
spin lets the GIC deliver the higher-priority Group 0 SGI before this
|
||||
handler returns. Bounded, because with nesting compiled out the
|
||||
second FIQ cannot arrive and this must not hang. */
|
||||
|
||||
if (board_fiq_provoke != 0UL)
|
||||
{
|
||||
unsigned long seen = board_fiq_high_count;
|
||||
unsigned long guard;
|
||||
|
||||
gicv3_send_sgi_group0(BOARD_FIQ_HIGH_INTID);
|
||||
|
||||
for (guard = 0UL; guard < 100000UL; guard++)
|
||||
{
|
||||
if (board_fiq_high_count != seen)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (intid == (unsigned long) BOARD_FIQ_HIGH_INTID)
|
||||
{
|
||||
board_fiq_high_count++;
|
||||
|
||||
/* Depth above 1 means this FIQ arrived inside another FIQ handler. */
|
||||
|
||||
if (board_fiq_depth > 1UL)
|
||||
{
|
||||
board_fiq_nested_count++;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
board_fiq_unexpected = intid;
|
||||
}
|
||||
|
||||
board_fiq_depth--;
|
||||
}
|
||||
|
||||
|
||||
/**************************************************************************/
|
||||
/* board_fiq_handler -- the non-nesting FIQ entry point. */
|
||||
/**************************************************************************/
|
||||
|
||||
void board_fiq_handler(void)
|
||||
{
|
||||
unsigned long intid = gicv3_acknowledge_group0();
|
||||
|
||||
board_fiq_service(intid);
|
||||
|
||||
if (intid != GICV3_SPURIOUS_INTID)
|
||||
{
|
||||
gicv3_end_of_interrupt_group0(intid);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
Reference in New Issue
Block a user