From OSDev Wiki
Jump to: navigation, search

The following code is the entire Real Mode code in the system. It prepares the system for Protected Mode by setting up relevant tables, but it also calls the BIOS to perform tasks, or get information to save away for later. The final thing it does is "stamp" the code with a BIOS signature, so that the various boot loaders will recognize it as a valid - albeit never-seen-before - Boot Sector.


Boot Sector size

One thing to note is that we only have 512 bytes to do everything. If all options are enabled (BIOS.VGA.90x60 takes a lot of work!) there are only a handful of bytes left. For this reason the following code doesn't use the formal, correct, and highly-laudable modular programming technique of defined subroutines that are individually CALLed - I don't have the space to waste on three bytes for every call, and one byte for every return.

Fall-through Code

Instead, everything is written as a stream-of-consciousness - sorry, -execution - paradigm where the tail end of one module (yes, there are still modules!) simply falls through to the start of the next one. There are places where CALL/RET are used - whenever the same bunch of code needs to be executed more than once, I put it into a subroutine. But that pesky RET at the end interferes with the stream-of-cons... er... -execution paradigm, and I have to JMP over it.

But sometimes I use a different technique. If I'm about to fall into the subroutine with carefully-set-up register values, and when I've finished I'm about to JMP past the subroutine anyway, instead I cheat. Instead of

             CALL        .Sub
             JMP         .Past
; Do stuff here

I use an assembly trick and code the following:

             PUSH        .Past   ; Fake a CALL from Past
; Do stuff here

When the RET is executed, it POPs off the return address - which is Past where I want to be!

I don't normally condone this kind of coding, but sometimes it's necessary in constrained environments - and this is very constrained indeed!

Common JMP Code

Having justified my use of a coding technique that would have Niklaus Wirth turning in his (hopefully long-postponed!) grave, I need to mention a more common coding technique that I also use, this time in code that is less constrained. I have some code that is repeated a number of times, but is extremely similar. Given a little initialization code, the rest is identical. In those cases, I do use the following construct:

          ; Perform setup for situation #1
          JMP       CommonCode
          ; Perform setup for situation #2
          JMP       CommonCode
          ; Perform setup for situation #3
          JMP       CommonCode
          ; Perform setup for situation #4
;         JMP       CommonCode
          ; Do what's common

For this technique, I am not apologetic. In fact, the above also demonstrates another technique I use: sometimes code is "obvious", usually by pattern with other code around it, but the effect of that obvious code is a no-effect - in other words, a waste of space. I have three choices:

  1. Leave the code in, taking up some measly number of bytes;
  2. Remove the code completely, meaning that in the future I might just notice the break in the pattern and unthinkingly add it in again; or
  3. Leave the code in, but comment it out to prevent it from being assembled.

Needless to say, I chose the last option.

Memory Map

At this point in time (BIOS Boot complete), the Memory Map looks something like:

Address Usage
0000_0000h Interrupt Vector Table
0000_0400h SS:SP Stack Top *
0000_0400h BIOS Data Area
0000_0500h Available
0000_0600h Master Boot Record (MBR) **
0000_0800h Available
0000_7C00h BIOS-loaded Boot Sector
0000_7E00h Available
0009_F???h *** Extended BIOS Data Area
000A_0000h Adapter / ROM Area
000B_8000h Text Video Memory
000F_0000h BIOS ROM
0010_0000h High Memory ****

* Yes! Believe it or not, the BIOS initializes the stack to point to the end of the Interrupt Vector table. Every CALL or PUSH you make adds a new, interesting vector to the IVT...
** The MBR only exists here if the PC has booted from a Hard Drive.
*** An INT 12h will help identify this value.
**** Note that this memory is only accessible in Protected Mode - except for the first 64 kiB (less 16 bytes), which you can access with a Segment Register set to 0FFFFh as long as the A20 Gate is off. By the way: DON'T DO THIS!


; Boot/
; This is the BIOS Boot entry point. It runs in Real Mode, and assumes the
; standard BIOS Boot Specification 1.01.
; It performs the following actions:
; 1) Loads the rest of the code into RAM;
; 2) Gets some information from the soon-to-be-unusable BIOS;
; 3) Sets up the PC for Protected Mode;
; 4) JMPs to the freshly-loaded Protected Mode code
; After that, the memory used to hold this code is avaiable to the system.
; Note that one of the goals of the Boot sector is to jettison itself:
; 7C00h is such an awkward location, so ideally after doing all the Real Mode
; initialisation, we can JMP to Protected Mode and simply leave this code behind.
; To save space, I take advantage of some HORRIBLE things you can do with
; assembly language: PUSHing values to fake CALLs, or worse, SELF-MODIFYING CODE!
; In Protected Mode, this last is very difficult to organise - and unreliable
; with later processors that have on-board caches - so not worth it.
; In Real Mode, this can be very useful as a register- and code-saving device!
; To save space, I've done away with byte-wasting CALLs and RETs for "modular"
; programming. Not that modules aren't in and of themselves useful - indeed,
; they're still modularised within their own little files - but the result is
; simply a stream of bytes to execute. Where I have defined subroutines to be
; CALLed, the stream of code has to either JMP over the subroutine's RET, or
; a CALL is faked by PUSHing the desired continuation address onto the stack.
                SEGMENT         Real  VSTART=0  ALIGN=1
                USE16                      ; Real Mode? 16 bits!
; At this point, DL is probably the drive number of the Boot Drive.
; ES:SI is possibly a pointer to the MBR Partition table entry (See
; In any case: don't clobber DL, ES or SI before!
%include        "Boot/" ; Entry code to initialise important registers
%include        "Boot/"  ; Check CPU is (at least) a '386
%include        "Boot/" ; Load Protected Mode code
%include        "Boot/"  ; Move IDT to final location
%include        "Boot/"  ; Get RAM size
%include        "Boot/"  ; Enable A20 gate
%include        "Boot/"  ; Speed up keyboard
%include        "Boot/"  ; Switch Video mode
%include        "Boot/"   ; Switch to Protected Mode
; At this point, we still must be inside the first 512-byte Boot sector.
; We need to confirm to the BIOS that this sector is a valid Boot sector by
; having BIOS.Sig.Value at the correct place.
; Note that if the previous code was too large, the following Pad calculation
; will underflow and generate an error.
Real.Padding    EQU             BIOS.Sig.Pos - ($ - $$)
TIMES           Real.Padding    DB 00h
                DW              BIOS.Sig.Value
Personal tools