editing disabled

A FAT-following floppy disk boot sector

In a nutshell

When booted this code will read and execute a particular file - typically an operating system loader - from the floppy from which it was booted. You can use it as it stands or it can help you develop your own OS loader.

The code works particularly well with a virtual environment such as VMWare and others. See a simple development cycle, below.

What makes this boot sector different?

Unlike many other boot sectors this one is able to read and interpret the FAT floppy structures. This has a number of advantages:
  1. Development of an OS loader is simplified. Updates can be made by simply copying the updated OS loader to the floppy disk using normal commands. There is no need to place the OS loader in specific sectors.
  2. When making changes to the boot files there is no need to rebuild the entire floppy or to update anything other than individual files which have changed.
  3. The boot sector and the OS loader can each be changed independently. The only link between them is the name of the file the boot sector looks for.
  4. Other operating systems will understand the floppy contents. Notably, Microsoft operating systems will not suggest formatting the diskette believing it to be invalid.
  5. You can examine and change the contents of the floppy from other operating systems.
  6. You can use the floppy for data and program storage as you would any other. The presence or absence of this boot sector is only relevant when booting a machine.

The boot process

The nominal order of execution during system boot is
boot sector --> OS loader --> OS
where the code herein is the boot sector component. The OS loader will be started in exactly the same mode as a boot sector. The only differences are where the OS loader sits in memory and how large it can be.

In this document the term OS loader will be used for the file that this boot sector reads and executes since that is the intended use. That said, the boot sector could just as easily read and execute a small operating system or some other program which fits in 64k.[1]

To build the boot sector

You need a copy of the Netwide Assembler, Nasm. Then copy the code below to a file called fatbootsect.nasm and build it with
nasm -f bin -O 2 fatbootsect.nasm -o fatbootsect.sys
Make sure you use the right letter 'O's - upper case for the optimisation level then lower case for the output file name.

To install to a floppy

The boot sector code works with a FAT-formatted floppy and will use whatever geometry settings already exist. These are found in the boot sector which is already on the diskette. You only need to format the floppy if it doesn't have a FAT filesystem. Most floppies come preformatted with FAT.

Since the floppy disk's FAT geometry will already be in its boot sector (occupying bytes at offsets 3 to 61 inclusive) you need to overwrite bytes at offsets 0 to 2 and 62 to 511. On Unix or Linux you can use the dd command. On Windows use dd for Windows or on plain DOS the program jsys.nasm can carry out the updates for you.

Once the new boot sector is installed you can use the floppy as normal. The presence of the new boot sector will only be relevant when booting from that floppy.

To remove from a floppy

If you want to remove the boot sector code you can simply overwrite it as above or by using the
sys a:
command on DOS or older versions of Windows.

Remember that the non-data contents of the boot sector are only relevant when booting the machine and that on boot the BIOS will try to execute what it finds in the boot sector. Some BIOSes will try to execute the boot sector even if it doesn't have the signature of 0x55, 0xaa at the end.[2] Boot sectors which are not intended to be executed may still contain enough executable code to write an error message - something like, "Cannot boot from this floppy."

To install your OS loader

To place your OS loader onto the floppy simply copy it with the name osld.sys. For example, in Windows if your OS loader is called myosloader type
copy myosloader a:\osld.sys
Note that the case of the name osld.sys is unimportant. The FAT system will store a directory entry in upper case as OSLD.SYS. This is the one the boot sector looks for.

Your OS loader will be loaded into memory at location 0x0001_0000 and begin executing at its first byte. The boot sector jumps to it with
jmp 0x1000:0000
On entry to your code the following will be the case
  • Your code will start at the beginning of the second 64k and be up to 64k in size. (The CS register will contain your code segment of 0x1000 and IP will be zero.)
  • There will be a working stack at the high end of the first 64k. (The SS register will contain zero and the SP register will point near the top of the lowest 64k.)
  • The DL register will contain the number of the drive from which you have been loaded.
  • Interrupts will be enabled.

To update your OS loader

Simply copy the new version to the floppy disk under the name osld.sys exactly as you did when installing it. For example, in Windows type
copy myosloader a:\osld.sys

