Wed Nov 15 18:51:16 UTC 2023

This commit is contained in:
Nero 2023-11-15 18:51:16 +00:00
parent 2c13f25c30
commit 92bf96f21d
3 changed files with 335 additions and 634 deletions

316
src/@bootsec.asm Normal file
View File

@ -0,0 +1,316 @@
base equ 0x7C00
org base
; boot sector identification bytes:
jmp short init ; EB3C
nop ; 90
; OEM vendor field
vendor .space 8, 0x20
; bios parameter block
ss .space 2 ; word bytes per sector
sc .space 1 ; byte sectors per cluster
rsc .space 2 ; word reserved sector count
fn .space 1 ; byte number of fat tables
rde .space 2 ; word root disk entries
ts .space 2 ; word total sectors
md .space 1 ; byte media descriptor byte
fs .space 2 ; word sectors per fat
spt .space 2 ; word sectors per track
nos .space 2 ; word number of sides/heads
hs .space 4 ; dword partition offset
lts .space 4 ; dword large total sectors
; extended bios parameter block data
.space base+03Eh-$
init xor ax, ax ; ax as quick zero value
; initialize all segment registers
; the docs say we cant rely on them to have any sensible values
; we only work with the first 64kB per default
; so we set all of them to zero
mov ds, ax
mov es, ax
mov ss, ax
mov sp, ax
jmp main
; print AX in base BX
; destroys DX
putnum xor dx, dx
div bx
test cx, cx
loopnz .pad
test ax, ax
je .nib
.pad push dx
call putnum
pop dx
; print lower DL as digit
.nib add dl, 0x30
cmp dl, 0x3a
jl putc
add dl, 7
; print DL as char
putc mov ah, 0x0e
mov al, dl
int 0x10
ret
; print str at SI
; NUL-terminated
puts lodsb
test al, al
jz .l01
mov dl, al
call putc
jmp puts
.l01 ret
; print a newline
newlin mov dl, 0x0A
call putc
mov dl, 0x0D
jmp putc
; print register set
; order AX CX DX BX BP SI DI IP
; trashes flags
debug push di
push si
push bp
push bx
push dx
push cx
push ax
mov si, sp
call dump
pop ax
pop cx
pop dx
pop bx
pop bp
pop si
pop di
ret
; dump the 8 words at SI
dump lea di, [si+16]
mov bx, 0x10
.loop lodsw
mov cx, 4
call putnum
mov dl, 020h ; space char
call putc
cmp si, di
jc .loop
jmp newlin
; Advantages over using int13h directly:
; - Translation from linear sector number into CHS data
; - Reads across different tracks.
; Older BIOSes don't support that.
; - Nearby sector reads/writes are coalesced into one int 13h call
; This improves performance on real hardware.
; Only floppies are supported, and only variants with two sides
; This includes the standard 5 1/4 360kB and 3 1/2 1.44MB formats
; When a sector is requested, int13h is not called yet, the parameters
; for int 13h are recorded. When the next sector is requested, either
; it it suitable for coalescing, then the recorded parameters are
; adjusted. When the sector is not suitable for coalescing, the
; previous sector is read/written and the parameters for the new
; sector are put into the record. If the program is done with listing
; the wanted sectors, another function can be called to force doing
; the recorded io request
; Select a sector for a disk operation
; IN ax linear sector number
; cx number of sectors to read/write
; dl drive number
sector push ax
push cx
; split up sector number into CHS data
; divisor is sectors per track field in bios parameter block
; quotient al as track number
; remainder ah as sector number (starting with 0)
div byte [spt]
; phys sector number cl (starting with 1)
mov cl, ah
inc cl
; get head number dh from lowest bit of track number
; hard-coded assumption: two-sided floppy
mov dh, al
and dh, 1
; cylinder number ch is track number, except the head number shifted out
mov ch, al
shr ch, 1
; Unlucky if different head (dh)
cmp byte dh, [disk_dh]
jne .miss
; Unlucky if different track (ch) or not subsequent sector (cl + number of op sectors)
mov ax, cx
sub al, [disk_al]
cmp ax, [disk_cx]
jne .miss
; We are lucky! We can extend the current request by increasing the number of op sectors
; Do nothing else
inc byte [disk_al]
jmp .end
; The sector we just selected does not fit into the recorded io request
; So we submit that request first and then record a new io request
.miss: push cx
push dx
call diskio
pop dx
pop cx
; Record new io request
mov word [disk_cx], cx
mov byte [disk_dh], dh
mov byte [disk_al], 1 ; only one sector yet
.end pop cx
pop ax
inc ax
loop sector
ret
; Do the io request we recorded previously now
diskio mov ax, [disk_ax]
mov cx, [disk_cx]
mov dx, [disk_dx]
mov bx, [disk_bx]
; no-op if amount of sectors is zero
cmp al, 0
je .ret
; TODO: retry is needed for real floppies
call debug
int 0x13
jc .ret
; Advance read/write pointer in memory
mov ah, 0
mov cl, 9
shl ax, cl
add word [disk_bx], ax
; Reset op sector length, work done
mov byte [disk_al], 0
.ret ret
; Load a file from disk to 0x7C00
; calculate size of the directory in sectors
load: mov ax, [rde]
mov cl, 4 ; sector=512b, entry=32b -> shift 4 bits
shr ax, cl
mov cx, ax
push cx ; remember number of dir entries for later
; calculate start of second fat table
mov ax, [fs] ; read fat table length
push ax
add cx, ax ; make cx size of FAT + dir
inc ax ; add one for the boot sector
; Read second FAT and directory into buffer
mov byte [disk_ah], 2 ; set read OP
mov word [disk_bx], fatbuf ; write into our own buffer
call sector
call diskio
; get address of root directory in frame
pop bx
mov cl, 9
shl bx, cl
add bx, fatbuf
pop dx
mov word [disk_bx], 0x7C00
; search through root directory
.loop: mov si, bx
mov ax, [bx+0x1A]
mov di, fname
mov cx, 11
repe cmpsb
je .cloop
add bx, 0x20
or byte [bx], 0
jz .end
jmp .loop
; calculate sector offset in cluster area
.cloop push ax
sub ax, 2
mov ch, 0
mov cl, [sc]
push cx
mul cx
; calculate sector offset from root directory
mov dx, [rde]
mov cl, 4 ; sector=512b, entry=32b -> shift 4 bits
shr dx, cl
add ax, dx
; add length of fat tables
add ax, [fs]
add ax, [fs]
; plus 1 for the boot sector
inc ax
; load cluster
pop cx
call sector
; follow fat chain
pop ax
mov si, ax
shr si, 1
add si, ax
mov dx, [fatbuf+si]
; shift value if needed
test ax, 1
jz .noshr
mov cl, 4
shr dx, cl
.noshr and dx, 0x0FFF
mov ax, dx
cmp ax, 0xFF0
jc .cloop
.fend: call diskio
.end: ret
main: mov byte [disk_dl], dl
call load
db 0xEA ; jmp far 0:0x7C00
dw 0x7C00, 0
; register buffer for disk io
disk_al db 0 ; number of sectors to read
disk_ah db 2 ; 2=read op
disk_cl db 1 ; sector number
disk_ch db 1 ; cylinder number
disk_dl db 0 ; drive number
disk_dh db 0 ; head number
disk_bx dw fatbuf ; data buffer addr, advanced by 'diskio'
disk_ax equ disk_al
disk_cx equ disk_cl
disk_dx equ disk_dl
fname db "@RDOS ", "COM"
.space base+01FEh-$
dw 0xAA55
; buffer for fat table and root directory
; we load it together to save an int 13h read
; at the cost of having to calculate offset of rootdir
fatbuf equ $

