80x86 Port with Hardware FP Support

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

-1

Generate 80186 code

-B

Compile and call assembler

-c

Compiler to .OBJ

-d

Merge duplicate strings

-f287

Use FPU hardware instructions

-G

Select code for speed

-I

Path to compiler include files is C:\BC45\INCLUDE

-k-

Standard stack frame

-L

Path to compiler libraries is C:\BC45\LIB

-ml

Large memory model

-N-

Do not check for stack overflow

-n..\obj

Path where to place object files is ..\OBJ

-O

Optimize jumps

-Ob

Dead code elimination

-Oe

Global register allocation

-Og

Optimize globally

-Oi

Expand common intrinsic functions inline

-Ol

Loop optimization

-Om

Invariant code motion

-Op

Copy propagation

-Ov

Induction variable

-v

Source debugging ON

-vi

Turn inline expansion ON

-wpro

Error reporting: call to functions with no prototype

-Z

Suppress 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

/MX

Case sensitive on globals

/ZI

Full debug info

/O

Generate 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.