        .text
*****************************************************************
**                                                              *
**                   sage cpm bios interface                    *
**                   -----------------------                    *
**                                                              *
**      date      modification                                  *
**    ---------   ------------------------------------------    *
**    03-oct-82   initialization                                *
**    19-Jan-83   added hard disk support                       *
**    12-Feb-83   Buffer configuration                          *
**                LRU buffering scheme                          *
**                Ram disk support                              *
**                                                              *
*****************************************************************


* global definitions

        .globl  _ccp            ; CPM's CCP entry point
        .globl  _init           ;bios initialization routine

* trap equates

trap3:  .equ    $8c             ;trap 3 entry point
poll:   .equ    8               ;trap  8: poll requested device
keyin:  .equ    9               ;trap  9: console input routine
trmout: .equ    10              ;trap 10: console output routine
prtout: .equ    11              ;trap 11: printer output routine
remin:  .equ    12              ;trap 12: remote input routine
remout: .equ    13              ;trap 13: remote output routine
biosl:  .equ    14              ;trap 14: long bios trap command

* I/O byte standard initialization

fiobyte: .equ   0               ;assumed to be 0

* Word / Byte control definitions

highbyt:  .equ  $0              ;High byte offset of word
lowbyt:   .equ  $1              ;Low byte offset of word

* Sage bios definitions

dsk0def:  .equ  $0c             ;Disk 0-A: Configuration table offset
dsk0spc:  .equ  $1e             ;          CPM information offset
dsk1def:  .equ  $2c             ;Disk 1-B: Configuration table offset
dsk1spc:  .equ  $1e             ;          CPM information offset
dsk2def:  .equ  $00             ;Disk 2-C: Not configured
dsk2spc:  .equ  $00             ;          Not configured
dsk3def:  .equ  $00             ;Disk 3-D: Not configured
dsk3spc:  .equ  $00             ;          Not configured
rdskdef:  .equ  $58             ;Disk 4-E: Ram disk definition
rdskspc:  .equ  $08             ;          Ram disk flag
spcfix:   .equ  0               ;            Fixed / Removable flag

dskside:  .equ  0               ;Number of sides on disk
dskcyl:   .equ  1               ;Number of cylinders on disk
dsksec:   .equ  2               ;Number of sectors per track
dskbyt:   .equ  6               ;Number of bytes per sector
dbufsiz:  .equ  $6a             ;Offset to buffer size configuration
dnumbuf:  .equ  $6b             ;Offset to number of buf config
dramspc:  .equ  $6c             ;2 bytes - Offset to ramdisk def

* Buffer control definitions

bflag:    .equ  0               ;Buffer control flags
fuse:     .equ  0               ;  Buffer in use flag
fwrite:   .equ  1               ;  Buffer has been updated flag

bdsk:     .equ  1               ;Disk drive
blow:     .equ  2               ;Low sector (128 byte) address
bhigh:    .equ  6               ;High sector (128 byte) address
bfp:      .equ  10              ;Forward pointer
bbp:      .equ  12              ;Backward pointer
cntsize:  .equ  14              ;Length 14 bytes

* TPA definitions

tpa:     .equ   $400            ;tpa start address
tpalngh: .equ   1               ;length of tpa (10000)
tpalngl: .equ   $07c00

* fixed memory addressed

initbios: .equ  $0408           ;load address of sage bios
cpmadr:   .equ  $40c            ;CPM load address
bootdrv:  .equ  $410            ;boot drive
sbiosadr: .equ  $412            ;Bios load address

* bios entry routine

*                       d0.w : function code
*                       d1   : first parameter
*                       d2   : second parameter
*       trap    3
*                       all return registers destroyed

bios:
        move.l  a6,-(a7)        ;save a6
        lea     bios,a6         ;bios base address
        move.l  a7,oldstack     ;swap to internal stack
        move.l  #biostack,a7
        andi.w  $a000,sr        ;Enable interupts
        
* jump to requested bios routine

z1:
        lea     tranvec,a0      ;address of routine jump table
        cmpi.w  #22,d0          ;greater than max vector
        bgt     badvect         ;yes display error
        asl.w   #1,d0           ;convert index to offset
        adda.w  d0,a0
        movea.w (a0),a0         ;get bios relative address
        jsr     0(a0,a6.l)      ;execute requested function
        lea     bios,a6         ;assure a6 correct
        movea.l oldstack,a7     ;restore old stack
        movea.l (a7)+,a6        ;restore a6
        rte                     ;return to caller
        
* function code jump table:

tranvec:
        .dc.w   _init-bios      ;bios initialization routine
        .dc.w   wboot-bios      ;warm boot
        .dc.w   const-bios      ;console status
        .dc.w   conin-bios      ;console input routine
        .dc.w   conout-bios     ;console output
        .dc.w   list-bios       ;printer output
        .dc.w   punch-bios      ;punch output
        .dc.w   reader-bios     ;reader input
        .dc.w   home-bios       ;home disk (set to track 0)
        .dc.w   seldsk-bios     ;select disk drive
        .dc.w   settrk-bios     ;set track
        .dc.w   setsec-bios     ;set sector
        .dc.w   setdma-bios     ;set dma address
        .dc.w   read-bios       ;read disk
        .dc.w   write-bios      ;write disk
        .dc.w   listst-bios     ;printer status
        .dc.w   sectran-bios    ;sector translation
        .dc.w   badvec2-bios    ;illegal vector
        .dc.w   memreg-bios     ;returns address of memory region table
        .dc.w   getio-bios      ;gets i/o byte
        .dc.w   setio-bios      ;sets i/o byte
        .dc.w   flush-bios      ;flush disk buffers
        .dc.w   setexcep-bios   ;set exception vector

* bogas break routine

breakrt:
        rts

* cpm bios intialization routine

*       input:
*               d0.w : 0
*               a6.l : base address of bios
*       output:
*               all registers destroyed

_init:

* set up internal trap vector

        lea     bios,a0         ;trap address
        move.l  a0,trap3
        