View File

@ -1,633 +1,18 @@
org 0x700 org 600h
cseg equ 0x0
%include "version.inc" jmp end
jmp _entry add [bx+si], al
banner db "RDOS KERNEL 1.0", 0x0A, 0x0D, '$', 0x1A add al, al
_entry call .l01 add ax, ax
.l01 pop ax rep movsb
sub ax, .l01 cs xor word [bp], 0x7C00
mov cl, 4 xor word [bp+04h], 0x7C00
shr ax, cl lock es xor word [04h], 0x7C00
mov cx, cs
add cx, ax
push cx
mov ax, start
push ax
retf
;;; Character I/O dw foo
; direct console I/O end hlt
; IN dl character to output or 0xFF if input jmp end
; OUT al character to read
conio cmp dl, 0xFF
je conin
; raw console output foo equ end+040h
; IN dl character
conout xchg ax, dx
mov ah, 0x0e
mov bx, 7
int 0x10
ret
; read character from console
; OUT al character or zero
conin call const
test al, al
jz .ret
mov al, 0
int 0x16
.ret ret
; check if character has characters ready
; OUT al 0xFF if data, 0 if none
const mov ah, 1
int 0x16
jz .emp
mov ax, 0xFF
ret
.emp mov al, 0
ret
; dummy: write to auxillary dev
auxout ret
; dummy: read from auxillary dev
auxin mov al, 0x1A
ret
; dummy: lister out
lstout ret
; character input with echo
; OUT al character
getc call const
test al, al
jz getc
call conin
push ax
mov dl, al
call putc
pop ax
ret
; character input without echo
readc call const
test al, al
jz readc
jmp conin
; character output
; IN dl character
putc jmp conout
; output character string
; IN ds:dx far ptr to string, '$'-terminated
puts push si
mov si, dx
.loop lodsb
cmp al, '$'
je .ret
mov dl, al
call putc
jmp .loop
.ret pop si
ret
; send a beep ctl to console
beep mov dl, 0x07
jmp conout
; buffered input
; IN ds:dx far ptr to buffer
gets mov si, dx
mov cx, [si]
mov ch, 0
; ch = actual number of chars
; cl = maximum number of chars
.loop call readc
cmp al, 0x0D
je .cr
cmp al, 0x08
je .bs
cmp al, 0x20
jc .loop
; check if there is space in the buffer
; emit a beep if not
mov dl, cl
dec dl
cmp ch, dl
jc .append
call beep
jmp .loop
; append to buffer
.append mov bh, 0
mov bl, ch
mov [si+2+bx], al
inc ch
mov dl, al
call putc
jmp .loop
; handle backspace
.bs: test ch, ch
jz .loop
mov dl, 0x08
call putc
mov dl, 0x20
call putc
mov dl, 0x08
call putc
dec ch
jmp .loop
; handle carriage return
.cr mov bx, 0
mov bl, ch
mov [si+2+bx], al
mov [si], cx
mov dl, 0x0A
call putc
mov dl, 0x0D
call putc
ret
;;; System functions
; Return OS version from version.inc
ver mov bx, bdosver | 0x1000
ret
;;; Disk i/o
%include "bpb.inc"
; dflags bitfields
DRVLOG equ 0x01 ; bit 0 - drive logged in (see drvnum)
DRVCTL equ 0x02 ; bit 1 - controller configured
DRVEXT equ 0x04 ; bit 2 - EBIOS supported
DIRTY equ 0x08 ; bit 3 - dskbuf dirty
DRVCHS equ 0x10 ; bit 4 - CHS geometry known
; Wrapper around int 13h
int13 int 0x13
; TODO: on error: reset & retry
; TODO: record errors
; TODO: multi-track reading
; TODO: handle DMA boundaries
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 test byte [dflags], DRVEXT
jnz .ret
; fallback non-zero values (5 1/4", 160kB)
mov word [drvspt], 8
mov word [drvspc], 8
; do the query
mov ah, 8
mov dl, [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 [drvspt], cx
; get and store number heads
xchg dl, dh
and dx, 0xFF
inc dx
; multiply with sectors per track
mov ax, dx
mul cx
; store sectors per cylinder
mov [drvspc], ax
; test if DPT ptr is non-zero
.nochs mov ax, es
or ax, di
test ax, ax
jz .ret
; copy BIOS dpt table over ours
;mov dx, dpt
;mov bx, di
;mov cx, 11
;call lodfar
.ret ret
; select a drive for io
; IN dl drive number
; dont do anything if drive already selected
select cmp dl, [drvnum]
je getprm.ret
; clear out current contents
push dx
call flush
mov ax, 0xFFFF
mov [drvseek], ax
mov [drvseek+2], ax
and word [dflags], ~(DRVLOG|DRVCTL|DRVEXT|DIRTY|DRVCHS)
pop dx
; set current drive number
mov [drvnum], dl
; fork off if hdd
cmp dl, 2
jnc loghdd
logfdd mov [biosnum], dl
and dx, 0x0D
; query bios for geometry and DPT
call getprm
; assume 512 bytes per sector
; TODO: read this from DPT
mov byte [drvss], 2
; fill partition offset with zeroes
xor ax, ax
mov di, drvoff
stosw
stosw
; fill partition end with 1's
not ax
stosw
stosw
; shared code with loghdd
jmp rdbpb
; Log in a drive on a hard disk
; IN dl bits 0-1: partition number
; bits 2-7: drive number
; Partition offset is read from MBR
; split drive & partition number
loghdd mov dh, dl
and ah, 0x3
shr dl, 1
shl dl, 1
add dl, 0x80
; partition number in DH
mov byte [biosnum], dl
push dx
; test for EBIOS extensions
mov ah, 0x41
mov bx, 0x55AA
call int13
sbb bx, 0xAA55
jnz .noeb
; enable EBIOS and assume controller is configured
or word [dflags], DRVCTL | DRVEXT
; hard disks are always assumed to be 512 bytes / sector
.noeb mov byte [drvss], 2
; query BIOS for geometry
call getprm
or word [dflags], DRVCHS
; read MBR
xor ax, ax
xor dx, dx
call map
; get partition number
pop bx
xor bh, bh
mov cl, 4
sal bx, cl
; bail out if partition undef
cmp byte [dskbuf+0x1be+bx+4], 0
je .ret
; load partition offset
mov ax, [dskbuf+0x1be+bx+8]
mov dx, [dskbuf+0x1be+bx+8+2]
; store partition offset
mov [drvoff], ax
mov [drvoff+2], dx
; add partition length
add ax, [dskbuf+0x1be+bx+12]
adc dx, [dskbuf+0x1be+bx+12+2]
; partitions after 4 TB mark not allowed
jc .ret
; store partition end
mov [drvend], ax
mov [drvend+2], dx
.ret ret
; parse bios parameter block from drvoff
; drvoff and biosnum need to be set up
rdbpb mov ax, [drvoff]
mov dx, [drvoff+2]
call map
; read partition start
mov ax, [drvoff]
mov dx, [drvoff+2]
; add reserved sectors
add ax, [dskbuf+BPBOFF+BPBRSC]
adc dx, 0
jc .ret
; save offset of fat table
mov [drvfat], ax
mov [drvfat+2], dx
; calculate length of all fat tables
mov ah, 0
mov al, [dskbuf+BPBOFF+BPBFN]
mov dx, [dskbuf+BPBOFF+BPBFS]
mov [drvfn], dx
mul dx
add ax, [drvfat]
adc dx, [drvfat+2]
; save offset of root directory
mov [drvrd], ax
mov [drvrd+2], dx
; calculate length of root directory
mov ax, [dskbuf+BPBOFF+BPBRDE]
mov cl, [drvss]
add cl, 2
shr ax, cl
; read offset to cluster area
; read cluster size
.ret ret
; map sector into dskbuf
; skip doing a read if sector number matches
; IN dx:ax absolute sector number
map cmp ax, [drvseek]
jne .do
cmp dx, [drvseek+2]
jne .do
ret
; flush previous contents
.do push ax
push dx
call flush
pop dx
pop ax
; set sector number
mov [drvseek], ax
mov [drvseek+2], dx
; issue read cmd
mov ch, 2
jmp diskio
; mark dskbuf as containing unwritten changes
dirty or word [cs:dflags], DIRTY
l002 ret
; flush buffer if dirty
flush test word [cs:dflags], DIRTY
jz l002
; issue write cmd
mov ch, 3
; Do disk I/O
; IN ch 2 = read
; 3 = write
; Sector number is read from [drvseek]
diskio mov cl, 1 ; read len
; DS := ES := CS
mov ds, [cs:dseg]
; check if ebios supported
test word [dflags], DRVEXT
jz .noext
; set up regs for ebios call
xchg ax, cx
or ah, 0x40
jmp .do
; check if we can skip controller reset
.noext test word [dflags], DRVCTL
jnz .norst
; do controller reset
mov dl, [biosnum]
mov ah, 0
call int13
or word [dflags], DRVCTL
; put linear sector num into dx:ax
.norst mov ax, [drvseek]
mov dx, [drvseek+2]
; dx:ax = linear count
div word [drvspc]
; TODO: is it possible to get an overflow here?
xchg ax, dx
; dx = cylinder, ax = head * spt + sector
div byte [drvspt]
; dx = cylinder, al = head, ah = sector
xchg ax, dx
xchg al, ah
ror al, 1
ror al, 1
or al, dh
inc ax
; dl: head number
; ah bit 0-7: cylinder bits 0-7
; al bit 0-5: sector bits 0-5
; al bit 6-7: cylinder bits 8-9
; shuffle values around for bios
xchg ax, cx
xchg dh, dl
.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
push es
push si
mov si, dap
les bx, [si+4]
call int13
pop si
pop es
jc .err
; clear dirty flag on success
and word [dflags], ~DIRTY
clc
ret
; assume controller is misconfigured
.err and word [dflags], ~DRVCTL
; exit with carry flag set
stc
ret
;;; Export functions as syscalls
; SP, BP, SI, DI and ES must be preserved by kernel code
; DL, DX or DS:DX is input argument from prog
; AL, BX or ES:BX is return value to prog
srb equ 0x8000
srw equ 0x4000
; DOS style syscall interface
int21h push bx
xor bx, bx
mov bl, ah
jmp sysc
; CP/M-86 style syscall interface
intE0h push bx
xor bx, bx
mov bl, cl
; bx = syscall number
; stack must be -> BX IP CS FL
; for return to prog
sysc sti
cld
add bx, bx
mov bx, [cs:bx+stab]
test bh, srw >> 8 ; fork off bx returns
jnz .l02
test bh, srb >> 8 ; fork off al returns
jnz .l01
; syscall with no return value
push dx
push cx
push ax
push ds
call bx
pop ds
pop ax
pop cx
pop dx
pop bx
iret
; syscalls returning byte in al
.l01 push dx
push cx
push ds
and bh, ~(srb >> 8)
call bx
pop ds
pop cx
pop dx
pop bx
iret
; syscalls returning bx or es:bx
.l02 add sp, 2 ; discard bx on the stack
push dx
push cx
push ax
push ds
and bh, ~(srw >> 8)
call bx
pop ds
pop ax
pop cx
pop dx
ret
start xor ax, ax
mov ds, ax
; install int20h handler
mov word [4*0x20], start
mov [4*0x20+2], cs
; install int21h handler
mov word [4*0x21], int21h
mov [4*0x21+2], cs
; install intE0h handler
mov word [4*0xE0], intE0h
mov [4*0xE0+2], cs
mov ax, cs
mov ds, ax
mov [dseg], ax
; print banner
mov dx, banner
call puts
; read cmdline
mov dl, '>'
call putc
mov dx, inbuf
mov byte [inbuf], 72
call gets
halt sti
hlt
jmp halt
section .rodata
; syscall table
stab dw 0 ; 0 reboot
dw getc + srb ; 1 console input
dw putc ; 2 console output
dw auxin ; 3 reader in
dw auxout + srb ; 4 puncher out
dw lstout ; 5 lister out
dw 0 ; 6 direct console i/o
dw 0 ; 7 read i/o byte
dw 0 ; 8 get i/o byte
dw puts ; 9 string output
dw gets ; 10 string input
dw 0 ; 11 console status
dw ver ; 12 get version number
dw 0 ; 13 reset disks
dw 0 ; 14 set drive
dw 0 ; 15 open file
dw 0 ; 16 close file
dw 0 ; 17 find first
dw 0 ; 18 find next
dw 0 ; 19 delete file
dw 0 ; 20 sequential read
dw 0 ; 21 sequential write
dw 0 ; 22 create file
dw 0 ; 23 rename file
dw 0 ; 24 get login vector
dw 0 ; 25 get drive
dw 0 ; 26 set dma addr
dw 0 ; 27 alloc vector
dw 0 ; 28 set drive r/o
dw 0 ; 29 get r/o vector
dw 0 ; 30 file attr
dw 0 ; 31 get addr of disk parameter block
dw 0 ; 32 get user number
dw 0 ; 33 random read
dw 0 ; 34 random write
dw 0 ; 35 calculate file size
dw 0 ; 36 calculate cur record number
dw 0 ; 37 reset drive
dw 0 ; 38
dw 0 ; 39
dw 0 ; 40 random write with block init
stab_e nop
section .data
dap db 0x10, 0
dw 1
dw dskbuf
dseg dw 0
drvseek dw 0,0,0,0
; bit 0 (1) - drive logged in (see drvnum)
; bit 1 (2) - controller configured
; bit 2 (4) - EBIOS supported
; bit 3 (8) - dskbuf dirty
dflags dw 0
section .bss
; drive currently logged in
drvnum resb 1
; number, just for bios
biosnum resb 1
; Information for logged in drive
drvss resb 1 ; sector size, 2^(7+n) bytes
drvcs resb 1 ; cluster size, 2^(7+n) bytes
alignb 4
drvspt resb 2 ; sectors per track
drvspc resb 2 ; sectors per cylinder
drvoff resb 4 ; partition offset
drvend resb 4 ; first sector after the partition
drvfat resb 4 ; offset to fat table
drvrd resb 4 ; offset of root directory
drvcla resb 4 ; offset to cluster area
drvfn resb 2 ; sectors per fat table
; disk buffer for I/O operations
alignb 2
dskbuf resb 1024
inbuf resb 128

View File

@ -2,14 +2,14 @@
; This is primarily for testing purposes ; This is primarily for testing purposes
org 0x0100 org 0x0100
main: mov si, string main mov si, str
mov ah, 0x0e mov ah, 0x0e
xor bx, bx xor bx, bx
.loop: lodsb main1 lodsb
test al, al test al, al
jz .end jz main2
int 0x10 int 0x10
jmp .loop jmp main1
.end: ret main2 ret
string: db "Hello world!", 0x0A, 0x0D, 0 str db "Hello world!", 0x0A, 0x0D, 0