80x86 Port with Hardware FP Support

Real Mode, Large Model with Hardware Floating-Point Support

This section describes how µC/OS-II has been ported to the Intel 80x86 series of processors that provides a Floating-Point Unit (FPU). Some of the processors that can make use of this port are the Intel 80486™, Pentiums™ (all models), Xeon™, AMD Athlon™, K6™-series, ElanSC520™ and more. The port assumes the Borland C/C++ compiler V4.51 and was setup to generate code for the large memory model. The processor is assumed to be running in real mode. The code for this port is very similar to the one presented in 80x86 Port with Emulated FP Support and in some cases, I will only be presenting the differences.

This port assumes that you enabled code generation for OSTaskCreateExt (by setting OS_TASK_CREATE_EXT_EN to 1 in OS_CFG.H) and that you enabled µC/OS-IIís memory management services (by setting OS_MEM_EN to 1 in OS_CFG.H). Of course, you must set OS_MAX_MEM_PART to at least 1. Finally, tasks that will perform floating-point operations MUST be created using OSTaskCreateExt and set the OS_TASK_OPT_SAVE_FP option.

Figure 15.1 shows the programming model of an 80x86 processor running in real mode. The integer registers are identical to those presented in 80x86 Port with Emulated FP Support. In fact, they are saved and restored using the same technique. The only difference between this port and the one presented in that section is that we also need to save and restore the FPU registers which is done by using the context switch hook functions.

Figure - Figure 15.1 80x86 real-mode register model


Development Tools

As in the section 80x86 Port with Emulated FP Support, I used the Borland C/C++ V4.51 compiler along with the Borland Turbo Assembler for porting and testing. This compiler generates reentrant code and provides in-line assembly language instructions that can be inserted in C code. The compiler can be directed to generate code specifically to make use of the FPU. I tested the code on a 300 MHz Pentium-II-based computer running the Microsoft Windows 2000 operating system. In fact, I configured the compiler to generate a DOS executable which was run in a DOS window.

Finally, you can also adapt the port provided in this section to other 80x86 compiler as long as they generate real-mode code. You will most likely have to change some of the compiler options and assembler directives if you use a different development environment.

Table 15.1 shows the Borland C/C++ compiler V4.51 options (i.e., flags) supplied on the command line. These settings were used to compile the port as well as example 4 provided in Getting Started with µC/OS-II.

Table - Table 15.1, Compiler options used to compile port and examples
Option (i.e., setting)Description
-1Generate 80186 code
-BCompile and call assembler
-cCompiler to .OBJ
-dMerge duplicate strings
-f287Use FPU hardware instructions
-GSelect code for speed
-IPath to compiler include files is C:\BC45\INCLUDE
-k-Standard stack frame
-LPath to compiler libraries is C:\BC45\LIB
-mlLarge memory model
-N-Do not check for stack overflow
-n..\objPath where to place object files is ..\OBJ
-OOptimize jumps
-ObDead code elimination
-OeGlobal register allocation
-OgOptimize globally
-OiExpand common intrinsic functions inline
-OlLoop optimization
-OmInvariant code motion
-OpCopy propagation
-OvInduction variable
-vSource debugging ON
-viTurn inline expansion ON
-wproError reporting: call to functions with no prototype
-ZSuppress redundant loads


Table 15.2 shows the Borland Turbo Assembler V4.0 options (i.e., flags) supplied on the command line. These settings were used to assemble OS_CPU_A.ASM.

Table - Table 15.2, Assembler options used to assemble .ASM files
Option (i.e., setting)Description
/MXCase sensitive on globals
/ZIFull debug info
/OGenerate overlay code


Directories and Files

The installation program provided on the companion CD installs the port for the Intel 80x86 (real mode, large model with FPU support) on your hard disk. The port is found under the:

\SOFTWARE\uCOS-II\Ix86L-FP\BC45

directory. The directory name stands for Intel 80x86 real mode, Large model with hardware Floating-Point instructions and is placed in the Borland C++ V4.5x directory. The source code for the port is found in the following files: OS_CPU.H, OS_CPU_C.C, and OS_CPU_A.ASM.

INCLUDES.H

Listing 15.1 shows the contents of INCLUDES.H for this 80x86 port. It is identical to the one used in 80x86 Port with Emulated FP Support.

INCLUDES.H is not really part of the port but is described here because it is needed to compile the port files.

Listing - Listing 15.1 INCLUDES.H
#include    <stdio.h>
#include    <string.h>
#include    <ctype.h>
#include    <stdlib.h>
#include    <conio.h>
#include    <dos.h>
#include    <math.h>
#include    <setjmp.h>
 
#include    "os_cpu.h"
#include    "os_cfg.h"
#include    "ucos_ii.h"
#include    "pc.h"


OS_CPU.H

OS_CPU.H contains processor- and implementation-specific #defines constants, macros, and typedefs. OS_CPU.H for the 80x86 port is shown in Listing 15.2. Most of OS_CPU.H is identical to the OS_CPU.H of the section 80x86 Port with Emulated FP Support.

Listing - Listing 15.2 OS_CPU.H
#ifdef  OS_CPU_GLOBALS
#define OS_CPU_EXT
#else
#define OS_CPU_EXT  extern
#endif
 