* initialize i/o byte

        move.w  #fiobyte,iobyte     ;initial i/o byte value
        
* intialize bios hardware interfaces

        move.l  sbiosadr,biosdefs ;Save bios load address
        movea.l initbios,a0     ;jump to ccp
        jsr     (a0)            ;initialize bios
        moveq   #3,d0           ;Install bios in debugger I/O
        trap    #14
        moveq   #10,d0          ;Clear bios spc char handling
        lea     charinit,a0
        trap    #14

* Initializes TPA & buffer lengths

inittpa:
        move.l  cpmadr,d0       ;Load address of CPM

        move.l  sbiosadr,a1     ;Set address to Sagebios
        move.b  dbufsiz(a1),d1  ;Calc buffer size
        andi.w  #$1f,d1
        addq.w  #1,d1
        mulu    #512,d1         ;D1 - I/O buffer size
        move.l  d1,d2           ;Compute # 128 byte blocks / buffer
        divu    #128,d2
        move.w  d2,bufsize      ;Save calc
        move.b  dnumbuf(a1),d2  ;Calculate number of buffers
        andi.w  #$f,d2          ;Number of buffers - 1
        move.w  d2,numbuf       ;Saves number of buffers -1
        addq.w  #1,d2
        move.w  d2,d3
        mulu    #cntsize,d2     ;Calculate storage requirements
        sub.l   d2,d0           ;Lower TPA by amount
        move.l  d0,bufctl       ;Save buffer control address
        mulu    d3,d1           ;Storage requir of I/O buffers
        sub.l   d1,d0           ;End of TPA
        move.l  d0,iobufs       ;Save start of I/O buffers
        clr.w   bufhead         ;Clear head of buffers to zero

        moveq   #-cntsize,d1    ;Backward pointer
        moveq   #cntsize,d4     ;Set increment size
        moveq   #cntsize,d2     ;Forward pointer
        movea.l bufctl,a2       ;Address of buffer control
        clr.w   d3              ;Set index
        move.w  numbuf,d5       ;loop control
        beq     q4              ;If only one buffer skip first part
        subq.w  #1,d5           ;Do all but last
q3:
        clr.b   bflag(a2,d3.w)  ;Buffer not in use
        move.w  d1,bbp(a2,d3.w) ;Set backward pointer
        move.w  d2,bfp(a2,d3.w) ;Set forward pointer
        add.w   d4,d1           ;Move next
        add.w   d4,d2           ;Move next
        add.w   d4,d3           ;Move to next entry
        dbf     d5,q3
q4:
        clr.b   bflag(a2,d3.w)  ;Clear last
        move.w  d1,bbp(a2,d3.w) ;Set backward pointer
        move.w  #-1,bfp(a2,d3.w) ;Set backward pointer

        lea     dramdef,a3      ;Ramdisk definition
        clr.b   dskside(a3)     ;Assume ramdisk disabled
        tst.l   rdskdef(a1)     ;Set TPA size
        beq     q10             ;No skip all ramdisk setup
        cmp.l   rdskdef(a1),d0  ;TPA must be >= minimum amount
        bge     q5              ;All ok
        moveq   #1,d1           ;Try one less buffer
        sub.b   d1,dnumbuf(a1)
        bge     inittpa         ;Retry unless down to one buffer
        clr.w   dnumbuf(a1)     ;Set to one buffer in use min
q5:
        lea     dramspc(a1),a2  ;Set address to ramdisk cpm def
        move.w  (a2),d1         ;Move cpm def into user area
        andi.w  #$ff80,d1       ;Allow no directory offset
        move.w  d1,rdskspc(a3)
        bsr     cpmdef          ;Unpack cpm definitions
        move.l  d6,d4           ;Save # of directory entries
        mulu    #32,d4          ;Calc number of bytes needed for dir
        beq     q10             ;No entries / No ram disk
        move.l  d0,d1           ;Get working copy of max storage
        sub.l   rdskdef(a1),d1  ;Size of ram disk in bytes
        ble     q10             ;No room for ram disk
        move.w  #1024,d2        ;Calculate block size
        lsl.w   d7,d2
        divu    d2,d1           ;Calculate # blocks in ramdisk
        divu    d2,d4           ;Calculate # blocks in directory
        add.w   #5,d4           ;Leave room for atleast some files
        cmp.w   d4,d1           ;Have enough room for ramdisk
        ble     q10             ;No / No ram disk
        move.l  rdskdef(a1),d0  ;Set Top of TPA 
        move.l  d0,ramdisk      ; and base of ramdisk
        lsl.w   d7,d1           ;Calculate number of tracks (8 sects)
        moveq   #1,d2           ;Start with one track
q7:
        cmpi.w  #$ff,d1         ;More than 256 tracks
        ble     q9              ;No done
        lsr.w   #1,d1           ;Halve tracks and double sides
        lsl.b   #1,d2
        bra     q7              ;Try again
q9:
        move.b  d1,dskcyl(a3)   ;Set number of cylinders
        move.b  d2,dskside(a3)
        subq.w  #1,d6           ;Erase directory
        move.l  d0,a0           ;Set directory base
q8:
        move.b  #$e5,(a0)       ;Mark dir entry deleted
        adda.w  #32,a0          ;Move to next entry
        dbf     d6,q8
q10:
        subi.l  #tpa,d0         ;Subtract TPA start address
        move.l  d0,tpalen       ;save in memory table
        moveq   #10,d2          ;convert to K bytes
        asr.l   d2,d0
        lea     log1msg,a1      ;setup buffer pointer
q1:
        andi.l  #$ffff,d0       ;use lower 16 bits
        divu    d2,d0           ;convert digit
        tst.l   d0              ;done?
        beq     q2              ;yes
        move.l  d0,d1
        swap    d1
        add.b   #"0",d1
        move.b  d1,-(a1)
        bra     q1