To execute the boot sector

Once you have the boot sector installed and your osld.sys file copied onto the floppy you can simply boot from it.

A simple development cycle

Rather than reboot the PC you are developing on or move the floppy to a test machine you may wish to consider virtualisation software. VMware server, Bochs and Virtual PC are all free to use. They allow you to both develop and test on one PC. Under such a scheme the process is simply
  1. Compile or assemble your changes
  2. If the virtual machine is running stop it
  3. Copy the changed files to the floppy disk
  4. Start the virtual machine
This is quick to do. You can then test your OS or OS loader in the virtual machine and repeat as necessary.

Boot progress

Space in 512 bytes is extremely limited but fatbootsect.nasm is just able to emit single-character progress updates. They occur in the following order after the boot parameter block values have been checked.
F - About to read-in the first FAT
R - About to read through the root directory
L - About to load the next file (the OS loader)
G (Go) - About to run the loaded code
In addition, if one of the parts fails you will see
Q - Quit
and for each sector read you will see a dot
. - A sector has been successfully read
The idea is that if the load process stops you can see what it was trying to do when it failed. The rule is that letters precede the action and the dot follows the action. So if you receive a report that says
three FAT sectors were successfully read (one for each dot) and the fourth one failed to read.

Note that the code is designed to
  1. read all of the first FAT from the diskette and then
  2. read only as many root directory sectors as necessary to either find the file or establish that it is not present.

The entire FAT is read-in because the FAT chains may jump about to arbitrary locations. By contrast, the root directory entries only need to be read one at a time.

If you see
the boot sector read in all of the first FAT and two sectors of the root directory. It failed to read the third sector of the root directory.

In particular, note that if you receive a report which says
the boot sector found an error in the floppy disk's boot parameter block and so didn't even try to read in the first FAT.

The code

;* fatbootsect - a floppy disk boot sector
;Code taken from http://codewiki.wikispaces.com/fatbootsect.nasm.
;The purpose of this code is to load and start an OS loader using
;BIOS calls. We have 512 bytes maximum of loaded code and data.
;Build using
;  nasm -f bin -O 2 fatbootsect.nasm -o fatbootsect.sys
;Note that there are two letter 'O's in the command line. The first
;one is upper case and specifies the optimisation level. The second
;one is lower case and specifies the output file name.
;If this is to be assembled as a com file define AS_COM_FILE here or
;as an assembly predefine (i.e. on the Nasm command line). Otherwise
;this will be assembled as a boot sector. Other assembler options
;may need to be set accordingly.
;;%define AS_COM_FILE
;When loaded as a boot sector this code will be located starting
;at address 0x7c00.
;All code here will need to be 16-bit and not assume the presence of
;anything beyond an 8086. Once the more complete OS loader is in
;memory it can check for an 80386 or above.
;Since we are running on a mainly empty PC but with interrupts
;enabled it is worth bearing in mind the machine memory map.
;Based on Ralf Brown's Interrupt List (RBIL) the basic memory
;map is as follows. Addresses are given in hex and decimal.
;Approximate sizes of the areas are in decimal.
;  00000/0,000,000 -  003ff/0,001,023 (1k)    Int vectors 256@4by
;  00400/0,001,024 -  004ff/0,001,279 (256)   BIOS Data Area
;  00500/0,001,280 -  00500/0,001,280 (1)     Print screen status
;  00501/0,001,281 -  00501/0,001,281 (1)     NEC PC-9800 screen mode
;  00502/0,001,282 -  00503/0,001,283 (2)     Undocumented. May be used
;  00504/0,001,284 -  005ff/0,001,535 (252)   DOS or ROM Basic, avail
;  00600/0,001,536 -  007ff/0,002,047 (512)   DOS space, available
;  00800/0,002,048 -  00865/0,002,149 (102)   i286 Loadall space, avail
;  00866/0,002,150 -  07bff/0,031,743 (29594) Space, available
;  07c00/0,031,744 -  07dff/0,032,255 (512)   This code
;  07e00/0,032,256 -  07fff/0,032,767 (512)   Ram above us, available
;  08000/0,032,768 -  0ffff/0,065,535 (32k)   Space, available. We
;                                             set the stack to the
;                                             top of this space.
;  10000/0,065,536 -  1ffff/0,131,071 (64k)   Ram presumed in any PC.
;                                             The file is loaded from
;                                             the start of this space
;                                             and can be 64k in size.
;  20000/0,065,536 -  7ffff/0,524,287 (384k)  Ram presumed in any PC
;  80000/0,524,288 -  9ffff/0,655,359 (128k)  Possible extra Ram
;  a0000/0,655,360 -  bffff/0,786,431 (128k)  Video memory
;  c0000/0,786,432 -  effff/0,983,039 (192k)  Device space
;  f0000/0,983,040 -  fffff/1,048,575 (64k)   BIOS code space
; 100000/1,048,576 - 10ffef/1,114,095 (64k)   The High Memory Area
;The goals of this program:
;   1. To load the next file, the OS loader, as generally as possible
;      - Allow the diskette to be recognised by Microsoft and Unix OSs
;         - This requires following the FAT-12 chain
;      - Not requiring the OS loader to be in certain sectors
;      - Not to expect a particular diskette geometry
;   2. To work robustly and include error/sanity checking
;   3. To keep the user informed of loading progress
;Ensure that all code herein works on the 8086 since we don't as yet
;know which CPU is present. That can be investigated later by the OS
        cpu     8086
