How to Use Task Callbacks

How to Use Task Callbacks

Task callbacks add powerful tools to the embedded programmers toolbox. Each task can have its own callback function, or no callback function, as follows:

void cbfunA(u32 mode)

{

     switch (mode)

     {

        case EXIT:

            /* save extended state */

            break

        case ENTER:

            /* restore extended state */

                break;

        case INIT:

            /* initialize task */

            break;

        case DELETE:

            /* task cleanup and release resources */

                break;

        case STOP:

            /* save context */

                break;

        case START:

            /* restore context if not first start */

            break;

        default:

            /* report error */

     }

}

cbfunA is the callback function for taskA. If enabled, it is called automatically by the scheduler and by certain task services. Task callback functions can be used in many ways, some of which are as follows.

Saving Extended Task Contexts

Most RTOSs provide task callbacks to save and restore extended task contexts. For example, when a task switch occurs, Cortex-M automatically saves the first 16 floating-point registers. The remaining floating-point registers, if used, must be saved by the application when the task is suspended and they must be restored when the task is resumed. Task callback functions make this easier to do:

cbfun(EXIT);

is called by the scheduler when the current task is being suspended and

cbfun(ENTER);

is called by the scheduler when it is being resumed.

These cases can be used to save additional floating point registers. On the other hand, if only a few floating point registers are actually being used, it may be desirable to turn off the Cortex-M automatic floating-point state preservation and use the EXIT and ENTER callbacks to save and restore just the floating-point registers actually being used. This reduces task switching times and memory usage.

In addition to the floating-point unit, other coprocessors may need their registers to be saved and restored during task switches. Also, task-specific variables may need to be saved and restored

Managing Task Resources

When a task first starts, it may need to be initialized and to acquire system resources, such semaphores, mutexes, and memory. This can be done with:

cbfun(INIT);

If it becomes necessary to delete the task due to its being breached by malware or due to something else, the resources can be automatically released with:

cbfun(DELETE);

The advantage of this approach is that the DELETE case is immediately below the INIT case in cbfun(). This helps to ensure that every resource acquired during task initialization is released during task deletion. Being able to shut down a task without resource leaks is important for disabling malware that has breached a task, before it can cause harm. cbfun(INIT) is called from TaskStart(), which normally is called during initialization. cbfun(DELETE) is called from TaskDelete(), which normally is called during task or system exit. So, neither adds to task switching time.

An alternate approach is for cbfun(DELETE) to send a message to an exchange where a recovery task waits to release the deleted task’s resources and to recover normal operation. The message would identify the deleted task and the recovery task would call:

cbfunA(RECOVER);

where cbfunA is the callback function for taskA and RECOVER is a case to release taskA’s resources, report the event causing taskA to be stopped, then attempt to restart taskA or start a replacement task. This is a good technique to rid a breached task of malware, if possible, or to shut it down until a fix is available, if not. In the latter case, the replacement task might be a stub that enables the system to continue running and performing its main functions.

For resources that may be acquired during run time, special handling is needed. For example, if semA may or may not have been acquired:

case DELETE:

   if (semA)

      SemDelete(semA);

Preserving One-Shot Task Contexts

The STOP and START cases are unique to SMX one-shot tasks. They serve the same purposes as the EXIT and ENTER cases do for normal tasks. Note: the first time a task is started, cbfun() is not called.

Controlling Task Callbacks

Not all tasks require callbacks and, for some tasks a callback may be necessary only in part of its code. This can be controlled as follows:

TaskSet(ct, SMX_ST_CBFUN, cbfun, 1);

    // callback is active and all cases are enabled

TaskSet(ct, SMX_ST_CBFUN, cbfun, 0);

     // callback is active and only INIT and DELETE are enabled

TaskSet(ct, SMX_ST_CBFUN, NULL, 0);

     // callback is inactive

The INIT and DELETE cases may be needed for security, as previously discussed.

Callback functions can also be enabled and disabled while a task is running:

smx_TaskSet(ct, SMX_ST_CBFUN, (u32)fpfunA, 1);

     // do floating-point operations

smx_TaskSet(ct, SMX_ST_CBFUN, cbfunA, 1);

     // return to normal

Here fpfunA(), which preserves floating-point registers and extended task context, in the event of task suspension, is being enabled before a section of code that uses floating-point. After the floating-point section, cbfunA(), which preserves only extended task context, is enabled.

Conclusion

Task callbacks provide methods to deal with difficult problems. Callback code can be found in xtask.c and xsched.c at www.github.com/Micro-Digital/SecureSMX.

#SMX, #RTOS, #Callback Function, #Task, #Scheduler

 

Hi Ralph, I would like to suggest that in a truly event driven scheduling environment, invocation of the state machine associated with an event is in essence the most powerful and efficient 'callback' feature available. The only other situation where I am comfortable with the use of 'callbacks' is when implementing target RTOS/OS agnostic libraryies and a 'callback' function supplied by an application implements the correct mechanism for returning status/data back to the application using application specific OS primitives. It's nice to read and ponder your articles/posts - very relevant topic.

Like
Reply

When do task callbacks add clarity—and when do they add unnecessary complexity?

Like
Reply

Great explanation! Quick question regarding performance, since these callbacks are performed during task transitions such as on task start, stop or switch, does this risk slowing down the system? How do we ensure that with regard to things like resource cleanup on task switches, the use of callbacks does not cause delays in the real-time processing of tasks?

Like
Reply

Why only one call back ? A set of call-back is nice. Plus à state variable and a static table that will indicate which call-back is to be called. Then, when this is done, discover that the task is useless. Only the call-backs are useful. In fact, I took that path to discover that FSMs are a much wider concepts than task. So I get rid of tasks. And then came Viacess that required tasks. So I introduced tasks as limites cases of FSMs

To view or add a comment, sign in

More articles by Ralph Moore

  • eheap, Part 2: Bins

    Like dlmalloc and similar heaps, embedded heap uses bins to hold free chunks[1]. However, eheap bins are designed for…

    2 Comments
  • eheap, Part 1: Basics

    embedded heap, eheap, is specifically designed for embedded systems. Traditionally, embedded systems have used block…

    2 Comments
  • Memory Protection Unit, Part 3: Memory Efficiency

    Contrary to popular belief, it is possible to achieve acceptable memory efficiency with the Armv6/7-M MPU. This article…

    5 Comments
  • Memory Protection Unit, Part 2: Advanced Concepts

    Basic MPU control was discussed in Part 1 of this series [ref 1]. In this part we will examine ways to work around MPU…

    8 Comments
  • Memory Protection Unit, Part 1: Basics

    MPU Operation The above figure shows the basic operation of a memory protection unit (MPU). For simplicity, this MPU…

    1 Comment
  • Portals Isolate Partitions

    APIs generally require that API functions be accessible to callers. If one partition is trying to access services in…

    3 Comments
  • Making ISRs Safe

    The Problem In the early days of embedded systems (50 years ago) we had a general rule to make ISRs as short as…

    13 Comments
  • Using the SVC Exception Part 3: Type 3 Functions

    The Problem A Type 3 function has one or more mid-function waits with active code that must execute on either side of…

    9 Comments
  • Using the SVC Exception Part 2: Type 2 Functions

    A Type 2 function has a wait at the start of the function followed by active code that must execute. Heap services are…

  • Using The SVC Exception, Part 1: Type 1 Functions

    For Cortex-M processors, code that runs in unprivileged mode (umode) can only access code in privileged mode (pmode) or…

    6 Comments

Others also viewed

Explore content categories