q2:
        lea     logmsg,a0       ;print logon message
        bsr     prttxt          ;print message
        bsr     clrbuf          ;Clears floppy buffers
        move.w  bootdrv,d0      ;Select boot floppy
        and.w   #1,d0
        rts

        
*warm boot routine

*       input:
*               a6.l : base address of bios
*

wboot:
        bsr     clrbuf          ;clears floppy buffers
        move.l  oldstack,a7     ;restore stack
        jmp     _ccp            ;goto ccp

* Clear floppy buffers

clrbuf:
        clr.l   d3              ;Set to beginning of buffer
        movea.l bufctl,a3       ;Set to beginning of buffers
        move.w  numbuf,d5       ;Number of buffers to process
c1:
        clr.b   bflag(a3,d3.w)  ;Release buffer
        add.w   #cntsize,d3     ;Move to next entry
        dbf     d5,c1
        rts                     ;return
        
* console status routine

*       input:
*               a6.l : base address of bios
*       output:
*               d0.w : console status
*                       00 : no characters waiting
*                       ff : character waiting

const:
        moveq   #1,d0           ;request keyboard status
        trap    #poll           ;poll requested routine
        beq.b   e1              ;no characters waiting
        move.w  #$0ff,d0        ;set characters waiting
        rts
e1:
        clr.w   d0              ;set no characters waiting
        rts
        
*console input routine

*       input:
*               a6.l : bios base address
*       output:
*               d0.w : character received

conin:
        moveq   #1,d0           ;request keyboard status
        trap    #poll           ;poll requested routine
        beq.b   conin           ;no characters waiting
        trap    #keyin          ;get char from keyboard routine
        andi.w  #$7f,d0         ;allow only lower 7 bits
        rts
        
* console output routine

*       input:
*               a6.l : bios base address
*               d1.w : character to be sent to console

conout:
        move.b  d1,d0           ;move char to proper register
        trap    #trmout
        rts
        
*printer output routine

*       input:
*               a6.l : bios base address
*               d1.w : character to be sent to printer

list:
        move.b  d1,d0           ;move char to proper register
        trap    #prtout
        rts                     ;currently unemplimented
        
* punch output routine

*       input:
*               a6.l : bios base address
*               d1.w : character to be sent to remote channel

punch:
        move.b  d1,d0           ;Set register for output
        trap    #remout         ;Send character to remote ser chan
        rts
        
* reader input routine

*       input:
*               a6.l : bios base address
*       output:
*               d0.b : character returned by remote channel

reader:
        trap    #remin          ;Read character from rem ser chan
        rts
        
* home disk

*       input:
*               a6.l : bios base address

home:
        clr.w   track    
        bsr     setreg          ;Setup registers for home operation
        beq     home1           ;Internal definition
        bsr     setupio         ;setup for home function
        moveq   #10,d0
        trap    #biosl
        clr.w   d0
home1:
        rts
        
* select disk drive

*       input:
*               a6.l : bios base address
*               d1.b : disk selected
*                       00 = a:
*                       01 = b:
*               d2.b : bit 0: 0 = first access
*                             1 = following accessed
*       output:
*               d0.l : address of disk parameter block
*                       0 = illegal disk drive selection

seldsk:
        clr.l   d0              ;assume error detected
        andi.w  #$0ff,d1        ;force to byte quanity
        move.w  d1,a3           ;Save unit number
        cmpi.b  #dpbsiz,d1      ;selected disk > max
        bgt     y5              ;yes return error
        bsr     compdpb         ;Compute DPB table address
        btst    #0,d2           ;First disk access?
        bne     y1              ; No - Return
        tst.w   dskdef(a5)      ;Disk configured in CPM
        beq     y5              ; No - Return error
        lea     dramdef,a1      ;Assume ramdisk definition
        move.w  dskchan(a5),defchan ;Set channel for request and
*                                   ;  test for Ram disk
        beq     y6              ; Yes - Skip setup as ramdisk set
        movem.l d0/a5,-(a7)     ;Save registers
        moveq   #10,d0          ;Reset disk system
        lea     reqpkt,a0
        move.w  dskchan(a5),(a0)
        trap    #biosl
        moveq   #11,d0          ;Get disk definition
        lea     defpkt,a0
        trap    #biosl
        movem.l (a7)+,d0/a5     ;Restore registers
        lea     defbuf,a1       ;Set working buffer
y6:
        movea.l a1,a2
        add.w   dskspc(a5),a2   ;Pointer to Sage bios CPM def
        clr.l   d5              ;Get # sectors
        move.b  dsksec(a1),d5
        beq     y5              ;Disk unconfigured in Sagebios
        clr.l   d4              ;Get # bytes per sector
        move.w  dskbyt(a1),d4
        mulu    d5,d4           ;Compute 128 byte sectors per track
        move.l  d4,d3
        lsr.l   #7,d3           ; divide 128
        move.w  d3,spt(a5)      ;Save sectors per track
        
        bsr     cpmdef          ;Unpack CPM definitions
        beq     y5              ;No Directory entries
        addq.b  #3,d7           ;Block shift factor
        move.b  d7,bsh(a5)      

        moveq   #1,d2           ;Block mask
        lsl.b   d7,d2
        subq.w  #1,d2
        move.b  d2,blm(a5)

        move.w  d5,off(a5)      ;Directory track offset

        subq.w  #1,d6           ;Directory entries - 1
        move.w  d6,drm(a5)
        addq.w  #1,d6

        move.w  d6,d2           ;Directory check size
        lsr.w   #2,d2
        btst    #spcfix,lowbyt(a2) ;Fixed disk media
        sne     d3
        tst.l   dskchk(a5)      ;Allocation area allocated?
        sne     d1
        and.b   d1,d2
        and.b   d3,d2
        andi.w  #$ff,d2         ;Force less than 256
        move.w  d2,chk(a5)

        clr.l   d1              ;Disk block size
        clr.l   d2
        move.b  dskside(a1),d1  ;  Compute total tracks
        beq     y5              ;  Disk drive configured
        move.b  dskcyl(a1),d2
        mulu    d2,d1
        sub.l   d5,d1           ;  Available tracks
        mulu    d4,d1           ;  Total bytes per disk
        move.w  #128,d2         ;  Compute block size
        lsl.w   d7,d2
        divu    d2,d1           ;  Blocks per disk
        subq.w  #1,d1           ;  Calculate actual value
        move.w  d1,dsm(a5)

        move.l  d2,d4           ;Extent mask
        move.l  d1,d3
        lsr.w   #8,d3           ;  Block size > 255
        beq     y2
        lsr.w   #1,d4           ;  Yes - lower by one
