diff --git a/ports/cortex_r52/gnu/example_build/s32z280_evb/CMakeLists.txt b/ports/cortex_r52/gnu/example_build/s32z280_evb/CMakeLists.txt index 2545b3d06..94fe43301 100644 --- a/ports/cortex_r52/gnu/example_build/s32z280_evb/CMakeLists.txt +++ b/ports/cortex_r52/gnu/example_build/s32z280_evb/CMakeLists.txt @@ -55,6 +55,43 @@ target_link_options(s32z280_boot.elf PRIVATE ) +# Same image as s32z280_boot.elf with the interrupt service body placed in ATCM +# instead of code RAM, so the two can be compared on the same board in the same +# session. Separate target rather than an option so both exist at once and a +# result can be reproduced without reconfiguring. +add_executable(s32z280_atcm.elf EXCLUDE_FROM_ALL + ${EVB_DIR}/entry.S + ${EVB_DIR}/bsp_boot.c + ${EVB_DIR}/linflexd.c + ${EVB_DIR}/timer.c + ${EVB_DIR}/gic_probe.c + ${EVB_DIR}/mpu.c + ${EVB_DIR}/gicv3.c + ${EVB_DIR}/irq_dispatch.c + ${EVB_DIR}/cache.c + ${EVB_DIR}/tcm.c +) + +target_include_directories(s32z280_atcm.elf PRIVATE ${EVB_DIR}) + +# -g only: this image exists to be inspected through a debugger, and without +# DWARF the identity structure has no type for GDB to walk. No effect on the +# generated code. +target_compile_options(s32z280_atcm.elf PRIVATE -g) + +target_link_options(s32z280_atcm.elf PRIVATE + -T${EVB_DIR}/link.lds + -nostartfiles + -Wl,-Map=s32z280_atcm.map + # Bare metal: one code region and one data region, with access control + # belonging to the MPU rather than to segment permissions. + ${EVB_LINK_QUIET_RWX} +) + + +target_compile_definitions(s32z280_atcm.elf PRIVATE TX_R52_ATCM_ISR) + + # ThreadX demo on silicon: threads, preemption and the timer tick. Separate # target from s32z280_boot.elf, which deliberately does not link the kernel so # that a boot failure there is unambiguously a board problem. diff --git a/ports/cortex_r52/gnu/example_build/s32z280_evb/bsp_boot.c b/ports/cortex_r52/gnu/example_build/s32z280_evb/bsp_boot.c index 96ee23d75..50d22a2b2 100644 --- a/ports/cortex_r52/gnu/example_build/s32z280_evb/bsp_boot.c +++ b/ports/cortex_r52/gnu/example_build/s32z280_evb/bsp_boot.c @@ -569,7 +569,26 @@ void bsp_main(void) proves the interrupt is delivered, which is a different claim. */ MARK(0x50); - linflexd_puts("I1 gicv3_init\n"); + /* Copy the handler into ATCM now, and not earlier: T4 and T5 write test + patterns to the first and last words of the bank, which would land on top + of this code. The ECC preload in T3 has already established check bits + for the whole bank, and the 64-bit stores in the copy maintain them for + every location it writes. */ + + linflexd_puts("T6 copying .atcm_text into ATCM\n"); + { + unsigned long copied = tcm_copy_atcm_text(); + + report("atcm bytes", (unsigned int) copied); + if (copied == 0UL) + { + linflexd_puts(" nothing placed in ATCM in this image\n"); + } + } + + timer_cycles_enable(); + + linflexd_puts("I1 gicv3_init\n"); gicv3_init(); MARK(0x51); @@ -595,12 +614,18 @@ void bsp_main(void) MARK(0x53); { + extern unsigned int board_service_count; unsigned int spins = 0U; /* Wait for ticks. Bounded: a missing interrupt must report itself rather than hang the boot. */ - while ((board_irq_count < 5UL) && (spins < 30U)) + /* Wait for a full set of latency samples, not just a few ticks: + the figure of interest is the worst case, and a handful of + samples cannot show it. Still bounded, so a missing interrupt + reports itself rather than hanging the boot. */ + + while ((board_service_count < 64U) && (spins < 400U)) { timer_spin(200000U); spins++; @@ -612,6 +637,53 @@ void bsp_main(void) report("timer INTID", (unsigned int) board_timer_intid); report("spurious ", (unsigned int) board_spurious_count); report("unexpected", (unsigned int) board_unexpected_intid); + + /* Handler body execution time. Reported as min, max and mean rather + than mean alone: the spread is the point. */ + + { + extern unsigned int board_service_cycles[]; + extern unsigned int board_service_count; + + unsigned int i; + unsigned int lo = 0xFFFFFFFFU; + unsigned int hi = 0U; + unsigned long sum = 0UL; + unsigned int n; + + /* Stop the source and snapshot the count before reading any of it. + The timer re-arms inside the handler, so samples keep arriving + otherwise: the first version of this read let the count grow + between computing the extremes and dividing for the mean, and + printed a mean below the minimum. */ + + timer_stop(); + n = board_service_count; + + for (i = 0U; i < n; i++) + { + unsigned int v = board_service_cycles[i]; + + if (v < lo) { lo = v; } + if (v > hi) { hi = v; } + sum += (unsigned long) v; + } + + linflexd_puts("I5 handler body cycles "); +#ifdef TX_R52_ATCM_ISR + linflexd_puts("(body in ATCM)\n"); +#else + linflexd_puts("(body in code RAM)\n"); +#endif + report("samples ", n); + if (n > 0U) + { + report("min ", lo); + report("max ", hi); + report("mean ", (unsigned int) (sum / n)); + report("spread ", hi - lo); + } + } report("first INTID", (unsigned int) board_first_intid); linflexd_puts("I4 interrupt test done\n"); } diff --git a/ports/cortex_r52/gnu/example_build/s32z280_evb/irq_dispatch.c b/ports/cortex_r52/gnu/example_build/s32z280_evb/irq_dispatch.c index 193f14dea..0ef90b7b4 100644 --- a/ports/cortex_r52/gnu/example_build/s32z280_evb/irq_dispatch.c +++ b/ports/cortex_r52/gnu/example_build/s32z280_evb/irq_dispatch.c @@ -164,7 +164,55 @@ void board_init(void) /* before nesting starts. Does not acknowledge and does not EOI. */ /**************************************************************************/ +/**************************************************************************/ +/* Handler execution time, and where the handler's instructions live. */ +/* */ +/* TX_R52_ATCM_ISR places the service routine in ATCM. ATCM runs at full */ +/* core speed with one wait state; .text lives in RTU code RAM, which runs */ +/* at half the core frequency (S32Z2 RM 6.3.6). ATCM also has no cache to */ +/* miss, which is the property that matters for a determinism argument. */ +/* */ +/* Measured in cycles from the PMU counter, not CNTPCT: at 8 MHz CNTPCT */ +/* cannot resolve a handler body, let alone the variation in one. */ +/* */ +/* The timing wrapper below stays in .text in both configurations, so its */ +/* own cost appears in every sample and cancels when the two are compared. */ +/* Only the body moves. What matters in the result is the spread: a warm */ +/* instruction cache can match ATCM on the mean and cannot match it on the */ +/* worst case. */ +/**************************************************************************/ + +#define BOARD_LATENCY_SAMPLES 64U + +unsigned int board_service_cycles[BOARD_LATENCY_SAMPLES]; +unsigned int board_service_count; + +#ifdef TX_R52_ATCM_ISR +#define BOARD_ISR_SECTION __attribute__((section(".atcm_text"))) +#else +#define BOARD_ISR_SECTION +#endif + +static BOARD_ISR_SECTION void board_irq_service_body(unsigned long intid); + void board_irq_service(unsigned long intid) +{ + unsigned int before; + unsigned int after; + + before = timer_read_cycles(); + board_irq_service_body(intid); + after = timer_read_cycles(); + + if (board_service_count < BOARD_LATENCY_SAMPLES) + { + board_service_cycles[board_service_count] = after - before; + board_service_count++; + } +} + + +static BOARD_ISR_SECTION void board_irq_service_body(unsigned long intid) { if (intid == GICV3_SPURIOUS_INTID) { diff --git a/ports/cortex_r52/gnu/example_build/s32z280_evb/link.lds b/ports/cortex_r52/gnu/example_build/s32z280_evb/link.lds index ac0583b3c..1b39be82a 100644 --- a/ports/cortex_r52/gnu/example_build/s32z280_evb/link.lds +++ b/ports/cortex_r52/gnu/example_build/s32z280_evb/link.lds @@ -44,6 +44,11 @@ __sys_stack_size__ = 0x0800; MEMORY { CODE (rx) : ORIGIN = 0x79900000, LENGTH = 0x00700000 /* 7 MB */ + /* ATCM. entry.S programmes IMP_ATCMREGIONR to this base and enables the + bank before anything runs from it, and mpu.c maps it executable. Code + placed here runs at full core speed with one wait state, where CODE is + RTU code RAM and runs at half the core frequency (S32Z2 RM 6.3.6). */ + ATCM (rwx) : ORIGIN = 0x30000000, LENGTH = 0x00010000 /* 64 KB */ DATA (rwx) : ORIGIN = 0x31780000, LENGTH = 0x00080000 /* 512 KB: DRAM0 + DRAM1, both full core speed */ } @@ -89,6 +94,26 @@ SECTIONS *(.glue_7t) } > CODE + /* Code that runs from tightly-coupled memory. Loaded into CODE and copied + to ATCM by atcm_copy_text() before it is called, because ECC is enabled + on this part and a TCM location must be written before it is read + (Cortex-R52 TRM 6.2.2). The copy uses 64-bit stores for the same + reason: ATCM requires them where BTCM and CTCM accept 32-bit. + + ALIGN(8) at both ends so the copy can move whole 64-bit units without a + tail case, which is what keeps the ECC requirement satisfied for every + location written. */ + + .atcm_text : ALIGN(8) + { + __atcm_text_start__ = .; + *(.atcm_text*) + . = ALIGN(8); + __atcm_text_end__ = .; + } > ATCM AT> CODE + + __atcm_text_load__ = LOADADDR(.atcm_text); + .rodata : { *(.rodata*) diff --git a/ports/cortex_r52/gnu/example_build/s32z280_evb/tcm.c b/ports/cortex_r52/gnu/example_build/s32z280_evb/tcm.c index a2b9dd68a..081dc4140 100644 --- a/ports/cortex_r52/gnu/example_build/s32z280_evb/tcm.c +++ b/ports/cortex_r52/gnu/example_build/s32z280_evb/tcm.c @@ -260,3 +260,45 @@ void tcm_preload(unsigned int index, unsigned long base, unsigned long bytes) __asm volatile ("dsb sy" ::: "memory"); } + + +/**************************************************************************/ +/* Copy .atcm_text into ATCM. */ +/**************************************************************************/ + +extern char __atcm_text_start__; +extern char __atcm_text_end__; +extern char __atcm_text_load__; + +unsigned long tcm_copy_atcm_text(void) +{ + /* The linker aligns both ends of the section to 8, so the whole extent is a + whole number of 64-bit units and there is no tail to handle narrowly. + That matters here rather than being tidiness: a 32-bit store to ATCM does + not establish the ECC check bits for its location, so a tail copied 32 + bits at a time would leave words that fault when read. */ + + volatile unsigned long long *dst; + const volatile unsigned long long *src; + unsigned long bytes; + unsigned long i; + + dst = (volatile unsigned long long *) (void *) &__atcm_text_start__; + src = (const volatile unsigned long long *) (void *) &__atcm_text_load__; + bytes = (unsigned long) (&__atcm_text_end__ - &__atcm_text_start__); + + for (i = 0UL; i < (bytes / 8UL); i++) + { + dst[i] = src[i]; + } + + /* Complete the writes, then discard any stale instruction-side copy of the + destination before it is executed. */ + + __asm__ volatile("dsb sy" ::: "memory"); + __asm__ volatile("mcr p15, 0, r0, c7, c5, 0" : : : "memory"); /* ICIALLU */ + __asm__ volatile("dsb sy" ::: "memory"); + __asm__ volatile("isb" ::: "memory"); + + return bytes; +} diff --git a/ports/cortex_r52/gnu/example_build/s32z280_evb/tcm.h b/ports/cortex_r52/gnu/example_build/s32z280_evb/tcm.h index b47358f26..b80f226d3 100644 --- a/ports/cortex_r52/gnu/example_build/s32z280_evb/tcm.h +++ b/ports/cortex_r52/gnu/example_build/s32z280_evb/tcm.h @@ -148,4 +148,14 @@ unsigned int tcm_enable(unsigned int index, unsigned long base); void tcm_preload(unsigned int index, unsigned long base, unsigned long bytes); +/* Copy the .atcm_text section from its load address in CODE to its run address + in ATCM. Must run before anything in that section is called, and after the + bank is enabled. Returns the number of bytes copied, which is zero when the + section is empty -- the normal case for an image that places nothing there. + + Uses 64-bit stores because ECC is enabled on this part and ATCM requires + them; see tcm_preload and Cortex-R52 TRM 6.2.2. */ + +unsigned long tcm_copy_atcm_text(void); + #endif diff --git a/ports/cortex_r52/gnu/example_build/s32z280_evb/timer.c b/ports/cortex_r52/gnu/example_build/s32z280_evb/timer.c index 544102083..9fc5c0a17 100644 --- a/ports/cortex_r52/gnu/example_build/s32z280_evb/timer.c +++ b/ports/cortex_r52/gnu/example_build/s32z280_evb/timer.c @@ -146,3 +146,41 @@ void timer_stop(void) __asm volatile ("mcr p15, 0, %0, c14, c2, 1" :: "r" (control)); __asm volatile ("isb"); } + + +/**************************************************************************/ +/* PMU cycle counter. */ +/* */ +/* PMCR.E gates all counters including the dedicated cycle counter, and */ +/* PMCNTENSET bit 31 enables that counter specifically. Both are needed. */ +/* PMCR.D is left clear so the counter advances every cycle rather than */ +/* every 64th, which is the resolution these measurements want. */ +/**************************************************************************/ + +#define PMCR_E (1UL << 0) /* enable all counters */ +#define PMCR_P (1UL << 1) /* reset event counters */ +#define PMCR_C (1UL << 2) /* reset cycle counter */ +#define PMCNTENSET_C (1UL << 31) /* cycle counter enable */ + +void timer_cycles_enable(void) +{ + unsigned long pmcr; + + /* Allow EL0/EL1 access and enable, resetting the counter as we go. */ + + __asm__ volatile("mrc p15, 0, %0, c9, c12, 0" : "=r"(pmcr)); + pmcr |= (PMCR_E | PMCR_C); + __asm__ volatile("mcr p15, 0, %0, c9, c12, 0" : : "r"(pmcr) : "memory"); + + __asm__ volatile("mcr p15, 0, %0, c9, c12, 1" + : : "r"((unsigned long) PMCNTENSET_C) : "memory"); + + __asm__ volatile("isb" ::: "memory"); +} + +unsigned int timer_read_cycles(void) +{ + unsigned long value; + __asm__ volatile("mrc p15, 0, %0, c9, c13, 0" : "=r"(value)); + return (unsigned int) value; +} diff --git a/ports/cortex_r52/gnu/example_build/s32z280_evb/timer.h b/ports/cortex_r52/gnu/example_build/s32z280_evb/timer.h index 7cd411efc..0a0866b8f 100644 --- a/ports/cortex_r52/gnu/example_build/s32z280_evb/timer.h +++ b/ports/cortex_r52/gnu/example_build/s32z280_evb/timer.h @@ -61,4 +61,12 @@ void timer_start_oneshot_irq(unsigned int ticks); unsigned int timer_fired(void); void timer_stop(void); +/* PMU cycle counter. CNTPCT runs at 8 MHz on this part, so its 125 ns + granularity is too coarse to resolve interrupt latency, let alone the jitter + in it. The cycle counter runs at the core clock and is what those + measurements use. timer_cycles_enable must be called before any read. */ + +void timer_cycles_enable(void); +unsigned int timer_read_cycles(void); + #endif /* TIMER_H */