Flat Real ModeAssembler/80386

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

What is Flat Real Mode

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

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:

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: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