%define READTRIES 6     ;Maximum number of times to try a disk op
%define MAXFATSECS 40   ;Maximum number of sectors per FAT we permit
%define DIRENTSIZE 32   ;Size of a directory entry in bytes
%define V_INK 7         ;Foreground colour
%define GeometryTableSize (GeometryTableEnd - GeometryTableStart)
;Write a char if running as com file to aid debugging.
;There is simply no space to write this as a boot sector.
%macro putchar 1
        push    bx
        push    ax
        mov     al,%1                   ;Write this character
        call    emitone                 ; to the screen
        pop     ax
        pop     bx
        org     0x100
;;;;    [org    0x7c00]         ;Nasm seems to ignore
        jmp short bootstart     ;Standard jump (see FAT12 spec)
        nop                     ;Standard filler (see FAT12 spec)
;Set up the rest of the Bios Parameter Block (BPB) with default
;values for a 1.44Mbyte floppy. Note that the values here are samples
;only. The code is designed to work with various values in this table
;as long as they are valid and sensible. This table should be
;replaced by the one already on the floppy disk when the floppy's
;boot sector is replaced by the code that follows.
;Note that because we must be 512 bytes in size we will assume that
;BPB_BytsPerSec is 512. We also assume and depend on each root
;directory entry being 32 bytes in size. Other values are mentioned
;here and are used as given on the table which comes from the disk.
GeometryTableStart      equ     $
BS_OEMName      db      "jasscl01"
BPB_BytsPerSec  dw      512
BPB_SecPerClus  db      1
BPB_RsvdSecCnt  dw      1
BPB_NumFATs     db      2
BPB_RootEntCnt  dw      224
BPB_TotSec16    dw      2880
BPB_Media       db      0xf0
BPB_FATSz16     dw      9
BPB_SecPerTrk   dw      18
BPB_NumHeads    dw      2
BPB_HiddSec     dd      0
BPB_TotSec32    dd      0
BS_DrvNum       db      0x00
BS_Reserved1    db      0
BS_BootSig      db      0x29
BS_VolID        dd      0x12345678
BS_VolLab       db      "VOLUMELABEL"
BS_FilSysType   db      "FAT12   "
GeometryTableEnd        equ     $
%if GeometryTableSize <> 59
%error "Geometry table as defined in this code has the wrong length."
;Define any constants used in the code
word3           dw      3
fname           db      "OSLD    SYS"   ;File to be loaded - 11 bytes
%if $ - fname <> 11
%error "The OS loader file name must be exactly 11 bytes long (8.3)"
;This code is in four parts,
;       1) Set up environment, checking and precalculations
;       2) Read in the first FAT
;       3) Scan the root directory for the loader file
;       4) Load and run the OS Loader
;* Short subroutines
;Provide a redirect for the initial short jumps
        jmp     badparms                ;Skip to main error block
