<<< | >>>

ColdFire RTOS Implementation

Phase 4: Preemptive Multitasking

By far the most complex phase of the project was implementing preemptive multitasking.

  1. Setting up a timer to generate RTOS ticks,
  2. Writing an interrupt handler to handle the RTOS tick events, and
  3. Modifying context-switching code to work in this new situation.

Timers

A timer is required in preemptive multitasking to interrupt execution at regular intervals in order to check if a context-switch is required. FreeRTOS™ calls this interrupts the RTOS tick.

The MCF5272 platform supports up to four general-purpose timers. Timers are set up by specifying a reference number which is the number of ticks from the system clock that should be counted before an interrupt from the timer is sent. At this point the count may restart or continue counting. This is determined by the free-run bit (FRR) of the corresponding timer mode register (TMRx).

However, the reference number is a 16-bit number giving a maximum count of 65,635. As the system clock runs at 66MHz, this gives a maximum timer period of approximately 1ms (i.e. frequency 1kHz). This may not be sufficient for some applications and so a clock prescaler is provided. The prescaler divides the clock input by a given value effectively allowing for a longer period. For example, by setting the prescaler value to 2, the clock value would be halved before comparing to our reference value. This would shift our timer frequency range to 500Hz - 33MHz (instead of 1kHz - 66MHz).

We have used the first timer (TMR0) for our RTOS tick and set the prescaler to 131 giving us a tick frequency of approximately 8Hz - 500kHz. The other fields and their values are described below. These figures are based on the MCF5272 datasheet.

Timer Mode Register

Bits

Name

Description

Value

15-8

PS

Prescaler. Set to 128 as discussed above.

1000 0000

7-6

CE

Capture edge behaviour. Not used in this case.

00

5

OM

Output mode. Not used in this application.

1

4

ORI

Output reference interrupt enable. Enables interrupts when the reference value is reached.

1

3

FRR

Free run/restart. Set to restart the counter when the reference value is reached.

1

2-1

CLK

Input clock source. Set to master system clock.

01

0

RST

Reset timer. Set to enable timer.

0

The other registers used in setting up the timer are as follows:

TRR0 Timer Reference Register:
This register simply contains the 16-bit reference number to which the (prescaled) clock count should be compared. For example, if a tick frequency of 1kHz was required, this number should be set to 66MHz (system clock frequency) / 1kHz (desired tick frequency) / 128 (prescaler value), i.e. 516.

TCAN0 Timer Capture Register:
This register is used to store the current value of the count when an external capture event occurs (e.g. for timing an external event). As we are not using external capture events this register can be ignored.

TCN0 Timer Counter:
This register stores the 16-bit value of the counter which is compared to the value in the reference register. We do not need to use this register directly, except perhaps to clear it when setting up the timer.

TER0 Timer Event Register:
This register reports recognised events. For example, when the counter reaches the reference value, the REF bit of the TER0 register is set. We do not need to use this register directly, except perhaps to clear it at startup.


Interrupt handling and modifying the context-switching code

When an interrupt occurs the MCF5272 saves the value of the status register, program counter and other information to a special exception stack frame which is pushed onto the stack. Control then jumps to the corresponding interrupt handler (also referred to as an exception handler as an interrupt is treated as a type of exception). When the interrupt handler has completed it issues an RTE (Return from Exception) instruction which restores the status register and program counter from the exception stack frame on the stack.

Having registered an interrupt handler for the TMR0 interrupt source our task was to write the handler such that when it returned, a different task was resumed as determined by the FreeRTOS™ scheduler. This involved putting a different exception stack frame on the top of the stack before issuing the RTE instruction. Accommodating such a change would have implications on the cooperative context-switching code. For example, when generating the initial context for a task we can not know in advance if that context would be loaded in a cooperative context-switch or a preemptive context-switch. Consequently we carefully analysed the situation and revised the cooperative context-switching code.

We decided to use the RTE instruction to return from all context switches and therefore to generate an exception stack frame both for the initial stack and for cooperative context-switches. The changes to the initial stack and restore context code are shown in Table 2.

 

Restore context assembly code

Comment

 

move.l pxCurrentTCB, %a0

Loads the address of the task's stack (stored in the task control block) onto the stack pointer.