typedef unsigned char  BOOLEAN;                  (1)
typedef unsigned char  INT8U;                    
typedef signed   char  INT8S;                    
typedef unsigned int   INT16U;                   
typedef signed   int   INT16S;                   
typedef unsigned long  INT32U;                   
typedef signed   long  INT32S;                   
typedef float          FP32;                     (2)
typedef double         FP64;                     
 
typedef unsigned int   OS_STK;                   (3)
typedef unsigned short OS_CPU_SR;                (4)

(1) If you were to consult the Borland compiler documentation, you would find that an int and a short are 16 bits and a long is 32 bits.

(2) Floating-point data types are included because itís assumed that you will be performing floating-point operations in your tasks. However, µC/OS-II itself doesnít make use of floating-point numbers.

(3) A stack entry for the 80x86 processor running in real mode is 16 bits wide; thus, OS_STK is declared accordingly. The stack width doesnít change because of this port. All task stacks must be declared using OS_STK as its data type.

(4) The status register (also called the processor flags) on the 80x86 processor running in real mode is 16 bits wide. The OS_CPU_SR data type is used only if OS_CRITICAL_METHOD is set to 3 which it isnít for this port. I included the OS_CPU_SR data type anyway, in case you use a different compiler and need to used OS_CRITICAL_METHOD #3.


OS_CPU.H, OS_ENTER_CRITICAL() and OS_EXIT_CRITICAL()

Listing - Listing 15.2 - OS_CPU.H
#define  OS_CRITICAL_METHOD    2                          (5)
 
#define  OS_ENTER_CRITICAL()  asm {PUSHF; CLI}            (6)
#define  OS_EXIT_CRITICAL()   asm  POPF

(5) For this port, the prefered critical method #2 because itís directly supported by the compiler.

(6) OS_ENTER_CRITICAL is implemented by saving the interrupt disable status onto the stack and then disable interrupts. This is accomplished on the 80x86 by executing the PUSHF instruction followed by the CLI instruction. OS_EXIT_CRITICAL simply needs to execute a POPF instruction to restore the original contents of the processorís SW register.


OS_CPU.H, Stack Growth

Listing - Listing 15.2 - OS_CPU.H
#define  OS_STK_GROWTH        1                       (7)

(7) The stack on an 80x86 processor grows from high to low memory, which means that OS_STK_GROWTH must be set to 1.


OS_CPU.H, OS_TASK_SW()

Listing - Listing 15.2 - OS_CPU.H
#define  uCOS                 0x80                    (8)
 
#define  OS_TASK_SW()         asm  INT   uCOS         (9)

(9) To switch context, OS_TASK_SW needs to simulate an interrupt. The 80x86 provides 256 software interrupts to accomplish this. The interrupt service routine (ISR) (also called the exception handler) must vector to the assembly language function OSCtxSw (see OS_CPU_A.ASM). We thus need to ensure that the pointer at vector 0x80 points to OSCtxSw.

(8) I tested the code on a PC and I decided to use interrupt number 128 (0x80).


OS_CPU.H, Tick Rate

I also decided (see 80x86 Port with Emulated FP Support for additional details) to change the tick rate of the PC from the standard 18.20648Hz to 200Hz (i.e., 5ms between ticks).

Listing - Listing 15.2 - OS_CPU.H
OS_CPU_EXT  INT8U  OSTickDOSCtr;                              (10)

(10) This statement declares an 8-bit variable (OSTickDOSCtr) that keeps track of the number of times the ticker is called. Every 11th time, the DOS tick handler is called. OSTickDOSCtr is used in OS_CPU_A.ASM and really only applies to a PC environment.


OS_CPU.H, Floating-Point Functions

This port defines three special functions that are specific to the floating-point capabilities of the 80x86. In other words, I had to add three new functions to the port to handle the floating-point hardware.

Listing - Listing 15.2 - OS_CPU.H
void       OSFPInit(void);                                 (11)
void       OSFPRestore(void *pblk);                        (12)
void       OSFPSave(void *pblk);                           (13)

(11) A function has been added to initialize the floating-point handling mechanism described in this port.

(12) OSFPRestore will be called to retrieve the value of the floating-point registers when a task is being switched-in. OSFPRestore is actually written in assembly language and is thus found in OS_CPU_A.ASM.

(13) OSFPSave will be called to save the current value of the floating-point registers when a task is being suspended. OSFPSave is also written in assembly language and found in OS_CPU_A.ASM.


OS_CPU_C.C

As mentioned in Porting µC/OS-II and in 80x86 Port with Emulated FP Support, µC/OS-II port requires that you write ten fairly simple C functions:

OSTaskStkInit()
OSTaskCreateHook()
OSTaskDelHook()
OSTaskSwHook()
OSTaskIdleHook()
OSTaskStatHook()
OSTimeTickHook()
OSInitHookBegin()
OSInitHookEnd()
OSTCBInitHook()

µC/OS-II only requires OSTaskStkInit. The other nine functions must be declared but generally donít need to contain any code. However, this port will make use of OSTaskCreateHook, OSTaskDelHook, OSTaskSwHook and OSInitHookEnd.

The #define constant OS_CPU_HOOKS_EN (see OS_CFG.H) should be set to 1.

OSTaskStkInit()

