713 lines
10 KiB
NASM
713 lines
10 KiB
NASM
; SPDX-License-Identifier: MIT
|
|
; Copyright (c) 2021 Juhani 'nortti' Krekelä.
|
|
|
|
debug equ 1
|
|
|
|
iosegment equ 60h
|
|
dossegment equ iosegment + 1*1024/16 ; DOS starts 1KiB after IO system
|
|
|
|
dos segment at dossegment
|
|
org 0
|
|
dosinit proc far
|
|
dosinit endp
|
|
dos ends
|
|
|
|
code segment
|
|
code ends
|
|
|
|
constants segment
|
|
constants ends
|
|
|
|
data segment
|
|
data ends
|
|
|
|
iogroup group code, constants, data
|
|
|
|
code segment
|
|
assume cs:iogroup
|
|
org 0
|
|
|
|
; Jump table
|
|
jmp init ; 0000
|
|
jmp near ptr status ; 0003
|
|
jmp near ptr getch ; 0006
|
|
jmp near ptr putch ; 0009
|
|
jmp unimplemented ; 000c Output to printer
|
|
jmp unimplemented ; 000f Serial read
|
|
jmp unimplemented ; 0012 Serial write
|
|
jmp near ptr diskread ; 0015
|
|
jmp diskwrite ; 0018
|
|
jmp near ptr diskchange ; 001b
|
|
jmp near ptr setdate ; 001e
|
|
jmp near ptr settime ; 0021
|
|
jmp near ptr gettime ; 0024
|
|
jmp near ptr flush ; 0027
|
|
jmp near ptr mapdev ; 002a
|
|
|
|
init:
|
|
cld
|
|
|
|
cli
|
|
mov ax, cs
|
|
mov ds, ax
|
|
mov es, ax
|
|
; Put setup stack just below 32K
|
|
xor ax, ax
|
|
mov ss, ax
|
|
mov sp, 8000h
|
|
sti
|
|
|
|
; Figure out memory size
|
|
int 12h
|
|
; AX = memory size in kilobytes
|
|
; We want it in paragraphs
|
|
; There are 64 paragraphs in a kilobyte
|
|
mov cx, 6
|
|
shl ax, cl
|
|
; Memory size is passed in dx
|
|
mov dx, ax
|
|
if debug
|
|
call hexprint16
|
|
call newline
|
|
endif
|
|
|
|
; Disk table is passed in si
|
|
mov si, offset iogroup:disks_table
|
|
|
|
call dosinit
|
|
|
|
; Memory for command.com is at ds, with PSP already set up
|
|
|
|
; Retrieve amount of memory in the program segment
|
|
mov cx, word ptr ds:6
|
|
|
|
; Set up DTA
|
|
mov dx, 100h
|
|
mov ah, 1ah
|
|
int 21h
|
|
|
|
; Save command.com's segment for later and revert ds for opening the FCB
|
|
mov bx, ds
|
|
mov ax, cs
|
|
mov ds, ax
|
|
|
|
; Open command.com
|
|
mov dx, offset iogroup:command_fcb
|
|
mov ah, 0fh
|
|
int 21h
|
|
test al, al
|
|
jnz open_error
|
|
|
|
if debug
|
|
mov al, '+'
|
|
mov ah, 0eh
|
|
int 10h
|
|
endif
|
|
|
|
; Set random record field 0
|
|
mov word ptr iogroup:command_fcb+33, 0
|
|
mov word ptr iogroup:command_fcb+35, 0
|
|
|
|
; Set record size to 1 byte
|
|
mov word ptr iogroup:command_fcb+14, 1
|
|
|
|
; Read command.com into memory
|
|
mov ah, 27h
|
|
int 21h
|
|
|
|
jcxz read_error
|
|
|
|
cmp al, 1
|
|
jne read_error
|
|
|
|
if debug
|
|
mov ax, cx
|
|
call hexprint16
|
|
|
|
mov al, '.'
|
|
mov ah, 0eh
|
|
int 10h
|
|
endif
|
|
|
|
; Set up segments for command.com
|
|
mov ds, bx
|
|
mov es, bx
|
|
mov ss, bx
|
|
mov sp, 5ch ; Microsoft's documentation says to use this
|
|
|
|
; Put a zero at top of the stack
|
|
xor ax, ax
|
|
push ax
|
|
|
|
; Set default DTA
|
|
mov dx, 80h
|
|
mov ah, 1ah
|
|
int 21h
|
|
|
|
; Jump to command.com
|
|
push bx
|
|
mov ax, 100h
|
|
push ax
|
|
|
|
trampoline proc far
|
|
ret
|
|
trampoline endp
|
|
|
|
; TODO: Better error reporting
|
|
open_error:
|
|
mov al, '-'
|
|
jmp unfinished
|
|
|
|
read_error:
|
|
mov al, '!'
|
|
|
|
unfinished:
|
|
mov ah, 0eh
|
|
int 10h
|
|
jmp hang
|
|
|
|
; OUT:
|
|
; al = character if any
|
|
; zf = there were no characters
|
|
status proc far
|
|
push bx
|
|
push ax
|
|
mov ah, 1
|
|
int 16h
|
|
mov bl, al
|
|
pop ax
|
|
mov al, bl
|
|
pop bx
|
|
ret
|
|
status endp
|
|
|
|
; OUT:
|
|
; al = character
|
|
getch proc far
|
|
push bx
|
|
push ax
|
|
xor ah, ah
|
|
int 16h
|
|
mov bl, al
|
|
pop ax
|
|
mov al, bl
|
|
pop bx
|
|
ret
|
|
getch endp
|
|
|
|
; IN:
|
|
; al = character
|
|
putch proc far
|
|
push ax
|
|
mov ah, 0eh
|
|
int 10h
|
|
pop ax
|
|
ret
|
|
putch endp
|
|
|
|
; IN:
|
|
; al = driver number
|
|
; ds:bx = buffer
|
|
; cx = number of sectors to read
|
|
; dx = LBA of first sector
|
|
; OUT:
|
|
; TODO: Document
|
|
diskread proc far
|
|
if debug
|
|
push ax
|
|
mov ax, 0e00h + 'r'
|
|
int 10h
|
|
pop ax
|
|
call far_caller
|
|
push ax
|
|
call hexprint8
|
|
call space
|
|
mov ax, ds
|
|
call hexprint16
|
|
call space
|
|
mov ax, bx
|
|
call hexprint16
|
|
call space
|
|
mov ax, cx
|
|
call hexprint16
|
|
call space
|
|
mov ax, dx
|
|
call hexprint16
|
|
call newline
|
|
pop ax
|
|
endif
|
|
|
|
; TODO: Everything except sregs can be trashed
|
|
push es
|
|
push ax
|
|
push bx
|
|
push cx
|
|
push dx
|
|
push di
|
|
|
|
; Save driver number
|
|
push ax
|
|
|
|
; BIOS uses es:bx instead of ds:bx
|
|
mov ax, ds
|
|
mov es, ax
|
|
|
|
mov ax, dx
|
|
|
|
; Put drive(r) number in dl
|
|
pop dx
|
|
|
|
sector_read_loop:
|
|
jcxz ret_diskread
|
|
|
|
mov di, 3 + 1 ; 3 retries, + 1 since we dec first
|
|
|
|
try_sector_read:
|
|
push ax
|
|
push cx
|
|
|
|
call chs
|
|
|
|
mov ah, 2
|
|
mov al, 1
|
|
int 13h
|
|
|
|
jnc sector_read_success
|
|
|
|
dec di
|
|
jz sector_read_fail
|
|
|
|
reset_disk:
|
|
if debug
|
|
mov al, '"'
|
|
mov ah, 0eh
|
|
int 10h
|
|
endif
|
|
xor ax, ax
|
|
int 13h
|
|
|
|
pop cx
|
|
pop ax
|
|
jmp try_sector_read
|
|
|
|
sector_read_fail:
|
|
mov al, ah
|
|
if debug
|
|
call hexprint8;debg
|
|
endif
|
|
mov al, '?'
|
|
mov ah, 0eh
|
|
int 10h
|
|
pop cx
|
|
pop ax
|
|
|
|
pop di
|
|
pop dx
|
|
pop ax ; Would be cx, don't overwrite
|
|
pop bx
|
|
pop ax
|
|
pop es
|
|
|
|
mov al, 12 ; TODO: Don't hardcode
|
|
stc
|
|
ret
|
|
|
|
sector_read_success:
|
|
pop cx
|
|
pop ax
|
|
dec cx
|
|
inc ax
|
|
add bx, 512
|
|
jmp sector_read_loop
|
|
|
|
ret_diskread:
|
|
pop di
|
|
pop dx
|
|
pop cx
|
|
pop bx
|
|
pop ax
|
|
pop es
|
|
|
|
clc
|
|
ret
|
|
|
|
diskread endp
|
|
|
|
; IN:
|
|
; ax = LBA
|
|
; OUT:
|
|
; ch = cylinder
|
|
; cl = sector & 2 high bits of cylinder
|
|
; dh = head
|
|
chs proc
|
|
push ax
|
|
push dx
|
|
|
|
xor dx, dx
|
|
; cylinder (track) - head - sector
|
|
; cylinder = LBA / sectorspertrack / heads
|
|
; head = LBA / sectorspertrack % heads
|
|
; sector = LBA % sectorspertrack + 1
|
|
div iogroup:sectorspertrack
|
|
; ax = LBA / sectorspertrack
|
|
; dx = LBA % sectorspertrack
|
|
|
|
; sector
|
|
mov cl, dl
|
|
inc cl
|
|
|
|
xor dx, dx
|
|
div iogroup:heads
|
|
; ax = LBA / sectorspertrack / heads
|
|
; dx = LBA / sectorspertrack % heads
|
|
|
|
; head
|
|
mov dh, dl
|
|
|
|
; cylinder (track)
|
|
mov ch, al
|
|
shr ax, 1
|
|
shr ax, 1
|
|
and al, 0c0h
|
|
or cl, al
|
|
|
|
mov ax, dx
|
|
pop dx
|
|
mov dh, ah
|
|
pop ax
|
|
|
|
ret
|
|
chs endp
|
|
|
|
diskwrite:
|
|
mov al, 'w'
|
|
jmp error
|
|
|
|
; IN:
|
|
; al = drive
|
|
; OUT:
|
|
; ah = -1 different / 0 dunno / 1 same
|
|
; cf = 0 -> al = driver num
|
|
; cf = 1 -> al = disk error code
|
|
diskchange proc far
|
|
; TODO: Implement
|
|
mov ax, 0000h
|
|
clc
|
|
ret
|
|
diskchange endp
|
|
|
|
; IN:
|
|
; ax = days since 1980-01-01
|
|
setdate proc far
|
|
mov iogroup:days_since_epoch, ax
|
|
ret
|
|
setdate endp
|
|
|
|
; IN:
|
|
; ch = hour
|
|
; cl = minute
|
|
; dh = second
|
|
; dl = 1/100 second
|
|
settime proc far
|
|
push ax
|
|
push bx
|
|
push cx
|
|
push dx
|
|
push si
|
|
push di
|
|
|
|
; Save second & 1/100 second in bx
|
|
mov bx, dx
|
|
|
|
; Hour
|
|
mov al, ch
|
|
cbw
|
|
mul iogroup:doubleticks_per_hour
|
|
shl ax, 1
|
|
rcl dx, 1
|
|
; Store the partial sum in si:di
|
|
mov di, ax
|
|
mov si, dx
|
|
|
|
; Minute
|
|
mov al, cl
|
|
cbw
|
|
mul iogroup:ticks_per_minute
|
|
add di, ax
|
|
adc si, dx
|
|
|
|
; Second
|
|
mov al, bh
|
|
cbw
|
|
mul iogroup:ticks_per_second
|
|
add di, ax
|
|
adc si, dx
|
|
|
|
; 1/100 second
|
|
mov al, bl
|
|
cbw
|
|
div iogroup:cs_per_tick
|
|
xor ah, ah
|
|
add di, ax
|
|
adc si, 0
|
|
|
|
; Move result to cx:ds and set timer
|
|
mov cx, si
|
|
mov dx, di
|
|
mov ah, 01h
|
|
int 1ah
|
|
|
|
pop di
|
|
pop si
|
|
pop dx
|
|
pop cx
|
|
pop bx
|
|
pop ax
|
|
ret
|
|
settime endp
|
|
|
|
; OUT:
|
|
; ax = days since 1980-01-01
|
|
; ch = hour
|
|
; cl = minute
|
|
; dh = second
|
|
; dl = 1/100 second
|
|
gettime proc far
|
|
; TODO: Use RTC if available
|
|
push bx
|
|
|
|
; Get ticks since midnight and day rollover flag
|
|
xor ah, ah
|
|
int 1ah
|
|
|
|
; al is nonzero if day has changed
|
|
test al, al
|
|
jz same_day
|
|
inc iogroup:days_since_epoch
|
|
|
|
same_day:
|
|
|
|
; Divide the ticks by 2, to keep all our arithmetic within bounds
|
|
shr cx, 1
|
|
rcr dx, 1
|
|
|
|
; int 1ah / ah=00 returns the ticks in cx:dx, div operates on dx:ax
|
|
mov ax, dx
|
|
mov dx, cx
|
|
div iogroup:doubleticks_per_hour
|
|
mov ch, al ; Hour
|
|
|
|
; Remainder in dx, range [0,doubleticks_per_hour[ represents an hour
|
|
; Multiply by 60, divide by doubleticks_per_hour to get the minute
|
|
mov ax, dx
|
|
mul iogroup:sixty
|
|
div iogroup:doubleticks_per_hour
|
|
mov cl, al ; Minute
|
|
|
|
; Do same again to get seconds
|
|
mov ax, dx
|
|
mul iogroup:sixty
|
|
div iogroup:doubleticks_per_hour
|
|
mov bl, al ; Save seconds in bl, since dh gets destroyed by next muldiv
|
|
|
|
; For 1/100th of a second, multiply by 100 instead
|
|
mov ax, dx
|
|
mul iogroup:hundred
|
|
div iogroup:doubleticks_per_hour
|
|
mov dh, bl ; Second
|
|
mov dl, al ; 1/100 second
|
|
|
|
mov ax, iogroup:days_since_epoch
|
|
|
|
pop bx
|
|
ret
|
|
gettime endp
|
|
|
|
flush proc far
|
|
push ax
|
|
|
|
flush_loop:
|
|
mov ah, 1
|
|
int 16h
|
|
jz flush_ret
|
|
|
|
xor ah, ah
|
|
int 16h
|
|
jmp flush_loop
|
|
|
|
flush_ret:
|
|
pop ax
|
|
ret
|
|
flush endp
|
|
|
|
mapdev proc far
|
|
; TODO: Implement
|
|
ret
|
|
mapdev endp
|
|
|
|
unimplemented:
|
|
mov al, '@'
|
|
|
|
error:
|
|
mov ah, 0eh
|
|
int 10h
|
|
mov ah, 0eh
|
|
mov al, '!'
|
|
int 10h
|
|
if debug
|
|
call far_caller
|
|
endif
|
|
|
|
hang:
|
|
hlt
|
|
jmp hang
|
|
|
|
code ends
|
|
|
|
; TODO: STRUC?
|
|
|
|
constants segment
|
|
|
|
sectorspertrack dw 9 ; TODO: Don't hardcode
|
|
heads dw 2 ; TODO: Don't hardcode
|
|
|
|
disks_table:
|
|
db 1 ; 1 drive, TODO: Don't hardcode
|
|
|
|
db 0 ; Physical drive 0
|
|
;dw offset iogroup:parameters_320k
|
|
dw offset iogroup:parameters_720k
|
|
|
|
parameters_320k:
|
|
dw 512 ; Sector size in bytes
|
|
db 2 ; Sectors per cluster
|
|
dw 1 ; Number of reserved sectors
|
|
db 2 ; Number of FATs
|
|
dw 112 ; Number of directory entries
|
|
dw 320*2 ; Number of sectors
|
|
|
|
parameters_720k:
|
|
dw 512 ; Sector size in bytes
|
|
db 2 ; Sectors per cluster
|
|
dw 1 ; Number of reserved sectors
|
|
db 2 ; Number of FATs
|
|
dw 112 ; Number of directory entries
|
|
dw 720*2 ; Number of sectors
|
|
|
|
doubleticks_per_hour dw 32772 ; 1800B0h / 2 / 24
|
|
ticks_per_minute dw 1092 ; 1800B0h / 24 / 60
|
|
ticks_per_second dw 18 ; 1800B0h / 24 / 60 / 60
|
|
cs_per_tick db 5 ; 24 * 60 * 60 * 100 / 1800B0h
|
|
|
|
sixty dw 60
|
|
hundred dw 100
|
|
|
|
constants ends
|
|
|
|
data segment
|
|
|
|
command_fcb:
|
|
db 1 ; First drive, TODO: Don't hardcode
|
|
db "COMMAND COM"
|
|
db 25 dup (?)
|
|
|
|
days_since_epoch: dw 0
|
|
|
|
data ends
|
|
|
|
code segment
|
|
if debug
|
|
|
|
hexprint16 proc
|
|
xchg al, ah
|
|
call hexprint8
|
|
xchg al, ah
|
|
hexprint8 proc
|
|
rol al, 1
|
|
rol al, 1
|
|
rol al, 1
|
|
rol al, 1
|
|
call hexprint4
|
|
rol al, 1
|
|
rol al, 1
|
|
rol al, 1
|
|
rol al, 1
|
|
hexprint4 proc
|
|
push ax
|
|
|
|
and al, 0fh
|
|
cmp al, 9
|
|
jbe under_10
|
|
add al, 'a' - 10 - '0'
|
|
|
|
under_10:
|
|
add al, '0'
|
|
mov ah, 0eh
|
|
int 10h
|
|
|
|
pop ax
|
|
ret
|
|
hexprint4 endp
|
|
hexprint8 endp
|
|
hexprint16 endp
|
|
newline proc
|
|
push ax
|
|
mov ax, 0e0dh
|
|
int 10h
|
|
mov ax, 0e0ah
|
|
int 10h
|
|
pop ax
|
|
ret
|
|
newline endp
|
|
space proc
|
|
push ax
|
|
mov ax, 0e20h
|
|
int 10h
|
|
pop ax
|
|
ret
|
|
space endp
|
|
|
|
far_caller proc
|
|
push ax
|
|
push bx
|
|
mov bx, sp
|
|
mov ax, [ss:bx + 8]
|
|
call hexprint16
|
|
mov al, ':'
|
|
mov ah, 0eh
|
|
int 10h
|
|
pop bx
|
|
pop ax
|
|
|
|
near_caller proc
|
|
push ax
|
|
push bx
|
|
mov bx, sp
|
|
mov ax, [ss:bx + 6]
|
|
call hexprint16
|
|
call space
|
|
pop bx
|
|
pop ax
|
|
ret
|
|
near_caller endp
|
|
far_caller endp
|
|
|
|
logaddr proc
|
|
push ax
|
|
push bx
|
|
mov al, '>'
|
|
mov ah, 0eh
|
|
int 10h
|
|
mov bx, sp
|
|
mov ax, [ss:bx + 4]
|
|
call hexprint16
|
|
call space
|
|
pop bx
|
|
pop ax
|
|
ret
|
|
logaddr endp
|
|
|
|
endif
|
|
code ends
|
|
|
|
end
|