This was a little embarassing. The swap code got this right, and the
interrupt exit path got it right, but on entry we weren't ever saving
the shift and loop registers for the interrupted context.
This almost always worked anyway as the loop registers aren't ever
used in any Zephyr code (gcc won't generate this style of loop AFAICT)
and the SAR shift amount register is generally used only in two pairs
of adjacent instructions making the chance of hitting that exact cycle
quite low in general.
But of course we have shift-happy crypto code in our tests, so this
got caught, thankfully.
See https://github.com/zephyrproject-rtos/zephyr/issues/6470
Signed-off-by: Andy Ross <andrew.j.ross@intel.com>
When returning into a different thread than we interrupted, we
obviously need to spill all the existing register windows to make sure
all their values are in the old thread's stack. But the code to do
this forgot to reset the current stack pointer to the value it had at
interrupt time (it was still pointing to the saved context below
that), so the caller of the interrupted function was spilling to the
wrong spot.
This wouldn't show up as an instant failure, it would only happen when
switching BACK to the improperly-spilled thread. And even then it
would be a noop if the original interrupt handler was deep enough to
have spilled that function naturally.
In practice, this happened only in some instances on ESP-32 (which has
more windowed registers than qemu) when interrupting the idle thread
(which is very shallow) with a (very simple) timer interrupt. Trivial
to see, hard to find.
See https://github.com/zephyrproject-rtos/zephyr/issues/6346 for more
detail.
Signed-off-by: Andy Ross <andrew.j.ross@intel.com>
Xtensa register windows have a special exception that happens when the
stack pointer needs to be moved, but the caller function has already
spilled its registers below it.
I thought these were unexercised in Zephyr code, but they turn out to
be thrown by the existing mem_pool tests when run in the 32-register
qemu environment (but not on 64-register hardwre). Because the effect
of the exception is to unspill the caller, there is no good way to
handle this in a traditional handler. Instead put a 5-instruction
stub in front of the user exception handler (i.e. incurring that cost
on every trap and every L1 interrupt) to test before doing the normal
entry.
Works, but would be nicer to optimize this in the future so that only
true alloca exceptions take that cost.
Signed-off-by: Andy Ross <andrew.j.ross@intel.com>
This adds vectors for all interrupt levels defined by core-isa.h.
Modify the entry code a little bit to select correct linker sections
(levels 1, 6 and 7 get special names for... no particularly good
reason) and to constructed the interrupted PS value correctly (no EPS1
register for exceptions since they had to have interrupted level 0
code and thus differ only in the EXCM bit).
Signed-off-by: Andy Ross <andrew.j.ross@intel.com>
SMP needs a new context switch primitive (to disentangle _swap() from
the scheduler) and new interrupt entry behavior (to be able to take a
global spinlock on behalf of legacy drivers). The existing code is
very obtuse, and working with it led me down a long path of "this
would be so much better if..." So this is a new context and entry
framework, intended to replace the code that exists now, at least on
SMP platforms.
New features:
* The new context switch primitive is xtensa_switch(), which takes a
"new" context handle as an argument instead of getting it from the
scheduler, returns an "old" context handle through a pointer
(e.g. to save it to the old thread context), and restores the lock
state(PS register) exactly as it is at entry instead of taking it as
an argument.
* The register spill code understands wrap-around register windows and
can avoid spilling A4-A15 registers when they are unused by the
interrupted function, saving as much as 48 bytes of stack space on
the interrupted stacks.
* The "spill register windows" routine is entirely different, using a
different mechanism, and is MUCH FASTER (to the tune of almost 200
cycles). See notes in comments.
* Even better, interrupt entry can be done via a clever "cross stack
call" I worked up, meaning that the interrupted thread's registers
do not need to be spilled at all until they are naturally pushed out
by the interrupt handler or until we return from the interrupt into
a different thread. This is a big efficiency win for tiny
interrupts (e.g. timers), and a big latency win for all interrupts.
* Interrupt entry is 100% symmetric with respect to medium/high
interrupts, avoiding the problems seen with hooking high priority
interrupts with the current code (e.g. ESP-32's watchdog driver).
* Much smaller code size. No cut and paste assembly. No use of HAL
calls.
* Assumes "XEA2" interrupt architecture, the register window extension
(i.e. no CALL0 ABI), and the "high priority interrupts" extension.
Does not support the legacy processor variants for which we have no
targets. The old code has some stuff in there to support this, but
it seems bitrotten, untestable, and I'm all but certain it doesn't
work.
Note that this simply adds the primitives to the existing tree in a
form where they can be unit tested. It does not replace the existing
interrupt/exception handling or _Swap() implementation.
Signed-off-by: Andy Ross <andrew.j.ross@intel.com>