This function is called by OSTaskCreate and OSTaskCreateExt and is identical to the OSTaskStkInit presented in section 14.01.01. You may recall that OSTaskStkInit is called to initialize the stack frame of a task so that it looks as if an interrupt has just occurred and all of the processor integer registers were pushed onto it. Figure 15.2 (identical to Figure 14.3) shows what OSTaskStkInit puts on the stack of the task being created. Note that the diagram doesnít show the stack frame of the code calling OSTaskStkInit but rather, the stack frame of the task being created. Also, the stack frame only contains the contents of the integer registers, nothing about the floating point registers. Iíll discuss how we handle the FPU registers shortly.

Figure - Figure 15.2 Stack frame initialization with pdata passed on the stack


For reference, Listing 15.3 shows the code for OSTaskStkInit which is identical to the one shown in 80x86 Port with Emulated FP Support (Listing 14.3).

Listing - Listing 15.3 - OS_CPU_C.C, OSTaskStkInit()
OS_STK  *OSTaskStkInit (void  (*task)(void *pd), 
                        void   *pdata, 
                        OS_STK *ptos, 
                        INT16U  opt)
{
    INT16U *stk;
 
 
    opt    = opt;                           
    stk    = (INT16U *)ptos;
    *stk-- = (INT16U)FP_SEG(pdata); 
    *stk-- = (INT16U)FP_OFF(pdata);         
    *stk-- = (INT16U)FP_SEG(task);
    *stk-- = (INT16U)FP_OFF(task);
    *stk-- = (INT16U)0x0202;      
    *stk-- = (INT16U)FP_SEG(task);
    *stk-- = (INT16U)FP_OFF(task);
    *stk-- = (INT16U)0xAAAA;      
    *stk-- = (INT16U)0xCCCC;                
    *stk-- = (INT16U)0xDDDD;                
    *stk-- = (INT16U)0xBBBB;                
    *stk-- = (INT16U)0x0000;                
    *stk-- = (INT16U)0x1111;                
    *stk-- = (INT16U)0x2222;                
    *stk-- = (INT16U)0x3333;                
    *stk-- = (INT16U)0x4444;                
    *stk   = _DS;           
    return ((OS_STK *)stk);
}


OSFPInit()

OSFPInit is called by OSInitHookEnd when OSInit is done initializing µC/OS-IIís internal structures (I will discuss OSInitHookEnd later). OSFPInit is basically used to initialize the floating-point context switching mechanism presented in this section. OSFPInit assumes that you enabled µC/OS-IIís memory management functions (i.e., you must set OS_MEM_EN to 1 in OS_CFG.H). The code for OSFPInit is shown in Listing 15.4.

Listing - Listing 15.4
#define OS_NTASKS_FP       (OS_MAX_TASKS + OS_N_SYS_TASKS - 1)               (1)
#define OS_FP_STORAGE_SIZE 128                                               (2)
 
static  OS_MEM *OSFPPartPtr;                                                 (3)
static  INT32U  OSFPPart[OS_NTASKS_FP][OS_FP_STORAGE_SIZE / sizeof(INT32U)]; (4)
 
 
void  OSFPInit (void)
{
    INT8U    err;
#if OS_TASK_STAT_EN
    OS_TCB  *ptcb;
    void    *pblk;
#endif
    
    
    OSFPPartPtr = OSMemCreate(&OSFPPart[0][0],                                (5)
                  OS_NTASKS_FP, 
                  OS_FP_STORAGE_SIZE, 
                  &err);
    
#if OS_TASK_STAT_EN && OS_TASK_CREATE_EXT_EN 
    ptcb            = OSTCBPrioTbl[OS_STAT_PRIO];                             (6)
    ptcb->OSTCBOpt |= OS_TASK_OPT_SAVE_FP;                                    (7)
    pblk            = OSMemGet(OSFPPartPtr, &err);                            (8)
    if (pblk != (void *)0) {                                                  (9)
        ptcb->OSTCBExtPtr = pblk;                                            (10)
        OSFPSave(pblk);                                                      (11)
    }
#endif
}

(1) Although not actually part of OSFPInit, I defined this constant that will be used to determine how many storage ëbuffersí will be needed to save FPU register values. In this case, I decided to have as many buffers as I have tasks plus one for the statistic task as described below.

(2) The 80x86 FPU requires 108 bytes of storage. I decided to allocate 128 bytes for future expansion. If you are tight on memory, you could save 20 bytes per task by setting this value to 108.

(3) We will be using a µC/OS-II memory partition for the storage of all the FPU contexts. OSFPPartPtr is a pointer to the partition created for this purpose. Because OSFPPartPtr is declared static, your application will not know it exist.

(4) OSFPPart[][] is the actual partition that will hold the storage for all the FPU registers of all the tasks. As you can probably tell, you need to have at least:

(OS_MAX_TASKS + 1) * 128

bytes of RAM (i.e., data space) for this partition. Because OSFPPart[][] is declared static, your application will not know it exist.

(5) OSFPInit tells µC/OS-II about this partition. You may recall that OSMemCreate will break the partition into memory blocks (each of 128 bytes) and links these blocks in a singly-linked list. If an FPU storage block is needed, we simply need to call OSMemGet (discussed in OSTaskCreateHook).

