[Flat Real Mode]                                      [Assembler][/][80386]

This page contains information concerning Flat Real Mode. The information
comes from various sources.

   * What is flat real mode
   * How does it works
   * How to use it
   * EMM/QEMM compatibilty
   * Is it usefull?

What is Flat Real Mode

As many of you probably know, the 386+ has a few different processor modes,
most important are:

   * Real Mode
   * Protected Mode
   * V86 Mode

The advantage of Protected Mode [PM] above Real Mode [RM] is that in PM you
can setup flat memory. Flat memory will provide you with a 32 bit full
addressing range along with 32-bit protected mode. This means that there is
little need for segmentation. Another advantage is that you can protect
code and data. Intel recommends using 32-bit flat platform when programming
for an Intel processor.
The problem with protected mode is that you can not easily access some of
the routines in the PC BIOS and DOS, some of your old DOS routines may also
not work correctly.
There is however a way around this problem. You can setup a variant of real
mode, namely Flat Real Mode [FRM], and use the BIOS and DOS as you normally
would have done.

How does it works

The problem in real mode are the 64 kB segment limits. These limits reside
in the segment descriptors. If we are in real mode we can change to pmode,
access the descriptor table and change the segment limit to 4 GB, and then
switch back to real mode.
To achive this we need to setup the follwing data tables:

Code16GDT = 8
Data16GDT = 16
Data32GDT = 24

mem32_GDT       dw      4 dup(0)

        dw      0ffffh,0ffh,9a00h,0
        dw      0ffffh,0ffh,9200h,0
        dw      0ffffh,0ffh,9200h,8fh

GDTptr label fword
        dw      offset GDTptr-1-offset Mem32_GDT
        dd      offset Mem32_GDT ; Absolute adress GDTtable
        dw      0

To change the segement descriptor we must change to protected mode first.
This can only be done if the computer is running in real mode. Switching to
protected mode is rather simple for this easy task we want to perform. We
do not have to set interrupt tables etc. We must, however, switch
interrupts of so no interrupt occurs while initialising protected mode as
this would lock the computer. Then we set the segment limits and return to
real mode.
This can be done with the following snippet:

Set4Gig:

        mov     eax,cr0         ; check for V86 mode
        ror     eax,1
        jc      leave4gb        ; exit routine if V86 mode is set.

        mov     ax,cs           ; set up GDT for this code segment
        mov     ds,ax
        movzx   eax,ax
        shl     eax,4
        add     dword ptr ds:GDTptr+2,eax
        lgdt    fword ptr ds:GDTptr

        mov     ax,cs
        and     eax,65535
        shl     eax,4
        mov     word ptr ds:Mem32_GDT[Code16GDT+2],ax
        mov     word ptr ds:Mem32_GDT[Data16GDT+2],ax
        ror     eax,16
        mov     byte ptr ds:Mem32_GDT[Code16GDT+4],al
        mov     byte ptr ds:Mem32_GDT[Data16GDT+4],al
        mov     byte ptr ds:Mem32_GDT[Code16GDT+7],ah
        mov     byte ptr ds:Mem32_GDT[Data16GDT+7],ah

        cli                     ; no interrupts

        mov     eax,cr0         ; set protected mode
        or      al,1
        mov     cr0,eax

        db      0eah            ; far jump to pmode label
        dw      offset pmode
        dw      Code16GDT

pmode:  mov     ax,Data32GDT    ; now we are in protected mode
        mov     ds,ax           ; set all selector limits to 4 GB
        mov     es,ax
        mov     fs,ax
        mov     gs,ax

        mov     eax,cr0         ; restore real mode
        and     al,0feh
        mov     cr0,eax

        db      0eah            ; far jump to rmode label
        dw      offset rmode
        dw      Code

rmode:  clc                     ; now we are back in real mode, zero carry
        sti                     ; to indicate ok and enable interrupts

leave4gb:
        ret

The real theory behind the working of flat real mode is rather complex and
not needed to use the routine. Interrested people should read a book
dedicated to protected mode for more information. There are some good, free
ones, on the net.

How to use it

After initialising you have access to the full 4 GB addressing range. Most
of that area is probably not present in your computer. Other parts can
allready be occupied by other programs. It is wital that none of that
memory is change or the computer will lock up. To know which memory that is
free it is possibly to use HIMEM.SYS. The following snippet allow you to
initialize HIMEM.SYS and allocate/deallocate XMS memory, and some other
usefull functions:

;
; initialises HIMEM.SYS for FRM program. if HIMEM.SYS isn't present a carry
; will be returned.
;
; this routine must be called before any memory access outside the first
; meg is performed. (normally a call to the routines would follow immediately
; after a call to set4gig)
;
; note that the routines may not be called in protected mode since they
; write to the code segment.
;
; input:
;   none
;
; output:
;   cf = set if operation failed, else cleared
;
; destroys:
;   ax, bx
;   flags
;

himem_init:
        mov     ax,4300h
        int     2Fh
        cmp     al,80h
        je      himemfound
        stc
        ret

himemfound:
        push    es
        mov     ax,4310h
        int     2Fh
        mov     [word ptr cs:himem_entry],bx
        mov     [word ptr cs:himem_entry+2],es
        pop     es
        clc
        ret

himem_entry     dd      ?

;
; allocate XMS memory
;
; input:
;   dx = size in kB
;
; output:
;   cf = set if alloc failed (probably out of memory)
;   dx = handle
;
; destroys:
;   ax
;   flags
;

alloc_xms:
        mov     ah,9
        call    [cs:himem_entry]
        cmp     dx,ax
        jnz     allocok
        or      ax,ax
        jnz     allocok
        stc
        ret
allocok:
        clc
        ret

;
; deallocate xms memory
;
; no error checking is performed.
;
; input:
;   dx = handle
;
; output:
;   none
;
; destroys:
;  ax
;  flags
;

dalloc_xms:
        push    dx
        mov     ah,0dh
        call    [cs:himem_entry]
        pop     dx
        mov     ah,0ah          ; deallocate
        call    [cs:himem_entry]
        ret

;
; returns absolute 32-bit adress of an xms memoryblock
;
; no error checking performed
;
; input:
;   dx = handle
;
; output:
;   edx = 32Bit start adress of memoryblock
;
; destroys:
;   ax, bx
;   flags
;

getlinearaddress:
        mov     ah,0ch
        call    [cs:himem_entry]
        shl     edx,16
        mov     dx,bx
        ret

;
; returns the largest available xms memoryblock
;
; no error checking performed
;
; input:
;   none
;
; output:
;   dx = size of largets available memoryblock (in kB)
;
; destroys:
;   ax
;   flags
;

getmax_xms:
        mov     ah, 8           ; available memory
        call    [cs:Himem_Entry]
        ret

To access the memory you must set a segment register to 0 and read or write
to the absolute 32-bit address to the memory block. Another important issue
it to make sure that the A20 addressline is enabled (snippet follows). If
the A20 address line isn't enabled the address will be clipped at 1 MB.
Some HIMEM function also switch the A20 address line off, so for these
reasons it is safest to enable it everytime you have used any of the HIMEM
functions. Here is the enable function:

;
; enables the A20 adress line.
;
; this is needed to adress the memory above 1Mb. do this at the start of
; your program and everytime another program has been active. in an
; interrupt routine this means you should do it everytime the
; routine is being called, don't worry, it doesn't takes much time.
;
; input:
;   none
;
; output:
;   none
;
; destroys:
;   ax
;   flags
;

enablea20:
        mov     al,0d1h
        out     64h,al
        call    a20wait
        mov     al,0dfh
        out     60h,al
        call    a20wait
        mov     al,0ffh
        out     64h,al
        call    a20wait
        ret

a20wait:
        in      al,64h
        jmp     $+2
        and     al,2
        jnz     a20wait
        ret

EMM/QEMM compatibilty

This is the big glitch with flat real mode. It does not work if the
computer is allready in protected mode. Mostly protected mode is intialized
by EMM386 or similar. If the computer is in protected mode what shall one
do? In some cases the following solution might work:

Windows runs in protected mode, and it runs fine even if EMM386 is allready
running. Andrew Schulman writes in his book called "Unauthorized Windows
95" that there exist a function to tell EMM386 to shut down and return the
computer to real mode. What we need to do is call that function, in the
same manner as Windows does, and hopefully EMM386 will shut down.
Windows generates four messages when calling INT 2Fh:

   * Start up
   * Start up complete
   * Begin exit
   * Exit

Any driver that traps INT 2Fh will receive these messages. (i.e. If driver
requires to load VxD). The first message is what we need. Let's look at it
closer:

Parameters:

   ax    = 1605h
   cx    = 0
   dx    = flags (bit0=0 - enhanced mode, bit0=1 - standard mode)
   di    = windows version (30ah = 3.1)
   es:bx = 0:0 or pointer to previous struct win386_startup_info_struct
   ds:si = 0:0 or pointer to v86 mode toggle function

returns:
   cx     = 0 if windows can be started, !=0 if not
   es:bx  = pointer to new struct win386_startup_info_struct
   ds:si  = 0:0 or V86 mode toggle function

