mirror of
https://github.com/eclipse-threadx/threadx.git
synced 2026-10-06 06:59:08 +08:00
Added a ThreadX module manager port for the Cortex-R52 (#639)
cortex_m / Cortex M0 build (push) Canceled after 0s
cortex_m / Cortex M3 build (push) Canceled after 0s
cortex_m / Cortex M4 build (push) Canceled after 0s
cortex_m / Cortex M7 build (push) Canceled after 0s
gcc_check / gnu (push) Canceled after 0s
r52_fvp / r52 (push) Canceled after 0s
regression_test / tx (push) Canceled after 0s
regression_test / smp (push) Canceled after 0s
regression_test / freertos (push) Canceled after 0s
regression_test / riscv (push) Canceled after 0s
regression_test / deploy (push) Canceled after 0s
regression_template / run_tests (push) Canceled after 0s
regression_template / deploy_code_coverage (push) Canceled after 0s
cortex_m / Cortex M0 build (push) Canceled after 0s
cortex_m / Cortex M3 build (push) Canceled after 0s
cortex_m / Cortex M4 build (push) Canceled after 0s
cortex_m / Cortex M7 build (push) Canceled after 0s
gcc_check / gnu (push) Canceled after 0s
r52_fvp / r52 (push) Canceled after 0s
regression_test / tx (push) Canceled after 0s
regression_test / smp (push) Canceled after 0s
regression_test / freertos (push) Canceled after 0s
regression_test / riscv (push) Canceled after 0s
regression_test / deploy (push) Canceled after 0s
regression_template / run_tests (push) Canceled after 0s
regression_template / deploy_code_coverage (push) Canceled after 0s
Added the first GNU ThreadX module port for an Arm R-profile core. The port combines the PMSAv8-R MPU model from Cortex-M33 with the AArch32 privilege and processor-mode handling from Cortex-R4. It provides the complete module path: headers, manager sources, scheduler integration, user-mode entry, SVC dispatch, data- and prefetch-abort capture, fault notification, relocatable module loading, and shared-memory regions. Module isolation uses eight MPU regions (8–15) with a 64-byte granule. The scheduler replaces those regions on each module switch and manages a separate privileged loading window in region 16. Assembly-visible structure offsets and region-layout assumptions are checked at build time. Added independent module demonstrations for the S32Z280-594EVB and Armv8-R AEM FVP. The automated FVP regressions cover: - Loading the same position-independent module at different addresses - User-mode data and instruction access violations - Fault capture and notification for both abort types - Shared-region access, alignment, exhaustion, empty-size, and overflow handling - Required module-property combinations - The GCC CLZ-based priority search The port requires user mode and memory protection together, at least 17 EL1 MPU regions, and currently validates A32 modules; Thumb module execution remains unvalidated. Validated with GNU Arm 14.3.1. The FVP suites pass 8/8 in the default configuration and 11/11 with hard-float, FIQ, and interrupt nesting enabled. The S32Z280 images build cleanly, and the module isolation and relocation paths were exercised on S32Z280 silicon during development. Matching user documentation is provided by rtos-docs-asciidoc PR #41. Assisted-by: Claude Code (Opus 5) <noreply@anthropic.com>
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,149 @@
|
||||
/***************************************************************************
|
||||
* 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 */
|
||||
/* */
|
||||
/* cache.c Cortex-R52/GNU */
|
||||
/* 6.5.2 */
|
||||
/* AUTHOR */
|
||||
/* */
|
||||
/* Frédéric Desbiens, Eclipse Foundation */
|
||||
/* */
|
||||
/* DESCRIPTION */
|
||||
/* */
|
||||
/* Cache maintenance for a loader that copies code. See cache.h for */
|
||||
/* why only these three functions exist. */
|
||||
/* */
|
||||
/* MISRA C:2012 deviations (justified) */
|
||||
/* */
|
||||
/* Directive 4.3 -- cache maintenance and CTR are reachable only */
|
||||
/* through CP15; every access is encapsulated in a one-line accessor */
|
||||
/* below and nowhere else in this file. */
|
||||
/* Rule 11.6 (conversion between a pointer and an integer) -- a cache */
|
||||
/* maintenance operation takes a virtual address as a register value, */
|
||||
/* so the conversion is what the instruction requires. */
|
||||
/* */
|
||||
/**************************************************************************/
|
||||
|
||||
#include "cache.h"
|
||||
|
||||
/* The smallest data-cache line in the machine, from CTR.DminLine. Held as a
|
||||
log2 of the number of 32-bit words, so the byte count is 4 << DminLine.
|
||||
DminLine and not the L1 geometry: a maintenance-by-address walk has to step
|
||||
by the smallest line any level implements, or it skips lines in that level. */
|
||||
|
||||
#define CTR_DMINLINE_SHIFT 16U
|
||||
#define CTR_DMINLINE_MASK 0xFUL
|
||||
|
||||
|
||||
/**************************************************************************/
|
||||
/* CP15 accessors. The only assembly in this file (MISRA C:2012 Dir 4.3).*/
|
||||
/**************************************************************************/
|
||||
|
||||
static unsigned long read_ctr(void)
|
||||
{
|
||||
unsigned long value;
|
||||
__asm__ volatile("mrc p15, 0, %0, c0, c0, 1" : "=r"(value));
|
||||
return value;
|
||||
}
|
||||
|
||||
static void clean_dcache_line(unsigned long address)
|
||||
{
|
||||
__asm__ volatile("mcr p15, 0, %0, c7, c10, 1" :: "r"(address) : "memory");
|
||||
}
|
||||
|
||||
static void invalidate_icache_all_op(void)
|
||||
{
|
||||
unsigned long zero = 0UL;
|
||||
__asm__ volatile("mcr p15, 0, %0, c7, c5, 0" :: "r"(zero) : "memory");
|
||||
}
|
||||
|
||||
static void data_sync_barrier(void)
|
||||
{
|
||||
__asm__ volatile("dsb sy" ::: "memory");
|
||||
}
|
||||
|
||||
static void instruction_barrier(void)
|
||||
{
|
||||
__asm__ volatile("isb" ::: "memory");
|
||||
}
|
||||
|
||||
|
||||
/**************************************************************************/
|
||||
/* cache_dcache_line_bytes */
|
||||
/**************************************************************************/
|
||||
|
||||
unsigned long cache_dcache_line_bytes(void)
|
||||
{
|
||||
return 4UL << ((read_ctr() >> CTR_DMINLINE_SHIFT) & CTR_DMINLINE_MASK);
|
||||
}
|
||||
|
||||
|
||||
/**************************************************************************/
|
||||
/* cache_clean_range */
|
||||
/* */
|
||||
/* Clean by virtual address over [start, start + length). */
|
||||
/* */
|
||||
/* The start is rounded DOWN to a line boundary and the walk continues */
|
||||
/* past the end until the last line containing a requested byte has been */
|
||||
/* cleaned. Rounding the start up instead would leave the first partial */
|
||||
/* line dirty, which is the whole failure this exists to prevent and is */
|
||||
/* invisible whenever the caller happens to be line aligned. */
|
||||
/**************************************************************************/
|
||||
|
||||
void cache_clean_range(const void *start_address, unsigned long length)
|
||||
{
|
||||
unsigned long line = cache_dcache_line_bytes();
|
||||
unsigned long address;
|
||||
unsigned long end;
|
||||
|
||||
if (length == 0UL)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
address = (unsigned long) start_address;
|
||||
end = address + length;
|
||||
|
||||
address &= ~(line - 1UL);
|
||||
|
||||
while (address < end)
|
||||
{
|
||||
clean_dcache_line(address);
|
||||
address += line;
|
||||
}
|
||||
|
||||
/* The stores have to be complete before anything fetches from the range. */
|
||||
|
||||
data_sync_barrier();
|
||||
}
|
||||
|
||||
|
||||
/**************************************************************************/
|
||||
/* cache_invalidate_icache_all */
|
||||
/**************************************************************************/
|
||||
|
||||
void cache_invalidate_icache_all(void)
|
||||
{
|
||||
invalidate_icache_all_op();
|
||||
|
||||
/* DSB then ISB: the invalidate must complete, and the pipeline must be
|
||||
flushed of anything fetched before it did. */
|
||||
|
||||
data_sync_barrier();
|
||||
instruction_barrier();
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
/***************************************************************************
|
||||
* 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 */
|
||||
/* */
|
||||
/* cache.h Cortex-R52/GNU */
|
||||
/* 6.5.2 */
|
||||
/* AUTHOR */
|
||||
/* */
|
||||
/* Frédéric Desbiens, Eclipse Foundation */
|
||||
/* */
|
||||
/* DESCRIPTION */
|
||||
/* */
|
||||
/* The cache maintenance a loader owes the instruction side, and */
|
||||
/* nothing else. */
|
||||
/* */
|
||||
/* The S32Z280 board support carries a full cache driver -- enable, */
|
||||
/* disable, geometry, set/way sweeps -- because silicon bring-up needed */
|
||||
/* to ask the hardware what it had. None of that is needed here: the */
|
||||
/* caches are turned on once in mpu_init and never turned off, and the */
|
||||
/* model's geometry is not in question. What IS needed is the pair of */
|
||||
/* operations that make copied code executable, so that is all this is. */
|
||||
/* */
|
||||
/* Why it is needed at all: the module area is Normal write-back memory, */
|
||||
/* so a byte copy of module code leaves the bytes in dirty data-cache */
|
||||
/* lines while the instruction side -- which is not coherent with the */
|
||||
/* data cache on this core -- fetches whatever main memory still holds. */
|
||||
/* Cleaning by address range and then invalidating the instruction cache */
|
||||
/* is what closes that gap. */
|
||||
/* */
|
||||
/* By range rather than by set/way, deliberately. A set/way sweep needs */
|
||||
/* the cache geometry and touches every line in the machine; the loader */
|
||||
/* knows exactly which bytes it wrote, so the range form is both tighter */
|
||||
/* and shorter to get right. */
|
||||
/* */
|
||||
/**************************************************************************/
|
||||
|
||||
#ifndef CACHE_H
|
||||
#define CACHE_H
|
||||
|
||||
/* Clean (write back) the data cache over one address range, so that main
|
||||
memory holds what the copy wrote. Rounds outwards to cache-line
|
||||
boundaries; a length of zero does nothing. */
|
||||
|
||||
void cache_clean_range(const void *start_address, unsigned long length);
|
||||
|
||||
/* Invalidate the whole instruction cache, so no stale line from a previous
|
||||
image at the same address can be served. Invalidate-all rather than by
|
||||
range because it is one register write and this runs once per load. */
|
||||
|
||||
void cache_invalidate_icache_all(void);
|
||||
|
||||
/* Data cache line length in bytes, from CTR.DminLine. Published because the
|
||||
range walk above depends on it and a caller may want to report it. */
|
||||
|
||||
unsigned long cache_dcache_line_bytes(void);
|
||||
|
||||
#endif /* CACHE_H */
|
||||
File diff suppressed because it is too large
Load Diff
@@ -101,6 +101,20 @@
|
||||
.equ HSR_EC_SHIFT, 26
|
||||
.equ HSR_EC_HVC, 0x12 /* HVC executed in AArch32 */
|
||||
|
||||
#ifdef TXM_MODULE_MANAGER
|
||||
|
||||
/* The module manager owns three of the EL1 vectors in a manager build: the
|
||||
supervisor call, which is the privilege boundary a module crosses to reach
|
||||
the kernel, and the two aborts, which are how a module that reaches outside
|
||||
its regions is caught. Declared rather than merely referenced so that a
|
||||
build with the manager half-configured fails at the link with a name in it. */
|
||||
|
||||
.extern __tx_module_svc_interrupt
|
||||
.extern _txm_module_manager_data_abort
|
||||
.extern _txm_module_manager_prefetch_abort
|
||||
|
||||
#endif
|
||||
|
||||
/* Report which vector was taken, then halt. The FVP offers no GDB stub
|
||||
(Iris only), so a self-identifying fault is the primary debugging tool
|
||||
for this port. Uses no stack: only r0/r1 and a semihosting call. */
|
||||
@@ -144,7 +158,11 @@ el2_vectors:
|
||||
el1_vectors:
|
||||
b el1_trap_reset /* 0x00 reset */
|
||||
b el1_trap_undef /* 0x04 undefined instruction */
|
||||
#ifdef TXM_MODULE_MANAGER
|
||||
b __tx_module_svc_interrupt /* 0x08 module kernel boundary */
|
||||
#else
|
||||
b el1_trap_svc /* 0x08 supervisor call */
|
||||
#endif
|
||||
b el1_trap_pabt /* 0x0C prefetch abort */
|
||||
b el1_trap_dabt /* 0x10 data abort */
|
||||
b el1_trap_reserved /* 0x14 reserved */
|
||||
@@ -476,6 +494,26 @@ el1_trap_svc: FAULT_REPORT msg_el1_svc
|
||||
and this handler returns there, restoring the pre-fault mode from SPSR. */
|
||||
|
||||
el1_trap_pabt:
|
||||
#ifdef TXM_MODULE_MANAGER
|
||||
|
||||
/* A fault from User mode is a module reaching outside its code region, and
|
||||
it belongs to the module manager, which records it and terminates the
|
||||
thread. A fault from a privileged mode falls through to the paths below,
|
||||
which the MPU self-test depends on.
|
||||
|
||||
r0 is pushed and popped around the test rather than simply used: the
|
||||
capture in the manager records the faulting registers, and clobbering one
|
||||
here would have it record ours instead. LDM does not affect the flags,
|
||||
so the comparison still holds after the pop. */
|
||||
|
||||
stmdb sp!, {r0}
|
||||
mrs r0, spsr
|
||||
and r0, r0, #0x1F
|
||||
cmp r0, #0x10 /* User mode? */
|
||||
ldmia sp!, {r0}
|
||||
beq _txm_module_manager_prefetch_abort
|
||||
|
||||
#endif
|
||||
#ifdef TX_R52_MPU_FAULT_TEST
|
||||
push {r0, r1}
|
||||
ldr r0, =mpu_expect_pabt
|
||||
@@ -532,6 +570,19 @@ mpu_try_execute_land:
|
||||
Runs on the Abort-mode stack set up above. */
|
||||
|
||||
el1_trap_dabt:
|
||||
#ifdef TXM_MODULE_MANAGER
|
||||
|
||||
/* A fault from User mode is a module reaching outside its data region. See
|
||||
el1_trap_pabt above for why r0 is saved across the test. */
|
||||
|
||||
stmdb sp!, {r0}
|
||||
mrs r0, spsr
|
||||
and r0, r0, #0x1F
|
||||
cmp r0, #0x10 /* User mode? */
|
||||
ldmia sp!, {r0}
|
||||
beq _txm_module_manager_data_abort
|
||||
|
||||
#endif
|
||||
#ifdef TX_R52_MPU_FAULT_TEST
|
||||
push {r0, r1}
|
||||
ldr r0, =mpu_expect_abort
|
||||
|
||||
@@ -294,6 +294,80 @@ static void program_region(unsigned int index, const MPU_REGION *region_ptr)
|
||||
}
|
||||
|
||||
|
||||
#ifdef TXM_MODULE_MANAGER
|
||||
|
||||
/**************************************************************************/
|
||||
/* mpu_module_window_init */
|
||||
/* */
|
||||
/* A window over the module area, for the manager to load through. */
|
||||
/* */
|
||||
/* No region in the table above covers the module area, which is what */
|
||||
/* stops every thread from reaching a module's memory -- but the manager */
|
||||
/* has to read the preamble and write the module's data to load it at */
|
||||
/* all. Without this the load faults on its first read of the image. */
|
||||
/* */
|
||||
/* Not opened and closed around the load. It is left enabled here and */
|
||||
/* the SCHEDULER owns it from then on: it turns this region on for every */
|
||||
/* thread that owns no module and off for every thread that does, which */
|
||||
/* is what keeps it and a module's own regions -- which cover the same */
|
||||
/* memory -- from ever being enabled together. PMSAv8-R has no region */
|
||||
/* priority, so an access hitting more than one enabled region takes a */
|
||||
/* translation fault (TRM 8.1), and mutual exclusion by ownership is the */
|
||||
/* only form of it that does not depend on remembering to bracket a call. */
|
||||
/* */
|
||||
/* EL1 read/write with no EL0 access, and execute-never: the manager can */
|
||||
/* load through it, and a module cannot use it to reach anything. */
|
||||
/**************************************************************************/
|
||||
|
||||
unsigned long mpu_module_window_prbar;
|
||||
unsigned long mpu_module_window_prlar;
|
||||
|
||||
|
||||
unsigned int mpu_module_window_init(void)
|
||||
{
|
||||
MPU_REGION window;
|
||||
|
||||
/* Asked of the hardware rather than assumed. See MPU_MODULE_REGIONS_REQUIRED
|
||||
in mpu.h: this model reports a region count no real Cortex-R52 can have,
|
||||
so the number is checked here and the shortfall is a refusal. */
|
||||
|
||||
if (mpu_region_count() < MPU_MODULE_REGIONS_REQUIRED)
|
||||
{
|
||||
return 0U;
|
||||
}
|
||||
|
||||
window.mpu_region_base = FVP_MODULE_AREA_BASE;
|
||||
window.mpu_region_limit = FVP_MODULE_AREA_BASE
|
||||
+ FVP_MODULE_AREA_SIZE - 1UL;
|
||||
window.mpu_region_ap = MPU_AP_RW_EL1;
|
||||
window.mpu_region_execute_never = 1U;
|
||||
window.mpu_region_shareability = MPU_SH_NON;
|
||||
window.mpu_region_attr_index = MPU_ATTR_NORMAL_WB;
|
||||
window.mpu_region_name = "module window RW NX EL1";
|
||||
|
||||
/* Published for the scheduler, which turns this region on and off on every
|
||||
dispatch and has no business computing register layouts in assembly. */
|
||||
|
||||
mpu_module_window_prbar = (window.mpu_region_base & 0xFFFFFFC0UL)
|
||||
| (((unsigned long) window.mpu_region_shareability & 0x3UL) << 3)
|
||||
| (((unsigned long) window.mpu_region_ap & 0x3UL) << 1)
|
||||
| ((unsigned long) window.mpu_region_execute_never & 0x1UL);
|
||||
|
||||
mpu_module_window_prlar = (window.mpu_region_limit & 0xFFFFFFC0UL)
|
||||
| (((unsigned long) window.mpu_region_attr_index & 0x7UL) << 1)
|
||||
| 1UL;
|
||||
|
||||
program_region(MPU_MODULE_LOAD_REGION, &window);
|
||||
|
||||
data_sync_barrier();
|
||||
instruction_barrier();
|
||||
|
||||
return 1U;
|
||||
}
|
||||
|
||||
#endif /* TXM_MODULE_MANAGER */
|
||||
|
||||
|
||||
/**************************************************************************/
|
||||
/* mpu_init */
|
||||
/**************************************************************************/
|
||||
@@ -311,6 +385,20 @@ unsigned int mpu_init(void)
|
||||
return 0U;
|
||||
}
|
||||
|
||||
#ifdef TXM_MODULE_MANAGER
|
||||
|
||||
/* A module manager build needs regions the table above does not describe:
|
||||
the eight handed to a module and the window over the module area. If the
|
||||
implementation cannot supply them, stop here rather than enable a
|
||||
protection map that is missing the half a module depends on. */
|
||||
|
||||
if (available < MPU_MODULE_REGIONS_REQUIRED)
|
||||
{
|
||||
return 0U;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
/* Memory types first: a region's attribute index is meaningless until
|
||||
MAIR is populated. */
|
||||
|
||||
@@ -332,6 +420,20 @@ unsigned int mpu_init(void)
|
||||
write_prlar(0UL);
|
||||
}
|
||||
|
||||
#ifdef TXM_MODULE_MANAGER
|
||||
|
||||
/* After the loop above, which would otherwise disable it again: the module
|
||||
window lives above the regions this table uses, so it counts as unused
|
||||
here. The region count was checked at the top of this function, so the
|
||||
call cannot fail at this point. */
|
||||
|
||||
if (mpu_module_window_init() == 0U)
|
||||
{
|
||||
return 0U;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
data_sync_barrier();
|
||||
|
||||
/* Caches are invalidated before enabling. The instruction cache has a
|
||||
|
||||
@@ -116,4 +116,54 @@ const MPU_REGION *mpu_region_table(unsigned int *count_ptr);
|
||||
void mpu_read_region(unsigned int index, unsigned long *prbar_ptr,
|
||||
unsigned long *prlar_ptr);
|
||||
|
||||
/* ---------------------------------------------------------------------------
|
||||
ThreadX modules.
|
||||
|
||||
Kept in this header rather than behind TXM_MODULE_MANAGER because
|
||||
txm_module_manager_offset_check.c includes it to check the region number
|
||||
against the one tx_thread_schedule.S writes in assembly, and a definition
|
||||
that appears only in some builds cannot be checked in the others.
|
||||
--------------------------------------------------------------------------- */
|
||||
|
||||
/* Region index for the manager's load window over the module area. Above the
|
||||
kernel's 0-2 and above the eight the manager hands to a module (8-15), so
|
||||
neither the boot table nor the scheduler's per-thread region load can
|
||||
disturb it. tx_thread_schedule.S spells the same number in an MCR to
|
||||
PRSELR; the offset check asserts the two agree. */
|
||||
|
||||
#define MPU_MODULE_LOAD_REGION 16U
|
||||
|
||||
/* How many EL1 regions a module manager build needs: 0-2 kernel, 8-15 for the
|
||||
module block, 16 for the window. Region 16 is the highest index used, so
|
||||
seventeen regions is the requirement.
|
||||
|
||||
Checked against MPUIR rather than assumed, even though the AEMv8-R model
|
||||
reports 32. The model reports a region count NO Cortex-R52 can have -- the
|
||||
TRM gives MPUIR.DREGION as 16, 20 or 24 -- so a green run here says nothing
|
||||
about whether the budget fits silicon, and a part configured with 16 regions
|
||||
cannot run this port at all. The check is here so that the shortfall is a
|
||||
refusal rather than a region that silently does not exist. */
|
||||
|
||||
#define MPU_MODULE_REGIONS_REQUIRED 17U
|
||||
|
||||
/* The module area window, which is what lets privileged code reach module
|
||||
memory at all -- no other kernel region covers it.
|
||||
|
||||
PMSAv8-R has no region priority, so this must never be enabled at the same
|
||||
time as the regions a module is given, which cover the same memory. That is
|
||||
guaranteed by who owns it rather than by careful calling: the scheduler turns
|
||||
this region on for every thread that is not a module thread and off for every
|
||||
thread that is, so the window is enabled exactly when no module regions are
|
||||
loaded. The two register words are published for it below.
|
||||
|
||||
Enabled at boot, because everything before the first module thread is
|
||||
privileged code that may need to reach module memory. Returns 0 if the
|
||||
implementation has fewer than MPU_MODULE_REGIONS_REQUIRED regions, in which
|
||||
case nothing was programmed. */
|
||||
|
||||
unsigned int mpu_module_window_init(void);
|
||||
|
||||
extern unsigned long mpu_module_window_prbar;
|
||||
extern unsigned long mpu_module_window_prlar;
|
||||
|
||||
#endif /* MPU_H */
|
||||
|
||||
@@ -71,6 +71,59 @@
|
||||
|
||||
#define SYSTEM_COUNTER_HZ 100000000
|
||||
|
||||
/* ---------------------------------------------------------------------------
|
||||
Memory for ThreadX modules.
|
||||
|
||||
Only the module manager build uses this, but the addresses are stated here
|
||||
rather than in the module example because they are a property of the board's
|
||||
memory map: a module's code and data are handed to it as MPU regions, and
|
||||
PMSAv8-R will not allow those to overlap a kernel region. So module memory
|
||||
has to be memory NO kernel region covers, and deciding that is the memory
|
||||
map's job.
|
||||
|
||||
link_module.lds ends the kernel's DRAM region at this base and gives the
|
||||
64 KB above it to the module area; mpu.c covers exactly the same range with
|
||||
the manager's load window (region 16), which is the only mapping privileged
|
||||
code has over it. The three have to agree, so all three read these two
|
||||
values.
|
||||
|
||||
Chosen at 0x003F0000 -- just under 4 MB -- because the manager image with its
|
||||
stacks and its 1 MB of heap is far smaller than that, and the link script
|
||||
asserts the two do not meet rather than trusting the margin. */
|
||||
|
||||
#define FVP_MODULE_AREA_BASE 0x003F0000
|
||||
#define FVP_MODULE_AREA_SIZE 0x00010000 /* 64 KB */
|
||||
|
||||
/* The shared granules the sample module reports its progress through, at the
|
||||
base of the module area.
|
||||
|
||||
The first of them exists because the FVP has no debugger seam: on silicon a
|
||||
GDB harness reads the module's own progress variable out of its data area,
|
||||
and here nothing outside the image can read anything. So the manager grants
|
||||
the module a shared region over this word and reads it back afterwards, which
|
||||
is what lets the FVP test judge what the module actually managed to do rather
|
||||
than only that it faulted.
|
||||
|
||||
The rest exist to exercise the shared-region machinery itself. A module may
|
||||
be granted TXM_MODULE_MPU_SHARED_ENTRIES regions, and one grant proves only
|
||||
that the first entry works, so the area holds one granule per entry -- plus
|
||||
one more that the manager NEVER grants, sandwiched between two that it does.
|
||||
A limit register masked the wrong way, or a base off by a granule, leaks into
|
||||
that gap from one side or the other, and a module that can write it is a
|
||||
module whose grant covered more than was asked for.
|
||||
|
||||
FVP_MODULE_STATUS_SIZE is the size of ONE granule, which is the length of
|
||||
each individual grant; the area is FVP_MODULE_STATUS_GRANULES of them.
|
||||
|
||||
Fixed addresses on both sides, and checked at run time rather than trusted:
|
||||
the manager grants the regions from the linker's symbol and refuses to
|
||||
continue if that symbol is not this address. */
|
||||
|
||||
#define FVP_MODULE_STATUS_BASE 0x003F0000
|
||||
#define FVP_MODULE_STATUS_SIZE 0x40 /* one MPU granule */
|
||||
#define FVP_MODULE_STATUS_GRANULES 6 /* five granted, one not */
|
||||
#define FVP_MODULE_STATUS_UNGRANTED 2 /* the one never granted */
|
||||
|
||||
#ifndef __ASSEMBLER__
|
||||
|
||||
/* 32-bit device register access. */
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -149,6 +149,12 @@ cntfrq_at_el2: .word 0
|
||||
hmpuir_at_el2: .word 0
|
||||
.global probe_stage
|
||||
probe_stage: .word 0
|
||||
#ifdef TXM_MODULE_MANAGER
|
||||
.extern __tx_module_svc_interrupt
|
||||
.extern _txm_module_manager_data_abort
|
||||
.extern _txm_module_manager_prefetch_abort
|
||||
#endif
|
||||
|
||||
.global fault_expected
|
||||
fault_expected: .word 0
|
||||
.global fault_taken
|
||||
@@ -197,7 +203,11 @@ el2_vectors:
|
||||
el1_vectors:
|
||||
b fault_el1_reset /* 0x00 */
|
||||
b fault_el1_undef /* 0x04 */
|
||||
#ifdef TXM_MODULE_MANAGER
|
||||
b __tx_module_svc_interrupt /* 0x08 module kernel boundary */
|
||||
#else
|
||||
b fault_el1_svc /* 0x08 */
|
||||
#endif
|
||||
b fault_el1_pabt /* 0x0C */
|
||||
b fault_el1_dabt /* 0x10 */
|
||||
b fault_el1_reserved /* 0x14 */
|
||||
@@ -891,6 +901,26 @@ fault_el1_svc:
|
||||
mov r3, lr
|
||||
FAULT_TAIL 0x48
|
||||
fault_el1_pabt:
|
||||
#ifdef TXM_MODULE_MANAGER
|
||||
/* A fault from User mode is a module reaching outside its regions, and it
|
||||
belongs to the module manager, which will record it and terminate the
|
||||
thread. A fault from a privileged mode continues on the recoverable path
|
||||
below, which the boot probes depend on.
|
||||
|
||||
r0 is pushed and popped around the test rather than simply used. The code
|
||||
below clobbers r0 through r2 without saving them, which is tolerable for a
|
||||
probe that provokes its own fault, but the module capture records the
|
||||
faulting registers and would record ours instead. LDM does not affect the
|
||||
flags, so the comparison still holds after the pop. */
|
||||
|
||||
stmdb sp!, {r0}
|
||||
mrs r0, spsr
|
||||
and r0, r0, #0x1F
|
||||
cmp r0, #0x10 /* User mode? */
|
||||
ldmia sp!, {r0}
|
||||
beq _txm_module_manager_prefetch_abort
|
||||
#endif
|
||||
|
||||
/* Recoverable when a test has armed fault_expected_pabt.
|
||||
*
|
||||
* Recovery differs from the data-abort case in kind, not degree. There,
|
||||
@@ -932,6 +962,26 @@ fault_el1_pabt_fatal:
|
||||
mrc p15, 0, r3, c6, c0, 2 /* IFAR */
|
||||
FAULT_TAIL 0x4C
|
||||
fault_el1_dabt:
|
||||
#ifdef TXM_MODULE_MANAGER
|
||||
/* A fault from User mode is a module reaching outside its regions, and it
|
||||
belongs to the module manager, which will record it and terminate the
|
||||
thread. A fault from a privileged mode continues on the recoverable path
|
||||
below, which the boot probes depend on.
|
||||
|
||||
r0 is pushed and popped around the test rather than simply used. The code
|
||||
below clobbers r0 through r2 without saving them, which is tolerable for a
|
||||
probe that provokes its own fault, but the module capture records the
|
||||
faulting registers and would record ours instead. LDM does not affect the
|
||||
flags, so the comparison still holds after the pop. */
|
||||
|
||||
stmdb sp!, {r0}
|
||||
mrs r0, spsr
|
||||
and r0, r0, #0x1F
|
||||
cmp r0, #0x10 /* User mode? */
|
||||
ldmia sp!, {r0}
|
||||
beq _txm_module_manager_data_abort
|
||||
#endif
|
||||
|
||||
/* Recoverable when a test has armed fault_expected. The protection map is
|
||||
only meaningful if a violation actually faults, so the test provokes one
|
||||
deliberately and this path lets it continue: record the syndrome and
|
||||
|
||||
@@ -54,7 +54,10 @@ MEMORY
|
||||
Used for data that wants deterministic access without depending on a
|
||||
cache -- an enabled TCM is never cached. */
|
||||
BTCM (rw) : ORIGIN = 0x30100000, LENGTH = 0x00004000 /* 16 KB */
|
||||
DATA (rwx) : ORIGIN = 0x31780000, LENGTH = 0x00080000 /* 512 KB: DRAM0 + DRAM1, both full core speed */
|
||||
DATA (rwx) : ORIGIN = 0x31780000, LENGTH = 0x00070000 /* 448 KB: DRAM0 + DRAM1 below the module area */
|
||||
/* Module memory, uncovered by any kernel MPU region on purpose. See
|
||||
S32Z_MODULE_AREA_BASE in platform.h. */
|
||||
MODULE (rwx) : ORIGIN = 0x317F0000, LENGTH = 0x00010000 /* 64 KB */
|
||||
}
|
||||
|
||||
ENTRY(_start)
|
||||
|
||||
@@ -302,8 +302,12 @@ static void build_table(void)
|
||||
Reference Manual and confirmed on the board. */
|
||||
|
||||
mpu_regions[1].mpu_region_base = S32Z_DATA_SRAM_BASE;
|
||||
/* Stops before the module area at the top of DRAM1. A module's memory must
|
||||
not be covered by a kernel region, or every thread could reach it and the
|
||||
manager's own regions would be decoration. */
|
||||
|
||||
mpu_regions[1].mpu_region_limit = S32Z_DATA_SRAM_BASE
|
||||
+ S32Z_DATA_SRAM_SIZE - 1UL;
|
||||
+ S32Z_DATA_SRAM_SHARED_SIZE - 1UL;
|
||||
mpu_regions[1].mpu_region_ap = MPU_AP_RW_EL1;
|
||||
mpu_regions[1].mpu_region_execute_never = 1U;
|
||||
mpu_regions[1].mpu_region_shareability = MPU_SH_NON;
|
||||
@@ -453,6 +457,73 @@ static void program_region(unsigned int index, const MPU_REGION *region_ptr)
|
||||
}
|
||||
|
||||
|
||||
/**************************************************************************/
|
||||
/* mpu_module_load_window_open / mpu_module_load_window_close */
|
||||
/* */
|
||||
/* A window over the module area, for the manager to load through. */
|
||||
/* */
|
||||
/* No kernel region covers the module area, which is what stops every */
|
||||
/* thread from reaching a module's memory -- but the manager has to read */
|
||||
/* the preamble and write the module's data to load it at all. Without */
|
||||
/* this the load faulted on its first read of the image, and because a */
|
||||
/* privileged data abort ends in a handler that only spins, that looked */
|
||||
/* exactly like the load hanging. */
|
||||
/* */
|
||||
/* Opened around the load and closed straight after, rather than left in */
|
||||
/* place, because PMSAv8-R has no region priority: if this region were */
|
||||
/* still enabled when a module thread ran it would overlap the module's */
|
||||
/* own regions, and an access hitting both takes a translation fault */
|
||||
/* (TRM 8.1). */
|
||||
/* Closing it before any module thread starts is what keeps the two from */
|
||||
/* ever being enabled together. */
|
||||
/* */
|
||||
/* Region 16, above both the kernel's 0-7 and the eight the manager */
|
||||
/* hands to a module, so neither the scheduler's per-thread region load */
|
||||
/* nor the boot table can disturb it. MPUIR reports 20 EL1 regions on */
|
||||
/* this part. EL1 read/write with no EL0 access: the manager can load */
|
||||
/* through it and a module cannot use it to reach anything. */
|
||||
/**************************************************************************/
|
||||
|
||||
unsigned long mpu_module_window_prbar;
|
||||
unsigned long mpu_module_window_prlar;
|
||||
|
||||
|
||||
void mpu_module_window_init(void)
|
||||
{
|
||||
MPU_REGION window;
|
||||
|
||||
window.mpu_region_base = S32Z_MODULE_AREA_BASE;
|
||||
window.mpu_region_limit = S32Z_MODULE_AREA_BASE
|
||||
+ S32Z_MODULE_AREA_SIZE - 1UL;
|
||||
window.mpu_region_ap = MPU_AP_RW_EL1;
|
||||
window.mpu_region_execute_never = 1U;
|
||||
window.mpu_region_shareability = MPU_SH_NON;
|
||||
window.mpu_region_attr_index = MPU_ATTR_NORMAL_WB;
|
||||
window.mpu_region_name = "module window RW NX EL1";
|
||||
|
||||
/* Published for the scheduler, which turns this region on and off on every
|
||||
dispatch and has no business computing register layouts in assembly. */
|
||||
|
||||
mpu_module_window_prbar = (window.mpu_region_base & 0xFFFFFFC0UL)
|
||||
| (((unsigned long) window.mpu_region_shareability & 0x3UL) << 3)
|
||||
| (((unsigned long) window.mpu_region_ap & 0x3UL) << 1)
|
||||
| ((unsigned long) window.mpu_region_execute_never & 0x1UL);
|
||||
|
||||
mpu_module_window_prlar = (window.mpu_region_limit & 0xFFFFFFC0UL)
|
||||
| (((unsigned long) window.mpu_region_attr_index & 0x7UL) << 1)
|
||||
| 1UL;
|
||||
|
||||
/* Enabled now, because everything running before the first module thread is
|
||||
privileged code that may need to reach module memory -- the manager loads
|
||||
a module from a kernel thread. */
|
||||
|
||||
program_region(MPU_MODULE_LOAD_REGION, &window);
|
||||
|
||||
data_sync_barrier();
|
||||
instruction_barrier();
|
||||
}
|
||||
|
||||
|
||||
/**************************************************************************/
|
||||
/* mpu_init */
|
||||
/**************************************************************************/
|
||||
@@ -499,6 +570,16 @@ unsigned int mpu_init(void)
|
||||
write_prlar(0UL);
|
||||
}
|
||||
|
||||
#ifdef TXM_MODULE_MANAGER
|
||||
|
||||
/* After the loop above, which would otherwise disable it again: the module
|
||||
window lives above the regions this table uses, so it counts as unused
|
||||
here. */
|
||||
|
||||
mpu_module_window_init();
|
||||
|
||||
#endif
|
||||
|
||||
MARK(0x40);
|
||||
data_sync_barrier();
|
||||
|
||||
|
||||
@@ -115,4 +115,28 @@ const MPU_REGION *mpu_region_table(unsigned int *count_ptr);
|
||||
void mpu_read_region(unsigned int index, unsigned long *prbar_ptr,
|
||||
unsigned long *prlar_ptr);
|
||||
|
||||
/* Region index for the manager's load window over the module area. Above the
|
||||
kernel's 0-7 and above the eight the manager hands to a module, so neither
|
||||
the boot table nor the scheduler's per-thread region load can disturb it. */
|
||||
|
||||
#define MPU_MODULE_LOAD_REGION 16U
|
||||
|
||||
/* The module area window, which is what lets privileged code reach module
|
||||
memory at all -- no other kernel region covers it.
|
||||
|
||||
PMSAv8-R has no region priority, so this must never be enabled at the same
|
||||
time as the regions a module is given, which cover the same memory. That is
|
||||
guaranteed by who owns it rather than by careful calling: the scheduler turns
|
||||
this region on for every thread that is not a module thread and off for every
|
||||
thread that is, so the window is enabled exactly when no module regions are
|
||||
loaded. The two register words are published for it below.
|
||||
|
||||
Enabled here at boot, because everything before the first module thread is
|
||||
privileged code that may need to reach module memory. */
|
||||
|
||||
void mpu_module_window_init(void);
|
||||
|
||||
extern unsigned long mpu_module_window_prbar;
|
||||
extern unsigned long mpu_module_window_prlar;
|
||||
|
||||
#endif /* MPU_H */
|
||||
|
||||
@@ -130,6 +130,61 @@
|
||||
#define S32Z_DRAM0_SIZE 0x00040000UL
|
||||
#define S32Z_DRAM1_BASE 0x317C0000UL
|
||||
#define S32Z_DRAM1_SIZE 0x00040000UL
|
||||
/* Memory for loadable modules: the top 64 KB of DRAM1, deliberately left out of
|
||||
the broad data region in mpu.c.
|
||||
|
||||
It has to be outside every region the board support package programs. The
|
||||
manager gives a module its own regions, and if the kernel's map already covered
|
||||
that memory then every thread could reach the module and back -- the isolation
|
||||
would be nominal. Carving a hole is the only way to make it real.
|
||||
|
||||
DRAM1 rather than DRAM2 because it runs at full core speed (S32Z2 RM 6.3.6),
|
||||
and because code and data can be contiguous here. Putting module code in the
|
||||
code RAM region instead would have been the obvious choice and does not work:
|
||||
that region is read-only, so a module's data would have to live somewhere else
|
||||
and the module would straddle two carve-outs.
|
||||
|
||||
64 KB is arbitrary but not accidental: it leaves 448 KB of full-speed RAM for
|
||||
the kernel's data, stacks and bss, which currently use about 14 KB. */
|
||||
|
||||
#define S32Z_MODULE_AREA_BASE 0x317F0000UL
|
||||
#define S32Z_MODULE_AREA_SIZE 0x00010000UL /* 64 KB */
|
||||
|
||||
/* The shared granules the sample module reports its progress through, at the
|
||||
base of the module area.
|
||||
|
||||
A module cannot print -- the console belongs to the board support package and
|
||||
lies outside every region a module owns -- so the manager grants it shared
|
||||
regions and reads them back. On this board a GDB harness reads the module's
|
||||
own progress variable as well, out of the data area the manager allocated;
|
||||
these granules are the channel that needs no debugger, and having both means
|
||||
the two agree or the run says so.
|
||||
|
||||
Six granules rather than one, because a module may be granted
|
||||
TXM_MODULE_MPU_SHARED_ENTRIES regions and one grant only ever exercises the
|
||||
first of them. Five are granted, one granule per entry, and
|
||||
S32Z_MODULE_STATUS_UNGRANTED -- index 2, so a granted granule sits on either
|
||||
side of it -- is never granted to anything. A limit register masked the wrong
|
||||
way, or a base off by a granule, extends a region into that gap from one side
|
||||
or the other, and a module that can write it was given more than was asked
|
||||
for.
|
||||
|
||||
S32Z_MODULE_STATUS_SIZE is the size of ONE granule, which is also the length
|
||||
of each individual grant; the area is GRANULES of them. Fixed addresses on
|
||||
both sides, checked at run time against the linker's symbol rather than
|
||||
trusted. */
|
||||
|
||||
#define S32Z_MODULE_STATUS_BASE 0x317F0000UL
|
||||
#define S32Z_MODULE_STATUS_SIZE 0x40UL /* one MPU granule */
|
||||
#define S32Z_MODULE_STATUS_GRANULES 6UL /* five granted, one not */
|
||||
#define S32Z_MODULE_STATUS_UNGRANTED 2UL /* the one never granted */
|
||||
|
||||
/* What is left of the full-speed pair for the kernel itself. link.lds sizes its
|
||||
DATA region from this, so the linker cannot place kernel data in the module
|
||||
area by accident. */
|
||||
|
||||
#define S32Z_DATA_SRAM_SHARED_SIZE (S32Z_MODULE_AREA_BASE - S32Z_DRAM0_BASE)
|
||||
|
||||
/* The top 8 KB of DRAM2 is deliberately left out of the broad data region in
|
||||
mpu.c and mapped one window at a time, per thread, instead. Isolation is only
|
||||
meaningful in memory that no other region already grants access to, and every
|
||||
|
||||
@@ -66,16 +66,59 @@ static unsigned int max_cycles;
|
||||
/* CP15 accessors. */
|
||||
/**************************************************************************/
|
||||
|
||||
/* Direct per-region access rather than PRSELR.
|
||||
|
||||
The Cortex-R52 TRM 8.4 encodes the region number into CRm and opc2: CRm is
|
||||
0b1rrr where rrr is region_number[3:1], with opc2 0 and 1 for an even region
|
||||
and 4 and 5 for an odd one. Regions 0 through 15 take CP15 op1 0 and regions
|
||||
16 through 24 take op1 1. PRBAR and PRLAR without a number are the indirect
|
||||
view selected by PRSELR.
|
||||
|
||||
Region 8 is therefore op1 0, CRm c12, opc2 0 and 1. Using it removes the
|
||||
PRSELR write and, more importantly, the ISB that has to follow PRSELR before
|
||||
the region registers can be written.
|
||||
|
||||
Measured on this part: 542 to 604 cycles through PRSELR against 434 to 470
|
||||
direct, for the same region and the same isolation result. The saving matters
|
||||
most where several regions are programmed at once, which is what a module
|
||||
switch does -- there the PRSELR route pays an ISB per region while the direct
|
||||
route pays one barrier pair for the whole block.
|
||||
|
||||
The TRM disagrees with itself about the range, and the next reader should not
|
||||
have to rediscover that. The register descriptions at 3.3.85 and 3.3.86 say
|
||||
"Direct access is provided to PRBAR0 to PRBAR15" and list only the op1 0
|
||||
form; 8.4 and Table 8-9 list both forms. Table 8-9 also prints opc2 0 for
|
||||
the even limit registers where the prose of 8.4 says 1. What is written here
|
||||
uses op1 0 only, which is the half of the manual this part has been measured
|
||||
against. */
|
||||
|
||||
/* Kept for regions 16 and above. Those have direct op1 1 encodings in TRM 8.4,
|
||||
but only the selector route is proven on this part, and nothing here needs a
|
||||
region that high while the per-thread window lives at region 8. */
|
||||
|
||||
__attribute__((unused))
|
||||
static void write_prselr(unsigned long value)
|
||||
{
|
||||
__asm__ volatile("mcr p15, 0, %0, c6, c2, 1" : : "r"(value) : "memory");
|
||||
}
|
||||
|
||||
static void write_prbar8_direct(unsigned long value)
|
||||
{
|
||||
__asm__ volatile("mcr p15, 0, %0, c6, c12, 0" : : "r"(value) : "memory");
|
||||
}
|
||||
|
||||
static void write_prlar8_direct(unsigned long value)
|
||||
{
|
||||
__asm__ volatile("mcr p15, 0, %0, c6, c12, 1" : : "r"(value) : "memory");
|
||||
}
|
||||
|
||||
__attribute__((unused))
|
||||
static void write_prbar(unsigned long value)
|
||||
{
|
||||
__asm__ volatile("mcr p15, 0, %0, c6, c3, 0" : : "r"(value) : "memory");
|
||||
}
|
||||
|
||||
__attribute__((unused))
|
||||
static void write_prlar(unsigned long value)
|
||||
{
|
||||
__asm__ volatile("mcr p15, 0, %0, c6, c3, 1" : : "r"(value) : "memory");
|
||||
@@ -154,9 +197,6 @@ void thread_mpu_activate(TX_THREAD *thread_ptr)
|
||||
}
|
||||
}
|
||||
|
||||
write_prselr(THREAD_MPU_REGION);
|
||||
__asm__ volatile("isb" ::: "memory");
|
||||
|
||||
if (base == 0UL)
|
||||
{
|
||||
/* No window for this thread: disable the region rather than leaving the
|
||||
@@ -164,16 +204,16 @@ void thread_mpu_activate(TX_THREAD *thread_ptr)
|
||||
protection silently becomes no protection -- the last thread to run
|
||||
would leave its window open to whatever ran next. */
|
||||
|
||||
write_prlar(0UL);
|
||||
write_prlar8_direct(0UL);
|
||||
}
|
||||
else
|
||||
{
|
||||
write_prbar((base & ~0x3FUL)
|
||||
write_prbar8_direct((base & ~0x3FUL)
|
||||
| ((unsigned long) MPU_SH_NON << 3)
|
||||
| ((unsigned long) MPU_AP_RW_EL1 << 1)
|
||||
| 1UL); /* XN: data only */
|
||||
|
||||
write_prlar(((base + S32Z_THREAD_WINDOW_SIZE - 1UL) & ~0x3FUL)
|
||||
write_prlar8_direct(((base + S32Z_THREAD_WINDOW_SIZE - 1UL) & ~0x3FUL)
|
||||
| ((unsigned long) MPU_ATTR_NORMAL_WB << 1)
|
||||
| 1UL); /* EN */
|
||||
}
|
||||
|
||||
@@ -255,17 +255,72 @@ typedef unsigned short USHORT;
|
||||
#define TX_TIMER_DELETE_EXTENSION(timer_ptr)
|
||||
|
||||
|
||||
/* Determine if the ARM architecture has the CLZ instruction. This is available on
|
||||
architectures v5 and above. If available, redefine the macro for calculating the
|
||||
lowest bit set. */
|
||||
/* Determine whether this core has the CLZ instruction and this compiler will
|
||||
admit to it, and if so replace the portable lowest-set-bit search with it.
|
||||
|
||||
#if __TARGET_ARCH_ARM > 4
|
||||
The guard is not upstream's. Upstream asks __TARGET_ARCH_ARM > 4, which is an
|
||||
Arm Compiler 5 predefine. GCC does not define it -- it predefines the ACLE
|
||||
macros __ARM_ARCH and __ARM_FEATURE_CLZ instead -- so under GCC the test reads
|
||||
0 > 4, this whole block is dropped and tx_thread.h's portable loop runs on a
|
||||
core that has had the instruction since Armv5. Measured with
|
||||
arm-none-eabi-gcc 14.3 on 20 Aug 2026: zero CLZ instructions in the built
|
||||
scheduler objects.
|
||||
|
||||
That was not a dormant path. Half the TX_LOWEST_SET_BIT_CALCULATE call sites
|
||||
in tx_thread_suspend.c and tx_thread_system_suspend.c sit OUTSIDE the
|
||||
TX_MAX_PRIORITIES > 32 guards, so the portable loop was running in the
|
||||
scheduler's priority search in the default 32-priority configuration, which is
|
||||
the one every R52 build uses.
|
||||
|
||||
__ARM_FEATURE_CLZ is the ACLE answer to the question actually being asked, and
|
||||
the compiler defines it exactly when the architecture has the instruction, so
|
||||
a core without CLZ is excluded by construction rather than by an architecture
|
||||
number. Arm Compiler 5's spelling is kept beside it, now wrapped in defined()
|
||||
so the test no longer leans on an undefined identifier evaluating to zero --
|
||||
which is what -Wundef reports and how this was found.
|
||||
|
||||
The __thumb__ guard stays, and it is load-bearing rather than inherited
|
||||
caution: __ARM_FEATURE_CLZ describes the ARCHITECTURE, not the instruction
|
||||
set. Checked on 20 Aug 2026 -- GCC defines it for -mthumb -march=armv5te,
|
||||
where Thumb-1 has no CLZ at all and this asm would fail to assemble. A Thumb
|
||||
build therefore keeps the portable loop on purpose. (On this core it is moot:
|
||||
the R52 toolchain file builds -marm.)
|
||||
|
||||
Two deliberate deviations, per AGENTS.md:
|
||||
|
||||
- Rule 1.2, language extensions. Inline assembly is the entire point of the
|
||||
macro; there is no conforming way to reach CLZ. Spelled __asm__ and not
|
||||
asm, because the asm keyword is rejected under -std=c99 -- verified, it is
|
||||
an "'asm' undeclared" error -- and AGENTS.md requires C99 compatibility.
|
||||
|
||||
- Rule 10.1 / 10.3 on the isolation step, which is why it is respelled.
|
||||
Upstream isolates the lowest set bit with (ULONG) (-((LONG) m)): that
|
||||
converts an unsigned map to signed and negates it, which is undefined for
|
||||
the one input whose top bit is set. (~(m)) + 1 is the same value in
|
||||
well-defined unsigned arithmetic, and it is character-for-character what
|
||||
tx_thread.h's portable version uses -- so the two implementations now
|
||||
visibly compute the same thing instead of merely agreeing.
|
||||
|
||||
Rule 20.7 is a straight fix rather than a deviation: upstream leaves m and b
|
||||
unparenthesised in the expansion.
|
||||
|
||||
PRECONDITION: m must be non-zero, and the two implementations DISAGREE when it
|
||||
is not. CLZ(0) is 32, so this yields 31 - 32; the portable loop yields 0.
|
||||
All twelve call sites in common/src reach the macro only on a map already
|
||||
tested against zero -- every one checked on 20 Aug 2026 -- so the divergence is
|
||||
unreachable today. It is written down because a new call site is exactly how
|
||||
it would stop being unreachable, and demo_clz.c pins both answers so that
|
||||
changing this has to be a decision. */
|
||||
|
||||
#if defined(__ARM_FEATURE_CLZ) || (defined(__TARGET_ARCH_ARM) && (__TARGET_ARCH_ARM > 4))
|
||||
|
||||
#ifndef __thumb__
|
||||
|
||||
#define TX_LOWEST_SET_BIT_CALCULATE(m, b) m = m & ((ULONG) (-((LONG) m))); \
|
||||
asm volatile (" CLZ %0,%1 ": "=r" (b) : "r" (m) ); \
|
||||
b = 31 - b;
|
||||
#define TX_LOWEST_SET_BIT_CALCULATE(m, b) \
|
||||
(m) = (m) & ((~(m)) + ((ULONG) 1)); \
|
||||
__asm__ volatile (" CLZ %0,%1 " : "=r" (b) : "r" (m)); \
|
||||
(b) = 31 - (b);
|
||||
|
||||
#endif
|
||||
#endif
|
||||
|
||||
|
||||
@@ -51,7 +51,7 @@ baseline selects the soft ABI rather than removing the FPU.
|
||||
-DCMAKE_TOOLCHAIN_FILE=cmake/cortex_r52.cmake \
|
||||
-DTX_R52_BUILD_FVP_EXAMPLE=ON .
|
||||
ninja -C build_r52 boot_check.elf demo_m2.elf demo_m3.elf \
|
||||
demo_threadx.elf demo_mpu.elf
|
||||
demo_threadx.elf demo_mpu.elf demo_clz.elf
|
||||
ctest --test-dir build_r52
|
||||
|
||||
The images target the free Armv8-R AEM FVP (FVP_BaseR_AEMv8R):
|
||||
@@ -61,6 +61,7 @@ The images target the free Armv8-R AEM FVP (FVP_BaseR_AEMv8R):
|
||||
demo_m3.elf generic timer tick, GICv3 and preemption
|
||||
demo_threadx.elf the standard eight-thread demo plus verification
|
||||
demo_mpu.elf PMSAv8-R protection and cache enable
|
||||
demo_clz.elf the CLZ lowest-set-bit priority search
|
||||
demo_m5.elf lazy VFP context save (needs TX_R52_ENABLE_VFP)
|
||||
|
||||
Each image reports its own result and terminates the model through the
|
||||
@@ -135,6 +136,20 @@ and the thread stack pointer and run counter offsets, at compile time: a
|
||||
layout change becomes a build failure instead of silent corruption.
|
||||
|
||||
|
||||
Also worth knowing: this port replaces tx_thread.h's portable lowest-set-bit
|
||||
search with the CLZ instruction, which is what the scheduler uses to pick the
|
||||
next thread to run. Upstream gates that on __TARGET_ARCH_ARM, an Arm Compiler
|
||||
5 predefine that GCC does not define, so the optimisation had never once been
|
||||
compiled in under GCC; the guard here asks __ARM_FEATURE_CLZ instead. It is
|
||||
worth 40% of the priority search's code size (2188 to 1316 bytes in
|
||||
tx_thread_system_suspend.o) and it applies in the default 32-priority
|
||||
configuration, not only above 32. A Thumb build deliberately keeps the
|
||||
portable loop, because __ARM_FEATURE_CLZ describes the architecture rather
|
||||
than the instruction set and Thumb-1 has no CLZ. demo_clz.elf is the
|
||||
regression test, and it fails to build rather than silently testing the
|
||||
portable loop if the CLZ path is ever disabled again.
|
||||
|
||||
|
||||
7. Memory Protection
|
||||
|
||||
PMSAv8-R regions are described by a table rather than a sequence of
|
||||
@@ -145,13 +160,24 @@ disables the regions it does not use.
|
||||
|
||||
Two cautions for anyone reusing this code on silicon:
|
||||
|
||||
- The PRBAR.AP encoding used here was calibrated against the hardware.
|
||||
The low bit is read-only and the high bit grants EL0 access, which is
|
||||
the reverse of the widely-published Armv8-R AArch64 macro set. Getting
|
||||
it wrong produces regions that report as read-only and accept writes,
|
||||
because region coverage is still enforced: an unmapped address faults
|
||||
while a "read-only" region does not. Re-calibrate before trusting
|
||||
these values on a different implementation.
|
||||
- The PRBAR.AP encoding is the standard Armv8-R one, as published:
|
||||
AP[2] selects read-only and AP[1] grants EL0 access. No calibration is
|
||||
needed, and an earlier revision of this file said otherwise -- it
|
||||
claimed the two bits were reversed, on the strength of a real
|
||||
measurement with a wrong cause. program_region() had been shifting
|
||||
every PRBAR field one bit too far left, so the AP value's low bit
|
||||
landed in the true AP[2] and writes faulted exactly when that bit was
|
||||
set, which looks precisely like a reversed encoding. The shift is
|
||||
fixed; see the comment on the MPU_AP_* macros in mpu.h for the full
|
||||
calibration table and what it actually proved.
|
||||
|
||||
The reason it took a silicon run to notice: region coverage is enforced
|
||||
even when permissions are not what you asked for, so an unmapped
|
||||
address faults while a "read-only" region quietly accepts writes. A
|
||||
configuration-only review cannot tell the two apart -- provoke a real
|
||||
fault instead. Verified on S32Z280 silicon: a write to an RO region
|
||||
faults and execution from an XN region takes a prefetch abort, the
|
||||
latter never having worked under the old shift.
|
||||
|
||||
- The code and data regions must not share a 64-byte granule. The
|
||||
linker script separates them with ".data ALIGN(64) :" on the output
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,69 @@
|
||||
@/***************************************************************************
|
||||
@ * 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
|
||||
@ **************************************************************************/
|
||||
@
|
||||
@/**************************************************************************/
|
||||
@/* */
|
||||
@/* MODULE MANAGER RELEASE */
|
||||
@/* */
|
||||
@/* module_blob.S Cortex-R52/GNU */
|
||||
@/* 6.5.2 */
|
||||
@/* AUTHOR */
|
||||
@/* */
|
||||
@/* Frédéric Desbiens, Eclipse Foundation */
|
||||
@/* */
|
||||
@/* DESCRIPTION */
|
||||
@/* */
|
||||
@/* Carries the demonstration module into the manager image as data. */
|
||||
@/* */
|
||||
@/* The module is built as its own link unit and objcopied to a raw */
|
||||
@/* binary, which is included here verbatim. It has to be a separate */
|
||||
@/* link: the module library defines shims named after the ThreadX API */
|
||||
@/* entry points the kernel also defines, and in one link the shims win */
|
||||
@/* -- objects beat archive members -- so the manager's own service calls */
|
||||
@/* end up trapping into the module. */
|
||||
@/* */
|
||||
@/* Included as bytes rather than linked as objects, so the module's */
|
||||
@/* symbols never enter the manager's link at all. Nothing here is */
|
||||
@/* called: the manager finds the preamble at the start of the image and */
|
||||
@/* reaches everything else through that. */
|
||||
@/* */
|
||||
@/* A separate copy from the S32Z280 one, and identical to it: the two */
|
||||
@/* module examples are deliberately independent builds, and each names */
|
||||
@/* its own raw image through its own assembler include path. */
|
||||
@/* */
|
||||
@/**************************************************************************/
|
||||
|
||||
.section .module_blob, "a"
|
||||
|
||||
@ 64-byte aligned, the PMSAv8-R granule, so the image starts where a region
|
||||
@ can start. The linker script places this section at the module area base,
|
||||
@ which is the address the module itself was linked for.
|
||||
|
||||
.align 6
|
||||
|
||||
.global __demo_module_image
|
||||
.global __demo_module_image_end
|
||||
|
||||
__demo_module_image:
|
||||
|
||||
@ demo_module.bin is produced by the fvp_demo_module.elf target. The
|
||||
@ assembler finds it through an include path pointing at the build directory,
|
||||
@ set in CMakeLists.txt -- .incbin searches the -I paths, not the source tree.
|
||||
|
||||
.incbin "demo_module.bin"
|
||||
|
||||
__demo_module_image_end:
|
||||
|
||||
.align 6
|
||||
File diff suppressed because it is too large
Load Diff
+1624
File diff suppressed because it is too large
Load Diff
+1086
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,151 @@
|
||||
@/***************************************************************************
|
||||
@ * 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
|
||||
@ **************************************************************************/
|
||||
@
|
||||
@/**************************************************************************/
|
||||
@/* */
|
||||
@/* MODULE PREAMBLE RELEASE */
|
||||
@/* */
|
||||
@/* txm_module_preamble.S Cortex-R52/GNU */
|
||||
@/* 6.5.2 */
|
||||
@/* AUTHOR */
|
||||
@/* */
|
||||
@/* Frédéric Desbiens, Eclipse Foundation */
|
||||
@/* */
|
||||
@/* DESCRIPTION */
|
||||
@/* */
|
||||
@/* The header the module manager reads before it will load a module. */
|
||||
@/* */
|
||||
@/* It must be the first thing in the module image, which is what the */
|
||||
@/* linker script arranges, and every entry point in it is an offset */
|
||||
@/* from the start of the preamble rather than an address. A module is */
|
||||
@/* position independent: the manager decides where it lands, so the */
|
||||
@/* module cannot know its own addresses at link time. */
|
||||
@/* */
|
||||
@/* Code and data sizes come from the linker script rather than being */
|
||||
@/* written in by hand. The Cortex-R4 preamble carries literal numbers */
|
||||
@/* for them, which is a standing invitation to grow a module past its */
|
||||
@/* declared size and have the manager map less memory than it uses -- */
|
||||
@/* a fault in a module that did nothing wrong, whose cause is a */
|
||||
@/* constant in a file nobody thought to change. */
|
||||
@/* */
|
||||
@/**************************************************************************/
|
||||
|
||||
.syntax unified
|
||||
.arm
|
||||
|
||||
.global __txm_module_preamble
|
||||
|
||||
.extern _txm_module_thread_shell_entry
|
||||
.extern _txm_module_callback_request_thread_entry
|
||||
.extern demo_module_start
|
||||
|
||||
@ Supplied by the module's linker script.
|
||||
|
||||
.extern __txm_module_code_size
|
||||
.extern __txm_module_data_size
|
||||
|
||||
@ Properties. The compiler field tells the manager which set of entry-point
|
||||
@ adjustments to apply, and the option bits say what the module is asking for:
|
||||
@ user mode and memory protection, which together are the point of this port.
|
||||
@
|
||||
@ 0x02000000 TXM_MODULE_GNU_COMPILER
|
||||
@ 0x00000001 TXM_MODULE_USER_MODE
|
||||
@ 0x00000002 TXM_MODULE_MEMORY_PROTECTION
|
||||
@
|
||||
@ Both option bits are REQUIRED here, unlike on every other module port, and a
|
||||
@ preamble that carries either alone is refused by the loader with
|
||||
@ TXM_MODULE_INVALID_PROPERTIES. On PMSAv8-R that is not a policy: the port
|
||||
@ programs a module's regions only when both bits are set, the scheduler closes
|
||||
@ the kernel's window over module memory before it dispatches a module thread,
|
||||
@ and there is no background region to fall back on -- so a module that asked
|
||||
@ for user mode without protection would have no mapping at all rather than a
|
||||
@ weaker one. See TXM_MODULE_MANAGER_REQUIRED_OPTIONS in txm_module_port.h.
|
||||
@
|
||||
@ 0x00000004 TXM_MODULE_SHARED_EXTERNAL_MEMORY_ACCESS
|
||||
@
|
||||
@ is supported and optional. A module that does not ask for it simply never has
|
||||
@ a shared region granted to it.
|
||||
|
||||
.equ MODULE_PROPERTIES, 0x02000003
|
||||
|
||||
.section .txm_module_preamble, "a"
|
||||
.align 6
|
||||
|
||||
__txm_module_preamble:
|
||||
|
||||
.word 0x4D4F4455 @ Module ID, "MODU"
|
||||
.word 0x6 @ Major version
|
||||
.word 0x1 @ Minor version
|
||||
.word 32 @ Preamble size, 32-bit words
|
||||
.word 0x52520001 @ Application-defined ID
|
||||
.word MODULE_PROPERTIES @ Properties, see above
|
||||
|
||||
@ Entry points, as offsets from the preamble.
|
||||
|
||||
@ Entry points are stored relative to the word that holds them, not to the
|
||||
@ start of the preamble. That is what the manager expects: it recovers the
|
||||
@ offset from the module base by adding the field's own byte offset back --
|
||||
@ TXM_MODULE_GNU_SHELL_ADJUST 24, START 28, STOP 32, CALLBACK 44, which are
|
||||
@ exactly the offsets of the four words below. Storing these relative to
|
||||
@ __txm_module_preamble instead counts that offset twice, and the module is
|
||||
@ then entered that many bytes into its shell entry: past the prologue, with
|
||||
@ the arguments never saved and the frame pointer never set up, so the first
|
||||
@ dereference goes through a register the stack build had zeroed. On silicon
|
||||
@ that read faulted at 0x1C, which is offset 0x1C from a null r3.
|
||||
@
|
||||
@ Every other GNU module port writes these the same way; cortex_m33's
|
||||
@ preamble, which this port was seeded from, spells it "symbol - . - 0".
|
||||
|
||||
.word _txm_module_thread_shell_entry - .
|
||||
.word demo_module_start - .
|
||||
.word 0 @ No stop thread
|
||||
.word 1 @ Start/stop thread priority
|
||||
.word 1024 @ Start/stop thread stack size
|
||||
.word _txm_module_callback_request_thread_entry - .
|
||||
.word 1 @ Callback thread priority
|
||||
.word 1024 @ Callback thread stack size
|
||||
|
||||
@ Sizes, from the linker script. The manager rounds both up to the 64-byte MPU
|
||||
@ granule and maps exactly this much; anything the module touches beyond it
|
||||
@ faults, which is the intended behaviour and not a bug to work around by
|
||||
@ inflating these numbers.
|
||||
|
||||
.word __txm_module_code_size
|
||||
.word __txm_module_data_size
|
||||
|
||||
.word 0 @ Reserved 0
|
||||
.word 0 @ Reserved 1
|
||||
.word 0 @ Reserved 2
|
||||
.word 0 @ Reserved 3
|
||||
.word 0 @ Reserved 4
|
||||
.word 0 @ Reserved 5
|
||||
.word 0 @ Reserved 6
|
||||
.word 0 @ Reserved 7
|
||||
.word 0 @ Reserved 8
|
||||
.word 0 @ Reserved 9
|
||||
.word 0 @ Reserved 10
|
||||
.word 0 @ Reserved 11
|
||||
.word 0 @ Reserved 12
|
||||
.word 0 @ Reserved 13
|
||||
.word 0 @ Reserved 14
|
||||
.word 0 @ Reserved 15
|
||||
|
||||
@ The preamble declares its own length in its fourth word, and the manager
|
||||
@ believes it. If the two ever disagree the manager reads entry points from the
|
||||
@ wrong offsets, so the assembler checks rather than the reader.
|
||||
|
||||
.if (. - __txm_module_preamble) != (32 * 4)
|
||||
.error "txm_module_preamble is not the 32 words its size field declares"
|
||||
.endif
|
||||
@@ -0,0 +1,199 @@
|
||||
/***************************************************************************
|
||||
* 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
|
||||
**************************************************************************/
|
||||
|
||||
/* Link map for the demonstration module alone, for the S32Z280-594EVB.
|
||||
*
|
||||
* The module is a separate link unit from the manager, and it has to be. Both
|
||||
* sides define the ThreadX API: the kernel defines the real _txe_* entry points
|
||||
* and the module library defines shims of the same names that trap into the
|
||||
* kernel instead. Linking them together does not produce a duplicate-symbol
|
||||
* error, because the kernel arrives as a static library and the module library
|
||||
* as ordinary objects, and an object always beats an archive member. The module
|
||||
* shims therefore win for the whole image, and the manager's own calls to
|
||||
* tx_thread_create and friends are quietly redirected into the module.
|
||||
*
|
||||
* That was not a theory. It put _txe_thread_create at 0x317F1700, inside the
|
||||
* module area, which no kernel MPU region covers -- so tx_application_define
|
||||
* called into unmapped memory and the core took a prefetch abort with nothing on
|
||||
* the console to say so.
|
||||
*
|
||||
* POSITION INDEPENDENT, and the two addresses below are fictions
|
||||
* ==============================================================
|
||||
*
|
||||
* The module is built -fpic -msingle-pic-base, so every reference it makes to
|
||||
* its own data goes through the global offset table with r9 as the base. It
|
||||
* therefore does not run at the addresses in this file and is not meant to: the
|
||||
* two segment origins are nominal, chosen only so that the loader can tell a
|
||||
* code address from a data address by comparing against __data_segment_start__.
|
||||
* What actually happens at run time is that _gcc_setup rewrites every GOT entry
|
||||
* from these nominal addresses to the addresses the manager decided on.
|
||||
*
|
||||
* That is why they are far apart and why neither is zero. Far apart, because
|
||||
* the whole discrimination is "below the data origin means code"; non-zero,
|
||||
* because _gcc_setup treats a zero GOT entry as one the linker never filled in
|
||||
* and skips it, so a real address of zero would be silently dropped.
|
||||
*
|
||||
* 0x01000000 code preamble, text, rodata, and the load images of the
|
||||
* GOT and .data -- this is the blob the manager embeds
|
||||
* 0x02000000 data the GOT the module runs against, .data and .bss, all
|
||||
* of which live in memory the manager allocates
|
||||
*
|
||||
* The load images matter. A module's .data cannot be used where it was linked,
|
||||
* because the manager never copies it there: _txm_module_manager_internal_load
|
||||
* allocates the module's data area from its byte pool and TX_MEMSETs it to zero,
|
||||
* and that is the only memory the module is given a region for. The module's
|
||||
* own .data, wherever it was linked, is outside every region the module owns.
|
||||
* On silicon that faulted at the first write to an initialised variable, with
|
||||
* DFAR pointing into the gap between the granted code and the granted data.
|
||||
*
|
||||
* So .data and .got have their VMAs in the data segment, where the module will
|
||||
* run, and their LMAs in the code segment, inside the blob, where _gcc_setup can
|
||||
* find them and copy them out. AT>CODE is what says that.
|
||||
*/
|
||||
|
||||
MEMORY
|
||||
{
|
||||
/* Nominal. See the note above: the module runs where the manager puts it,
|
||||
not here. Sized generously because nothing is reserved by being large --
|
||||
the raw binary is only as long as the sections actually emitted. */
|
||||
|
||||
CODE (rx) : ORIGIN = 0x01000000, LENGTH = 0x00100000
|
||||
DATA (rw) : ORIGIN = 0x02000000, LENGTH = 0x00100000
|
||||
}
|
||||
|
||||
__code_segment_start__ = 0x01000000;
|
||||
__data_segment_start__ = 0x02000000;
|
||||
|
||||
SECTIONS
|
||||
{
|
||||
/* ---------------------------------------------------------------------
|
||||
The code segment, which is the blob byte for byte.
|
||||
--------------------------------------------------------------------- */
|
||||
|
||||
.module_image : ALIGN(64)
|
||||
{
|
||||
__module_image_start__ = .;
|
||||
|
||||
/* The preamble is first because that is where the manager looks for it:
|
||||
it reads the properties, the entry points and the two sizes from the
|
||||
first words of the image. KEEP because nothing references it. */
|
||||
|
||||
KEEP(*(.txm_module_preamble))
|
||||
|
||||
*(.text .text.*)
|
||||
*(.glue_7)
|
||||
*(.glue_7t)
|
||||
*(.rodata .rodata.*)
|
||||
|
||||
. = ALIGN(4);
|
||||
} >CODE AT>CODE
|
||||
|
||||
/* ---------------------------------------------------------------------
|
||||
The data segment. VMAs here, load images back in the code segment.
|
||||
--------------------------------------------------------------------- */
|
||||
|
||||
/* The GOT, and it must be first in the data segment. r9 is the GOT base as
|
||||
far as the compiler is concerned -- every R_ARM_GOT32 is an offset from
|
||||
it -- and the manager sets r9 to txm_module_instance_module_data_base_address,
|
||||
which is the start of the module's data area. So GOT origin and data
|
||||
origin have to be the same address, or every offset the compiler emitted
|
||||
is measured from the wrong place. __data_segment_start__ above is that
|
||||
address on the nominal side; r9 is that address on the real side, and
|
||||
_gcc_setup's rebase is the difference between the two. */
|
||||
|
||||
.got : ALIGN(4)
|
||||
{
|
||||
__new_got_start__ = .;
|
||||
*(.got.plt)
|
||||
*(.igot.plt)
|
||||
*(.got)
|
||||
__new_got_end__ = .;
|
||||
} >DATA AT>CODE
|
||||
|
||||
__got_load_start__ = LOADADDR(.got);
|
||||
|
||||
.data : ALIGN(4)
|
||||
{
|
||||
__data_start__ = .;
|
||||
*(.data .data.*)
|
||||
*(.gnu.linkonce.d.*)
|
||||
. = ALIGN(4);
|
||||
__data_end__ = .;
|
||||
} >DATA AT>CODE
|
||||
|
||||
__data_load_start__ = LOADADDR(.data);
|
||||
|
||||
/* NOLOAD, so .bss contributes nothing to the blob. The manager has already
|
||||
zeroed the whole data allocation by the time the module runs -- but
|
||||
_gcc_setup zeroes .bss anyway, because "the manager happens to memset it"
|
||||
is a property of one loader and not something a module may rely on. */
|
||||
|
||||
.bss (NOLOAD) : ALIGN(4)
|
||||
{
|
||||
__bss_start__ = .;
|
||||
*(.bss .bss.*)
|
||||
*(.gnu.linkonce.b.*)
|
||||
*(COMMON)
|
||||
. = ALIGN(64);
|
||||
__bss_end__ = .;
|
||||
} >DATA
|
||||
|
||||
/* ---------------------------------------------------------------------
|
||||
The two sizes the preamble declares.
|
||||
--------------------------------------------------------------------- */
|
||||
|
||||
/* Code covers everything in the blob, the load images of the GOT and .data
|
||||
included, because the module has to be able to read them to copy them out
|
||||
and the code region is the only mapping it has over the blob. Ending it
|
||||
at .rodata instead would put the GOT template outside every region the
|
||||
module owns and _gcc_setup would fault on its first read. */
|
||||
|
||||
__txm_module_code_end__ = __data_load_start__ + SIZEOF(.data);
|
||||
__txm_module_code_size = __txm_module_code_end__ - __code_segment_start__;
|
||||
|
||||
/* Data covers the GOT, .data and .bss. Not the thread stacks: the manager
|
||||
adds the start/stop and callback stack sizes to this figure itself, from
|
||||
the two stack-size words further down the preamble. Counting them here
|
||||
as well would double them. */
|
||||
|
||||
__txm_module_data_size = __bss_end__ - __data_segment_start__;
|
||||
|
||||
/* Unwind tables and toolchain notes would otherwise land at address zero and
|
||||
be carried into the raw binary. A module has no unwinder.
|
||||
|
||||
.rel.dyn and friends are discarded because this is a static link: ld
|
||||
resolves every R_ARM_GOT32 itself and writes the finished address into the
|
||||
GOT, so there is nothing left for a dynamic loader to do. If one of these
|
||||
ever stops being empty, the assumption above has broken and the module
|
||||
needs relocation processing rather than a rebase -- so they are listed
|
||||
explicitly, to be found by whoever goes looking. */
|
||||
|
||||
/DISCARD/ :
|
||||
{
|
||||
*(.ARM.exidx*)
|
||||
*(.ARM.extab*)
|
||||
*(.comment)
|
||||
*(.note.*)
|
||||
*(.dynsym)
|
||||
*(.dynstr)
|
||||
*(.hash)
|
||||
*(.gnu.hash)
|
||||
*(.dynamic)
|
||||
*(.interp)
|
||||
*(.rel.dyn)
|
||||
*(.rel.plt)
|
||||
*(.plt)
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,65 @@
|
||||
@/***************************************************************************
|
||||
@ * 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
|
||||
@ **************************************************************************/
|
||||
@
|
||||
@/**************************************************************************/
|
||||
@/* */
|
||||
@/* MODULE MANAGER RELEASE */
|
||||
@/* */
|
||||
@/* module_blob.S Cortex-R52/GNU */
|
||||
@/* 6.5.2 */
|
||||
@/* AUTHOR */
|
||||
@/* */
|
||||
@/* Frédéric Desbiens, Eclipse Foundation */
|
||||
@/* */
|
||||
@/* DESCRIPTION */
|
||||
@/* */
|
||||
@/* Carries the demonstration module into the manager image as data. */
|
||||
@/* */
|
||||
@/* The module is built as its own link unit and objcopied to a raw */
|
||||
@/* binary, which is included here verbatim. It has to be a separate */
|
||||
@/* link: the module library defines shims named after the ThreadX API */
|
||||
@/* entry points the kernel also defines, and in one link the shims win */
|
||||
@/* -- objects beat archive members -- so the manager's own service calls */
|
||||
@/* end up trapping into the module. */
|
||||
@/* */
|
||||
@/* Included as bytes rather than linked as objects, so the module's */
|
||||
@/* symbols never enter the manager's link at all. Nothing here is */
|
||||
@/* called: the manager finds the preamble at the start of the image and */
|
||||
@/* reaches everything else through that. */
|
||||
@/* */
|
||||
@/**************************************************************************/
|
||||
|
||||
.section .module_blob, "a"
|
||||
|
||||
@ 64-byte aligned, the PMSAv8-R granule, so the image starts where a region
|
||||
@ can start. The linker script places this section at the module area base,
|
||||
@ which is the address the module itself was linked for.
|
||||
|
||||
.align 6
|
||||
|
||||
.global __demo_module_image
|
||||
.global __demo_module_image_end
|
||||
|
||||
__demo_module_image:
|
||||
|
||||
@ demo_module.bin is produced by the s32z280_demo_module.elf target. The
|
||||
@ assembler finds it through an include path pointing at the build directory,
|
||||
@ set in CMakeLists.txt -- .incbin searches the -I paths, not the source tree.
|
||||
|
||||
.incbin "demo_module.bin"
|
||||
|
||||
__demo_module_image_end:
|
||||
|
||||
.align 6
|
||||
File diff suppressed because it is too large
Load Diff
+1736
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
+187
@@ -0,0 +1,187 @@
|
||||
#!/bin/bash
|
||||
# Copyright (c) 2026 Eclipse ThreadX contributors
|
||||
# SPDX-License-Identifier: MIT
|
||||
# Some portions generated by Claude Code (Opus 5).
|
||||
#
|
||||
# Run the ThreadX module manager demonstration on the S32Z280-594EVB and capture
|
||||
# the evidence: the console log and the fault registers.
|
||||
#
|
||||
# Usage: tools/run_module_demo.sh [<build directory>]
|
||||
#
|
||||
# Paths that vary by machine come from the environment: S32DS_ROOT, S32Z280_PROBE
|
||||
# and GDB_PY_HOME, each described where it is resolved below. UART and LOG
|
||||
# override the console device and the capture file.
|
||||
#
|
||||
# The console is the test method here, not a convenience, so this captures the
|
||||
# UART to a file before the image runs and prints the captured bytes afterwards.
|
||||
# Both LINFlexD bugs found during bring-up were invisible from the target and
|
||||
# showed up only in a byte diff of a captured log.
|
||||
#
|
||||
# Prerequisites this cannot do for you:
|
||||
# * the *Windows* CCS listening on 41475, started as "ccs.exe -noportquit".
|
||||
# The Linux CCS's pushes to the probe stall after "Sending code ... done".
|
||||
# * board wiring for the console: LIN9 -> daughtercard USB-UART, jumper J248
|
||||
# at 1-2, micro-USB at J119 on the daughtercard.
|
||||
set -u
|
||||
|
||||
BUILD=${1:-build_mod}
|
||||
EVB_BIN="$BUILD/ports/cortex_r52/gnu/example_build/s32z280_evb"
|
||||
HERE=$(cd "$(dirname "$0")" && pwd)
|
||||
|
||||
# Where the tools live. None of these paths are portable, so each is
|
||||
# overridable and each is checked before it is used -- a wrong one otherwise
|
||||
# surfaces as a gdb that will not start or an attach that never connects.
|
||||
#
|
||||
# S32DS_ROOT the S32 Design Studio installation. Tried in order: the
|
||||
# variable, a system-wide install, the per-user layout.
|
||||
# S32Z280_PROBE the debug probe, as the gdb scripts expect to address it.
|
||||
# GDB_PY_HOME the source-built CPython that gdb-py needs; see PYTHONHOME
|
||||
# below for why the one inside gdb is not enough.
|
||||
if [ -n "${S32DS_ROOT:-}" ]; then
|
||||
R=$S32DS_ROOT
|
||||
elif [ -d /opt/nxp/S32DS.3.6.10 ]; then
|
||||
R=/opt/nxp/S32DS.3.6.10
|
||||
else
|
||||
R=$HOME/NXP/S32DS.3.6.10
|
||||
fi
|
||||
|
||||
if [ ! -d "$R" ]; then
|
||||
echo "ERROR: S32 Design Studio not found at $R" >&2
|
||||
echo " Set S32DS_ROOT to your installation, for example:" >&2
|
||||
echo " S32DS_ROOT=/opt/nxp/S32DS.3.6.10 $0 ${1:-}" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
GDB=$R/S32DS/tools/gdb-arm/arm32-eabi/bin/arm-none-eabi-gdb-py
|
||||
GTA=$R/S32DS/tools/S32Debugger/Debugger/Server/gta
|
||||
|
||||
for f in "$GDB" "$GTA/gta"; do
|
||||
if [ ! -x "$f" ]; then
|
||||
echo "ERROR: $f is missing or not executable." >&2
|
||||
echo " S32DS_ROOT=$R does not look like an S32 Design Studio install." >&2
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
# Resolved here and exported, so the gdb scripts use these values rather than
|
||||
# resolving the same defaults again and possibly differently.
|
||||
export S32DS_ROOT=$R
|
||||
export S32Z280_PROBE=${S32Z280_PROBE:-s32dbg:192.168.50.238}
|
||||
|
||||
echo "S32DS_ROOT = $R"
|
||||
echo "S32Z280_PROBE = $S32Z280_PROBE"
|
||||
|
||||
MANAGER_ELF=$EVB_BIN/s32z280_module.elf
|
||||
MODULE_ELF=$EVB_BIN/s32z280_demo_module.elf
|
||||
UART=${UART:-/dev/ttyUSB0}
|
||||
LOG=${LOG:-/tmp/module_demo_uart.log}
|
||||
|
||||
for f in "$MANAGER_ELF" "$MODULE_ELF"; do
|
||||
if [ ! -f "$f" ]; then
|
||||
echo "ERROR: $f not found. Build it first:" >&2
|
||||
echo " ninja -C $BUILD s32z280_module.elf" >&2
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
# gdb-py needs the source-built interpreter: its embedded Python has a minimal
|
||||
# builtin set and loads _struct/_socket/_ctypes as separate .so files. Compute
|
||||
# the module address BEFORE exporting PYTHONHOME, since that breaks system tools.
|
||||
# What is wanted is an OFFSET, not an address. The module is position
|
||||
# independent: the address nm reports is a nominal one from a segment the module
|
||||
# never runs at, and its real data lives wherever the manager allocated it -- a
|
||||
# different place in each pass. So the offset of module_progress
|
||||
# within the module's data segment is computed here, and the gdb script adds it
|
||||
# to each pass's data base, which it reads off the target.
|
||||
PROGRESS_SYM=$(nm "$MODULE_ELF" | awk '$3=="module_progress"{print $1}')
|
||||
DATA_SEG=$(nm "$MODULE_ELF" | awk '$3=="__data_segment_start__"{print $1}')
|
||||
if [ -z "$PROGRESS_SYM" ] || [ -z "$DATA_SEG" ]; then
|
||||
echo "ERROR: module_progress or __data_segment_start__ not found in $MODULE_ELF" >&2
|
||||
exit 1
|
||||
fi
|
||||
PROGRESS_OFFSET=$(( 0x$PROGRESS_SYM - 0x$DATA_SEG ))
|
||||
if [ "$PROGRESS_OFFSET" -lt 0 ]; then
|
||||
echo "ERROR: module_progress is below the module's data segment; the link map" >&2
|
||||
echo " and this script disagree about where the module's data starts." >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "module_progress is $PROGRESS_OFFSET bytes into the module's data segment"
|
||||
echo " (nominal 0x$PROGRESS_SYM, data segment 0x$DATA_SEG -- neither is a real address)"
|
||||
|
||||
if pgrep -x ccs > /dev/null; then
|
||||
echo "ERROR: a Linux CCS is running (pid $(pgrep -x ccs | tr '\n' ' '))." >&2
|
||||
echo " It holds 41475 and blocks the Windows CCS. Kill it: pkill -x ccs" >&2
|
||||
exit 1
|
||||
fi
|
||||
# /dev/tcp rather than a Python probe: PYTHONHOME below points at the
|
||||
# source-built 3.10 for gdb-py, which would make system python3 fail and this
|
||||
# check spuriously report "no CCS".
|
||||
if ! timeout 5 bash -c "exec 3<>/dev/tcp/127.0.0.1/41475" 2>/dev/null; then
|
||||
echo "ERROR: nothing listening on 41475." >&2
|
||||
echo " CCS quits when its client disconnects unless started with" >&2
|
||||
echo " -noportquit. Relaunch on Windows as: ccs.exe -noportquit" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# --- console capture, started BEFORE the image runs -------------------------
|
||||
if [ -c "$UART" ]; then
|
||||
stty -F "$UART" 115200 cs8 -parenb -cstopb -crtscts raw -echo
|
||||
: > "$LOG"
|
||||
cat "$UART" > "$LOG" &
|
||||
CAT_PID=$!
|
||||
echo "capturing $UART -> $LOG (pid $CAT_PID)"
|
||||
else
|
||||
CAT_PID=""
|
||||
echo "WARNING: $UART is not a character device; running without a console capture." >&2
|
||||
fi
|
||||
|
||||
cleanup() {
|
||||
if [ -n "$CAT_PID" ]; then
|
||||
kill "$CAT_PID" 2>/dev/null || true
|
||||
fi
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
P=${GDB_PY_HOME:-$HOME/toolchains/py310-src}
|
||||
if [ ! -d "$P/lib/python3.10" ]; then
|
||||
echo "ERROR: no source-built CPython 3.10 at $P" >&2
|
||||
echo " gdb-py's embedded interpreter loads _struct, _socket and" >&2
|
||||
echo " _ctypes as separate .so files and needs a real install to" >&2
|
||||
echo " find them. Set GDB_PY_HOME to one." >&2
|
||||
exit 1
|
||||
fi
|
||||
export PYTHONHOME=$P
|
||||
export PYTHONPATH=$P/lib/python3.10:$P/lib/python3.10/lib-dynload:$P/lib/python3.10/site-packages
|
||||
export S32Z280_ELF=$MANAGER_ELF
|
||||
export S32Z280_MODULE_ELF=$MODULE_ELF
|
||||
export S32Z280_MODULE_PROGRESS_OFFSET=$PROGRESS_OFFSET
|
||||
|
||||
# pkill -x, not -f: these run as ./gta, so a -f pattern like 'gta/gta' never
|
||||
# matches and a stale server silently serves the next attach.
|
||||
pkill -x gta 2>/dev/null
|
||||
sleep 2
|
||||
( cd "$GTA" && nohup ./gta -p 45000 -k > /tmp/gta_module_demo.log 2>&1 & )
|
||||
sleep 4
|
||||
if ! pgrep -x gta > /dev/null; then
|
||||
echo "ERROR: GTA failed to start; see /tmp/gta_module_demo.log" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
timeout -k 20 500 "$GDB" -batch -x "$HERE/run_module_demo.gdb"
|
||||
rc=$?
|
||||
|
||||
# Let the last of the console output arrive before the capture is torn down.
|
||||
sleep 2
|
||||
|
||||
echo ""
|
||||
echo "===== console, as captured from $UART ====="
|
||||
if [ -s "$LOG" ]; then
|
||||
cat "$LOG"
|
||||
else
|
||||
echo "(nothing arrived on the console)"
|
||||
echo "Check: jumper J248 at 1-2, micro-USB at J119 on the daughtercard, and"
|
||||
echo "LED J122 for board-to-host traffic."
|
||||
fi
|
||||
echo "===== end of console ====="
|
||||
echo "=== gdb exit=$rc ==="
|
||||
exit $rc
|
||||
@@ -0,0 +1,151 @@
|
||||
@/***************************************************************************
|
||||
@ * 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
|
||||
@ **************************************************************************/
|
||||
@
|
||||
@/**************************************************************************/
|
||||
@/* */
|
||||
@/* MODULE PREAMBLE RELEASE */
|
||||
@/* */
|
||||
@/* txm_module_preamble.S Cortex-R52/GNU */
|
||||
@/* 6.5.2 */
|
||||
@/* AUTHOR */
|
||||
@/* */
|
||||
@/* Frédéric Desbiens, Eclipse Foundation */
|
||||
@/* */
|
||||
@/* DESCRIPTION */
|
||||
@/* */
|
||||
@/* The header the module manager reads before it will load a module. */
|
||||
@/* */
|
||||
@/* It must be the first thing in the module image, which is what the */
|
||||
@/* linker script arranges, and every entry point in it is an offset */
|
||||
@/* from the start of the preamble rather than an address. A module is */
|
||||
@/* position independent: the manager decides where it lands, so the */
|
||||
@/* module cannot know its own addresses at link time. */
|
||||
@/* */
|
||||
@/* Code and data sizes come from the linker script rather than being */
|
||||
@/* written in by hand. The Cortex-R4 preamble carries literal numbers */
|
||||
@/* for them, which is a standing invitation to grow a module past its */
|
||||
@/* declared size and have the manager map less memory than it uses -- */
|
||||
@/* a fault in a module that did nothing wrong, whose cause is a */
|
||||
@/* constant in a file nobody thought to change. */
|
||||
@/* */
|
||||
@/**************************************************************************/
|
||||
|
||||
.syntax unified
|
||||
.arm
|
||||
|
||||
.global __txm_module_preamble
|
||||
|
||||
.extern _txm_module_thread_shell_entry
|
||||
.extern _txm_module_callback_request_thread_entry
|
||||
.extern demo_module_start
|
||||
|
||||
@ Supplied by the module's linker script.
|
||||
|
||||
.extern __txm_module_code_size
|
||||
.extern __txm_module_data_size
|
||||
|
||||
@ Properties. The compiler field tells the manager which set of entry-point
|
||||
@ adjustments to apply, and the option bits say what the module is asking for:
|
||||
@ user mode and memory protection, which together are the point of this port.
|
||||
@
|
||||
@ 0x02000000 TXM_MODULE_GNU_COMPILER
|
||||
@ 0x00000001 TXM_MODULE_USER_MODE
|
||||
@ 0x00000002 TXM_MODULE_MEMORY_PROTECTION
|
||||
@
|
||||
@ Both option bits are REQUIRED here, unlike on every other module port, and a
|
||||
@ preamble that carries either alone is refused by the loader with
|
||||
@ TXM_MODULE_INVALID_PROPERTIES. On PMSAv8-R that is not a policy: the port
|
||||
@ programs a module's regions only when both bits are set, the scheduler closes
|
||||
@ the kernel's window over module memory before it dispatches a module thread,
|
||||
@ and there is no background region to fall back on -- so a module that asked
|
||||
@ for user mode without protection would have no mapping at all rather than a
|
||||
@ weaker one. See TXM_MODULE_MANAGER_REQUIRED_OPTIONS in txm_module_port.h.
|
||||
@
|
||||
@ 0x00000004 TXM_MODULE_SHARED_EXTERNAL_MEMORY_ACCESS
|
||||
@
|
||||
@ is supported and optional. A module that does not ask for it simply never has
|
||||
@ a shared region granted to it.
|
||||
|
||||
.equ MODULE_PROPERTIES, 0x02000003
|
||||
|
||||
.section .txm_module_preamble, "a"
|
||||
.align 6
|
||||
|
||||
__txm_module_preamble:
|
||||
|
||||
.word 0x4D4F4455 @ Module ID, "MODU"
|
||||
.word 0x6 @ Major version
|
||||
.word 0x1 @ Minor version
|
||||
.word 32 @ Preamble size, 32-bit words
|
||||
.word 0x52520001 @ Application-defined ID
|
||||
.word MODULE_PROPERTIES @ Properties, see above
|
||||
|
||||
@ Entry points, as offsets from the preamble.
|
||||
|
||||
@ Entry points are stored relative to the word that holds them, not to the
|
||||
@ start of the preamble. That is what the manager expects: it recovers the
|
||||
@ offset from the module base by adding the field's own byte offset back --
|
||||
@ TXM_MODULE_GNU_SHELL_ADJUST 24, START 28, STOP 32, CALLBACK 44, which are
|
||||
@ exactly the offsets of the four words below. Storing these relative to
|
||||
@ __txm_module_preamble instead counts that offset twice, and the module is
|
||||
@ then entered that many bytes into its shell entry: past the prologue, with
|
||||
@ the arguments never saved and the frame pointer never set up, so the first
|
||||
@ dereference goes through a register the stack build had zeroed. On silicon
|
||||
@ that read faulted at 0x1C, which is offset 0x1C from a null r3.
|
||||
@
|
||||
@ Every other GNU module port writes these the same way; cortex_m33's
|
||||
@ preamble, which this port was seeded from, spells it "symbol - . - 0".
|
||||
|
||||
.word _txm_module_thread_shell_entry - .
|
||||
.word demo_module_start - .
|
||||
.word 0 @ No stop thread
|
||||
.word 1 @ Start/stop thread priority
|
||||
.word 1024 @ Start/stop thread stack size
|
||||
.word _txm_module_callback_request_thread_entry - .
|
||||
.word 1 @ Callback thread priority
|
||||
.word 1024 @ Callback thread stack size
|
||||
|
||||
@ Sizes, from the linker script. The manager rounds both up to the 64-byte MPU
|
||||
@ granule and maps exactly this much; anything the module touches beyond it
|
||||
@ faults, which is the intended behaviour and not a bug to work around by
|
||||
@ inflating these numbers.
|
||||
|
||||
.word __txm_module_code_size
|
||||
.word __txm_module_data_size
|
||||
|
||||
.word 0 @ Reserved 0
|
||||
.word 0 @ Reserved 1
|
||||
.word 0 @ Reserved 2
|
||||
.word 0 @ Reserved 3
|
||||
.word 0 @ Reserved 4
|
||||
.word 0 @ Reserved 5
|
||||
.word 0 @ Reserved 6
|
||||
.word 0 @ Reserved 7
|
||||
.word 0 @ Reserved 8
|
||||
.word 0 @ Reserved 9
|
||||
.word 0 @ Reserved 10
|
||||
.word 0 @ Reserved 11
|
||||
.word 0 @ Reserved 12
|
||||
.word 0 @ Reserved 13
|
||||
.word 0 @ Reserved 14
|
||||
.word 0 @ Reserved 15
|
||||
|
||||
@ The preamble declares its own length in its fourth word, and the manager
|
||||
@ believes it. If the two ever disagree the manager reads entry points from the
|
||||
@ wrong offsets, so the assembler checks rather than the reader.
|
||||
|
||||
.if (. - __txm_module_preamble) != (32 * 4)
|
||||
.error "txm_module_preamble is not the 32 words its size field declares"
|
||||
.endif
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,168 @@
|
||||
/***************************************************************************
|
||||
* Copyright (c) 2024 Microsoft Corporation
|
||||
* Copyright (c) 2026-present 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.
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
**************************************************************************/
|
||||
|
||||
|
||||
/**************************************************************************/
|
||||
/**************************************************************************/
|
||||
/** */
|
||||
/** ThreadX Component */
|
||||
/** */
|
||||
/** Module */
|
||||
/** */
|
||||
/**************************************************************************/
|
||||
/**************************************************************************/
|
||||
|
||||
#ifndef TXM_MODULE
|
||||
#define TXM_MODULE
|
||||
#endif
|
||||
|
||||
#ifndef TX_SOURCE_CODE
|
||||
#define TX_SOURCE_CODE
|
||||
#endif
|
||||
|
||||
|
||||
/* Include necessary system files. */
|
||||
|
||||
#include "txm_module.h"
|
||||
#include "tx_thread.h"
|
||||
|
||||
/* Define the global module entry pointer from the start thread of the module. */
|
||||
|
||||
TXM_MODULE_THREAD_ENTRY_INFO *_txm_module_entry_info;
|
||||
|
||||
|
||||
/* Define the dispatch function pointer used in the module implementation. */
|
||||
|
||||
ULONG (*_txm_module_kernel_call_dispatcher)(ULONG kernel_request, ULONG param_1, ULONG param_2, ULONG param3);
|
||||
|
||||
|
||||
/* Define the GCC startup code that clears the uninitialized global data and sets up the
|
||||
preset global variables. */
|
||||
|
||||
extern VOID _gcc_setup(TXM_MODULE_INSTANCE *);
|
||||
|
||||
|
||||
/**************************************************************************/
|
||||
/* */
|
||||
/* FUNCTION RELEASE */
|
||||
/* */
|
||||
/* _txm_module_thread_shell_entry Cortex-R52/GNU */
|
||||
/* 6.1.5 */
|
||||
/* AUTHOR */
|
||||
/* */
|
||||
/* Scott Larson, Microsoft Corporation */
|
||||
/* */
|
||||
/* DESCRIPTION */
|
||||
/* */
|
||||
/* This function calls the specified entry function of the thread. It */
|
||||
/* also provides a place for the thread's entry function to return. */
|
||||
/* If the thread returns, this function places the thread in a */
|
||||
/* "COMPLETED" state. */
|
||||
/* */
|
||||
/* INPUT */
|
||||
/* */
|
||||
/* thread_ptr Pointer to current thread */
|
||||
/* thread_info Pointer to thread entry info */
|
||||
/* */
|
||||
/* OUTPUT */
|
||||
/* */
|
||||
/* None */
|
||||
/* */
|
||||
/* CALLS */
|
||||
/* */
|
||||
/* _gcc_setup GNU global init function */
|
||||
/* thread_entry Thread's entry function */
|
||||
/* tx_thread_resume Resume the module callback thread */
|
||||
/* _txm_module_thread_system_suspend Module thread suspension routine */
|
||||
/* */
|
||||
/* CALLED BY */
|
||||
/* */
|
||||
/* Initial thread stack frame */
|
||||
/* */
|
||||
/**************************************************************************/
|
||||
VOID _txm_module_thread_shell_entry(TX_THREAD *thread_ptr, TXM_MODULE_THREAD_ENTRY_INFO *thread_info)
|
||||
{
|
||||
|
||||
#ifndef TX_DISABLE_NOTIFY_CALLBACKS
|
||||
VOID (*entry_exit_notify)(TX_THREAD *, UINT);
|
||||
#endif
|
||||
|
||||
|
||||
/* Determine if this is the start thread. If so, we must prepare the module for
|
||||
execution. If not, simply skip the C startup code. */
|
||||
if (thread_info -> txm_module_thread_entry_info_start_thread)
|
||||
{
|
||||
/* Initialize the GNU C environment. */
|
||||
_gcc_setup(thread_info -> txm_module_thread_entry_info_code_base_address);
|
||||
|
||||
/* Save the entry info pointer, for later use. */
|
||||
_txm_module_entry_info = thread_info;
|
||||
|
||||
/* Save the kernel function dispatch address. This is used to make all resident calls from
|
||||
the module. */
|
||||
_txm_module_kernel_call_dispatcher = thread_info -> txm_module_thread_entry_info_kernel_call_dispatcher;
|
||||
|
||||
/* Ensure that we have a valid pointer. */
|
||||
while (!_txm_module_kernel_call_dispatcher)
|
||||
{
|
||||
/* Loop here, if an error is present getting the dispatch function pointer!
|
||||
An error here typically indicates the resident portion of _tx_thread_schedule
|
||||
is not supporting the trap to obtain the function pointer. */
|
||||
}
|
||||
|
||||
/* Resume the module's callback thread, already created in the manager. */
|
||||
_txe_thread_resume(thread_info -> txm_module_thread_entry_info_callback_request_thread);
|
||||
}
|
||||
|
||||
#ifndef TX_DISABLE_NOTIFY_CALLBACKS
|
||||
|
||||
/* Pickup the entry/exit application callback routine. */
|
||||
entry_exit_notify = thread_info -> txm_module_thread_entry_info_exit_notify;
|
||||
|
||||
/* Determine if an application callback routine is specified. */
|
||||
if (entry_exit_notify != TX_NULL)
|
||||
{
|
||||
|
||||
/* Yes, notify application that this thread has been entered! */
|
||||
(entry_exit_notify)(thread_ptr, TX_THREAD_ENTRY);
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Call current thread's entry function. */
|
||||
(thread_info -> txm_module_thread_entry_info_entry) (thread_info -> txm_module_thread_entry_info_parameter);
|
||||
|
||||
/* Suspend thread with a "completed" state. */
|
||||
|
||||
|
||||
#ifndef TX_DISABLE_NOTIFY_CALLBACKS
|
||||
|
||||
/* Pickup the entry/exit application callback routine again. */
|
||||
entry_exit_notify = thread_info -> txm_module_thread_entry_info_exit_notify;
|
||||
|
||||
/* Determine if an application callback routine is specified. */
|
||||
if (entry_exit_notify != TX_NULL)
|
||||
{
|
||||
|
||||
/* Yes, notify application that this thread has exited! */
|
||||
(entry_exit_notify)(thread_ptr, TX_THREAD_EXIT);
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Call actual thread suspension routine. */
|
||||
_txm_module_thread_system_suspend(thread_ptr);
|
||||
|
||||
#ifdef TX_SAFETY_CRITICAL
|
||||
|
||||
/* If we ever get here, raise safety critical exception. */
|
||||
TX_SAFETY_CRITICAL_EXCEPTION(__FILE__, __LINE__, 0);
|
||||
#endif
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,193 @@
|
||||
@/***************************************************************************
|
||||
@ * Copyright (c) 2024 Microsoft Corporation
|
||||
@ * 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.
|
||||
@ *
|
||||
@ * SPDX-License-Identifier: MIT
|
||||
@ **************************************************************************/
|
||||
@ Some portions generated by Claude Code (Opus 5).
|
||||
@
|
||||
@
|
||||
@/**************************************************************************/
|
||||
@/**************************************************************************/
|
||||
@/** */
|
||||
@/** ThreadX Component */
|
||||
@/** */
|
||||
@/** Thread */
|
||||
@/** */
|
||||
@/**************************************************************************/
|
||||
@/**************************************************************************/
|
||||
#ifdef TX_INCLUDE_USER_DEFINE_FILE
|
||||
#include "tx_user.h"
|
||||
#endif
|
||||
|
||||
.global _tx_thread_system_state
|
||||
.global _tx_thread_current_ptr
|
||||
.global _tx_irq_processing_return
|
||||
.global _tx_execution_isr_enter
|
||||
@
|
||||
@
|
||||
@/* No 16-bit Thumb mode veneer code is needed for _tx_thread_context_save
|
||||
@ since it will never be called 16-bit mode. */
|
||||
@
|
||||
.arm
|
||||
.text
|
||||
.align 2
|
||||
@/**************************************************************************/
|
||||
@/* */
|
||||
@/* FUNCTION RELEASE */
|
||||
@/* */
|
||||
@/* _tx_thread_context_save Cortex-R52/GNU */
|
||||
@/* 6.5.2 */
|
||||
@/* AUTHOR */
|
||||
@/* */
|
||||
@/* Frédéric Desbiens, Eclipse Foundation */
|
||||
@/* */
|
||||
@/* Derived from the Cortex-R5/GNU port originally written by */
|
||||
@/* William E. Lamie, Microsoft Corporation. */
|
||||
@/* */
|
||||
@/* DESCRIPTION */
|
||||
@/* */
|
||||
@/* This function saves the context of an executing thread in the */
|
||||
@/* beginning of interrupt processing. The function also ensures that */
|
||||
@/* the system stack is used upon return to the calling ISR. */
|
||||
@/* */
|
||||
@/* INPUT */
|
||||
@/* */
|
||||
@/* None */
|
||||
@/* */
|
||||
@/* OUTPUT */
|
||||
@/* */
|
||||
@/* None */
|
||||
@/* */
|
||||
@/* CALLS */
|
||||
@/* */
|
||||
@/* None */
|
||||
@/* */
|
||||
@/* CALLED BY */
|
||||
@/* */
|
||||
@/* ISRs */
|
||||
@/* */
|
||||
@/**************************************************************************/
|
||||
@VOID _tx_thread_context_save(VOID)
|
||||
@{
|
||||
.global _tx_thread_context_save
|
||||
.type _tx_thread_context_save,function
|
||||
_tx_thread_context_save:
|
||||
@
|
||||
@ /* Upon entry to this routine, it is assumed that IRQ interrupts are locked
|
||||
@ out, we are in IRQ mode, and all registers are intact. */
|
||||
@
|
||||
@ /* Check for a nested interrupt condition. */
|
||||
@ if (_tx_thread_system_state++)
|
||||
@ {
|
||||
@
|
||||
STMDB sp!, {r0-r3} @ Save some working registers
|
||||
#ifdef TX_ENABLE_FIQ_SUPPORT
|
||||
CPSID if @ Disable FIQ interrupts
|
||||
#endif
|
||||
LDR r3, =_tx_thread_system_state @ Pickup address of system state variable
|
||||
LDR r2, [r3] @ Pickup system state
|
||||
CMP r2, #0 @ Is this the first interrupt?
|
||||
BEQ __tx_thread_not_nested_save @ Yes, not a nested context save
|
||||
@
|
||||
@ /* Nested interrupt condition. */
|
||||
@
|
||||
ADD r2, r2, #1 @ Increment the interrupt counter
|
||||
STR r2, [r3] @ Store it back in the variable
|
||||
@
|
||||
@ /* Save the rest of the scratch registers on the stack and return to the
|
||||
@ calling ISR. */
|
||||
@
|
||||
MRS r0, SPSR @ Pickup saved SPSR
|
||||
SUB lr, lr, #4 @ Adjust point of interrupt
|
||||
STMDB sp!, {r0, r10, r12, lr} @ Store other registers
|
||||
@
|
||||
@ /* Return to the ISR. */
|
||||
@
|
||||
MOV r10, #0 @ Clear stack limit
|
||||
|
||||
#ifdef TX_ENABLE_EXECUTION_CHANGE_NOTIFY
|
||||
@
|
||||
@ /* Call the ISR enter function to indicate an ISR is executing. */
|
||||
@
|
||||
PUSH {lr} @ Save ISR lr
|
||||
BL _tx_execution_isr_enter @ Call the ISR enter function
|
||||
POP {lr} @ Recover ISR lr
|
||||
#endif
|
||||
|
||||
B __tx_irq_processing_return @ Continue IRQ processing
|
||||
@
|
||||
__tx_thread_not_nested_save:
|
||||
@ }
|
||||
@
|
||||
@ /* Otherwise, not nested, check to see if a thread was running. */
|
||||
@ else if (_tx_thread_current_ptr)
|
||||
@ {
|
||||
@
|
||||
ADD r2, r2, #1 @ Increment the interrupt counter
|
||||
STR r2, [r3] @ Store it back in the variable
|
||||
LDR r1, =_tx_thread_current_ptr @ Pickup address of current thread ptr
|
||||
LDR r0, [r1] @ Pickup current thread pointer
|
||||
CMP r0, #0 @ Is it NULL?
|
||||
BEQ __tx_thread_idle_system_save @ If so, interrupt occurred in
|
||||
@ scheduling loop - nothing needs saving!
|
||||
@
|
||||
@ /* Save minimal context of interrupted thread. */
|
||||
@
|
||||
MRS r2, SPSR @ Pickup saved SPSR
|
||||
SUB lr, lr, #4 @ Adjust point of interrupt
|
||||
STMDB sp!, {r2, r10, r12, lr} @ Store other registers
|
||||
@
|
||||
@ /* Save the current stack pointer in the thread's control block. */
|
||||
@ _tx_thread_current_ptr -> tx_thread_stack_ptr = sp;
|
||||
@
|
||||
@ /* Switch to the system stack. */
|
||||
@ sp = _tx_thread_system_stack_ptr@
|
||||
@
|
||||
MOV r10, #0 @ Clear stack limit
|
||||
|
||||
#ifdef TX_ENABLE_EXECUTION_CHANGE_NOTIFY
|
||||
@
|
||||
@ /* Call the ISR enter function to indicate an ISR is executing. */
|
||||
@
|
||||
PUSH {lr} @ Save ISR lr
|
||||
BL _tx_execution_isr_enter @ Call the ISR enter function
|
||||
POP {lr} @ Recover ISR lr
|
||||
#endif
|
||||
|
||||
B __tx_irq_processing_return @ Continue IRQ processing
|
||||
@
|
||||
@ }
|
||||
@ else
|
||||
@ {
|
||||
@
|
||||
__tx_thread_idle_system_save:
|
||||
@
|
||||
@ /* Interrupt occurred in the scheduling loop. */
|
||||
@
|
||||
@ /* Not much to do here, just adjust the stack pointer, and return to IRQ
|
||||
@ processing. */
|
||||
@
|
||||
MOV r10, #0 @ Clear stack limit
|
||||
|
||||
#ifdef TX_ENABLE_EXECUTION_CHANGE_NOTIFY
|
||||
@
|
||||
@ /* Call the ISR enter function to indicate an ISR is executing. */
|
||||
@
|
||||
PUSH {lr} @ Save ISR lr
|
||||
BL _tx_execution_isr_enter @ Call the ISR enter function
|
||||
POP {lr} @ Recover ISR lr
|
||||
#endif
|
||||
|
||||
ADD sp, sp, #16 @ Recover saved registers
|
||||
B __tx_irq_processing_return @ Continue IRQ processing
|
||||
@
|
||||
@ }
|
||||
@}
|
||||
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,191 @@
|
||||
@/***************************************************************************
|
||||
@ * Copyright (c) 2024 Microsoft Corporation
|
||||
@ * 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.
|
||||
@ *
|
||||
@ * SPDX-License-Identifier: MIT
|
||||
@ **************************************************************************/
|
||||
@ Some portions generated by Claude Code (Opus 5).
|
||||
@
|
||||
@
|
||||
@/**************************************************************************/
|
||||
@/**************************************************************************/
|
||||
@/** */
|
||||
@/** ThreadX Component */
|
||||
@/** */
|
||||
@/** Thread */
|
||||
@/** */
|
||||
@/**************************************************************************/
|
||||
@/**************************************************************************/
|
||||
#ifdef TX_INCLUDE_USER_DEFINE_FILE
|
||||
#include "tx_user.h"
|
||||
#endif
|
||||
|
||||
.arm
|
||||
|
||||
@ Bare CPSR mode field, spelled _MODE_BITS as the base port does in
|
||||
@ tx_thread_irq_nesting_start.S and tx_thread_fiq_context_restore.S. In this
|
||||
@ port a plain _MODE name means a whole CPSR value with the interrupt masks
|
||||
@ already in it -- tx_thread_context_restore.S defines SYS_MODE as 0xDF or 0x9F
|
||||
@ -- so the two must not share a name. The base port's SVC_MODE = 0x13 here was
|
||||
@ the frame's mode before this port moved kernel threads to System mode; nothing
|
||||
@ uses it now, so it is gone rather than left to be picked up by mistake.
|
||||
|
||||
SYS_MODE_BITS = 0x1F @ SYS mode, privileged
|
||||
#ifdef TX_ENABLE_FIQ_SUPPORT
|
||||
CPSR_MASK = 0xDF @ Mask initial CPSR, IRQ & FIQ interrupts enabled
|
||||
#else
|
||||
CPSR_MASK = 0x9F @ Mask initial CPSR, IRQ interrupts enabled
|
||||
#endif
|
||||
@
|
||||
@
|
||||
@/* Define the 16-bit Thumb mode veneer for _tx_thread_stack_build for
|
||||
@ applications calling this function from to 16-bit Thumb mode. */
|
||||
@
|
||||
.text
|
||||
.align 2
|
||||
.thumb
|
||||
.global $_tx_thread_stack_build
|
||||
.type $_tx_thread_stack_build,function
|
||||
$_tx_thread_stack_build:
|
||||
BX pc @ Switch to 32-bit mode
|
||||
NOP @
|
||||
.arm
|
||||
STMFD sp!, {lr} @ Save return address
|
||||
BL _tx_thread_stack_build @ Call _tx_thread_stack_build function
|
||||
LDMFD sp!, {lr} @ Recover saved return address
|
||||
BX lr @ Return to 16-bit caller
|
||||
@
|
||||
@
|
||||
.text
|
||||
.align 2
|
||||
@/**************************************************************************/
|
||||
@/* */
|
||||
@/* FUNCTION RELEASE */
|
||||
@/* */
|
||||
@/* _tx_thread_stack_build Cortex-R52/GNU */
|
||||
@/* 6.5.2 */
|
||||
@/* AUTHOR */
|
||||
@/* */
|
||||
@/* Frédéric Desbiens, Eclipse Foundation */
|
||||
@/* */
|
||||
@/* Derived from the Cortex-R5/GNU port originally written by */
|
||||
@/* William E. Lamie, Microsoft Corporation. */
|
||||
@/* */
|
||||
@/* DESCRIPTION */
|
||||
@/* */
|
||||
@/* This function builds a stack frame on the supplied thread's stack. */
|
||||
@/* The stack frame results in a fake interrupt return to the supplied */
|
||||
@/* function pointer. */
|
||||
@/* */
|
||||
@/* INPUT */
|
||||
@/* */
|
||||
@/* thread_ptr Pointer to thread control blk */
|
||||
@/* function_ptr Pointer to return function */
|
||||
@/* */
|
||||
@/* OUTPUT */
|
||||
@/* */
|
||||
@/* None */
|
||||
@/* */
|
||||
@/* CALLS */
|
||||
@/* */
|
||||
@/* None */
|
||||
@/* */
|
||||
@/* CALLED BY */
|
||||
@/* */
|
||||
@/* _tx_thread_create Create thread service */
|
||||
@/* */
|
||||
@/**************************************************************************/
|
||||
@VOID _tx_thread_stack_build(TX_THREAD *thread_ptr, VOID (*function_ptr)(VOID))
|
||||
@{
|
||||
.global _tx_thread_stack_build
|
||||
.type _tx_thread_stack_build,function
|
||||
_tx_thread_stack_build:
|
||||
@
|
||||
@
|
||||
@ /* Build a fake interrupt frame. The form of the fake interrupt stack
|
||||
@ on the ARM9 should look like the following after it is built:
|
||||
@
|
||||
@ Stack Top: 1 Interrupt stack frame type
|
||||
@ CPSR Initial value for CPSR
|
||||
@ a1 (r0) Initial value for a1
|
||||
@ a2 (r1) Initial value for a2
|
||||
@ a3 (r2) Initial value for a3
|
||||
@ a4 (r3) Initial value for a4
|
||||
@ v1 (r4) Initial value for v1
|
||||
@ v2 (r5) Initial value for v2
|
||||
@ v3 (r6) Initial value for v3
|
||||
@ v4 (r7) Initial value for v4
|
||||
@ v5 (r8) Initial value for v5
|
||||
@ sb (r9) Initial value for sb
|
||||
@ sl (r10) Initial value for sl
|
||||
@ fp (r11) Initial value for fp
|
||||
@ ip (r12) Initial value for ip
|
||||
@ lr (r14) Initial value for lr
|
||||
@ pc (r15) Initial value for pc
|
||||
@ 0 For stack backtracing
|
||||
@
|
||||
@ Stack Bottom: (higher memory address) */
|
||||
@
|
||||
LDR r2, [r0, #16] @ Pickup end of stack area
|
||||
BIC r2, r2, #7 @ Ensure 8-byte alignment
|
||||
SUB r2, r2, #76 @ Allocate space for the stack frame
|
||||
@
|
||||
@ /* Actually build the stack frame. */
|
||||
@
|
||||
MOV r3, #1 @ Build interrupt stack type
|
||||
STR r3, [r2, #0] @ Store stack type
|
||||
MOV r3, #0 @ Build initial register value
|
||||
STR r3, [r2, #8] @ Store initial r0
|
||||
STR r3, [r2, #12] @ Store initial r1
|
||||
STR r3, [r2, #16] @ Store initial r2
|
||||
STR r3, [r2, #20] @ Store initial r3
|
||||
STR r3, [r2, #24] @ Store initial r4
|
||||
STR r3, [r2, #28] @ Store initial r5
|
||||
STR r3, [r2, #32] @ Store initial r6
|
||||
STR r3, [r2, #36] @ Store initial r7
|
||||
STR r3, [r2, #40] @ Store initial r8
|
||||
STR r3, [r2, #44] @ Store initial r9
|
||||
LDR r3, [r0, #12] @ Pickup stack starting address
|
||||
STR r3, [r2, #48] @ Store initial r10 (sl)
|
||||
LDR r3,=_tx_thread_schedule @ Pickup address of _tx_thread_schedule for GDB backtrace
|
||||
STR r3, [r2, #60] @ Store initial r14 (lr)
|
||||
MOV r3, #0 @ Build initial register value
|
||||
STR r3, [r2, #52] @ Store initial r11
|
||||
STR r3, [r2, #56] @ Store initial r12
|
||||
STR r1, [r2, #64] @ Store initial pc
|
||||
STR r3, [r2, #68] @ 0 for back-trace
|
||||
MRS r1, CPSR @ Pickup CPSR
|
||||
BIC r1, r1, #CPSR_MASK @ Mask mode bits of CPSR
|
||||
@
|
||||
@ /* SYS mode, not SVC. In a module port no thread runs in SVC mode: SVC is
|
||||
@ reserved for the supervisor call handler, which is how a module reaches
|
||||
@ the kernel, and a thread sitting in SVC mode would be using the handler's
|
||||
@ banked stack pointer as its own. Kernel threads therefore start in SYS
|
||||
@ mode, which is privileged and shares User mode's banked sp -- the same sp
|
||||
@ a module thread uses -- so the context save and restore paths can reach a
|
||||
@ thread's stack the same way whoever it belongs to.
|
||||
@
|
||||
@ This is the one line that differs from the base port's stack build, and
|
||||
@ leaving it as SVC would have put kernel threads on the SVC stack while the
|
||||
@ restore path looked for them on the SYS one. */
|
||||
@
|
||||
ORR r3, r1, #SYS_MODE_BITS @ Build CPSR, SYS mode, interrupts enabled
|
||||
STR r3, [r2, #4] @ Store initial CPSR
|
||||
@
|
||||
@ /* Setup stack pointer. */
|
||||
@ thread_ptr -> tx_thread_stack_ptr = r2;
|
||||
@
|
||||
STR r2, [r0, #8] @ Save stack pointer in thread's
|
||||
@ control block
|
||||
#ifdef __THUMB_INTERWORK
|
||||
BX lr @ Return to caller
|
||||
#else
|
||||
MOV pc, lr @ Return to caller
|
||||
#endif
|
||||
@}
|
||||
|
||||
|
||||
@@ -0,0 +1,169 @@
|
||||
@/***************************************************************************
|
||||
@ * Copyright (c) 2024 Microsoft Corporation
|
||||
@ * 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.
|
||||
@ *
|
||||
@ * SPDX-License-Identifier: MIT
|
||||
@ **************************************************************************/
|
||||
@ Some portions generated by Claude Code (Opus 5).
|
||||
@
|
||||
@
|
||||
@/**************************************************************************/
|
||||
@/**************************************************************************/
|
||||
@/** */
|
||||
@/** ThreadX Component */
|
||||
@/** */
|
||||
@/** Thread */
|
||||
@/** */
|
||||
@/**************************************************************************/
|
||||
@/**************************************************************************/
|
||||
#ifdef TX_INCLUDE_USER_DEFINE_FILE
|
||||
#include "tx_user.h"
|
||||
#endif
|
||||
|
||||
.arm
|
||||
@
|
||||
@
|
||||
.global _tx_thread_current_ptr
|
||||
.global _tx_timer_time_slice
|
||||
.global _tx_thread_schedule
|
||||
.global _tx_execution_thread_exit
|
||||
@
|
||||
@
|
||||
@
|
||||
@/* Define the 16-bit Thumb mode veneer for _tx_thread_system_return for
|
||||
@ applications calling this function from to 16-bit Thumb mode. */
|
||||
@
|
||||
.text
|
||||
.align 2
|
||||
.global $_tx_thread_system_return
|
||||
.type $_tx_thread_system_return,function
|
||||
$_tx_thread_system_return:
|
||||
.thumb
|
||||
BX pc @ Switch to 32-bit mode
|
||||
NOP @
|
||||
.arm
|
||||
STMFD sp!, {lr} @ Save return address
|
||||
BL _tx_thread_system_return @ Call _tx_thread_system_return function
|
||||
LDMFD sp!, {lr} @ Recover saved return address
|
||||
BX lr @ Return to 16-bit caller
|
||||
@
|
||||
@
|
||||
.text
|
||||
.align 2
|
||||
@/**************************************************************************/
|
||||
@/* */
|
||||
@/* FUNCTION RELEASE */
|
||||
@/* */
|
||||
@/* _tx_thread_system_return Cortex-R52/GNU */
|
||||
@/* 6.5.2 */
|
||||
@/* AUTHOR */
|
||||
@/* */
|
||||
@/* Frédéric Desbiens, Eclipse Foundation */
|
||||
@/* */
|
||||
@/* Derived from the Cortex-R5/GNU port originally written by */
|
||||
@/* William E. Lamie, Microsoft Corporation. */
|
||||
@/* */
|
||||
@/* DESCRIPTION */
|
||||
@/* */
|
||||
@/* This function is target processor specific. It is used to transfer */
|
||||
@/* control from a thread back to the ThreadX system. Only a */
|
||||
@/* minimal context is saved since the compiler assumes temp registers */
|
||||
@/* are going to get slicked by a function call anyway. */
|
||||
@/* */
|
||||
@/* INPUT */
|
||||
@/* */
|
||||
@/* None */
|
||||
@/* */
|
||||
@/* OUTPUT */
|
||||
@/* */
|
||||
@/* None */
|
||||
@/* */
|
||||
@/* CALLS */
|
||||
@/* */
|
||||
@/* _tx_thread_schedule Thread scheduling loop */
|
||||
@/* */
|
||||
@/* CALLED BY */
|
||||
@/* */
|
||||
@/* ThreadX components */
|
||||
@/* */
|
||||
@/**************************************************************************/
|
||||
@VOID _tx_thread_system_return(VOID)
|
||||
@{
|
||||
.global _tx_thread_system_return
|
||||
.type _tx_thread_system_return,function
|
||||
_tx_thread_system_return:
|
||||
@
|
||||
@ /* Lockout interrupts. */
|
||||
@
|
||||
MRS r1, CPSR @ Pickup the CPSR
|
||||
#ifdef TX_ENABLE_FIQ_SUPPORT
|
||||
CPSID if @ Disable IRQ and FIQ interrupts
|
||||
#else
|
||||
CPSID i @ Disable IRQ interrupts
|
||||
#endif
|
||||
@ /* Save minimal context on the stack. */
|
||||
@
|
||||
STMDB sp!, {r4-r11, lr} @ Save minimal context
|
||||
LDR r5, =_tx_thread_current_ptr @ Pickup address of current ptr
|
||||
LDR r6, [r5, #0] @ Pickup current thread pointer
|
||||
@
|
||||
#ifdef TX_ENABLE_VFP_SUPPORT
|
||||
LDR r0, [r6, #144] @ Pickup the VFP enabled flag
|
||||
CMP r0, #0 @ Is the VFP enabled?
|
||||
BEQ _tx_skip_solicited_vfp_save @ No, skip VFP solicited save
|
||||
VMRS r4, FPSCR @ Pickup the FPSCR
|
||||
STR r4, [sp, #-4]! @ Save FPSCR
|
||||
VSTMDB sp!, {D8-D15} @ Save D8-D15
|
||||
_tx_skip_solicited_vfp_save:
|
||||
#endif
|
||||
@
|
||||
MOV r0, #0 @ Build a solicited stack type
|
||||
STMDB sp!, {r0-r1} @ Save type and CPSR
|
||||
@
|
||||
@
|
||||
#ifdef TX_ENABLE_EXECUTION_CHANGE_NOTIFY
|
||||
@
|
||||
@ /* Call the thread exit function to indicate the thread is no longer executing. */
|
||||
@
|
||||
BL _tx_execution_thread_exit @ Call the thread exit function
|
||||
#endif
|
||||
@
|
||||
LDR r2, =_tx_timer_time_slice @ Pickup address of time slice
|
||||
LDR r1, [r2, #0] @ Pickup current time slice
|
||||
@
|
||||
@ /* Save current stack and switch to system stack. */
|
||||
@ _tx_thread_current_ptr -> tx_thread_stack_ptr = sp;
|
||||
@ sp = _tx_thread_system_stack_ptr;
|
||||
@
|
||||
STR sp, [r6, #8] @ Save thread stack pointer
|
||||
@
|
||||
@ /* Determine if the time-slice is active. */
|
||||
@ if (_tx_timer_time_slice)
|
||||
@ {
|
||||
@
|
||||
MOV r4, #0 @ Build clear value
|
||||
CMP r1, #0 @ Is a time-slice active?
|
||||
BEQ __tx_thread_dont_save_ts @ No, don't save the time-slice
|
||||
@
|
||||
@ /* Save time-slice for the thread and clear the current time-slice. */
|
||||
@ _tx_thread_current_ptr -> tx_thread_time_slice = _tx_timer_time_slice;
|
||||
@ _tx_timer_time_slice = 0;
|
||||
@
|
||||
STR r4, [r2, #0] @ Clear time-slice
|
||||
STR r1, [r6, #24] @ Save current time-slice
|
||||
@
|
||||
@ }
|
||||
__tx_thread_dont_save_ts:
|
||||
@
|
||||
@ /* Clear the current thread pointer. */
|
||||
@ _tx_thread_current_ptr = TX_NULL;
|
||||
@
|
||||
STR r4, [r5, #0] @ Clear current thread pointer
|
||||
B _tx_thread_schedule @ Jump to scheduler!
|
||||
@
|
||||
@}
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
/***************************************************************************
|
||||
* Copyright (c) 2024 Microsoft Corporation
|
||||
* Copyright (c) 2026-present 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.
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
**************************************************************************/
|
||||
|
||||
// Some portions generated by Claude Code (Opus 5).
|
||||
|
||||
|
||||
/**************************************************************************/
|
||||
/**************************************************************************/
|
||||
/** */
|
||||
/** ThreadX Component */
|
||||
/** */
|
||||
/** Module Manager */
|
||||
/** */
|
||||
/**************************************************************************/
|
||||
/**************************************************************************/
|
||||
|
||||
#define TX_SOURCE_CODE
|
||||
|
||||
#include "tx_api.h"
|
||||
#include "txm_module.h"
|
||||
|
||||
|
||||
/**************************************************************************/
|
||||
/* */
|
||||
/* FUNCTION RELEASE */
|
||||
/* */
|
||||
/* _txm_module_manager_alignment_adjust Cortex-R52 */
|
||||
/* 6.1.8 */
|
||||
/* AUTHOR */
|
||||
/* */
|
||||
/* Scott Larson, Microsoft Corporation */
|
||||
/* */
|
||||
/* DESCRIPTION */
|
||||
/* */
|
||||
/* This function adjusts the alignment and size of the code and data */
|
||||
/* section for a given module implementation. */
|
||||
/* */
|
||||
/* INPUT */
|
||||
/* */
|
||||
/* module_preamble Pointer to module preamble */
|
||||
/* code_size Size of the code area (updated) */
|
||||
/* code_alignment Code area alignment (updated) */
|
||||
/* data_size Size of data area (updated) */
|
||||
/* data_alignment Data area alignment (updated) */
|
||||
/* */
|
||||
/* OUTPUT */
|
||||
/* */
|
||||
/* None */
|
||||
/* */
|
||||
/* CALLS */
|
||||
/* */
|
||||
/* None */
|
||||
/* */
|
||||
/* CALLED BY */
|
||||
/* */
|
||||
/* Initial thread stack frame */
|
||||
/* */
|
||||
/**************************************************************************/
|
||||
VOID _txm_module_manager_alignment_adjust(TXM_MODULE_PREAMBLE *module_preamble,
|
||||
ULONG *code_size,
|
||||
ULONG *code_alignment,
|
||||
ULONG *data_size,
|
||||
ULONG *data_alignment)
|
||||
{
|
||||
|
||||
/* The preamble is not read here, and the signature is not ours to change:
|
||||
_txm_module_manager_internal_load calls this through a fixed prototype
|
||||
shared by every port. Referenced and discarded so that the parameter is
|
||||
used, which is what MISRA C:2012 Rule 2.7 asks for and what
|
||||
-Wunused-parameter reports. A port that did have to grow or reposition a
|
||||
module -- the Cortex-R4 one, below -- reads it. */
|
||||
|
||||
(VOID)module_preamble;
|
||||
|
||||
/* Rounding to the granule is all this has to do, and that is a property of
|
||||
PMSAv8-R rather than a shortcut. The Cortex-R4 port's equivalent runs to
|
||||
183 lines because PMSAv7 regions must be a power of two in size and
|
||||
aligned to their own size, so a module's code and data have to be grown
|
||||
and repositioned to fit the nearest legal region. Base and limit pairs
|
||||
have no such constraint: any 64-byte-aligned extent is a legal region. */
|
||||
|
||||
/* Round code and data size UP to TXM_MODULE_MPU_ALIGNMENT bytes. */
|
||||
*code_size = (*code_size + TXM_MODULE_MPU_ALIGNMENT - 1) & ~(TXM_MODULE_MPU_ALIGNMENT - 1);
|
||||
*data_size = (*data_size + TXM_MODULE_MPU_ALIGNMENT - 1) & ~(TXM_MODULE_MPU_ALIGNMENT - 1);
|
||||
|
||||
/* Alignment for code and data is TXM_MODULE_MPU_ALIGNMENT bytes. */
|
||||
*code_alignment = TXM_MODULE_MPU_ALIGNMENT;
|
||||
*data_alignment = TXM_MODULE_MPU_ALIGNMENT;
|
||||
}
|
||||
+228
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
+139
@@ -0,0 +1,139 @@
|
||||
/***************************************************************************
|
||||
* Copyright (c) 2024 Microsoft Corporation
|
||||
* Copyright (c) 2026-present 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.
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
**************************************************************************/
|
||||
|
||||
|
||||
/**************************************************************************/
|
||||
/**************************************************************************/
|
||||
/** */
|
||||
/** ThreadX Component */
|
||||
/** */
|
||||
/** Module Manager */
|
||||
/** */
|
||||
/**************************************************************************/
|
||||
/**************************************************************************/
|
||||
|
||||
// Some portions generated by Claude Code (Opus 5).
|
||||
|
||||
#define TX_SOURCE_CODE
|
||||
|
||||
#include "tx_api.h"
|
||||
#include "tx_thread.h"
|
||||
#include "txm_module.h"
|
||||
|
||||
|
||||
/* This handler is architecture-neutral: it terminates the faulting thread and
|
||||
calls the notification callback. The fault registers are captured before it
|
||||
runs, in the abort vector, because DFSR, DFAR, IFSR and IFAR must be read
|
||||
before anything else can fault and overwrite them -- and on this core the
|
||||
abort is taken in Abort mode with its own banked lr and sp, so the capture has
|
||||
to happen there rather than here.
|
||||
|
||||
Data aborts and prefetch aborts both arrive here. A module can violate its
|
||||
protection either way: writing outside its data region, or branching outside
|
||||
its code region. Which pair of registers is meaningful depends on which it
|
||||
was, and the fault info structure carries both.
|
||||
|
||||
THIS FUNCTION RETURNS, AND THAT IS A REQUIREMENT ON THE ABORT VECTOR.
|
||||
|
||||
It terminates the faulting thread and then calls the application's notify
|
||||
callback. The second of those is only reached if _tx_thread_terminate returns,
|
||||
and terminating the RUNNING thread returns only when the kernel believes it is
|
||||
inside an exception: _tx_thread_terminate ends in
|
||||
_tx_thread_system_preempt_check, which calls _tx_thread_system_return whenever
|
||||
_tx_thread_system_state and _tx_thread_preempt_disable are both zero. On this
|
||||
architecture _tx_thread_system_return switches context immediately and never
|
||||
comes back, so the callback would be unreachable.
|
||||
|
||||
The body below is therefore left exactly as the cortex_m33, cortex_a7 and
|
||||
cortex_m7 ports have it, and the requirement is met where it belongs -- in
|
||||
txm_module_manager_fault_capture.S, which increments _tx_thread_system_state
|
||||
around this call, clears _tx_thread_current_ptr afterwards and returns into
|
||||
the scheduler. The Cortex-M ports need no such bracket because their
|
||||
_tx_thread_system_return only pends PendSV and returns. Anyone tempted to
|
||||
reorder the two statements below to "fix" a notify callback that does not fire
|
||||
should look at the abort vector first: the ordering here is upstream's and it
|
||||
is not the defect. */
|
||||
|
||||
/* Define the user's fault notification callback function pointer. This is
|
||||
setup via the txm_module_manager_memory_fault_notify API. */
|
||||
|
||||
VOID (*_txm_module_manager_fault_notify)(TX_THREAD *, TXM_MODULE_INSTANCE *);
|
||||
|
||||
|
||||
/* Define a macro that can be used to allocate global variables useful to
|
||||
store information about the last fault. This macro is defined in
|
||||
txm_module_port.h and is usually populated in the assembly language
|
||||
fault handling prior to the code calling _txm_module_manager_memory_fault_handler. */
|
||||
|
||||
TXM_MODULE_MANAGER_FAULT_INFO
|
||||
|
||||
|
||||
/**************************************************************************/
|
||||
/* */
|
||||
/* FUNCTION RELEASE */
|
||||
/* */
|
||||
/* _txm_module_manager_memory_fault_handler Cortex-R52 */
|
||||
/* 6.1.8 */
|
||||
/* AUTHOR */
|
||||
/* */
|
||||
/* Scott Larson, Microsoft Corporation */
|
||||
/* */
|
||||
/* DESCRIPTION */
|
||||
/* */
|
||||
/* This function handles a fault associated with a memory protected */
|
||||
/* module. */
|
||||
/* */
|
||||
/* INPUT */
|
||||
/* */
|
||||
/* None */
|
||||
/* */
|
||||
/* OUTPUT */
|
||||
/* */
|
||||
/* None */
|
||||
/* */
|
||||
/* CALLS */
|
||||
/* */
|
||||
/* _tx_thread_terminate Terminate thread */
|
||||
/* */
|
||||
/* CALLED BY */
|
||||
/* */
|
||||
/* Fault handler */
|
||||
/* */
|
||||
/**************************************************************************/
|
||||
VOID _txm_module_manager_memory_fault_handler(VOID)
|
||||
{
|
||||
|
||||
TXM_MODULE_INSTANCE *module_instance_ptr;
|
||||
TX_THREAD *thread_ptr;
|
||||
|
||||
/* Pickup the current thread. */
|
||||
thread_ptr = _tx_thread_current_ptr;
|
||||
|
||||
/* Initialize the module instance pointer to NULL. */
|
||||
module_instance_ptr = TX_NULL;
|
||||
|
||||
/* Is there a thread? */
|
||||
if (thread_ptr)
|
||||
{
|
||||
/* Pickup the module instance. */
|
||||
module_instance_ptr = thread_ptr -> tx_thread_module_instance_ptr;
|
||||
|
||||
/* Terminate the current thread. */
|
||||
_tx_thread_terminate(_tx_thread_current_ptr);
|
||||
}
|
||||
|
||||
/* Determine if there is a user memory fault notification callback. */
|
||||
if (_txm_module_manager_fault_notify)
|
||||
{
|
||||
/* Yes, call the user's notification memory fault callback. */
|
||||
(_txm_module_manager_fault_notify)(thread_ptr, module_instance_ptr);
|
||||
}
|
||||
}
|
||||
+78
@@ -0,0 +1,78 @@
|
||||
/***************************************************************************
|
||||
* Copyright (c) 2024 Microsoft Corporation
|
||||
* Copyright (c) 2026-present 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.
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
**************************************************************************/
|
||||
|
||||
|
||||
/**************************************************************************/
|
||||
/**************************************************************************/
|
||||
/** */
|
||||
/** ThreadX Component */
|
||||
/** */
|
||||
/** Module Manager */
|
||||
/** */
|
||||
/**************************************************************************/
|
||||
/**************************************************************************/
|
||||
|
||||
#define TX_SOURCE_CODE
|
||||
|
||||
#include "tx_api.h"
|
||||
#include "tx_thread.h"
|
||||
#include "txm_module.h"
|
||||
|
||||
|
||||
/* Define the external user's fault notification callback function pointer. This is
|
||||
setup via the txm_module_manager_memory_fault_notify API. */
|
||||
|
||||
extern VOID (*_txm_module_manager_fault_notify)(TX_THREAD *, TXM_MODULE_INSTANCE *);
|
||||
|
||||
|
||||
|
||||
/**************************************************************************/
|
||||
/* */
|
||||
/* FUNCTION RELEASE */
|
||||
/* */
|
||||
/* _txm_module_manager_memory_fault_notify Cortex-R52 */
|
||||
/* 6.1.8 */
|
||||
/* AUTHOR */
|
||||
/* */
|
||||
/* Scott Larson, Microsoft Corporation */
|
||||
/* */
|
||||
/* DESCRIPTION */
|
||||
/* */
|
||||
/* This function registers an application callback when/if a memory */
|
||||
/* fault occurs. The supplied thread is automatically terminated, but */
|
||||
/* any other threads in the same module may still execute. */
|
||||
/* */
|
||||
/* INPUT */
|
||||
/* */
|
||||
/* notify_function Memory fault notification */
|
||||
/* function, NULL disables. */
|
||||
/* */
|
||||
/* OUTPUT */
|
||||
/* */
|
||||
/* status Completion status */
|
||||
/* */
|
||||
/* CALLS */
|
||||
/* */
|
||||
/* None */
|
||||
/* */
|
||||
/* CALLED BY */
|
||||
/* */
|
||||
/* Application Code */
|
||||
/* */
|
||||
/**************************************************************************/
|
||||
UINT _txm_module_manager_memory_fault_notify(VOID (*notify_function)(TX_THREAD *, TXM_MODULE_INSTANCE *))
|
||||
{
|
||||
/* Setup notification function. */
|
||||
_txm_module_manager_fault_notify = notify_function;
|
||||
|
||||
/* Return success. */
|
||||
return(TX_SUCCESS);
|
||||
}
|
||||
+219
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
+251
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,133 @@
|
||||
@/***************************************************************************
|
||||
@ * 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
|
||||
@ **************************************************************************/
|
||||
@
|
||||
@/**************************************************************************/
|
||||
@/* */
|
||||
@/* MODULE MANAGER RELEASE */
|
||||
@/* */
|
||||
@/* txm_module_manager_user_mode_entry.S Cortex-R52/GNU */
|
||||
@/* 6.5.2 */
|
||||
@/* AUTHOR */
|
||||
@/* */
|
||||
@/* Frédéric Desbiens, Eclipse Foundation */
|
||||
@/* */
|
||||
@/* DESCRIPTION */
|
||||
@/* */
|
||||
@/* The only way a module reaches the kernel. */
|
||||
@/* */
|
||||
@/* A module runs in User mode and cannot execute kernel code or touch */
|
||||
@/* kernel memory. When it calls a ThreadX service, the call arrives */
|
||||
@/* here: SVC 1 raises privilege, the dispatch function performs the */
|
||||
@/* service, SVC 2 drops back to User mode, and the module continues. */
|
||||
@/* */
|
||||
@/* This function is the entire privileged surface a module can see. */
|
||||
@/* It gets an MPU region of its own -- the one at */
|
||||
@/* TXM_MODULE_MPU_KERNEL_ENTRY_INDEX -- because a module must be able */
|
||||
@/* to execute these few instructions and nothing else on that side of */
|
||||
@/* the boundary. Everything the module is allowed to ask for is */
|
||||
@/* decided inside _txm_module_manager_kernel_dispatch, in kernel */
|
||||
@/* memory the module cannot reach. */
|
||||
@/* */
|
||||
@/* SVC 1 and SVC 2 are handled by the port's supervisor call vector. */
|
||||
@/* */
|
||||
@/* CALLS */
|
||||
@/* */
|
||||
@/* SVC 1 Leave User mode */
|
||||
@/* _txm_module_manager_kernel_dispatch Perform the service */
|
||||
@/* SVC 2 Return to User mode */
|
||||
@/* */
|
||||
@/* CALLED BY */
|
||||
@/* */
|
||||
@/* Modules in User mode */
|
||||
@/* */
|
||||
@/**************************************************************************/
|
||||
|
||||
.arm
|
||||
|
||||
@ Its own section, placed after __code_end__ by the linker script, so that this
|
||||
@ function sits OUTSIDE the kernel's code region.
|
||||
@
|
||||
@ It has to. A module runs in User mode and the kernel code region grants no
|
||||
@ EL0 access, so the module needs a region of its own covering these
|
||||
@ instructions -- and PMSAv8-R has no region priority, so that region must not
|
||||
@ overlap the kernel's. An access that hits more than one enabled region takes
|
||||
@ a translation fault (TRM 8.1); the abort is specified, not a part-specific
|
||||
@ resolution of unpredictable behaviour. With this function still inside
|
||||
@ .text, the first memory access after a module's regions were loaded took a
|
||||
@ data abort in the scheduler.
|
||||
@
|
||||
@ Nothing else may share the section, because whatever does becomes executable
|
||||
@ by every module.
|
||||
|
||||
.section .txm_user_entry, "ax", %progbits
|
||||
|
||||
@ 64-byte aligned, which is the PMSAv8-R granule and all that is needed here.
|
||||
@
|
||||
@ The Cortex-R4 module port aligns this to 4 KB, and has to: PMSAv7 regions must
|
||||
@ be a power of two in size and aligned to their own size, so the smallest
|
||||
@ region that can cover this function without also covering its neighbours is a
|
||||
@ page. A base and limit pair has no such constraint, so the kernel entry
|
||||
@ region can be sized to these instructions and stop. That matters for
|
||||
@ isolation rather than for memory: on PMSAv7 whatever else shares the page is
|
||||
@ inside the one region a module is allowed to execute.
|
||||
|
||||
.align 6
|
||||
|
||||
.global _txm_module_manager_user_mode_entry
|
||||
.global _txm_module_manager_user_mode_entry_end
|
||||
|
||||
@ The supervisor call handler compares the faulting lr against these two, to
|
||||
@ refuse an SVC 1 or SVC 2 raised from anywhere else. They have to be visible
|
||||
@ outside this file for that check to link, which also means they are the two
|
||||
@ addresses the whole privilege boundary rests on.
|
||||
|
||||
.global _txm_system_mode_enter
|
||||
.global _txm_system_mode_exit
|
||||
.extern _txm_module_manager_kernel_dispatch
|
||||
|
||||
.type _txm_module_manager_user_mode_entry, %function
|
||||
_txm_module_manager_user_mode_entry:
|
||||
|
||||
_txm_system_mode_enter:
|
||||
|
||||
@ Leave User mode. The supervisor call vector recognises 1 and raises the
|
||||
@ thread to privileged mode on its kernel stack.
|
||||
|
||||
SVC 1
|
||||
|
||||
_txm_module_priv:
|
||||
|
||||
@ Privileged now. r3 is pushed alongside lr only to keep the stack eight-byte
|
||||
@ aligned, which the ABI requires at a public interface.
|
||||
|
||||
PUSH {r3, lr}
|
||||
BL _txm_module_manager_kernel_dispatch
|
||||
POP {r3, lr}
|
||||
|
||||
_txm_system_mode_exit:
|
||||
|
||||
@ Back to User mode before returning to the module. If this were skipped the
|
||||
@ module would resume privileged, which is the whole protection gone -- so it is
|
||||
@ unconditional and sits between the dispatch and every return path.
|
||||
|
||||
SVC 2
|
||||
|
||||
BX lr
|
||||
|
||||
@ Marks the end of the region a module is allowed to execute, so the region can
|
||||
@ be sized to this function rather than rounded up to something larger.
|
||||
|
||||
_txm_module_manager_user_mode_entry_end:
|
||||
NOP
|
||||
Reference in New Issue
Block a user