/**************************************************************************** * arch/arm/src/armv7-a/arm_cpupause.c * * Copyright (C) 2016 Gregory Nutt. All rights reserved. * Author: Gregory Nutt * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * 3. Neither the name NuttX nor the names of its contributors may be * used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * ****************************************************************************/ /**************************************************************************** * Included Files ****************************************************************************/ #include #include #include #include #include #include "up_internal.h" #include "gic.h" #include "sched/sched.h" #ifdef CONFIG_SMP /**************************************************************************** * Private Data ****************************************************************************/ /* These spinlocks are used in the SMP configuration in order to implement * up_cpu_pause(). The protocol for CPUn to pause CPUm is as follows * * 1. The up_cpu_pause() implementation on CPUn locks both g_cpu_wait[m] * and g_cpu_paused[m]. CPUn then waits spinning on g_cpu_paused[m]. * 2. CPUm receives the interrupt it (1) unlocks g_cpu_paused[m] and * (2) locks g_cpu_wait[m]. The first unblocks CPUn and the second * blocks CPUm in the interrupt handler. * * When CPUm resumes, CPUn unlocks g_cpu_wait[m] and the interrupt handler * on CPUm continues. CPUm must, of course, also then unlock g_cpu_wait[m] * so that it will be ready for the next pause operation. */ static volatile spinlock_t g_cpu_wait[CONFIG_SMP_NCPUS]; static volatile spinlock_t g_cpu_paused[CONFIG_SMP_NCPUS]; /**************************************************************************** * Public Functions ****************************************************************************/ /**************************************************************************** * Name: up_cpu_pausereq * * Description: * Return true if a pause request is pending for this CPU. * * Input Parameters: * cpu - The index of the CPU to be queried * * Returned Value: * true = a pause request is pending. * false = no pasue request is pending. * ****************************************************************************/ bool up_cpu_pausereq(int cpu) { return spin_islocked(&g_cpu_paused[cpu]); } /**************************************************************************** * Name: up_cpu_paused * * Description: * Handle a pause request from another CPU. Normally, this logic is * executed from interrupt handling logic within the architecture-specific * However, it is sometimes necessary necessary to perform the pending * pause operation in other contexts where the interrupt cannot be taken * in order to avoid deadlocks. * * This function performs the following operations: * * 1. It saves the current task state at the head of the current assigned * task list. * 2. It waits on a spinlock, then * 3. Returns from interrupt, restoring the state of the new task at the * head of the ready to run list. * * Input Parameters: * cpu - The index of the CPU to be paused * * Returned Value: * On success, OK is returned. Otherwise, a negated errno value indicating * the nature of the failure is returned. * ****************************************************************************/ int up_cpu_paused(int cpu) { FAR struct tcb_s *tcb = this_task(); /* Update scheduler parameters */ sched_suspend_scheduler(tcb); /* Save the current context at CURRENT_REGS into the TCB at the head * of the assigned task list for this CPU. */ up_savestate(tcb->xcp.regs); /* Wait for the spinlock to be released */ spin_unlock(&g_cpu_paused[cpu]); spin_lock(&g_cpu_wait[cpu]); /* Restore the exception context of the tcb at the (new) head of the * assigned task list. */ tcb = this_task(); /* Reset scheduler parameters */ sched_resume_scheduler(tcb); /* Then switch contexts. Any necessary address environment changes * will be made when the interrupt returns. */ up_restorestate(tcb->xcp.regs); spin_unlock(&g_cpu_wait[cpu]); return OK; } /**************************************************************************** * Name: arm_pause_handler * * Description: * This is the handler for SGI2. It performs the following operations: * * 1. It saves the current task state at the head of the current assigned * task list. * 2. It waits on a spinlock, then * 3. Returns from interrupt, restoring the state of the new task at the * head of the ready to run list. * * Input Parameters: * Standard interrupt handling * * Returned Value: * Zero on success; a negated errno value on failure. * ****************************************************************************/ int arm_pause_handler(int irq, FAR void *context) { int cpu = this_cpu(); /* Check for false alarms. Such false could occur as a consequence of * some deadlock breaking logic that might have already serviced the SG2 * interrupt by calling up_cpu_paused(). If the pause event has already * been processed then g_cpu_paused[cpu] will not be locked. */ if (spin_islocked(&g_cpu_paused[cpu])) { return up_cpu_paused(cpu); } return OK; } /**************************************************************************** * Name: up_cpu_pause * * Description: * Save the state of the current task at the head of the * g_assignedtasks[cpu] task list and then pause task execution on the * CPU. * * This function is called by the OS when the logic executing on one CPU * needs to modify the state of the g_assignedtasks[cpu] list for another * CPU. * * Input Parameters: * cpu - The index of the CPU to be stopped * * Returned Value: * Zero on success; a negated errno value on failure. * ****************************************************************************/ int up_cpu_pause(int cpu) { int ret; DEBUGASSERT(cpu >= 0 && cpu < CONFIG_SMP_NCPUS && cpu != this_cpu()); /* Take the both spinlocks. The g_cpu_wait spinlock will prevent the SGI2 * handler from returning until up_cpu_resume() is called; g_cpu_paused * is a handshake that will prefent this function from returning until * the CPU is actually paused. */ DEBUGASSERT(!spin_islocked(&g_cpu_wait[cpu]) && !spin_islocked(&g_cpu_paused[cpu])); spin_lock(&g_cpu_wait[cpu]); spin_lock(&g_cpu_paused[cpu]); /* Execute SGI2 */ ret = arm_cpu_sgi(GIC_IRQ_SGI2, (1 << cpu)); if (ret < 0) { /* What happened? Unlock the g_cpu_wait spinlock */ spin_unlock(&g_cpu_wait[cpu]); } else { /* Wait for the other CPU to unlock g_cpu_paused meaning that * it is fully paused and ready for up_cpu_resume(); */ spin_lock(&g_cpu_paused[cpu]); } spin_unlock(&g_cpu_paused[cpu]); /* On successful return g_cpu_wait will be locked, the other CPU will be * spinninf on g_cpu_wait and will not continue until g_cpu_resume() is * called. g_cpu_paused will be unlocked in any case. */ return ret; } /**************************************************************************** * Name: up_cpu_resume * * Description: * Restart the cpu after it was paused via up_cpu_pause(), restoring the * state of the task at the head of the g_assignedtasks[cpu] list, and * resume normal tasking. * * This function is called after up_cpu_pause in order resume operation of * the CPU after modifying its g_assignedtasks[cpu] list. * * Input Parameters: * cpu - The index of the CPU being re-started. * * Returned Value: * Zero on success; a negated errno value on failure. * ****************************************************************************/ int up_cpu_resume(int cpu) { DEBUGASSERT(cpu >= 0 && cpu < CONFIG_SMP_NCPUS && cpu != this_cpu()); /* Release the spinlock. Releasing the spinlock will cause the SGI2 * handler on 'cpu' to continue and return from interrupt to the newly * established thread. */ DEBUGASSERT(spin_islocked(&g_cpu_wait[cpu]) && !spin_islocked(&g_cpu_paused[cpu])); spin_unlock(&g_cpu_wait[cpu]); return OK; } #endif /* CONFIG_SMP */