/**************************************************************************** * arch/xtensa/src/common/xtensa_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 "xtensa.h" #include "sched/sched.h" #ifdef CONFIG_SMP /**************************************************************************** * Private Data ****************************************************************************/ static spinlock_t g_cpu_wait[CONFIG_SMP_NCPUS] SP_SECTION; static spinlock_t g_cpu_paused[CONFIG_SMP_NCPUS] SP_SECTION; /**************************************************************************** * 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 *otcb = this_task(); FAR struct tcb_s *ntcb; /* Update scheduler parameters */ sched_suspend_scheduler(otcb); /* Copy the CURRENT_REGS into the OLD TCB (otcb). The co-processor state * will be saved as part of the return from xtensa_irq_dispatch(). */ xtensa_savestate(otcb->xcp.regs); /* Wait for the spinlock to be released */ spin_unlock(&g_cpu_paused[cpu]); spin_lock(&g_cpu_wait[cpu]); /* Upon return, we will restore the exception context of the new TCB * (ntcb) at the head of the ready-to-run task list. */ ntcb = this_task(); /* Reset scheduler parameters */ sched_resume_scheduler(ntcb); /* Did the task at the head of the list change? */ if (otcb != ntcb) { /* Set CURRENT_REGS to the context save are of the new TCB to start. * This will inform the return-from-interrupt logic that a context * switch must be performed. */ xtensa_restorestate(ntcb->xcp.regs); } spin_unlock(&g_cpu_wait[cpu]); } /**************************************************************************** * Name: xtensa_pause_handler * * Description: * This is the handler for CPU_INTCODE_PAUSE CPU interrupt. This * implements up_cpu_pause() by performing the following operations: * * 1. The current task state at the head of the current assigned task * list was saved when the interrupt was entered. * 2. This function simply waits on a spinlock, then returns. * 3. Upon return, the interrupt exit logic will restore the state of * the new task at the head of the ready to run list. * * Input Parameters: * None * * Returned Value: * None * ****************************************************************************/ void xtensa_pause_handler(void) { (void)up_cpu_paused(up_cpu_index()); } /**************************************************************************** * 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 = xtensa_intercpu_interrupt(cpu, CPU_INTCODE_PAUSE); 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 */