Initial stack

move.l (%a0),%sp

0x6

move.l %sp@+,%a6

Pops the value on the top of the stack onto register A6 (also known as FP for frame pointer) and increments the stack pointer.

0x5

move.l %sp@+,%a5

Likewise for register A5…

0x4

move.l %sp@+,%a4

 

0x3

move.l %sp@+,%a3

 

0x2

move.l %sp@+,%a2

 

0x1

move.l %sp@+,%a1

 

0x0

move.l %sp@+,%a0

 

0x7

move.l %sp@+,%d7

Pops the value on the top of the stack onto register D7.

0x6

move.l %sp@+,%d6

Likewise for register D6…

0x5

move.l %sp@+,%d5

 

0x4

move.l %sp@+,%d4

 

0x3

move.l %sp@+,%d3

 

0x2

move.l %sp@+,%d2

 

0x1

move.l %sp@+,%d1

 

0x2000

move.l %sp@+,%d0

move.w %d0,%sr

Pops the value on the top of stack onto register D0 and then onto the status register.

0x0

move.l %sp@+,%d0

Pops the value on the top of the stack onto register D0.

(address of first instruction of task)

 

 

0x0

 

 

(function parameter—address passed in)

 

 

0x33

 

 

0x22

 

 

0x11

 

 

Table 2 - Comparison of initial stack with updated restore context code.

There are two basic changes in this diagram. The first change is the inclusion of the extra value on the stack 0x41142000. This is the first longword of the exception stack frame as shown in Figure 3.


Figure 3 - Exception stack frame form.

The value 0x41142000 corresponds to an exception generated by the TMR0 interrupt where the status register indicates the interrupts are enabled and the supervisor mode is set (also Format is set to 4 and FS is set to 0).

The other change shown in Table 2 is that the status register is no longer on the stack. This is because it is included in the exception stack frame. This is fine for preemptive context-switches because the MCF5272 will automatically generate an exception stack frame with the values of the status register (thereby addressing the problem we identified earlier where the status register might not be saved correctly if we shuffle it in and out of a data register first) however for cooperative context-switches we need to generate our own exception stack frame. To achieve this, the yield function was modified as shown in Table 3.

Previous implementation in vPortYield()

Revised implementation in vPortYield()

 

portSAVE_CONTEXT();
vTaskSwitchContext();
portRESTORE_CONTEXT();

asm volatile ("rts");

/* Save status register but
 * reenable interrupt.s */
asm volatile ( "move.l %%d1,-8(%%sp) \n\t" \
  "clr.l %%d1 \n\t"          \
  "move.w %%sr,%%d1 \n\t"    \
  "ori.l %0,%%d1 \n\t"       \
  "andi.l %1,%%d1 \n\t"      \
  "move.l %%d1,%%sp@- \n\t"  \
  "move.l -4(%%sp),%%d1\n\t" \
  : /* no output */          \
  : "n" (EXSF_FORMAT(4) | \ EXSF_VECTOR(VECTOR_TMR0)), \
    "n" (~STATUS_I(7)) );

portSAVE_CONTEXT();
vTaskSwitchContext();
portRESTORE_CONTEXT();

asm volatile ("rte");

Table 3 - Changes to vPortYield() to generate an exception stack frame.

Disabling Interrupts

Now that we had enabled interrupts within our port we needed to protect critical sections of the operation system by defining methods for enabling and disabling interrupts and marking critical sections of code.

Unlike many microprocessors, the MCF5272 does not have instructions for enabling and disabling interrupts. Instead, interrupts are enabled or disabled by setting the interrupt level bits in the status register (SR). Bits 10-8 of the status register set the interrupt priority mask which determines which interrupt requests are serviced. If the value is set to binary 111, then all interrupts are serviced but if the value is set to binary 000, then all interrupt requests are inhibited, effectively disabling the interrupts.

The SimpleOS disables and enables interrupts with the following instructions:

#define enable_interrupts() asm("move #0x2000,%sr")
#define disable_interrupts() asm("move #0x2700,%sr")
The value 0x2000 corresponds to setting the interrupt priority to the maximum level (i.e. allowing all interrupt requests to be services) and setting the supervisor bit (bit 13).

The value 0x2700 corresponds to setting the interrupt to the minimum level and setting the supervisor mode bit.

