; Copyright (c) 2008-2009, diamond
; All rights reserved.
;
; Redistribution and use in source and binary forms, with or without
; modification, are permitted provided that the following conditions are met:
;       * Redistributions of source code must retain the above copyright
;       notice, this list of conditions and the following disclaimer.
;       * Redistributions in binary form must reproduce the above copyright
;       notice, this list of conditions and the following disclaimer in the
;       documentation and/or other materials provided with the distribution.
;       * Neither the name of the <organization> nor the
;       names of its contributors may be used to endorse or promote products
;       derived from this software without specific prior written permission.
;
; THIS SOFTWARE IS PROVIDED BY Alexey Teplov aka <Lrz> ''AS IS'' AND ANY
; EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
; WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
; DISCLAIMED. IN NO EVENT SHALL <copyright holder> BE LIABLE FOR ANY
; DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
; (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
; ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
; (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
; SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
;*****************************************************************************

        org     0x7E00
; the KordOS FAT32 bootsector loads first cluster of this file to 0:7E00 and transfers control to here
; ss:bp = 0:7C00
; ds = 0
virtual at bp
                rb      3       ; BS_jmpBoot
                rb      8       ; BS_OEMName, ignored
                dw      ?       ; BPB_BytsPerSec
BPB_SecsPerClus db      ?
BPB_RsvdSecCnt  dw      ?
BPB_NumFATs     db      ?
BPB_RootEntCnt  dw      ?
                dw      ?       ; BPB_TotSec16
                db      ?       ; BPB_Media
                dw      ?       ; BPB_FATSz16 = 0 for FAT32
BPB_SecPerTrk   dw      ?
BPB_NumHeads    dw      ?
BPB_HiddSec     dd      ?
                dd      ?       ; BPB_TotSec32
BPB_FATSz32     dd      ?
BPB_ExtFlags    dw      ?
                dw      ?       ; BPB_FSVer
BPB_RootClus    dd      ?
filesize:
                dw      ?       ; BPB_FSInfo
                dw      ?       ; BPB_BkBootSec
                rb      12      ; BPB_Reserved
BS_DrvNum       db      ?
                db      ?       ; BS_Reserved1
                db      ?       ; BS_BootSig
                dd      ?       ; BS_VolID
;               rb      11      ; BS_VolLab
;               rb      5       ; BS_FilSysType, first 5 bytes
read_sectors32  dw      ?
read_sectors2   dw      ?
err_             dw      ?
noloader        dw      ?
cachelimit      dw      ?
fatcachehead    rw      2
fatcacheend     dw      ?
                rb      3       ; BS_FilSysType, last 3 bytes
curseg          dw      ?
num_sectors     dd      ?
cur_cluster     dd      ?
next_cluster    dd      ?
flags           dw      ?
cur_delta       dd      ?
end virtual

; procedures from boot sector
; LBA version
lba_read_sectors2 = 7CD6h
lba_err = 7CAAh
lba_noloader = 7CA7h    ; = lba_err - 3
; CHS version
chs_read_sectors2 = 7CD2h
chs_err = 7CA6h
chs_noloader = 7CA3h    ; = chs_err - 3

        push    eax cx          ; save our position on disk
; determine version of bootsector (LBA vs CHS)
        mov     [read_sectors2], chs_read_sectors2
        mov     bx, chs_err
        mov     [err_], bx
;       mov     [noloader], chs_noloader
        cmp     byte [bx], 0xE8         ; [chs_err] = 0xE8 for CHS version, 0x14 for LBA version
        jz      @f
        add     [read_sectors2], lba_read_sectors2 - chs_read_sectors2
        add     [err_], lba_err - chs_err
;       mov     [noloader], lba_noloader
@@:
        xor     bx, bx
; determine size of cache for folders
        int     12h             ; ax = size of available base memory in Kb
        sub     ax, 92000h / 1024
        jae     @f
nomem:
        mov     si, nomem_str
        jmp     [err_]
@@:
        shr     ax, 3
        mov     [cachelimit], ax        ; size of cache - 1
        mov     es, bx
; no folders in cache yet
        mov     di, foldcache_clus
        mov     cx, 8*4/2 + 1
        xor     ax, ax
        rep     stosw
; bootsector code caches one FAT sector, [bp-14], in 6000:0000
; initialize our (more advanced) FAT caching from this
        mov     di, 8400h
        mov     cx, di
        lea     si, [fatcachehead]
        mov     [si], si                ; no sectors in cache:
        mov     [si+2], si              ; 'prev' & 'next' links point to self
        mov     [fatcacheend], di       ; first free item = 8400h
        stosw                   ; 'next cached sector' link
        stosw                   ; 'prev cached sector' link
        mov     eax, [bp-14]
        stosd                           ; first sector number in cache
        test    eax, eax
        js      @f
        mov     [si], cx                ; 'first cached sector' link = 8400h
        mov     [si+2], cx              ; 'next cached sector' link = 8400h
        mov     [fatcacheend], di       ; first free item = 8406h
@@:
; if cluster = sector, we need to read second part of our file
; (bootsector loads only first cluster of kordldr.f32)
        pop     cx eax          ; restore our position on disk
        cmp     cx, 1
        ja      kordldr_full
        sub     eax, [bp-10]
        inc     eax
        inc     eax             ; eax = first cluster of kordldr.f32
        call    get_next_cluster
        jc      @f
;       jmp     [noloader]
        mov     ax, [err_]
        sub     ax, 3
        jmp     ax
@@:
        dec     eax
        dec     eax
        push    0x800
        pop     es
        call    [read_sectors2]
kordldr_full:
; bootsector code has read some data of root directory to 8000:0000
; initialize our folder caching from this
        mov     eax, [BPB_RootClus]
        mov     [foldcache_clus], eax
        mov     cx, [curseg]
        mov     ax, 8000h
        sub     cx, ax          ; cx = size of data read in paragraphs (0x10 bytes)
        shr     cx, 1           ; cx = size of folder data read in entries (0x20 bytes)
        mov     [foldcache_size], cx
        shl     cx, 4
        push    ds
        mov     ds, ax
        push    0x9000
        pop     es
        xor     si, si
        xor     di, di
        rep     movsw
        pop     ds
; ...continue loading...
        mov     di, secondary_loader_info
        call    load_file
        test    bx, bx
        mov     bx, [err_]
        jz      @f
        mov     si, aKernelNotFound
        jmp     bx
@@:
; for subsequent calls to callback function, hook error handler
; push hooked_err / ret
        mov     dword [bx], 0x68 + (hooked_err shl 8) + (0xC3 shl 24)
; set registers for secondary loader
        mov     ah, [bp-2]              ; drive id
        mov     al, 'f'
        btr     ax, 15
        jnc     @f
        mov     al, 'h'
@@:
        mov     bx, '32'
        mov     si, callback
        jmp     far [si+secondary_loader_info-callback]

nomem_str       db      'No memory',0

cluster2sector:
        sub     eax, 2
clustersz2sectorsz:
        movzx   ecx, [BPB_SecsPerClus]
        mul     ecx
        ret

get_next_cluster:
; in: eax = cluster
; out: if there is next cluster: CF=1, eax = next cluster
; out: if there is no next cluster: CF=0
        push    di bx
        push    ds
        push    ss
        pop     ds
        push    ax
        shr     eax, 7
; eax = FAT sector number; look in cache
        mov     di, 8400h
.cache_lookup:
        cmp     di, [fatcacheend]
        jae     .not_in_cache
        scasd
        scasd
        jnz     .cache_lookup
.in_cache:
        sub     di, 8
; delete this sector from the list
        push    si
        mov     si, [di]
        mov     bx, [di+2]
        mov     [si+2], bx
        mov     [bx], si
        pop     si
        jmp     @f
.not_in_cache:
; cache miss
; cache is full?
        mov     di, [fatcacheend]
        cmp     di, 8C00h
        jnz     .cache_not_full
; yes, delete the oldest entry
        mov     di, [fatcachehead]
        mov     bx, [di]
        mov     [fatcachehead], bx
        push    word [di+2]
        pop     word [bx+2]
        jmp     .cache_append
.cache_not_full:
; no, allocate new sector
        add     [fatcacheend], 8
.cache_append:
; read FAT
        mov     [di+4], eax
        push    es
        pushad
        lea     cx, [di + 0x10000 - 0x8400 + (0x6000 shr (9-3))]        ; +0x10000 - for FASM
        shl     cx, 9-3
        mov     es, cx
        xor     bx, bx
        mov     cx, 1
        add     eax, [bp-6]     ; FAT start
        sub     eax, [bp-10]
        call    [read_sectors2]
        popad
        pop     es
@@:
; add new sector to the end of list
        mov     bx, di
        xchg    bx, [fatcachehead+2]
        push    word [bx]
        pop     word [di]
        mov     [bx], di
        mov     [di+2], bx
; get requested item
        lea     ax, [di + 0x10000 - 0x8400 + (0x6000 shr (9-3))]
        pop     di
        and     di, 0x7F
        shl     di, 2
        shl     ax, 9-3
        mov     ds, ax
        and     byte [di+3], 0x0F
        mov     eax, [di]
        pop     ds
        pop     bx di
        ;and    eax, 0x0FFFFFFF
        cmp     eax, 0x0FFFFFF7
        ret

if $ > 0x8000
error 'get_next_cluster must fit in first sector of kordldr.f32!'
end if

load_file:
; in: ss:bp = 0:7C00
; in: ds:di -> information structure
;       dw:dw   address
;       dw      limit in 4Kb blocks (0x1000 bytes) (must be non-zero and not greater than 0x100)
;       ASCIIZ  name
; out: bx = status: bx=0 - ok, bx=1 - file is too big, only part of file was loaded, bx=2 - file not found
; out: dx:ax = file size (0xFFFFFFFF if file not found)
        mov     eax, [BPB_RootClus]     ; start from root directory
        or      dword [filesize], -1    ; initialize file size with invalid value
        lea     si, [di+6]
parse_dir_loop:
; convert name to FAT name
        push    di
        push    ax
        push    ss
        pop     es
; convert ASCIIZ filename to FAT name
filename equ bp
        mov     di, filename
        push    di
        mov     cx, 8+3
        mov     al, ' '
        rep     stosb
        pop     di
        mov     cl, 8   ; 8 symbols per name
        mov     bl, 1
nameloop:
        lodsb
        test    al, al
        jz      namedone
        cmp     al, '/'
        jz      namedone
        cmp     al, '.'
        jz      namedot
        dec     cx
        js      badname
        cmp     al, 'a'
        jb      @f
        cmp     al, 'z'
        ja      @f
        sub     al, 'a'-'A'
@@:
        stosb
        jmp     nameloop
namedot:
        inc     bx
        jp      badname
        add     di, cx
        mov     cl, 3
        jmp     nameloop
badname:        ; do not make direct js/jp to notfound_pop:
                ; this generates long forms of conditional jumps and results in longer code
        jmp     notfound_pop
namedone:
; scan directory
        pop     ax      ; eax = cluster of directory
                        ; high word of eax is preserved by operations above
        push    ds
        push    si
; read a folder sector-by-sector and scan
; first, try to use the cache
        push    ss
        pop     ds
        mov     di, foldcache_mark
        xor     bx, bx
        mov     cx, [cachelimit]
@@:
        lea     si, [di+bx]
        mov     edx, dword [foldcache_clus+si-foldcache_mark+bx]
        cmp     edx, eax
        jz      cacheok
        test    edx, edx
        jz      cacheadd        ; the cache has place for new entry
        inc     bx
        inc     bx
        dec     cx
        jns     @b
; the folder is not present in the cache, so add it
; the cache is full; find the oldest entry and replace it with the new one
        mov     bx, -2
        mov     dx, [cachelimit]
@@:
        inc     bx
        inc     bx
        cmp     word [di+bx], dx        ; marks have values 0 through [cachelimit]
        jnz     @b
        lea     si, [di+bx]
cacheadd:
        or      word [di+bx], 0xFFFF    ; very big value, it will be changed soon
        and     [foldcache_size+di-foldcache_mark+bx], 0        ; no folder items yet
        mov     dword [foldcache_clus+si-foldcache_mark+bx], eax
cacheok:
; update cache marks
        mov     dx, [di+bx]
        mov     cx, [foldcache_size+di-foldcache_mark+bx]
        mov     di, [cachelimit]
        add     di, di
cacheupdate:
        cmp     [foldcache_mark+di], dx
        adc     [foldcache_mark+di], 0
        dec     di
        dec     di
        jns     cacheupdate
        and     [foldcache_mark+bx], 0
; done, bx contains (position in cache)*2
        ;mov    dx, bx
        ;shl    dx, 8           ; dx = (position in cache)*0x2000/0x10
        ;add    dx, 0x9000
        lea     dx, [bx + 0x90]
        xchg    dl, dh
        mov     ds, dx
        mov     si, filename    ; ss:si -> filename in FAT style
        call    scan_for_filename
        jz      lookup_done
; cache miss, read folder data from disk
        mov     bx, cx
        shr     bx, 4
        shl     cx, 5
        mov     di, cx          ; es:di -> free space in cache entry
; external loop: scan clusters
folder_next_cluster:
; internal loop: scan sectors in cluster
        push    eax
        call    cluster2sector
folder_next_sector:
; skip first bx sectors
        dec     bx
        jns     folder_skip_sector
        push    cx
        push    es di
        push    0x8000
        pop     es
        xor     bx, bx
        mov     cx, 1
        push    es
        push    eax
        call    [read_sectors2]
        pop     eax
; copy data to the cache...
        pop     ds
        pop     di es
        cmp     di, 0x2000      ; ...if there is free space, of course
        jae     @f
        pusha
        mov     cx, 0x100
        xor     si, si
        rep     movsw
        mov     di, es
        shr     di, 8
        add     [ss:foldcache_size+di-0x90], 0x10       ; 0x10 new entries in the cache
        popa
@@:
        push    es
        mov     cl, 0x10        ; ch=0 at this point
        call    scan_for_filename
        pop     es
        pop     cx
        jz      lookup_done_pop
folder_skip_sector:
        inc     eax
        loop    folder_next_sector
        pop     eax     ; eax = current cluster
        call    get_next_cluster
        jc      folder_next_cluster
        stc
        push    eax
lookup_done_pop:
        pop     eax
lookup_done:
        pop     si
; CF=1 <=> failed
        jnc     found
        pop     ds
notfound:
        pop     di
notfound2:
        mov     bx, 2   ; file not found
        mov     ax, 0xFFFF
        mov     dx, ax  ; invalid file size
        ret
notfound_pop:
        pop     ax
        jmp     notfound
found:
        mov     eax, [di+20-2]
        mov     edx, [di+28]
        mov     ax, [di+26]     ; get cluster
        test    byte [di+11], 10h       ; directory?
        pop     ds
        pop     di
        jz      regular_file
        cmp     byte [si-1], 0
        jz      notfound2       ; don't read directories as regular files
; ok, we have found a directory and the caller requested a file into it
        jmp     parse_dir_loop  ; restart with new cluster in ax
regular_file:
        cmp     byte [si-1], 0
        jnz     notfound2       ; file does not contain another files
; ok, we have found a regular file and the caller requested it
; save file size
        mov     [filesize], edx
        mov     si, [di+4]      ; [ds:di+4] = limit in 4K blocks
        shl     si, 3
        push    si
        les     bx, [di]        ; es:bx -> buffer
clusloop:
; eax = first cluster, top of stack contains limit in sectors
        mov     esi, eax        ; remember current cluster
        xor     ecx, ecx        ; ecx will contain number of consecutive clusters
        mov     [cur_delta], ecx
        mov     edi, eax
clusfind:
        inc     edi
        inc     ecx
        call    get_next_cluster
        jnc     clusread
        cmp     eax, edi
        jz      clusfind
        stc
clusread:
        pop     di      ; limit in sectors
        movzx   edi, di
        push    eax     ; save next cluster
        pushf           ; save flags
; read cx clusters, starting from si
; calculate number of sectors
        xchg    eax, ecx
        call    clustersz2sectorsz
        mov     [num_sectors], eax
        jmp     @f
continue_load_file:
        les     bx, [di]        ; es:bx -> buffer
        movzx   edi, word [di+4]        ; di = limit in 4K blocks
        shl     di, 3   ; now di = limit in sectors
        mov     eax, [num_sectors]
        mov     esi, [cur_cluster]
        push    [next_cluster]
        push    [flags]
        test    eax, eax
        jz      nextclus
@@:
; eax = number of sectors; compare with limit
        cmp     eax, edi
        seta    dl
        push    dx      ; limit was exceeded?
        jbe     @f
        mov     eax, edi
@@:
        sub     di, ax  ; calculate new limit
        sub     [num_sectors], eax
        mov     [cur_cluster], esi
; calculate starting sector
        push    ax
        xchg    eax, esi
        call    cluster2sector
        pop     cx
        add     eax, [cur_delta]
        add     [cur_delta], ecx
; read
        call    [read_sectors2]
        pop     dx
; next cluster?
nextclus:
        popf
        pop     eax
        mov     [next_cluster], eax
        pushf
        pop     [flags]
        jnc     @f      ; no next cluster => return
        mov     dl, 1   ; dh=0 in any case
        test    di, di
        jz      @f      ; if there is next cluster but current limit is 0 => return: limit exceeded
        push    di
        jmp     clusloop        ; all is ok, continue
hooked_err:
        mov     sp, 7C00h-14-2  ; restore stack
        mov     dx, 3           ; return: read error
@@:
        mov     bx, dx
        mov     ax, [filesize]
        mov     dx, [filesize+2]
        ret

scan_for_filename:
; in: ss:si -> 11-bytes FAT name
; in: ds:0 -> part of directory data
; in: cx = number of entries
; in: bh = 0
; out: if found: CF=0, ZF=1, es:di -> directory entry
; out: if not found, but continue required: CF=1 and ZF=0
; out: if not found and zero item reached: CF=1 and ZF=1
        push    ds
        pop     es
        xor     di, di
        push    cx
        jcxz    snoent
sloop:
        cmp     byte [di], bh
        jz      snotfound
        test    byte [di+11], 8         ; volume label?
        jnz     scont                   ; ignore volume labels
        pusha
        mov     cx, 11
        repz    cmps byte [ss:si], byte [es:di]
        popa
        jz      sdone
scont:
        add     di, 0x20
        loop    sloop
snoent:
        inc     cx      ; clear ZF flag
snotfound:
        stc
sdone:
        pop     cx
lrdret:
        ret

; Callback function for secondary loader
callback:
; in: ax = function number; only functions 1 and 2 are defined for now
; save caller's stack
        mov     dx, ss
        mov     cx, sp
; set our stack (required because we need ss=0)
        xor     si, si
        mov     ss, si
        mov     sp, 7C00h-10
        mov     bp, 7C00h
        push    dx
        push    cx
; call our function
        stc     ; unsupported function
        dec     ax
        jz      callback_readfile
        dec     ax
        jnz     callback_ret
; function 2: continue loading file
; can be called only after function 1 returned value bx=1 (only part of file was loaded)
; in: ds:di -> information structure
;       dw:dw   address
;       dw      limit in 4Kb blocks (0x1000 bytes) (must be non-zero and not greater than 0x100)
; out: bx=0 - ok, bx=1 - still only part of file was loaded, bx=3 - read error
; out: dx:ax = file size
        call    continue_load_file
        jmp     callback_ret_succ
callback_readfile:
; function 1: read file
; in: ds:di -> information structure
;       dw:dw   address
;       dw      limit in 4Kb blocks (0x1000 bytes) (must be non-zero and not greater than 0x100)
;       ASCIIZ  name
; out: bx=0 - ok, bx=1 - file is too big, only part of file was loaded, bx=2 - file not found, bx=3 - read error
; out: dx:ax = file size (0xFFFFFFFF if file was not found)
        call    load_file
callback_ret_succ:
        clc     ; function is supported
callback_ret:
; restore caller's stack
        pop     cx
        pop     ss
        mov     sp, cx
; return to caller
        retf

secondary_loader_info:
        dw      0, 0x1000
        dw      0x30000 / 0x1000
        db      'kord/loader',0
aKernelNotFound db      'Fatal error: cannot load the secondary loader',0

;if $ > 0x8200
;error 'total size of kordldr.f32 must not exceed 1024 bytes!'
;end if

;foldcache_clus dd      0,0,0,0,0,0,0,0 ; start with no folders in cache
;foldcache_mark dw      0
;               rw      7
;foldcache_size rw      8
foldcache_clus  rd      8
foldcache_mark  rw      8
foldcache_size  rw      8