;Write one character to the screen, character supplied in AL
        push    bx
        mov     bx,0x0007               ;Page 0; colour 7 if graphics
        mov     ah,0x0e                 ;Write the character
        int     0x10                    ; by calling the BIOS
        pop     bx
        ret                             ;Go back
;* The main code
;We are here courtesy of the BIOS loader. Assume that we have been
;loaded at the designated location, 0x07c00 (ie, a little below
;the 32kbyte point). Set up all other registers etc.
;Set up the data segment registers.
        mov     ax,0x07c0               ;Point at start of this code
        mov     ds,ax                   ;Set DS
        mov     es,ax                   ;Set ES
;Set up the stack
        xor     ax,ax                   ;Clear AX
        mov     ss,ax                   ;Set SS and pause interrupts
        mov     sp,ax                   ;Stack at top of base 64k
        mov     [BS_DrvNum],dl          ;Save boot drive
        cld                             ;Work in ascending direction
;Check and precalculate values - note that AX begins as zero from
;the above code.
        or      al,[BPB_SecPerTrk+1]    ;Test high byte of sec/track
        or      al,[BPB_NumHeads+1]     ;Test high byte of num heads
        jnz     badparms1               ;Quit if any are nonzero
        mov     byte al,[BPB_SecPerClus]        ;Secs/cluster in AX
        mul     word [BPB_BytsPerSec]   ;Form bytes per cluster
        or      dx,dx                   ;Test high 16-bit word
        jnz     badparms1               ;Abort if not zero
        mov     [ByPerClus],ax          ;Save for later
        mov     ax,0xffff               ;Set value for tests of zero
        test    al,[BPB_SecPerTrk]      ;Sectors per track = 0?
        jz      badparms1               ;Quit if yes
        test    al,[BPB_NumHeads]       ;Number of heads = 0?
        jz      badparms1               ;Quit if yes
        test    ax,[BPB_RsvdSecCnt]     ;Num of reserved sectors = 0?
        jz      badparms1               ;Quit if yes
        mov     ax,[BPB_FATSz16]        ;Fetch FAT size in sectors
        cmp     ax,MAXFATSECS           ;Check for highest allowed
        jg      badparms1               ;Quit if too high
        mov     ax,[BPB_SecPerTrk]      ;Fetch sectors per track
        cmp     ax,62                   ;Compare with highest allowed
        jg      badparms1               ;1-based too big for BIOS
        mul     byte [BPB_NumHeads]     ;Form sectors per cylinder
        or      ah,ah                   ;Test high byte
        jnz     badparms1               ;Abort if not zero
        mov     [SecsPerCyl],al         ;Save as a byte
;Calculate the starting sector of imaginary cluster 0. Note
;that due to the presence of a media byte etc the first usable
;cluster in the FAT is number 2. This refers to the cluster that
;follows on from the last sector of the root directory.
        mov     dx,[BPB_RootEntCnt]     ;Entries in root dir
        dec     dx                      ;Root ent - 1
        mov     cl,4                    ;Form (root ent - 1)/16
        shr     dx,cl                   ; in DX
        inc     dx                      ;Size of root dir in sectors
        mov     [RootDirSecs],dx        ;Save for later
        mov     al,[BPB_FATSz16]        ;Only significant byte
        mul     byte [BPB_NumFATs]      ;Space taken by FATs in AX
        add     ax,[BPB_RsvdSecCnt]     ;Add boot sector etc
        mov     [RootDirStart],ax       ;Save 1st sector of root dir
        add     ax,dx                   ;Form cluster 2 pointer
;;;     mov     [FileAreaStart],ax      ;1st sector of file area
        xor     dh,dh                   ;Zero top byte to
        mov     dl,[BPB_SecPerClus]     ; get secs per cluster in DX
        sub     ax,dx                   ;Take off one cluster.
        sub     ax,dx                   ;Take off another cluster.
        mov     [ClusBase],ax           ;Cluster "0" offset in sectors