y2:
        divu    #1024,d4
        subq.w  #1,d4
        blt     y5              ;Illegal value
        move.b  d4,exm(a5)

        moveq   #4,d3           ;Directory Allocation bit map
        lsl.w   d7,d3
        divu    d3,d6
        move.l  d6,d1           ;If remainder add extra block
        swap    d1
        tst.w   d1              ;Remainder?
        bne     y4              ;Yes
        subq.w  #1,d6
y4:
        clr.l   d1
y3:
        bset    #0,d1
        ror.w   #1,d1
        dbf     d6,y3
        move.w  d1,al0(a5)
y1:
        move.l  a5,d0           ;Return table location
        move.w  a3,unit         ;Save unit number
y5:
        rts

* Compute DPB table address

*                       d1.w : unit number of device
*       bsr     compdpb
*                       d1.l : ?
*                       a5.l : Table address

compdpb:
        mulu    #dpblen,d1      ;Compute relative position
        lea     dpbase,a5       ;Compute absolute position
        adda.l  d1,a5
        rts

* Unpack CPM definitions

*                       a2.l : Address of CPM definition
*       bsr     cpmdef
*                       a2.l : Unchanged
*                       d5.l : Directory offset (in tracks)
*                       d6.l : Number of directory entries
*                       d7.l : Logical block size
*                          Z : No directory entries
*                         NZ : Directory entries found

cpmdef:
        clr.l   d7              ; d5 : Directory offset
        move.w  (a2),d7
        lsr.w   #4,d7
        move.l  d7,d5
        andi.w  #7,d5
        lsr.w   #3,d7           ; d6 : Number of directory entries
        move.l  d7,d6
        andi.w  #63,d6
        lsl.l   #5,d6
        lsr.w   #6,d7           ; d7 : Logical block size
        andi.w  #7,d7
        tst.w   d6              ;Any directory entries
        rts
        
* set track

*       input:
*               a6.l : bios base address
*               d1.w : track selected

settrk:
        move.w  d1,track        ;save track address
        rts
        
*set sector

*       input:
*               a6.l : bios base address
*               d1.w : sector selected

setsec:
        move.w  d1,sector       ;save sector address
        rts
        
* set dma address (disk i/o memory address)

*       input:
*               a6.l : bios base address
*               d1.l : dma address

setdma:
        move.l  d1,dmaadr       ;save dma address
        rts
        
* read 128 bytes from disk

*       input:
*               a6.l : bios base address
*       output:
*               d0.w : error status
*                       0 : no errors occured
*                      >0 : errors occured

read:
        bsr     setreg         ;Setup registers for I/O
        bne     f1             ;Not ram disk
        bsr     setramd        ;Setup for ram disk
        bne     f4             ;Report errors if found
        bra     f3
f1:
        bsr     getbuf         ;Get buffer (fill if necessary)
        bne     f4             ;Error detected report it
        bsr     transet        ;Setup for transfer into user buffer
f3:
        move.b  (a1)+,(a0)+    ;Transfer info
        dbf     d1,f3
f4:
        rts

* write 128 bytes to disk

*       input:
*               a6.l : bios base address
*               d1.b : write type
*                       0 : data to be written to standard sector
*                       1 : data to be written to directory
*                       2 : data to be written to first sector of new
*                           block
*       output:
*               d0.w : error status
*                       0 : no errors occured
*                      >0 : errors occured

write:
        move.w  d1,wrtflag      ;save output flag
        bsr     setreg          ;Setup register for I/O
        bne     g3              ;Not ram disk
        bsr     setramd         ;Setup for ramdisk transfer
        bne     g2              ;Report any errors
        bra     g1              ;Transfer data
g3:
        bsr     getbuf          ;Get buffer fill it if necessary
        bne     g2              ;Error detected report it
        bset    #fwrite,bflag(a3,d3.w) ;Set write flag
        bsr     transet         ;Set up registers to transfer data
g1:
        move.b  (a0)+,(a1)+     ;Transfer data into buffer
        dbf     d1,g1
        move.w  wrtflag,d1      ;Directory operation
        subq.b  #1,d1
        bne     g2              ;No
        bsr     dumpbuf         ;Yes - dump buffer
g2:
        tst.w   d0              ;Report error
        rts

* Setup registers for I/O

*       bsr     setreg
*                       a3.l : Set buffer control area
*                       a4.l : I/O buffer base address
*                       d3.w : Most reciently used buffer offset
*                       a5.l : Address of current disk parameter block
*                       d5.l : 128 byte sector requested 
*                          Z : Ram disk channel
*                         NZ : External channel

setreg:
        movea.l bufctl,a3       ;Set address of buffer control
        movea.l iobufs,a4       ;Base address of I/O buffer
        move.w  bufhead,d3      ;Header buffer
        move.w  unit,d1         ;Calc address of DPB
        bsr     compdpb
        move.w  track,d5        ;Calc 128 byte sector address
        mulu    spt(a5),d5
        clr.l   d1
        move.w  sector,d1
        add.l   d1,d5           ;d5 - 128 byte sector address
        tst.w   dskchan(a5)     ;Return channel type
        rts

* Ram disk calculation routine

*                       a5 : Ramdisk disk parameter block
*       bsr     setramd
*                       d4.l : Address of data to transfer
*                       a1.l : Internal buffer address
*                       d1.l : 127 (transfer size - 1)
*                       a0.l : User buffer address
*                       d0.w : 0 -> No errors