(6) I decided to change the attributes of OS_TaskStat to allow it to perform floating-point math. You may wonder why I do this since OS_TaskStat does not perform any floating-point operations. I did this because you may decide to extend the functionality of OS_TaskStat through OSTaskStatHook and, possibly perform floating-point calculations. OSFPInit finds the pointer to the statistic taskís OS_TCB.

(7) The .OSTCBOpt flag is set indicating that OS_TaskStat is a task that needs to save and restore floating-point registers because µC/OS-II doesnít set this option by default.

(8) I get a storage buffer that will hold the contents of the floating-point registers for OS_TaskStat when OS_TaskStat is switched-out.

(9) Always prudent to check for an invalid pointer.

(10) The pointer to the FPU storage area is saved in the OS_TCB extension pointer, .OSTCBExtPtr. This will allow the context switch code to know where floating-point registers are saved.

(11) The function OSFPSave (see OS_CPU_A.ASM) is called to store the current contents of the FPU registers at the location pointed to by pblk. It doesnít really matter what the FPU registers contain when we do this. The important thing to realize is that the FPU registers contain valid values, whatever they may be. OSFPSave is discussed in section 15.05.05.


You should be careful that your code doesn't generate any floating-point exception (e.g. divide by zero) because µC/OS-II will not do anything about them. Run-time exceptions can, however, be avoided by adding range testing code to your application. In fact, you should make it a practice to check for possible divide by zero and the like.

OSTaskCreateHook()

Listing 15.5 shows the code for OSTaskCreateHook. Recall that OSTaskCreateHook is called by OS_TCBInit (which in turn is called by OSTaskCreate or OSTaskCreateExt).

Listing - Listing 15.5
void  OSTaskCreateHook (OS_TCB *ptcb)
{
    INT8U  err;
    void  *pblk;
    
    
    if (ptcb->OSTCBOpt & OS_TASK_OPT_SAVE_FP) {            (1)
        pblk = OSMemGet(OSFPPartPtr, &err);                (2)
        if (pblk != (void *)0) {                           (3)
            ptcb->OSTCBExtPtr = pblk;                      (4)
            OSFPSave(pblk);                                (5)
        }
    }
}

(1) If you create a task that will perform floating-point calculations, you must set the OS_TASK_OPT_SAVE_FP bit in opt argument of OSTaskCreateExt. This option tells OSTaskCreateHook that the task will make use of the FPU and thus, we will need to save and restore the values of these registers during a context switch into or out of this task.

(2) Because we are creating a task that will use the FPU, we need to allocate storage for the FPU registers.

(3) Again, itís a good idea to validate the pointer.

(4) The pointer to the storage area is saved in the OS_TCB of the task being created.

(5) Again, the function OSFPSave (see OS_CPU_A.ASM) is called to store the current contents of the FPU registers at the location pointed to by pblk. It doesnít really matter what the FPU registers contain when we do this. The important thing to realize is that the FPU registers contain valid values, whatever they may be. OSFPSave is discussed in section 15.05.05.


Figure 15.3 shows the relationship between some of the data structures after OSTaskCreateHook has executed.

Figure - Figure 15.3 Initialized stack and FPU register storage


OSTaskDelHook()

You may recall that OSTaskDelHook is called by OSTaskDel to extend the functionality of OSTaskDel. Because we allocated a memory block to hold the contents of the floating-point registers when the task was created, we need to deallocate the block when the task is deleted. Listing 15.6 shows how this is accomplished by OSTaskDelHook.

Listing - Listing 15.6 OS_CPU_C.C, OSTaskDelHook
void OSTaskDelHook (OS_TCB *ptcb)
{
    if (ptcb->OSTCBOpt & OS_TASK_OPT_SAVE_FP) {            (1)
        if (ptcb->OSTCBExtPtr != (void *)0) {              (2)
            OSMemPut(OSFPPartPtr, ptcb->OSTCBExtPtr);      (3)
        }
    }
}

(1)

(2) We first need to confirm that we allocated a memory block that was used for floating-point context storage.

(3) The memory block is returned to the its proper memory partition.


OSTaskSwHook()

OSTaskSwHook is used to extend the functionality of the context switch code. You may recall that OSTaskSwHook is called by OSStartHighRdy, the task-level context switch function OSCtxSw and, the ISR context switch function OSIntCtxSw. Listing 15.7 shows how OSTaskSwHook is implemented.

Listing - Listing 15.7
void  OSTaskSwHook (void)
{
    INT8U  err;
    void  *pblk;
                                                 
    if (OSRunning == TRUE) {                               (1)
        if (OSTCBCur->OSTCBOpt & OS_TASK_OPT_SAVE_FP) {    (2)
            pblk = OSTCBCur->OSTCBExtPtr;
            if (pblk != (void *)0) {                       (3)
                OSFPSave(pblk);                            (4)
            }
        }
    }
                                                           
    if (OSTCBHighRdy->OSTCBOpt & OS_TASK_OPT_SAVE_FP) {    (5)
        pblk = OSTCBHighRdy->OSTCBExtPtr;                  
        if (pblk != (void *)0) {                           (6)
            OSFPRestore(pblk);                             (7)
        }
    }
}

