mirror of
https://github.com/apache/nuttx.git
synced 2026-06-02 09:38:37 +08:00
947856efe0
This commit changed the type of the delay ticks to the unsigned, which can reduce the useless branch conditions Besides, this commit added max delay tick limitation to fix incorrect timing behavior if we delay SCLOCK_MAX in the SMP build. Signed-off-by: ouyangxiangzhen <ouyangxiangzhen@xiaomi.com>
533 lines
15 KiB
C
533 lines
15 KiB
C
/****************************************************************************
|
|
* sched/wdog/wd_start.c
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*
|
|
* 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 <sys/types.h>
|
|
|
|
#include <stdint.h>
|
|
#include <stdbool.h>
|
|
#include <sys/param.h>
|
|
#include <unistd.h>
|
|
#include <sched.h>
|
|
#include <assert.h>
|
|
#include <debug.h>
|
|
#include <errno.h>
|
|
|
|
#include <nuttx/irq.h>
|
|
#include <nuttx/arch.h>
|
|
#include <nuttx/wdog.h>
|
|
#include <nuttx/sched_note.h>
|
|
|
|
#include "sched/sched.h"
|
|
#include "wdog/wdog.h"
|
|
|
|
/****************************************************************************
|
|
* Pre-processor Definitions
|
|
****************************************************************************/
|
|
|
|
#ifndef CONFIG_SCHED_CRITMONITOR_MAXTIME_WDOG
|
|
# define CONFIG_SCHED_CRITMONITOR_MAXTIME_WDOG 0
|
|
#endif
|
|
|
|
#if CONFIG_SCHED_CRITMONITOR_MAXTIME_WDOG > 0
|
|
# define CALL_FUNC(func, arg) \
|
|
do \
|
|
{ \
|
|
clock_t start; \
|
|
clock_t elapsed; \
|
|
sched_note_wdog(NOTE_WDOG_ENTER, func, (FAR void *)arg); \
|
|
start = perf_gettime(); \
|
|
func(arg); \
|
|
elapsed = perf_gettime() - start; \
|
|
sched_note_wdog(NOTE_WDOG_LEAVE, func, (FAR void *)arg); \
|
|
if (elapsed > CONFIG_SCHED_CRITMONITOR_MAXTIME_WDOG) \
|
|
{ \
|
|
CRITMONITOR_PANIC("WDOG %p, %s IRQ, execute too long %ju\n", \
|
|
func, up_interrupt_context() ? \
|
|
"IN" : "NOT", (uintmax_t)elapsed); \
|
|
} \
|
|
} \
|
|
while (0)
|
|
#else
|
|
# define CALL_FUNC(func, arg) \
|
|
do \
|
|
{ \
|
|
sched_note_wdog(NOTE_WDOG_ENTER, func, (FAR void *)arg); \
|
|
func(arg); \
|
|
sched_note_wdog(NOTE_WDOG_LEAVE, func, (FAR void *)arg); \
|
|
} \
|
|
while (0)
|
|
|
|
#endif
|
|
|
|
#define wdparm_to_ptr(type, arg) ((type)(uintptr_t)arg)
|
|
#define ptr_to_wdparm(ptr) wdparm_to_ptr(wdparm_t, ptr)
|
|
|
|
/****************************************************************************
|
|
* Private Data
|
|
****************************************************************************/
|
|
|
|
#ifdef CONFIG_SCHED_TICKLESS
|
|
static unsigned int g_wdtimernested;
|
|
#endif
|
|
|
|
/****************************************************************************
|
|
* Private Functions
|
|
****************************************************************************/
|
|
|
|
/****************************************************************************
|
|
* Name: wdentry_period
|
|
*
|
|
* Description:
|
|
* Periodic watchdog timer callback function.
|
|
*
|
|
* Input Parameters:
|
|
* arg - The argument of the wdog callback.
|
|
*
|
|
* Returned Value:
|
|
* None
|
|
*
|
|
****************************************************************************/
|
|
|
|
static void wdentry_period(wdparm_t arg)
|
|
{
|
|
FAR struct wdog_period_s *wdperiod;
|
|
|
|
wdperiod = wdparm_to_ptr(FAR struct wdog_period_s *, arg);
|
|
|
|
wdperiod->func(wdperiod->wdog.arg);
|
|
|
|
if (wdperiod->period != 0)
|
|
{
|
|
wd_start_abstick(&wdperiod->wdog,
|
|
wdperiod->wdog.expired + wdperiod->period,
|
|
wdentry_period, wdperiod->wdog.arg);
|
|
}
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: wd_expiration
|
|
*
|
|
* Description:
|
|
* Check if the timer for the watchdog at the head of list is ready to
|
|
* run. If so, remove the watchdog from the list and execute it.
|
|
*
|
|
* Input Parameters:
|
|
* ticks - current time in ticks
|
|
*
|
|
* Returned Value:
|
|
* None
|
|
*
|
|
****************************************************************************/
|
|
|
|
static inline_function void wd_expiration(clock_t ticks)
|
|
{
|
|
FAR struct wdog_s *wdog;
|
|
irqstate_t flags;
|
|
wdentry_t func;
|
|
wdparm_t arg;
|
|
|
|
flags = spin_lock_irqsave(&g_wdspinlock);
|
|
|
|
#ifdef CONFIG_SCHED_TICKLESS
|
|
/* Increment the nested watchdog timer count to handle cases where wd_start
|
|
* is called in the watchdog callback functions.
|
|
*/
|
|
|
|
g_wdtimernested++;
|
|
#endif
|
|
|
|
/* Process the watchdog at the head of the list as well as any
|
|
* other watchdogs that became ready to run at this time
|
|
*/
|
|
|
|
while (!list_is_empty(&g_wdactivelist))
|
|
{
|
|
wdog = list_first_entry(&g_wdactivelist, struct wdog_s, node);
|
|
|
|
/* Check if expected time is expired */
|
|
|
|
if (!clock_compare(wdog->expired, ticks))
|
|
{
|
|
break;
|
|
}
|
|
|
|
/* Remove the watchdog from the head of the list */
|
|
|
|
list_delete(&wdog->node);
|
|
|
|
/* Indicate that the watchdog is no longer active. */
|
|
|
|
func = wdog->func;
|
|
wdog->func = NULL;
|
|
|
|
arg = func != wdentry_period ? wdog->arg : ptr_to_wdparm(
|
|
list_container_of(wdog, struct wdog_period_s, wdog));
|
|
|
|
/* Execute the watchdog function */
|
|
|
|
up_setpicbase(wdog->picbase);
|
|
spin_unlock_irqrestore(&g_wdspinlock, flags);
|
|
|
|
CALL_FUNC(func, arg);
|
|
|
|
flags = spin_lock_irqsave(&g_wdspinlock);
|
|
}
|
|
|
|
#ifdef CONFIG_SCHED_TICKLESS
|
|
/* Decrement the nested watchdog timer count */
|
|
|
|
g_wdtimernested--;
|
|
#endif
|
|
|
|
spin_unlock_irqrestore(&g_wdspinlock, flags);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: wd_insert
|
|
*
|
|
* Description:
|
|
* Insert the timer into the global list to ensure that
|
|
* the list is sorted in increasing order of expiration absolute time.
|
|
*
|
|
* Input Parameters:
|
|
* wdog - Watchdog ID
|
|
* expired - expired absolute time in clock ticks
|
|
* wdentry - Function to call on timeout
|
|
* arg - Parameter to pass to wdentry
|
|
*
|
|
* Assumptions:
|
|
* wdog and wdentry is not NULL.
|
|
*
|
|
* Returned Value:
|
|
* Whether the head of the watchdog list has changed.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static inline_function
|
|
bool wd_insert(FAR struct wdog_s *wdog, clock_t expired,
|
|
wdentry_t wdentry, wdparm_t arg)
|
|
{
|
|
FAR struct wdog_s *curr;
|
|
FAR struct wdog_s *head;
|
|
|
|
/* Traverse the watchdog list */
|
|
|
|
head = list_first_entry(&g_wdactivelist, struct wdog_s, node);
|
|
|
|
list_for_every_entry(&g_wdactivelist, curr, struct wdog_s, node)
|
|
{
|
|
/* Until curr->expired has not timed out relative to expired */
|
|
|
|
if (!clock_compare(curr->expired, expired))
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* There are two cases:
|
|
* - Traverse to the end, where curr == &g_wdactivelist.
|
|
* - Find a curr such that curr->expected has not timed out
|
|
* relative to expired.
|
|
* In either case 1 or 2, we just insert the wdog before curr.
|
|
*/
|
|
|
|
list_add_before(&curr->node, &wdog->node);
|
|
|
|
wdog->func = wdentry;
|
|
up_getpicbase(&wdog->picbase);
|
|
wdog->arg = arg;
|
|
wdog->expired = expired;
|
|
|
|
/* Return whether the head of the watchdog list has changed. */
|
|
|
|
return head == curr;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Public Functions
|
|
****************************************************************************/
|
|
|
|
/****************************************************************************
|
|
* Name: wd_start_abstick
|
|
*
|
|
* Description:
|
|
* This function adds a watchdog timer to the active timer queue. The
|
|
* specified watchdog function at 'wdentry' will be called from the
|
|
* interrupt level after the specified number of ticks has reached.
|
|
* Watchdog timers may be started from the interrupt level.
|
|
*
|
|
* Watchdog timers execute in the address environment that was in effect
|
|
* when wd_start() is called.
|
|
*
|
|
* Watchdog timers execute only once.
|
|
*
|
|
* To replace either the timeout delay or the function to be executed,
|
|
* call wd_start again with the same wdog; only the most recent wdStart()
|
|
* on a given watchdog ID has any effect.
|
|
*
|
|
* Input Parameters:
|
|
* wdog - Watchdog ID
|
|
* ticks - Absolute time in clock ticks
|
|
* wdentry - Function to call on timeout
|
|
* arg - Parameter to pass to wdentry.
|
|
*
|
|
* NOTE: The parameter must be of type wdparm_t.
|
|
*
|
|
* Returned Value:
|
|
* Zero (OK) is returned on success; a negated errno value is return to
|
|
* indicate the nature of any failure.
|
|
*
|
|
* Assumptions:
|
|
* The watchdog routine runs in the context of the timer interrupt handler
|
|
* and is subject to all ISR restrictions.
|
|
*
|
|
****************************************************************************/
|
|
|
|
int wd_start_abstick(FAR struct wdog_s *wdog, clock_t ticks,
|
|
wdentry_t wdentry, wdparm_t arg)
|
|
{
|
|
irqstate_t flags;
|
|
bool reassess = false;
|
|
|
|
/* Verify the wdog and setup parameters */
|
|
|
|
if (wdog == NULL || wdentry == NULL)
|
|
{
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* NOTE: There is a race condition here... the caller may receive
|
|
* the watchdog between the time that wd_start_abstick is called and
|
|
* the critical section is established.
|
|
*/
|
|
|
|
flags = spin_lock_irqsave(&g_wdspinlock);
|
|
#ifdef CONFIG_SCHED_TICKLESS
|
|
/* We need to reassess timer if the watchdog list head has changed. */
|
|
|
|
if (WDOG_ISACTIVE(wdog))
|
|
{
|
|
reassess |= list_is_head(&g_wdactivelist, &wdog->node);
|
|
list_delete(&wdog->node);
|
|
wdog->func = NULL;
|
|
}
|
|
|
|
reassess |= wd_insert(wdog, ticks, wdentry, arg);
|
|
|
|
if (!g_wdtimernested && reassess)
|
|
{
|
|
/* Resume the interval timer that will generate the next
|
|
* interval event. If the timer at the head of the list changed,
|
|
* then this will pick that new delay.
|
|
*/
|
|
|
|
spin_unlock_irqrestore(&g_wdspinlock, flags);
|
|
nxsched_reassess_timer();
|
|
}
|
|
else
|
|
{
|
|
spin_unlock_irqrestore(&g_wdspinlock, flags);
|
|
}
|
|
#else
|
|
UNUSED(reassess);
|
|
|
|
/* Check if the watchdog has been started. If so, delete it. */
|
|
|
|
if (WDOG_ISACTIVE(wdog))
|
|
{
|
|
list_delete(&wdog->node);
|
|
wdog->func = NULL;
|
|
}
|
|
|
|
wd_insert(wdog, ticks, wdentry, arg);
|
|
spin_unlock_irqrestore(&g_wdspinlock, flags);
|
|
#endif
|
|
|
|
sched_note_wdog(NOTE_WDOG_START, wdentry, (FAR void *)(uintptr_t)ticks);
|
|
return OK;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: wd_start
|
|
*
|
|
* Description:
|
|
* This function adds a watchdog timer to the active timer queue. The
|
|
* specified watchdog function at 'wdentry' will be called from the
|
|
* interrupt level after the specified number of ticks has elapsed.
|
|
* Watchdog timers may be started from the interrupt level.
|
|
*
|
|
* Watchdog timers execute in the address environment that was in effect
|
|
* when wd_start() is called.
|
|
*
|
|
* Watchdog timers execute only once.
|
|
*
|
|
* To replace either the timeout delay or the function to be executed,
|
|
* call wd_start again with the same wdog; only the most recent wdStart()
|
|
* on a given watchdog ID has any effect.
|
|
*
|
|
* Input Parameters:
|
|
* wdog - Watchdog ID
|
|
* delay - Delay count in clock ticks
|
|
* wdentry - Function to call on timeout
|
|
* arg - Parameter to pass to wdentry
|
|
*
|
|
* NOTE: The parameter must be of type wdparm_t.
|
|
*
|
|
* Returned Value:
|
|
* Zero (OK) is returned on success; a negated errno value is return to
|
|
* indicate the nature of any failure.
|
|
*
|
|
* Assumptions:
|
|
* The watchdog routine runs in the context of the timer interrupt handler
|
|
* and is subject to all ISR restrictions.
|
|
*
|
|
****************************************************************************/
|
|
|
|
int wd_start(FAR struct wdog_s *wdog, clock_t delay,
|
|
wdentry_t wdentry, wdparm_t arg)
|
|
{
|
|
/* Ensure delay is within the range the wdog can handle. */
|
|
|
|
if (delay > WDOG_MAX_DELAY)
|
|
{
|
|
return -EINVAL;
|
|
}
|
|
|
|
return wd_start_abstick(wdog, clock_delay2abstick(delay), wdentry, arg);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: wd_start_period
|
|
*
|
|
* Description:
|
|
* This function periodically adds a watchdog timer to the active timer.
|
|
*
|
|
* Input Parameters:
|
|
* wdog - Pointer of the periodic watchdog.
|
|
* delay - Delayed time in system ticks.
|
|
* period - Period in system ticks.
|
|
* wdentry - Function to call on timeout.
|
|
* arg - Parameter to pass to wdentry.
|
|
*
|
|
* NOTE: The parameter must be of type wdparm_t.
|
|
*
|
|
* Returned Value:
|
|
* Zero (OK) is returned on success; a negated errno value is return to
|
|
* indicate the nature of any failure.
|
|
*
|
|
* Assumptions:
|
|
* The watchdog routine runs in the context of the timer interrupt handler
|
|
* and is subject to all ISR restrictions.
|
|
*
|
|
****************************************************************************/
|
|
|
|
int wd_start_period(FAR struct wdog_period_s *wdog, clock_t delay,
|
|
clock_t period, wdentry_t wdentry, wdparm_t arg)
|
|
{
|
|
if (!wdog || !period || !wdentry)
|
|
{
|
|
return -EINVAL;
|
|
}
|
|
|
|
wdog->func = wdentry;
|
|
wdog->period = period;
|
|
|
|
return wd_start(&wdog->wdog, delay, wdentry_period, arg);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: wd_timer
|
|
*
|
|
* Description:
|
|
* This function is called from the timer interrupt handler to determine
|
|
* if it is time to execute a watchdog function. If so, the watchdog
|
|
* function will be executed in the context of the timer interrupt
|
|
* handler.
|
|
*
|
|
* Input Parameters:
|
|
* ticks - If CONFIG_SCHED_TICKLESS is defined then the number of ticks
|
|
* in the interval that just expired is provided. Otherwise,
|
|
* this function is called on each timer interrupt and a value of one
|
|
* is implicit.
|
|
* noswitches - True: Can't do context switches now.
|
|
*
|
|
* Returned Value:
|
|
* If CONFIG_SCHED_TICKLESS is defined then the number of ticks for the
|
|
* next delay is provided (zero if no delay). Otherwise, this function
|
|
* has no returned value.
|
|
*
|
|
* Assumptions:
|
|
* Called from interrupt handler logic with interrupts disabled.
|
|
*
|
|
****************************************************************************/
|
|
|
|
#ifdef CONFIG_SCHED_TICKLESS
|
|
clock_t wd_timer(clock_t ticks, bool noswitches)
|
|
{
|
|
FAR struct wdog_s *wdog;
|
|
irqstate_t flags;
|
|
sclock_t ret;
|
|
|
|
/* Check if the watchdog at the head of the list is ready to run */
|
|
|
|
if (!noswitches)
|
|
{
|
|
wd_expiration(ticks);
|
|
}
|
|
|
|
flags = spin_lock_irqsave(&g_wdspinlock);
|
|
|
|
/* Return the delay for the next watchdog to expire */
|
|
|
|
if (list_is_empty(&g_wdactivelist))
|
|
{
|
|
spin_unlock_irqrestore(&g_wdspinlock, flags);
|
|
return 0;
|
|
}
|
|
|
|
/* Notice that if noswitches, expired - g_wdtickbase
|
|
* may get negative value.
|
|
*/
|
|
|
|
wdog = list_first_entry(&g_wdactivelist, struct wdog_s, node);
|
|
ret = wdog->expired - ticks;
|
|
|
|
spin_unlock_irqrestore(&g_wdspinlock, flags);
|
|
|
|
/* Return the delay for the next watchdog to expire */
|
|
|
|
return MAX(ret, 1);
|
|
}
|
|
|
|
#else
|
|
void wd_timer(clock_t ticks)
|
|
{
|
|
/* Check if there are any active watchdogs to process */
|
|
|
|
wd_expiration(ticks);
|
|
}
|
|
#endif /* CONFIG_SCHED_TICKLESS */
|