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. 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. Other pages
• protected mode (FOLDOC definition) 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.edu • www7.informatik.uni-erlangen.de • my.tele2.ee • webster.cs.ucr.edu • www.logix.cz • www.microsym.com (PDF) • www.online.ee • www.itu.dk • joyfire.net You can also download the full Pentium 4 manuals (as PDF files) from Intel. Check the index for other entries. |