(1) When OSStartHighRdy calls OSTaskSwHook, it is trying to ërestoreí the contents of the floating-point registers of the highest priority task. When OSStartHighRdy is called, OSRunning is FALSE indicating that we havenít started multitasking yet and thus, OSTaskSwHook must not ësaveí the floating-point registers.

(2) If OSTaskSwHook is called by either OSCtxSw or OSIntCtxSw, then we are ëswitching-outí a task (i.e., suspending a lower priority task) and thus, we check to see if this task was created with the floating-point option.

(3) Just to be sure, we also check the contents of the .OSTCBExtPtr to make sure itís not a NULL pointer, it shoudnít.

(4) As usual, we call OSFPSave to save the current contents of the floating-point registers to the memory block allocated for that purpose.

(5) We then check to see if the task to be ëswitched-iní (i.e., the higher priority task) was created with the floating-point option. In other words, it checks whether you told OSTaskCreateExt that this task will be doing floating-point operations.

(6) Just to be sure, we also check the contents of the .OSTCBExtPtr in case itís a NULL pointer.

(7) The function OSFPRestore (see OS_CPU_A.ASM) is called to restore the current contents of the FPU registers from the location pointed to by pblk. OSFPRestore is discussed in section 15.05.06.


OSTaskIdleHook()

OS_CPU_C.C doesn't do anything in this function.

Listing - Listing 15.8
void  OSTaskIdleHook (void)
{
}


OSTaskStatHook()

OS_CPU_C.C doesn't do anything in this function. See Example 3 in Getting Started with µC/OS-II for an example on what you can do with OSTaskStatHook.

Listing - Listing 15.9
void  OSTaskStatHook (void)
{
}


OSTimeTickHook()

OS_CPU_C.C doesn't do anything in this function either.

Listing - Listing 15.10
void  OSTimeTickHook (void)
{
}


OSInitHookBegin()

OS_CPU_C.C doesn't do anything in this function.

Listing - Listing 15.11
void  OSInitHookBegin (void)
{
}


OSInitHookEnd()

OSInitHookEnd is called just before OSInit returns. This means that OSInit initialized µC/OS-IIís memory partition services (which you should have to use this port by setting OS_MEM_EN to 1 in OS_CFG.H). OSInitHook simply calls OSFPInit (see section 15.04.02) which is responsible for setting up the memory partition reserved to hold the contents of floating-point registers for each task. The code for OSInitHookEnd is shown in Listing 15.12.

Listing - Listing 15.12
void  OSInitHookEnd (void)
{
    OSFPInit();
}


OSTCBInitHook()

OS_CPU_C.C doesnít do anything in this function.

Listing - Listing 15.13
void  OSTCBInitHook (void)
{
}


OS_CPU_A.ASM

A µC/OS-II port requires that you write four assembly language functions:

OSStartHighRdy()
OSCtxSw()
OSIntCtxSw()
OSTickISR()

This port adds two functions called OSFPSave and OSFPRestore and are found in OS_CPU_A.ASM. These functions are responsible for saving and restoring the contents of floating-point registers during a context switch, respectively.

OSStartHighRdy()

This function is called by OSStart to start the highest priority task ready to run. It is identical to the OSStartHighRdy presented in 80x86 Port with Emulated FP Support. The code is shown again in Listing 15.14 for your convenience but will not be discussed since you can review it from the section 80x86 Port with Emulated FP Support.

Listing - Listing 15.14 - OSStartHighRdy()
_OSStartHighRdy  PROC FAR
 
            MOV    AX, SEG _OSTCBHighRdy          
            MOV    DS, AX                         
;
            CALL   FAR PTR _OSTaskSwHook
;
            MOV    AL, 1                
            MOV    BYTE PTR DS:_OSRunning, AL     
;
            LES    BX, DWORD PTR DS:_OSTCBHighRdy
            MOV    SS, ES:[BX+2]                  
            MOV    SP, ES:[BX+0]                  
;
            POP    DS                            
            POP    ES                                            
            POPA                                                 
;
            IRET
 
_OSStartHighRdy  ENDP


OSCtxSw()

A task-level context switch is accomplished on the 80x86 processor by executing a software interrupt instruction. The interrupt service routine must vector to OSCtxSw. The sequence of events that leads µC/OS-II to vector to OSCtxSw begins when the current task calls a service provided by µC/OS-II, which causes a higher priority task to be ready to run. At the end of the service call, µC/OS-II calls the function OS_Sched, which concludes that the current task is no longer the most important task to run. OS_Sched loads the address of the OS_TCB of the highest priority task into OSTCBHighRdy, then executes the software interrupt instruction by invoking the macro OS_TASK_SW. Note that the variable OSTCBCur already contains a pointer to the current taskís task control block, OS_TCB. The code for OSCtxSw which is identical to the one presented in 80x86 Port with Emulated FP Support is shown in Listing 15.15. OSCtxSw will be discussed again because of the added complexity of the floating-point context switch.

Listing - Listing 15.15 - OSCtxSw()
_OSCtxSw    PROC   FAR                                           (1)
;
            PUSHA                                                (2)
            PUSH   ES                              
            PUSH   DS                              
;
            MOV    AX, SEG _OSTCBCur               
            MOV    DS, AX                          
;
            LES    BX, DWORD PTR DS:_OSTCBCur                    (3)
            MOV    ES:[BX+2], SS                   
            MOV    ES:[BX+0], SP                   
