
; Memory layout for the FreeDOS FAT32 single stage boot process:
;
;	...
;	|-------| 1FE0h:7E00h = 27C00h (159 KiB)
;	|BOOTSEC| loader relocates itself here first thing,
;	|RELOC.	|  before loading root directory/FAT/kernel file
;	|-------| 1FE0h:7C00h = 27A00h (158 KiB)
;	| STACK | below relocated loader, above FAT sector (size 22 KiB)
;	...
;	|-------| 2200h:2000h = 24000h (144 KiB)
;	|  FAT  | (only 1 sector buffered, maximum sector size 8 KiB)
;	|-------| 2200h:0000h = 22000h (136 KiB)
;	...
;	|-------| 0000h:7E00h = 07E00h (31.5 KiB)
;	|BOOTSEC| overwritten by the kernel, so the
;	|ORIGIN | bootsector relocates itself up...
;	|-------| 0000h:7C00h = 07C00h (31 KiB)
;	...
;	|-------|
;	|KERNEL	| maximum size 128 KiB (overwrites bootsec origin)
;	|LOADED	| (holds 1 sector directory buffer before kernel file load)
;	|-------| 0060h:0000h = 00600h (1.5 KiB)
;	...
; The kernel load segment may be patched using the SYS /L switch.
;  We support values between 0x60 and 0x200 here, with file size
;  of up to 128 KiB (rounded to cluster size). Default is 0x60.

;%define MULTI_SEC_READ  1

                ; NOTE: sys must be updated if magic offsets change
%assign ISFAT1216DUAL 0
	%include "magic.mac"


segment	.text

%define BASE            0x7c00

                org     BASE

Entry:          jmp     short real_start
		nop

;       bp is initialized to 7c00h
%define bsOemName       bp+0x03      ; OEM label
%define bsBytesPerSec   bp+0x0b      ; bytes/sector
%define bsSecPerClust   bp+0x0d      ; sectors/allocation unit
%define bsResSectors    bp+0x0e      ; # reserved sectors
%define bsFATs          bp+0x10      ; # of fats
%define bsRootDirEnts   bp+0x11      ; # of root dir entries
%define bsSectors       bp+0x13      ; # sectors total in image
%define bsMedia         bp+0x15      ; media descrip: fd=2side9sec, etc...
%define sectPerFat      bp+0x16      ; # sectors in a fat
%define sectPerTrack    bp+0x18      ; # sectors/track
%define nHeads          bp+0x1a      ; # heads
%define nHidden         bp+0x1c      ; # hidden sectors
%define nSectorHuge     bp+0x20      ; # sectors if > 65536
%define xsectPerFat     bp+0x24      ; Sectors/Fat
%define xrootClst       bp+0x2c      ; Starting cluster of root directory
%define drive           bp+0x40      ; Drive number

		times   52h - ($ - $$) db 0
		; The filesystem ID is used by lDOS's instsect (by ecm)
		;  by default to validate that the filesystem matches.
		db "FAT32"
		times   5Ah - ($ - $$) db 32


%define LOADSEG         0x0060

%define FATSEG          0x2200

%define fat_sector      bp+0x48         ; last accessed sector of the FAT

%define loadsegoff_60	bp+loadseg_off-Entry ; FAR pointer = 60:0
%define loadseg_60	bp+loadseg_seg-Entry

%define fat_start       bp+0x5e         ; first FAT sector
%define data_start      bp+0x62         ; first data sector
%define fat_secmask     bp+0x66		; number of clusters in a FAT sector - 1
%define fat_secshift    bp+0x68         ; fat_secmask+1 = 2^fat_secshift

;-----------------------------------------------------------------------
;   ENTRY
;-----------------------------------------------------------------------

real_start:     cld
		cli
                sub	ax, ax
		mov	ds, ax
                mov     bp, 0x7c00

		mov	ax, 0x1FE0
		mov	es, ax
		mov	si, bp
		mov	di, bp
		mov	cx, 0x0100
		rep	movsw           ; move boot code to the 0x1FE0:0x0000
		jmp     word 0x1FE0:cont

loadseg_off	dw	0
	magicoffset "loadseg", 78h
loadseg_seg	dw	LOADSEG

cont:           mov     ds, ax
                mov     ss, ax
                lea     sp, [bp-0x20]
		sti
	magicoffset "set unit", 82h
                mov     [drive], dl     ; BIOS passes drive number in DL

