diff --git a/arch/arm/src/armv6-m/arm_schedulesigaction.c b/arch/arm/src/armv6-m/arm_schedulesigaction.c index 78f881f9b49..00b2fc18f72 100644 --- a/arch/arm/src/armv6-m/arm_schedulesigaction.c +++ b/arch/arm/src/armv6-m/arm_schedulesigaction.c @@ -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 */ diff --git a/arch/arm/src/armv6-m/arm_sigdeliver.c b/arch/arm/src/armv6-m/arm_sigdeliver.c index 4fc1fc5f3fd..186b843adb5 100644 --- a/arch/arm/src/armv6-m/arm_sigdeliver.c +++ b/arch/arm/src/armv6-m/arm_sigdeliver.c @@ -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. */