;
            CALL   FAR PTR _OSTaskSwHook                         (4)
;
            MOV    AX, WORD PTR DS:_OSTCBHighRdy+2               (5)
            MOV    DX, WORD PTR DS:_OSTCBHighRdy   
            MOV    WORD PTR DS:_OSTCBCur+2, AX     
            MOV    WORD PTR DS:_OSTCBCur, DX       
;
            MOV    AL, BYTE PTR DS:_OSPrioHighRdy                (6)
            MOV    BYTE PTR DS:_OSPrioCur, AL      
;
            LES    BX, DWORD PTR DS:_OSTCBHighRdy                (7)
            MOV    SS, ES:[BX+2]                   
            MOV    SP, ES:[BX]                     
;
            POP    DS                                            (8)
            POP    ES                              
            POPA                                   
;
            IRET                                                 (9)
;
_OSCtxSw    ENDP


Figure 15.4 shows the stack frames as well as the FPU storage areas of the task being suspended and the task being resumed.

Figure - Figure 15.4 80x86 stack frames and FPU storage during a task-level context switch

The notes below apply both and simultaneously to Listing 15.15 and Figure 15.4. When reading each numbered note, refer to both the listing and the figure.

(1) F15.4

(1) L15.15() - On the 80x86 processor, the software interrupt instruction forces the SW register to be pushed onto the current taskís stack followed by the return address (segment and then offset) of the task that executed the INT instruction [i.e., the task that invoked OS_TASK_SW].

(2) F15.4

(2) L15.15() - The remaining CPU registers of the task to suspend are saved onto the current taskís stack.

(3) F15.4

(3) L15.15() - The pointer to the new stack frame is saved into the taskís OS_TCB. This pointer is composed of the stack segment (SS register) and the stack pointer (SS register). The OS_TCB in µC/OS-II is organized such that the stack pointer is placed at the beginning of the OS_TCB structure to make it easier to save and restore the stack pointer using assembly language.

(4) F15.4

(5) F15.4

(4) L15.15() - The task switch hook OSTaskSwHook is then called. Note that when OSTaskSwHook is called, OSTCBCur points to the current taskís OS_TCB, while OSTCBHighRdy points to the new taskís OS_TCB. You can thus access each taskís OS_TCB from OSTaskSwHook. OSTaskSwHook first saves the current contents of the FPU registers into the storage area allocated to the current task. This storage is pointed to by the .OSTCBExtPtr field of the current taskís OS_TCB. The FPU registers are then loaded with the values stored in the new taskís storage area. Again, the .OSTCBExtPtr field of the new task points to the storage area of the floating-point registers. Of course, the storage and retrieval is contingent on the .OSTCBExtPtr of each task being non-NULL. However, it is quite possible for the new task to not require floating-point and thus not have any storage area for it. In this case, OSTaskSwHook would not change the contents of the FPU.

(5) L15.15() - Upon return from OSTaskSwHook, OSTCBHighRdy is copied to OSTCBCur because the new task will now also be the current task.

(6) L15.15() - Also, OSPrioHighRdy is copied to OSPrioCur for the same reason.

(6) F15.4

(7) L15.15() - At this point, OSCtxSw can load the processorís registers with the new taskís context. This is done by retrieving the SS and SP registers from the new taskís OS_TCB.

(7) F15.4

(8) L15.15() - The remaining CPU registers are pulled from the new taskís stack.

(8) F15.4

(9) L15.15() - An IRET instruction is executed to load the new taskís program counter and status word. After this instruction, the processor resumes execution of the new task.


Note that interrupts are disabled during OSCtxSw and also during execution of OSTaskSwHook.

OSIntCtxSw()

OSIntCtxSw is called by OSIntExit to perform a context switch from an ISR (Interrupt Service Routine). Because OSIntCtxSw is called from an ISR, it is assumed that all the processorís integer registers are already properly saved onto the interrupted taskís stack.

The code is shown in Listing 15.16 and is identical to the OSIntCtxSw presented in 80x86 Port with Emulated FP Support. The floating-point registers are handled by OSTaskSwHook. Figure 15.5 shows the context switch process from OSIntCtxSwí s point of view.

As in 80x86 Port with Emulated FP Support, let's assume that the processor receives an interrupt. Letís also supposed that interrupts are enabled. The processor completes the current instruction and initiates an interrupt handling procedure.

Figure - Figure 15.5 80x86 stack frames and FPU storage during an interrupt-level context switch

(1) The 80x86 automatically pushes the processorís SW register followed by the return address of the interrupted task onto the stack. The CPU then vectors to the proper ISR. µC/OS-II requires that your ISR begins by saving the rest of the processorís integer registers. Once the registers are saved, µC/OS-II requires that you also save the contents of the stack pointer in the taskís OS_TCB.


Your ISR then needs to either call OSIntEnter or, increment the global variable OSIntNesting by one. At this point, we can assume that the task is suspended and we could, if needed, switch to a different task.

The ISR can now start servicing the interrupting device and possibly, make a higher priority task ready. This occurs if the ISR sends a message to a task by calling either OSFlagPost, OSMboxPost, OSMboxPostOpt, OSQPostFront, OSQPost or OSQPostOpt. A higher priority task can also be resumed if the ISR calls OSTaskResume, OSTimeTick or OSTimeDlyResume.