;Set up the screen for later writes
        mov     ax,0x0500               ;Set display page to 0
        int     0x10                    ; by calling the BIOS
;* Read in the whole of the first FAT
;Since the FAT will be the next entry on the diskette read in one full
;copy of the diskette's FAT. This will be used to load the file later.
        mov     al,'F'                  ;Write this character
        call    emitone                 ; to the screen
        mov     bx,LoadedFAT            ;Point BX at FAT buffer
        mov     ax,[BPB_RsvdSecCnt]     ;First FAT sector
        mov     [RawSecNum],ax          ;Save sector to read
        mov     cx,[BPB_FATSz16]        ;All sectors to be read
        call    get1sector              ;Get one sector to ES:BX
        inc     word [RawSecNum]        ;Next sector number
        loop    rdfatlp                 ;Go back for another read
;* Read through the root directory until we find the target file name
;Read the root directory and see if the loader file is there. Note
;that the file will normally be hidden but need not be as we will
;check each file in turn whether hidden or not. Since we don't need
;to keep this information the same sector buffer will be used
;for each sector of the root directory.
        mov     al,'R'                  ;Write this character
        call    emitone                 ; to the screen
        mov     ax,[RootDirStart]       ;1st sector of root dir
        mov     [RawSecNum],ax          ;Start at the beginning
        mov     cx,[RootDirSecs]        ;Max sectors to read
        push    cx                      ;Save sector down-counter
        putchar 'd'
        mov     bx,SectorBuf            ;Point at the target
        push    bx
        call    get1sector              ;Get the sector
        pop     bx                      ;Keep pointing at this sector
        mov     cx,16                   ;Root dir entries in a sector
        push    cx                      ;Save dir entry down-counter
        putchar 'n'
;Compare this entry's name with that of the file we are seeking. Note
;that the comparison will be in the 8.3 format and case-dependent.
        mov     si,bx                   ;Get pointer to this dir entry
        mov     cx,11                   ;Number of bytes to compare
        mov     di,fname                ;Point at the filename we want
        repe cmpsb                      ;A match?
        je      gotname                 ;Skip if yes
        add     bx,DIRENTSIZE           ;Skip over this directory entry
        pop     cx                      ;Restore the dir entry counter
        loop    namelp                  ;Go back for another entry
;That's all the names in this sector. Go on to the next.
        inc     word [RawSecNum]        ;Next sector
        pop     cx                      ;Restore sector counter
        loop    dirlp                   ;Back for next sector
        jmp     notfound                ;Abort to error section.
;Note that the stack has an extra copy of CX but we can leave it in
;place to save code space as the stack will be rebuilt by the OS
;* Read in the target file
;We have found the name we are looking for. First extract the starting
;cluster number from the root directory entry.
        mov     si,bx                   ;Set SI to start of entry
        mov     al,'L'                  ;Write this character
        call    emitone                 ; to the screen
        mov     ax,[si+26]              ;Fetch starting cluster number
        mov     [CurrClus],ax           ;Store start cluster number
;Get the number of clusters to be read. This is <file length> divided
;by <cluster size>.
;Since the number of bytes per cluster will not exceed 32k we can
;ignore the value in DX. It will be zero.
        mov     ax,[si+28]              ;Fetch lower part of file size
        mov     dx,[si+30]              ;Fetch top part of file size
        or      dx,dx                   ;Test it
        jz      gotfsize                ;Skip if zero; keep the lower
        xor     ax,ax                   ;Clear AX
        dec     ax                      ;Make AX 0xFFFF - read max
;We have the file size in AX. Note that this is limited to 65535
;bytes for two reasons, 1) we can only write to 64K and 2) the OS
;loader will be able to load faster should any more be required.
        dec     ax                      ;Get file size - 1
        xor     dx,dx                   ;Prepare for long division
        mov     cx,[ByPerClus]          ;Bytes per cluster
        div     cx                      ;Split: get clusters - 1 in AX
        mov     cx,ax                   ;Copy AX and add one to get
        inc     cx                      ; number of clusters to read
;Now work through the FAT loading each cluster in turn and after each
;one skipping on to the next according to the FAT entry for the one
;just loaded.
        mov     bx,comfileloadarea      ;Point at available memory