setramd:
        moveq    #1,d0           ;Assume errors
        clr.w    wrtflag         ;Set never directory
        move.w   track,d4        ;Calculate sector offset
        mulu     spt(a5),d4
        clr.l    d1              ;Include sector
        move.w   sector,d1
        add.l    d1,d4
        lsl.l    #7,d4           ;Convert to byte offset
        add.l    ramdisk,d4      ;Add in base of ramdisk
        cmp.l    iobufs,d4       ;Gone too far?
        bge      f20             ;Yes report it
        clr.l    d0              ;Report all OK
        movea.l  dmaadr,a0       ;Set user buffer address
        moveq    #127,d1
        move.l   d4,a1           ;Set internal address
f20:
        tst.w    d0              ;Return error status
        rts

* Gets buffer needed for I/O reads if not already in memory

*                       d5.l : Sector to be read
*                       a3.l : Base of buffer control
*                       a4.l : Base of I/O buffer
*       bsr     getbuf
*                       d5.l : Unchanged
*                       a3.l : Unchanged
*                       a4.l : Unchanged
*                       d3.w : Offset to currently used buffer control
*                       d4.l : Offset to currently used buffer
*                       d0.w : = 0 -> no error
*                              # 0 -> error during I/O

getbuf:
        move.l  d5,-(a7)        ;Save register
        bsr     findmatch       ;Search for matching disk & sector
        beq     f18             ;Found one process it
        btst    #fuse,bflag(a3,d3.w) ;Buffer in use?
        beq     f10             ;No - Skip reading it
        btst    #fwrite,bflag(a3,d3.w) ; Buffer need to be flushed?
        beq     f10             ;No - Skip dumping it
        bsr     dumpbuf         ;Dump buffer
        bne     f18             ;If error detected report it
f10:
        clr.l   d1
        move.w  sector,d1       ;Calculate starting sector
        clr.l   d2
        move.w  bufsize,d2
        clr.l   d7
        move.w  spt(a5),d7      ;Sectors per track
        divu    d2,d1           ;Sector offset on track
        mulu    d2,d1
        move.w  track,d0        ;Calculate absolute sector to read 
        mulu    d7,d0
        move.l  d0,d6           ;Last abs sector on track
        add.l   d7,d6
        add.l   d1,d0           ;Absolute sector to start reading
        move.l  d0,blow(a3,d3.w)
        add.l   d2,d0           ;Last possible sector to read
        cmp.l   d0,d6           ;Will read cross track boundry?
        bgt     f11             ;No
        move.l  d6,d0           ;Use lesser of two
f11:
        subq.l  #1,d0           ;Set actual upgrade
        move.l  d0,bhigh(a3,d3.w)
        bset    #fuse,bflag(a3,d3.w) ;Set sector in use
        move.b  unit+1,bdsk(a3,d3.w)   ;Set drive to use
        bsr     fillbuf         ;Fill buffer
f18:
        move.l  (a7)+,d5        ;Restore d5
        tst.w   d0              ;Any errors
        rts

* internal to user buffer transfer setup
* Find I/O buffer or set to least recently used buffer

*                       a3.l : Base of I/O buffer control
*                       d5.l : Sector searched for
*       bsr     findmatch
*                       a3.l : Unchanged
*                       d5.l : Unchanged
*                       d3.l : Offset to matching sector /
*                              LRU sector
*                       d4.l : Offset to I/O buffer
*                       d0.w : 0 -> found sector
*                              #0 -> LRU sector given

findmatch:
        move.w  bufhead,d3      ;Set Head of Buffer list MRU
        move.w  unit,d2         ;Set disk drive requested
        clr.l   d0              ;Assume no errors
g10:
        btst    #fuse,bflag(a3,d3.w) ;Buffer in use?
        beq     g18             ;No - skip further testing
        cmp.b   bdsk(a3,d3.w),d2 ;Buffer for same disk?
        bne     g18             ;No - skip further testing
        cmp.l   blow(a3,d3.w),d5 ;Sector in range
        blt     g18             ;No
        cmp.l   bhigh(a3,d3.w),d5
        ble     g21             ;Found sector
g18:
        tst.w   bfp(a3,d3.w)    ;Tested last buffer?
        blt     g20             ;Reached last buffer
        move.w  bfp(a3,d3.w),d3 ;Move to next buffer
        bra     g10             ;Try again
g20:
        moveq   #1,d0           ;Buffer not found
g21:
        cmp.w   bufhead,d3      ;Sector already MRU sector?
        beq     g22
        move.w  bfp(a3,d3.w),d1 ;Get next pointer
        move.w  bbp(a3,d3.w),d2 ;Get last pointer
        move.w  d1,bfp(a3,d2.w) ;Remove current buffer from list
        blt     g23             ;No next - skip link update
        move.w  d2,bbp(a3,d1.w)
g23:
        move.w  bufhead,d1      ;Add to front of list
        move.w  d3,bufhead      ;Set new header sector
        move.w  bbp(a3,d1.w),bbp(a3,d3.w) ;Set current buf backptr
        move.w  d1,bfp(a3,d3.w)
        move.w  d3,bbp(a3,d1.w)
g22:
        bsr     compboff        ;Calculate buffer offset
        tst.w   d0              ;Report any errors
        rts

* Calculate buffer offset

*                       d3.w : Buffer control offset
*      bsr      compbuff
*                       d4.l : Buffer offset

compboff:
        clr.l   d4              ;Calculate buffer offset
        move.w  d3,d4
        divu    #cntsize,d4
        mulu    bufsize,d4
        lsl.l   #7,d4           ;Convert to byte offset
        rts


*                       a4.l : base of I/O buffer
*                       d4.l : offset to beginning of buffer
*                       a3.l : Base of buffer control
*                       d3.w : Offset to buffer
*       bsr     transet
*                       a4.l : Unchanged
*                       d4.l : Unchanged
*                       a3.l : Unchanged
*                       d3.w : Unchanged
*                       a1.l : Address of internal buffer data
*                       a0.l : Address of user buffer
*                       d1.w : Number of bytes to transfer (128-1)

