; ==================================================================
; The Mike Operating System bootloader
; Copyright (C) 2006 - 2013 MikeOS Developers -- see doc/LICENSE.TXT
;
; Based on a free boot loader by E Dehling. It scans the FAT12
; floppy for KERNEL.BIN, loads it and executes it.
; This must grow no larger than 512 bytes (one sector), with the final
; two bytes being the boot signature (AA55h). Note that in FAT12,
; a cluster is the same as a sector: 512 bytes.
; ==================================================================
; To test this program install qemu (sudo apt-get install qemu-system)
; In this program kernel.bin from http://mikeos.sourceforge.net/ is used
bits 16
jmp short bootloader_start ; Jump past disk description section
nop ; Pad out before disk description
; ------------------------------------------------------------------
; Disk description table, to make it a valid floppy
; Note: some of these values are hard-coded in the source!
; Values are those used by IBM for 1.44 MB, 3.5" diskette
OEMLabel: db "BOOTDISK" ;Disk label
BytesPerSector: dw 512 ;Bytes per sector
SectorsPerCluster: db 1 ;Sectors per cluster
ReservedForBoot: dw 1 ;Reserved sectors for boot record
NumberOfFats: db 2 ;Number of copies of the FAT
RootDirEntries: dw 224 ;Number of entries in root dir
;(224 * 32 = 7168 = 14 sectors to read)
LogicalSectors: dw 2880 ;Number of logical sectors
MediumByte: db 0F0h ;Medium descriptor byte
SectorsPerFat: dw 9 ;Sectors per FAT
SectorsPerTrack: dw 18 ;Sectors per track (36/cylinder)
Sides: dw 2 ;Number of sides/heads
HiddenSectors: dd 0 ;Number of hidden sectors
LargeSectors: dd 0 ;Number of LBA sectors
DriveNo: dw 0 ;Drive No: 0
Signature: db 41 ;Drive signature: 41 for floppy
VolumeID: dd 00000000h ;Volume ID: any number
VolumeLabel: db "BOOTDISK ";Volume Label: any 11 chars
FileSystem: db "FAT12 " ;File system type: don't change!
; ------------------------------------------------------------------
; Main bootloader code
bootloader_start:
mov ax, 07C0h ; Set up 4K of stack space above buffer
add ax, 544 ; 8k buffer = 512 paragraphs + 32 paragraphs (loader)
cli ; Disable interrupts while changing stack
mov ss, ax
mov sp, 4096
sti ; Restore interrupts
mov ax, 07C0h ; Set data segment to where we're loaded
mov ds, ax
; NOTE: A few early BIOSes are reported to improperly set DL
cmp dl, 0
je no_change
mov [bootdev], dl ; Save boot device number
mov ah, 8 ; Get drive parameters
int 13h
jc fatal_disk_error
and cx, 3Fh ; Maximum sector number
mov [SectorsPerTrack], cx ; Sector numbers start at 1
movzx dx, dh ; Maximum head number
add dx, 1 ; Head numbers start at 0 - add 1 for total
mov [Sides], dx
no_change:
mov eax, 0 ; Needed for some older BIOSes
; First, we need to load the root directory from the disk. Technical details:
; Start of root = ReservedForBoot + NumberOfFats * SectorsPerFat = logical 19
; Number of root = RootDirEntries * 32 bytes/entry / 512 bytes/sector = 14
; Start of user data = (start of root) + (number of root) = logical 33
floppy_ok: ; Ready to read first block of data
mov ax, 19 ; Root dir starts at logical sector 19
call l2hts
mov si, buffer ; Set ES:BX to point to our buffer (see end of code)
mov bx, ds
mov es, bx
mov bx, si
mov ah, 2 ; Params for int 13h: read floppy sectors
mov al, 14 ; And read 14 of them
pusha ; Prepare to enter loop
read_root_dir:
popa ; In case registers are altered by int 13h
pusha
stc ; A few BIOSes do not set properly on error
int 13h ; Read sectors using BIOS
jnc search_dir ; If read went OK, skip ahead
call reset_floppy ; Otherwise, reset floppy controller and try again
jnc read_root_dir ; Floppy reset OK?
jmp reboot ; If not, fatal double error
search_dir:
popa
mov ax, ds ; Root dir is now in [buffer]
mov es, ax ; Set DI to this info
mov di, buffer
mov cx, word [RootDirEntries] ; Search all (224) entries
mov ax, 0 ; Searching at offset 0
next_root_entry:
xchg cx, dx ; We use CX in the inner loop...
mov si, kern_filename ; Start searching for kernel filename
mov cx, 11
rep cmpsb
je found_file_to_load ; Pointer DI will be at offset 11
add ax, 32 ; Bump searched entries by 1 (32 bytes per entry)
mov di, buffer ; Point to next entry
add di, ax
xchg dx, cx ; Get the original CX back
loop next_root_entry
mov si, file_not_found ; If kernel is not found, bail out
call print_string
jmp reboot
found_file_to_load: ; Fetch cluster and load FAT into RAM
mov ax, word [es:di+0Fh] ; Offset 11 + 15 = 26, contains 1st cluster
mov word [cluster], ax
mov ax, 1 ; Sector 1 = first sector of first FAT
call l2hts
mov di, buffer ; ES:BX points to our buffer
mov bx, di
mov ah, 2 ; int 13h params: read (FAT) sectors
mov al, 9 ; All 9 sectors of 1st FAT
pusha ; Prepare to enter loop
read_fat:
popa ; In case registers are altered by int 13h
pusha
stc
int 13h ; Read sectors using the BIOS
jnc read_fat_ok ; If read went OK, skip ahead
call reset_floppy ; Otherwise, reset floppy controller and try again
jnc read_fat ; Floppy reset OK?
; ******************************************************************
fatal_disk_error:
; ******************************************************************
mov si, disk_error ; If not, print error message and reboot
call print_string
jmp reboot ; Fatal double error
read_fat_ok:
popa
mov ax, 2000h ; Segment where we'll load the kernel
mov es, ax
mov bx, 0
mov ah, 2 ; int 13h floppy read params
mov al, 1
push ax ; Save in case we (or int calls) lose it
; Now we must load the FAT from the disk. Here's how we find out where it starts:
; FAT cluster 0 = media descriptor = 0F0h
; FAT cluster 1 = filler cluster = 0FFh
; Cluster start = ((cluster number) - 2) * SectorsPerCluster + (start of user)
; = (cluster number) + 31
load_file_sector:
mov ax, word [cluster] ; Convert sector to logical
add ax, 31
call l2hts ; Make appropriate params for int 13h
mov ax, 2000h ; Set buffer past what we've already read
mov es, ax
mov bx, word [pointer]
pop ax ; Save in case we (or int calls) lose it
push ax
stc
int 13h
jnc calculate_next_cluster ; If there's no error...
call reset_floppy ; Otherwise, reset floppy and retry
jmp load_file_sector
; In the FAT, cluster values are stored in 12 bits, so we have to
; do a bit of maths to work out whether we're dealing with a byte
; and 4 bits of the next byte -- or the last 4 bits of one byte
; and then the subsequent byte!
calculate_next_cluster:
mov ax, [cluster]
mov dx, 0
mov bx, 3
mul bx
mov bx, 2
div bx ; DX = [cluster] mod 2
mov si, buffer
add si, ax ; AX = word in FAT for the 12 bit entry
mov ax, word [ds:si]
or dx, dx ; If DX = 0 [cluster] is even; if DX = 1 then it's odd
jz even ; If [cluster] is even, drop last 4 bits of word
; with next cluster; if odd, drop first 4 bits
odd:
shr ax, 4 ; Shift out first 4 bits (they belong to another entry)
jmp short next_cluster_cont
even:
and ax, 0FFFh ; Mask out final 4 bits
next_cluster_cont:
mov word [cluster], ax ; Store cluster
cmp ax, 0FF8h ; FF8h = end of file marker in FAT12
jae end
add word [pointer], 512 ; Increase buffer pointer 1 sector length
jmp load_file_sector
end: ; We've got the file to load!
pop ax ; Clean up the stack (AX was pushed earlier)
mov dl, byte [bootdev] ; Provide kernel with boot device info
jmp 2000h:0000h ; Jump to entry point of loaded kernel!
; ------------------------------------------------------------------
; BOOTLOADER SUBROUTINES
reboot:
mov ax, 0
int 16h ; Wait for keystroke
mov ax, 0
int 19h ; Reboot the system
print_string: ; Output string in SI to screen
pusha
mov ah, 0Eh ; int 10h teletype function
.repeat:
lodsb ; Get char from string
cmp al, 0
je .done ; If char is zero, end of string
int 10h ; Otherwise, print it
jmp short .repeat
.done:
popa
ret
reset_floppy: ; IN: [bootdev] = boot device; OUT: carry set on error
push ax
push dx
mov ax, 0
mov dl, byte [bootdev]
stc
int 13h
pop dx
pop ax
ret
l2hts: ; Calculate head, track and sector settings for int 13h
; IN: logical sector in AX, OUT: correct registers for int 13h
push bx
push ax
mov bx, ax ; Save logical sector
mov dx, 0 ; First the sector
div word [SectorsPerTrack]
add dl, 01h ; Physical sectors start at 1
mov cl, dl ; Sectors belong in CL for int 13h
mov ax, bx
mov dx, 0 ; Now calculate the head
div word [SectorsPerTrack]
mov dx, 0
div word [Sides]
mov dh, dl ; Head/side
mov ch, al ; Track
pop ax
pop bx
mov dl, byte [bootdev] ; Set correct device
ret
; ------------------------------------------------------------------
; STRINGS AND VARIABLES
kern_filename db "KERNEL BIN" ;kernel filename
disk_error db "Floppy error! Press any key...", 0
file_not_found db "KERNEL.BIN not found!", 0
bootdev db 0 ; Boot device number
cluster dw 0 ; Cluster of the file we want to load
pointer dw 0 ; Pointer into Buffer, for loading kernel
; ------------------------------------------------------------------
; END OF BOOT SECTOR AND BUFFER START
times 510-($-$$) db 0 ; Pad remainder of boot sector with zeros
dw 0AA55h ; Boot signature (DO NOT CHANGE!)
buffer: ; Disk buffer begins (8k after this, stack starts)
; ==================================================================
kernel.asm
;name: kernel.asm
;
;description: This is the "kernel" file which will be loaded by the bootloader.
; For the sake of simplicity it just displays a message on the screen.
; This "kernel" can on the other hand start memory management routines, loads
; a command prompt like DOS does or switch to protected mode or long mode for
; 64 bit operations. To make a long story short: here the main work starts.
;
;build: see makefile
bits 16
kernel:
cli ;disable interrupts
xor ax,ax
mov ss,ax ;set stack segment and pointer
mov sp,0xFFFF
sti ;enable interrupts
cld
mov ax,0x2000 ;set all segments to match where kernel is loaded
mov ds,ax ;after this, we don't need to bother with
mov es,ax ;segments ever again,as its programs
mov fs,ax ;live entirely in 64K
mov gs,ax
mov si,welcome
pusha
mov ah,0x0E ;int 10h teletype function
.repeat:
lodsb ;get char from string
and al,al
jz .done ;if char is zero, end of string
int 0x10 ;otherwise, print it
jmp .repeat ;and move on to next char
.done:
popa
.loop:
hlt
jmp .loop
welcome: db 'Kernel example by Agguro - Version 1.0.0.0', 10, 13, 0
makefile
# Use of makefile
# make : build bootloader64 and bootloader16.lst
# for a quick view you can use
# qemu-system-x86_64 -hda bootloader64 -k nl-be -vga std (you can use cirrus instead of std too)
# as mentioned on the developers website.
# make clean : is called with make run to be sure to delete the floppy and iso image otherwise make results in an error
# make again : to rebuild the program, even if the source isn't changed, can be run after makefile modifications
# make dump : to create a hexdump of the different builded files
# make images : build the images
# make run : (re-)build all files except dumps and simulate with qemu-x86_64 the iso image
# make tar : make a tar.gz file
# make all : make programs and images
.PHONY : clean
.PHONY : again
.PHONY : dump
.PHONY : run
.PHONY : images
.PHONY : tar
.PHONY : all
NAME=bootloader16
TARGET=bin
AS=nasm
LD=ld
O = .o
ASM = .asm
INC = .inc
LST = .lst
DUMP = .dump
OBJS = $(NAME)$(O)
$(NAME).o : $(NAME).asm
$(AS) -f $(TARGET) -o $(NAME).$(TARGET) $(NAME)$(ASM) -l $(NAME)$(LST)
nasm -fbin kernel.asm -o kernel.bin
clean:
@rm -rf *$(O) *~ *$(LST) *$(DUMP) $(NAME)
@rm -rf floppy/
@rm -rf iso/
@rm -rf dumps/
@rm -rf tmp-loop/
@rm -rf *$(TARGET)
again:
touch $(NAME)$(ASM)
make
dump:
@rm -rf dumps/
mkdir dumps
make again
hexdump -C $(NAME).$(TARGET) > dumps/$(NAME)$(DUMP)
hexdump -C floppy/$(NAME).flp > dumps/$(NAME).flp$(DUMP)
hexdump -C iso/$(NAME).iso > dumps/$(NAME).iso$(DUMP)
images:
make again
rm -rf floppy/
rm -rf iso/
rm -rf tmp-loop/
mkdir floppy
mkdir iso
mkdosfs -C floppy/$(NAME).flp 1440
dd status=noxfer conv=notrunc count=1 if=$(NAME).$(TARGET) of=floppy/$(NAME).flp
mkdir tmp-loop
sudo mount -o loop -t vfat floppy/bootloader16.flp tmp-loop
sudo cp kernel.bin tmp-loop/
sleep 0.2
sudo umount tmp-loop
rm -rf tmp-loop/
mkisofs -quiet -V 'AGGURO' -input-charset iso8859-1 -o iso/$(NAME).iso -b $(NAME).flp floppy/
run:
make images
qemu-system-i386 -cdrom iso/$(NAME).iso -k nl-be -vga std
tar:
make clean
tar czf ../$(NAME).tar.gz ../$(NAME)/
all:
make again
make images
make dump