fat.inc
kolibrios-nextgen_kolibrios-kernel/kernel/sec_loader/trunk/boot/after_win/fat.inc
; 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.
;*****************************************************************************

; in: ss:bp = 0:dat
; in: es:bx = address to load file
; in: ds:si -> ASCIIZ name
; in: cx = limit in sectors
; out: bx = status: bx=0 - ok, bx=1 - file is too big, only part of file has been loaded, bx=2 - file not found
; out: dx:ax = file size (0xFFFFFFFF if file not found)
load_file_fat:
        mov     eax, [bp + root_clus - dat]
        mov     [bp + cur_obj - dat], root_string
        push    es
        push    bx
        push    cx
.parse_dir_loop:
; convert name to FAT name
        push    [bp + cur_obj - dat]
        push    ax
        mov     [bp + cur_obj - dat], si
        push    ss
        pop     es
; convert ASCIIZ filename to FAT name
        mov     di, fat_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:
        mov     si, badname_msg
        jmp     find_error_si
.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     bx, -2
        mov     cx, [bp + rootcache_size - dat]
        cmp     [bp + root_clus - dat], eax
        jz      .lookcache_root
        mov     di, foldcache_mark
        xor     bx, bx
        mov     cx, [bp + cachelimit - dat]
@@:
        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
        js      @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, [bp + cachelimit - dat]
@@:
        inc     bx
        inc     bx
        cmp     word [di+bx], dx        ; marks have values 0 through [cachelimit]
        jnz     @b
.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
        lea     si, [di+bx]
        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, [bp + cachelimit - dat]
        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
.lookcache_root:
; bx = (position in cache)*2 for non-root folders; bx = -2 for root folder
        ;mov    dx, bx
        ;shl    dx, 8
        ;add    dx, 0x9200
        lea     dx, [bx + 0x92]
        xchg    dl, dh
        mov     ds, dx
        mov     si, fat_filename        ; ss:si -> filename in FAT style
        call    fat_scan_for_filename
        jz      .lookup_done
; cache miss, read folder data from disk
; we are reading parent directory, it can result in disk read errors; restore [cur_obj]
        mov     di, sp
        mov     bx, [bp + cur_obj - dat]
        xchg    bx, [ss:di+4]
        mov     [bp + cur_obj - dat], bx
        mov     bx, cx
        add     bx, 0xF
        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
        movzx   ecx, byte [ss:0x320D]   ; BPB_SecPerClus
        push    eax
; FAT12/16 root - special handling
        test    eax, eax
        jnz     .folder_notroot
        mov     cx, [ss:0x3211]         ; BPB_RootEntCnt
        mov     dx, cx
        add     cx, 0xF
        rcr     cx, 1
        shr     cx, 3
        mov     eax, [bp + root_start - dat]
        jmp     .folder_next_sector
.folder_notroot:
        mul     ecx
        add     eax, [bp + data_start - dat]
.folder_next_sector:
        sub     dx, 0x10
; 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
        call    read
        jc      ..found_disk_error
; 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
        cmp     di, 0x90
        jz      .update_rootcache_size
        add     [ss:foldcache_size+di-0x92], 0x10       ; 0x10 new entries in the cache
        jmp     .updated_cachesize
.update_rootcache_size:
        mov     cl, 0x10
        cmp     cx, dx
        jb      @f
        mov     cx, dx
@@:
        add     [bp + rootcache_size - dat], cx
.updated_cachesize:
        popa
@@:
        push    es
        mov     cl, 0x10        ; ch=0 at this point
        cmp     cx, dx
        jb      @f
        mov     cx, dx
@@:
        call    fat_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
        test    eax, eax
        jz      @f
        call    [bp + get_next_cluster_ptr - dat]
        jc      .folder_next_cluster
@@:
        stc
        push    eax
.lookup_done_pop:
        pop     eax
.lookup_done:
        pop     si
; CF=1 <=> failed
        jnc     .found
        pop     ds
        pop     [bp + cur_obj - dat]
        mov     si, error_not_found
        jmp     find_error_si
.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     [bp + cur_obj - dat]    ; forget old [cur_obj]
        jz      .regular_file
        cmp     byte [si-1], 0
        jnz     .parse_dir_loop
..directory_error:
        mov     si, directory_string
        jmp     find_error_si
.regular_file:
        cmp     byte [si-1], 0
        jz      @f
..notdir_error:
        mov     si, notdir_string
        jmp     find_error_si
@@:
; ok, we have found a regular file and the caller requested it
; parse FAT chunk
        push    ss
        pop     es
        push    ss
        pop     ds
        mov     di, 0x4005
        mov     byte [di-5], 1  ; non-resident attribute
        mov     dword [di-4], 1
        stosd
        pop     cx
        push    cx
.parsefat:
        call    [bp + get_next_cluster_ptr - dat]
        jnc     .done
        mov     esi, [di-8]
        add     esi, [di-4]
        cmp     eax, esi
        jz      .contc
        mov     dword [di], 1
        scasd
        stosd
        jmp     @f
.contc:
        inc     dword [di-8]
@@:
        sub     cl, [0x320D]
        sbb     ch, 0
        ja      .parsefat
.done:
        xor     eax, eax
        stosd
        mov     si, 0x4000
load_file_common_end:
        xor     ecx, ecx
        pop     cx
        pop     bx
        pop     es
        mov     [bp + filesize - dat], edx
        mov     [bp + sectors_read - dat], ecx
        add     edx, 0x1FF
        shr     edx, 9
        mov     [bp + filesize_sectors - dat], edx
        cmp     edx, ecx
        seta    al
        mov     ah, 0
        push    ax
        call    read_file_chunk
