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.
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.
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
.
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.
#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.
#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()
#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
#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()
#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).
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.
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.
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).
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.
#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
).
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.
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
.
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.
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.
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
.
void OSTaskStatHook (void) { }
OSTimeTickHook()
OS_CPU_C.C
doesn't do anything in this function either.
void OSTimeTickHook (void) { }
OSInitHookBegin()
OS_CPU_C.C
doesn't do anything in this function.
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.
void OSInitHookEnd (void) { OSFPInit(); }
OSTCBInitHook()
OS_CPU_C.C
doesnít do anything in this function.
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.
_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.
_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.
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.
(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.
_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.
_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
.
_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
.
_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.