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 @@ -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.
Expand Down
76 changes: 74 additions & 2 deletions ports/cortex_r52/gnu/example_build/s32z280_evb/bsp_boot.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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++;
Expand All @@ -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");
}
Expand Down
48 changes: 48 additions & 0 deletions ports/cortex_r52/gnu/example_build/s32z280_evb/irq_dispatch.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
Expand Down
25 changes: 25 additions & 0 deletions ports/cortex_r52/gnu/example_build/s32z280_evb/link.lds
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
}

Expand Down Expand Up @@ -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*)
Expand Down
42 changes: 42 additions & 0 deletions ports/cortex_r52/gnu/example_build/s32z280_evb/tcm.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
10 changes: 10 additions & 0 deletions ports/cortex_r52/gnu/example_build/s32z280_evb/tcm.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
38 changes: 38 additions & 0 deletions ports/cortex_r52/gnu/example_build/s32z280_evb/timer.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
8 changes: 8 additions & 0 deletions ports/cortex_r52/gnu/example_build/s32z280_evb/timer.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
Loading