; FOSSIL.COM - ATDT's FOSSIL driver (FTS-0061 subset) for the DOS
; door machine. One port: COM1 at 0x3F8, polled. The emulated UART
; runs at bus speed, so polling is exact and buffers are pointless;
; the one refinement is a single byte of read-ahead so function 0Ch
; (non-destructive peek) can be honest.
;
; Implemented: 00-0F, 10 (no-op), 11-13 (cursor/screen via BIOS),
; 18/19 (block read/write), 1A (no-op), 1B (driver info). The
; signature word 1954h sits at handler+6 with the max-function byte
; at +8, per the spec, and function 04h answers AX=1954h.
;
; Build: nasm -f bin -o FOSSIL.COM fossil.asm
; Part of ATDT; same license as the repository.

        org 0x100

UARTD   equ 0x3F8
UARTLSR equ 0x3FD
UARTMSR equ 0x3FE
UARTMCR equ 0x3FC

start:  jmp install

; ---- the resident part -------------------------------------------------

handler:
        jmp short h_entry       ; +0: 2 bytes
        nop                     ; +2
        nop                     ; +3
        nop                     ; +4
        nop                     ; +5
        dw 0x1954               ; +6: the FOSSIL signature
        db 0x1B                 ; +8: highest function implemented
        db 5                    ; +9: revision

h_entry:
        cmp ah, 0x1B
        jbe .ours
        iret
.ours:
        cmp dx, 0               ; only COM1 is real hardware here
        je  .port_ok
        cmp ah, 0x0D            ; keyboard/screen functions carry no port
        je  .port_ok
        cmp ah, 0x0E
        je  .port_ok
        cmp ah, 0x11
        jae .port_ok
        mov ax, 0x1954          ; other ports: answer politely, do nothing
        iret
.port_ok:
        ; Only SI, DI, DS are blanket-saved: several functions RETURN
        ; values in BX and DX (04h, 07h, 12h) and each function keeps
        ; what it merely uses to itself.
        push si
        push di
        push ds
        push cs
        pop  ds                 ; DS = resident segment (org offsets valid)

        push bx
        mov  bl, ah
        xor  bh, bh
        shl  bx, 1
        mov  si, [dispatch + bx]
        pop  bx
        call si
        pop  ds
        pop  di
        pop  si
        iret

; ---- helpers (resident) ------------------------------------------------

; status word per FTS-0061: AH from the line status, AL from the modem
; status with bit 3 forced on ("this is a FOSSIL" marker).
f_status:
        call rda
        mov  ah, 0
        jz   .no
        or   ah, 0x01           ; data ready
.no:
        push dx
        mov  dx, UARTLSR
        in   al, dx
        and  al, 0x60           ; THRE + TEMT
        or   ah, al
        mov  dx, UARTMSR
        in   al, dx
        or   al, 0x08
        pop  dx
        ret

; ZF clear when a byte is available (read-ahead or UART).
rda:
        cmp  byte [have], 0
        jnz  .yes
        push dx
        push ax
        mov  dx, UARTLSR
        in   al, dx
        test al, 0x01
        pop  ax
        pop  dx
        ret
.yes:
        or   byte [have], 1     ; sets NZ
        ret

; blocking read into AL (consumes read-ahead first)
getb:
        cmp  byte [have], 0
        jz   .wait
        mov  al, [held]
        mov  byte [have], 0
        ret
.wait:
        push dx
        mov  dx, UARTLSR
.w:     in   al, dx
        test al, 0x01
        jz   .w
        mov  dx, UARTD
        in   al, dx
        pop  dx
        ret

; blocking write of AL
putb:
        push dx
        push ax
        mov  dx, UARTLSR
.w:     in   al, dx
        test al, 0x20
        jz   .w
        pop  ax
        mov  dx, UARTD
        out  dx, al
        pop  dx
        ret

; ---- the functions -----------------------------------------------------

fn00:                           ; set baud: nothing to set, report status
fn03:
        call f_status
        ret

fn01:                           ; transmit with wait
        call putb
        call f_status
        ret

fn02:                           ; receive with wait
        call getb
        mov  ah, 0
        ret

fn04:                           ; init: the signature answer
        mov  ax, 0x1954
        mov  bl, 0x1B
        mov  bh, 5
        push dx
        mov  dx, UARTMCR
        push ax
        mov  al, 0x0B           ; DTR + RTS + OUT2 up
        out  dx, al
        pop  ax
        pop  dx
        ret

fn05:                           ; deinit
        mov  ax, 0x1954
        ret

fn06:                           ; DTR raise/lower (AL: 1 up, 0 down)
        push dx
        push ax
        mov  dx, UARTMCR
        test al, 1
        jz   .down
        mov  al, 0x0B
        out  dx, al
        jmp  .out
.down:
        mov  al, 0x08
        out  dx, al
.out:
        pop  ax
        pop  dx
        ret

fn07:                           ; timer info: int 1Ch, ~18.2 ticks, 55ms
        mov  ah, 0x1C
        mov  al, 18
        mov  dx, 55
        ret