transet:
        movea.l dmaadr,a0       ;Setup user buffer address
        movea.l a4,a1           ;Calc internal CPM address
        adda.l  d4,a1
        move.l  d5,d1           ;Computer buffer offset
        sub.l   blow(a3,d3.w),d1
        mulu    #128,d1
        adda.l  d1,a1
        moveq   #127,d1         ;number of bytes to transfer
        rts
        
* fills internal input buffer with block from disk

*                       a6.l : bios base address
*       bsr     fillbuf
*                       all registers except a2,a4,a6,d2,d4
*                       d0.w : contains error status

fillbuf:
        movem.l a3-a5/d3-d6,-(a7) ;save registers
        bsr     setupio         ;setup for i/o
        moveq   #11,d0          ;call floppy read routine
        trap    #biosl
        movem.l (a7)+,a3-a5/d3-d6 ;restore registers
        lea     reqpkt,a0
        move.w  2(a0),d0        ;get result
        rts

* dumps internal output buffer to block from disk

*                       a6.l : bios base address
*       bsr     dumpbuf
*                       all registers except a2,a4,a6,d2,d4
*                       d0.w : contains error status

dumpbuf:
        movem.l a3-a5/d3-d6,-(a7) ;save registers
        bclr    #fwrite,fuse(a3,d3.w) ;Clear write flag
        bsr     setupio         ;setup for i/o
        moveq   #12,d0          ;call floppy read routine
        trap    #biosl
        movem.l (a7)+,a3-a5/d3-d6 ;restore registers
        lea     reqpkt,a0
        move.w  2(a0),d0        ;get result
        rts

* sets registers and request packet for i/o request

*                       a6.l : bios base address
*                       d0.l : track & sector address
*       bsr     setupio
*                       a0.l : request packet for i/o
*                       d0.w : ?
*                       d1.w : ?

setupio:
        move.l  blow(a3,d3.w),d0  ;setup sector
        move.l  d0,d2
        lsr.l   #2,d0           ;convert to sector number
        lea     reqpkt,a0       ;address of request packet
        move.w  d0,12(a0)       ;save in packet
        clr.l   d1              ;Calc dpb offset
        move.b  bdsk(a3,d3.w),d1
        bsr     compdpb
        move.w  dskchan(a5),0(a0)
        move.l  bhigh(a3,d3.w),d1 ;Compute transfer size
        sub.l   d2,d1
        addq.l  #1,d1
        lsl.l   #7,d1
        move.l  d1,4(a0)        ;transfer length
        move.w  #$0c,14(a0)     ;transfer flags
        movea.l a4,a2           ;Set transfer address
        adda.l  d4,a2
        move.l  a2,8(a0)
        rts

* printer status

*       input:
*               a6.l : bios base address
*       output:
*               d0.w : console status
*                       00 : printer not ready for new character
*                       ff : printer ready to accept new character

listst:
        moveq   #6,d0           ;request printer status
        trap    #poll           ;poll requested routine
        beq.b   h1              ;no characters waiting
        clr.w   d0              ;set no characters waiting
        rts
h1:
        move.w  #$0ff,d0        ;set characters waiting
        rts

* sector translation routine

*       input:
*               a6.l : bios base address
*               d1.w : logical sector address
*               d2.l : address of sector translation table
*       output:
*               d0.w : physical sector address

sectran:
        move.w  d1,d0           ;assume no translation
        tst.l   d2              ;any sector translation needed
        beq.b   i1              ;no
        movea.l d2,a0           ;setup for physical sector retreive
        move.b  0(a0,d1.w),d0   ;get physical sector
i1:
        rts

* return memory region table address

*       input:
*               a6.l : bios base address
*       output:
*               d0.l : address of memory region table

memreg:
        lea     memreg1,a0      ;set address of memory region table
        move.l  a0,d0
        rts
        
* get i/o byte

*       input:
*               a6.l : bios base address
*       output:
*               d0.w : 8 bit i/o byte

getio:
        move.w  iobyte,d0       ;get i/o byte
        rts

* set i/o byte

*       input:
*               a6.l : bios base address
*               d1.w : 8 bit i/o byte

setio:
        move.w  d1,iobyte       ;get i/o byte
        rts
        
* flush buffers

*       input:
*               a6.l : bios base address
*       output:
*               d0.w : error status
*                       0 : no errors occured
*                      >0 : errors occured

flush:
        clr.l   d3              ;Set to beginning of buffer
        movea.l bufctl,a3       ;Set to beginning of buffers
        movea.l iobufs,a4       ;Set to beginning of I/O buffers
        move.w  numbuf,d5       ;Number of buffers to process
        clr.l   d6              ;Clear error accumulator
j1:
        btst    #fuse,bflag(a3,d3.w) ;Buffer in use
        beq     j4              ;Buffer not in use
        btst    #fwrite,bflag(a3,d3.w) ;Buffer to be written
        beq     j4              ;Buffer not to be written
        bsr     compboff        ;Compute buffer offset
        bsr     dumpbuf         ;Write buffer
        or.w    d0,d6           ;Accumulate errors
j4:
        clr.b   bflag(a3,d3.w)  ;Release buffer
        add.w   #cntsize,d3     ;Move to next entry
        dbf     d5,j1
        move.w  d6,d0           ;Return error
        rts                     ;return

* set exception vectors

*       input:
*               a6.l : bios base address
*               d1.w : exception vector address
*               d2.l : address to execute exception vector

setexcep:
        andi.w  #$0ff,d1        ;assure vector address in range
        asl.w   #2,d1           ;convert index to offset
        movea.w d1,a0           ;set address
        move.l  (a0),d0         ;Get orginal value