For simply enabling and disabling interrupts we have used the same approach. For critical sections of code however we have defined a slightly more complicated macro which leaves the remaining bits of the status register untouched as shown in Code Extract 2.

#define portENTER_CRITICAL() asm volatile ( \
  "move.w %%sr,%%d1     \n\t" \
  "ori.l #0x700,%%d1    \n\t" \
  "move.w %%d1,%%sr"          \
   : : : "d1" );
#define portEXIT_CRITICAL() asm volatile ( \
  "move.w %%sr,%%d1     \n\t" \
  "andi.l #0xF8FF,%%d1  \n\t" \
  "move.w %%d1,%%sr"          \
  : : : "d1" );
Code Extract 2 - Critical code Although preemptive multitasking was the most complex phase of our project, we experienced very few unexpected problems in this phase due to our thorough study during previous phases. Some of the issues we faced (both expectedly and unexpectedly) are described here:

Frame Pointers:
During this phase we decided to address more thoroughly the issue with frame pointers that we had identified in the previous phase. Our solution was to include a define in the makefile which would indicate if frame pointers were included. Based on this we could manually remove the frame pointer from the stack when saving the process context. For example, we have inserted the code in Code Extract 3 at the start of the vPortYield function before the call to save the context.

  /* As the naked attribute is not
   * supported for m68k we need to
   * reverse the effects of the
   * function prologue by removing
   * the frame pointer. */
#ifndef OMIT_FRAME_POINTER
  asm volatile ( "unlk %fp" );
#endif
Code Extract 3 - Code to remove the frame pointer from stack when saving the process context

The compiler also puts a frame pointer on the stack when interrupt handlers are called and so a similar modification was required there. The interrupt handler was also modified so that context saving and restoring was performed directly within the handler function (rather than in a called function). This reduced some of the complexity in removing frame pointers.

Reentrancy in Newlib functions: Preemptive context switches may occur during calls to Newlib functions. Although the Newlib functions are reentrant, some functions require additional support in terms of defining a reentrancy block for each execution thread. This can be performed either by the application or the real time operating system . We investigated implementing this in our port of the FreeRTOS™ although it appeared impossible to achieve without changing common FreeRTOS™ code because there is no possibility of freeing the reentrancy block when the task is terminated. Hence, it is necessary for the application to provide its own reentrancy block. We suggest that a future enhancement to FreeRTOS™ could be to define a port-specific function to be called when a task is terminated.


To test our implementation of preemptive multitasking we developed a simple test program, the body of which is shown in Program 3.

static void vTestTask( void *pvParameters );
static int task1id = 1;
static int task2id = 2;
static int task3id = 3;
static int task4id = 4;
xSemaphoreHandle xSemaphore = NULL;

portSHORT main( void )
{
    xTaskCreate( vTestTask, "Task 1", 1000,
    (void *) &task1id, tskIDLE_PRIORITY + 2, NULL );
    xTaskCreate( vTestTask, "Task 2", 1000,
    (void *) &task2id, tskIDLE_PRIORITY + 2, NULL );
    xTaskCreate( vTestTask, "Task 3", 1000,
    (void *) &task3id, tskIDLE_PRIORITY + 2, NULL );
    xTaskCreate( vTestTask, "Task 4", 1000,
    (void *) &task4id, tskIDLE_PRIORITY + 2, NULL );

    printf("starting scheduler...\n");

    vSemaphoreCreateBinary( xSemaphore );
    vTaskStartScheduler( pdTRUE );

    return 0;
}
/*-----------------------------------------*/

static void vTestTask( void *pvParameters )
{
    int taskId = *((int *)pvParameters);
    while(1)
    {
        if ( cSemaphoreTake( xSemaphore, 0 ) )
        {
            printf("Task %d running.\n", taskId);
            cSemaphoreGive( xSemaphore );
        }
    }
}
Program 3 - Program to demonstrate preemptive multitasking

The results of running this program are shown in Figure 4.


Figure 4 - Output from the test program demonstrating cooperative multitasking.

NEXT >>> ColdFire RTOS Port - Phase 5 - Running The Software Tests

Copyright (C) Amazon Web Services, Inc. or its affiliates. All rights reserved.