arch: armv6-m: Apply armv7-m signal handling logic

Summary:
- This commit applies armv7-m signal handling logic

Impact:
- armv6-m signal handling

Testing:
- Tested with ostest with the following configs
- raspberrypi-pico:nsh, raspberrypi-pico:smp

Signed-off-by: Masayuki Ishikawa <Masayuki.Ishikawa@jp.sony.com>
This commit is contained in:
Masayuki Ishikawa 2021-02-26 21:35:03 +09:00 committed by Xiang Xiao
parent 40fdf388bd
commit ef1826e133
2 changed files with 291 additions and 25 deletions

View File

@ -52,6 +52,8 @@
#include "arm_internal.h"
#include "arm_arch.h"
#include "irq/irq.h"
/****************************************************************************
* Public Functions
****************************************************************************/
@ -92,13 +94,15 @@
*
****************************************************************************/
#ifndef CONFIG_SMP
void up_schedule_sigaction(struct tcb_s *tcb, sig_deliver_t sigdeliver)
{
sinfo("tcb=0x%p sigdeliver=0x%p\n", tcb, sigdeliver);
DEBUGASSERT(tcb != NULL && sigdeliver != NULL);
/* Refuse to handle nested signal actions */
if (!tcb->xcp.sigdeliver)
if (tcb->xcp.sigdeliver == NULL)
{
/* First, handle some special cases when the signal is being delivered
* to the currently executing task.
@ -109,12 +113,14 @@ void up_schedule_sigaction(struct tcb_s *tcb, sig_deliver_t sigdeliver)
if (tcb == this_task())
{
/* CASE 1: We are not in an interrupt handler and a task is
* signalling itself for some reason.
* signaling itself for some reason.
*/
if (!CURRENT_REGS)
{
/* In this case just deliver the signal now. */
/* In this case just deliver the signal now.
* REVISIT: Signal handle will run in a critical section!
*/
sigdeliver(tcb);
}
@ -127,28 +133,30 @@ void up_schedule_sigaction(struct tcb_s *tcb, sig_deliver_t sigdeliver)
else
{
/* Save the return PC, CPSR, and PRIMASK registers (and
* perhaps the LR). These will be restored by the signal
* trampoline after the signal has been delivered.
/* Save the return PC, CPSR and PRIMASK
* registers (and perhaps also the LR). These will be
* restored by the signal trampoline after the signal has been
* delivered.
*/
tcb->xcp.sigdeliver = sigdeliver;
tcb->xcp.saved_pc = CURRENT_REGS[REG_PC];
tcb->xcp.saved_primask = CURRENT_REGS[REG_PRIMASK];
tcb->xcp.saved_xpsr = CURRENT_REGS[REG_XPSR];
tcb->xcp.sigdeliver = (FAR void *)sigdeliver;
tcb->xcp.saved_pc = CURRENT_REGS[REG_PC];
tcb->xcp.saved_primask = CURRENT_REGS[REG_PRIMASK];
tcb->xcp.saved_xpsr = CURRENT_REGS[REG_XPSR];
#ifdef CONFIG_BUILD_PROTECTED
tcb->xcp.saved_lr = CURRENT_REGS[REG_LR];
tcb->xcp.saved_lr = CURRENT_REGS[REG_LR];
#endif
/* Then set up to vector to the trampoline with interrupts
* disabled. The kernel-space trampoline must run in
* privileged thread mode.
*/
CURRENT_REGS[REG_PC] = (uint32_t)arm_sigdeliver;
CURRENT_REGS[REG_PRIMASK] = 1;
CURRENT_REGS[REG_XPSR] = ARMV6M_XPSR_T;
CURRENT_REGS[REG_PC] = (uint32_t)arm_sigdeliver;
CURRENT_REGS[REG_PRIMASK] = 1;
CURRENT_REGS[REG_XPSR] = ARMV6M_XPSR_T;
#ifdef CONFIG_BUILD_PROTECTED
CURRENT_REGS[REG_LR] = EXC_RETURN_PRIVTHR;
CURRENT_REGS[REG_LR] = EXC_RETURN_PRIVTHR;
CURRENT_REGS[REG_EXC_RETURN] = EXC_RETURN_PRIVTHR;
#endif
/* And make sure that the saved context in the TCB is the same
* as the interrupt return context.
@ -158,25 +166,215 @@ void up_schedule_sigaction(struct tcb_s *tcb, sig_deliver_t sigdeliver)
}
}
/* Otherwise, we are (1) signalling a task is not running from an
/* Otherwise, we are (1) signaling a task is not running from an
* interrupt handler or (2) we are not in an interrupt handler and the
* running task is signalling some non-running task.
* running task is signaling* some non-running task.
*/
else
{
/* Save the return PS, CPSR and PRIMASK register (a perhaps also
* the LR). These will be restored by the signal trampoline after
* the signal has been delivered.
/* Save the return PC, CPSR and PRIMASK
* registers (and perhaps also the LR). These will be restored
* by the signal trampoline after the signal has been delivered.
*/
tcb->xcp.sigdeliver = sigdeliver;
tcb->xcp.saved_pc = tcb->xcp.regs[REG_PC];
tcb->xcp.saved_primask = tcb->xcp.regs[REG_PRIMASK];
tcb->xcp.saved_xpsr = tcb->xcp.regs[REG_XPSR];
tcb->xcp.sigdeliver = (FAR void *)sigdeliver;
tcb->xcp.saved_pc = tcb->xcp.regs[REG_PC];
tcb->xcp.saved_primask = tcb->xcp.regs[REG_PRIMASK];
tcb->xcp.saved_xpsr = tcb->xcp.regs[REG_XPSR];
#ifdef CONFIG_BUILD_PROTECTED
tcb->xcp.saved_lr = tcb->xcp.regs[REG_LR];
tcb->xcp.saved_lr = tcb->xcp.regs[REG_LR];
#endif
/* Then set up to vector to the trampoline with interrupts
* disabled. We must already be in privileged thread mode to be
* here.
*/
tcb->xcp.regs[REG_PC] = (uint32_t)arm_sigdeliver;
tcb->xcp.regs[REG_PRIMASK] = 1;
tcb->xcp.regs[REG_XPSR] = ARMV6M_XPSR_T;
#ifdef CONFIG_BUILD_PROTECTED
tcb->xcp.regs[REG_LR] = EXC_RETURN_PRIVTHR;
#endif
}
}
}
#endif /* !CONFIG_SMP */
#ifdef CONFIG_SMP
void up_schedule_sigaction(struct tcb_s *tcb, sig_deliver_t sigdeliver)
{
int cpu;
int me;
sinfo("tcb=0x%p sigdeliver=0x%p\n", tcb, sigdeliver);
/* Refuse to handle nested signal actions */
if (!tcb->xcp.sigdeliver)
{
/* First, handle some special cases when the signal is being delivered
* to task that is currently executing on any CPU.
*/
sinfo("rtcb=0x%p CURRENT_REGS=0x%p\n", this_task(), CURRENT_REGS);
if (tcb->task_state == TSTATE_TASK_RUNNING)
{
me = this_cpu();
cpu = tcb->cpu;
/* CASE 1: We are not in an interrupt handler and a task is
* signaling itself for some reason.
*/
if (cpu == me && !CURRENT_REGS)
{
/* In this case just deliver the signal now.
* REVISIT: Signal handler will run in a critical section!
*/
sigdeliver(tcb);
}
/* CASE 2: The task that needs to receive the signal is running.
* This could happen if the task is running on another CPU OR if
* we are in an interrupt handler and the task is running on this
* CPU. In the former case, we will have to PAUSE the other CPU
* first. But in either case, we will have to modify the return
* state as well as the state in the TCB.
*/
else
{
/* If we signaling a task running on the other CPU, we have
* to PAUSE the other CPU.
*/
if (cpu != me)
{
/* Pause the CPU */
up_cpu_pause(cpu);
/* Wait while the pause request is pending */
while (up_cpu_pausereq(cpu))
{
}
/* Now tcb on the other CPU can be accessed safely */
/* Copy tcb->xcp.regs to tcp.xcp.saved. These will be
* restored by the signal trampoline after the signal has
* been delivered.
*/
tcb->xcp.sigdeliver = (FAR void *)sigdeliver;
tcb->xcp.saved_pc = tcb->xcp.regs[REG_PC];
tcb->xcp.saved_primask = tcb->xcp.regs[REG_PRIMASK];
tcb->xcp.saved_xpsr = tcb->xcp.regs[REG_XPSR];
#ifdef CONFIG_BUILD_PROTECTED
tcb->xcp.saved_lr = tcb->xcp.regs[REG_LR];
#endif
/* Then set up vector to the trampoline with interrupts
* disabled. We must already be in privileged thread mode
* to be here.
*/
tcb->xcp.regs[REG_PC] = (uint32_t)arm_sigdeliver;
tcb->xcp.regs[REG_PRIMASK] = 1;
tcb->xcp.regs[REG_XPSR] = ARMV6M_XPSR_T;
#ifdef CONFIG_BUILD_PROTECTED
tcb->xcp.regs[REG_LR] = EXC_RETURN_PRIVTHR;
#endif
}
else
{
/* tcb is running on the same CPU */
/* Save the return PC, CPSR and either the BASEPRI or
* PRIMASK registers (and perhaps also the LR). These
* will be restored by the signal trampoline after the
* signal has been delivered.
*/
tcb->xcp.sigdeliver = (FAR void *)sigdeliver;
tcb->xcp.saved_pc = CURRENT_REGS[REG_PC];
tcb->xcp.saved_primask = CURRENT_REGS[REG_PRIMASK];
tcb->xcp.saved_xpsr = CURRENT_REGS[REG_XPSR];
#ifdef CONFIG_BUILD_PROTECTED
tcb->xcp.saved_lr = CURRENT_REGS[REG_LR];
#endif
/* Then set up vector to the trampoline with interrupts
* disabled. The kernel-space trampoline must run in
* privileged thread mode.
*/
CURRENT_REGS[REG_PC] = (uint32_t)arm_sigdeliver;
CURRENT_REGS[REG_PRIMASK] = 1;
CURRENT_REGS[REG_XPSR] = ARMV6M_XPSR_T;
#ifdef CONFIG_BUILD_PROTECTED
CURRENT_REGS[REG_LR] = EXC_RETURN_PRIVTHR;
#endif
/* And make sure that the saved context in the TCB is the
* same as the interrupt return context.
*/
arm_savestate(tcb->xcp.regs);
}
/* Increment the IRQ lock count so that when the task is
* restarted, it will hold the IRQ spinlock.
*/
DEBUGASSERT(tcb->irqcount < INT16_MAX);
tcb->irqcount++;
/* NOTE: If the task runs on another CPU(cpu), adjusting
* global IRQ controls will be done in the pause handler
* on the CPU(cpu) by taking a critical section.
* If the task is scheduled on this CPU(me), do nothing
* because this CPU already took a critical section
*/
/* RESUME the other CPU if it was PAUSED */
if (cpu != me)
{
up_cpu_resume(cpu);
}
}
}
/* Otherwise, we are (1) signaling a task is not running from an
* interrupt handler or (2) we are not in an interrupt handler and the
* running task is signaling some other non-running task.
*/
else
{
/* Save the return PC, CPSR and either the BASEPRI or PRIMASK
* registers (and perhaps also the LR). These will be restored
* by the signal trampoline after the signal has been delivered.
*/
tcb->xcp.sigdeliver = (FAR void *)sigdeliver;
tcb->xcp.saved_pc = tcb->xcp.regs[REG_PC];
tcb->xcp.saved_primask = tcb->xcp.regs[REG_PRIMASK];
tcb->xcp.saved_xpsr = tcb->xcp.regs[REG_XPSR];
#ifdef CONFIG_BUILD_PROTECTED
tcb->xcp.saved_lr = tcb->xcp.regs[REG_LR];
#endif
/* Increment the IRQ lock count so that when the task is restarted,
* it will hold the IRQ spinlock.
*/
DEBUGASSERT(tcb->irqcount < INT16_MAX);
tcb->irqcount++;
/* Then set up to vector to the trampoline with interrupts
* disabled. We must already be in privileged thread mode to be
@ -192,3 +390,4 @@ void up_schedule_sigaction(struct tcb_s *tcb, sig_deliver_t sigdeliver)
}
}
}
#endif /* CONFIG_SMP */

View File

@ -83,6 +83,15 @@ void arm_sigdeliver(void)
int saved_errno = get_errno();
#ifdef CONFIG_SMP
/* In the SMP case, we must terminate the critical section while the signal
* handler executes, but we also need to restore the irqcount when the
* we resume the main thread of the task.
*/
int16_t saved_irqcount;
#endif
board_autoled_on(LED_SIGNAL);
sinfo("rtcb=%p sigdeliver=%p sigpendactionq.head=%p\n",
@ -93,6 +102,26 @@ void arm_sigdeliver(void)
arm_copyfullstate(regs, rtcb->xcp.regs);
#ifdef CONFIG_SMP
/* In the SMP case, up_schedule_sigaction(0) will have incremented
* 'irqcount' in order to force us into a critical section. Save the
* pre-incremented irqcount.
*/
saved_irqcount = rtcb->irqcount - 1;
DEBUGASSERT(saved_irqcount >= 0);
/* Now we need call leave_critical_section() repeatedly to get the irqcount
* to zero, freeing all global spinlocks that enforce the critical section.
*/
do
{
leave_critical_section((uint16_t)regs[REG_PRIMASK]);
}
while (rtcb->irqcount > 0);
#endif /* CONFIG_SMP */
#ifndef CONFIG_SUPPRESS_INTERRUPTS
/* Then make sure that interrupts are enabled. Signal handlers must always
* run with interrupts enabled.
@ -108,10 +137,32 @@ void arm_sigdeliver(void)
/* Output any debug messages BEFORE restoring errno (because they may
* alter errno), then disable interrupts again and restore the original
* errno that is needed by the user logic (it is probably EINTR).
*
* I would prefer that all interrupts are disabled when
* arm_fullcontextrestore() is called, but that may not be necessary.
*/
sinfo("Resuming\n");
/* Call enter_critical_section() to disable local interrupts before
* restoring local context.
*
* Here, we should not use up_irq_save() in SMP mode.
* For example, if we call up_irq_save() here and another CPU might
* have called up_cpu_pause() to this cpu, hence g_cpu_irqlock has
* been locked by the cpu, in this case, we would see a deadlock in
* later call of enter_critical_section() to restore irqcount.
* To avoid this situation, we need to call enter_critical_section().
*/
#ifdef CONFIG_SMP
enter_critical_section();
#else
up_irq_save();
#endif
/* Restore the saved errno value */
set_errno(saved_errno);
/* Modify the saved return state with the actual saved values in the
@ -132,6 +183,22 @@ void arm_sigdeliver(void)
#endif
rtcb->xcp.sigdeliver = NULL; /* Allows next handler to be scheduled */
#ifdef CONFIG_SMP
/* Restore the saved 'irqcount' and recover the critical section
* spinlocks.
*
* REVISIT: irqcount should be one from the above call to
* enter_critical_section(). Could the saved_irqcount be zero? That
* would be a problem.
*/
DEBUGASSERT(rtcb->irqcount == 1);
while (rtcb->irqcount < saved_irqcount)
{
enter_critical_section();
}
#endif
/* Then restore the correct state for this thread of
* execution.
*/