*       cmpi.w  #(9*4),d1       ;trace mode - test begin
*       beq     k2
        cmpi.w  #(47*4),d1      ;trap 15
        beq     k2              ;   - test end
        cmpi.w  #(25*4),d1      ;skip autovectors
        blt.b   k1
        cmpi.w  #(31*4),d1
        ble     k2
        cmpi.w  #(40*4),d1      ;less than trap 8
        blt.b   k1              ;process it
        cmpi.w  #(46*4),d1      ;between trap 8 and trap 14
        ble.b   k2              ;yes dont process it
k1:
        move.l  d2,(a0)
k2:
        rts

* bad command vector

*       input:
*               d0.w : vector number in error
*               a6.l : bios load address

badvec2:
        move.w  sr,-(a7)        ;convert to trap call
badvect:
        lea     vecterr,a0      ;print vector error
        move.w  d0,-(a7)        ;save number to be printed
        bsr.b   prttxt          ;print the message
        move.w  (a7)+,d0        ;restore number
        bsr.b   prtnum          ;print number
        rte
prttxt:
        move.b  (a0)+,d0        ;get byte to printe
        beq.b   l2              ;print value
        move.l  a0,-(a7)        ;save string address
        trap    #trmout
        movea.l (a7)+,a0        ;restore string address
        bra     prttxt
l2:
        rts
prtnum:
        moveq   #3,d2           ;print 4 bytes
        clr.l   d3              ;clear nonzero digit flag
l3:
        asl.w   #4,d0           ;print next digit
        moveq   #$0f,d1         ;set mask
        and.w   d0,d1           ;get character
        bne.b   l5              ;nonzero digit
        tst.b   d3              ;any other zeros
        bne.b   l5              ;non digits preceeded print 0
        moveq   #$20,d1         ;print a space
        bra.b   l4              ;print it
l5:
        moveq   #1,d3           ;set nonzero digit flag
        addi.w  #"0",d1         ;convert to digit
        cmpi.b  #"9",d1         ;greater than 9
        ble.b   l4              ;no
        addq.w  #7,d1           ;yes convert to a - f
l4:
        movem.w d0/d2/d3,-(a7)  ;save working registers
        move.b  d1,d0           ;print digit
        trap    #trmout
        movem.w (a7)+,d0/d2/d3  ;restore working registers
        dbf     d2,l3           ;repeat for all digits
        rts                     ;return

        .data
*bios data storage

