ChorusOS 5.0 Application Developer's Guide

Using a Watchdog Timer

The watchdog API is typically used to monitor the progress of an application. In the following example, a thread is dedicated to the watchdog.


Example 14-5 Using the Watchdog Timer

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <assert.h>

#include <util/chKnTimeVal.h>
#include <sched/chSched.h>
#include <exec/chExec.h>
#include <sync/chSem.h>
#include <cx/wdt.h>


#define WDT_RUNS          10   /* number of times the test is run */
#define WDT_PAT_TIMES      3   /* number of times the timer 
                                * is patted at each interval
				*/
#define WDT_PAT_INC       10   /* timeout interval increment */
#define WDT_PAT_MIN        5   /* initial, minimum timeout interval */
#define WDT_STACKSIZE   1000   /* worker thread stack size */
#define WDT_THREAD_PRIO    3   /* worker thread (very high) priority */
#define WDT_EXTRA_SLEEP   10   /* extra delay to check that the timer 
				* is truly disarmed
				*/

wdt_handle_t wdt_handle = 0;   /* watchdog timer handle */


/*
 * sleep for 'secs' seconds, displaying a symbol at each elapsed second
 */
void
wdt_sleep(int secs)
{
    KnTimeVal tv = { 1, 0 };
    int       i;

    putchar('[');

    for (i = 0; i < secs; i++) {
        threadDelay(&tv);
        if (i && (i % 60 == 59))
            putchar('#');
        else
        if (i && (i % 10 == 9))
            putchar('|');
        else
            putchar('.');

        fflush(stdout);
    }
    printf("]\n");
}


/*
 * check the return value of a system call
 */
void
wdt_check(char* fn, int res)
{
    printf("%s: ", fn);

    if (res < 0)
        printf("ERROR: res = %d, errno = %d (%s)\n", res,
	        errno, strerror(errno));
    else
        printf("SUCCESS: res = %d\n", res);
}


/*
 * arm, pat, then disarm the watchdog timer
 */
int
wdt_arm_pat_disarm(int min, int max, int inc, int times, int armed)
{
    timespec_t tv;
    int res, intv, wait, pats;

    intv = min;
    while (intv < max) {

        tv.tv_sec  = intv;
        tv.tv_nsec = 0;

        /* timer already armed? */
        if (!armed) {
            printf("\nsetting interval to %d s...\n", intv);
            res = wdt_set_interval(wdt_handle, &tv);
            wdt_check("wdt_set_interval()", res);
            if (res < 0)
                return res;

            printf("arming...\n");
            res = wdt_arm(wdt_handle);
            wdt_check("wdt_arm()", res);
            if (res < 0) 
                return res;
        }
        
        printf("patting...\n");
        for (pats = 0; pats < times; pats++) {

            /* pat 1 sec before the timer expires */
            wait = intv - 1;
            
            printf("sleeping for %d s\n", wait);
            wdt_sleep(wait);

            res = wdt_pat(wdt_handle);
            wdt_check("wdt_pat()", res);
            if (res < 0)
                return res;
        }

        printf("disarming...\n");
        res = wdt_disarm(wdt_handle);
        wdt_check("wdt_disarm()", res);
        if (res < 0)
            return res;
        armed = 0;

        /* wait 'WDT_EXTRA_SLEEP' secs more than last configured interval */
        wait = intv + WDT_EXTRA_SLEEP;
        printf("checking that the watchdog timer is stopped...\n");
        printf("sleeping for %d s...\n", wait);
        wdt_sleep(wait);

        intv += inc;
    }

    return 0;
}


static KnSem wdt_sem;


/*
 * worker thread
 */
void
wdt_thread()
{    
    timespec_t max;
    int armed, i, res, min;
    
    for (i = 0; i < WDT_RUNS; i++) {
        
      /* Allocate watchdog timer. If timer is already allocated (busy),
         a previous instance of the actor was killed. In this case, 
	 reallocate timer and, if it is armed, continue the test
         without disarming it */
        res = wdt_alloc(&wdt_handle);
        wdt_check("wdt_alloc()", res);

        if (res < 0 && errno == EBUSY) {

            printf("watchdog timer busy -> reallocating it\n");
            
            /* Reallocate timer, using the magic handle (as we have
               no knowledge of the actual handle previously allocated).
               this call also pats the timer if it is armed */
            wdt_handle = WDT_MAGIC_HANDLE;
            res = wdt_realloc(&wdt_handle);
            wdt_check("wdt_realloc()", res);
        }

        printf("--> watchdog handle = %d\n", wdt_handle);
        
        if (res < 0)
            break;

        /* the timer may be already armed when we reallocate it */
        res = armed = wdt_is_armed(wdt_handle);
        wdt_check("wdt_is_armed()", res);
        if (res < 0)
            break;
        printf("--> the watchdog timer is currently%s armed\n",
	       armed ? "" : " NOT");

        /* ok, get current timeout interval of the armed timer */
        if (armed) {
            timespec_t intv;
          
            res = wdt_get_interval(wdt_handle, &intv);
            wdt_check("wdt_get_interval()", res);
            if (res < 0)
                break;
            printf("--> current interval = %d.%09d s\n",
	           intv.tv_sec, intv.tv_nsec);

            /* continue test at point where preceding actor was killed */
            min = intv.tv_sec;
        }
        else {
            min = WDT_PAT_MIN;
        }

        /* then get the maximum allowed timeout interval */
        res = wdt_get_maxinterval(wdt_handle, &max);
        wdt_check("wdt_get_maxinterval()", res);
        printf( "--> max interval = %d.%09d s\n", max.tv_sec, max.tv_nsec);
        if (res < 0)
            break;
        
        res = wdt_arm_pat_disarm(min, max.tv_sec - WDT_EXTRA_SLEEP, 
                                 WDT_PAT_INC, WDT_PAT_TIMES, armed);
        if (res < 0)
            break;
        
        res = wdt_free(wdt_handle);
        wdt_check("wdt_free()", res);
        if (res < 0)
            break;
    }
    
    /* test completed */
    semV(&wdt_sem); 

    threadDelete(K_MYACTOR, K_MYSELF);
}


static long wdt_stack[WDT_STACKSIZE];  /* watchdog thread stack */


int
main()
{
    KnThreadLid          childLid;
    KnThreadDefaultSched schedParam;
    KnDefaultStartInfo_f startInfo;
    KnActorPrivilege     actorPriv;
    int                  res;

    semInit(&wdt_sem, 0);

    actorPrivilege(K_MYACTOR, &actorPriv, NULL);
    
    startInfo.dsPrivilege        = (actorPriv == K_SUPACTOR) ? 
                                   K_SUPTHREAD : K_USERTHREAD;
    startInfo.dsType             = K_DEFAULT_START_INFO;
    startInfo.dsSystemStackSize  = K_DEFAULT_STACK_SIZE;
    startInfo.dsUserStackPointer = &wdt_stack[WDT_STACKSIZE];
    startInfo.dsEntry            = (KnPc) wdt_thread;
    
    /* heavily increase the priority of the worker thread to ensure that
       it will not be later delayed by other applicative threads (if any) */
    schedParam.tdClass           = K_SCHED_DEFAULT;
    schedParam.tdPriority        = WDT_THREAD_PRIO;

    /* create the worker thread */
    res = threadCreate(K_MYACTOR, &childLid, K_ACTIVE,
                       &schedParam, &startInfo);
    assert(res == 0);

    /* then wait for the completion of the test */
    semP(&wdt_sem, K_NOTIMEOUT);

    return 0;
}