Assume that a higher priority task is made ready to run by the ISR. µC/OS-II requires that an ISR calls OSIntExit() when it has finished servicing the interrupting device. OSIntExit basically tell µC/OS-II that itís time to return back to task-level code if all nested interrupts have completed. In other words, when OSIntNesting is decremented to 0 by OSIntExit, OSIntExit would return to task level code.

When OSIntExit executes, it notices that the interrupted task is no longer the task that needs to run because a higher priority task is now ready. In this case, the pointer OSTCBHighRdy is made to point to the new taskís OS_TCB, and OSIntExit calls OSIntCtxSw to perform the context switch.

Listing - Listing 15.16
_OSIntCtxSw PROC   FAR
;
            CALL   FAR PTR _OSTaskSwHook                        (1)
;
            MOV    AX, SEG _OSTCBCur               
            MOV    DS, AX                          
;
            MOV    AX, WORD PTR DS:_OSTCBHighRdy+2              (2)
            MOV    DX, WORD PTR DS:_OSTCBHighRdy   
            MOV    WORD PTR DS:_OSTCBCur+2, AX     
            MOV    WORD PTR DS:_OSTCBCur, DX       
;
            MOV    AL, BYTE PTR DS:_OSPrioHighRdy               (3)
            MOV    BYTE PTR DS:_OSPrioCur, AL
;
            LES    BX, DWORD PTR DS:_OSTCBHighRdy               (4)
            MOV    SS, ES:[BX+2]                   
            MOV    SP, ES:[BX]                     
;
            POP    DS                                           (5)
            POP    ES                              
            POPA                                   
;
            IRET                                                (6)
;
_OSIntCtxSw ENDP

The notes below apply both and simultaneously to Listing 15.16 and Figure 15.5. When reading each numbered note, refer to both the listing and the figure.

(2)

(3)

(1) The first thing OSIntCtxSw does is call OSTaskSwHook. Note that when OSTaskSwHook is called, OSTCBCur points to the current taskís OS_TCB, while OSTCBHighRdy points to the new taskís OS_TCB. You can thus access each taskís OS_TCB from OSTaskSwHook. As previously discussed, OSTaskSwHook first saves the current contents of the FPU registers into the storage area allocated to the current task. This storage is pointed to by the .OSTCBExtPtr field of the current taskís OS_TCB. The FPU registers are then loaded with the values stored in the new taskís storage area. Again, the .OSTCBExtPtr field of the new task points to the storage area of the floating-point registers.

(2) Upon return from OSTaskSwHook, OSTCBHighRdy is copied to OSTCBCur because the new task will now also be the current task.

(3) OSPrioHighRdy is also copied to OSPrioCur for the same reason.

(4)

(4) At this point, OSCtxSw can load the processorís registers with the new taskís context. This is done by retrieving the SS and SP registers from the new taskís OS_TCB.

(5)

(5) The remaining CPU registers are pulled from the stack.

(6)

(6) An IRET instruction is executed to load the new taskís program counter and status word. After this instruction, the processor resumes execution of the new task.


Note that interrupts are disabled during OSIntCtxSw and also during execution of OSTaskSwHook.

OSTickISR()

As mentioned in section 15.03.05, Tick Rate, the tick rate of an RTOS should be set between 10 and 100Hz. On the PC, however, the ticker occurs every 54.93ms (18.20648Hz) and is obtained by a hardware timer that interrupts the CPU. Recall that I reprogrammed the tick rate to 200Hz because it was a multiple of 18.20648Hz. The ticker on the PC is assigned to vector 0x08 but µC/OS-II redefined it so that it vectors to OSTickISR instead. Because of this, the PCís tick handler is saved [see PC.C, PC_DOSSaveReturn] in vector 129 (0x81). To satisfy DOS, however, the PCís handler is called every 54.93ms. OSTickISR for this port is identical to the OSTickISR presented in section 14.05.04 and thus, there is no need to repeat the description here. I did, however, include the code in Listing 15.17 for your convenience.

Listing - Listing 15.17
_OSTickISR  PROC   FAR
;
            PUSHA
            PUSH   ES
            PUSH   DS
;
            MOV    AX, SEG(_OSIntNesting)
            MOV    DS, AX
            INC    BYTE PTR DS:_OSIntNesting     
;
            CMP    BYTE PTR DS:_OSIntNesting, 1                               
            JNE    SHORT _OSTickISR1             
            MOV    AX, SEG(_OSTCBCur)            
            MOV    DS, AX
            LES    BX, DWORD PTR DS:_OSTCBCur  
            MOV    ES:[BX+2], SS                 
            MOV    ES:[BX+0], SP                 
;
_OSTickISR1:
            MOV    AX, SEG(_OSTickDOSCtr)
            MOV    DS, AX
            DEC    BYTE PTR DS:_OSTickDOSCtr
            CMP    BYTE PTR DS:_OSTickDOSCtr, 0
            JNE    SHORT _OSTickISR2             
;
            MOV    BYTE PTR DS:_OSTickDOSCtr, 11
            INT    081H                          
            JMP    SHORT _OSTickISR3
 
_OSTickISR2:
            MOV    AL, 20H
            MOV    DX, 20H                       
            OUT    DX, AL                        