;Fix the load destination to the second 64k of memory.
        xor     bx,bx                   ;Clear BX
        mov     ax,0x1000               ;0x1000=4096
        mov     es,ax                   ;Make ES point to 0x10000 (65536)
;From now on ES no longer points to the first 64k of memory but to
;the second.
;In the loop that follows the variable CurrClus maintains the
;number of the cluster in question
        push    cx                      ;Save clusters to read
        call    get1cluster             ;Get 1 cluster
        call    getfat12ent             ;Follow FAT chain to next cluster
        pop     cx                      ;Restore number left to read
        loop    clustlp                 ;Go back for the next cluster
;* Execute the loaded file
;We have read in all of the file. Now execute it.
        mov     al,'G'                  ;Write this character
        call    emitone                 ; to the screen
        mov     dl,[BS_DrvNum]          ;Get the boot drive id
        int     0x20                    ;Exit to DOS
;       jmp     comfileloadarea         ;Run the loaded code
        jmp     0x1000:0000             ;Exit to loaded file.
;* Fatal error
;Code to execute on fatal error
        mov     al,'Q'                  ;Write this character
        call    emitone                 ; to the screen
        int     20h                     ;Sharp exit.
        jmp short stopnow               ;Stop here.
;* Get one sector
;Attempt to read one sector from the diskette using the bios
;On entry
;       16-bit  RawSecNum
;               Absolute number of sector to be read (from zero)
;       Byte    SecsPerCyl
;               Number of sectors in each cylinder
;       ES:BX   Address of the buffer which is to receive the sector
;       16-bit  BPB_SecPerTrk
;                       Number of sectors on a track
;       16-bit  BPB_NumHeads
;                       Number of heads in a cylinder
;       Byte    BS_DrvNum
;                       Diskette drive number
;On return if successful
;       Buffer pointed to by old BX containing sector as read
;This routine will not return if there is a failure
;CX will be preserved across this routine
;Other registers are not preserved over this routine
        putchar 's'
        push    cx                      ;Preserve incoming CX
        mov     cx,READTRIES            ;Number of times to try read
        push    cx                      ;Save the read attempt count
;Convert the zero-based sector number to cylinder, head and sector
;numbers in the registers as required by the bios.
        mov     ax,[RawSecNum]          ;Fetch 0-based sector number
        div     byte [SecsPerCyl]       ;Split into two parts
        mov     ch,al                   ;Save the cylinder number
        mov     al,ah                   ;Copy sector within the cyl
        xor     ah,ah                   ;Make it 16-bit
        div     byte [BPB_SecPerTrk]    ;Split this into two
        mov     dh,al                   ;Save track or head number
        mov     cl,ah                   ;Form 1-based
        inc     cl                      ; sector number
        mov     dl,[BS_DrvNum]          ;Fetch disk drive number
        mov     ax,0x0201               ;Command to read 1 sector
        push    bx                      ;Save output pointer
        int     0x13                    ;Call BIOS
;Note the parameters in the above call,
;       ES:BX   Output location
;       CH,CL   Cylinder number, 1-based sector number
;       DH,DL   Track/head number, drive number
        pop     bx                      ;Restore output pointer
        jnc     got1sector              ;Skip forward if read successful
        xor     ah,ah                   ;Reset disk subsystem
        int     0x13                    ; via the BIOS
        pop     cx                      ;Fetch attempt downcounter
        loop    g1sreadloop             ;Go back for another
        jmp     diskfail                ;Abort
        mov     al, "."                 ;Indicate one
        call    emitone                 ; sector read successfully
        pop     ax                      ;Waste local CX stored on stack
        add     bx,[BPB_BytsPerSec]     ;Point past sector just read
        pop     cx                      ;Restore incoming CX
        ret                             ;Go back to caller
