; ==================================================================
; 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