flat assembler
Message board for the users of flat assembler.

Index > OS Construction > Case Study: FAT12 bootloader

Author
Thread Post new topic Reply to topic
bitshifter



Joined: 04 Dec 2007
Posts: 796
Location: Massachusetts, USA
bitshifter 31 Jul 2010, 04:02
Here is a free FAT12 bootloader designed and written by me.
It will find/load/run kernel from root of 3.5" 1.44mb floppy.
Kernel is loaded and executed at segment 0050h, offset 0.
I provide a sample kernel to verify some important registers.
Any questions/comments (or size optimizations) are welcome.

Edit: New memory map (boot code updated)

Memory map:

0000:0000 - Interrupt vector table (1024 bytes)
0040:0000 - Bios communication area (256 bytes)
0050:0000 - Kernel load address (30464 bytes)
07c0:0000 - Boot load address (512 bytes)
07e0:0000 - Disk load buffer (8192 bytes)
09e0:0000 - System stack (8192 bytes)
0be0:0000 - Free memory (xxx bytes)
a000:0000 - Graphics video memory (65535 bytes)
b800:0000 - Text video memory (65535 bytes)

Boot:
Code:
use16
org 7c00h

        jmp     after_data
        nop

                    db 'MSWIN4.1'       ;oem disk label (8 bytes)

                    dw 512              ;bytes per sector
                    db 1                ;sectors per cluster
                    dw 1                ;reserved sectors (1 for boot code)
                    db 2                ;number of file allocation tables
                    dw 224              ;number of root directory entries
                    dw 2880             ;number of logical sectors
                    db 0f0h             ;media descriptor byte
                    dw 9                ;9 sectors per FAT
_sectors_per_track  dw 18               ;18 sectors per track (36/cylinder)
_heads_per_cylinder dw 2                ;2 heads per cylinder
                    dd 0                ;no hidden sectors
                    dd 0                ;no large sectors

_drive_number       db 0                ;drive number (for drive A)
                    db 0                ;reserved (current head)
                    db 29h              ;drive signature (29h for floppy)
                    dd 0                ;volume ID (serial number)
                    db 'NONAME     '    ;volume label (11 bytes)
                    db 'FAT12   '       ;file system type (8 bytes)

_file_name          db 'SYSTEM  BIN'    ;file name (11 bytes)

after_data:

        jmp     0:purge_code

purge_code:

        cld

        mov     ax,cs
        mov     ds,ax
        mov     es,ax

        mov     [ds:_drive_number],dl

        cli
        mov     ss,ax
        mov     sp,system_stack
        sti

        mov     al,14
        mov     bx,disk_buffer
       ;ES:BX -> near buffer
        mov     cx,2
        mov     dh,1
        call    read_sectors

        mov     si,_file_name
        mov     di,bx;disk_buffer
        mov     cx,224

parse_root:

        pusha
        mov     cx,11
        rep     cmpsb
        popa

        je      load_fat
        add     di,32
        loop    parse_root

        mov     al,'F'

fatal_error:

        mov     ah,0eh
        xor     bh,bh
        int     10h

        xor     ah,ah
        int     16h

        int     18h

read_sectors:
        ;       AL = number of sectors to read
        ;       ES:BX -> destination buffer
        ;       CX,DH = cylinder/head/sector
        pusha
        mov     dl,[ds:_drive_number]
        mov     si,5            ; attempts to read
.read:  mov     ah,02h          ; BIOS read sectors function
        stc
        int     13h
        jnc     .done
        dec     si
        jnc     .reset
        mov     al,'R'
        jmp     fatal_error
.reset: mov     di,5            ; attempts to reset
.retry: xor     ah,ah           ; BIOS reset disk function
        stc
        int     13h
        jnc     .read
        dec     di
        jnc     .retry
        mov     al,'D'
        jmp     fatal_error
.done:  popa
        ret                     ; never returns if failure

load_fat:

        mov     si,[es:di+26]

        mov     al,9
       ;ES:BX -> near buffer
        mov     cx,2
        xor     dx,dx
        call    read_sectors

        mov     ax,0050h
        mov     es,ax
        mov     fs,ax
        mov     gs,ax

        xor     bx,bx
       ;ES:BX -> far buffer

load_file:

        mov     ax,si
        add     ax,31

        xor     dx,dx
        div     word[ds:_sectors_per_track]
        inc     dx
        mov     cl,dl
        xor     dx,dx
        div     word[ds:_heads_per_cylinder]
        mov     ch,al
        mov     dh,dl

        mov     al,1
       ;ES:BX -> far buffer
        call    read_sectors
        add     bx,512

        mov     ax,si
        mov     cx,3
        mul     cx
        dec     cx
        div     cx

        mov     di,ax
        mov     si,[ds:di+disk_buffer]

        or      dx,dx
        jz      even_cluster

        shr     si,4
even_cluster:
        and     si,0fffh
        cmp     si,0ff8h
        jl      load_file

        mov     dl,[ds:_drive_number]

        push    es
        pop     ds

        push    es
        push    0
        retf

rb 510-($-$$)
dw 0aa55h