;* Get one cluster
;Attempt to read one cluster from the diskette
;Subroutine get1sector is called to read each sector.
;On entry
;       16-bit  CurrClus
;                       Number of cluster to be read (from zero)
;       16-bit  ClusBase
;                       Number of sectors to cluster "0"
;       ES:BX   Address of the output buffer
;       Byte    BPB_SecPerClus
;                       Number of sectors in a cluster
;       16-bit  BPB_SecPerTrk
;                       Number of sectors on a track
;                       Used by get1sector
;       16-bit  BPB_NumHeads
;                       Number of heads in a cylinder
;                       Used by get1sector
;       Byte    BS_DrvNum
;                       Diskette drive number
;                       Used by get1sector
;On return if successful
;       Buffer pointed to by old BX containing cluster
;       ES:BX   pointing just past the data read
;       CX will be modified by this routine
;This routine will not return if there is a failure.
        putchar 'c'
        xor     ah,ah
        mov     al,[BPB_SecPerClus]     ;Sectors per cluster in AX
        mul     word [CurrClus]
        add     ax,[ClusBase]           ;First required sector number
        mov     [RawSecNum],ax          ;Save number of first sector
        xor     ch,ch                   ;Zero high byte
        mov     cl,[BPB_SecPerClus]     ;Num of sectors to read
        call    get1sector              ;Fetch this sector
        inc     word [RawSecNum]        ;Next sector in this cluster
        loop    clustloop
;* Get an entry from the loaded FAT12
;Given an area of memory containing a valid FAT12 return to the
;calling routine the contents of a given entry.
;Note that entries 0 and 1 are dummies and that no range checking will
;be performed. AX is assumed to be in the range 0 to the number of
;the last entry in the FAT. The absolute maximum valid value for AX
;is 21845 to allow short arithmetic to be used.
;On entry
;       AX      FAT entry desired
;               Silently assumed to be <= 21845 (65535/3)
;On return
;       AX containing FAT entry requested, right justified
;       SI modified
;       CX modified
;       DX zeroed
;There are no failure conditions.
        putchar 'f'
        mov     ax,[CurrClus]           ;Fetch number of this cluster
        mul     word [word3]            ;Form DX:AX = AX*3; zero DX
        shr     ax,1                    ;carry selects nibbles needed
        mov     si,ax                   ;Address of FAT12 16-bit word
        mov     ax,[si+LoadedFAT]       ;Load the required word
        jnc     .lowbits                ;Skip if in lower 12 bits
        mov     cl,4                    ;Drop the lower
        shr     ax,cl                   ; 4 bits
        and     ax,0x0fff               ;Drop the top 4 bits
        mov     [CurrClus],ax           ;Save the new cluster number
        ret                             ;Back to caller
;* Epilogue
;Make the above fit exactly 510 bytes and insert the signature "55AA"
;at the end. Note that if the size of the preceding code is too high
;the following directive (times) will report an error.
        times   509-$+boottop db 0      ;Space out to offset 508
        db      GeometryTableSize       ;Bytes in geometry table
        db      0x55, 0xaa              ;Boot sector signature, 55, aa
        section .bss
SectorBuf       resb    512             ;Available space.
SectorBufE      equ     SectorBuf+512   ;End of buffer.
ByPerClus       resw    1               ;Bytes per my FAT12 cluster
SecsPerCyl      resw    1               ;Bytes per my FAT12 cluster
DirEntLeft      resw    1               ;Dir entries left to process
RootDirSecs     resw    1               ;Sectors in the root dir
RootDirStart    resw    1               ;Start sector of root dir
ClusBase        resw    1               ;Sector number of cluster "0"
CurrClus        resw    1               ;Current cluster number
RawSecNum       resw    1               ;Number of raw sector to be read
 resb 512-($-SectorBufE)                ;Space out to 32768, 0x8000
LoadedFAT       resb    512*MAXFATSECS  ;Space for the FAT
comfileloadarea resb    10000
end             equ     $

  1. ^ Because 64k is large for a boot loader and in order to support embedded PCs with very small memories a future version of this code may be written to load only 32k. If this change is made it will likely place the OS loader at location 0x8000 or 32,768 with the stack just below.
  2. ^ The correct sequence of these two bytes is 0x55, 0xaa - i.e. low then high, 0x55 at offset 510 and 0xaa at offset 511. The value is sometimes seen as 0xaa55 such as when looking at the two bytes in little endian form.