continue_load_common_end:
        mov     [bp + cur_chunk_ptr - dat], si
        pop     bx
        mov     ax, word [bp + filesize - dat]
        mov     dx, word [bp + filesize+2 - dat]
        jnc     @f
        mov     bl, 3   ; read error
@@:
        ret

continue_load_file:
; es:bx -> buffer for output, ecx = cx = number of sectors
        mov     si, [bp + cur_chunk_ptr - dat]
        push    ecx
        add     ecx, [bp + sectors_read - dat]
        mov     [bp + sectors_read - dat], ecx
        cmp     [bp + filesize_sectors - dat], ecx
        pop     ecx
        seta    al
        mov     ah, 0
        push    ax
        push    continue_load_common_end
        push    ss
        pop     ds
        cmp     [bp + cur_chunk_resident - dat], ah
        jnz     .nonresident
.resident:
        mov     ax, word [bp + num_sectors - dat]
        jmp     read_file_chunk.resident.continue
.nonresident:
        mov     eax, [bp + cur_cluster - dat]
        mov     edx, [bp + num_sectors - dat]
        add     eax, [bp + cur_delta - dat]
        jmp     read_file_chunk.nonresident.continue

fat_scan_for_filename:
; in: ss:si -> 11-bytes FAT name
; in: ds:0 -> part of directory data
; in: cx = number of entries
; 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    .noent
.loop:
        cmp     byte [di], 0
        jz      .notfound
        test    byte [di+11], 8         ; volume label?
        jnz     .cont                   ; ignore volume labels
        pusha
        mov     cx, 11
        repz cmps byte [ss:si], byte [es:di]
        popa
        jz      .done
.cont:
        add     di, 0x20
        loop    .loop
.noent:
        inc     cx      ; clear ZF flag
.notfound:
        stc
.done:
        pop     cx
        ret

fat12_get_next_cluster:
; in: ax = cluster (high word of eax is zero)
; out: if there is next cluster: CF=1, ax = next cluster
; out: if there is no next cluster: CF=0
        push    si
        push    ds
        push    0x6000
        pop     ds
        mov     si, ax
        shr     si, 1
        add     si, ax
        test    al, 1
        lodsw
        jz      @f
        shr     ax, 4
@@:
        and     ax, 0xFFF
        cmp     ax, 0xFF7
        pop     ds si
        ret

fat16_get_next_cluster:
; in: ax = cluster (high word of eax is zero)
; out: if there is next cluster: CF=1, ax = next cluster
; out: if there is no next cluster: CF=0
; each sector contains 200h bytes = 100h FAT entries
; so ah = # of sector, al = offset in sector
        push    si
        mov     si, ax
        shr     si, 8
; calculate segment for this sector of FAT table
; base for FAT table is 6000:0000, so the sector #si has to be loaded to (60000 + 200*si)
; segment = 6000 + 20*si, offset = 0
        push    es
        push    si
        shl     si, 5
        add     si, 0x6000
        mov     es, si
        pop     si
        cmp     byte [ss:0x3400+si], 0  ; sector already loaded?
        jnz     .noread
; load corresponding sector, try all FATs if disk read error detected
        pusha
        movzx   di, byte [ss:0x3210]    ; BPB_NumFATs
        xor     bx, bx
        mov     ax, [ss:0x320E]         ; BPB_RsvdSecCnt
        xor     dx, dx
        add     ax, si
        adc     dx, bx
@@:
        push    es
        push    dx ax
        pop     eax
        mov     cx, 1   ; read 1 sector
        call    read
        pop     es
        jnc     @f
        add     ax, [ss:0x3216]         ; BPB_FATSz16
        adc     dx, bx
        dec     di
        jnz     @b
..found_disk_error:
        mov     si, disk_error_msg
        jmp     find_error_si
@@:
        popa
.noread:
        mov     si, ax
        and     si, 0xFF
        add     si, si
        mov     ax, [es:si]
        pop     es
        cmp     ax, 0xFFF7
        pop     si
        ret

fat32_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
        push    ax
        shr     eax, 7
; eax = FAT sector number; look in cache
        push    si
        mov     si, cache1head
        call    cache_lookup
        pop     si
        jnc     .noread
; read FAT, try all FATs if disk read error detected
        push    es
        pushad
        movzx   edx, word [ss:0x320E]   ; BPB_RsvdSecCnt
        add     eax, edx
        movzx   si, byte [ss:0x3210]    ; BPB_NumFATs
@@:
        lea     cx, [di - 0x3400 + (0x6000 shr (9-3))]
        shl     cx, 9-3
        mov     es, cx
        xor     bx, bx
        mov     cx, 1
        call    read
        jnc     @f
        add     eax, [ss:0x3224]        ; BPB_FATSz32
        dec     si
        jnz     @b
        jmp     ..found_disk_error
@@:
        popad
        pop     es
.noread:
; get requested item
        lea     ax, [di - 0x3400 + (0x6000 shr (9-3))]
        pop     di
        and     di, 0x7F
        shl     di, 2
        shl     ax, 9-3
        push    ds
        mov     ds, ax
        and     byte [di+3], 0x0F
        mov     eax, [di]
        pop     ds
        pop     di
        ;and    eax, 0x0FFFFFFF
        cmp     eax, 0x0FFFFFF7
        ret