;                call    print
;                db      "Loading ",0

;      Calc Params
;      Fat_Start
		mov	si, word [nHidden]
		mov	di, word [nHidden+2]
		add	si, word [bsResSectors]
		adc	di, byte 0

		mov	word [fat_start], si
		mov	word [fat_start+2], di
 ;	Data_Start
		mov	al, [bsFATs]
		cbw
		push	ax
		mul	word [xsectPerFat+2]
		add	di, ax
		pop	ax
		mul	word [xsectPerFat]
		add	ax, si
		adc	dx, di
		mov	word[data_start], ax
		mov	word[data_start+2], dx
;      fat_secmask
		mov	ax, word[bsBytesPerSec]
		shr	ax, 1
		shr	ax, 1
		dec	ax
		mov	word [fat_secmask], ax
;      fat_secshift
; cx = temp
; ax = fat_secshift
		xchg	ax, cx ; cx = 0 after movsw
		inc	cx
secshift:	inc	ax
		shr	cx, 1
		cmp	cx, 1
		jne	secshift
		mov	word [fat_secshift], ax
		dec	cx
 
;       FINDFILE: Searches for the file in the root directory.
;
;       Returns:
;            DX:AX = first cluster of file

                mov     word [fat_sector], cx           ; CX is 0 after "dec"
                mov     word [fat_sector + 2], cx

                mov     ax, word [xrootClst]
                mov     dx, word [xrootClst + 2]
ff_next_cluster:
                push    dx                              ; save cluster
                push    ax
                call    convert_cluster
                jc      boot_error                      ; EOC encountered
                                
ff_next_sector:
                push    bx                              ; save sector count

                les     bx, [loadsegoff_60]
                call    readDisk
                push    dx                              ; save sector
                push    ax

                mov     ax, [bsBytesPerSec]

		; Search for KERNEL.SYS file name, and find start cluster.
ff_next_entry:  mov     cx, 11
                mov     si, filename
                mov     di, ax
                sub     di, 0x20
                repe    cmpsb
                jz      ff_done

                sub     ax, 0x20
                jnz     ff_next_entry
                pop     ax                      ; restore  sector
                pop     dx
                pop     bx                      ; restore sector count
                dec     bx
                jnz     ff_next_sector
ff_find_next_cluster:
                pop     ax                      ; restore current cluster
                pop     dx
                call    next_cluster
                jmp     short ff_next_cluster
ff_done:
                
                mov     ax, [es:di+0x1A-11]        ; get cluster number
                mov     dx, [es:di+0x14-11]
c4:
                sub     bx, bx                  ; ES points to LOADSEG      
c5:             push    dx
                push    ax
                push    bx
                call    convert_cluster
                jc      boot_success
                mov     di, bx
                pop     bx
c6:
                call    readDisk
                dec     di
                jnz     c6
                pop     ax
                pop     dx
                call    next_cluster
                jmp     short c5
                
boot_error:
		mov ax, 0E00h | '!'
		int 10h				; display the error sign
		mov ax, 0E07h
		int 10h				; beep
		xor	ah,ah
		int	0x16			; wait for a key
		int	0x19			; reboot the machine

; input: 
;    DX:AX - cluster
; output:
;    DX:AX - next cluster
;    CX = 0
; modify:
;    DI
next_cluster:  
                push    es
                mov     di, ax
                and     di, [fat_secmask]
                
                mov     cx, [fat_secshift]
cn_loop:
                shr     dx,1
                rcr     ax,1
                loop    cn_loop                ; DX:AX fat sector where our
                                               ; cluster resides
                                               ; DI - cluster index in this
                                               ; sector
                                               
                shl     di,1                   ; DI - offset in the sector
                shl     di,1
                add     ax, [fat_start]
                adc     dx, [fat_start+2]      ; DX:AX absolute fat sector

                push    bx
                mov     bx, FATSEG
                mov     es, bx
                sub     bx, bx

                cmp     ax, [fat_sector]
                jne     cn1                    ; if the last fat sector we
                                               ; read was this, than skip
                cmp     dx,[fat_sector+2]
                je      cn_exit
cn1:
                mov     [fat_sector],ax        ; save the fat sector number,
                mov     [fat_sector+2],dx      ; we are going to read
                call    readDisk
