1. Introduction
1.1 Writing ROMmable Programs With CC386
The CAC-UL CC386 compiler is an excellent tool for producing multi-segmented protected mode programs for the i386EX. However, it not supplied in a condition which is suitable for those wishing to produce "traditional" ROM + RAM embedded programs. Such applications have no integral monitor or operating system and are sometimes also known as "barefoot" programs.
It assumes that the user will link in some sort of monitor debugger such as XDB, CSIMON or telemon386 which will take care of the protected mode initialisation. Alternatively, an operating system such as pSOS is relied upon to establish the protected mode environment. In many applications, it is not desirable to include a monitor and a costly operating system is not a possibility.
In these cases, the user is required to write the startup code himself, which while not impossible, certainly does required an in-depth understanding of protected concepts and structures. At the start of a project, this specialist knowledge is not likely to be available. To correct this and help new 386EX protected mode programs get underway as quickly as possible, Hitex has produced some 386EX Protected Mode "Template" programs which are intended to be used as the basis for your own projects.
To make use of the simplest protected mode features is not really that difficult, it is just that much of the terminology is very obscure, being deeply rooted in operating system theory. However, it is the ability of the 386EX to implement basic operating system functions directly in the silicon that is perhaps its best (free!) feature. Some years ago, Intel published a now obsolete booklet of "Protected Mode Templates", which tried to show how to set the different varieties of protected mode. Unfortunately, this was written for the now defunct Intel IC386 C compiler and was at best, tricky to understand!
This Hitex technical bulletin is designed to show how the potentially most complex but powerful protected mode can be initialised and then exploited to provide a very robust basis for many types of application development. It avoids using the obscure "language of protected mode" that Intel (inadvertently) used in their obsolete templates book. Two versions are provided:
NOTASK
A true multi-segmented protected mode program but which does not use any of the multi-tasking capabilities of the 386 core. This represents the simplest way of getting the protection mechanisms to help increase the ruggedness of your software.
TWOTASK
A true multi-segmented, multi-tasking protected mode program but which demonstrates a simple application of the task-switching capabilities of the 386 core. It is well-worth taking the time to understand how the multi-tasking structures are initialised and used as this is the only free lunch you'll ever get from Intel!
1.2 Preparing The CADUL CC386 C Compiler Kit For True Embedded Use
1.2.1 Making The Run Time Library ROMmable
Before the CADUL kit can be used, you will have to process the default i386CC00.LIB ANSI runtime library for embedded use. Large library functions such as printf() require their RAM data areas to be initialised with specific start values before they are called for the first time and the ROMLIB batch file is part of the means for putting this data in place automatically. The other parts of this process is explained in section 4 as it is not essential at this stage to understand all aspects of it.
ROMLIB.BAT:
LIB386 i386cc00.lib -X * del *.o ren isalpha.obj isalpha.o ren iscntrl.obj iscntrl.o ren isalnum.obj isalnum.o FOR %%I IN (*.OBJ) DO INI386 -INIT DATA %%I ren isalpha.o isalpha.obj ren iscntrl.o iscntrl.obj ren isalnum.o isalnum.obj echo I386CC01.LIB > LIB.CMD FOR %%i In (*.OBJ) DO echo %%i >> LIB.CMD LIB386 @LIB.CMD del LIB.CMD del *.obj del *.o
1.2.2 Converting The Library
The ROMmable library conversion procedure is very simple:
Copy the batch file "ROMLIB.BAT" to the LIBRARY directory of the CC386 compiler. This will be either:
\CC386\LIB
Or:
\ORGANON\CC386\LIB
Enter the directory and just type:
ROMLIB <return>
If you are using the Windows Explorer, just double-click the ROMLIB icon.
The batch file will now perform the following functions:
1. Extract all the object files from the default I386CC00.LIB
library
2. Rename the static initialised data segment from each object
(.OBJ) file to "INIT_DATA"
3. Create a new library called "I386CC01.LIB" which
holds the processed object files.
4. Clean up the directory
If successful, the LIB directory will contain a new library "I386CC01.LIB". All the example programs reference this library.
1.3 Example Overview
The example program consists of the following files:
TASK1 Files
USERINT.C MAIN.C 386EXCON.C Initialise 386EX hardware INI386EX.C Intialise 386EX hardware ICU.C Setup up interrupt controller TIMER.C Setup timer unit SERIAL.C Low level serial port drivers T1INT.C Timer 1 interrupt service routine WRITE.C Low level serial port interface for printf() GPF_HAND.C General protection fault handler COMMS.C General comms routines TASK2 Files TASK2.C Simple main function with endless loop for TASK2 GLOBAL Files GLOBAL.C Globally visible data items LCD.C Create variables that overlay LCD panel control register
STARTUP Files
STARTUP.ASM RESET code and protected mode intialisation INTRPT.ASM
Header Files
EV386EX.H 80386EX.H GLOBAL.H LCD.H MAIN.H COMMS.H USERINT.H TASK2.H
Command Files
SAMPLE.CMD STARTUP.CMD TASK01.CMD GLOBAL.CMD TASK02.CMD
Final Build File
SAMPLE.BLD
Script Files For HITOP386
LOAD.SCR Load SAMPLE program as a HEX and symbol file SAMPLE.SCR Set up emulator for protected mode operation
ROMmable Runtime Library Convertor
ROMLIB.BAT
The program is designed to show the basic features of a typical multi-segmented protected mode 386EX program. It shows how the timer unit and serial ports are setup to produce interrupts and it generates produce a simple clock display on a VT52 terminal, attached to serial port 1. The Intel Application Builder was used to set up some of the peripherals. We recommend this tool for anybody new to the i386EX!
The example is the simplest possible multi-segmented protected mode, multi-tasking program construction method and has been written to help explain how the 386 core is really supposed to be used. You are free to use it as a basis for your own programs. However, it is important to understand how the 386 core is initialised to run in protected mode and you are advised to study this document before trying to write your own programs. The two most important files to understand are STARTUP.ASM and SAMPLE.BLD. Print these out and read the comments carefully to see how the various controls and address labels correspond. There are several constants in these files that must correspond otherwise the CPU will crash so do not be tempted to alter anything unless you are sure you know what you are doing! Where these quantities exist, they are noted in the copious comments.
The steps required to get into protected mode are reasonably simple. Unfortunately, it is all too easy to make this look very complicated and indeed many other example programs are guilty of this. When used properly, the LINK386 does most of the tricky things for you.
2. Basic 386 Protected Mode Concepts
The GDT (Global Descriptor Table), IDT (Interrupt Descriptor Table) and LDTs (Local Descriptor Tables) are simply look-up tables which tell the 386 which areas in the 64MB memory space are filled with memory or memory-mapped IO devices and what sort of access is permissible to them. Essentially, the tables just contain the start and end addresses of EPROM and RAM areas. Usually CODE and CONSTANT areas are read only and the RAM read/write so the entries in the GDT and/or LDT will reflect this. When the 386 runs, every time a jump or call occurs or some data is accessed, the 386 checks in the GDT (or LDTs) to see if the user has specified that the memory region being addressed is known and acceptable. If the address concerned is not within a legal region, the 386 vectors off to interrupt 13 which is where the "GENERAL PROTECTION FAULT" service routine is located.
GDT 1 0008H 1 0 RW 16 00000200H 000000FFH GDT: 2 0010H 1 0 RW 16 00000300H 000001FFH IDT: 7 0038H 1 0 RO 32 03FFF000H 000007FFH ROM_TABLES ß EPROM area 8 0043H 1 3 RW 32 00000700H 00000006H RAM_TABLES 9 0048H 1 0 RWD 32 00003000H FFFFEFFFH stack_seg 10 0050H 1 0 ER 16 03FFFA00H 000005F3H RESET_CODE ß EPROM area 11 0058H 1 0 ER 32 03FFC800H 00000028H GLOBAL_CODE ß EPROM area 12 0060H 1 0 RW 32 00018000H 00003FFFH GLOBAL_DATA 13 0068H 1 0 RWD 32 00006000H FFFFEFFFH GLOBAL_STACK
In REAL mode, the 386's interrupt table is located at address zero. In protected mode, it is called the Interrupt Descriptor Table (IDT) and can be located anywhere, but it is usually at the bottom of the RAM.
The LDTs are additional tables of address regions which list memory areas for individual tasks. A task in 386 terms is a sub-program which has its own data and code regions, listed in its LDT - in the example, TASK1 uses LDT_FIRST and TASK2 uses LDT_SECOND and so on.
LDT.1 (LDT_FIRST) 1 000CH 1 0 RW 16 00000580H 0000007FH LDT_FIRST: 2 0014H 1 0 ER 32 03FE4000H 00007FFFH T1_CODE ß EPROM area 3 001CH 1 0 RW 32 00008000H 00007FFFH T1_DATA 4 0024H 1 0 RWD 32 00004000H FFFFEFFFH T1_STACK 5 002CH 1 0 RO 32 03FFD000H 00000036H INIT_DATA ß TASK1 has initialised data 6 0034H 1 0 ER 32 03FFC000H 00000111H S_CODE ß EPROM area LDT.2 (LDT_SECOND) 1 000CH 1 0 RW 16 00000680H 0000007FH LDT_SECOND: 2 0014H 1 0 ER 32 03FEC000H 00003FFFH T2_CODE ß EPROM area 3 001CH 1 0 RW 32 00010000H 00007FFFH T2_DATA 4 0024H 1 0 RWD 32 00005000H FFFFEFFFH T2_STACK
In PC terms, Microsoft Word is a "task", albeit a big one, running under Windows 95. If a task tries to access a memory region that is not listed in its own LDT, the 386 will again vector to entry 13 in the interrupt descriptor table to get the address of the GENERAL PROTECTION FAULT service routine. This hardware-enforced isolation of tasks is one of the 386EX's best features and is what makes it potentially one of the best platforms for very high integrity software.
Interestingly, if a memory region is listed in the Global Descriptor Table (GDT), code in any task may access it freely. If task1 wants to pass some data to task2, it would deposit it in a memory region that is listed in the GDT, from where task2 could pick it up. If code in task1 wants to call a function in task2, a "GATE" is used, i.e. a special CALL which allows the passing from one task to another. Thus calls between tasks can only take place at defined points or GATEs. The example program has a memory region (or more correctly segment) called "GLOBAL_DATA" which appears in the GDT and in which some globally visible variables exist. Both TASK1 and TASK2 can see them as a result.
All setting up of the protected mode is really concerned with is telling the 386 where it can find the GDT and IDT! All the complications arise due to the need to use a small amount of 32 bit assembly language plus in a ROM + RAM system, getting the tables out of ROM and into RAM.
3. Building The GDT, IDT and LDT Tables
Before we can tell the 386 where the tables are, they must first be built. The location and contents of all the tables is specified by the programmer in the file SAMPLE.BLD, which is processed by LINK386.EXE. The addresses of the code and data for each task must also be given. This procedure is based on memory "SEGMENTS" which are just collections of like objects, i.e. all the functions in a program would be in a segment called "CODE32" and all the variables in a segment called "DATA". The .BLD file has the SEGMENT statement which allows the CODE32 segment to be placed in EPROM and the DATA segment in RAM. It also has the TABLE control which creates the GDT etc. and puts segments into them.
3.1 Memory Segments
When CC386 compiles a file, it produces an .OBJ file that contains SEGMENTS of code or data that have the names:
Segment Contents CODE32: All your compiled C statements and constants DATA: All your variables STACK: Any stack required by your C functions
When the different .OBJ files that constitute a task are linked together, you can rename these segments to make them more distinctive, when they appear in the SAMPLE.BLD file. Taking TASK1 as an example, the TASK01.CMD control file for the linker is:
-DB -LI -NAME First_Task -OBJECT task01.bnd -PRINT task01.mp1 -VERBOSE -RENAME CODE32=T1_CODE -RENAME DATA=T1_DATA -RENAME STACK=T1_STACK
Here, CODE32 is RENAMEd (abbreviated to "-RN") to T1_CODE and the DATA to T1_DATA. Hopefully, these new segment names show that they are associated with TASK1 ("T1_"). All the segments in TASK1 are collectively known as "First_Task", through the "-NAME" control.
Likewise, the CODE32 and DATA segments for TASK2 object files are renamed to T2_CODE and T2_DATA, know collectively as "SECOND_TASK".
This SAMPLE.BLD extract shows the various segments being allocated to their addresses in ROM and RAM:
SEGMENT
stack_seg (base = 000002000h, DPL=0, USE32, LIMIT=00FFFH),
T1_STACK (base = 000003000h, DPL=0, USE32, LIMIT=00FFFH),
T2_STACK (base = 000004000h, DPL=0, USE32, LIMIT=00FFFH),
GLOBAL_STACK(base = 000005000h, DPL=0, USE32, LIMIT=00FFFH),
T1_DATA (base = 000008000h, DPL=0, USE32, LIMIT=07FFFH),
T2_DATA (base = 000010000h, DPL=0, USE32, LIMIT=07FFFH),
GLOBAL_DATA (base = 000018000h, DPL=0, USE32, LIMIT=03FFFH),
ROM_TABLES (base = 003FFF000h, DPL=0, USE16, LIMIT=07FFH),
RESET_CODE (base = 003FFFA00H, DPL=0, USE16),
S_CODE (BASE = 003FFC000H, DPL=0, USE32),
GLOBAL_CODE (BASE = 003FFC800H, DPL=0, USE32),
T1_CODE (BASE = 003FE4000H, DPL=0, USE32,LIMIT=07FFFH),
T2_CODE (BASE = 003FEC000H, DPL=0, USE32,LIMIT=03FFFH),
INIT_DATA (BASE = 003FFD000H, DPL=0, USE32); -- Created by INI386.EXE
The BASE control sets the base address of the segment and the LIMIT is the length of the segment-1. These segments are then listed in either the GDT or one of the two LDTs. The positions in the tables are generally known as "slots":
Here are the "GLOBAL_xxx" segments in GDT slots:
ENTRY =( -- 0: reserved
-- 1:GDT_ALIAS, -- Filled in automatically by LINK386
-- 2:IDT_ALIAS, -- Filled in automatically by LINK386
3:FIRST_TASK_TSS,
4:LDT_FIRST,
5:SECOND_TASK_TSS,
6:LDT_SECOND,
7:ROM_TABLES,
8:RAM_TABLES,
9:stack_seg,
10:RESET_CODE,
11:GLOBAL_CODE,
12:GLOBAL_DATA,
13:GLOBAL_STACK,
14:FirstTaskGate,
15:SecondTaskGate),
LIMIT = 32);
Here are all the segments in TASK1 being allocated to slots in LDT_FIRST, through the NAME "FIRST_TASK" appearing as the argument to the "ENTRY" clause:
TABLE
LDT_FIRST
(LOCATION = LDT1_location,
ENTRY = (FIRST_TASK), -- FIRST_TASK is a module name from TASK01.CMD
LIMIT = 16), -- size of LDT == 0x80
LIMIT = 16); -- size of LDT == 0x80
It is not important to understand how the slots in the interrupt descriptor table (IDT) are filled just yet but just be aware that it contains indirect references to interrupt service routines:
TABLE
IDT ( -- IDT_in_ROM is a public symbol in the
LOCATION = IDT_location, -- "STARTUP" initialization module.
-- In the buffer starting at IDT_location
-- the builder places two bytes of the IDT
-- limit and four bytes of the IDT base
-- values in the format required for use
-- by LIDT instruction.
ENTRY =( 0:int0_gate,
1:int1_gate,
2:int2_gate,
3:int3_gate,
4:int4_gate,
5:int5_gate,
6:int6_gate,
7:int7_gate,
8:int8_gate,
9:int9_gate,
10:int10_gate,
11:int11_gate,
..
The addresses of the GDT, IDT and LDTs is placed into RAM at 0x200 to 0x9FF by the memory/range control:
MEMORY (
RANGE = (RAM_TABLES = RAM (0200h..09ffh)),
ALLOCATE = (RAM_TABLES = (GDT,IDT,FIRST_TASK_TSS, LDT_FIRST,
SECOND_TASK_TSS, LDT_SECOND)) );
We now have constructed the GDT, IDT and LDT tables and placed them at 0x200, in the order GDT, IDT (address 0x300), LDT_FIRST, LDT_SECOND. The TSS objects mentioned in the MEMORY control are not important at this point. Bear in mind that 0x200 is in RAM which will be full of junk after reset
3.2 Telling The 386 Where The Tables Are
One of the first actions of STARTUP.ASM is to put the address and limit (length-1) of the GDT into the GDTR register. It then does the same for the IDT:
Extract From STARTUP.ASM:
; Load GDTR for GDT in RAM
;
mov bx,OFFSET_FROM_03FF0000
opprefix
lgdt PWORD PTR CS:[bx + OFFSET GDT_location]
;
; load IDTR for IDT in RAM : NOTE: HITOPWIN REAL IDT One = 0x000
;
opprefix
lidt PWORD PTR CS:[bx + OFFSET IDT_location]
;
; Now IDT is at 0x300, HITOPWIN REAL IDT Two = 0x300
;
The next step is to set the Protection Enable bit (PE) in the CR0 control register:
; Switch to protected mode ; mov eax, CR0 ; get current CR0 add eax, 1 ; set PE bit mov CR0, eax ; begin protected mode ;
Finally, the 386 must perform a jump to the next address to clear the prefetch queue.
; Clear prefetch queue ; jmp short flush ; ;**************************************************************************** ;*<<<<<<<<<<<<<<<<<<<<<<< Protected Mode Now Active >>>>>>>>>>>>>>>>>>>>>>>>* ;**************************************************************************** ; flush: ;
We are now into protected mode and the 386EX now becomes a whole lot more useful
4. Some Complications!
Initialised Data
Unfortunately, in a true embedded 386EX system, the program boots up into EPROM, with just the /UCS active. The RAM is not enabled. Copies of the GDT etc. must therefore stored in ROM (at 0x3FFF000 in the example) and then copied to the final RAM address of 0x200 before the GDTR and IDTR registers can be loaded. Therefore, STARTUP.ASM firstly enables chip select 2 to activate the RAM on the Intel 386EX evaluation board used to develop the program
Extract From STARTUP.ASM:
;
; CS2 is at 0x000000, length 128k
;
mov ax,CS2ADL
mov dx,ax
mov ax,00303H
out dx,ax
;
mov ax,CS2ADH
mov dx,ax
mov ax,00000H
out dx,ax
;
mov ax,CS2MSKL
mov dx,ax
mov ax,0FC01H
out dx,ax
;
mov ax,CS4MSKH
mov dx,ax
mov ax,00001H
out dx,ax
then enable P2.2 to its alternate function of chip select /CS2
Extract From STARTUP.ASM:
;
; Set up Port 2.2 as /CS2 pin
;
mov ax,P2LTC
mov dx,ax
mov ax,000FBH
out dx,ax
;
mov ax,P2DIR
mov dx,ax
mov ax,000FBH
out dx,ax
;
mov ax,P2CFG
mov dx,ax
mov ax,00004H
out dx,ax
;
and finally copies the tables' images (created by GDT.BAT and ABS.BAT) from ROM at 0x3FFF000 to 0x200
;
; Copy GDT, IDT etc. from ROM area at 0x3FFF000 to 0x200 in RAM
; A20 and above not yet enabled so real mode copy will work as ROM
; appears to be at 0xFF000 even though it is really 0x3FFF000 physical
;
mov ax,RAM_TABLES_DATA_SEG
mov ds,ax
mov si,ROM_TABLES_OFFSET
mov di,0
mov cx,ROM_TABLES_LENGTH
;
copy:
mov ax,CS:[si]
mov DS:[di],ax
inc si
inc di
cmp di,cx
jne copy
;
; All tables are now in RAM
;
It then goes on to load the GDTR and IDTR, as shown previously:
; Load GDTR for GDT in RAM
;
mov bx,OFFSET_FROM_03FF0000
opprefix
lgdt PWORD PTR CS:[bx + OFFSET GDT_location]
;
; load IDTR for IDT in RAM : NOTE: HITOPWIN REAL IDT One = 0x000
;
opprefix
lidt PWORD PTR CS:[bx + OFFSET IDT_location]
;
; Now IDT is at 0x300, HITOPWIN REAL IDT Two = 0x300
;
4.2 Storing The GDT, IDT and LDTs In The EPROM
The .BLD file builds the tables and puts them at 0x200. In fact, even though this is a RAM area, the resulting OMF386 file, SAMPLE.ABS contains them at this address. Of course, when the program is blown into EPROM, this data will be lost. It is therefore necessary to make a copy of them in the EPROM by some means so that the copy routine in STARTUP.ASM can copy them into RAM again.
In the segments address allocation .
T1_DATA (base = 000008000h, DPL=0, USE32, LIMIT=07FFFH),
T2_DATA (base = 000010000h, DPL=0, USE32, LIMIT=07FFFH),
GLOBAL_DATA (base = 000018000h, DPL=0, USE32, LIMIT=03FFFH),
ROM_TABLES (base = 003FFF000h, DPL=0, USE16, LIMIT=07FFH),
RESET_CODE (base = 003FFFA00H, DPL=0, USE16),
S_CODE (BASE = 003FFC000H, DPL=0, USE32),
GLOBAL_CODE (BASE = 003FFC800H, DPL=0, USE32),
T1_CODE (BASE = 003FE4000H, DPL=0, USE32,LIMIT=07FFFH),
a space is reserved in the EPROM area by the segment ROM_TABLEs at 0x3FFF000 but this is left empty by LINK386.
A CAD-UL tool called ABS386.EXE is used to move the redundant table data from 0x200 up to reserved area at 0x3FFF000. The SAMPLE.GDT control file for ABS386.EXE achieves this:
-delete unmapped -eii -m 00000200H-000009FFH=03FFF000H ß This is where the tables are moved up to 0x3FFF000 -m 003FE0000H-003FFEFFFH=03FE0000H ß Do not move code and constants -m 003FFFA00H-003FFFFFFH=03FFFA00H ß Do not move reset code from STARTUP.ASM sample.abs sample.hex
The GDT.BAT batch file will do this automatically and in the example, it is called from the SP.BAT file If the resulting SAMPLE.HEX file is blown into EPROM or loaded into the T32/386's emulation memory, the program will run correctly.
Some EPROM programmers cannot handle the 32 bit addresses like 0x3FFF000 etc., so a second run of ABS386.EXE with a different control file SAMPLE.FIX, is performed via ABS.BAT .
-delete unmapped -ii -m 00000200H-000009FFH=01F000H -m 003FE0000H-003FFEFFFH=00000H -m 003FFFA00H-003FFFFFFH=01FA00H sample.abs sample.h86
produces SAMPLE.H86 which has only addresses suitable for EPROM blowing.
4.3 Initialising Data For Program Variables And Library Functions
The intialisation of start values of variables is somewhat different in CC386 to that found in compilers for other CPUs as usually, the programmer is entirely unaware of this process.
Here is an example of the type of intialised data in question:
unsigned short test0 = 11 ; /* The '11' will be put into INIT_DATA by INI386.EXE */
unsigned short test1 = 10 ; /* The '10' will be put into INIT_DATA by INI386.EXE */
void main (void) {
const char *temp ;
clear_task1_memory() ; /* Zero T1_DATA to keep printf() happy */
ini386_init() ; /* Initialise RAM data */
enable(); /* Enable Interrupts */
The INI386.EXE utility is used to rename the segment containing the start values (here "10" and "11") to "INIT_DATA". The CC.BAT compilation batch file automatically performs this step. In addition to start values for variables, the INIT_DATA segment also contains the intialised data required by some library functions such as printf() and scanf(). The BUILD stage puts the completed INIT_DATA segment into EPROM at an address specified by the user in the SAMPLE.BLD file.
T1_CODE (BASE = 003FE4000H, DPL=0, USE32,LIMIT=07FFFH),
T2_CODE (BASE = 003FEC000H, DPL=0, USE32,LIMIT=03FFFH),
INIT_DATA (BASE = 003FFD000H, DPL=0, USE32); -- Created by INI386.EXE
Due to the potential task isolation of CC386 programs, it is up to the user to call a CADUL-supplied function "ini386_init()" early in each task to copy the data from 0x3FFD000 in ROM into its correct place in RAM. In the example program, only TASK1 has intialised data and this is copied into RAM early in the main() function by ini386_init().
5. Producing A Symbol File For The HITOP386 Emulator Debugger
HITOP386 loads SAMPLE.SYM to give full source level debugging. The SP.BAT file does this
Extract From SP.BAT:
spomf386 sample.abs -pEX -v -w1 -Pnoexpnd>sp.out
Finally, the HIGDT.EXE utility extracts the GDT and IDT addresses for later use by HITOP386, in the form of a script file, SAMPLE.SCR:
RESET TARGET
WAIT
PROCESSOR GDT = {known...}
PROCESSOR GDTBASE = 0x03FFF000
PROCESSOR GDTLIMIT = 0xff
PROCESSOR realIDTR = {2}
PROCESSOR realBas1 = 0x00000000
PROCESSOR realBas2 = 0x00000300
PROCESSOR protIDTR = {1}
PROCESSOR protBas1 = 0x00000300
We now have all the elements to load the program into HITOP386 or to blow an EPROM.
5.1 Run Time Initialisation Steps Summary
1. Remap Chip select registers to make them visible
2. Enable chip selects. In particular, enable the RAM chip select.
3. Enable P2.2 as chip select /CS2
4. Copy tables from ROM at 0x3FFF000 to 0x200
5. Load GDTR register with GDT address of 0x200
6. Load IDTR register with IDT address of 0x300
7. Set PE bit
8. Jump to next instruction to flush queue
9. Set task register to TASK1
10. Set the DS register to the slot number of T1_DATA in LDT_FIRST
11. Set the ESP register to the slot number of T1_STACK in LDT_FIRST
12. Jump to main()
Building The Program
The MAKE.BAT file performs all the steps required to build the entire program and all files for debugging and EPROM blowing, in one process. You can perform the individual steps to build the program using the separate batch files provided.
Compile a file that has initialised data items:
CC MAIN.C
Compile a file that has no initialised data items:
CCNI TASK2.C
Link all the .OBJ files that make up TASK1 to TASK1.BND
TASK1.BAT
Link all the .OBJ files that make up TASK2 to TASK2.BND
TASK2.BAT
Link all the .OBJ files in the GLOBAL area to GLOBAL.BND
GLOBAL.BAT
Link all the .OBJ files that make up the startup code to STARTUP.BND
STARTUP.BAT
Link all the .BND files to an absolute .ABS file (OMF386)
BUILD.BAT
Create HEX file for EPROM blowing
ABS.BAT
Create Symbol File For HITOP386
SP.BAT calls GDT.BAT to create HEX file for HITOP386
These batch files are now examined in detail:
MAKE.BAT Assemble and compile all files. Link individual modules together to form tasks. Link tasks together and the locate tasks to final addresses.
Operation: Input File Output File(s) Assemble: .ASM .OBJ, .LST Compile: .C .OBJ, .LST Link: .OBJ .BND, MP1 Build: .BND .ABS, MP2
MAKE.BAT:
rem rem Compile files that will have data initialisation rem call cc USERINT call cc 386EXCON call cc MAIN call cc COMMS call cc INI386EX call cc ICU call cc SERIAL call cc T1INT call cc TIMER call cc WRITE call cc GPF_HAND rem rem Compile files that will have no data initialisation rem call ccni GLOBAL call ccni TASK2 pause rem rem Assemble files rem call ass startup call ass intrpt pause rem rem Link Modules To Make Tasks rem call task1 call task2 call startup call global pause rem rem Link tasks and locate rem call build pause rem rem Produce HEX file for EPROM blowing rem call abs rem rem Produce Symbol file for AX386/T32 Emulators rem Produce SAMPLE.SCR file for initialising GDT in HITOP rem Produce SAMPLE.HEX file for EPROM blowing or emulation rem call sp
Each of the batch files used are covered below:
5.2 Summary Of Important Batch Files In MAKE.BAT
These batch files are required to build the program:
5.2.1 ASS.BAT
Assemble a file with the appropriate switches
Example: ASS STARTUP
ASS.BAT:
AS386 -L -SYMINFO -VCPU=80386 -VDB -CEXTENDCHAR %1.asm
5.2.2 CC.BAT
Compile a file with appropriate switches. Produce a table (INIT_DATA) that will be used to make sure program variables are set to their correct initial values. This is a two-stage process, requiring CC386.EXE and INI386.EXE. The latter takes the .OBJ file produced by the compiler and makes a copy of the intialised data in a new EPROM segment called "INIT_DATA". This procedure is covered in section 4. above.
Example: CC MAIN
CC.BAT
Compile a C source file to an object file with the necessary controls:
cc386 -L -V -VNEARFAR -VSUBSYS=libc.sub -VCOMPACT -VDB -VBUILTIN -VANSI -VROM -VNOALIGN %1.c > %1.err if errorlevel 1 goto :ABORT ini386 -V -I DATA=0 %1.obj goto:OK :ABORT type %1.err pause edit %1.lst :OK
5.2.2.1 Fundamental CC386 Compiler Controls For Embedded Use
NEARFAR:
To write multi-segmented programs, the far attribute is required. far is used to show that the data item or function is in a segment other than the default one for the TASK you are in. near would be used for objects which are in the default segment for the TASK.
In TASK1, the default data segment is T1_DATA and the default code segment is T1_CODE. Objects in the GLOBAL_DATA segment are declared as far to make them available to all TASKs.
SUBSYS=libc.sub:
As CC386 can work with several operating systems, it can access library functions from several sources or "sub-systems". In the example barefoot embedded programs, the sub-system is the run time library set, LIBC.SUB. These can be found the CC386\LIB directory and I386CC01 is the one normally referenced at link and build time.
COMPACT:
To make the compiler able to generate code suitable for multi-segmented programs, the COMPACT memory is used. This is identical to LARGE but should be used in preference to this. The NEARFAR control only becomes effective when this memory model is selected.
DB:
This puts the symbol information necessary for source level debuggers such as HITOP386 into the object files.
ROM:
This puts constants into the CODE32 segment so that they eventually end up in ROM. Thus
char const message[] = "HELLO" ;
will put the string H,E,L,L,0,/0 into ROM.
BUILTIN:
There are some occasions when specific 386 assembler instructions need to be accessed from C. In the example program, suspending TASK2 is made through the "IRET" instruction. This can be forced in-line through the "waitforinterrupt()" built-in (or "instrinsic") function.
5.2.3 CCNI.BAT
Compile a file with appropriate switches. Do not produce the initialised data table.
Example: CCNI TASK2
CCNI.BAT:
cc386 -L -V -VNEARFAR -VSUBSYS=libc.sub -VCOMPACT -VDB -VBUILTIN -VANSI -VROM -VNOALIGN %1.c > %1.err if errorlevel 1 goto :ABORT goto:OK :ABORT type %1.err pause edit %1.lst :OK
5.2.4 GLOBAL.BAT
Link GLOBAL.OBJ to form GLOBAL.BND
GLOBAL.BAT:
LINK386 global.obj lcd.obj -CF global.cmd
GLOBAL.CMD:
-DB -LI -NA Global_Objects -OJ global.bnd -PR global.mp1 -V -RN CODE32=GLOBAL_CODE -RN DATA=GLOBAL_DATA -RN STACK=GLOBAL_STACK
STARTUP.BAT: Link STARTUP.OBJ and INTRPT.OBJ together to form STARTUP.BND
5.2.4.1 Important Linker Controls
These controls appear in all the other link stage control files as well.
DB: Pass symbolic debug information through to .BND file.
LI: Pass line number debug information through to .BND file.
OJ: The name of the output .BND file.
PR: The name of the map file associated with .BND file.
V: Report all warnings and errors.
RN: Rename segments from .OBJ files when putting them into .BND file
5.2.5 STARTUP.BAT
Link together the assembler files that initialised the 386EX:
LINK386 startup.obj intrpt.obj -CF startup.cmd
STARTUP.CMD:
-DB -LI -NA startup -OJ startup.bnd -PR startup.mp1 -V -RN CODE32=S_CODE
5.2.6 TASK1.BAT
Link the modules in TASK1 -
386EXCON.OBJ, INI386EX.OBJ, MAIN.OBJ, SERIAL.OBJ, TIMER.OBJ, T1INT.OBJ, WRITE.OBJ, ICU.OBJ, USERINT.OBJ, T1CLRMEM.OBJ, GPF_HAND.OBJ, I386CC00.LIB - together to from TASK01.BND
TASK1.BAT:
LINK386 main.obj ini386.obj gpf_hand.obj userint.obj comms.obj write.obj 386excon.obj serial.obj ini386ex.obj icu.obj t1int.obj timer.obj \CC386\LIB\i386cc00.lib -CF task01.cmd
TASK01.CMD:
-DB -LI -NA First_Task -OJ task01.bnd -PR task01.mp1 -V -RN CODE32=T1_CODE -RN DATA=T1_DATA -RN STACK=T1_STACK
5.2.7 TASK2.BAT
Link TASK2.OBJ to form TASK02.BND
TASK2.BAT:
LINK386 task2.obj -CF task02.cmd
TASK02.CMD:
-DB -LI -NA Second_Task -OJ task02.bnd -PR task02.mp1 -V -RN CODE32=T2_CODE -RN DATA=T2_DATA -RN STACK=T2_STACK
5.2.8 BUILD.BAT
Link together the BND files and locate to absolute addresses
to form SAMPLE.ABS:
STARTUP.BND, TASK01.BND, TASK02.BND, GLOBAL.BND
Then locate according to controls in SAMPLE.BLD to SAMPLE.ABS
& SAMPLE.MP2
BUILD.BAT:
LINK386 -BUILD -CONTROLFILE sample.cmd
SAMPLE.CMD:
startup.bnd task01.bnd task02.bnd global.bnd \CC386\LIB\i386cc00.lib -BUILDFILE sample.bld -NAME sample -OJ sample.abs -PRINT sample.mp2 -DEBUG -LONGMAP -SYMAP ALL -XREF -INITDATA 0FFH
Extract From SAMPLE.BLD:
sample;
-- build program id
SEGMENT
stack_seg (base = 000002000h, DPL=0, USE32, LIMIT=00FFFH),
T1_STACK (base = 000003000h, DPL=0, USE32, LIMIT=00FFFH),
T2_STACK (base = 000004000h, DPL=0, USE32, LIMIT=00FFFH),
GLOBAL_STACK(base = 000005000h, DPL=0, USE32, LIMIT=00FFFH),
T1_DATA (base = 000008000h, DPL=0, USE32, LIMIT=07FFFH),
T2_DATA (base = 000010000h, DPL=0, USE32, LIMIT=07FFFH),
GLOBAL_DATA (base = 000018000h, DPL=0, USE32, LIMIT=03FFFH),
ROM_TABLES (base = 003FFF000h, DPL=0, USE16, LIMIT=07FFH),
RESET_CODE (base = 003FFFA00H, DPL=0, USE16),
S_CODE (BASE = 003FFC000H, DPL=0, USE32),
GLOBAL_CODE (BASE = 003FFC800H, DPL=0, USE32),
T1_CODE (BASE = 003FE4000H, DPL=0, USE32,LIMIT=07FFFH),
T2_CODE (BASE = 003FEC000H, DPL=0, USE32,LIMIT=03FFFH),
INIT_DATA (BASE = 003FFD000H, DPL=0, USE32); -- Created by INI386.EXE
-- NOTE: The address of RESET_CODE segment must be same as:
-- RESET_CODE_ADDRESS EQU 03FFFA00H ; This code segment is at 0x3FFFC00
-- in STARTUP.ASM
-- NOTE: The address of ROM_TABLES segment must be same as:
-- ROM_TABLES_ADDRESS EQU 03FFF000H ; Image of tables in EPROM
-- in STARTUP.ASM
TABLE
LDT_FIRST
(LOCATION = LDT1_location,
ENTRY = (FIRST_TASK), -- FIRST_TASK is a module name from TASK01.CMD
LIMIT = 16), -- size of LDT == 0x80
LDT_SECOND
(LOCATION = LDT2_location,
ENTRY = (SECOND_TASK),
LIMIT = 16); -- size of LDT == 0x80
TASK
-- Task is for ICE(TM)-386
FIRST_TASK_TSS (
LIMIT= 7fh, -- size of TSS 1 == 0x80
CODE = main,
DATA = T1_DATA,
STACKS = (T1_STACK),
LDT = LDT_FIRST,
INITIAL),
SECOND_TASK_TSS (
LIMIT= 7fh, -- size of TSS 2 == 0x80
CODE = task2_main,
DATA = T2_DATA,
STACKS = (T2_STACK),
LDT = LDT_SECOND);
GATE
FirstTaskGate (TASK, DPL = 0, ENTRY = FIRST_TASK_TSS),
SecondTaskGate (TASK, DPL = 0, ENTRY = SECOND_TASK_TSS),
int0_gate (INTERRUPT, DPL = 0, ENTRY = int0),
int1_gate (INTERRUPT, DPL = 0, ENTRY = int1),
.
.
int11_gate (INTERRUPT, DPL = 0, ENTRY = int11),
int12_gate (INTERRUPT, DPL = 0, ENTRY = int12),
int13_gate (INTERRUPT, DPL = 0, ENTRY = general_protection_fault),
int14_gate (INTERRUPT, DPL = 0, ENTRY = int14),
.
.
int48_gate (INTERRUPT, DPL = 0, ENTRY = int48),
int49_gate (INTERRUPT, DPL = 0, ENTRY = int49),
int63_gate (INTERRUPT, DPL = 0, ENTRY = int63);
TABLE
IDT ( -- IDT_in_ROM is a public symbol in the
LOCATION = IDT_location, -- "STARTUP" initialization module.
-- In the buffer starting at IDT_location
-- the builder places two bytes of the IDT
-- limit and four bytes of the IDT base
-- values in the format required for use
-- by LIDT instruction.
ENTRY =( 0:int0_gate,
1:int1_gate,
.
.
62:int62_gate,
63:int63_gate),
LIMIT = 64) ; -- end IDT
TABLE
GDT ( -- GDT_in_ROM is a public symbol in
-- the "STARTUP.ASM" initialisation module.
LOCATION = GDT_location, -- In the buffer starting at GDT_location
-- BLD386 places the GDT base and
-- GDT limit values. Buffer must be
-- 6 bytes long. The base and limit
-- values are places in this buffer
-- as two bytes of limit plus
-- four bytes of base in the format
-- required for use by LGDT instruction.
DPL = 0,
ENTRY =( -- 0: reserved
-- 1:GDT_ALIAS, -- Filled in automatically by LINK386
-- 2:IDT_ALIAS, -- Filled in automatically by LINK386
3:FIRST_TASK_TSS,
4:LDT_FIRST,
5:SECOND_TASK_TSS,
6:LDT_SECOND,
7:ROM_TABLES,
8:RAM_TABLES,
9:stack_seg,
10:RESET_CODE,
11:GLOBAL_CODE,
12:GLOBAL_DATA,
13:GLOBAL_STACK,
14:FirstTaskGate,
15:SecondTaskGate),
LIMIT = 32);
MEMORY (
RANGE = (RAM_TABLES = RAM (0200h..09ffh)),
ALLOCATE = (RAM_TABLES = (GDT,IDT,FIRST_TASK_TSS, LDT_FIRST,
SECOND_TASK_TSS, LDT_SECOND))
);
-- NOTE: RAM_TABLES in the above must be same as:
-- RAM_TABLES_ADDRESS EQU 00000200H ; Address of tables when in RAM
-- from STARTUP.ASM
END
5.2.9 ABS.BAT
Produce a HEX image of program for blowing a 128KB EPROM
Input File Output File
SAMPLE.ABS SAMPLE.H86 Hexfile
SAMPLE.ROM Map file of EPROM
ABS.BAT:
abs386 -CONTROLFILE SAMPLE.FIX > SAMPLE.ROM
SAMPLE.FIX:
-delete unmapped -ii -m 00000200H-000009FFH=01F000H -m 003FE0000H-003FFEFFFH=00000H -m 003FFFA00H-003FFFFFFH=01FA00H sample.abs sample.h86
5.2.10 GDT.BAT
Produce an extended 32-bit .HEX image of program for loading into HITOP386 debugger
Input File Output File SAMPLE.ABS SAMPLE.HEX Hexfile SAMPLE.LOD Map file
GDT.BAT:
abs386 -CONTROLFILE SAMPLE.gdt > SAMPLE.lod
SAMPLE.GDT:
-delete unmapped -eii -m 00000200H-000009FFH=03FFF000H -m 003FE0000H-003FFEFFFH=03FE0000H -m 003FFFA00H-003FFFFFFH=03FFFA00H sample.abs sample.hex
HIGDT.EXE: Extract GDT information from .MP2 file for HITOPWIN
Input File Output File SAMPLE.MP2 SAMPLE.SCR
6. Example Program Memory Map
The program will run on any standard AX386 or T32/386 emulator
with a minimum 256KB emulation memory.
RAM: 0x00000000 - 0x0001FFFF EPROM: 0x03FE0000 - 0x03FFFFFF
Instructions For Running Program
To run the program, start HITOP386 then enter:
Comment: Setup emulation memory to hold program Setup-Map Insert ID: RAM Start: 0x0 Length: 128k HIGHSPEED <return>
Comment: Setup emulation memory to hold data Insert ID: ROM Start: 0x3fe0000 Length: 128k HIGHSPEED <return> Comment: Configure emulator for protected mode operation - required only in first debugging session SAMPLE.SCR <return> Comment: Load program HEX and symbol files - always required File-Execute LOAD.SCR <return> Comment: Reset 386EX and go to main() Target-Reset GO UNTIL main <return>
You should now be able to single step the program and use HiTOP386 in the usual way.
7. Features Of The Example Program In More Detail
7.1 Multi-Tasking
The program contains two tasks which have their own LDTs (local descriptor tables) and thus task isolation. The LDTs are just further slots in the GDT. The program performs a task switch from TASK1 to TASK2 via a "CALL GATE" at the end of the TimerISR() 1ms interrupt function. Within TASK2's single function, there are in-line IRET instructions which cause the program to revert back to TASK1. The function "task2_main()" is split into stages, separated by IRET instructions (#define Suspend_Task) so that each time the CALL GATE in the TimerISR calls it, it executes the next stage.
For more information on GATEs, see the next section.
The crucial point about this is that execution of TASK2 resumes at the next instruction after the last IRET. This is true multi-tasking and only the 386 architecture can do this in hardware, without an operating system. What's more, it's free!
There is no need to use this feature but for producing simple timeslice-type schedulers, it is very useful.
To switch TASKs, the 386 must store the state of the current TASK in another table. This would include the working registers, EAX, EBX, ESP, CS:IP etc.. The areas set aside to hold this snapshot of the task state is known as the Task State Segment (TSS). In the program, the TSS for TASK1 is called FIRST_TASK_TSS and the TSS for TASK2 is called SECOND_TASK_TSS and these are allocated slots in the GDT.
In STARTUP.ASM, the 386's task register (TR) is loaded with the slot number of the FIRST_TASK_TSS. It then puts the GDT slot number that contains the LDT_FIRST for TASK1 into the LDT register .
; Set Up LDT1 & 2 For Multitasking
;
mov ax, TSS1_DESC ; Task1 is entered first
ltr ax ; So put number in task register TR that corresponds
; to TSS1
;
.
.
.
mov ax,LDT1_DESC ; Load LDTR for task1
lldt ax
;
;
Finally, the Data Segment Register, DS, is loaded with the slot number of TASK1's data area (T1_DATA) in LDT_FIRST and the Stack Segment Register is loaded with the slot number of TASK1's stack area (T1_STACK)
; ;**************************************************************************** ;*< Set Up Selectors For First Task To Make TASK1_DATA & TASK1_CODE Available >>* ;**************************************************************************** ; mov ax,LDT_DATA_DESC ; Load data segment selectors for task1 mov ds,ax mov es,ax ; mov ax,LDT_STACK_DESC ; Load stack segment mov ss,ax mov esp,0
The next step is to jump to main().
7.1 Coping With Interrupts
The Interrupt Descriptor Table (IDT) is a list of GATEs which will be passed through each time an interrupt occurs. For example, when the serial port 1 interrupt occurs, the 386 refers to slot 35 in the IDT to see which GATE is to be used to get to the service routine
Extract From SAMPLE.BLD:
TABLE
IDT ( -- IDT_in_ROM is a public symbol in the
LOCATION = IDT_location, -- "STARTUP" initialization module.
-- In the buffer starting at IDT_location
-- the builder places two bytes of the IDT
-- limit and four bytes of the IDT base
-- values in the format required for use
-- by LIDT instruction.
ENTRY =( 0:int0_gate,
1:int1_gate,
2:int2_gate,
3:int3_gate,
.
.
33:int33_gate,
34:int34_gate,
35:int35_gate, ß This is a gate leading to serial port interrupt service routine.
36:int36_gate,
Where the GATE leads to is specified by the user in the GATE statement in SAMPLE.BLD
int33_gate (INTERRUPT, DPL = 0, ENTRY = int33),
int34_gate (INTERRUPT, DPL = 0, ENTRY = int34),
int35_gate (INTERRUPT, DPL = 0, ENTRY = SIO1_RX_TX_interrupt), ß Go to SIO1_RX_TX_interrupt ()
int36_gate (INTERRUPT, DPL = 0, ENTRY = int36),
int37_gate (INTERRUPT, DPL = 0, ENTRY = int37),
int38_gate (INTERRUPT, DPL = 0, ENTRY = int38),
The INTERRUPT keyword shows that this GATE is entered from an interrupt source like the serial port or timer unit, for example. The SIO1_RX_TX_interrupt() service routine is terminated by an IRET, as normal.
Task switches are also made through GATEs and those required in the example program are declared as per:
Extract From SAMPLE.BLD:
GATE
FirstTaskGate (TASK, DPL = 0, ENTRY = FIRST_TASK_TSS),
SecondTaskGate (TASK, DPL = 0, ENTRY = SECOND_TASK_TSS), ß Get to second task through gate
int0_gate (INTERRUPT, DPL = 0, ENTRY = int0),
int1_gate (INTERRUPT, DPL = 0, ENTRY = int1),
int2_gate (INTERRUPT, DPL = 0, ENTRY = int2),
In the C program, the SecondTaskGate GATE is made to look like an external function call and is invoked in the usual C manner:
Extract From T1INT.C:
extern void far SecondTaskGate(void) ;
/*** One Millisecond Interrupt Function ***/
void TimerISR(void) {
SecondTaskGate() ; /* Reactivate Task2 Every 1ms */
NonSpecificEOI();
}
In task2_main(), each stage is executed and then control reverts back to the TimerISR calling calling.
/* Task 2 Main Loop */
while(1) {
task2_stage0++ ;
SuspendTask() ; /* End of phase 2 of task 2 */
task2_stage1++ ;
SuspendTask() ; /* End of phase 3 of task 2 */
.
.
task2_stage8++ ;
SuspendTask() ; /* End of phase 10 of task 2 */
task2_stage9++ ;
current_task2_stage9 = task2_stage9 ;
global_flags |= New_Data_Ready_Fl ; /* Pass data to task1 for printing */
SuspendTask() ; /* End of phase 11 of task 2 */
}
} /* Exit Task Now Completed - next task gate will restart from top... */
7.2 Memory-Mapped IO Devices
It is quite common to map additional IO devices such UARTs, real time clocks, LCD panels etc. into the memory space of the 386EX. At first, it might appear difficult to accommodate small regions into the overall protected mode memory map. However, it is quite straightforward, as demonstrated in the following example.
There is an LCD panel with two registers mapped at 0x20000 in the memory space. The decoding hardware makes the registers "LCD_control" appear at 0x20000 and "LCD_data" at 0x20001. A new file is created called LCD.C which just declares two variables, in the order in which they appear in memory. Thus the line
LCD_control = 0xAA ;
will write 0xAA to address 0x20000.
The compiler control SEGMENT will rename the DATA segment produced by default to a distinctive name such as "LCD_REGS". As these items could be accessed from any task, they are declared as "far".
Extract From LCD.C
#pragma SEGMENT DATA LCD_REGS /* Make a new segment called LCD_REGS */ /* Variables that will line-up with the real registers in the LCD panel */ unsigned char far LCD_control ; unsigned char far LCD_data ;
The .OBJ file "LCD.OBJ", is linked with the global data from GLOBAL.OBJ to form GLOBAL.BND. The SAMPLE.BLD file now contains a SEGMENT control for this new segment:
Extract From SAMPLE.BLD:
SEGMENT
stack_seg (base = 000002000h, DPL=0, USE32, LIMIT=00FFFH),
T1_STACK (base = 000003000h, DPL=0, USE32, LIMIT=00FFFH),
T2_STACK (base = 000004000h, DPL=0, USE32, LIMIT=00FFFH),
GLOBAL_STACK(base = 000005000h, DPL=0, USE32, LIMIT=00FFFH),
T1_DATA (base = 000008000h, DPL=0, USE32, LIMIT=07FFFH),
T2_DATA (base = 000010000h, DPL=0, USE32, LIMIT=07FFFH),
GLOBAL_DATA (base = 000018000h, DPL=0, USE32, LIMIT=03FFFH),
LCD_REGS (base = 000020000h, DPL=0, USE32, LIMIT=000FFH), ß Fix new segment 0x20000-0x200FF
ROM_TABLES (base = 003FFF000h, DPL=0, USE16, LIMIT=07FFH),
RESET_CODE (base = 003FFFA00H, DPL=0, USE16),
S_CODE (BASE = 003FFC000H, DPL=0, USE32),
GLOBAL_CODE (BASE = 003FFC800H, DPL=0, USE32),
T1_CODE (BASE = 003FE4000H, DPL=0, USE32,LIMIT=07FFFH),
T2_CODE (BASE = 003FEC000H, DPL=0, USE32,LIMIT=03FFFH),
INIT_DATA (BASE = 003FFD000H, DPL=0, USE32); -- Created by INI386.EXE
The new segment is placed in the GDT at slot 16:
ENTRY =( -- 0: reserved
-- 1:GDT_ALIAS, -- Filled in automatically by LINK386
-- 2:IDT_ALIAS, -- Filled in automatically by LINK386
3:FIRST_TASK_TSS,
4:LDT_FIRST,
5:SECOND_TASK_TSS,
6:LDT_SECOND,
7:ROM_TABLES,
8:RAM_TABLES,
9:stack_seg,
10:RESET_CODE,
11:GLOBAL_CODE,
12:GLOBAL_DATA,
13:GLOBAL_STACK,
14:FirstTaskGate,
15:SecondTaskGate,
16:LCD_REGS), ß New segment is slot 16
LIMIT = 32);
As a result, any access to LCD_control will cause address 0x20000 to be addressed. Should the program fail and an address 0x20100 be addressed, a GENERAL PROTECTION FAULT will occur as this is outside the segment.
Note: It would be possible restrict access to the LCD by putting the LCD_REGS segment into LDT_FIRST so that it is only visible from TASK1.
7.3 Getting The Code Into HiTOP And EPROMs
In a traditional embedded system using perhaps an 8051 or a C166, the absolute object file usually contains only a binary image of the executable code. In these cases, the OMF51 and OMF166 (object module format) are used. When the EPROM is blown, all of the binary contents of the OMF file are used, via a HEX file.
OMF386 is somewhat different in that it contains not only the binary code belonging to the program but also an image of the data that the program will use. Section 4.3 contains some basic information on this. This data is at a low address in the RAM area. During the symbol processing stage (SPOMF386.EXE), it is passed through to the .HTX file at its original address. Thus if the .HTX file is loaded into the emulator, it will try to put the data into the RAM area. This is not usually possible as the chip select which enables the RAM on the target will not be activated. Consequently, a "memory write error" will be reported. One fix is to map some emulation memory over the RAM so that the data will have somewhere to go. Unfortunately, the presence of the data before the compiler's initialisation has run can mask problems with library functions and other code which assumes certain data is in place before they are first called.
The best solution is provided by the GDT.BAT batch file which uses the ABS386.EXE OMF386 to HEX conversion program to carry the code through into the HEX file at the correct address around 0x3fe000 but reject the data in the RAM area. The resulting .HEX file is then loaded into HITOP, followed by the .SYM file produced by SPOMF386.EXE in the normal way. The LOAD.SCR script file combines this into a single operation.
The advantage of this approach is that it exactly duplicates how the final 386EX system will run when in the field. It the program runs correctly in the emulator, it should also run standalone.
7.4 Further Information
We will add further in-depth examination of other protected mode concepts in future issues.
© Hitex (UK) Ltd. 1997