rdos/kernel/drive.asm

352 lines
6.8 KiB
NASM

; code for disk i/o
; from outside, only the following methods are meant to be called:
; - logdrv: select drive DL to operate on
; - map*: map sector DX:AX into dskbuf
; - dirty: inform that dskbuf has been written to
; access is done by accessing dskbuf directly
section .bss
; drive actually selected for I/O
dsknum: resb 1
; bios drive we end up using
biosnum: resb 1
dap: resw 2
.buf: resw 2
; current sector number
; absolute, ignores partition
dskseek: resd 2
; bit 0 (1) - dirty flag for dskbuf
; bit 1 (2) - controller configured
; bit 2 (4) - EBIOS supported
dskflag: resb 1
; disk buffer for I/O operations
dskbuf: resb 512
dpt: resb DPTSIZE
bpb: resb BPBSIZ4
section .text
; initial setup for disk i/o
dinit: ; copy previously set DPT to our data area
push cs
pop es
lds si, [es:4*0x1E]
mov di, dpt
mov cx, 11
rep movsb
; set vector
mov word [cs:4*0x1E], dpt
mov word [cs:4*0x1E+2], ds
; set to invalid drive
mov byte [cs:dsknum], 0xFF
ret
; int 13 stub
int13: ; debug output
push bx
push dx
push cx
push ax
call printf
db "int13 CALL AX=",2," CX=",2," DX=",2," BX=",2,0x0A,0x0D,0
; do the call
int 0x13
jc .err
ret
.err: push ax
call printf
db "DISK ERR AX=",2,0x0A,0x0D,0
stc
ret
; get drive parameters
; read BIOS int13h/AH=8 values
; DPT data gets copied over our dpt
; CHS data gets inserted into our bpb
getprm: ; skip if EBIOS extension enabled
test byte [cs:dskflag], 4
jnz .ret
; do the query
mov ah, 8
mov dl, [cs:biosnum]
call int13
; bail out if error
jc .ret
; ignore CHS values if odd
test cl, cl
jz .nochs
; get and store sector number
and cx, 0x3F
mov word [cs:bpb+BPBSPT], cx
; get and store number of heads
xchg dl, dh
and dx, 0xFF
inc dx
mov [cs:bpb+BPBNOS], dx
; test if DPT ptr is non-zero
.nochs: mov ax, es
or ax, di
test ax, ax
jz .ret
; DS:SI = ES:DI
mov ax, es
mov ds, ax
mov si, di
; ES:DI = dpt
xor ax, ax
mov es, ax
mov di, dpt
; do the copy
mov cx, 11
rep movsb
.ret: ret
; log in drive
; IN dl drive number
logdrv: ; DS := CS
push cs
pop ds
; dont do anything if drive already selected
cmp dl, [dsknum]
je logfdd.ret
; clear out current contents
push dx
push ds
call flush
pop ds
mov ax, 0xFFFF
mov [dskseek], ax
mov [dskseek+2], ax
mov byte [dskflag], 0
pop dx
; set current drive number
mov [dsknum], dl
; fork off if hdd
cmp dl, 2
jnc loghdd
logfdd: ; save info for bios
mov [biosnum], dl
; reset dpt to defaults
push ds
call getprm
pop ds
; set default geometry (1.44 MB floppy)
mov word [bpb+BPBNOS], 2
mov word [bpb+BPBSPT], 18
; load boot sector
push ds
xor ax, ax
xor dx, dx
call mapabs
pop ds
; copy bios parameter block
; TODO: guess from first byte of FAT if BPB invalid
push ds
pop es
mov si, dskbuf+BPBOFF
mov di, bpb
mov cx, BPBSIZ4
rep movsb
; copy SPT to DPT
mov al, [bpb+BPBSPT]
mov [dpt+4], al
; make sure partition offset is forced zero
xor ax, ax
mov [bpb+BPBHS], ax
mov [bpb+BPBHS+2], ax
.ret: ret
; assumes that DS == CS
loghdd: sub dl, 2
cmp dl, 4
jnc logerr
push dx
mov dl, 0x80
mov byte [biosnum], dl
; test for ebios extensions
mov ah, 0x41
mov bx, 0x55AA
call int13
sbb bx, 0xAA55
jnz .chs
; enable ebios and assume controller is configured
or byte [dskflag], (2+4)
; initialize DAP
mov word [dap], 0x10
mov word [dap+2], 1
mov word [dap+4], dskbuf
xor ax, ax
mov word [dap+6], ax
mov word [dap+12], ax
mov word [dap+14], ax
; get chs data (needed or we cant load vbr)
.chs: call getprm
; read mbr
xor ax, ax
xor dx, dx
call mapabs
; get partition number
pop bx
xor bh, bh
mov cl, 4
sal bx, cl
; bail out if no partition
cmp byte [cs:dskbuf+0x1be+bx+4], 0
je logerr
; load partition offset
mov ax, [cs:dskbuf+0x1be+bx+8]
mov dx, [cs:dskbuf+0x1be+bx+8+2]
; save to to stack
push dx
push ax
; load vbr
call mapabs
; copy bpb
mov ax, cs
mov ds, ax
mov es, ax
mov si, dskbuf+BPBOFF
mov di, bpb
mov cx, BPBSIZ4
rep movsb
; fix CHS data
call getprm
; fix partition offset
pop ax
pop dx
mov [cs:bpb+BPBHS], ax
mov [cs:bpb+BPBHS+2], dx
ret
logerr: stc
ret
mapclu: ; counting from begin of cluster area
mov bx, [cs:bpb+BPBRDE]
mov cl, 4
shr bx, cl ; 32 bytes per entry
add ax, bx
adc dx, 0
maprd: ; counting from beginning of dir
; add fat table sizes
xor ch, ch
mov cl, byte [cs:bpb+BPBFN]
.loop: add ax, [cs:bpb+BPBFS]
adc dx, 0
loop .loop
mapfat: ; counting from beginning of FAT
; add reserved sector count
add ax, [cs:bpb+BPBRSC]
adc dx, 0
map: ; count from partition start
add ax, [cs:bpb+BPBHS]
add dx, [cs:bpb+BPBHS+2]
mapabs: ; absolute sector count
; skip doing a read if sector number matches
cmp ax, [cs:dskseek]
jne read
cmp dx, [cs:dskseek+2]
jne read
ret
read: push ax
push dx
call flush
pop dx
pop ax
; store the sector number
mov [cs:dskseek], ax
mov [cs:dskseek+2], dx
; do the actual read
; low level read and write
; call again to retry, no input registers
; read or write is configured in cx
_read: mov ch, 2
db 0x3D ; cmp ax, imm16: causes next instr to be skipped
_write: mov ch, 3
mov cl, 1 ; read len
; DS := ES := CS
mov ax, cs
mov ds, ax
mov es, ax
; check if ebios supported
test byte [dskflag], 4
jz .l00
mov ax, cx
or ah, 0x40
mov si, dap
jmp .do
; check if we can skip controller reset
.l00: test byte [dskflag], 2
jnz .l01
; do controller reset
mov dl, [biosnum]
mov ah, 0
call int13
or byte [dskflag], 2
.l01: ; put sectors per cylinder into bx
mov ax, [bpb+BPBSPT]
mul word [bpb+BPBNOS]
mov bx, ax
; put linear sector num into dx:ax
mov ax, [dskseek]
mov dx, [dskseek+2]
; dx:ax = linear count, bx = sectors / cylinder
div bx
xchg ax, dx
; dx = cylinder, ax = head * spt + sector
mov bl, [bpb+BPBSPT]
div byte bl
; dx = cylinder, al = head, ah = sector
xchg dl, dh
ror dl, 1
ror dl, 1
or dl, ah
inc dx
; al: head number
; dh bit 0-7: cylinder bits 0-7
; dl bit 0-5: sector bits 0-5
; dl bit 6-7: cylinder bits 8-9
; shuffle values around for bios
xchg ax, cx
xchg cx, dx
xchg dh, dl
mov bx, dskbuf
.do: mov dl, [biosnum]
; ah: subfunction selected via cx previously
; al: 1 = reading 1 sector
; cx: sector and cylinder number
; dh: head number
; dl: drive number
; bx: offset to disk buffer
call int13
jc .err
; clear dirty flag on success
and byte [dskflag], 0xFE
clc
ret
.err: ; assume controller is misconfigured
and byte [dskflag], 0xFD
; exit with carry flag set
stc
ret
; mark dskbuf as containing unwritten changes
dirty: or byte [cs:dskflag], 1 ; dirty
ret
; flush buffer if dirty
flush: test byte [cs:dskflag], 1
jz .ret
; TODO: error handling & retries
jmp _write
.ret: ret