efc863217b
Signed-off-by: Xiang Xiao <xiaoxiang@xiaomi.com>
1344 lines
40 KiB
C
1344 lines
40 KiB
C
/****************************************************************************
|
|
* sched/sched/sched_sporadic.c
|
|
*
|
|
* Licensed to the Apache Software Foundation (ASF) under one or more
|
|
* contributor license agreements. See the NOTICE file distributed with
|
|
* this work for additional information regarding copyright ownership. The
|
|
* ASF licenses this file to you under the Apache License, Version 2.0 (the
|
|
* "License"); you may not use this file except in compliance with the
|
|
* License. You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
* License for the specific language governing permissions and limitations
|
|
* under the License.
|
|
*
|
|
****************************************************************************/
|
|
|
|
/****************************************************************************
|
|
* Included Files
|
|
****************************************************************************/
|
|
|
|
#include <nuttx/config.h>
|
|
|
|
#include <stdint.h>
|
|
#include <stdbool.h>
|
|
#include <string.h>
|
|
#include <assert.h>
|
|
#include <debug.h>
|
|
#include <errno.h>
|
|
|
|
#include <nuttx/sched.h>
|
|
#include <nuttx/arch.h>
|
|
#include <nuttx/wdog.h>
|
|
#include <nuttx/clock.h>
|
|
|
|
#include <arch/irq.h>
|
|
|
|
#include "clock/clock.h"
|
|
#include "sched/sched.h"
|
|
|
|
#ifdef CONFIG_SCHED_SPORADIC
|
|
|
|
/****************************************************************************
|
|
* Pre-processor Definitions
|
|
****************************************************************************/
|
|
|
|
#ifndef MIN
|
|
# define MIN(a,b) (((a) < (b)) ? (a) : (b))
|
|
#endif
|
|
|
|
/****************************************************************************
|
|
* Private Function Prototypes
|
|
****************************************************************************/
|
|
|
|
/* Priority controls */
|
|
|
|
static int sporadic_set_lowpriority(FAR struct tcb_s *tcb);
|
|
static int sporadic_set_hipriority(FAR struct tcb_s *tcb);
|
|
|
|
/* State changes that initiate timer actions */
|
|
|
|
static int sporadic_budget_start(FAR struct replenishment_s *mrepl);
|
|
static int sporadic_interval_start(FAR struct replenishment_s *mrepl);
|
|
static int sporadic_replenish_start(FAR struct replenishment_s *repl);
|
|
static int sporadic_replenish_delay(FAR struct replenishment_s *repl,
|
|
uint32_t period, uint32_t replenish);
|
|
|
|
/* Timer expiration handlers */
|
|
|
|
static void sporadic_budget_expire(wdparm_t arg);
|
|
static void sporadic_interval_expire(wdparm_t arg);
|
|
static void sporadic_replenish_expire(wdparm_t arg);
|
|
static void sporadic_delay_expire(wdparm_t arg);
|
|
|
|
/* Misc. helpers */
|
|
|
|
static void sporadic_timer_cancel(FAR struct tcb_s *tcb);
|
|
FAR struct replenishment_s *
|
|
sporadic_alloc_repl(FAR struct sporadic_s *sporadic);
|
|
|
|
/****************************************************************************
|
|
* Private Functions
|
|
****************************************************************************/
|
|
|
|
/****************************************************************************
|
|
* Name: sporadic_set_lowpriority
|
|
*
|
|
* Description:
|
|
* Force the thread to lower priority.
|
|
*
|
|
* Input Parameters:
|
|
* tcb - TCB of task whose priority will be modified
|
|
*
|
|
* Returned Value:
|
|
* Returns zero (OK) on success or a negated errno value on failure.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int sporadic_set_lowpriority(FAR struct tcb_s *tcb)
|
|
{
|
|
FAR struct sporadic_s *sporadic;
|
|
int ret;
|
|
|
|
DEBUGASSERT(tcb != NULL && tcb->sporadic != NULL);
|
|
sporadic = tcb->sporadic;
|
|
|
|
#ifdef CONFIG_SPORADIC_INSTRUMENTATION
|
|
/* Inform the monitor of this event */
|
|
|
|
arch_sporadic_lowpriority(tcb);
|
|
#endif
|
|
|
|
#ifdef CONFIG_PRIORITY_INHERITANCE
|
|
/* If the priority was boosted above the higher priority, than just
|
|
* reset the base priority and continue to run at the boosted priority.
|
|
*
|
|
* REVISIT: There is a logic flaw here... If the priority was NOT
|
|
* boosted above the hi_priority, then it still may still need to
|
|
* boosted with respect to the lo_priority. If the highest priority
|
|
* thread waiting on a semaphore held by the sporadic thread is greater
|
|
* than the low priority (but less than the hi_priority), the new
|
|
* sched_priority should be set to that priority, not to the lo_priority
|
|
*
|
|
* In order to do this we would need to know the highest priority from
|
|
* among all tasks waiting for the all semaphores held by the sporadic
|
|
* task. That information could be retained by the priority inheritance
|
|
* logic of sem_holder.c for use here.
|
|
*/
|
|
|
|
if (tcb->sched_priority > tcb->base_priority)
|
|
{
|
|
/* Thread priority was boosted while we were in the high priority
|
|
* state.
|
|
*/
|
|
|
|
tcb->base_priority = sporadic->low_priority;
|
|
}
|
|
else
|
|
#endif
|
|
{
|
|
/* Otherwise drop the priority of thread, possible causing a context
|
|
* switch.
|
|
*/
|
|
|
|
ret = nxsched_reprioritize(tcb, sporadic->low_priority);
|
|
if (ret < 0)
|
|
{
|
|
serr("ERROR: nxsched_reprioritize failed: %d\n", ret);
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
return OK;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: sporadic_set_hipriority
|
|
*
|
|
* Description:
|
|
* Force the thread to higher priority.
|
|
*
|
|
* Input Parameters:
|
|
* tcb - TCB of task whose priority will be modified
|
|
*
|
|
* Returned Value:
|
|
* Returns zero (OK) on success or a negated errno value on failure.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int sporadic_set_hipriority(FAR struct tcb_s *tcb)
|
|
{
|
|
FAR struct sporadic_s *sporadic;
|
|
int ret;
|
|
|
|
DEBUGASSERT(tcb != NULL && tcb->sporadic != NULL);
|
|
sporadic = tcb->sporadic;
|
|
|
|
#ifdef CONFIG_SPORADIC_INSTRUMENTATION
|
|
/* Inform the monitor of this event */
|
|
|
|
arch_sporadic_start(tcb);
|
|
#endif
|
|
|
|
#ifdef CONFIG_PRIORITY_INHERITANCE
|
|
/* If the priority was boosted above the higher priority, than just
|
|
* reset the base priority.
|
|
*
|
|
* First, was the priority boosted above the lo_priority which should be
|
|
* the same as the base_priority here? (This is an unnecessary test.
|
|
* sched_priority > hi_priority would be sufficient).
|
|
*/
|
|
|
|
if (tcb->sched_priority > tcb->base_priority)
|
|
{
|
|
/* Boosted... Do we still need to reprioritize? If we were boosted to
|
|
* a priority above the hi_priority then we do not need to do anything
|
|
* except to adjust the base_priority
|
|
*
|
|
* REVISIT: This logic is probably okay. But may lead to problems
|
|
* when the hi_priority is resumed. See REVISIT comments in
|
|
* sporadic_set_lowpriority().
|
|
*/
|
|
|
|
if (tcb->sched_priority > sporadic->hi_priority)
|
|
{
|
|
/* No.. the new execution priority is lower than the boosted
|
|
* priority. Just reset the base priority.
|
|
*/
|
|
|
|
tcb->base_priority = sporadic->hi_priority;
|
|
return OK;
|
|
}
|
|
}
|
|
|
|
/* The thread priority has not been boosted or it has been boosted to a
|
|
* lower priority than the high priority. So, in either case, we need to
|
|
* reset the priority.
|
|
*/
|
|
|
|
#endif
|
|
|
|
/* Then reprioritize to the higher priority */
|
|
|
|
ret = nxsched_reprioritize(tcb, sporadic->hi_priority);
|
|
if (ret < 0)
|
|
{
|
|
serr("ERROR: nxsched_reprioritize failed: %d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
return OK;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: sporadic_budget_start
|
|
*
|
|
* Description:
|
|
* Start the next scheduler cycle by (1) increasing the priority of
|
|
* the thread to the high priority and (2) setting up a timer for the
|
|
* budgeted portion of the scheduler interval.
|
|
*
|
|
* Input Parameters:
|
|
* tcb - TCB of the thread whose priority is being boosted.
|
|
* mrepl - The main timer
|
|
* budget - Budgeted execution time
|
|
*
|
|
* Returned Value:
|
|
* Returns zero (OK) on success or a negated errno value on failure.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int sporadic_budget_start(FAR struct replenishment_s *mrepl)
|
|
{
|
|
FAR struct sporadic_s *sporadic;
|
|
FAR struct tcb_s *tcb;
|
|
|
|
DEBUGASSERT(mrepl->tcb != NULL);
|
|
tcb = mrepl->tcb;
|
|
sporadic = tcb->sporadic;
|
|
DEBUGASSERT(sporadic);
|
|
|
|
/* Start the next replenishment interval */
|
|
|
|
tcb->timeslice = sporadic->budget;
|
|
sporadic->active = mrepl;
|
|
mrepl->budget = sporadic->budget;
|
|
mrepl->flags &= ~SPORADIC_FLAG_REPLENISH;
|
|
|
|
/* Save the time that the budget was started */
|
|
|
|
sporadic->eventtime = clock_systime_ticks();
|
|
|
|
/* And start the timer for the budget interval */
|
|
|
|
DEBUGVERIFY(wd_start(&mrepl->timer, sporadic->budget,
|
|
sporadic_budget_expire, (wdparm_t)mrepl));
|
|
|
|
/* Then reprioritize to the higher priority */
|
|
|
|
return sporadic_set_hipriority(tcb);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: sporadic_interval_start
|
|
*
|
|
* Description:
|
|
* Start the low-priority part of the next, normal cycle, decreasing the
|
|
* priority of the thread to the low priority.
|
|
*
|
|
* Input Parameters:
|
|
* mrepl - The main timer
|
|
*
|
|
* Returned Value:
|
|
* Returns zero (OK) on success or a negated errno value on failure.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int sporadic_interval_start(FAR struct replenishment_s *mrepl)
|
|
{
|
|
FAR struct sporadic_s *sporadic;
|
|
FAR struct tcb_s *tcb;
|
|
uint32_t remainder;
|
|
|
|
DEBUGASSERT(mrepl != NULL && mrepl->tcb != NULL);
|
|
tcb = mrepl->tcb;
|
|
sporadic = tcb->sporadic;
|
|
DEBUGASSERT(sporadic != NULL);
|
|
|
|
/* Enter the low-priority phase of the replenishment cycle */
|
|
|
|
tcb->timeslice = 0;
|
|
sporadic->active = mrepl;
|
|
mrepl->flags &= ~SPORADIC_FLAG_REPLENISH;
|
|
|
|
/* Calculate the remainder of the replenishment interval. This is
|
|
* permitted to be zero, in which case we just restart the budget
|
|
* interval without lowering the priority.
|
|
*/
|
|
|
|
DEBUGASSERT(sporadic->repl_period >= sporadic->budget);
|
|
remainder = sporadic->repl_period - sporadic->budget;
|
|
if (remainder == 0)
|
|
{
|
|
/* Start the hi-priority portion of the next scheduler cycle */
|
|
|
|
return sporadic_budget_start(mrepl);
|
|
}
|
|
|
|
/* Start the timer that will terminate the low priority cycle. This timer
|
|
* expiration is independent of what else may occur (except that it must
|
|
* be canceled if the thread exits.
|
|
*/
|
|
|
|
DEBUGVERIFY(wd_start(&mrepl->timer, remainder,
|
|
sporadic_interval_expire, (wdparm_t)mrepl));
|
|
|
|
/* Drop the priority of thread, possible causing a context switch. */
|
|
|
|
return sporadic_set_lowpriority(tcb);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: sporadic_replenish_start
|
|
*
|
|
* Description:
|
|
* Start the next replenishment by (1) increasing the priority of the
|
|
* thread to the high priority and (2) setting up a timer for the
|
|
* replenishment interval.
|
|
*
|
|
* Input Parameters:
|
|
* repl - The replenishment timer to use
|
|
*
|
|
* Returned Value:
|
|
* Returns zero (OK) on success or a negated errno value on failure.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int sporadic_replenish_start(FAR struct replenishment_s *repl)
|
|
{
|
|
FAR struct sporadic_s *sporadic;
|
|
FAR struct tcb_s *tcb;
|
|
|
|
DEBUGASSERT(repl->tcb != NULL);
|
|
tcb = repl->tcb;
|
|
sporadic = tcb->sporadic;
|
|
DEBUGASSERT(sporadic);
|
|
|
|
/* Start the replenishment interval */
|
|
|
|
tcb->timeslice = repl->budget;
|
|
sporadic->active = repl;
|
|
repl->flags |= SPORADIC_FLAG_REPLENISH;
|
|
|
|
/* And start the timer for the budget interval */
|
|
|
|
DEBUGVERIFY(wd_start(&repl->timer, repl->budget,
|
|
sporadic_replenish_expire, (wdparm_t)repl));
|
|
|
|
/* Then reprioritize to the higher priority */
|
|
|
|
return sporadic_set_hipriority(tcb);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: sporadic_replenish_delay
|
|
*
|
|
* Description:
|
|
* Start the delay prior to providing the replenishment.
|
|
*
|
|
* Input Parameters:
|
|
* repl - Replenishment timer to be used
|
|
* period - Delay to the timer expiration
|
|
* replenish - The replenish time to be applied after the delay
|
|
*
|
|
* Returned Value:
|
|
* Returns zero (OK) on success or a negated errno value on failure.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int sporadic_replenish_delay(FAR struct replenishment_s *repl,
|
|
uint32_t period, uint32_t replenish)
|
|
{
|
|
DEBUGASSERT(repl->tcb != NULL && repl->tcb->sporadic != NULL);
|
|
|
|
/* Save information about the replenishment */
|
|
|
|
repl->budget = replenish;
|
|
repl->flags |= SPORADIC_FLAG_REPLENISH;
|
|
|
|
/* And start the timer for the delay prior to replenishing. */
|
|
|
|
DEBUGVERIFY(wd_start(&repl->timer, period,
|
|
sporadic_delay_expire, (wdparm_t)repl));
|
|
return OK;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: sporadic_budget_expire
|
|
*
|
|
* Description:
|
|
* Handles the expiration of a budget interval. It saves the budget
|
|
* For the next interval, drops the priority of the thread, and restarts
|
|
* the timer for the rest of the replenishment period.
|
|
*
|
|
* REVISIT: If there was no execution at all during the entire budget
|
|
* interval, we make no attempt to replenish that. It is just lost
|
|
* execution time.
|
|
*
|
|
* Input Parameters:
|
|
* Standard watchdog parameters
|
|
*
|
|
* Returned Value:
|
|
* None
|
|
*
|
|
* Assumptions:
|
|
* The thread is still running and is still using the sporadic
|
|
* scheduling policy.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static void sporadic_budget_expire(wdparm_t arg)
|
|
{
|
|
FAR struct replenishment_s *mrepl = (FAR struct replenishment_s *)arg;
|
|
FAR struct replenishment_s *repl;
|
|
FAR struct sporadic_s *sporadic;
|
|
FAR struct tcb_s *tcb;
|
|
|
|
DEBUGASSERT(mrepl != NULL && mrepl->tcb != NULL);
|
|
tcb = mrepl->tcb;
|
|
|
|
/* As a special case, we can do nothing here if scheduler has been locked.
|
|
* We cannot drop the priority because that might cause a context switch,
|
|
* violating the lock.
|
|
*
|
|
* What we do instead is just deallocate all timers. When the lock is
|
|
* finally released, nxsched_sporadic_lowpriority() and that will restart
|
|
* the interval period. timeslice == -1 is the cue to sched_unlock() that
|
|
* this operation is needed.
|
|
*/
|
|
|
|
if (nxsched_islocked_tcb(tcb))
|
|
{
|
|
DEBUGASSERT((mrepl->flags & SPORADIC_FLAG_ALLOCED) != 0);
|
|
|
|
/* Set the timeslice to the magic value */
|
|
|
|
tcb->timeslice = -1;
|
|
|
|
/* Cancel and free all timers */
|
|
|
|
sporadic_timer_cancel(tcb);
|
|
return;
|
|
}
|
|
|
|
/* Were we suspended at the end of the budget time? If so, start a new
|
|
* replenishment timer for the time since we were suspended (which might
|
|
* be as long as the whole budget interval).
|
|
*/
|
|
|
|
sporadic = tcb->sporadic;
|
|
DEBUGASSERT(sporadic != NULL);
|
|
|
|
if (sporadic->suspended)
|
|
{
|
|
uint32_t unrealized;
|
|
|
|
/* The unrealized time is the interval from when the thread was
|
|
* suspended (or when the budget interval was started in the case
|
|
* that the thread was delayed for the entire interval).
|
|
*/
|
|
|
|
unrealized = clock_systime_ticks() - sporadic->eventtime;
|
|
if (unrealized > 0)
|
|
{
|
|
/* Allocate a new replenishment timer. This will limit us to the
|
|
* maximum number of replenishments (max_repl).
|
|
*/
|
|
|
|
repl = sporadic_alloc_repl(sporadic);
|
|
if (repl != NULL)
|
|
{
|
|
uint32_t period;
|
|
|
|
/* Calculate the delay to when replenishment interval begins.
|
|
* That time is one half of the scheduler cycle relative to
|
|
* the suspend time. The delay relative to the current time
|
|
* is then:
|
|
*
|
|
* repl_time = susp_time + repl_interval / 2;
|
|
* delay = repl_time - curr_time
|
|
* delay = susp_time - curr_time + repl_interval / 2
|
|
* delay = repl_interval / 2 - unrealized
|
|
*/
|
|
|
|
DEBUGASSERT(unrealized <= (sporadic->repl_period >> 1));
|
|
period = (sporadic->repl_period >> 1) - unrealized;
|
|
|
|
/* Start the delay into the next cycle to perform the
|
|
* replenishment.
|
|
*/
|
|
|
|
DEBUGVERIFY(sporadic_replenish_delay(repl, period,
|
|
unrealized));
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Drop the priority of the thread and start the timer for the rest of the
|
|
* scheduler interval.
|
|
*/
|
|
|
|
DEBUGVERIFY(sporadic_interval_start(mrepl));
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: sporadic_interval_expire
|
|
*
|
|
* Description:
|
|
* Handles the expiration of a replenishment interval by starting the
|
|
* next replenishment interval.
|
|
*
|
|
* Input Parameters:
|
|
* Standard watchdog parameters
|
|
*
|
|
* Returned Value:
|
|
* None
|
|
*
|
|
* Assumptions:
|
|
* The thread is still running and is still using the sporadic
|
|
* scheduling policy.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static void sporadic_interval_expire(wdparm_t arg)
|
|
{
|
|
FAR struct replenishment_s *mrepl = (FAR struct replenishment_s *)arg;
|
|
|
|
DEBUGASSERT(mrepl != NULL);
|
|
|
|
/* If we get here, then (1) this should be the main thread, and (2) there
|
|
* should be no active replenishment thread.
|
|
*/
|
|
|
|
DEBUGASSERT((mrepl->flags & SPORADIC_FLAG_MAIN) != 0);
|
|
|
|
/* Start the hi-priority portion of the next scheduler cycle */
|
|
|
|
DEBUGVERIFY(sporadic_budget_start(mrepl));
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: sporadic_replenish_expire
|
|
*
|
|
* Description:
|
|
* Handles the expiration of a replenishment interval timer.
|
|
*
|
|
* REVISIT: If there was no execution at all during the entire
|
|
* replenishment interval, we make no attempt to replenish the
|
|
* replenishment. It is just lost execution time.
|
|
*
|
|
* Input Parameters:
|
|
* Standard watchdog parameters
|
|
*
|
|
* Returned Value:
|
|
* None
|
|
*
|
|
****************************************************************************/
|
|
|
|
static void sporadic_replenish_expire(wdparm_t arg)
|
|
{
|
|
FAR struct replenishment_s *repl = (FAR struct replenishment_s *)arg;
|
|
FAR struct sporadic_s *sporadic;
|
|
FAR struct tcb_s *tcb;
|
|
|
|
DEBUGASSERT(repl != NULL && repl->tcb != NULL);
|
|
tcb = repl->tcb;
|
|
|
|
sporadic = tcb->sporadic;
|
|
DEBUGASSERT(sporadic != NULL);
|
|
|
|
/* This should not be the main timer */
|
|
|
|
DEBUGASSERT((repl->flags & (SPORADIC_FLAG_MAIN | SPORADIC_FLAG_REPLENISH))
|
|
== SPORADIC_FLAG_REPLENISH);
|
|
|
|
/* As a special case, we can do nothing here if scheduler has been locked.
|
|
* We cannot drop the priority because that might cause a context switch,
|
|
* violating the lock.
|
|
*
|
|
* What we do instead is just deallocate all timers. When the lock is
|
|
* finally released, nxsched_sporadic_lowpriority() and that will restart
|
|
* the interval period. timeslice == -1 is the cue to sched_unlock() that
|
|
* this operation is needed.
|
|
*/
|
|
|
|
if (nxsched_islocked_tcb(tcb))
|
|
{
|
|
/* Set the timeslice to the magic value */
|
|
|
|
tcb->timeslice = -1;
|
|
|
|
/* Cancel and free all timers */
|
|
|
|
sporadic_timer_cancel(tcb);
|
|
return;
|
|
}
|
|
|
|
/* Make sure that we are in synch with the timer process. */
|
|
|
|
tcb->timeslice = 0;
|
|
|
|
/* Discard the timer. It has served its purpose. */
|
|
|
|
repl->flags = 0;
|
|
sporadic->nrepls--;
|
|
|
|
/* Drop the priority of thread, possible causing a context switch. */
|
|
|
|
DEBUGVERIFY(sporadic_set_lowpriority(tcb));
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: sporadic_delay_expire
|
|
*
|
|
* Description:
|
|
* Handle the expiration of a delay prior to providing a replenishment.
|
|
*
|
|
* Input Parameters:
|
|
* tcb - The TCB of the thread that has the scheduler locked
|
|
*
|
|
* Returned Value:
|
|
* None
|
|
*
|
|
****************************************************************************/
|
|
|
|
static void sporadic_delay_expire(wdparm_t arg)
|
|
{
|
|
FAR struct replenishment_s *repl = (FAR struct replenishment_s *)arg;
|
|
|
|
DEBUGASSERT(repl != NULL);
|
|
|
|
/* Start the replenishment */
|
|
|
|
DEBUGVERIFY(sporadic_replenish_start(repl));
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: sporadic_timer_cancel
|
|
*
|
|
* Description:
|
|
* Cancel all timers. We do this if/when the budget time expires while the
|
|
* scheduler is locked. Basically we need to stop everything and restart
|
|
* when the scheduler is unlocked.
|
|
*
|
|
* Input Parameters:
|
|
* tcb - The TCB of the thread that has the scheduler locked
|
|
*
|
|
* Returned Value:
|
|
* None
|
|
*
|
|
****************************************************************************/
|
|
|
|
static void sporadic_timer_cancel(FAR struct tcb_s *tcb)
|
|
{
|
|
FAR struct sporadic_s *sporadic;
|
|
FAR struct replenishment_s *repl;
|
|
int i;
|
|
|
|
DEBUGASSERT(tcb && tcb->sporadic);
|
|
sporadic = tcb->sporadic;
|
|
|
|
/* Cancel all timers. */
|
|
|
|
for (i = 0; i < CONFIG_SCHED_SPORADIC_MAXREPL; i++)
|
|
{
|
|
repl = &sporadic->replenishments[i];
|
|
|
|
/* Cancel any outstanding timer activity */
|
|
|
|
wd_cancel(&repl->timer);
|
|
repl->flags = 0;
|
|
}
|
|
|
|
sporadic->nrepls = 0;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: sporadic_alloc_repl
|
|
*
|
|
* Description:
|
|
* Allocate a replenishment timer structure.
|
|
*
|
|
* Input Parameters:
|
|
* sporadic - The task's sporadic scheduling state.
|
|
*
|
|
* Returned Value:
|
|
* The allocated replenishment timer structure; NULL is returned on a
|
|
* failure
|
|
*
|
|
****************************************************************************/
|
|
|
|
FAR struct replenishment_s *
|
|
sporadic_alloc_repl(FAR struct sporadic_s *sporadic)
|
|
{
|
|
FAR struct replenishment_s *repl = NULL;
|
|
int i;
|
|
|
|
if (sporadic->nrepls < sporadic->max_repl)
|
|
{
|
|
/* Allocate a new replenishment timer */
|
|
|
|
DEBUGASSERT(sporadic->max_repl <= CONFIG_SCHED_SPORADIC_MAXREPL);
|
|
for (i = 0; i < sporadic->max_repl; i++)
|
|
{
|
|
FAR struct replenishment_s *tmp = &sporadic->replenishments[i];
|
|
if ((tmp->flags & SPORADIC_FLAG_ALLOCED) == 0)
|
|
{
|
|
repl = tmp;
|
|
repl->flags = SPORADIC_FLAG_ALLOCED;
|
|
sporadic->nrepls++;
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* Since we no that we have not yet reached the max_repl number of
|
|
* timers, the above search should never fail.
|
|
*/
|
|
|
|
DEBUGASSERT(repl != NULL);
|
|
}
|
|
|
|
return repl;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Public Functions
|
|
****************************************************************************/
|
|
|
|
/****************************************************************************
|
|
* Name: nxsched_initialize_sporadic
|
|
*
|
|
* Description:
|
|
* Allocate resources needed by the sporadic scheduling policy.
|
|
*
|
|
* Input Parameters:
|
|
* tcb - TCB of the thread whose priority is being boosted.
|
|
*
|
|
* Returned Value:
|
|
* Returns zero (OK) on success or a negated errno value on failure.
|
|
*
|
|
****************************************************************************/
|
|
|
|
int nxsched_initialize_sporadic(FAR struct tcb_s *tcb)
|
|
{
|
|
FAR struct sporadic_s *sporadic;
|
|
int i;
|
|
|
|
DEBUGASSERT(tcb != NULL && tcb->sporadic == NULL);
|
|
|
|
/* Allocate the sporadic add-on data structure that will hold the
|
|
* sporadic scheduling parameters and state data.
|
|
*/
|
|
|
|
sporadic = (FAR struct sporadic_s *)kmm_zalloc(sizeof(struct sporadic_s));
|
|
if (sporadic == NULL)
|
|
{
|
|
serr("ERROR: Failed to allocate sporadic data structure\n");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
/* The initialize required is to set the back pointer to the TCB in
|
|
* each of the replenishment structures.
|
|
*/
|
|
|
|
for (i = 0; i < CONFIG_SCHED_SPORADIC_MAXREPL; i++)
|
|
{
|
|
sporadic->replenishments[i].tcb = tcb;
|
|
}
|
|
|
|
/* Hook the sporadic add-on into the TCB */
|
|
|
|
tcb->sporadic = sporadic;
|
|
return OK;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: nxsched_start_sporadic
|
|
*
|
|
* Description:
|
|
* Called to initialize sporadic scheduling on a given thread. This
|
|
* function is called in the following circumstances:
|
|
*
|
|
* - When starting a pthread with sporadic scheduling specified in
|
|
* the pthread attributes.
|
|
* - When establishing sporadic scheduling policy via
|
|
* sched_setscheduler()
|
|
* - When the sporadic scheduling parameters are changed via
|
|
* sched_setparam().
|
|
*
|
|
* Input Parameters:
|
|
* tcb - The TCB of the thread that is beginning sporadic scheduling.
|
|
*
|
|
* Returned Value:
|
|
* Returns zero (OK) on success or a negated errno value on failure.
|
|
*
|
|
* Assumptions:
|
|
* - Interrupts are disabled
|
|
* - All sporadic scheduling parameters in the TCB are valid
|
|
* - The thread is not currently using the sporadic scheduling policy.
|
|
*
|
|
****************************************************************************/
|
|
|
|
int nxsched_start_sporadic(FAR struct tcb_s *tcb)
|
|
{
|
|
FAR struct sporadic_s *sporadic;
|
|
FAR struct replenishment_s *mrepl;
|
|
|
|
DEBUGASSERT(tcb && tcb->sporadic);
|
|
sporadic = tcb->sporadic;
|
|
|
|
/* Allocate the main replenishment timer (should never fail) */
|
|
|
|
mrepl = sporadic_alloc_repl(sporadic);
|
|
DEBUGASSERT(mrepl != NULL && sporadic->nrepls == 1);
|
|
mrepl->flags |= SPORADIC_FLAG_MAIN;
|
|
|
|
/* Save the time that the scheduler was started */
|
|
|
|
sporadic->eventtime = clock_systime_ticks();
|
|
sporadic->suspended = true;
|
|
|
|
/* Then start the first interval */
|
|
|
|
return sporadic_budget_start(mrepl);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: nxsched_stop_sporadic
|
|
*
|
|
* Description:
|
|
* Called to terminate sporadic scheduling on a given thread and to
|
|
* free all resources associated with the policy. This function is
|
|
* called in the following circumstances:
|
|
*
|
|
* - When any thread exits with sporadic scheduling active.
|
|
* - When any thread using sporadic scheduling is changed to use
|
|
* some other scheduling policy via sched_setscheduler()
|
|
*
|
|
* Input Parameters:
|
|
* tcb - The TCB of the thread that is beginning sporadic scheduling.
|
|
*
|
|
* Returned Value:
|
|
* Returns zero (OK) on success or a negated errno value on failure.
|
|
*
|
|
* Assumptions:
|
|
* - Interrupts are disabled
|
|
* - All sporadic scheduling parameters in the TCB are valid
|
|
* - The thread is currently using the sporadic scheduling policy.
|
|
*
|
|
****************************************************************************/
|
|
|
|
int nxsched_stop_sporadic(FAR struct tcb_s *tcb)
|
|
{
|
|
DEBUGASSERT(tcb && tcb->sporadic);
|
|
|
|
/* Stop all timers, reset scheduling */
|
|
|
|
nxsched_reset_sporadic(tcb);
|
|
|
|
/* The free the container holder the sporadic scheduling parameters */
|
|
|
|
kmm_free(tcb->sporadic);
|
|
tcb->sporadic = NULL;
|
|
return OK;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: nxsched_reset_sporadic
|
|
*
|
|
* Description:
|
|
* Called to stop sporadic scheduling on a given thread. This
|
|
* function is called in the following circumstances:
|
|
*
|
|
* - When the sporadic scheduling parameters are changed via
|
|
* sched_setparam()
|
|
* - From nxsched_stop_sporadic when under those conditions.
|
|
*
|
|
* Input Parameters:
|
|
* tcb - The TCB of the thread that is beginning sporadic scheduling.
|
|
*
|
|
* Returned Value:
|
|
* Returns zero (OK) on success or a negated errno value on failure.
|
|
*
|
|
* Assumptions:
|
|
* - Interrupts are disabled
|
|
* - All sporadic scheduling parameters in the TCB are valid
|
|
* - The thread is currently using the sporadic scheduling policy.
|
|
*
|
|
****************************************************************************/
|
|
|
|
int nxsched_reset_sporadic(FAR struct tcb_s *tcb)
|
|
{
|
|
FAR struct sporadic_s *sporadic;
|
|
FAR struct replenishment_s *repl;
|
|
int i;
|
|
|
|
DEBUGASSERT(tcb && tcb->sporadic);
|
|
sporadic = tcb->sporadic;
|
|
|
|
/* Cancel all timers. */
|
|
|
|
for (i = 0; i < CONFIG_SCHED_SPORADIC_MAXREPL; i++)
|
|
{
|
|
repl = (FAR struct replenishment_s *)&sporadic->replenishments[i];
|
|
|
|
/* Cancel any outstanding timer activity */
|
|
|
|
wd_cancel(&repl->timer);
|
|
|
|
/* Re-initialize replenishment data */
|
|
|
|
repl->tcb = tcb;
|
|
repl->budget = 0;
|
|
repl->flags = 0;
|
|
}
|
|
|
|
/* Reset sporadic scheduling parameters and state data */
|
|
|
|
sporadic->suspended = true;
|
|
sporadic->hi_priority = 0;
|
|
sporadic->low_priority = 0;
|
|
sporadic->max_repl = 0;
|
|
sporadic->nrepls = 0;
|
|
sporadic->repl_period = 0;
|
|
sporadic->budget = 0;
|
|
sporadic->eventtime = 0;
|
|
sporadic->active = NULL;
|
|
return OK;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: nxsched_resume_sporadic
|
|
*
|
|
* Description:
|
|
* Called to start the next replenishment interval. This function is
|
|
* called in the following circumstances:
|
|
*
|
|
* - From up_unblocktask() via nxsched_resume_scheduler() when a task
|
|
* using the sporadic scheduling policy.
|
|
*
|
|
* This function does nothing if the budget phase as already elapsed or
|
|
* the maximum number of replenishments have already been performed.
|
|
*
|
|
* Input Parameters:
|
|
* tcb - The TCB of the thread that is beginning sporadic scheduling.
|
|
*
|
|
* Returned Value:
|
|
* Returns zero (OK) on success or a negated errno value on failure.
|
|
*
|
|
* Assumptions:
|
|
* - Interrupts are disabled
|
|
* - All sporadic scheduling parameters in the TCB are valid
|
|
* - The low priority interval timer is not running
|
|
*
|
|
****************************************************************************/
|
|
|
|
int nxsched_resume_sporadic(FAR struct tcb_s *tcb)
|
|
{
|
|
FAR struct sporadic_s *sporadic;
|
|
FAR struct replenishment_s *repl;
|
|
clock_t now;
|
|
uint32_t unrealized;
|
|
uint32_t last;
|
|
|
|
DEBUGASSERT(tcb && tcb->sporadic);
|
|
sporadic = tcb->sporadic;
|
|
|
|
/* If we are resumed more than one, ignore all but the first */
|
|
|
|
if (!sporadic->suspended)
|
|
{
|
|
return OK;
|
|
}
|
|
|
|
/* This is the first time */
|
|
|
|
sporadic->suspended = false;
|
|
|
|
#ifdef CONFIG_SPORADIC_INSTRUMENTATION
|
|
/* Inform the monitor of this event */
|
|
|
|
arch_sporadic_resume(tcb);
|
|
#endif
|
|
|
|
/* Get the time that the thread was [re-]started */
|
|
|
|
now = clock_systime_ticks();
|
|
|
|
/* Check if are in the budget portion of the replenishment interval. We
|
|
* know this is the case if the current timeslice is non-zero.
|
|
*/
|
|
|
|
if (tcb->timeslice > 0)
|
|
{
|
|
/* Unrealized budget time while the thread was suspended */
|
|
|
|
unrealized = now - sporadic->eventtime;
|
|
|
|
/* Ignore very short pre-emptions that are below
|
|
* our timing resolution.
|
|
*/
|
|
|
|
if (unrealized > 0)
|
|
{
|
|
/* Handle any part of the budget that was not utilized.
|
|
*
|
|
* current = The initial budget at the beginning of the
|
|
* interval.
|
|
* unrealized = The unused part of that budget when the
|
|
* thread did not execute.
|
|
*/
|
|
|
|
if (unrealized >= tcb->timeslice)
|
|
{
|
|
/* We lost the remainder of the timeslice, (and then some).
|
|
* No point in starting more timers.
|
|
*/
|
|
|
|
return OK;
|
|
}
|
|
|
|
DEBUGASSERT(sporadic->active);
|
|
tcb->timeslice -= unrealized;
|
|
|
|
/* Get the last budgeted time */
|
|
|
|
last = sporadic->active->budget;
|
|
|
|
/* This function could also be called before the budget period
|
|
* has had a chance to run, i.e., when the value of timeslice
|
|
* has not been decremented and is still equal to the initial
|
|
* value.
|
|
*/
|
|
|
|
if (tcb->timeslice < last)
|
|
{
|
|
/* Allocate a new replenishment timer. This will limit us to
|
|
* to maximum number of replenishments (max_repl).
|
|
*/
|
|
|
|
repl = sporadic_alloc_repl(sporadic);
|
|
if (repl != NULL)
|
|
{
|
|
uint32_t period;
|
|
|
|
/* Calculate the delay to when replenishment interval
|
|
* begins. That time is one half of the scheduler cycle
|
|
* relative to the suspend time. The delay relative to
|
|
* the current time is then:
|
|
*
|
|
* repl_time = susp_time + repl_interval / 2;
|
|
* delay = repl_time - curr_time
|
|
* delay = susp_time - curr_time + repl_interval / 2
|
|
* delay = repl_interval / 2 - unrealized
|
|
*/
|
|
|
|
DEBUGASSERT(unrealized <= (sporadic->repl_period >> 1));
|
|
period = (sporadic->repl_period >> 1) - unrealized;
|
|
|
|
/* Start the delay into the next cycle to perform the
|
|
* replenishment.
|
|
*/
|
|
|
|
return sporadic_replenish_delay(repl, period, unrealized);
|
|
}
|
|
|
|
/* We need to return success even on a failure to allocate.
|
|
* Doing nothing is our fall-back behavior and that is not a
|
|
* failure from the standpoint of higher level logic.
|
|
*/
|
|
|
|
serr("ERROR: Failed to allocate timer, nrepls=%d\n",
|
|
sporadic->nrepls);
|
|
}
|
|
}
|
|
}
|
|
|
|
sporadic->eventtime = now;
|
|
return OK;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: nxsched_suspend_sporadic
|
|
*
|
|
* Description:
|
|
* Called when a thread with sporadic scheduling is suspended. In this
|
|
* case, there will be unaccounted for time from the time that the last
|
|
* when the task is resumed. All that we need to do here is remember
|
|
* that time that we were suspended.
|
|
*
|
|
* Input Parameters:
|
|
* tcb - The TCB of the thread that is beginning sporadic scheduling.
|
|
*
|
|
* Returned Value:
|
|
* Returns zero (OK) on success or a negated errno value on failure.
|
|
*
|
|
****************************************************************************/
|
|
|
|
int nxsched_suspend_sporadic(FAR struct tcb_s *tcb)
|
|
{
|
|
FAR struct sporadic_s *sporadic;
|
|
|
|
DEBUGASSERT(tcb && tcb->sporadic);
|
|
sporadic = tcb->sporadic;
|
|
|
|
/* If we are suspended more than one, ignore all but the first */
|
|
|
|
if (!sporadic->suspended)
|
|
{
|
|
/* This is the first time */
|
|
|
|
sporadic->suspended = true;
|
|
|
|
#ifdef CONFIG_SPORADIC_INSTRUMENTATION
|
|
/* Inform the monitor of this event */
|
|
|
|
arch_sporadic_suspend(tcb);
|
|
#endif
|
|
|
|
/* Save the time that the thread was suspended */
|
|
|
|
sporadic->eventtime = clock_systime_ticks();
|
|
}
|
|
|
|
return OK;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: nxsched_process_sporadic
|
|
*
|
|
* Description:
|
|
* Process the elapsed time interval. Called from this context:
|
|
*
|
|
* - From the timer interrupt handler while the thread with sporadic
|
|
* scheduling is running.
|
|
*
|
|
* Input Parameters:
|
|
* tcb - The TCB of the thread that is beginning sporadic
|
|
* scheduling.
|
|
* ticks - The number of elapsed ticks since the last time this
|
|
* function was called.
|
|
* noswitches - We are running in a context where context switching is
|
|
* not permitted.
|
|
*
|
|
* Returned Value:
|
|
* The number if ticks remaining until the budget interval expires.
|
|
* Zero is returned if we are in the low-priority phase of the
|
|
* replenishment interval.
|
|
*
|
|
* Assumptions:
|
|
* - Interrupts are disabled
|
|
* - All sporadic scheduling parameters in the TCB are valid
|
|
*
|
|
****************************************************************************/
|
|
|
|
uint32_t nxsched_process_sporadic(FAR struct tcb_s *tcb, uint32_t ticks,
|
|
bool noswitches)
|
|
{
|
|
FAR struct sporadic_s *sporadic;
|
|
|
|
DEBUGASSERT(tcb != NULL && tcb->sporadic != NULL && ticks > 0);
|
|
|
|
/* If we are in the low-priority phase of the replenishment interval,
|
|
* then just return zero.
|
|
*
|
|
* > 0: In high priority phase of interval
|
|
* == 0: In the low priority phase of the interval
|
|
* < 0: Stuck in the high priority phase with pre-emption locked.
|
|
*/
|
|
|
|
if (tcb->timeslice == 0)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
/* Check if the budget interval has elapse If 'ticks' is greater
|
|
* than the timeslice value, then we ignore any excess amount.
|
|
*
|
|
* 'ticks' should never be greater than the remaining timeslice. We try
|
|
* to handle that gracefully but it would be an error in the scheduling
|
|
* if there ever were the case.
|
|
*
|
|
* Notice that in the case where were are stuck in the high priority
|
|
* phase with scheduler locked, timeslice will by -1 and any value of
|
|
* ticks will pass this test.
|
|
*/
|
|
|
|
if (ticks >= tcb->timeslice)
|
|
{
|
|
/* Does the thread have the scheduler locked? */
|
|
|
|
sporadic = tcb->sporadic;
|
|
if (nxsched_islocked_tcb(tcb))
|
|
{
|
|
/* Yes... then we have no option but to give the thread more
|
|
* time at the higher priority. Dropping the priority could
|
|
* result in a context switch.
|
|
*
|
|
* Set the timeslice value to a negative value to indicate this
|
|
* case.
|
|
*/
|
|
|
|
sporadic_timer_cancel(tcb);
|
|
tcb->timeslice = -1;
|
|
return 0;
|
|
}
|
|
|
|
/* We will also suppress context switches if we were called via one of
|
|
* the unusual cases handled by nxsched_reassess_timer(). In that case,
|
|
* we will return a value of one so that the timer will expire as soon
|
|
* as possible and we can perform this action in the normal timer
|
|
* expiration context.
|
|
*
|
|
* This is kind of kludge, but I am not to concerned because I hope
|
|
* that the situation is impossible or at least could only occur on
|
|
* rare corner-cases.
|
|
*/
|
|
|
|
if (noswitches)
|
|
{
|
|
tcb->timeslice = 1;
|
|
return 1;
|
|
}
|
|
|
|
/* Another possibility is the budget interval is equal to the
|
|
* entire replenishment interval. This would not seem like such a
|
|
* good thing to do, but is certainly permitted.
|
|
*/
|
|
|
|
if (sporadic->budget >= sporadic->repl_period)
|
|
{
|
|
tcb->timeslice = sporadic->budget;
|
|
return sporadic->budget;
|
|
}
|
|
|
|
/* Otherwise enter the low-priority phase of the replenishment cycle.
|
|
* Let the timers handle the priority changes.
|
|
*/
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* No.. then just decrement the time remaining in the budget interval
|
|
* and continue.
|
|
*/
|
|
|
|
else
|
|
{
|
|
tcb->timeslice -= ticks;
|
|
return tcb->timeslice;
|
|
}
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: nxsched_sporadic_lowpriority
|
|
*
|
|
* Description:
|
|
* Drop to the lower priority for the duration of the replenishment
|
|
* period. Called from:
|
|
*
|
|
* - sched_unlock(). When the budget expires while the thread had the
|
|
* scheduler locked.
|
|
*
|
|
* Input Parameters:
|
|
* tcb - The TCB of the thread that is entering the low priority phase.
|
|
* restart - Restart timers.
|
|
*
|
|
* Returned Value:
|
|
* None
|
|
*
|
|
* Assumptions:
|
|
* - Interrupts are disabled
|
|
* - All sporadic scheduling parameters in the TCB are valid
|
|
*
|
|
****************************************************************************/
|
|
|
|
void nxsched_sporadic_lowpriority(FAR struct tcb_s *tcb)
|
|
{
|
|
FAR struct sporadic_s *sporadic;
|
|
FAR struct replenishment_s *mrepl;
|
|
|
|
DEBUGASSERT(tcb && tcb->sporadic);
|
|
sporadic = tcb->sporadic;
|
|
|
|
/* Enter the low-priority phase of the replenishment cycle. (This is
|
|
* redundant).
|
|
*/
|
|
|
|
tcb->timeslice = 0;
|
|
|
|
/* Allocate a new main timer. There should be no timers active at this
|
|
* phase since they were stopped in nxsched_process_sporadic().
|
|
*/
|
|
|
|
DEBUGASSERT(sporadic->nrepls < sporadic->max_repl);
|
|
mrepl = sporadic_alloc_repl(sporadic);
|
|
DEBUGASSERT(mrepl != NULL);
|
|
|
|
tcb->timeslice = 0;
|
|
sporadic->active = mrepl;
|
|
|
|
mrepl->flags |= SPORADIC_FLAG_MAIN;
|
|
mrepl->budget = sporadic->budget;
|
|
|
|
/* Drop the priority of thread, possible causing a context switch. */
|
|
|
|
DEBUGVERIFY(sporadic_interval_start(mrepl));
|
|
}
|
|
|
|
#endif /* CONFIG_SCHED_SPORADIC */
|