dskcont:  .dc.l   0             ;DPB address for selected disk
oldstack: .dc.l   0             ;user stack
unit:    .dc.w    0             ;floppy unit number
track:   .dc.w   0              ;track address (trk & sec must keep 
sector:  .dc.w   0              ;sector address
dmaadr:  .dc.l   0              ;dma address
iobyte:  .dc.w   0              ;i/o byte
wrtflag: .dc.w   0              ;write control flag 0: data
biosdefs: .dc.l  0              ;Address of Sagebios
bufctl:  .dc.l  0               ;Base of I/O buffers control
iobufs:  .dc.l  0               ;Base of I/O buffers
ramdisk: .dc.l  0               ;Base of ramdisk
bufhead: .dc.w  0               ;Most current buffer
bufsize: .dc.w  0               ;# 128 byte blocks in I/O buffer
numbuf:  .dc.w  0               ;# of buffers -1

* Bios Messages

vecterr: .dc.b  $0d,$0a
        .dc.b  "bios error: vector error: "
        .dc.b   0
logmsg:  .dc.b  $0d,$0a,"CP/M - 68k  1.1  (     "
log1msg: .dc.b  "K )",$0d,$0a,0
        .even

* Console Initialization Area

charinit:
        .dc.w   1               ;Console channel
        .dc.w   0               ;return error
        .dc.l   breakrt         ;Break point routine
        .dc.l   chartrap        ;Character def array
chartrap:
        .dc.b   $00,$00,$00,$80 ;Clear trap
        .dc.b   0,0,0,0,0,0,0   ;Unused
        .dc.b   $7f             ;Character mask
        .even

* Disk parameter blocks

* disk parameter header for disk 0 (a:)

dpbase:
        .dc.l   0               ;logical to physical translation table
        .dc.w   0               ;scratch pad area
        .dc.w   0
        .dc.w   0
        .dc.l   dirbuf          ;directory scratch pad area
        .dc.l   dpba            ;disk parameter block
dskchk: .equ    *-dpbase
        .dc.l   csv00           ;disk change scrach pad area
        .dc.l   alv00           ;area for allocation area
dskchan: .equ   *-dpbase        ;Disk channel offset
        .dc.w   4               ;Drive A: = #4:
dskdef:  .equ   *-dpbase        ;Floppy configuration offset
        .dc.w   dsk0def
dskspc:  .equ   *-dpbase        ;CPM floppy configuration offset
        .dc.w   dsk0spc

* disk parameter block

dpba:
spt:    .equ    *-dpbase
        .dc.w   32              ;sectors per track
bsh:    .equ    *-dpbase
        .dc.b   4               ;block shift factor
blm:    .equ    *-dpbase
        .dc.b   15              ;block mask
exm:    .equ    *-dpbase
        .dc.b   0               ;extent mask
        .dc.b   0               ;dummy fill
dsm:    .equ    *-dpbase
        .dc.w   288             ;disk block size
drm:    .equ    *-dpbase
        .dc.w   63              ;total number of director entries
al0:    .equ    *-dpbase
        .dc.b   $80             ;director allocation
        .dc.b   0               ; 
chk:    .equ    *-dpbase
        .dc.w   16              ;check size
off:    .equ    *-dpbase
        .dc.w   2               ;track offset

dpblen: .equ    *-dpbase

* disk parameter header for disk 1 (b:)

        .dc.l   0               ;logical to physical translation table
        .dc.w   0               ;scratch pad area
        .dc.w   0
        .dc.w   0
        .dc.l   dirbuf          ;directory scratch pad area
        .dc.l   dpbb            ;disk parameter block
        .dc.l   csv01           ;disk change scrach pad area
        .dc.l   alv01           ;area for allocation area
        .dc.w   5               ;Drive B: = #5:
        .dc.w   dsk1def
        .dc.w   dsk1spc

* disk parameter block 

dpbb:
        .dc.w   32              ;sectors per track
        .dc.b   4               ;block shift factor
        .dc.b   15              ;block mask
        .dc.b   0               ;extent mask
        .dc.b   0               ;dummy fill
        .dc.w   288             ;disk block size
        .dc.w   63              ;total number of director entries
        .dc.b   $80             ;director allocation
        .dc.b   0               ; 
        .dc.w   16              ;check size
        .dc.w   2               ;track offset

* disk parameter header for hard disk 2 (c:)

        .dc.l   0               ;Logical to physical trans table
        .dc.w   0,0,0           ;Scratch area
        .dc.l   dirbuf          ;directory scratchpad area
        .dc.l   dpbc            ;hard disk parameter block
        .dc.l   0               ;No disk change scratch area
        .dc.l   alv02           ;Allocation area
        .dc.w   9               ;Disk channel = #9
        .dc.w   dsk2def         ;Disk def undefined in bios
        .dc.w   dsk2spc         ;CPM def undefined in bios

* disk parameter block (hard disks)

dpbc:
        .dc.w   32              ;Sectors per track
        .dc.b   5               ;Block shift factor
        .dc.b   31              ;block mask
        .dc.b   1               ;extent mask
        .dc.b   0               ;dummy
        .dc.w   1222            ;disk block size
        .dc.w   255             ;number of directory entries
        .dc.b   $c0             ;directory allocation
        .dc.b   0               ;
        .dc.w   0               ;Fixed disk no check size
        .dc.w   1               ;Track offset

* disk parameter header for hard disk 3 (d:)

        .dc.l   0               ;Logical to physical trans table
        .dc.w   0,0,0           ;Scratch area
        .dc.l   dirbuf          ;directory scratchpad area
        .dc.l   dpbd            ;hard disk parameter block
        .dc.l   0               ;No disk change scratch area
        .dc.l   alv03           ;Allocation area
        .dc.w   10              ;Disk channel = #10:
        .dc.w   dsk3def         ;Disk undefined in bios
        .dc.w   dsk3spc         ;CPM definition undefined in bios

* disk parameter block

dpbd:
        .dc.w   32              ;Sectors per track
        .dc.b   5               ;Block shift factor
        .dc.b   31              ;block mask
        .dc.b   1               ;extent mask
        .dc.b   0               ;dummy
        .dc.w   1222            ;disk block size
        .dc.w   255             ;number of directory entries
        .dc.b   $c0             ;directory allocation
        .dc.b   0               ;
        .dc.w   0               ;Fixed disk no check size
        .dc.w   1               ;Track offset

* disk parameter header for ram disk 4 (e:)

        .dc.l   0               ;Logical to physical trans table
        .dc.w   0,0,0           ;Scratch area
        .dc.l   dirbuf          ;directory scratchpad area
        .dc.l   dpbe            ;hard disk parameter block
        .dc.l   0               ;No disk change scratch area
        .dc.l   alv04           ;Allocation area
        .dc.w   0               ;Disk channel = Ram disk 
        .dc.w   rdskdef         ;Disk undefined in bios
        .dc.w   rdskspc         ;CPM definition undefined in bios

* disk parameter block

dpbe:
        .dc.w   32              ;Sectors per track
        .dc.b   5               ;Block shift factor
        .dc.b   31              ;block mask
        .dc.b   1               ;extent mask
        .dc.b   0               ;dummy
        .dc.w   1222            ;disk block size
        .dc.w   255             ;number of directory entries
        .dc.b   $c0             ;directory allocation
        .dc.b   0               ;
        .dc.w   0               ;Fixed disk no check size
        .dc.w   0               ;Track offset

dpbsiz: .equ    (*-dpbase)/dpblen-1

* Ram disk definition

dramdef:
         .dc.b   0              ;Number of sides
         .dc.b   0              ;Number of cylinders
         .dc.b   8              ;Sectors per track
         .dc.b   0,0,0          ;Unused
         .dc.w   128            ;Bytes per sector
         .dc.w   0              ;CPM definition

* Disk configuration request packet

defpkt:   .dc.w  128            ;Channel number
          .dc.w  0              ;Error return - Unused
          .dc.l  0              ;Transfer length - Unused
          .dc.l  defbuf         ;Configuration buffer
          .dc.w  0              ;Block address - Unused
defchan:  .dc.w  0              ;Disk channel (set by request)

* Disk configuration buffer

defbuf:   .ds.b  32             ;Disk configuration buffer

*memory region table


memreg1:
        .dc.w   1               ;entry 1
        .dc.w   0               ;tpa beginning address
        .dc.w   tpa
tpalen: .dc.w   tpalngh         ;tpa length
        .dc.w   tpalngl
        
        .bss
* allocation area

alv00:  .ds.b   2048/8+1        ;allocation area (floppy a:)
        .even
alv01:  .ds.b   2048/8+1        ;allocation area (floppy b:)
        .even
alv02:  .ds.b   2048/8+1        ;allocation area (hard c:)
        .even
alv03:  .ds.b   2048/8+1        ;allocation area (hard d:)
        .even
alv04:  .ds.b   2048/8+1        ;allocation area (ramdisk e:)
        .even                   ;Force to even boundry

* check area

csv00:  .ds.b   256/4           ;check area (floppy a:)
csv01:  .ds.b   256/4           ;check area (floppy b:)

* directory buffer

dirbuf: .ds.b   128

* disk routine request packet

**                                     ;floppy i/o request packet
reqpkt:
        .ds.b   2               ; channel
        .ds.b   2               ; error
        .ds.b   2               ; byte length
        .ds.b   2
        .ds.b   2               ; dma address
        .ds.b   2
        .ds.b   2               ; block address
        .ds.b   2               ; control word

* Internal bios stack 

        .ds.b   128
biostack:
        .ds.b   2
        .even
        .end