;
_OSTickISR3:
            CALL   FAR PTR _OSTimeTick
;
            CALL   FAR PTR _OSIntExit
;
            POP    DS                
            POP    ES
            POPA
;
            IRET
;
_OSTickISR  ENDP


OSFPSave()

OSFPSave is not normally part of a µC/OS-II port. OSFPSave basically takes the contents of the floating-point registers and saves them at the address passed to OSFPSave. OSFPSave is called from C but is written in assembly language because it must execute an FPU instruction which is not available from C. OSFPSave is called by the C functions OSFPInit, OSTaskCreateHook and OSTaskSwHook as follows:

OSFPSave((void *pblk);

Where pblk is the address of a storage area large enough to hold the FPU context and, must be at least 108 bytes.

Listing 15.18 shows the code for OSFPSave.

Listing - Listing 15.18
_OSFPSave    PROC   FAR
;
             PUSH   BP                                        (1)
             MOV    BP,SP
             PUSH   ES                              
             PUSH   BX
;
             LES    BX, DWORD PTR [BP+6]                      (2)
;
             FSAVE  ES:[BX]                                   (3)
;
             POP    BX                                        (4)
             POP    ES
             POP    BP
;
             RET                                              (5)
;
_OSFPSave    ENDP

(1) OSFPSave saves integer registers onto the current taskís stack because they are needed by this function.

(2) The pointer passed to OSFPSave as an argument is loaded into ES:BX.

(3) The FPU instruction FSAVE is executed. This instruction saves the whole context of the FPU (108 bytes worth) at the address found in ES:BX.

(4) The temporary registers are retrieved from the stack.

(5) OSFPSave returns to its caller.


OSFPRestore()

OSFPRestore is also not normally part of a µC/OS-II port. OSFPRestore basically loads the FPU registers with the contents of a memory buffer pointed to by the address passed to OSFPRestore. OSFPRestore is called from C but is written in assembly language because it must execute an FPU instruction which is not available from C. OSFPRestore is only called by OSTaskSwHook as follows:

OSFPRestore((void *pblk);

Where pblk is the address of a storage area large enough to hold the FPU context and, must be at least 108 bytes.

Listing 15.19 shows the code for OSFPRestore.

Listing - Listing 15.19
_OSFPRestore PROC   FAR
;
             PUSH   BP                              (1)
             MOV    BP,SP
             PUSH   ES                              
             PUSH   BX
;
             LES    BX, DWORD PTR [BP+6]            (2)
;
             FRSTOR ES:[BX]                         (3)
;
             POP    BX                              (4)
             POP    ES
             POP    BP
;
             RET                                    (5)
;
_OSFPRestore ENDP

(1) OSFPRestore saves integer registers onto the current taskís stack because they are needed by this function.

(2) The pointer passed to OSFPRestore as an argument is loaded into ES:BX.

(3) The FPU instruction FRSTOR is executed. This instruction loads the FPU with the contents of the memory location pointed to by ES:BX.

(4) The temporary registers are retrieved from the stack.

(5) OSFPRestore returns to its caller.


Memory Usage

The only code that changed in 80x86 Port with Hardware FP Support from the code provided in 80x86 Port with Emulated FP Support was OS_CPU_A.ASM, OS_CPU_C.C and OS_CPU.H. These files add only an additional 164 of code space (ROM).

You MUST include the code for OSTaskCreateExt (set OS_TASK_CREATE_EXT to 1 in OS_CFG.H) and the memory management services (set OS_MEM_EN to 1 in OS_CFG.H) because this port would not work without them.

With respect to data space, this port requires a memory buffer of 128 bytes (although we only need 108 bytes) for each task that will perform floating-point operations.

The spreadsheet for this port is found on the companion CD (\SOFTWARE\uCOS-II\Ix86L-FP\BC45\DOC\80x86L-FP-ROM-RAM.XLS). You need Microsoft Excel for Office 2000 (or higher) to use this file. The spreadsheet allows you to do ìwhat-ifî scenarios based on the options you select. You can change the configuration values (in RED) and see how they affects µC/OS-IIís ROM and RAM usage on the 80x86. For the ???_EN values, you MUST use either 0 or 1.

As with 80x86 Port with Emulated FP Support, I setup the Borland compiler to generate the fastest code. The number of bytes shown are not meant to be accurate but are simply provided to give you a relative idea of how much code space each of the µC/OS-II group of services require.

The spreadsheet also shows you the difference in code size based on the value of OS_ARG_CHK_EN in your OS_CFG.H. You donít need to change the value of OS_ARG_CHK_EN to see the difference.

The Data column is not as straightforward. Notice that the stacks for both the idle task and the statistics task have been set to 1,024 bytes (1Kb) each. Based on your own requirements, these number may be higher or lower. As a minimum, µC/OS-II requires about 3,500 bytes of RAM for µC/OS-II internal data structures if you configure the maximum number of tasks (62 application tasks). I added an entry that specifies the number of tasks that will be doing floating-point operations. Remember that each such task requires a buffer of 128 bytes. One buffer is always allocated because I changed the statistic task to allow floating-point.

If you use an 80x86 processor, you will most likely not be too restricted with memory and thus, µC/OS-II will most likely not be the largest user of memory.