; 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