disk_buffer:
rb 8192

rb 8192
system_stack:
    

Kernel:
Code:
macro PRINT_HEX16 _r {
        mov     word[g_format_string],`_r
        mov     dx,_r
        call    format_hex
}

use16
org 0

        jmp     start
        rb      4096 ; ensure long files are loaded normally

start:

        PRINT_HEX16 cs
        PRINT_HEX16 ds
        PRINT_HEX16 es
        PRINT_HEX16 fs
        PRINT_HEX16 gs
        PRINT_HEX16 ss
        PRINT_HEX16 sp

        jmp     $

format_hex:
        ; DX = value
        mov     cx,4
        mov     di,g_format_string+5
.fmt:   rol     dx,4
        mov     al,dl
        and     al,0x0f
        cmp     al,0x0a
        sbb     al,0x69
        das
        stosb
        loop    .fmt
        mov     si,g_format_string
.get:   lodsb
        test    al,al
        jz      .eos
        mov     ah,0eh
        int     10h
        jmp     .get
.eos:   ret

g_format_string:
        db '?? 0x????',13,10,0
    

_________________
Coding a 3D game engine with fasm is like trying to eat an elephant,
you just have to keep focused and take it one 'byte' at a time.


Last edited by bitshifter on 15 Jan 2011, 10:03; edited 4 times in total
Post 31 Jul 2010, 04:02
View user's profile Send private message Reply with quote
bitshifter



Joined: 04 Dec 2007
Posts: 796
Location: Massachusetts, USA
bitshifter 31 Jul 2010, 06:01
I am working on new memory map...
Soon kernel will have 64kb max size.
Also stack will have 64kb free space.
Post 31 Jul 2010, 06:01
View user's profile Send private message Reply with quote
egos



Joined: 10 Feb 2009
Posts: 144
egos 31 Jul 2010, 20:50
If you place stack before loader and loader before kernel you can use all remainder continuous space for loading kernel. But don't forget about EBDA. You would use int 12h to get available memory. Here is the memory map from my boot spec (I'm using linear addressing in RM):

00000: IVT
00400: BDA
00500: used by BIOS?
00600: stack for loader/maybe some buffers
07C00: first sector of bootblock
07E00: additional sector of bootblock/data/unused
08000: kernel (its size is multiple of 1K)
XXXXX aligned on 1K boundary: additional module (boot device and FS driver)
XXXXX: free
XXXXX aligned on 1K boundary: EBDA
A0000: video RAM
C0000: video ROM
etc.

When starting kernel it usually moves 0 into ss and 8000h into sp and then jumps to 0:next_instruction.
Post 31 Jul 2010, 20:50
View user's profile Send private message Reply with quote
SeproMan



Joined: 11 Oct 2009
Posts: 70
Location: Belgium
SeproMan 01 Aug 2010, 20:27
Some observations about your code:

  • _drive_number is NOT a word. It's a byte followed by a reserved byte!
  • _heads_per_cylinder is confusing. Better use just _heads
  • Whether the drive signature is 29h has nothing to do with a floppy or not
  • Luckily those 14 and 9 sectors are all on the same track. Else it would not work!
  • Make sure that the direction flag is clear before using string primitives
  • Better use REPE CMPSB instead of trusting default behaviour of any assembler
  • Your "0eh shl 4" must shift left 8 times to get AH=0Eh
  • The BIOS Teletype also needs at least BH=0 (BX=0007h)
  • Why not load 256 bytes higher in memory? Address 0500h does contain 1 BIOS variable.
  • For clarity don't be afraid to use expressions like ADD AX,1+9+9+14-2
  • Why do you STC before doing INT 13h ?
  • Your "read_sectors" routine will NEVER return with the carry set! It will try to load the sectors indefinitely because you put a limit of 5 tries only on the reset function!


To optimize your code:

  • Drop DS: and ES: everywhere
  • Use CWD instead of XOR DX,DX before that division, it safes 1 byte
  • Don't XOR DX,DX before MUL CX, it's useless
  • Because you're working on a floppy you can byte-divide by the number of heads thus saving the XOR DX,DX
  • You don't really need the JMP 0:purge_code
  • INC DL can safely be shortened with INC DX
  • By NOT calling routine "int13" you could save some bytes


One final suggestion. Take a look at the bootloader I posted today...

_________________
Real Address Mode.
Post 01 Aug 2010, 20:27
View user's profile Send private message Reply with quote
bitshifter



Joined: 04 Dec 2007
Posts: 796
Location: Massachusetts, USA
bitshifter 02 Aug 2010, 00:59
Thanks for the reply...

> _drive_number is NOT a word. It's a byte followed by a reserved byte!

I will seperate them for clarity...

> _heads_per_cylinder is confusing. Better use just _heads

Really?

> Whether the drive signature is 29h has nothing to do with a floppy or not

Hmm, i read that in the FAT12 specs...

> Luckily those 14 and 9 sectors are all on the same track. Else it would not work!

I took certain liberties for simplicity Razz

> Make sure that the direction flag is clear before using string primitives

Would the BIOS ever leave me with dirty FLAGS register?
Anyway it probably a good idea to use it at least once.

> Better use REPE CMPSB instead of trusting default behaviour of any assembler

What? and Where?

> Your "0eh shl 4" must shift left 8 times to get AH=0Eh

Oops, thats surely my mistake...

> The BIOS Teletype also needs at least BH=0 (BX=0007h)

Correct, BH = the page number, BL only used in graphics modes.

> Why not load 256 bytes higher in memory? Address 0500h does contain 1 BIOS variable.

I will have to check a bit further into this...

> For clarity don't be afraid to use expressions like ADD AX,1+9+9+14-2

Ok, understood.

> Why do you STC before doing INT 13h?

Some BIOS dont correctly set carry flag on failure.

> Your "read_sectors" routine will NEVER return with the carry set! It will try to load the sectors indefinitely because you put a limit of 5 tries only on the reset function!

Yep, i left the comment, but will remove it...


To optimize your code:



> Drop DS: and ES: everywhere

Im ot sure how/where that will make any difference?

> Use CWD instead of XOR DX,DX before that division, it safes 1 byte

Ok, cool

> Don't XOR DX,DX before MUL CX, it's useless

There is a div instruction a little further down uses it.

> Because you're working on a floppy you can byte-divide by the number of heads thus saving the XOR DX,DX

How you mean that?

> You don't really need the JMP 0:purge_code

Its to make sure we have valid CS:IP

> INC DL can safely be shortened with INC DX

It saves a byte, cool

> By NOT calling routine "int13" you could save some bytes

I wasnt sure if int 13h would trash any registers so i pusha/popa
I guess i can clean that up also.

Well it seems i have some work to do.
Thanks for taking a peek at it.
Post 02 Aug 2010, 00:59
View user's profile Send private message Reply with quote
egos



Joined: 10 Feb 2009
Posts: 144
egos 02 Aug 2010, 02:48
Wow, guys! May you will become my friends? I have written some boot loaders and now I'm working on CD (ElTorito) boot loader.
Post 02 Aug 2010, 02:48
View user's profile Send private message Reply with quote
revolution
When all else fails, read the source


Joined: 24 Aug 2004
Posts: 20299
Location: In your JS exploiting you and your system
revolution 02 Aug 2010, 02:52
bitshifter wrote:
> Don't XOR DX,DX before MUL CX, it's useless

There is a div instruction a little further down uses it.
MUL CX gives the result in DX:AX. So XOR DX,DX is indeed useless.
Post 02 Aug 2010, 02:52
View user's profile Send private message Visit poster's website Reply with quote
egos



Joined: 10 Feb 2009
Posts: 144
egos 02 Aug 2010, 07:04
Floppy is a legacy storage device. But writing code for it is a good practice. Do you know about short format of BPB or about redefinition DPT (diskette parameter table)? I should show my boot loader for floppy. Just please don't say me about hard coded constants and comments. It was written about 10 years ago in my pupilage and was oriented on the 2x80x18 floppies only. It is working fine in our projects a long time. But one my friend (and partner) said me something about checking for last valid dir entry.


Description:
Download
Filename: bootcode.zip
Filesize: 1.52 KB
Downloaded: 406 Time(s)

Post 02 Aug 2010, 07:04
View user's profile Send private message Reply with quote
SeproMan



Joined: 11 Oct 2009
Posts: 70
Location: Belgium
SeproMan 08 Aug 2010, 17:09
Quote:

> Drop DS: and ES: everywhere

Im ot sure how/where that will make any difference?

Each time you use a segment override prefix like DS: or ES: the assembler adds 1 byte to the code. When optimizing for size that counts!


Quote:

> You don't really need the JMP 0:purge_code

Its to make sure we have valid CS:IP

Since your program is up and running you already have a valid CS:IP.
You don't make any specific reference to CS, so the exact value is of no importance.

Quote:

> Because you're working on a floppy you can byte-divide by the number of heads thus saving the XOR DX,DX

How you mean that?

The numbers participating in this division are small. Both the quotient and the remainder fit in a byte. Don't waste DX.

Quote:

> Better use REPE CMPSB instead of trusting default behaviour of any assembler

What? and Where?

You wrote REP CMPSB to compare the 11 character filename in the directory entry.
REPE CMPSB is better because it truly expresses what you intended to achieve. "Repeat WHILE EQUAL Compare String Byte"

_________________
Real Address Mode.
Post 08 Aug 2010, 17:09
View user's profile Send private message Reply with quote
bitshifter



Joined: 04 Dec 2007
Posts: 796
Location: Massachusetts, USA
bitshifter 08 Aug 2010, 20:47
SeproMan wrote:

Each time you use a segment override prefix like DS: or ES: the assembler adds 1 byte to the code. When optimizing for size that counts!

This is only true if you specify a segment other than default.
So removing all those references does nothing to its size.
I put them in there for clarity.

_________________
Coding a 3D game engine with fasm is like trying to eat an elephant,
you just have to keep focused and take it one 'byte' at a time.
Post 08 Aug 2010, 20:47
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-2024, Tomasz Grysztar. Also on GitHub, YouTube.

Website powered by rwasa.