kolibrios/kernel/branches/kolibri-lldw/sec_loader/trunk/boot/cdfs/bootsect.asm
turbocat bdef8f9596 Created a branch for low-level work with disks
git-svn-id: svn://kolibrios.org@9191 a494cfbc-eb01-0410-851d-a64ba20cac60
2021-09-14 17:39:32 +00:00

1025 lines
28 KiB
NASM

; 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.
;*****************************************************************************
jmp far 0:real_start
; special text
org $+0x7C00
real_start:
; initialize
xor ax, ax
mov ss, ax
mov sp, 0x7C00
mov ds, ax
mov es, ax
cld
sti
mov [bootdrive], dl
; check LBA support
mov ah, 41h
mov bx, 55AAh
int 13h
mov si, aNoLBA
jc err_
cmp bx, 0AA55h
jnz err_
test cl, 1
jz err_
; get file system information
; scan for Primary Volume Descriptor
db 66h
push 10h-1
pop eax
pvd_scan_loop:
mov cx, 1
inc eax
mov bx, 0x1000
call read_sectors
jnc @f
fatal_read_err:
mov si, aReadError
err_:
call out_string
mov si, aPressAnyKey
call out_string
xor ax, ax
int 16h
int 18h
jmp $
@@:
push ds
pop es
cmp word [bx+1], 'CD'
jnz pvd_scan_loop
cmp word [bx+3], '00'
jnz pvd_scan_loop
cmp byte [bx+5], '1'
jnz pvd_scan_loop
; we have found ISO9660 descriptor, look for type
cmp byte [bx], 1 ; Primary Volume Descriptor?
jz pvd_found
cmp byte [bx], 0xFF ; Volume Descriptor Set Terminator?
jnz pvd_scan_loop
; Volume Descriptor Set Terminator reached, no PVD found - fatal error
mov si, no_pvd
jmp err_
pvd_found:
add bx, 80h
mov ax, [bx]
mov [lb_size], ax
; calculate number of logical blocks in one sector
mov ax, 800h
cwd
div word [bx]
mov [lb_per_sec], ax
; get location of root directory
mov di, root_location
movzx eax, byte [bx+1Dh]
add eax, [bx+1Eh]
stosd
; get memory size
int 12h
mov si, nomem_str
cmp ax, 71000h / 400h
jb err_
shr ax, 1
sub ax, 60000h / 800h
mov [size_rest], ax
mov [free_ptr], 60000h / 800h
; load path table
; if size > 62K => it's very strange, avoid using it
; if size > (size of cache)/2 => avoid using it too
mov ecx, [bx+4]
cmp ecx, 0x10000 - 0x800
ja nopathtable
shr ax, 1
cmp ax, 0x20
jae @f
shl ax, 11
cmp cx, ax
ja nopathtable
@@:
; size is ok, try to load it
mov [pathtable_size], cx
mov eax, [bx+12]
xor edx, edx
div dword [lb_per_sec]
imul dx, [bx]
mov [pathtable_start], dx
add cx, dx
call cx_to_sectors
xor bx, bx
push 6000h
pop es
call read_sectors
jc nopathtable
; path table has been loaded
inc [use_path_table]
sub [size_rest], cx
add [free_ptr], cx
nopathtable:
; init cache
mov ax, [size_rest]
mov [cache_size], ax
mov ax, [free_ptr]
mov [cache_start], ax
; load secondary loader
mov di, secondary_loader_info
call load_file
test bx, bx
jnz noloader
; set registers for secondary loader
mov ah, [bootdrive]
mov al, 'c'
mov bx, 'is'
mov si, callback
jmp far [si-callback+secondary_loader_info] ; jump to 1000:0000
noloader:
mov si, aKernelNotFound
jmp err_
read_sectors:
; es:bx = pointer to data
; eax = first sector
; cx = number of sectors
pushad
push ds
do_read_sectors:
push ax
push cx
cmp cx, 0x7F
jbe @f
mov cx, 0x7F
@@:
; create disk address packet on the stack
; dq starting LBA
db 66h
push 0
push eax
; dd buffer
push es
push bx
; dw number of blocks to transfer (no more than 0x7F)
push cx
; dw packet size in bytes
push 10h
; issue BIOS call
push ss
pop ds
mov si, sp
mov dl, [cs:bootdrive]
mov ah, 42h
int 13h
jc diskreaderr
; restore stack
add sp, 10h
; increase current sector & buffer; decrease number of sectors
movzx esi, cx
mov ax, es
shl cx, 7
add ax, cx
mov es, ax
pop cx
pop ax
add eax, esi
sub cx, si
jnz do_read_sectors
pop ds
popad
ret
diskreaderr:
add sp, 10h + 2*2
pop ds
popad
stc
out_string.ret:
ret
out_string:
; in: ds:si -> ASCIIZ string
lodsb
test al, al
jz .ret
mov ah, 0Eh
mov bx, 7
int 10h
jmp out_string
aNoLBA db 'The drive does not support LBA!',0
aReadError db 'Read error',0
no_pvd db 'Primary Volume Descriptor not found!',0
nomem_str db 'No memory',0
aPressAnyKey db 13,10,'Press any key...',13,10,0
load_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 = 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)
; parse path to the file
lea si, [di+6]
mov eax, [cs:root_location]
cmp [cs:use_path_table], 0
jz parse_dir
; scan for path in path table
push di
push 6000h
pop es
mov di, [cs:pathtable_start] ; es:di = pointer to current entry in path table
mov dx, 1 ; dx = number of current entry in path table, start from 1
mov cx, [cs:pathtable_size]
pathtable_newparent:
mov bx, dx ; bx = number of current parent in path table: root = 1
scan_path_table_e:
call is_last_component
jnc path_table_scanned
scan_path_table_i:
cmp word [es:di+6], bx
jb .next
ja path_table_notfound
call test_filename1
jc .next
@@:
lodsb
cmp al, '/'
jnz @b
jmp pathtable_newparent
.next:
; go to next entry
inc dx
movzx ax, byte [es:di]
add ax, 8+1
and al, not 1
add di, ax
sub cx, ax
ja scan_path_table_i
path_table_notfound:
pop di
mov ax, -1
mov dx, ax
mov bx, 2 ; file not found
ret
path_table_scanned:
movzx eax, byte [es:di+1]
add eax, [es:di+2]
pop di
parse_dir:
; eax = logical block, ds:di -> information structure, ds:si -> file name
; was the folder already read?
push di ds
push cs
pop ds
mov [cur_desc_end], 2000h
mov bx, cachelist
.scan1:
mov bx, [bx+2]
cmp bx, cachelist
jz .notfound
cmp [bx+4], eax
jnz .scan1
.found:
; yes; delete this item from the list (the following code will append this item to the tail)
mov di, [bx]
push word [bx+2]
pop word [di+2]
mov di, [bx+2]
push word [bx]
pop word [di]
mov di, bx
jmp .scan
.notfound:
; no; load first sector of the folder to get its size
push eax
push si
mov si, 1
call load_phys_sector_for_lb_force
mov bx, si
pop si
pop eax
jnc @f
; read error - return
.readerr:
pop ds
.readerr2:
pop di
mov ax, -1
mov dx, ax
mov bx, 3
ret
@@:
; first item of the folder describes the folder itself
; do not cache too big folders: size < 64K and size <= (total cache size)/2
cmp word [bx+12], 0
jnz .nocache
mov cx, [cache_size] ; cx = cache size in sectors
shr cx, 1 ; cx = (cache size)/2
cmp cx, 0x20
jae @f
shl cx, 11
cmp [bx+10], cx
ja .nocache
@@:
; we want to cache this folder; get space for it
mov cx, [bx+10]
call cx_to_sectors
jnz .yescache
.nocache:
push dword [bx+10]
pop dword [cur_nocache_len]
call lb_to_sector
push ds
pop es
pop ds
.nocache_loop:
push eax
mov dx, 1800h
call scan_for_filename_in_sector
mov cx, dx
pop eax
jnc .j_scandone
sub cx, bx
sub word [es:cur_nocache_len], cx
sbb word [es:cur_nocache_len+2], 0
jb .j_scandone
ja @f
cmp word [es:cur_nocache_len], 0
jz .j_scandone
@@:
mov cx, 1
inc eax
push es
mov bx, 1000h
call read_sectors
pop es
jc .readerr2
jmp .nocache_loop
.j_scandone:
jmp .scandone
.yescache:
push bx
mov bx, [cachelist.head]
.freeloop:
cmp cx, [size_rest]
jbe .sizeok
@@:
; if we are here: there is not enough free space, so we must delete old folders' data
; N.B. We know that after deleting some folders the space will be available (size <= (total cache size)/2).
; one loop iteration: delete data of one folder
pusha
mov dx, [bx+10]
mov es, dx ; es = segment of folder data to be deleted
xor di, di
mov ax, [bx+8]
add ax, 0x7FF
rcr ax, 1
shr ax, 10
push ax
shl ax, 11-4 ; get number of paragraphs in folder data to be deleted
mov cx, [cache_size]
add cx, [cache_start]
push ds
push ax
add ax, dx
mov ds, ax
pop ax
shl cx, 11-4
sub cx, dx ; cx = number of paragraphs to be moved
push si
xor si, si
; move cx paragraphs from ds:si to es:di to get free space in the end of cache
@@:
sub cx, 1000h
jbe @f
push cx
mov cx, 8000h
rep movsw
mov cx, ds
add cx, 1000h
mov ds, cx
mov cx, es
add cx, 1000h
mov es, cx
pop cx
jmp @b
@@:
add cx, 1000h
shl cx, 3
rep movsw
pop si
pop ds
; correct positions in cache for existing items
mov cx, 80h
mov di, 8400h
.correct:
cmp [di+10], dx
jbe @f
sub [di+10], ax
@@:
add di, 12
loop .correct
; some additional space is free now
pop ax
add [size_rest], ax
sub [free_ptr], ax
; add cache item to the list of free items
mov dx, [bx]
mov ax, [free_cache_item]
mov [bx], ax
mov [free_cache_item], bx
mov bx, dx
; current iteration done
popa
jmp .freeloop
.sizeok:
mov [cachelist.head], bx
mov word [bx+2], cachelist
; allocate new item in cache
mov di, [free_cache_item]
test di, di
jz .nofree
push word [di]
pop [free_cache_item]
jmp @f
.nofree:
mov di, [last_cache_item]
add [last_cache_item], 12
@@:
pop bx
push si di
; mov [di+4], eax ; start of folder
scasd
stosd
push ax
mov ax, [free_ptr]
shl ax, 11-4
mov [di+10-8], ax
mov es, ax
pop ax
add [free_ptr], cx
sub [size_rest], cx
; read folder data
; first sector is already in memory, 0000:bx
pusha
mov cx, [bx+10]
mov [di+8-8], cx ; folder size in bytes
mov si, bx
xor di, di
mov cx, 0x1800
sub cx, si
rep movsb
pop ax
push di
popa
; read rest of folder
mov esi, dword [lb_per_sec]
add eax, esi
dec si
not si
and ax, si
mov si, word [bx+10]
mov bx, di
pop di
sub si, bx
jbe @f
mov [cur_limit], esi
call read_many_bytes
pop si
jnc .scan
jmp .readerr
@@:
pop si
.scan:
; now we have required cache item; append it to the end of list
mov bx, [cachelist.tail]
mov [cachelist.tail], di
mov [di+2], bx
mov word [di], cachelist
mov [bx], di
; scan for given filename
mov es, [di+10]
mov dx, [di+8]
pop ds
xor bx, bx
call scan_for_filename_in_sector
.scandone:
push cs
pop es
mov bx, 2000h
cmp bx, [es:cur_desc_end]
jnz filefound
j_notfound:
jmp path_table_notfound
filefound:
@@:
lodsb
test al, al
jz @f
cmp al, '/'
jnz @b
@@:
mov cl, [es:bx+8]
test al, al
jz @f
; parse next component of file name
test cl, 2 ; directory?
jz j_notfound
mov eax, [es:bx]
pop di
jmp parse_dir
@@:
test cl, 2 ; directory?
jnz j_notfound ; do not allow read directories as regular files
; ok, now load the file
pop di
les bx, [di]
call normalize
movzx esi, word [di+4] ; esi = limit in 4K blocks
shl esi, 12 ; esi = limit in bytes
push cs
pop ds
mov [cur_limit], esi
mov di, 2000h
loadloop:
and [cur_start], 0
.loadnew:
mov esi, [cur_limit]
mov eax, [cur_start]
add esi, eax
mov [overflow], 1
sub esi, [di+4]
jb @f
xor esi, esi
dec [overflow]
@@:
add esi, [di+4] ; esi = number of bytes to read
mov [cur_start], esi
sub esi, eax
jz .loadcontinue
xor edx, edx
div dword [lb_size] ; eax = number of logical blocks to skip,
mov [first_byte], dx; [first_byte] = number of bytes to skip in 1st block
cmp byte [di+10], 0
jnz .interleaved
add eax, [di]
; read esi bytes from logical block eax to buffer es:bx
call read_many_bytes.with_first
jc .readerr3
.loadcontinue:
mov [cur_chunk], di
add di, 11
cmp di, [cur_desc_end]
jae @f
cmp [cur_limit], 0
jnz loadloop
@@:
mov bx, [overflow]
.calclen:
; calculate length of file
xor ax, ax
xor dx, dx
mov di, 2000h
@@:
add ax, [di+4]
adc dx, [di+6]
add di, 11
cmp di, [cur_desc_end]
jb @b
ret
.interleaved:
mov [cur_unit_limit], esi
push esi
; skip first blocks
movzx ecx, byte [di+9] ; Unit Size
movzx esi, byte [di+10] ; Interleave Gap
add si, cx
mov edx, [di]
@@:
sub eax, ecx
jb @f
add edx, esi
jmp @b
@@:
add ecx, eax ; ecx = number of logical blocks to skip
lea eax, [ecx+edx] ; eax = first logical block
pop esi
.interleaved_loop:
; get number of bytes in current file unit
push eax
movzx eax, byte [di+9]
sub ax, cx
imul eax, dword [lb_size]
cmp eax, esi
ja .i2
.i1:
xchg esi, eax
.i2:
pop eax
sub [cur_unit_limit], esi
push eax
; read esi bytes from logical block eax to buffer es:bx
call read_many_bytes.with_first
pop eax
jnc @f
.readerr3:
mov bx, 3
jmp .calclen
@@:
mov esi, [cur_unit_limit]
test esi, esi
jz .loadcontinue
movzx ecx, byte [di+9] ; add Unit Size
add cl, byte [di+10] ; add Interleave Gap
adc ch, 0
add eax, ecx
xor cx, cx
mov [first_byte], cx
jmp .interleaved_loop
cx_to_sectors:
add cx, 7FFh
rcr cx, 1
shr cx, 10
ret
is_last_component:
; in: ds:si -> name
; out: CF set <=> current component is not last (=> folder)
push si
@@:
lodsb
test al, al
jz @f
cmp al, '/'
jnz @b
stc
@@:
pop si
ret
test_filename1:
; in: ds:si -> filename, es:di -> path table item
; out: CF set <=> no match
pusha
mov cl, [es:di]
add di, 8
jmp test_filename2.start
test_filename2:
; in: ds:si -> filename, es:bx -> directory item
; out: CF set <=> no match
pusha
mov cl, [es:bx+32]
lea di, [bx+33]
.start:
mov ch, 0
@@:
lodsb
test al, al
jz .test1
cmp al, '/'
jz .test1
call toupper
mov ah, al
mov al, [es:di]
call toupper
inc di
cmp al, ah
loopz @b
jnz .next1
; if we have reached this point: current name is done
lodsb
test al, al
jz .ret
cmp al, '/'
jz .ret
; if we have reached this point: current name is done, but input name continues
; so they do not match
jmp .next1
.test1:
; if we have reached this point: input name is done, but current name continues
; "filename.ext;version" in ISO-9660 represents file "filename.ext"
; "filename." and "filename.;version" are also possible for "filename"
cmp byte [es:di], '.'
jnz @f
inc di
dec cx
jz .ret
@@:
cmp byte [es:di], ';'
jnz .next1
jmp .ret
.next1:
stc
.ret:
popa
ret
toupper:
; in: al=symbol
; out: al=symbol in uppercase
cmp al, 'a'
jb .ret
cmp al, 'z'
ja .ret
sub al, 'a'-'A'
.ret:
ret
scan_for_filename_in_sector:
; in: ds:si->filename, es:bx->folder data, dx=limit
; out: CF=0 if found
push bx
.loope:
push bx
.loop:
cmp bx, dx
jae .notfound
cmp byte [es:bx], 0
jz .loopd
test byte [es:bx+25], 4 ; ignore files with Associated bit
jnz .next
call test_filename2
jc .next
push ds es di
push es
pop ds
push cs
pop es
mov di, [es:cur_desc_end]
movzx eax, byte [bx+1]
add eax, [bx+2]
stosd ; first logical block
mov eax, [bx+10]
stosd ; length
mov al, [bx+25]
stosb ; flags
mov ax, [bx+26]
stosw ; File Unit size, Interleave Gap size
mov [es:cur_desc_end], di
cmp di, 3000h
pop di es ds
jae .done
test byte [es:bx+25], 80h
jz .done
.next:
add bl, [es:bx]
adc bh, 0
jmp .loop
.loopd:
mov ax, bx
pop bx
@@:
add bx, [cs:lb_size]
jz .done2
cmp bx, ax
jb @b
jmp .loope
.notfound:
stc
.done:
pop bx
.done2:
pop bx
ret
lb_to_sector:
xor edx, edx
div dword [lb_per_sec]
ret
load_phys_sector_for_lb_force:
; in: eax = logical block, ds=0
; in: si=0 - accept 0 logical blocks, otherwise force read at least 1
; out: 0000:1000 = physical sector data; si -> logical block
; out: eax = next physical sector
; out: CF=1 if read error
; destroys cx
; this procedure reads 0-3 or 1-4 logical blocks, up to the end of physical sector
call lb_to_sector
or si, dx
jnz @f
mov si, 1800h
jmp .done
@@:
mov si, 1000h
imul dx, [lb_size]
add si, dx
mov cx, 1
push es bx
push ds
pop es
mov bx, 1000h
call read_sectors
pop bx es
inc eax
.done:
ret
normalize:
; in: es:bx = far pointer
; out: es:bx = normalized pointer (i.e. 0 <= bx < 0x10)
push ax bx
mov ax, es
shr bx, 4
add ax, bx
mov es, ax
pop bx ax
and bx, 0x0F
ret
read_many_bytes:
and [first_byte], 0
read_many_bytes.with_first:
; read esi bytes from logical block dx:ax to buffer es:bx
; out: CF=1 <=> disk error
push di
; load first physical sector
push bx si
mov si, [first_byte]
call load_phys_sector_for_lb_force
jnc @f
pop si bx
.ret:
pop di
ret
@@:
add si, [first_byte]
mov ecx, 1800h
sub cx, si
mov ebx, esi
pop bx
sub ebx, ecx
jnc @f
add cx, bx
xor ebx, ebx
@@:
pop di
sub [cur_limit], ecx
rep movsb
mov esi, ebx
mov bx, di
call normalize
; load other physical sectors
; read esi bytes from physical sector eax to buffer es:bx
test esi, esi
jz .ret
push esi
add esi, 0x7FF
and si, not 0x7FF
cmp esi, [cur_limit]
jbe .okplace
.noplace:
sub esi, 800h
.okplace:
shr esi, 11 ; si = number of sectors
mov cx, si
jz @f
call read_sectors
@@:
pop esi
jc .ret
movzx ecx, cx
add eax, ecx
shl ecx, 11
sub [cur_limit], ecx
sub esi, ecx
jc .big
jz .nopost
push bx es
push ds
pop es
mov bx, 1000h
mov cx, 1
call read_sectors
pop es di
jc .ret2
mov cx, si
mov si, 1000h
sub word [cur_limit], cx
sbb word [cur_limit+2], 0
rep movsb
mov bx, di
call normalize
.nopost:
clc
.ret2:
pop di
ret
.big:
mov ax, es
sub ax, 80h
mov es, ax
add bx, 800h
add bx, si
call normalize
sub [cur_limit], esi
jmp .nopost
; Callback function for secondary loader
callback:
; in: ax = function number; only function 1 is defined for now
dec ax
jz callback_readfile
dec ax
jz callback_continueread
stc ; unsupported function
retf
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
clc ; function is supported
retf
callback_continueread:
; function 2: continue to 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)
; out: bx=0 - ok, bx=1 - file is too big, only part of file was loaded, bx=3 - read error
; out: dx:ax = file size
les bx, [di]
call normalize
movzx esi, word [di+4] ; si = limit in 4K blocks
shl esi, 12 ; bp:si = limit in bytes
push cs
pop ds
mov [cur_limit], esi
mov di, [cur_chunk]
call loadloop.loadnew
clc ; function is supported
retf
secondary_loader_info:
dw 0, 0x1000
dw 0x30000 / 0x1000
db 'kord/loader',0
aKernelNotFound db 'Fatal error: cannot load the secondary loader',0
align 2
cachelist:
.head dw cachelist
.tail dw cachelist
free_cache_item dw 0
last_cache_item dw 0x8400
use_path_table db 0
bootdrive db ?
align 2
lb_size dw ? ; Logical Block size in bytes
dw 0 ; to allow access dword [lb_size]
lb_per_sec dw ? ; Logical Blocks per physical sector
dw 0 ; to allow access dword [lb_per_sec]
free_ptr dw ? ; first free block in cache (cache block = sector = 0x800 bytes)
size_rest dw ? ; free space in cache (in blocks)
cache_size dw ?
cache_start dw ?
pathtable_size dw ?
pathtable_start dw ?
root_location dd ?
cur_desc_end dw ?
cur_nocache_len dd ?
cur_limit dd ?
cur_unit_limit dd ?
overflow dw ?
cur_chunk dw ?
first_byte dw ?
cur_start dd ?
;times 83FEh-$ db 0
db 43h
; just to make file 2048 bytes long :)
db 'd' xor 'i' xor 'a' xor 'm' xor 'o' xor 'n' xor 'd'
dw 0xAA55 ; this is not required for CD, but to be consistent...