Search (using Google):  Web Karig

 

23 November 2003

Unreal mode

I plan to have Karig run in protected mode so that I can use a 32-bit code segment. I also want to be able to use the BIOS services from within Karig, unless and until I start writing my own code to read from and write to disks directly. I also want to be able to load data into and save data from high memory — memory beyond the one-megabyte mark.

At first, these ideas seem irreconcilable. You can't use the BIOS services from protected mode; they'll only run in real mode. You also can't use the BIOS services to do anything with data in high memory because you can't access high memory in real mode.

The first solution that springs to mind is to switch back and forth between real mode and protected mode. This means that if I want to load a file larger than will fit into the memory that can be used into real mode, I have to switch from protected mode to real mode, read in a piece of a file, switch to protected mode, copy the piece into high memory, and repeat these steps until the entire file is read in.

A better solution is to use unreal mode instead of real mode. Unreal mode (sometimes called "flat real mode") is a variation on real mode. The code and the stack are still 16-bit, so code and stack segments can't be larger than 64KB. However, the data segments can cover the entire four-gigabyte address space, so you can use the 32-bit registers to access data anywhere in memory. The big bonus is that you can use BIOS services while in unreal mode, so you don't have to switch back and forth between real mode and unreal mode.

Code

To access high memory, enabling the A20 line isn't enough. You also have to put the computer into either unreal mode or protected mode. This code puts the computer into unreal mode.

(Test code here.)

Start in pure real mode.

[BITS 16]
main:

Disable interrupts. Note that I'm also disabling NMIs (non-maskable hardware interrupts, which are not disabled with the CLI instruction). I discovered this bit of code on the Net and thought it would be prudent to start adding it to my own code.

		cli
		mov	dx, 0x70
		in	al, dx
		or	al, 0x80
		out	dx, al

Enable the A20 line, using the short bit of code I introduced in my previous entry.

		mov	al, 0xD1
		out	0x64, al
		mov	al, 0x03
		out	0x60, al

Load the new segment database into the processor. This database is called a global descriptor table or GDT. It includes records (called descriptors) for the new four-gigabyte data segments.

		lgdt	[GDT]

Turn on protected mode (by turning on the first bit in Control Register Zero).

		mov	eax, cr0
		or	al, 1
		mov	cr0, eax

The processor still considers this code to be part of a 16-bit real-mode code segment, so I have to tell the processor to see this code as 32-bit. I do this by jumping to the next instruction, as an offset into the 32-bit code segment. The descriptor for the 32-bit code segment is eight bytes into the GDT, so that's the segment number I specify. This sets up CS for protected mode.

		jmp	dword 0x08:.1

Once this is done, the processor sees 32-bit instructions, not 16-bit instructions. I have to tell the assembler to generate 32-bit instructions.

[BITS 32]

Now each of the other segment registers has to be changed because they still contain values appropriate to real mode.

I want to change the size of each of the four data segments to four gigabytes, so I load the four data-segment registers with the number of the four-gigabyte data segment.

	.1:	mov	eax, GDT.dseg-GDT
		mov	ds, eax
		mov	es, eax
		mov	fs, eax
		mov	gs, eax

I don't want to change the size of the stack segment. In unreal mode, the stack segment is still 64KB, because the stack pointer in unreal mode is the 16-bit SP register, not the 32-bit ESP register. So I load the stack-segment register with the number of the 64KB data segment.

		mov	eax, GDT.ds16-GDT
		mov	ss, eax

Update the code-segment register by jumping to the 16-bit code segment. (The code segment in unreal mode can't be larger than 64KB either, because the processor uses the 16-bit IP register to get the next instruction, not the 32-bit EIP register.)

		jmp	dword 0x18:.2

This means that the assembler has to start generating 16-bit instructions again.

[BITS 16]

Turn off protected mode.

	.2:	mov	eax, cr0
		and	al, 0xFE
		mov	cr0, eax

Now that protected mode is off, we have to load the segment registers again, with values more appropriate to real mode.

First I reload CS by jumping to a real-mode segment. I specify segment zero here — which means that this code will not work unless it is stored in the first 64KB of memory. (I plan on using this code in the Karig boot sector, which is located entirely within the first 64KB of memory.)

		jmp	word 0x00:.3

Then I point all of the data-segment registers at segment zero also. Note that this does not change the size of the segment. The data segment is still four gigabytes, so instructions like MOV BX, [EAX] will work.

	.3:	xor	ax, ax
		mov	ds, ax
		mov	es, ax
		mov	fs, ax
		mov	gs, ax

I could have put the stack in segment zero also, but I decided to keep it in real-mode segment 0x1000 — above the bottom 64KB of memory.

		mov	ax, 0x1000
		mov	ss, ax

The machine is in real mode. To use BIOS services, I have to re-enable interrupts.

		mov	dx, 0x70
		in	al, dx
		and	al, 0x7F
		out	dx, al
		sti

GDT

This is the global descriptor table or GDT. It contains five segment descriptors, each eight bytes long.

The first descriptor is called the null descriptor and is never used, so the information about the GDT can be stored here. (The LGDT instruction takes a pointer to this information about the GDT — not a pointer to the GDT itself. I stored the information in the GDT to save a little space.)

The next two descriptors are used in protected mode. The 32-bit code segment is used only in protected mode; the 32-bit data segment is used in both protected mode and unreal mode.

The last two descriptors are used only in unreal mode. The 16-bit data segment is used for the unreal-mode stack only.

GDT:		dw gdt_limit ; null segment, used to store GDT metadata
		dd GDT
		dw 0

	.cseg:	dd 0x0000FFFF, 0x00CF9800 ; code segment, 32-bit, 0 to 4GB
	.dseg:	dd 0x0000FFFF, 0x00CF9200 ; data segment, 32-bit, 0 to 4GB
	.cs16:	dd 0x0000FFFF, 0x00009800 ; code segment, 16-bit, 0 to 64KB
	.ds16:	dd 0x0000FFFF, 0x00009200 ; data segment, 16-bit, 0 to 64KB

	gdt_limit equ $-GDT-1

Statistics

• The code and data here make up 140 bytes of machine code.
• The code takes up 100 bytes; the GDT, 40 bytes.
• The code to turn interrupts off and on again takes up 16 bytes.
• The code used here to turn on the A20 line takes up 8 bytes.

Other pages

protected mode (FOLDOC definition)
Protected mode by Chris Giese
The world of protected mode by Gregor Brunmar
Introduction to Protected Mode Programming
Embedded X86 Programming: Protected Mode
Protected Mode (page of links)
Protected Mode Basics
Segment Registers: Real mode vs. Protected mode
 
Flat real mode AKA Unreal mode AKA Voodoo mode
FLAT REAL / REAL BIG / UNREAL MODE (v1.2)
unreal.asm
The Segment Descriptor Cache by Robert R. Collins
Protected-mode tutorials by Alexei A Frounze (tutorial #10 demonstrates unreal mode)

You can also find the Intel 80386 Reference Programmer's Manual on various places around the Net. Look up Chapters 6 (Protection) and 14 (80386 Real-Address Mode) for discussions of real and protected mode.

homepages.ius.eduwww7.informatik.uni-erlangen.demy.tele2.eewebster.cs.ucr.eduwww.logix.czwww.microsym.com (PDF) • www.online.eewww.itu.dkjoyfire.net

You can also download the full Pentium 4 manuals (as PDF files) from Intel.

Check the index for other entries.