Skip to content

Commit df1ed08

Browse files
committed
Cortex-R5/GNU: Add GIC support with deferred EOI for level-triggered IRQs
Add optional GIC (Generic Interrupt Controller) support for the Cortex-R5 port, targeting GICv2 platforms such as the Xilinx Zynq UltraScale+ RPU. tx_thread_context_restore.S: Add TX_GIC_DEFERRED_EOI support. When enabled, GICC_EOIR is written AFTER CPSID in context_restore rather than in the IRQ handler. This closes the race window where a level-triggered interrupt re-triggers between EOIR and CPSID. After EOI, _tx_gic_iar is invalidated (set to 1023/spurious) to prevent duplicate EOIs on voluntary context switches (tx_thread_sleep, mutex wait, etc.). Constraints: single-core only (global _tx_gic_iar), incompatible with TX_ENABLE_IRQ_NESTING, GICv2 only. example_build/tx_initialize_low_level.S: Add reference GIC initialization (_tx_gic_initialize), IRQ dispatch with handler table support, deferred EOI storage, and runtime handler registration API. Guarded by TX_ENABLE_GIC_SUPPORT. Base addresses configurable via TX_GIC_DISTRIBUTOR_BASE / TX_GIC_CPU_INTERFACE_BASE. README.md: Document GIC support including base address discovery, initialization sequence, API reference, deferred EOI pattern, and platform-specific configuration for ZynqMP RPU.
1 parent 27af123 commit df1ed08

3 files changed

Lines changed: 995 additions & 10 deletions

File tree

ports/cortex_r5/gnu/README.md

Lines changed: 298 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1864,3 +1864,301 @@ If reconfiguring hardware from lockstep → SMP:
18641864
ThreadX requires a periodic interrupt source to manage all time-slicing, thread sleeps, timeouts, and application timers. Without such a timer interrupt source, these services are not functional but the remainder of ThreadX will still run.
18651865