cn_exit:
                pop     bx
                mov     ax, [es:di]             ; DX:AX - next cluster
                mov     dx, [es:di + 2]         ;
                pop     es
                ret


boot_success:
		mov dl, [drive]			; for Enhanced DR-DOS load
		mov bl, dl			; for FreeDOS load
		jmp	far [loadsegoff_60]

; Convert cluster to the absolute sector
;input:
;    DX:AX - target cluster
;output:
;    DX:AX - absoulute sector
;    BX - [bsSectPerClust]
;modify:
;    CX
convert_cluster:
                cmp     dx,0x0fff
                jne     c3
                cmp     ax,0xfff8
                jb      c3              ; if cluster is EOC (carry is set), do ret
                stc
                ret
c3:
                mov     cx, dx          ; sector = (cluster - 2)*clussize +
                                        ; + data_start
                sub     ax, 2
                sbb     cx, byte 0           ; CX:AX == cluster - 2
                mov     bl, [bsSecPerClust]
                dec     bx              ; bl = spc - 1, 0FFh if 256 spc
                sub     bh, bh          ; bx = spc - 1
                inc     bx              ; bx = spc
                xchg    cx, ax          ; AX:CX == cluster - 2
                mul     bx              ; first handle high word
                                        ; DX must be 0 here
                xchg    ax, cx          ; then low word
                mul     bx
                add     dx, cx                          ; DX:AX target sector
                add     ax, [data_start]
                adc     dx, [data_start + 2]
                ret

%if 0
; prints text after call to this function.

print_1char:        
                xor   bx, bx                   ; video page 0
                mov   ah, 0x0E                 ; else print it
                int   0x10                     ; via TTY mode
print:          pop   si                       ; this is the first character
print1:         lodsb                          ; get token
                push  si                       ; stack up potential return address
                cmp   al, 0                    ; end of string?
                jne   print_1char              ; until done
                ret                            ; and jump to it
%endif

;input:
;   DX:AX - 32-bit DOS sector number
;   ES:BX - destination buffer
;output:
;   ES:BX points one byte after the last byte read.
;   DX:AX - next sector
;modify:
;   ES if DI * bsBytesPerSec >= 65536, CX

readDisk:
read_next:      push    dx
                push    ax
                ;
                ; translate sector number to BIOS parameters
                ;

                ;
                ; abs = sector                          offset in track
                ;     + head * sectPerTrack             offset in cylinder
                ;     + track * sectPerTrack * nHeads   offset in platter
                ;
                xchg    ax, cx
                mov     al, [sectPerTrack]
                mul     byte [nHeads]
                xchg    ax, cx
                ; cx = nHeads * sectPerTrack <= 255*63
                ; dx:ax = abs
                div     cx
                ; ax = track, dx = sector + head * sectPertrack
                xchg    ax, dx
                ; dx = track, ax = sector + head * sectPertrack
                div     byte [sectPerTrack]
                ; dx =  track, al = head, ah = sector
                mov     cx, dx
                ; cx =  track, al = head, ah = sector

                ; the following manipulations are necessary in order to
                ; properly place parameters into registers.
                ; ch = cylinder number low 8 bits
                ; cl = 7-6: cylinder high two bits
                ;      5-0: sector
                mov     dh, al                  ; save head into dh for bios
                xchg    ch, cl                  ; set cyl no low 8 bits
                ror     cl, 1                   ; move track high bits into
                ror     cl, 1                   ; bits 7-6 (assumes top = 0)
                inc     ah                      ; sector offset from 1
                or      cl, ah                  ; merge sector into cylinder

                mov     ax, 0x0201
                mov     dl, [drive]
                int     0x13

                pop     ax
                pop     dx         
                jnc     read_ok                 ; jump if no error
                xor     ah, ah                  ; else, reset floppy
                int     0x13
                jmp     short read_next
read_ok:
                add     bx, word [bsBytesPerSec]

                jnc     no_incr_es              ; if overflow...

                mov     cx, es
                add     ch, 0x10                ; ...add 1000h to ES
                mov     es, cx

no_incr_es:
		inc ax
		jnz .no_carry
		inc dx
.no_carry:
                ret

       times   0x01f1-$+$$ db 0

	magicoffset "kernel name", 1F1h
filename        db      "KERNEL  SYS",0,0

sign            dw      0xAA55
