flat assembler
Message board for the users of flat assembler.

Index > OS Construction > FAT12 tutorial

Thread Post new topic Reply to topic

Joined: 06 Jan 2011
Posts: 200
Hey guys! I thought I might give a shot at writing a tutorial for a small boot sector for a FAT12 formatted floppy. I know it uses hard coded numbers, but I gave the equations in the comments. Let me know what you think Smile
;This bootsector was created by ME239 and is protected under the GNU license agreement.
;This article was intended for educational purposes and was optimized for readability and size.
;The numbers used for the sectors and number of sectors is written in an emperical way to save on
;space, but full equations are given in the comments
org 0 ; we start everything at zero
jmp start        ; the jump must be 3 bytes before the BPB. A jump is 2 bytes so we add a NOP, or 1 byte
nop              ; This is just the BPB
OEM  db 'SAMPLEBT' ; the name of our disk
BytesPerSecot dw 512
SectorsPerCluster db 1
ReservedSectors dw 1
NumberofFATs db 2
RootEntries dw 224
TotalSectors dw 2880
Media db 0xf8 ; what are we? A floppy, HD, etc.
SectorsPerFAT dw 9
SectorsPerTrack dw 18
HeadsPerCylinder dw 2
dd 0 ; These are hidden sectors, but we won't use them
dd 0
DriveNumber db 0      ;here is the start of the extended BPB (for DOs and windwos)
Unused db 0
Bootsig db 29h
Serial dd 0x1a2a3a
VolumeLabel db 'SAMPLEBOOT '
FileSystem db 'FAT12   '
        cli     ; let's disable interrupts while we configure the stack and segments
        mov     ax, 0x07c0 ; look at an explanation of segmented memory (at the bottom)_
        mov     es, ax  ; let's set all the registers to the new segment
        mov     ds, ax
        mov     gs, ax
        mov     fs, ax
        xor     ax, ax
        mov     ss, ax ; stack segment is now 0
        mov     sp, 0ffffh ; but the stack pointer is at 0xffff giving us a 65,535 byte stack
        sti     ; we're done here, let's re-enable interrupts
        mov     ax, 19 ; Root directory starts at 19 (look at FAT12 map for help)\
        mov     cx, 14 ; Root directory size (224 entries * 32 bytes per entry all divided by 512(sectors per byte)
        mov     bx, 200h ; we want to read the root directory to the end of the bootloader (200h = 512 decimal)
        call    readsectors ; put the sectors into RAM
        mov     di, 0x200 ; set the data index to 200h
        mov     cx, 224 ; max number of root entries so we don't read over the limit
findfile: ; let's find our kernel!
        push    cx ; save cx
        mov     cx, 11 ; each entry in FAT12 has an 11 byte name
        mov     si, filen ; the name of our kernel
        push    di ; save the data index
        repe    cmpsb ; let's compare
        pop     di ; restore DI
        je      filefound ; was it equal? If so, then we found our file
        pop     cx ; restore CX
        add     di, 32 ; let's search the next entry
        loop    findfile ; loop this only 224. If the file isn't found, then fail
        int     18h ; file wasn't found, let's bail!
filefound: ; we've found our kernel! Now let's load it!
        pop     cx ; let's restore CX to keep the stack intact
        mov     dx, word[di+1ah] ; each FAT12 entry contains the first cluster 26 bytes after the beginning of the entry (0x1a = 26 decimal)
        mov     word[cluster], dx ; let's save this for later
        mov     ax, 1 ; there is one reserved sector on our disk, the bootloader. So that means the FAT table is right after it!
        mov     cx, 9 ; there are to FAT tables. Each are 9 sectors and identical stored only as backups to eachother. So let's just load the first one
        mov     bx, 0x200 ; let's overwrite the root directory since we already have the first cluster
        call    readsectors ; let's read the FAT table into RAM
        mov     ax, 0x60 ; this is our new segment where we want to load the kernel
        mov     es, ax ; let's put that into the ES register
        xor     bx, bx ; zero out BX so the kernel will be loaded to 0060:0000 (ES:BX)
        push    bx ; save BX for the loop
        mov     ax, word[cluster] ; put the cluster number in AX
        sub     ax, 2 ; the conversion for cluster to LBA (or Logical Sector) is simply (cluster number - 2)* the number of sectors per cluster (we're a floppy so we only have one sector per cluster)
        add     ax, 33 ; the data portion starts at 33. (1 reserved sector + 2 FAT tables * 9 sectors each)+((224 root entries * 32 bytes per entry)/512 bytes per sector) = 33
        mov     cx, 1 ; we only have one sector per cluster, so we only read one sector
        pop     bx
        call    readsectors
        push    bx ; save BX so it doesn't get destroyed
        mov     ax, word[cluster] ; load AX with the cluster number
        mov     dx, ax
        mov     cx, ax ; let's do the same with DX and CX
        shr     dx, 1 ; divide DX by two
        add     cx, dx ; add DX back to CX so we now have 3/2 of the original cluster
        mov     bx, 0x200 ; move the FAT table into BX
        add     bx, cx ; BX = the new cluster number
        mov     dx, word[bx] ; put the new cluster number in DX
        test    ax, 1 ; tests the last cluster number to see if it is odd or even (all odd numbers have bit 1 enable, hence the test 1)
        jnz     odd_cluster ; the cluster was odd, so let's take care of it
        and     dx, 0fffh ; let's take the low 12 bits of the new cluster (12 bits, get it? FAT12, 12 bits per cluster number!)
        jmp     done ; let's jump over the odd_cluster routine
        shr     dx, 4 ; let's take the high twelve bytes excluding the low 4
        mov     word[cluster], dx ; let's save our new cluster
        cmp     dx, 0ff0h ; the number 0ff0h (or 4080 decimal) marks the end of a cluster chain, meaning we have finished reading the file!
        jb      clusterloop ; was it below 0xff0? If so let's keep reading
        mov     ax, es ; COM files are loaded at offset 0x100, so let's fix the memory (look at the bottom of the article for a better explanation)
        sub     ax, 10h
        mov     es, ax
        mov     ds, ax
        mov     ss, ax
        xor     sp, sp
        push    es
        push    100h
        retf    ; we've loaded our kernel, so let's execute it!
readsectors: ; our loop for reading the disk into RAM
        mov     di, 5 ; let's give it only 5 tries before quitting
        push    ax ; let's save our registers!
        push    bx
        push    cx
        push    dx
        call    lbachs ; we can't use logical sectors, so we must convert is to Cylinder, Head, and Sector
        mov     ah, 02h ; BIOS disk read funtion
        mov     al, 1 ; read only one sector
        mov     dl, 0 ; we're a floppy, so we are the first disk
        mov     dh, [head]
        mov     cl, [sector]
        mov     ch, [track]
        int     13h ; read it!
        jnc     success ; was there an error? If not, jump to the next routine
        xor     ax, ax ; BIOS disk reset function
        int     13h
        pop     dx ; restore the registers
        pop     cx
        pop     bx
        pop     ax
        dec     di ; decrement DI
        jnz     sectloop ; keep going until we reach five
        int     18h ; let's bail
        pop     dx ; restore our registers
        pop     cx
        pop     bx
        pop     ax
        inc     ax ; let's read the next sector
        add     bx, 512 ; there 512 bytes per sector, so let's update BX (our buffer)
        loop    readsectors ; remember, CX = number of sectors to read
        ret ; we read everything without a problem, so let's return
lbachs: ; Sector = LBA MOD SectorsPerTrack (1Cool
        ; Head = (LBA/SectorsPerTrack) MOD HeadsPerCylinder (2)
        ; Track(/Cylinder) = (LBA/SectorsPerTracl) / HeadsPerCylinder (2)
        pusha ; shortcut to save all registers
        xor   dx, dx ; since this is fixed point math, all remainders of divisions are stored in DX, so let's zero DX
        mov   cx, 18 ; SectorsPerTrack
        div   cx ; divide AX (LBA) by the
        inc   dl ; in reality the number of the first sector is 0, but BIOS funtion only accepts 1 as the first sector
        mov   byte[sector], dl ; save our sector
        mov   cx, 2 ; HeadsPerCylinder
        xor   dx, dx ; let's zero out DX again
        div   cx
        mov   byte[head], dl ; let's save the rest
        mov   byte[track], al
        popa ; restore our registers
        ret ; return
cluster dw  0 ; variable for our current cluster
sector  db  0 ; variables for CHS
head    db  0
track   db  0
filen   db  'KERNEL  COM' ; 11 byte file name, CAPITALIZED! 8 bytes for the file name, 3 bytes for the extension
times 510-($-$$) db 0 ; fill in the remaining space with zero
dw 0xaa55 ; legacy boot signature
;Here is a quick lesson in how segmented memory works.
;The layout goes like this SEGMENT:OFFSET. Basically, each segment is a place in memory and the offset is a more detailed spot.
;Now you may have realized that I used segment 7c0h rather than 7c00h. Now here is why, each segment is 0x10 (or 16 decimal) times as large as a single offset;
;therefore, the same location can be accessed at both 07c0:0000 or 0000:7c00. Now this also brings us to the explanation for the COM file execution. I subtracted
;0x10 from the segment, so this equal to setting the offset to 0x100. Then all the segments were set to the new segment along with the stack.
;Now here is map of how the FAT12 system is laid out
;|BootSector| FAT table 1| FAT table 2 | Root Directory| Data Region    |
;|512 bytes | 4,608 bytes| 4,608 bytes |  7,168 bytes  | 1,457,664 bytes|
;|1 sector  | 9 sectors  | 9 sectors   |  14 sectors   |  2,847 sectors |
;Here is an explanation of what clusters are.
;A cluster is simply the number (-2) of sectors after the Root Directory the data is located at.
;So it is really just a sector number. So then you ask, "If it's only one sector, then why don't they just call it a sector?".
;The answer to that question is that other larger FAT systems (FAT16, FAT32) use clusters that can larger than 1 sector at a time;
;therefore, we call it a cluster just because it is universal.
;Now here is a breif explantion of how the FAT12 table works.
;In the FAT12 system, the FAT table is comprised of numbers containing the next cluster
;in a file's chain. That's simple enough, now it's interpretting those numbers where it
;becomes confusing. The first cluster available on a disk is 2. So let's say our directory
;entry says it's first cluster is 2. OK, let's go read our table. To retrieve the number of our next cluster,
;we get 3/2 of our current cluster. So let's get 3/2 of 2 and now we have 3. Now we get the word at that offset
;in the FAT table. Good, now we are close to having the next sector! All that's left is to test and see if is was odd (since 3/2 of an odd number is a fraction).
;To do that we use the TEST instruction. Testing AX (assuming that AX contains the last cluster) with 1 tests bit 1 to see if it is enabled. If bit 1 is enabled,
;the number is odd otherwise it is even. To adjust the cluster if it is odd, we take the high 12 bits of the word. To do this we shift the new cluster over 4 places
;(1111111111110000b becomes 0000111111111111b). The instruction SHR means shift right, so shifting right four times is equivalent to dividing by 16 (2^4, this unrelated, but
;interesting). OK, so what if it's even? Then we take the low 12 bits. To do this we use the AND instruction as a filter. In the code I use 0xfff to filter it (0xfff =
; 000011111111111b). So now that we have the correct cluster, we repeat the previous steps with the new cluster until you reach 0ff0h, which means the end of a file's
;cluster chain.
Post 23 Jun 2011, 07:34
View user's profile Send private message Reply with quote
Display posts from previous:
Post new topic Reply to topic

Jump to:  

< Last Thread | Next Thread >
Forum Rules:
You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot vote in polls in this forum
You cannot attach files in this forum
You can download files in this forum

Copyright © 1999-2020, Tomasz Grysztar. Also on GitHub, YouTube, Twitter.

Website powered by rwasa.