And here follows two structures that are referenced to in the text

   struct Instance_Item_Struct {
      void far *IIS_Ptr;                        // seg:ofs to Instance data
      WORD ISS_Size;                            // number of bytes
   }

   struct win386_startup_info_struct {
      WORD SIS_Version;
      Win386_Startup_Info_Struct far *SIS_Next_Dev_Ptr;
      DWORD SIS_Virt_Dev_File_Ptr               // name of the VxD
      DWORD SIS_Reference_Date                  // data for VxD
      Instance_Item_Struct far *Instance_Data_Ptr;
   }

To be able to run flat real mode under EMM386 we need to:

   * set-up parameters and call INT 2Fh (AX = 1605h)
   * save DS:SI
   * call INT 2Fh (AX = 1608 - Start up complete)
   * call previously saved DS:SI with AX = 0 (switch to Real mode)
   * do anything,
   * call DS:SI with AX = 1 (switch to V86 mode)
   * call INT 2Fh (AX = 1609h - Begin exit)
   * call INT 2Fh (AX = 1606h - Exit)
   * that's all

Note: The following routine was origianlly written in the Gema assembler. I
have converted it from the original source and since Gema had a strange
format it might be bugs in the conversion.
As noted above, the following snippet is needed when we want to shut down
EMM386, ie exit protected mode. So before this function is called we must
check that we are in protected mode, or even better check if EMM386 is
running. If we are not in protected mode, just call the flat real mode init
routine. Back to EMM386 shutdown:

        push    ds              ; get data segment
        pop     es              ; since it will be overwritten

        mov     ax,01605h
        xor     dx,dx
        xor     cx,cx
        xor     dx,dx
        xor     si,si
        mov     ds,si
        mov     es,si
        mov     di,030ah        ; windows version 3.10
        int     2fh

        test    cx,cx           ; if cx=0 we can proceed
        jnz     some_error_handler

; address of V86 toggle function is in ds:si. we store it for later use.
        mov     [es:v86switch],si
        mov     [es:v86switch+2],ds

        mov     ax,01608h       ; send startup complete
        int     2fh

        xor     ax,ax           ; curcial part: we exit protected mode
        call    v86switch       ; by calling function 0

        smsw    ax              ; check if protected mode is active
        test    ax,1            ; and if so, jump to an error handler
        jnz     some_error_handler

        call    set4gb          ; initialize flat real mode
        jc      some_error_handler

; here we have initialised flat real mode under emm386. we can do many
; things here, but when you return do _not_ forget to restore segment
; registers because EMM386 might not like to have 4 GB segments.
;
; restoring segment limits can be done by modifying the variables used to
; initialise flat real mode and then call flat real mode init again.

        mov     ax,1            ; switch to V86 mode with EMM function
        call    v86switch

        mov     ax,01609h       ; begin exit
        int     2fh
        mov     ax,01606h
        int     2fh

        mov     ax,4c00h        ; here is it safe to exit to dos
        int     21h

This code was tested with both EMM386.EXE and QEMM 8.0 and works. There are
however some unexplained crashes that occur at random moments (upon exit
mostly). The program will not work under Windows (since Windows is using
its own protected mode extender).
NOTE: Be careful using memory once you've got complete control because you
can damage DOS data which could cause a crash. EMM uses "windows global EMM
import interface" to transport information about what's in use and what's
not, but I still don't know much about it. (I haven't got Dr. Dobbs'
Journal, August 1994, where that info is supposed to be).

Is it usefull?

There are two answers to this question: one short and one longer. The short
answer first:
No it is not usefull, mostly because you would still be in 16-bit mode and
any 32-bit instruction would carry a prefix taking 1 extra clock cycle to
decode.

The longer answer:
Flat Real Mode was invented when demos was using 486:s and running Windows
3.1 and no good extender using VCPI/DPMI existed. Nowadays there are
several good extenders (PMODE, PMODE/W, Watcom) which run entirely in
protected mode with ability to setup flat mode (this is not flat real mode,
it is 32-bit flat protected mode). When the Pentium was introduced,
optimized to run 32-bit code, flat real mode became less attractive since
it is still 16-bit. As noted above any 32-bit instruction would carry a
prefix and slow down exection severely. The flat real mode routine is also
quite instable and can, sometimes, easily crash the computer resulting in
data loss or other not wanted things.
There may however be times when it is usefull, mostly then for 486
computers running Windows 3.11 or lower. Otherwise there is no real point
implementing the algorithm.
                                                Gem writers: Jurjen Katsman
                                                                Voins / AVE
                                                              John Eckerdal
                                                   last updated: 1998-03-16
