You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
704 lines
10 KiB
704 lines
10 KiB
; 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 8 ; 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 |
|
|
|
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 |
|
|
|
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
|
|
|