fn08:                           ; flush output: wait for TEMT
        push dx
        push ax
        mov  dx, UARTLSR
.w:     in   al, dx
        test al, 0x40
        jz   .w
        pop  ax
        pop  dx
        ret

fn09:                           ; purge output: no buffer, nothing to do
fn0F:                           ; flow control: line is virtual
fn1A:                           ; break: not on this wire
        ret

fn0A:                           ; purge input: drop read-ahead + drain UART
        mov  byte [have], 0
        push dx
        push ax
        mov  dx, UARTLSR
.chk:   in   al, dx
        test al, 0x01
        jz   .done
        mov  dx, UARTD
        in   al, dx
        mov  dx, UARTLSR
        jmp  .chk
.done:
        pop  ax
        pop  dx
        ret

fn0B:                           ; transmit no wait: AX=1 sent, 0 not
        push dx
        push ax
        mov  dx, UARTLSR
        in   al, dx
        test al, 0x20
        pop  ax
        pop  dx
        jz   .busy
        call putb
        mov  ax, 1
        ret
.busy:
        xor  ax, ax
        ret

fn0C:                           ; peek: AX=FFFF none, else the char
        call rda
        jz   .none
        cmp  byte [have], 0
        jnz  .held
        push dx
        mov  dx, UARTD
        in   al, dx
        pop  dx
        mov  [held], al
        mov  byte [have], 1
.held:
        mov  al, [held]
        mov  ah, 0
        ret
.none:
        mov  ax, 0xFFFF
        ret

fn0D:                           ; keyboard read, no wait
        mov  ah, 0x01
        int  0x16
        jz   .none
        mov  ah, 0x00
        int  0x16
        ret
.none:
        mov  ax, 0xFFFF
        ret

fn0E:                           ; keyboard read, wait
        mov  ah, 0x00
        int  0x16
        ret

fn10:                           ; Ctrl-C/K check: never seen one
        xor  ax, ax
        ret

fn11:                           ; set cursor (DH row, DL col)
        push bx
        mov  ah, 0x02
        xor  bh, bh
        int  0x10
        pop  bx
        ret

fn12:                           ; read cursor
        push bx
        push cx
        mov  ah, 0x03
        xor  bh, bh
        int  0x10
        pop  cx
        pop  bx
        ret

fn13:                           ; write char to screen, teletype
        push bx
        mov  ah, 0x0E
        xor  bh, bh
        int  0x10
        pop  bx
        ret

fn18:                           ; read block: ES:DI, CX max -> AX read
        xor  si, si             ; SI counts
.loop:
        cmp  si, cx
        jae  .out
        call rda
        jz   .out
        call getb
        mov  [es:di], al
        inc  di
        inc  si
        jmp  .loop
.out:
        mov  ax, si
        ret

fn19:                           ; write block: ES:DI, CX -> AX written
        xor  si, si
.loop:
        cmp  si, cx
        jae  .out
        mov  al, [es:di]
        inc  di
        call putb
        inc  si
        jmp  .loop
.out:
        mov  ax, si
        ret

fn1B:                           ; driver info into ES:DI (CX bytes)
        push bx
        push cx
        mov  si, info
        mov  bx, cx
        cmp  bx, info_len
        jbe  .cap
        mov  bx, info_len
.cap:
        mov  cx, bx
        jcxz .none
.copy:
        lodsb
        mov  [es:di], al
        inc  di
        loop .copy
.none:
        mov  ax, bx
        pop  cx
        pop  bx
        ret

; ---- resident data -----------------------------------------------------

have    db 0                    ; read-ahead byte present?
held    db 0                    ; the byte itself

info:                           ; FTS-0061 driver info block
        dw info_len             ; structure size
        db 5                    ; spec version
        db 1                    ; driver revision
        dd 0                    ; id string: none
        dw 1, 1                 ; input buffer size / free (virtual)
        dw 1, 1                 ; output buffer size / free
        db 80, 25               ; screen width, height
        db 1                    ; baud mask: whatever you like
info_len equ $ - info

dispatch:
        dw fn00, fn01, fn02, fn03, fn04, fn05, fn06, fn07
        dw fn08, fn09, fn0A, fn0B, fn0C, fn0D, fn0E, fn0F
        dw fn10, fn11, fn12, fn13, fn03, fn03, fn03, fn03
        dw fn18, fn19, fn1A, fn1B

resident_end:

; ---- transient installer ----------------------------------------------

install:
        ; hook INT 14h
        mov  ax, 0x3514
        int  0x21               ; ES:BX = old vector (kept only politely)
        mov  ax, 0x2514
        mov  dx, handler
        int  0x21
        mov  dx, banner
        mov  ah, 0x09
        int  0x21
        ; stay resident: everything from the PSP through resident_end
        ; (the PSP is 0x100 bytes; label differences keep NASM's
        ; division happy)
        mov  dx, (resident_end - $$ + 0x100 + 15) / 16
        mov  ax, 0x3100
        int  0x21

banner  db 'ATDT FOSSIL 1.0 (FTS-0061) on COM1', 13, 10, '$'