18661866
To add the timer interrupt processing, simply make a call to `_tx_timer_interrupt` in the IRQ processing. An example of this can be found in the file `tx_initialize_low_level.S` for the demonstration system.
1867+
1868+
## 11. GIC (Generic Interrupt Controller) Support
1869+
1870+
The Cortex-R5 port includes optional GIC (Generic Interrupt Controller) initialization and IRQ dispatch support. This is essential for systems using ARM GIC v1/v2 interrupt controllers, such as the Xilinx Zynq UltraScale+ RPU.
1871+
1872+
**ARM Reference**: GIC Architecture Specification (IHI 0048B)
1873+
1874+
### 11.1 Enabling GIC Support
1875+
1876+
To enable GIC support, define `TX_ENABLE_GIC_SUPPORT` when compiling `tx_initialize_low_level.S`:
1877+
1878+
```bash
1879+
arm-none-eabi-gcc -DTX_ENABLE_GIC_SUPPORT -c tx_initialize_low_level.S
1880+
```
1881+
1882+
Optional defines:
1883+
1884+
| Define | Default | Description |
1885+
|--------|---------|-------------|
1886+
| `TX_GIC_DISTRIBUTOR_BASE` | 0xF9000000 | GIC Distributor (GICD) base address |
1887+
| `TX_GIC_CPU_INTERFACE_BASE` | 0xF9001000 | GIC CPU Interface (GICC) base address |
1888+
| `TX_GIC_MAX_INTERRUPTS` | 192 | Maximum interrupt count for handler table |
1889+
| `TX_GIC_TIMER_IRQ` | 29 | Interrupt ID for system timer (PPI #29) |
1890+
| `TX_GIC_USE_HANDLER_TABLE` | undefined | Enable handler table dispatch |
1891+
1892+
### 11.2 GIC Base Address Discovery
1893+
1894+
GIC base addresses can be discovered via:
1895+
1896+
**Option 1: CBAR Register (if implemented)**
1897+
```assembly
1898+
MRC p15, 4, r0, c15, c0, 0 @ Read CBAR
1899+
@ GICD = CBAR + 0x1000
1900+
@ GICC = CBAR + 0x2000
1901+
```
1902+
1903+
**Option 2: Platform-Specific (default)**
1904+
Override via compiler defines:
1905+
```bash
1906+
-DTX_GIC_DISTRIBUTOR_BASE=0xF9000000 -DTX_GIC_CPU_INTERFACE_BASE=0xF9001000
1907+
```
1908+
1909+
Common platform addresses:
1910+
1911+
| Platform | GICD Base | GICC Base |
1912+
|----------|-----------|-----------|
1913+
| Zynq UltraScale+ RPU | 0xF9000000 | 0xF9001000 |
1914+
| Zynq-7000 | 0xF8F01000 | 0xF8F00100 |
1915+
| TI K2x/AM6x | Varies | Varies |
1916+
1917+
### 11.3 GIC Initialization
1918+
1919+
Call `_tx_gic_initialize()` before enabling interrupts (typically in `tx_application_define` or early startup):
1920+
1921+
```c
1922+
extern void _tx_gic_initialize(void);
1923+
1924+
void tx_application_define(void *first_unused_memory)
1925+
{
1926+
/* Initialize GIC before creating threads */
1927+
_tx_gic_initialize();
1928+
1929+
/* Enable timer interrupt */
1930+
_tx_gic_enable_irq(TX_GIC_TIMER_IRQ);
1931+
_tx_gic_set_priority(TX_GIC_TIMER_IRQ, 0x80);
1932+
1933+
/* Create threads... */
1934+
}
1935+
```
1936+
1937+
The initialization sequence:
1938+
1. Disable distributor
1939+
2. Read GICD_TYPER to determine interrupt count
1940+
3. Disable all interrupts (GICD_ICENABLER)
1941+
4. Clear all pending interrupts (GICD_ICPENDR)
1942+
5. Clear all active interrupts (GICD_ICACTIVER)
1943+
6. Set default priority (0x80) for all interrupts
1944+
7. Set CPU target to CPU0 for all SPIs
1945+
8. Initialize CPU interface (PMR=0xFF, BPR=3)
1946+
9. Enable distributor and CPU interface
1947+
1948+
### 11.4 GIC API Functions
1949+
1950+
**Initialization:**
1951+
```c
1952+
void _tx_gic_initialize(void); /* Initialize GIC distributor and CPU interface */
1953+
```
1954+
1955+
**Interrupt Control:**
1956+
```c
1957+
void _tx_gic_enable_irq(uint32_t irq_id); /* Enable specific interrupt */
1958+
void _tx_gic_disable_irq(uint32_t irq_id); /* Disable specific interrupt */
1959+
void _tx_gic_set_priority(uint32_t irq_id, uint32_t priority); /* Set priority (0=highest) */
1960+
```
1961+
1962+
**Software Generated Interrupts (SGIs):**
1963+
```c
1964+
void _tx_gic_send_sgi(uint32_t sgi_id, uint32_t filter, uint32_t target_list);
1965+
/* filter: 0=use target_list, 1=all except self, 2=only self */
1966+
/* target_list: CPU bitmask (when filter=0) */
1967+
```
1968+
1969+
**Handler Table (when TX_GIC_USE_HANDLER_TABLE defined):**
1970+
```c
1971+
void* _tx_gic_register_handler(uint32_t irq_id, void (*handler)(uint32_t));
1972+
/* Returns previous handler, or NULL if none */
1973+
```
1974+
1975+
### 11.5 IRQ Dispatch Modes
1976+
1977+
**Mode 1: Direct Dispatch (default)**
1978+
1979+
The IRQ handler checks the interrupt ID and calls the appropriate handler directly:
1980+
1981+
```assembly
1982+
CMP r0, #TX_GIC_TIMER_IRQ
1983+
BNE _gic_not_timer
1984+
BL _tx_timer_interrupt
1985+
```
1986+
1987+
Add additional handlers by modifying `__tx_irq_processing_return` in `tx_initialize_low_level.S`.
1988+
1989+
**Mode 2: Handler Table Dispatch**
1990+
1991+
Enable with `-DTX_GIC_USE_HANDLER_TABLE`. Provides a function pointer table for dynamic handler registration:
1992+
1993+
```c
1994+
/* Handler signature */
1995+
typedef void (*gic_handler_t)(uint32_t irq_id);
1996+
1997+
/* Register handlers */
1998+
_tx_gic_register_handler(UART_IRQ, uart_isr);
1999+
_tx_gic_register_handler(DMA_IRQ, dma_isr);
2000+
_tx_gic_register_handler(TX_GIC_TIMER_IRQ, timer_isr);
2001+
```
2002+
2003+
Handler table size is `TX_GIC_MAX_INTERRUPTS * 4` bytes (default: 768 bytes for 192 interrupts).
2004+
2005+
### 11.6 IRQ Handler Flow
2006+
2007+
```
2008+
IRQ Exception
2009+
2010+
├─► _tx_thread_context_save (save thread context)
2011+
2012+
├─► Read GICC_IAR (acknowledge interrupt, get ID)
2013+
│ │
2014+
│ ├─► ID >= 1020? ──► Yes ──► Spurious, exit
2015+
│ │
2016+
│ └─► ID < 1020? ──► Dispatch to handler
2017+
│ │
2018+
│ └─► Write GICC_EOIR (signal completion)
2019+
2020+
└─► _tx_thread_context_restore (restore context, may switch threads)
2021+
```
2022+
2023+
**CRITICAL: IAR/EOIR Pairing**
2024+
2025+
The value written to GICC_EOIR **must** be the exact value read from GICC_IAR, including the CPU ID bits (bits 12:10) for SGIs. Failure to do so causes interrupt handling errors.
2026+
2027+
### 11.7 Timer Interrupt Configuration
2028+
2029+
The default timer interrupt ID is PPI #29 (Private Timer). Override for your platform:
2030+
2031+
```bash
2032+
-DTX_GIC_TIMER_IRQ=27 # Example: Use IRQ 27 for timer
2033+
```
2034+
2035+
Common timer interrupt IDs:
2036+
2037+
| Timer Type | Typical IRQ ID | Notes |
2038+
|------------|----------------|-------|
2039+
| ARM Private Timer | 29 (PPI #13) | Per-core timer |
2040+
| ARM Global Timer | 27 (PPI #11) | Shared timer |
2041+
| Platform Timer | Varies | Consult SoC TRM |
2042+
2043+
### 11.8 Example: Complete GIC Setup
2044+
2045+
```c
2046+
/* tx_application_define.c */
2047+
2048+
extern void _tx_gic_initialize(void);
2049+
extern void _tx_gic_enable_irq(unsigned int irq);
2050+
extern void _tx_gic_set_priority(unsigned int irq, unsigned int prio);
2051+
2052+
#define TIMER_IRQ 29 /* Private Timer PPI */
2053+
#define UART0_IRQ 53 /* UART0 SPI (example) */
2054+
2055+
void tx_application_define(void *first_unused_memory)
2056+
{
2057+
/* Initialize GIC */
2058+
_tx_gic_initialize();
2059+
2060+
/* Configure timer interrupt */
2061+
_tx_gic_set_priority(TIMER_IRQ, 0x80);
2062+
_tx_gic_enable_irq(TIMER_IRQ);
2063+
2064+
/* Configure UART interrupt */
2065+
_tx_gic_set_priority(UART0_IRQ, 0xA0); /* Lower priority than timer */
2066+
_tx_gic_enable_irq(UART0_IRQ);
2067+
2068+
/* Start platform timer hardware (vendor-specific) */
2069+
/* Example: configure_arm_private_timer(TICK_RATE_HZ); */
2070+
2071+
/* Create application threads... */
2072+
}
2073+
```
2074+
2075+
### 11.9 Common Mistakes
2076+
2077+
1. **Enabling interrupts before GIC init**: Always call `_tx_gic_initialize()` first
2078+
2. **Missing EOIR write**: Causes interrupt to remain pending, system hangs
2079+
3. **Wrong EOIR value**: Write exact IAR value, not just the interrupt ID
2080+
4. **Wrong base addresses**: Verify GICD/GICC addresses for your platform
2081+
5. **Timer not enabled in hardware**: GIC enable is separate from timer peripheral enable
2082+
2083+
### 11.10 GIC and FIQ Interaction
2084+
2085+
The GIC implementation routes all interrupts through the IRQ handler by default:
2086+
2087+
**Current Configuration:**
2088+
- All interrupts default to **Group 0** (GICD_IGROUPR registers not modified)
2089+
- **FIQEn = 0** in GICC_CTLR (bit 3 not set), meaning Group 0 signals as IRQ
2090+
- Result: All GIC-managed interrupts use the IRQ path with GIC dispatch
2091+
2092+
**FIQ Handler Independence:**
2093+
The FIQ handler (`__tx_fiq_handler`) operates independently of GIC:
2094+
- No GIC IAR/EOIR acknowledgment in FIQ path
2095+
- Suitable for dedicated FIQ sources not routed through GIC
2096+
- Used for latency-critical interrupts requiring minimal overhead
2097+
2098+
**Using GIC-Managed FIQ (Advanced):**
2099+
To route Group 0 interrupts to FIQ:
2100+
1. Set FIQEn bit: Modify `_tx_gic_initialize` to write `0x0F` to GICC_CTLR (instead of 0x07)
2101+
2. Add GIC dispatch to FIQ handler: Read GICC_AIAR (0x20), dispatch, write GICC_AEOIR (0x24)
2102+
3. Configure interrupt groups: Write GICD_IGROUPR to assign interrupts to Group 0 (FIQ) or Group 1 (IRQ)
2103+
2104+
**Typical ZynqMP RPU Usage:**
2105+
Most applications use all interrupts through IRQ. The default configuration is appropriate for:
2106+
- Timer interrupts (PPI #29 or TTC)
2107+
- UART, SPI, I2C peripherals
2108+
- DMA completion interrupts
2109+
- Inter-processor interrupts (IPI)
2110+
2111+
If dedicated low-latency FIQ is needed for a specific source not requiring GIC features, use `TX_ENABLE_FIQ_SUPPORT` with direct FIQ pin routing, bypassing GIC entirely
2112+
2113+
### 11.11 GIC Deferred EOI (Level-Triggered Interrupt Fix)
2114+
2115+
For platforms with level-triggered interrupts, there is a race window between the EOIR write in the IRQ handler and the CPSID in `_tx_thread_context_restore`. If the peripheral still asserts the interrupt signal, the interrupt can re-trigger before interrupts are masked.
2116+
2117+
**The Race Window:**
2118+
```
2119+
Normal flow:
2120+
Handler → EOIR write → ... → context_restore → CPSID
2121+
2122+
Race window: interrupt can re-trigger before CPSID
2123+
```
2124+
2125+
**Deferred EOI Solution:**
2126+
```
2127+
Deferred EOI:
2128+
Handler → save IAR → ... → context_restore → CPSID → EOIR write
2129+
2130+
Safe: IRQs already masked
2131+
```
2132+
2133+
**Enabling Deferred EOI:**
2134+
2135+
In `tx_user.h`:
2136+
```c
2137+
#define TX_GIC_DEFERRED_EOI
2138+
2139+
/* Optional: Override GIC base address if not ZynqMP */
2140+
#define TX_GIC_CPU_INTERFACE_BASE 0xF9001000
2141+
```
2142+
2143+
**Constraints:**
2144+
- **Single-core only**: Uses global `_tx_gic_iar` variable
2145+
- **NOT compatible with TX_ENABLE_IRQ_NESTING**: Build will fail with error
2146+
- **Requires TX_ENABLE_GIC_SUPPORT**: Must be enabled in tx_initialize_low_level.S
2147+
- **GICv2 only**: GICv3 uses different register interface
2148+
2149+
**How It Works:**
2150+
1. IRQ handler reads GICC_IAR and saves it to `_tx_gic_iar`
2151+
2. Handler runs normally but does NOT write EOIR
2152+
3. `_tx_thread_context_restore` executes CPSID (disabling interrupts)
2153+
4. After CPSID, deferred EOIR block writes the saved IAR to GICC_EOIR
2154+
5. Interrupt is now safely completed with no race window
2155+
2156+
**When to Use:**
2157+
- Level-triggered interrupts that remain asserted during handler execution
2158+
- Platforms where peripheral interrupt clear has latency
2159+
- Systems experiencing spurious interrupt storms
2160+
2161+
**When NOT to Use:**
2162+
- Edge-triggered interrupts (no race condition)
2163+
- Systems requiring IRQ nesting (incompatible)
2164+
- Multi-core systems (global variable not core-safe)

0 commit comments

Comments
 (0)