Skip to content

Added the boot-at-EL1 option to the S32Z280 entry path - #690

Merged
fdesbiens merged 1 commit into
eclipse-threadx:devfrom
fdesbiens:feature/zonex-guest-support
Sep 2, 2026
Merged

Added the boot-at-EL1 option to the S32Z280 entry path#690
fdesbiens merged 1 commit into
eclipse-threadx:devfrom
fdesbiens:feature/zonex-guest-support

Conversation

@fdesbiens

Copy link
Copy Markdown
Contributor

What this is

The Armv8-R AEM FVP entry path has carried TX_R52_BOOT_AT_EL1 since it was written, for the case its own comment describes: "an earlier boot stage or a vendor EL2 monitor has already dropped privilege to EL1". The S32Z280 entry path did not, so a kernel could not be built as a guest on that board at all -- and it is the board where that matters most, because it is the one with silicon behind it.

The bracket is the whole change. Everything from the Thumb reset trampoline to the ERET goes inside #ifndef TX_R52_BOOT_AT_EL1, and the #else supplies a one-instruction A32 _start that branches to el1_entry.

One file, additive, and no standalone build changes behaviour: with the option undefined the preprocessor produces the same code it produced before. Checked rather than asserted -- the assembled object has the same sections at the same sizes (.text.boot is 0x66c either way) and disassembles identically.

A32 and not T32

This is the one real difference from the standalone entry, and it is worth stating because getting it wrong does not fail where you would look.

The core resets in Thumb state on this board, because the boot instruction the RTU is given is a T32 branch. But a guest is not reached by reset -- it is reached by the monitor's ERET, and the monitor chooses the execution state through SPSR.T. If the two disagree, the guest dies on its first instruction with an undefined-instruction exception that looks exactly like a bad entry address, and sends the reader to the loader instead of to the ERET.

What the monitor inherits

Enumerated at the #ifndef itself, next to the code it replaces rather than in a document, because that is where somebody adding a third board will be looking. This board's EL2 block is considerably larger than the model's, and each item is something a guest at EL1 provably cannot do rather than something it merely does not:

  • CNTFRQ -- writable only at the highest implemented exception level, and it reads zero out of reset here, so a guest deriving a tick interval from it divides by zero.
  • HCPTR.TCP10/TCP11 -- both reset set, trapping every EL1 and EL0 floating-point access to EL2.
  • HSCTLR.TE -- an EL2 register. (SCTLR.TE is EL1's, and el1_entry still clears that itself.)
  • ICC_HSRE.SRE -- until it is set, every other ICC_* and ICH_* system register is UNDEFINED, so an EL1 kernel cannot acknowledge an interrupt at all.
  • IMP_PERIPHPREGIONR -- the low-latency peripheral port enables reset to zero, and an EL1 write to that register traps to EL2 when HACTLR.PERIPHPREGIONR is clear.
  • IMP_ATCMREGIONR / IMP_BTCMREGIONR -- the TCM enables are per-core and ENABLEEL2 is silently ignored when written from EL1. Measured on this part, on both BTCM and CTCM: the base took and bit 0 took while bit 1 stayed clear. A guest that places anything in a TCM therefore needs the monitor to program and ECC-preload it, because ECC is enabled here and a TCM location must be written before it can be read.

CNTHCTL.PL1PCTEN and PL1PCEN are the deliberate omission from that list. This path opens both, because a standalone kernel owns the physical timer. A monitor that time-partitions its guests must not: a partition's physical time keeps running while it is descheduled, so a guest reading it can observe that it was not running. Such a monitor hands out the virtual timer and a per-guest CNTVOFF instead. That is the monitor's decision rather than this file's, which is why the list says what a guest cannot do rather than what a monitor should do.

Verification

Both ways.

  • The three standalone S32Z280 images build and link unchanged, and the assembled entry object is identical to the one this branch started from.
  • A kernel built with the option boots at EL1 on an S32Z280-594EVB under an EL2 monitor, runs two threads through a queue and a semaphore, and reports back. It has since been run preemptively there as well -- servicing the virtual timer's PPI through this port's own TX_R52_USE_THREADX_IRQ path -- with no change to the kernel or to ports/cortex_r52 beyond this one bracket.

The Armv8-R AEM FVP entry path has carried TX_R52_BOOT_AT_EL1 since it
was written, for the case its own comment describes: "an earlier boot
stage or a vendor EL2 monitor has already dropped privilege to EL1".
This board's entry path did not, so a kernel could not be built as a
guest on it at all -- and it is the board where that matters most,
because it is the one with silicon behind it.

The bracket is the whole change.  Everything from the Thumb reset
trampoline to the ERET goes inside #ifndef TX_R52_BOOT_AT_EL1, and the
#else supplies a one-instruction A32 _start that branches to el1_entry.

A32 AND NOT T32, which is the one real difference from the standalone
entry.  The core resets in Thumb state here because the RTU boot
instruction NXP plants is a T32 branch, but a guest is not reached by
reset: it is reached by the monitor's ERET, and the monitor chooses the
state through SPSR.T.  Get the two out of agreement and the guest dies
on its first instruction with an undefined-instruction exception, which
looks exactly like a bad entry address and sends the reader to the
loader instead of to the ERET.

WHAT THE MONITOR INHERITS is enumerated at the #ifndef, next to the code
it replaces rather than in a document, because that is where somebody
adding a third board will be looking.  This board's EL2 block is
considerably larger than the model's, and each item on the list is
something a guest at EL1 provably cannot do rather than something it
merely does not: CNTFRQ is writable only at the highest implemented
exception level and reads zero out of reset; HCPTR.TCP10/TCP11 reset
set, trapping every EL1 floating-point access; HSCTLR.TE is an EL2
register (SCTLR.TE is EL1's, and el1_entry still clears it below);
ICC_HSRE.SRE makes every other ICC_* and ICH_* register exist at all;
the low-latency peripheral port enables reset to zero and an EL1 write
to that register traps to EL2; and the TCM enables are per-core with
ENABLEEL2 SILENTLY IGNORED from EL1 -- measured on both BTCM and CTCM,
the base took and bit 0 took while bit 1 stayed clear.

CNTHCTL.PL1PCTEN and PL1PCEN are the deliberate omission from that list,
and the note says why.  This path opens both, because a standalone
kernel owns the physical timer.  A monitor that TIME-partitions its
guests must not: a partition's physical time keeps running while it is
descheduled, so a guest reading it can observe that it was not running.
That is the monitor's decision rather than this file's, which is why the
list says what a guest cannot do rather than what a monitor should.

Verified both ways.  The three standalone images build and link
unchanged, and a kernel built with the option boots at EL1 on a
S32Z280-594EVB under an EL2 monitor, runs two threads through a queue
and a semaphore, and reports back -- with no other change to the kernel
or to its port.

Assisted-by: Claude Code (Opus 5) <noreply@anthropic.com>
@fdesbiens
fdesbiens merged commit 13c8c76 into eclipse-threadx:dev Sep 2, 2026
12 checks passed
@fdesbiens
fdesbiens deleted the feature/zonex-guest-support branch September 2, 2026 23:08
@fdesbiens
fdesbiens restored the feature/zonex-guest-support branch September 2, 2026 23:14
@fdesbiens
fdesbiens deleted the feature/zonex-guest-support branch September 2, 2026 23:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant