These are the CP/M 3.0 source files with modifications by Visual to operate on the Visual 1050.
BIOSKRNL.ASM BOOT.ASM BUF.LIB CALLVERS.ASM CHARIO.ASM CPM3.LIB DRIVES.ASM DRVTBL.ASM ECHOVERS.ASM ENDER.ASM INTERRUP.ASM LABEL.ASM LDRBOOT.ASM LDRDRV.ASM LDRKRNL.ASM LDRMOVE.ASM LDRTBL.ASM MODEBAUD.LIB MOVE.ASM PORTS.LIB PRIVATE.ASM RANDOM.ASM SCB.ASM VISLIB.LIB Z80.LIB
title 'Root module of relocatable BIOS for CP/M 3.0'
; version 1.0 15 Sept 82
true equ -1
false equ not true
banked equ true
; Copyright (C), 1982
; Digital Research, Inc
; P.O. Box 579
; Pacific Grove, CA 93950
; This is the invariant portion of the modular BIOS and is
; distributed as source for informational purposes only.
; All desired modifications should be performed by
; adding or changing externally defined modules.
; This allows producing "standard" I/O modules that
; can be combined to support a particular system
; configuration.
cr equ 13
lf equ 10
bell equ 7
ctlQ equ 'Q'-'@'
ctlS equ 'S'-'@'
ccp equ 0100h ; Console Command Processor gets loaded into the TPA
cseg ; GENCPM puts CSEG stuff in common memory
; variables in system data page
extrn @covec,@civec,@aovec,@aivec,@lovec ; I/O redirection vectors
extrn @mxtpa ; addr of system entry point
extrn @bnkbf ; 128 byte scratch buffer
; initialization
extrn ?init ; general initialization and signon
extrn ?ldccp,?rlccp ; load & reload CCP for BOOT & WBOOT
; private Bios calls
extrn privcall
; user defined character I/O routines
extrn ?ci,?co,?cist,?cost ; each take device in <B>
extrn ?cinit ; (re)initialize device in <C>
extrn @ctbl ; physical character device table
; disk communication data items
extrn @dtbl ; table of pointers to XDPHs
public @adrv,@rdrv,@trk,@sect ; parameters for disk I/O
public @dma,@dbnk,@cnt ; '' '' '' ''
; memory control
public @cbnk ; current bank
extrn ?xmove,?move ; select move bank, and block move
extrn ?bank ; select CPU bank
; clock support
extrn ?time ; signal time operation
; general utility routines
public ?pmsg,?pdec ; print message, print number from 0 to 65535
public ?pderr ; print BIOS disk error message header
maclib modebaud ; define mode bits
; External names for BIOS entry points
public ?boot,?wboot,?const,?conin,?cono,?list,?auxo,?auxi
public ?home,?sldsk,?sttrk,?stsec,?stdma,?read,?write
public ?lists,?sctrn
public ?conos,?auxis,?auxos,?dvtbl,?devin,?drtbl
public ?mltio,?flush,?mov,?tim,?bnksl,?stbnk,?xmov
; BIOS Jump vector.
; All BIOS routines are invoked by calling these
; entry points.
?boot: jmp boot ; initial entry on cold start
?wboot: jmp wboot ; reentry on program exit, warm start
?const: jmp const ; return console input status
?conin: jmp conin ; return console input character
?cono: jmp conout ; send console output character
?list: jmp list ; send list output character
?auxo: jmp auxout ; send auxilliary output character
?auxi: jmp auxin ; return auxilliary input character
?home: jmp home ; set disks to logical home
?sldsk: jmp seldsk ; select disk drive, return disk parameter info
?sttrk: jmp settrk ; set disk track
?stsec: jmp setsec ; set disk sector
?stdma: jmp setdma ; set disk I/O memory address
?read: jmp read ; read physical block(s)
?write: jmp write ; write physical block(s)
?lists: jmp listst ; return list device status
?sctrn: jmp sectrn ; translate logical to physical sector
?conos: jmp conost ; return console output status
?auxis: jmp auxist ; return aux input status
?auxos: jmp auxost ; return aux output status
?dvtbl: jmp devtbl ; return address of device def table
?devin: jmp ?cinit ; change baud rate of device
?drtbl: jmp getdrv ; return address of disk drive table
?mltio: jmp multio ; set multiple record count for disk I/O
?flush: jmp flush ; flush BIOS maintained disk caching
?mov: jmp ?move ; block move memory to memory
?tim: jmp ?time ; Signal Time and Date operation
?bnksl: jmp bnksel ; select bank for code execution and default DMA
?stbnk: jmp setbnk ; select different bank for disk I/O DMA operations.
?xmov: jmp ?xmove ; set source and destination banks for one operation
?priv jmp privcall ; Reserved for system implementor
jmp 0 ; reserved for future expansion
jmp 0 ; reserved for future expansion
; BOOT
; Initial entry point for system startup.
dseg ; this part can be banked
boot:
lxi sp,boot$stack
mvi c,15 ; initialize all 16 character devices
c$init$loop:
push b ! call ?cinit ! pop b
dcr c ! jp c$init$loop
call ?init ; perform any additional system initialization
; and print signon message
lxi b,16*256+0 ! lxi h,@dtbl ; init all 16 logical disk drives
d$init$loop:
push b ; save remaining count and abs drive
mov e,m ! inx h ! mov d,m ! inx h ; grab @drv entry
mov a,e ! ora d ! jz d$init$next ; if null, no drive
push h ; save @drv pointer
xchg ; XDPH address in <HL>
dcx h ! dcx h ! mov a,m ! sta @RDRV ; get relative drive code
mov a,c ! sta @ADRV ; get absolute drive code
dcx h ; point to init pointer
mov d,m ! dcx h ! mov e,m ; get init pointer
xchg ! call ipchl ; call init routine
pop h ; recover @drv pointer
d$init$next:
pop b ; recover counter and drive #
inr c ! dcr b ! jnz d$init$loop ; and loop for each drive
jmp boot$1
cseg ; following in resident memory
boot$1:
call set$jumps
call ?ldccp ; fetch CCP for first time
jmp ccp
; WBOOT
; Entry for system restarts.
wboot:
lxi sp,boot$stack
call set$jumps ; initialize page zero
call ?rlccp ; reload CCP
jmp ccp ; then reset jmp vectors and exit to ccp
set$jumps:
if banked
mvi a,1 ! call ?bnksl
endif
mvi a,JMP
sta 0 ! sta 5 ; set up jumps in page zero
lxi h,?wboot ! shld 1 ; BIOS warm start entry
lhld @MXTPA ! shld 6 ; BDOS system call entry
ret
ds 64
boot$stack equ $
; DEVTBL
; Return address of character device table
devtbl:
lxi h,@ctbl ! ret
; GETDRV
; Return address of drive table
getdrv:
lxi h,@dtbl ! ret
; CONOUT
; Console Output. Send character in <C>
; to all selected devices
conout:
lhld @covec ; fetch console output bit vector
jmp out$scan
; AUXOUT
; Auxiliary Output. Send character in <C>
; to all selected devices
auxout:
lhld @aovec ; fetch aux output bit vector
jmp out$scan
; LIST
; List Output. Send character in <C>
; to all selected devices.
list:
lhld @lovec ; fetch list output bit vector
out$scan:
mvi b,0 ; start with device 0
co$next:
dad h ; shift out next bit
jnc not$out$device
push h ; save the vector
push b ; save the count and character
not$out$ready:
call coster ! ora a ! jz not$out$ready
pop b ! push b ; restore and resave the character and device
call ?co ; if device selected, print it
pop b ; recover count and character
pop h ; recover the rest of the vector
not$out$device:
inr b ; next device number
mov a,h ! ora l ; see if any devices left
jnz co$next ; and go find them...
ret
; CONOST
; Console Output Status. Return true if
; all selected console output devices
; are ready.
conost:
lhld @covec ; get console output bit vector
jmp ost$scan
; AUXOST
; Auxiliary Output Status. Return true if
; all selected auxiliary output devices
; are ready.
auxost:
lhld @aovec ; get aux output bit vector
jmp ost$scan
; LISTST
; List Output Status. Return true if
; all selected list output devices
; are ready.
listst:
lhld @lovec ; get list output bit vector
ost$scan:
mvi b,0 ; start with device 0
cos$next:
dad h ; check next bit
push h ; save the vector
push b ; save the count
mvi a,0FFh ; assume device ready
cc coster ; check status for this device
pop b ; recover count
pop h ; recover bit vector
ora a ; see if device ready
rz ; if any not ready, return false
inr b ; drop device number
mov a,h ! ora l ; see if any more selected devices
jnz cos$next
ori 0FFh ; all selected were ready, return true
ret
coster: ; check for output device ready, including optional
; xon/xoff support
mov l,b ! mvi h,0 ; make device code 16 bits
push h ; save it in stack
dad h ! dad h ! dad h ; create offset into device characteristics tbl
lxi d,@ctbl+6 ! dad d ; make address of mode byte
mov a,m ! ani mb$xonxoff
pop h ; recover console number in <HL>
jz ?cost ; not a xon device, go get output status direct
lxi d,xofflist ! dad d ; make pointer to proper xon/xoff flag
call cist1 ; see if this keyboard has character
mov a,m ! cnz ci1 ; get flag or read key if any
cpi ctlq ! jnz not$q ; if its a ctl-Q,
mvi a,0FFh ; set the flag ready
not$q:
cpi ctls ! jnz not$s ; if its a ctl-S,
mvi a,00h ; clear the flag
not$s:
mov m,a ; save the flag
call cost1 ; get the actual output status,
ana m ; and mask with ctl-Q/ctl-S flag
ret ; return this as the status
cist1: ; get input status with <BC> and <HL> saved
push b ! push h
call ?cist
pop h ! pop b
ora a
ret
cost1: ; get output status, saving <BC> & <HL>
push b ! push h
call ?cost
pop h ! pop b
ora a
ret
ci1: ; get input, saving <BC> & <HL>
push b ! push h
call ?ci
pop h ! pop b
ret
; CONST
; Console Input Status. Return true if
; any selected console input device
; has an available character.
const:
lhld @civec ; get console input bit vector
jmp ist$scan
; AUXIST
; Auxiliary Input Status. Return true if
; any selected auxiliary input device
; has an available character.
auxist:
lhld @aivec ; get aux input bit vector
ist$scan:
mvi b,0 ; start with device 0
cis$next:
dad h ; check next bit
mvi a,0 ; assume device not ready
cc cist1 ; check status for this device
ora a ! rnz ; if any ready, return true
inr b ; drop device number
mov a,h ! ora l ; see if any more selected devices
jnz cis$next
xra a ; all selected were not ready, return false
ret
; CONIN
; Console Input. Return character from first
; ready console input device.
conin:
lhld @civec
jmp in$scan
; AUXIN
; Auxiliary Input. Return character from first
; ready auxiliary input device.
auxin:
lhld @aivec
in$scan:
push h ; save bit vector
mvi b,0
ci$next:
dad h ; shift out next bit
mvi a,0 ; insure zero a (nonexistant device not ready).
cc cist1 ; see if the device has a character
ora a
jnz ci$rdy ; this device has a character
inr b ; else, next device
mov a,h ! ora l ; see if any more devices
jnz ci$next ; go look at them
pop h ; recover bit vector
jmp in$scan ; loop til we find a character
ci$rdy:
pop h ; discard extra stack
jmp ?ci
; Utility Subroutines
ipchl: ; vectored CALL point
pchl
?pmsg: ; print message @<HL> up to a null
; saves <BC> & <DE>
push b
push d
pmsg$loop:
mov a,m ! ora a ! jz pmsg$exit
mov c,a ! push h
call ?cono ! pop h
inx h ! jmp pmsg$loop
pmsg$exit:
pop d
pop b
ret
?pdec: ; print binary number 0-65535 from <HL>
lxi b,table10! lxi d,-10000
next:
mvi a,'0'-1
pdecl:
push h! inr a! dad d! jnc stoploop
inx sp! inx sp! jmp pdecl
stoploop:
push d! push b
mov c,a! call ?cono
pop b! pop d
nextdigit:
pop h
ldax b! mov e,a! inx b
ldax b! mov d,a! inx b
mov a,e! ora d! jnz next
ret
table10:
dw -1000,-100,-10,-1,0
?pderr:
lxi h,drive$msg ! call ?pmsg ; error header
lda @adrv ! adi 'A' ! mov c,a ! call ?cono ; drive code
lxi h,track$msg ! call ?pmsg ; track header
lhld @trk ! call ?pdec ; track number
lxi h,sector$msg ! call ?pmsg ; sector header
lhld @sect ! call ?pdec ; sector number
ret
; BNKSEL
; Bank Select. Select CPU bank for further execution.
bnksel:
sta @cbnk ; remember current bank
jmp ?bank ; and go exit through users
; physical bank select routine
xofflist db -1,-1,-1,-1,-1,-1,-1,-1 ; ctl-s clears to zero
db -1,-1,-1,-1,-1,-1,-1,-1
dseg ; following resides in banked memory
; Disk I/O interface routines
; SELDSK
; Select Disk Drive. Drive code in <C>.
; Invoke login procedure for drive
; if this is first select. Return
; address of disk parameter header
; in <HL>
seldsk:
mov a,c ! sta @adrv ; save drive select code
mov l,c ! mvi h,0 ! dad h ; create index from drive code
lxi b,@dtbl ! dad b ; get pointer to dispatch table
mov a,m ! inx h ! mov h,m ! mov l,a ; point at disk descriptor
ora h ! rz ; if no entry in table, no disk
mov a,e ! ani 1 ! jnz not$first$select ; examine login bit
push h ! xchg ; put pointer in stack & <DE>
lxi h,-2 ! dad d ! mov a,m ! sta @RDRV ; get relative drive
lxi h,-6 ! dad d ; find LOGIN addr
mov a,m ! inx h ! mov h,m ! mov l,a ; get address of LOGIN routine
call ipchl ; call LOGIN
pop h ; recover DPH pointer
not$first$select:
ret
; HOME
; Home selected drive. Treated as SETTRK(0).
home:
lxi b,0 ; same as set track zero
; SETTRK
; Set Track. Saves track address from <BC>
; in @TRK for further operations.
settrk:
mov l,c ! mov h,b
shld @trk
ret
; SETSEC
; Set Sector. Saves sector number from <BC>
; in @sect for further operations.
setsec:
mov l,c ! mov h,b
shld @sect
ret
; SETDMA
; Set Disk Memory Address. Saves DMA address
; from <BC> in @DMA and sets @DBNK to @CBNK
; so that further disk operations take place
; in current bank.
setdma:
mov l,c ! mov h,b
shld @dma
lda @cbnk ; default DMA bank is current bank
; fall through to set DMA bank
; SETBNK
; Set Disk Memory Bank. Saves bank number
; in @DBNK for future disk data
; transfers.
setbnk:
sta @dbnk
ret
; SECTRN
; Sector Translate. Indexes skew table in <DE>
; with sector in <BC>. Returns physical sector
; in <HL>. If no skew table (<DE>=0) then
; returns physical=logical.
sectrn:
mov l,c ! mov h,b
mov a,d ! ora e ! rz
xchg ! dad b ! mov l,m ! mvi h,0
ret
; READ
; Read physical record from currently selected drive.
; Finds address of proper read routine from
; extended disk parameter header (XDPH).
read:
lhld @adrv ! mvi h,0 ! dad h ; get drive code and double it
lxi d,@dtbl ! dad d ; make address of table entry
mov a,m ! inx h ! mov h,m ! mov l,a ; fetch table entry
push h ; save address of table
lxi d,-8 ! dad d ; point to read routine address
jmp rw$common ; use common code
; WRITE
; Write physical sector from currently selected drive.
; Finds address of proper write routine from
; extended disk parameter header (XDPH).
write:
lhld @adrv ! mvi h,0 ! dad h ; get drive code and double it
lxi d,@dtbl ! dad d ; make address of table entry
mov a,m ! inx h ! mov h,m ! mov l,a ; fetch table entry
push h ; save address of table
lxi d,-10 ! dad d ; point to write routine address
rw$common:
mov a,m ! inx h ! mov h,m ! mov l,a ; get address of routine
pop d ; recover address of table
dcx d ! dcx d ; point to relative drive
ldax d ! sta @rdrv ; get relative drive code and post it
inx d ! inx d ; point to DPH again
pchl ; leap to driver
; MULTIO
; Set multiple sector count. Saves passed count in
; @CNT
multio:
sta @cnt ! ret
; FLUSH
; BIOS deblocking buffer flush. Not implemented.
flush:
xra a ! ret ; return with no error
; error message components
drive$msg db cr,lf,bell,'BIOS Error on ',0
track$msg db ': T-',0
sector$msg db ', S-',0
; disk communication data items
@adrv ds 1 ; currently selected disk drive
@rdrv ds 1 ; controller relative disk drive
@trk ds 2 ; current track number
@sect ds 2 ; current sector number
@dma ds 2 ; current DMA address
@cnt db 0 ; record count for multisector transfer
@dbnk db 0 ; bank for DMA operations
cseg ; common memory
@cbnk db 0 ; bank for processor operations
end
title 'Boot loader module for CP/M 3.0'
maclib vislib
maclib buf
maclib modebaud
maclib ports
maclib z80
public ?init,?ldccp,?rlccp,?time
public nint, ivect ,empty$ptr
extrn ?pmsg,?conin
extrn @civec,@covec,@aivec,@aovec,@lovec
extrn @cbnk,?bnksl
extrn aux$in$ptr, aux$out$ptr, kb$buf$in,dsp$buf$in
extrn rsint, kint, firq, vert$int, dspint
extrn @date,@hour,@min,@sec
extrn kmodei
bdos equ 5
if banked
tpa$bank equ 1
else
tpa$bank equ 0
endif
dseg ; init done from banked memory
?init:
di ; disable interrupts
lxi h,08000h ; hl <= device 0
shld @covec ; DISPLY = console output
lxi h,04000h ; hl <= device 1
shld @aivec ; AUX = auxiliary input
shld @aovec ; AUX = auxiliary output
lxi h,2000h ; hl <= device 2
shld @civec ; KB = console input
lxi h,1000h ; hl <= device 3
shld @lovec ; LPT = Centronics parallel ptr
; Set up interrupt vectors. Begin by pointing them all at a null
; interrupt routine so that every interrupt is satisfied in some way.
;
lxi d,nint ; get address of null vector
lxi h,ivect ; point to ivect table
ini$vec:
mov m,e ; store low byte of NINT
inr l ; increment hl
mov m,d ; store hi byte of NINT
inr l ; increment hl
jrnz ini$vec ; do for all
;
; Set up interrupt mode 2 and select page
;
IF REAL$1050
mvi a,ivect/256 ; get address of ivect
dw 47EDH ; "LD I,A"
dw 05eedh ; "IM2"
ENDIF
;
; Set up the interrupts that are implemented
;
intset lxi h,firq ; Floppy completion
IF REAL$1050
shld ivect+08
ELSE
shld ivect+2 ; Point to vector
ENDIF
lxi h,kint ; Keyboard interrupt
IF REAL$1050
shld ivect+10
ELSE
shld ivect+4 ; Point to vector
ENDIF
lxi h,rsint ; RS232 interrupt
IF REAL$1050
shld ivect+14
ELSE
shld ivect+16 ; Point to vector
ENDIF
IF REAL$1050 ; display interrupt
lxi h,dspint ; (from 6502 side)
shld ivect+04 ;
lxi h,vert$int ; Vertical retrace int
shld ivect+06
ENDIF
;
; Initialize interrupt buffers
;
lxix aux$in$ptr ; Point to aux input buff
call empty$ptr ; fix pointers to empty
lxix aux$out$ptr ; Point to aux output buff
call empty$ptr ; fix pointers to empty
lxix kb$buf$in ; Point to kbd buffer
call empty$ptr ; fix pointers to empty
lxix dsp$buf$in ; Point to display input
call empty$ptr ; fix pointers to empty
IF REAL$1050
mvi a,int$mask ; Get interrupt mask
out p$clk$portb ; Send out
mvi a,int$initial ; Initial value
out int$port ; to int port
ENDIF
ei ; reenable interrupts
;
; Put up sign on message
;
lxi h,signon$msg ; point to sign on msg
call ?pmsg ; print it out
ret
;
; Routine Empty$ptr
;
; "Empties" buffer by pointing empty and fill pointers to same
; location in buffer.
;
empty$ptr:
xra a ; Clear A
stx a,buf$empty ; store in empty ptr
stx a,buf$fill ; and fill ptr
ret
page
cseg ; boot loading most be done from resident memory
; This version of the boot loader loads the CCP from a file
; called CCP.COM on the system drive (A:).
?ldccp:
; Set up the FCB
;
xra a
sta ccp$fcb+15 ; zero extent
sta ccp$fcb+12 ; Zero ex field
sta ccp$fcb+32 ; Zero cr field
lxi d,ccp$fcb ; Point to FCB
call open ; open file
inr a
jrz no$ccp ; If not found, check again
;
; File found. Load into 100h.
;
lxi d,0100h
call setdma ;start of TPA
lxi d,128 ;
call setmulti ;Read 128 rec at a time
lxi d,ccp$fcb
call read ;load the thing
IF BANKED
;
; Now, Copy CCP to bank to bank 2 for reloading
;
lxi h,0100h
lxi b,1000h ; move 4K
lda @cbnk
push psw ; save current bank
ld$1:
mvi a,tpa$bank
call ?bnksl ; select TPA
mov a,m
push psw ; get a byte
mvi a,2
call ?bnksl ; select extra bank
pop psw
mov m,a ; save the byte
inx h
dcx b ; bump pointer, drop count
mov a,b
ora c ; test for done
jrnz ld$1
pop psw
call ?bnksl ; restore original bank
ENDIF
ldccp$exit:
ret
;
no$CCP: ; here if we couldn't find the file
lxi h,ccp$msg
call ?pmsg ; report this...
call ?conin ; get a response
jmp ?ldccp ; and try again
?rlccp:
IF BANKED
lxi h,0100h
lxi b,1000h ; Moving in 4k from bank 2
rl$1:
mvi a,2
call ?bnksl ; select extra bank
mov a,m
push psw ; get a byte
mvi a,tpa$bank
call ?bnksl ; select TPA
pop psw
mov m,a ; save the byte
inx h
dcx b ; bump pointer, drop count
mov a,b
ora c ; test for done
jrnz rl$1
ret
ELSE
jmp ?ldccp
ENDIF
IF REAL$1050
dseg
;
; This routine is called whenever the BDOS wants to either read
; or change the date and time. On entry, if C=0, a read is
; requested. If C=0FFH, a write is requested. DE and HL must
; be preserved.
;
?time:
push h ; Save h
push d ; and d
mov a,c ; Get entry switch
ora a ; Is it 0?
jnz time$set ; No. Check if setting time
;
; Requesting read of time and date
; First set up fields for SCB.
;
time$20:
;
; Get date from RTC. Start with year.
;
mvi a,0bh ; Getting Y1
call get$data ;
jrc time$20 ; If change, try again
mov l,c ; Move to L
mvi a,0ch ; Get Y10
call get$data ;
jrc time$20 ; If changed, do again
mov h,c ; move to h
call bcd$to$hex ; Convert to hex
sta year ; store year
;
; Get month.
;
mvi a,09h ; Getting M1
call get$data ;
jrc time$20 ; If change, try again
mov l,c ; Move to L
mvi a,0Ah ; Get M10
call get$data ;
jrc time$20 ; If changed, do again
mov h,c ; move to h
call bcd$to$hex ; Convert to hex
sta month ; store month
;
; Get day.
;
mvi a,07h ; Getting D1
call get$data ;
jrc time$20 ; If change, try again
mov l,c ; Move to L
mvi a,08h ; Get D10
call get$data ;
jrc time$20 ; If changed, do again
mov h,c ; move to h
call bcd$to$hex ; Convert to hex
sta day ; store day
;
; Get hours
;
mvi a,04h ; Get H1
call getdata ; Get it
jrc time$20 ; Jump if changed
mov b,c ; Save in B register
mvi a,05h ; Get H10
call getdata ;
jrc time$20 ; Jump if changed
mov a,c ; Get H10
ani 03h ; Low 2 bits only
mov c,a ; Put back into C
call pack$word ; Pack into word
sta @hour ; Store hour
;
; Get minutes
;
mvi a,02h ; Get M1
call getdata ; Get it
jrc time$20 ; Jump if changed
mov b,c ; Save in B register
mvi a,03h ; Get M10
call getdata ;
jrc time$20 ; Jump if changed
call pack$word ; Put it in
sta @min ; store minutes
;
; Get seconds
;
mvi a,0 ; Get S1
call getdata ; Get it
jrc time$20 ; Jump if changed
mov b,c ; Save in B register
mvi a,1 ; Get S10
call getdata ;
jc time$20 ; Jump if changed
call pack$word ; pack into word
sta @sec ; store minutes
;
; Now, compute the number of days since 1 Jan 78.
;
mvi a,2 ; 1978 is not leap year
sta leap ; so start leap count at 2
lxi h,0 ; Initialize hl for # days
lda year ; Get year
sui 78 ; minus 78
jm time$exit ; if less, error
jrz time$50 ; Jump if zero
push psw ; save year count
time$30:
lxi d,365 ; # days in normal year
lda leap ; Get leap count
cpi 4 ; Leap year?
jrnz time$40 ; Jump if not
inx d ; 366 days in a leap year
xra a ; reinit leap count
time$40:
dad d ; add # days in year
inr a ; Inc leap count
sta leap ; store new leap count
pop psw ; Get year count
dcr a ; decrement
jrz time$50 ; jump if zero
push psw ; else, save year count
jr time$30 ; and loop again
time$50:
lda month ; Get month
dcr a ; minus 1
jrz time$70 ; Jump if January
dcr a ; Else, decrement
push h ; save # days so far
lxi h,month$table ; Point to month table
mov e,a ; move month to E
xra a ;
mov d,a ; Move 0 to D
dad d ; Add to beginning of table
dad d ; (Twice for word entries)
mov e,m ; Load DE with entry
inx h ;
mov d,m ;
pop h ; Restore days so far
dad d ; Add days in preceding months
;
; If year is a leap year and month is March or later, add 1 to
; number of days.
;
lda leap ; Get leap count
cpi 4 ; Leap year?
jrnz time$70 ; Jump if not
lda month ; Get month
sui 3 ; March or >?
jm time$70 ; Jump if no
inx h ; Else, add 1 day
;
; Add in day of month
;
time$70:
lda day ; Get day of month
mov e,a ; Move to E
mvi d,0 ; Clear D
dad d ; add to date so far
;
; Update field in SCB
;
shld @date ; Store SPB value
jmp time$exit ; and exit
;
; Pack 2 bytes into BCD word
;
pack$word:
mov a,c ; Get into A
ral ; Rotate into high byte
ral ;
ral ;
ral ;
ora b ; OR in w/H1
ret ;
;
; Set clock using values in SPB.
;
time$set:
;
; Get hour from spb.
;
lda @hour ; Get hour
push psw ; save it
ani 0fh ; Clear hi bits
mov c,a ; move to C-reg
mvi a,4 ; Send H1
call put$data ;
pop psw ; Get hour again
rar ; rotate into low byte
rar ;
rar ;
rar ;
ani 0fh ; Clear hi bits
ori 08h ; Set 24 hour clock
mov c,a ; move to C-reg
mvi a,5 ; Send H10
call put$data ;
;
; Set minutes.
;
lda @min ; Get minutes
push psw ; save it
ani 0fh ; Clear hi bits
mov c,a ; move to C-reg
mvi a,2 ; Send M1
call put$data ;
pop psw ; Get mins again
rar ; rotate into low byte
rar ;
rar ;
rar ;
ani 0fh ; Clear hi bits
mov c,a ; move to C-reg
mvi a,3 ; Send M10
call put$data ;
;
; Set seconds.
;
lda @sec ; Get seconds
push psw ; save it
ani 0fh ; Clear hi bits
mov c,a ; move to C-reg
mvi a,0 ; Send S1
call put$data ;
pop psw ; Get secs again
rar ; rotate into low byte
rar ;
rar ;
rar ;
ani 0fh ; Clear hi bits
mov c,a ; move to C-reg
mvi a,1 ; Send S10
call put$data ;
;
; Convert date in days since 1 Jan 78 to calendar date.
;
lhld @date ; Get date
mvi a,2 ; Start leap count at 2
sta leap ;
mvi a,78 ; Year starts at 78
sta year ;
time$80:
push h ; Save # days
lda leap ; Get leap count
cpi 4 ; Leap year?
jrz time$85 ; Jump if yes
inr a ; Else, inc leap count
lxi d,365 ; # days in normal year
jr time$87 ;
time$85:
mvi a,1 ; Reinit leap count
lxi d,366 ; # days in leap year
time$87:
ora a ; Clear carry
db 0edh,052h ; SBC HL,DE
jm time$90 ; until negative
jrz time$90 ; (or zero)
sta leap ; Store new LEAP value
pop d ; pop
lda year ; Get year count
inr a ; Increment it
sta year ; Store it again
jr time$80 ; Loop
time$90:
pop h ; Restore day count
shld temp ; save it
;
; If this is a leap year, special considerations for Feb 29.
;
lda leap ; Get leap count
cpi 4 ; Leap year?
jrnz time$91 ; jump if not
xra a ; Clear carry
lxi d,60 ; Feb 29?
db 0edh,052h ; Check against date
jrz time$98 ; jump if zero
jm time$91 ; No offset if date is <
lhld temp ; Else, offset by 1
dcx h ;
shld temp ;
time$91:
mvi b,001h ; Start month at 1
mov c,l ; Start # days
lxi h,month$table ;
Time$92:
mov e,m ; Get # days in month
inx h ; Increment
mov d,m ;
inx h ;
push h ; Save table pointer
lhld temp ; Get # days
xra a ; Clear carry
db 0edh,052h ; subtract
jm time$95 ; Jump if negative
jrz time$95 ; (or zero)
mov c,l ; Save # days in C-reg
inr b ; Increment month
pop h ; restore table ptr
jr time$92 ; loop
time$95:
mov a,b ; Get month
sta month ; store
lhld temp ; Get # days
mov a,c ; Get day
sta day ;
pop h ; Restore stack
jr time$125 ;
time$98:
mvi a,2 ; Month = Feb
sta month ;
mvi a,29 ; Day = 29
sta day ;
time$125:
;
; Send year to RTC
;
lda year ; Get year
call hex$to$bcd ; Convert to BCD
lda ones ; Send Y1
mov c,a ;
mvi a,11 ;
call putdata ;
lda tens ; Send Y10
mov c,a ;
mvi a,12 ;
call putdata ;
;
; Send month.
;
lda month ; Get month
call hex$to$bcd ; Convert to BCD
lda ones ; Send M1
mov c,a ;
mvi a,09 ;
call putdata ;
lda tens ; Send M10
mov c,a ;
mvi a,10 ;
call putdata ;
;
; Send day.
;
lda day ; Get day
call hex$to$bcd ; Convert to BCD
lda ones ; Send D1
mov c,a ;
mvi a,07 ;
call putdata ;
lda tens ; Send D10
mov c,a ;
mvi a,08 ;
call putdata ;
time$exit:
di ; disable interrupts
mvi a,rtc$read ; Leave in READ state
out p$clk$control ;
mvi a,int$mask ; Set up int mask again
out p$clk$portb ;
ei ;
pop d
pop h
ret
;
; This subroutine is called with bits 0-3 set up as address
; input. Return w/C register = data. Carry will be set if
; data changed on 2 successive reads.
;
get$data:
push psw ; Save Read address
mvi a,rtc$write ; Writing
di
out p$clk$control ; set port to out
mvi a,rtc$select ; device select
out p$clk$control ;
pop psw ; Restore read address
out p$clk$porta ; Send out address
mvi a,add$write$hi ; Set up address write
out p$clk$control ;
mvi a,add$write$lo ; Clear address write
out p$clk$control ;
mvi a,rtc$read ; Reading
out p$clk$control ; Set port to in
mvi a,rtc$select ; Chip select
out p$clk$control ;
mvi a,read$hi ; set data read high
out p$clk$control ;
get$07:
in p$clk$portc ; Get status
ani 0000$1000b ; Busy?
jrz get$07 ; Loop till free
in p$clk$portA ; Get data
ani 0Fh ; Clear hi bits
mov c,a ; Save in C-reg
mvi a,read$lo ; Set data read low
out p$clk$control ;
mvi a,read$hi ; Set data read high
out p$clk$control ;
get$10:
in p$clk$portc ; Get status
ani 0000$1000b ; Busy?
jrz get$10 ; Loop till free
in p$clk$porta ; Get data again
ani 0fh ; Clear hi bits
cmp c ; Data same as last?
jrz get$20 ; Jump if yes
stc ; Else, set carry
get$20:
mvi a,read$lo ; Yes. Set data read low
out p$clk$control ;
mvi a,int$mask ; Set up interrupt mask
out p$clk$portb ;
mvi a,int$initial ; Reinit int ports
out int$port ;
ei ; reenable interrupts
ret ;
;
; Write to RTC
;
put$data:
push psw ; Save address for write
di ; Disable interrupts
mvi a,rtc$write ; Writing
out p$clk$control ; So set port to out
mvi a,rtc$select ; chip select
out p$clk$control ;
pop psw ; Restore write address
out p$clk$porta ; Send out address
mvi a,add$write$hi ; Set up address write
out p$clk$control ;
mvi a,add$write$lo ; Clear address write
out p$clk$control ;
mvi a,write$hi ; Set data write high
out p$clk$control ;
put$10:
in p$clk$portc ; Get status
ani 0000$1000b ; Busy?
jrz put$10 ; Loop till free
mov a,c ; Get data
out p$clk$porta ; write data
mvi a,write$lo ;
out p$clk$control ;
mvi a,rtc$read ; Leave port as in
out p$clk$control ;
mvi a,int$mask ; Set up interrupt mask
out p$clk$portb ;
mvi a,int$initial ; Reinit int ports
out int$port ;
ei ; reenable interrupts
ret ;
;
; Enter with 10's digit in H, 1's digit in L.
; Exit with hex number in A.
;
bcd$to$hex:
mov a,H ; Get 10's digit
ral ; *2
mov d,a ; (save)
ral ; *4
ral ; *8
add d ; *10
add l ; + 1's digit
ret ; it's that easy!
;
; Enter with # to be converted in A.
; Result in ONES and TENS.
;
hex$to$bcd:
push psw ; Save A
xra a ; Clear A
lxi h,tens ; Point to tens
mov m,a ; Clear TENS
pop psw ; Restore A
hex$10:
sui 10 ; Subtract 10
jm hex$20 ;
inr m ; Increment tens count
jr hex$10 ;
hex$20:
adi 10 ; Add 10 back
sta ones ; Store result in ONES
ret
year ds 1
month ds 1
day ds 1
leap ds 1
tens ds 1
ones ds 1
temp ds 2
month$table:
dw 31 ; January
dw 59 ; February (non-leap)
dw 90 ; March
dw 120 ; April
dw 151 ; May
dw 181 ; June
dw 212 ; July
dw 243 ; August
dw 273 ; September
dw 304 ; October
dw 334 ; November
ELSE
; No external clock.
?time:
ret
ENDIF
;
; Null interrupt handler
;
cseg
NINT:
IF REAL$1050
push psw
mvi a,int$initial
out int$port
pop psw
ENDIF
ei
ret
cseg
; CP/M BDOS Function Interfaces
scbcall:
mvi c,49 ;Set/Get SCB
jmp bdos
open:
mvi c,15
jmp bdos ; open file control block
setdma:
mvi c,26
jmp bdos ; set data transfer address
setmulti:
mvi c,44
jmp bdos ; set record count
read:
mvi c,20
jmp bdos ; read records
signon$msg db 13,10,13,10,'CP/M Version 3.0, BIOS version 1.0 '
IF BANKED
db 13,10,'***BANKED VERSION***'
ENDIF
db 27,';C'
db 13,10,0
ccp$msg db 13,10,'BIOS Err on '
ccp$drv db 'A'
db ': No '
db 'CCP'
db '.COM file',0
ccp$fcb db 01
destin db 'CCP ','COM',0,0,0,0
ds 16
db 0,0,0
scbpb db 06 ; Offset to user flag
db 00 ; 0 to do a get operation
scb$val dw 0 ; (nothing)
aseg
IF REAL$1050
org 0fff0h
ELSE
org 0ffe0h
ENDIF
ivect ds 32 ; 32-byte interrupt vector
end
; ; Buffer data structure definitions: buf$length equ 0 ; length of buf$buffer buf$empty equ 1 ; offset of next char in buffer buf$fill equ 2 ; offset of next free location ; (empty=fill means buffer empty) buf$buffer equ 3 ; start of buffer
; CALLVERS program bdos equ 5 ; entry point for BDOS prtstr equ 9 ; print string function vers equ 12 ; get version function cr equ 0dh ; carriage return lf equ 0ah ; line feed org 100h mvi d,5 ; Perform 5 times loop: push d ; save counter mvi c,prtstr lxi d,call$msg ; print call message call bdos mvi c,vers call bdos ; try to get version # ; CALLVERS will intercept mov a,l sta curvers pop d dcr d ; decrement counter jnz loop mvi c,0 jmp bdos call$msg: db cr,lf,'**** CALLVERS **** $' curvers db 0 end
title 'Character I/O handler '
; CHARIO.ASM
; Character I/O for the Modular CP/M 3 BIOS
; This module is organized into routines to do
; initialization, input, output, and status checks.
;
; Copyright (c) Visual Technology, 1983
;
; Revision history
; 01 (IRN) Modified from CPM/3 Kit
; 02 (LEL) Continued modification from kit
;
maclib vislib ; standard defs.
maclib Z80 ; define Z80 op codes
maclib ports ; define port addresses
maclib modebaud ; define mode bits and baud equates
maclib buf ; input buffer data structure
extrn ?pmsg
extrn ?bnksl,?wboot
public ?cinit,?ci,?co,?cist,?cost
public @ctbl
public dsp$buf$in
public aux$in$ptr,aux$out$ptr, kb$buf$in
public dspint, kint, rsint, kmodei
$*MACRO
max$devices equ 3
ctlz equ 26
cseg
page
;
; ?cinit
; This module is called for each of the 16 character devices, and
; initializes the devices. Register C contains the device number.
; The routine initializes the physical character device specified
; in register C to the baud rate contained in the appropriate entry
; of @ctbl. This routine need not be supplied if i/o redirection
; is not implemented. It is referenced only by the DEVICE utility.
;
?cinit:
lxi d,xfer$init ; jump table for ?cinit
mov b,c ; needs b
jr check$dev ; check for legality
;
; this table parallels @ctbl
;
xfer$init:
jr display$init ; init 6502
jr aux$init ; init rs-232 port
jr kb$init ; init keyboard port
jr lpt$init ; init parallel port
display$init:
lpt$init:
;
; Do nothing (set up in boot rom)
;
jr cinit$exit ; exit
kb$init:
if keyboard$1050
mvi a,kb$ir ; Internal reset
out p$kb$control ;
mvi a,04fh ; Mode:8bits,nopar,1stop,x16
out p$kb$control ;
mvi a,15h ; err reset,rxen,txen,rts0,dtr0
out p$kb$control ;
mvi a,48h ; enable keyclick & repeat
sta kmodei ; save settings
sta kmode ; (need both)
out p$kb$data ; send it out
endif
jr cinit$exit ; return
aux$init:
;
; get default baud rate, then set it, by setting the
; base baud rate in port c of misc. 8255, and baud rate
; factor in 8251 chip.
;
mov l,b ; get device number
mvi h,0 ; make into word
dad h ! dad h ! dad h ; *8 (table entry size)
lxi d,@ctbl+7 ; character device table
dad d ; device baud entry
mov l,m ; get baud rate
mvi h,0 ; make word
lxi b,baud$table ; settings to achieve baud rate
dad b ; entry in table
;
; now have pointer to baud table entry. set base rate.
;
di ; disable interrupts
in p$misc ; retain old settings
xra m ; get new setting
ani not misc$baudmask
; clear base rate bits
xra m ; set new ones
out p$misc ; reload it
;
; now set base rate factor in 8251 chip.
;
mvi a,com$ir ; internal reset
out p$aux1$control ; kick it
mov a,m ; get new base rate
xri mode$default ; default modes
ani mode$baudmask ; use baud rate from table
xri mode$default ; include defaults
out p$aux1$control ; kick it
mvi a,com$default ; reset command parameters
out p$aux1$control ; to its default
sta rspar ; Save RS232 parameters
ei ; enable interrupts
cinit$exit:
ret
page
; check$dev
; Assumes the address of a jump table in de, and a device in b.
; Preserves b and c.
check$dev:
mvi a,max$devices
cmp b ; b > max$devices?
jrc bad$device ; yes
mov l,b ; device to l
mvi h,0 ; make 16 bits
dad h ; double it
dad d ; entry in jump table
pchl
bad$device:
xra a
ret
page
; ci (b)
; Character device input. This routine is called with the
; character device in register b. It returns an 8 bit
; character with no parity.
?ci:
lxi d,xfer$in
jr check$dev
; must parallel device table
;
xfer$in:
jr crtin
jr auxin
jr kbin
jr lptin
crtin:
lxix dsp$buf$in ; Dsp input structure
call bufin ; Get character
ei ; reenable interrupts
ret
lptin:
mvi a,ctlz ; set to eof character
ret
auxin:
lxix aux$in$ptr ; auxin buffer structure
call bufin ; get character
ei ; enable interrupts
ret
kbin:
if keyboard$1050
lxix kb$buf$in ; Point to keyboard buffer
call bufin ; Get character
ei ; reenable interrupts
mov c,a ; Save character in C-reg
;
; Check caps lock
;
lda kmode ; Get keyboard mode byte
ani 010h ; check caps lock
jrz kbin$20 ; Jump if not on
mvi a,'a'-1 ; Check if >= 'a'
cmp c ;
jrnc kbin$20 ; Jump if below range
mvi a,'z' ; Check if <= 'z'
cmp c ;
jrc kbin$20 ; Jump if above range
mov a,c ; Else, get character
xri 20h ; Shift to upper case
jr kbin$exit ; and exit
;
; Check numeric pad
;
kbin$20
mov a,c ; Get keycode
cpi 0bfh ; Numeric pad or less?
jrnc kbin$50 ; Jump if no
cpi 83h ; Enter key?
jrz kbin$40 ; Jump if yes
cpi 0a0h ; Low end of numeric pad?
jrc kbin$50 ; Jump if less
kbin$40 lda kmode ; Test num lock
ani 20h ;
jrz kbin$50 ; Jump if not on
mov a,c ; Get keycode
cpi 83h ; Enter key?
mvi a,0dh ; (assume yes)
jrz kbin$exit ; Jump if true
res 7,c ; Else change to number
jr kbin$55 ;
;
; Test other special characters
;
kbin$50 cpi 08ch ; Tab key?
mvi a,09h ; Yes => Control-I
jrz kbin$exit
kbin$55 mov a,c ; Get keycode
kbin$exit:
ret
else
lxix kb$buf$in ; kb buffer structure
charin: call bufin ; returns a character
ei
ret
endif
page
; bufin (ix)
; Ix points to buffer data.
; Returns character in register a, after updating buffer
; management data structure. Buffer is circular.
; buf$empty is the offset of the next byte to return.
bufin:
spin:
di ;
call ?cist ; is there a character?
ei ;
jrz spin ; no, wait for one
di ; turn off interrupts
buffer$in:
ldx a,buf$empty ; offset of empty in buffer
ldx b,buf$length ; length of buffer
mov c,a ; save pointer
; add 1 mod length
;
inr a ; increment buffer pointer
cmp b ; did we go out of bounds?
jrnz inbounds$i ; if not
xra a ; else wrap around
inbounds$i:
stx a,buf$empty ; save new emptying pointer
mvi a,buf$buffer ; offset of buffer
add c ; offset of character
sta loc$i ; save in i-stream
ldx a,$-$ ; load character
loc$i equ $-1 ; disp. field of ldx
ret
page
; cist (b)
; Character device input status. This routine is called
; with a device number in register b and returns false
; if the specified device has no input character ready,
; and true if the device has an input character to be read.
?cist:
lxi d,xfer$st
jmp check$dev
; must parallel device table
;
xfer$st:
jr crtst
jr auxst
jr kbst
jr lptst
crtst:
lxix dsp$buf$in ; Display input structure
di ; disable interrupts
call inst ; Check input status
ei ; reenable interrupts
ret
lptst:
xra a
ret
auxst:
lxix aux$in$ptr ; aux buffer structure
di ; Disable interrupts
call inst ; Anything in buffer?
ei ; reeable interrupts
ret ; and return
page
kbst:
lxix kb$buf$in ; keyboard buffer structure
if keyboard$1050
di ; Disable interrupts
call inst ; Check input status
ei ; reenable interrupts
rz ; If no character, return
;
; There is a character in the buffer, but in order to know if
; it 'counts', we must get the exact character.
;
lxix kb$buf$in ; Point to input buffer
di ; disable interrupts
ldx b,buf$empty ; get empty pointer
push b ; save it
call buffer$in ; Get keycode
ldx d,buf$empty ; Get new empty pointer
pop b ; Get empty pointer
stx b,buf$empty ; restore it
ei ; reenable interrupts
;
; Now, check for 'non-characters', ie shift and num lock.
;
sui 80h ; Caps lock?
jrz kbds$1 ; jump if yes
dcr a ; Num lock?
jrz kbds$2 ; jump if yes
dcr a ; Num lock up?
jrz kbds$4 ; jump if yes
mvi a,true ; Character is ok
ret ;
;
; Handle caps and num lock
;
kbds$1:
lda kmode ; Get keyboard mode
xri 010h ; toggle caps lock
jr kbds$3 ;
kbds$2:
lda kmode ; Get keyboard mode
xri 020h ; Toggle numlock bit
kbds$3:
sta kmode ; Store new settings
kbds$4:
di ; disable interrupts
stx d,buf$empty ; store empty pointer
ei ; reenable interrupts
xra a ; a=0 for no char
ret
else
di
call inst
ei
ret
endif
page
;inst(ix)
; Ix is the input buffer structure for a character device.
; This routine returns true if there is a character waiting,
; false otherwise.
inst:
ldx a,buf$empty ; next byte to empty
ldx b,buf$fill ; next byte to fill
sub b ; same?
rz ; yes, it's empty
mvi a,true ; return true
ret
page
; co (b)
; Character output. This routine is called with a device number
; in register b, and the character to be output in c.
; It waits until the device (or buffer) is ready to accept
; another character, and then sends the character.
; The character is 8 bits, no parity.
?co:
lxi d,xfer$out
jmp check$dev
;
; must parallel device table
;
xfer$out:
jr disp$out
jr aux$out
jr kb$out
jr lpt$out
kb$out:
xra a
ret
aux$out:
call ?cost ; device ready for output?
jrz aux$out ; loop until ready
lxix aux$out$ptr ; set ix to out buffer
di ; disable interrupts
call bufout ; stuff it in
ei ;reenable interrupts
lda rspar ; Get RS232 parameters
ori com$txe ; enables transmit
out p$aux1$control ; aux1 control port
ret
lpt$out:
call ?cost ; busy
jrz lpt$out ; yes, spin
mov a,c ; get the character
cma
out p$printer
mvi a,1 ; strobe the printer
out p$8255$control ; to 1
xra a ; and back
out p$8255$control ; to 0
ret
disp$out:
call ?cost ; busy?
jrz disp$out ; yes, spin
mov a,c ; get the character
out p$disp$out ; send it out
; the following uses the bit set/reset feature
; of the 8255.
di
mvi a,0000$1110b ; reset port c bit 7
out p$disp$control ; to shake hands
inr a ; set port c bit 7
out p$disp$control ; to complete handshake
ei
ret
page
; bufout (ix,c)
; This routine takes the character to be sent out and
; stuffs it into the buffer denoted by register ix.
bufout:
ldx a,buf$fill ; fill point in buffer
ldx b,buf$length ; length of buffer
mov d,a ; save fill point
inr a ; next fill point?
cmp b ; outside?
jrnz inbounds$o ; no.
xra a ; yes, reset
inbounds$o:
stx a,buf$fill ; new fill point
mvi a,buf$buffer ; offset of buffer
add d ; old fill point
sta loc$o ; next byte to be sent
stx c,$-$ ; store in buffer
loc$o equ $-1
ret
page
; cost (b)
; Character output status. This routine is called with a
; device number in register b. It returns with register
; a set to zero if the device specified cannot accept a
; character immediately, and returns with ff otherwise.
; B and C must be preserved.
?cost:
lxi d,xfer$ost
jmp check$dev
xfer$ost:
jr disp$ost
jr aux$ost
jr kb$ost
jr lpt$ost
kb$ost:
xra a
ret
disp$ost:
in p$disp$c ; port c
ani disp$ready$rcv ; ready to receive?
rz ; no.
ost$true:
mvi a,true ; return ready
ret
aux$ost:
lxix aux$out$ptr ; aux xmit buffer
di ; critical section
ldx d,buf$empty ; next to read
ldx a,buf$fill ; next to fill
ei ; critical section
buf$full$cond:
; this check is to see if the buffer was full.
; a full condition is the empty pointer one
; more than the fill pointer. Empty must always
; be ahead of fill (modulo buffer length since
; circular).
inr a
ldx e,buf$length ; need to wrap?
cmp e ; if equal, wrap
jrnz inbound$ost ; not equal
xra a
inbound$ost:
cmp d ; is fill=empty?
jrnz ost$true ; no. not full
ost$false:
xra a ; yes. full.
ret
lpt$ost:
in p$misc ; printer status
ani misc$lpt$avail ; available?
jrz ost$false ; no.
in p$misc ; if available
ani misc$lpt$nobusy ; busy?
jrz ost$false ; yes
jr ost$true ; no
page
; The entries in the following table are indexed by the
; values for baud rates in modebaud.lib
;
baud$table:
db baud$none ; none
db baud$none ; 50
db baud$none ; 75
db baud$none ; 110
db baud$none ; 134.5
db baud$none ; 150
db misc$base01200 or mode$div64 ; 300
db misc$base02400 or mode$div64 ; 600
db misc$base01200 or mode$div16 ; 1200
db baud$none ; 1800
db misc$base02400 or mode$div16 ; 2400
db baud$none ; 3600
db misc$base19200 or mode$div64 ; 4800
db baud$none ; 7200
db misc$base09600 or mode$div16 ; 9600
db misc$base19200 or mode$div16 ; 19200
@ctbl db 'DISPLY' ; device 0, 6502 CRT
db mb$in$out
db baud$none
db 'AUX ' ; device 1, RS-232 port
db mb$in$out+mb$serial+mb$softbaud
db baud$1200
db 'KB ' ; device 2, Keyboard
db mb$input+mb$serial
db baud$1200
db 'LPT ' ; device 3, Centronics parallel printer
db mb$output
db baud$none
db 0 ; table terminator
page
;
; Keyboard interrupt routine
;
; Takes incoming character, checks for warm or cold boot sequences.
; If it is either one, it performs them immediately.
; Otherwise, it stores the keycode in the buffer and performs the keyclick.
; If there is not enough room in the buffer to store the keycode,
; the key is discarded and the keyboard will beep to indicate an error
; condition.
;
Kint:
Push psw ; Save A
push b ; and b
push d ; and d
push ix ; and ix
;
; Get keycode and check for warm or cold boot.
;
in p$kb$data ; get keycode
mov d,a ; save in d
if keyboard$1050
cpi 08ah ; Is it warm boot?
else
cpi 0aah ; is it warm boot?
endif
jrz kint$6 ; jump if yes
If keyboard$1050
cpi 08bh ; Or cold boot?
else
cpi 0bah ; or cold boot?
endif
jrnz kint$10 ; jump if not
page
;
; Key is cold boot. Jump to boot rom.
;
IF REAL$1050
XRA A ; A=0
OUT P$BOOT ; ENABLE BOOT PROM
ELSE
in 0 ; enable boot prom
ENDIF
jmp 0
;
; Key is warm boot.
;
kint$6
mvi a,fdc$reset ; set up for reset
out p$disk$control ; and do it
IF REAL$1050
in p$disk$bits ; Get current settings
ori 0100$1111b ; Turn off motor & deselect drvs
out p$disk$bits ; Send out again
mvi a,int$initial
out int$port
ei
ENDIF
jmp ?wboot ; boot
;
; Key not cold or warm boot.
;
kint$10:
mov a,d ; get keycode
if not keyboard$1050
cpi 08eh ; Space bar?
jrz key$space ; jump if yes
cpi 08dh ; Carriage return?
jrz key$return ;jump if yes
cpi 08ah ; Escape?
jrz key$escape ; jump if yes
cpi 08bh ; Backspace?
jrz key$backspace ; jump if yes
endif
kint$11
push d ; save keycode
lxix kb$buf$in ; point to buffer
ldx d,buf$empty ; Get empty pointer
ldx a,buf$fill ; and fill pointer
call buf$full$cond ; check full condition
pop d ; restore keycode
ora a ; (checking resultant flag)
jrnz kint$30 ; jump if true
;
; Keyboard buffer is full. Beep rather than click and then exit.
;
kint$23
call beep ; beep to indicate error
jr kint$99 ; go to exit
;
; Keyboard buffer not full. Put char into buffer.
;
kint$30 mov c,d ; Get char into C-reg
push d ;
call bufout ; put into buffer
pop psw ; Get character into A
if not keyboard$1050
mvi a,kb$click ; set up to click
out p$kb$data ; click
else
;
; Check for caps lock and num lock
;
cpi 80h ; Caps lock?
jrz kint$80 ; Jump if yes
cpi 81h ; Num lock?
jrz kint$81 ; Jump if yes
endif
jr kint$99 ; then exit
if keyboard$1050
;
; Handle caps lock
;
kint$80:
lda kmodei ; Get previous settings
xri 010h ; Toggle caps lock
jr kint$90 ;
;
; Handle num lock
;
kint$81:
lda kmodei ; Get previous setting
xri 020h ; Toggle num lock
;
; Turn on/off keyboard leds.
;
kint$90:
sta kmodei ; Store new num or caps lock
mov h,a ; store data
kint$l:
in p$kb$control ; Get status
ani 01h ; Test Tx empty
jrz kint$l ; loop if not
mov a,h ; Get data back
out p$kb$data ; Send to keyboard
jr kint$99 ; then exit
else
Key$space mvi d,020h ;Force to ascii
jr kint$11
key$return mvi d,13 ; Force to ascii
jr kint$11
key$escape mvi d,27
jr kint$11
key$backspace mvi d,8
jr kint$11
endif
;
; Get ready to exit
;
kint$99
IF REAL$1050
mvi a,int$initial ; Initialize
out int$port ; Interrupt port
ENDIF
pop ix ; start popping
pop d ;
pop b ;
pop psw ;
ei ; enable interrupts
ret ; and return
;
; Display Interrupt routine
;
; Takes incoming character from 6502 code. This info is used for
; keyboard info only.
;
Dspint:
Push psw ; Save A
push b ; and b
push d ; and d
push ix ; and ix
;
; Get character
;
in p$disp$in ; Get character
mov d,a ; save it
mvi a,12 ; Strobe
out p$disp$control ;
inr a ; then back
out p$disp$control ;
dspint$87:
in p$kb$control ; Get status
ani 4 ;
jrz dspint$87
;
; Get info. Mask out the 2 leds.
;
mov a,d ; Get char
ani 1100$1111b ; minus leds
mov d,a ; store in d
lda kmode ; Get kmodes
ani 0011$0000b ; Keep leds only
ora d ; OR in
ani 7fh ; (not bell, tho)
sta kmode
lda kmodei ; (both)
ani 0011$0000b ; Keep leds only
ora d ;
out p$kb$data ; Send out
ani 7fh ; (don't save beep)
sta kmodei ;
;
; Get ready to exit
;
dspint$99
IF REAL$1050
out clear$6502 ; Clear 6502 interrupt
mvi a,int$initial ; Initialize
out int$port ; Interrupt port
ENDIF
pop ix ; start popping
pop d ;
pop b ;
pop psw ;
ei ; enable interrupts
ret ; and return
;
; Beep routine
;
if keyboard$1050
beep: ei ; Enable interrupts
di ; But not for long
beep1:
in p$kb$control ; Get status
ani 4 ; Test Tx ready
jrz beep ; Loop if not
lda kmodei ; Get bits
ori 80h ; Set bell bit
out p$kb$data ; Send it out
ret
else
beep1:
beep mvi d,24 ; load count
beep0 in p$kb$control ; read status
rrc ; rotate
jrnc beep0 ; jump if no carry
mvi a,kb$click ; get click code
out p$kb$data ; send to keyboard
dcr d ; decrement count
jrnz beep0 ; till count =0
ret ; then return
endif
page
;REC/SND (RS-232) INT
;SEE IF RECEIVER OR TRANSMITTER CAUSED INT.
; IF RECEIVER, ACCEPT CHR INTO RECEIVE FIFO.
; IF TRANSMITTER, OUT A CHR FROM TRANSMIT FIFO;
; IF FIFO EMPTY, TURN OFF TRANSMITTER.
RSINT: PUSH PSW
push h
push D
push b
push ix
;SEE IF REC OR SND CAUSED INT.
in p$aux1$control ; read in status
RAR
RAR
jrnC SINT
;
;REC INT: CHR TO FIFO.
RINT:
IN p$aux1$data ; read incoming character
MOV D,A ; save in D
;
; Check for parity error
;
IN p$aux1$control ; get status
bit 3,a
jrz RINT0 ;Jump if ok
; Parity error. Reset error condition and replace char with '?'
;
LDA RSPAR
ORI 010H
OUT p$aux1$control
ANI 0EFH
OUT p$aux1$control ;RESET ERROR
MVI D,'?' ;REPLACE CHAR WITH '?'
; Processing continues here.
;
RINT0:
push d ; save character
lxix aux$in$ptr
ldx d,buf$empty
ldx a,buf$fill
call buf$full$cond ; buffer already full?
pop d ; (restore char)
JrZ rs$error ; If yes, error
mov c,d
call bufout ; Else, put into buffer
jr RS$EXIT
;
;SND INT: CHR FROM FIFO
;
SINT:
lxix aux$out$ptr ; Point to structure
call inst ; Anything there?
jrnz sint0 ; Jump if yes
LDA RSPAR
ANI 0FEH
STA RSPAR
OUT p$aux1$control ;SHUT OFF SND
jr rs$exit
SINT0: call buffer$in ; Get character from buffer
OUT p$aux1$data ; Send it
jr rs$exit
rs$error:
call beep ; Make noise
rs$exit:
IF REAL$1050
mvi a,int$initial ; Initialize
out int$port ; Interrupt port
ENDIF
pop ix
pop b
POP D
pop h
POP PSW
EI
RET
RSPAR: DS 1
;
; The keyboard mode byte is kept twice. One (KMODEI) is used
; by the interrupt routine so that the led can be turned on
; as soon as the caps or num lock key is hit. The other
; (kmode) is used to actually effect the change to incoming characters
; as they are being processed.
;
KMODEi: ds 1
kmode: ds 1
page
;
; Receive buffers
kb$length equ 32
kb$buf$in:
db kb$length ; length
db 0 ; next character to take
db 0 ; next character to fill
ds kb$length ; buffer
aux$length equ 32
aux$in$ptr:
db aux$length ; length
db 0 ; next character to take
db 0 ; next character to fill
ds aux$length ; buffer
dsp$length equ 16
dsp$buf$in:
db dsp$length ; length
db 0 ; next character to take
db 0 ; next character to fill
ds dsp$length ; buffer
;
; Transmit buffers
aux$length$o equ 16
aux$out$ptr:
db aux$length$o ; length
db 0 ; next character to take
db 0 ; next character to fill
ds aux$length$o ; buffer
bootadd equ 0
end
; Macro Definitions for CP/M3 BIOS Data Structures.
; dtbl <dph0,dph1,...> - drive table
; dph translate$table, - disk parameter header
; disk$parameter$block,
; checksum$size, (optional)
; alloc$size (optional)
; skew sectors, - skew table
; skew$factor,
; first$sector$number
; dpb physical$sector$size, - disk parameter block
; physical$sectors$per$track,
; number$tracks,
; block$size,
; number$dir$entries,
; track$offset,
; checksum$vec$size (optional)
; Drive Table. Contains 16 one word entries.
dtbl macro ?list
local ?n
?n set 0
irp ?drv,<?list>
?n set ?n+1
dw ?drv
endm
if ?n > 16
.' Too many drives. Max 16 allowed'
exitm
endif
if ?n < 16
rept (16-?n)
dw 0
endm
endif
endm
dph macro ?trans,?dpb,?csize,?asize
local ?csv,?alv
dw ?trans ; translate table address
db 0,0,0,0,0,0,0,0,0 ; BDOS Scratch area
db 0 ; media flag
dw ?dpb ; disk parameter block
if not nul ?csize
dw ?csv ; checksum vector
else
dw 0FFFEh ; checksum vector allocated by
endif ; GENCPM
if not nul ?asize
dw ?alv ; allocation vector
else
dw 0FFFEh ; alloc vector allocated by GENCPM
endif
dw 0fffeh,0fffeh,0fffeh ; dirbcb, dtabcb, hash alloc'd
; by GENCPM
db 0 ; hash bank
if not nul ?csize
?csv ds ?csize ; checksum vector
endif
if not nul ?asize
?alv ds ?asize ; allocation vector
endif
endm
dpb macro ?psize,?pspt,?trks,?bls,?ndirs,?off,?ncks
local ?spt,?bsh,?blm,?exm,?dsm,?drm,?al0,?al1,?cks,?psh,?psm
local ?n
;; physical sector mask and physical sector shift
?psh set 0
?n set ?psize/128
?psm set ?n-1
rept 8
?n set ?n/2
if ?n = 0
exitm
endif
?psh set ?psh + 1
endm
?spt set ?pspt*(?psize/128)
?bsh set 3
?n set ?bls/1024
rept 8
?n set ?n/2
if ?n = 0
exitm
endif
?bsh set ?bsh + 1
endm
?blm set ?bls/128-1
?size set (?trks-?off)*?spt
?dsm set ?size/(?bls/128)-1
?exm set ?bls/1024
if ?dsm > 255
if ?bls = 1024
.'Error, can''t have this size disk with 1k block size'
exitm
endif
?exm set ?exm/2
endif
?exm set ?exm-1
?all set 0
?n set (?ndirs*32+?bls-1)/?bls
rept ?n
?all set (?all shr 1) or 8000h
endm
?al0 set high ?all
?al1 set low ?all
?drm set ?ndirs-1
if not nul ?ncks
?cks set ?ncks
else
?cks set ?ndirs/4
endif
dw ?spt ; 128 byte records per track
db ?bsh,?blm ; block shift and mask
db ?exm ; extent mask
dw ?dsm ; maximum block number
dw ?drm ; maximum directory entry number
db ?al0,?al1 ; alloc vector for directory
dw ?cks ; checksum size
dw ?off ; offset for system tracks
db ?psh,?psm ; physical sector size shift
; and mask
endm
;
gcd macro ?m,?n
;; greatest common divisor of m,n
;; produces value gcdn as result
;; (used in sector translate table generation)
?gcdm set ?m ;;variable for m
?gcdn set ?n ;;variable for n
?gcdr set 0 ;;variable for r
rept 65535
?gcdx set ?gcdm/?gcdn
?gcdr set ?gcdm - ?gcdx*?gcdn
if ?gcdr = 0
exitm
endif
?gcdm set ?gcdn
?gcdn set ?gcdr
endm
endm
skew macro ?secs,?skf,?fsc
;; generate the translate table
?nxtsec set 0 ;;next sector to fill
?nxtbas set 0 ;;moves by one on overflow
gcd %?secs,?skf
;; ?gcdn = gcd(?secs,skew)
?neltst set ?secs/?gcdn
;; neltst is number of elements to generate
;; before we overlap previous elements
?nelts set ?neltst ;;counter
rept ?secs ;;once for each sector
db ?nxtsec+?fsc
?nxtsec set ?nxtsec+?skf
if ?nxtsec >= ?secs
?nxtsec set ?nxtsec-?secs
endif
?nelts set ?nelts-1
if ?nelts = 0
?nxtbas set ?nxtbas+1
?nxtsec set ?nxtbas
?nelts set ?neltst
endif
endm
endm
title 'Diskette Handler Module'
; CP/M-80 Version 3 -- Modular BIOS
; Disk I/O Module
; Port Address Equates
maclib vislib
maclib ports
maclib modebaud
; CP/M 3 Disk definition macros
maclib cpm3
; Z80 macro library instruction definitions
maclib Z80
; Disk drive dispatching tables for linked BIOS
public fddd0,fddd1,firq,vert$int
public u$conin$echo, error$table
; Variables containing parameters passed by BDOS
extrn @adrv,@rdrv
extrn @dma,@trk,@sect
extrn @dbnk,@cbnk
; System Control Block variables
extrn @ermde ; BDOS error mode
; Utility routines in standard BIOS
extrn ?wboot ; warm boot vector
extrn ?pmsg ; print message @<HL> up to 00, saves <BC> & <DE>
extrn ?pdec ; print binary number in <A> from 0 to 99.
extrn ?pderr ; print BIOS disk error header
extrn ?conin,?cono ; con in and out
extrn ?const ; get console status
extrn ivect
extrn ?bank
cr equ 13
lf equ 10
bell equ 7
page
; Extended Disk Parameter Headers (XPDHs)
CSEG
dw fd$write
dw fd$read
dw fd$login
dw fd$init
db 0 ; relative drive zero
db 2 ; TYPE field
fddd0:
fddd0$dpb equ $+12
dph trandd,dpbd0
dw fd$write
dw fd$read
dw fd$login
dw fd$init
db 1 ; relative drive one
db 02 ; TYPE field
fddd1:
fddd1$dpb equ $+12
dph trandd,dpbd1
CSEG ; DPB must be resident
dpbd0
dw 40 ;#128 byte records/track
db 4,0fh ;block shift mask (2K)
db 1 ;extent mask
dw 194 ;maximun block number
dw 127 ;max number of dir entry - 1
db 0C0H,00h ;alloc vector for directory
dw 0020h ;checksum size
dw 2 ;offset for sys tracks
db 2,3 ;physical sector shift (512 sector)
dpbd1
dw 40 ;#128 byte records/track
db 4,0Fh ;block shift mask (2K)
db 1 ;extent mask
dw 194 ;maximun block number
dw 127 ;max number of dir entry - 1
db 0C0H,00h ;alloc vector for directory
dw 32 ;checksum size
dw 2 ;offset for sys tracks
db 2,3 ;physical sector shift (512 sector)
;
; DPB for DEC Rainbow
;
RAINDPB:
dw 028h ;#128 byte records/track
db 4,0fh ;block shift mask (2K)
db 1 ;extent mask
dw 194 ;maximun block number
dw 127 ;max number of dir entry - 1
db 0C0H,00h ;alloc vector for directory
dw 0020h ;checksum size
dw 2 ;offset for sys tracks
db 2,3 ;physical sector shift (512 sector)
;
; DPB for DEC VT180
;
VT180$dpb:
dw 36 ;#128 byte records/track
db 3,7 ;block shift mask (2K)
db 0 ;extent mask
dw 170 ;maximum block number
dw 63 ;max number of dir entry - 1
db 0C0H,00h ;alloc vector for directory
dw 16 ;checksum size
dw 2 ;offset for sys tracks
db 2,3 ;physical sector shift (512 sector)
;
;DPB for KAYPRO II single side 48 tpi
;
kayprodpb:
dw 28h ;number of 128 records/track
db 3,7 ;block shift mask
db 0 ;extent mask
dw 194 ;max block number
dw 63 ;max dir entries - 1
db 0f0h,0 ;alloc vector for directory
dw 0010h ;checksum size
dw 1 ;offset sys tracks
db 2,3 ;physical sector shift (512)
DSEG
datbf: ds 512
CSEG
trandd skew 10,1,1
rainbowskew skew 10,2,1 ; Rainbow skew factor is 2
page
; Disk I/O routines for standardized BIOS interface
; Initialization entry point.
; called for first time initialization.
DSEG
fd$init:
mvi a,fdc$reset ;reset
out p$disk$control ;do it
ret
page
fd$login:
; This entry is called when a logical drive is about to
; be logged into for the purpose determining what type
; of disk is in use.
; It may adjust the parameters contained in the disk
; parameter header pointed at by <DE>
; The value of XDPH-1 (TYPE field) is adjusted to reflect
; the type of disk. This is determined by reading the
; disk label.
push d ; Save address of XDPH
;reset xdph for b: back to default condition (support of rainbow, kaypro)
lda @rdrv ;see which drive (dpc 10-17-83)
cpi 1 ;see if drive b "
lxi d,trandd ; (assume yes)
lxi h,dpbd1 ;
jrnz login$2 ;not b:, no need to fix drive a: "
shld fddd1dpb ;restore dpb1 addr pointer in dph "
sded fddd1 ;restore translate table (dpc 10-17-83)
jr login$3 ;
login$2:
lxi h,dpbd0 ;
shld fddd0$dpb ; Restore DPB
sded fddd0 ; Restore translate table
login$3:
xra a ;zero a
sta trk ;track zero
sta login$flag ; Indicate LOGIN entry
inr a ;a = 1
sta sect ;sector = 1
lxi h,datbf ; point to dma area
shld dmaadr ; store
lxi h,fd0$access ; assume A drive
lded @rdrv ; Get drive
xra a ;zero a
mov d,a ;zero d
dad d ; Point to correct flag
pop d ; Restore address of XDPH
login$10:
mvi a,0ffh ; flag=ff
mov m,a ; reset last track
sta old$track ;
lda @rdrv ; Get relative drive
inr a ; increment
xri 0fh ; bit=0 => on
sta select$mask ; save command
call read$label ;perform read
jrnz not$ok$1 ; Jump if read not OK
;
; Check to see if disk has our "signature"
;
call ours$check
jrz good$exit
;
; formats are dec rainbow 100, DEC VT180, and KAYPRO
;
;
lda @rdrv ;get relative drive (dpc 10-14-83)
cpi 1 ;set zero flg if zero (dpc 10-14-83)
jrnz not$ok$1 ;not drive b: so skip (dpc 10-14-83)
;
;now see if diskette is 96 tpi or 48 tpi
;
mvi a,2 ;96 tpi
sta fddd1-1 ;save in type field
dcr a ;acc = 1
sta trk ;track = 1
;sector = 1
lxi d,fddd1 ;point to xdph
call read$label ;perform read
jrnz try$vt180 ;error on read
; Must be a RAINBOW
;
;fddd1 points to skew table address in DPH
;fddd1 -1 points to type field in DPH
;
mvi a,2 ;dec rainbow is single sided 96 tpi
sta fddd1-1 ;store rainbow type field in dph
lxi h,raindpb ;get address of rainbow dpb (dpc 10-17-83)
;store in dpb address location of dph
login$80:
shld fddd1dpb ;the DPB of b:'s DPH points to a rainbow dpb
lxi h,rainbowskew ;get skew table for dec rainbow (dpc 10-14)
shld fddd1 ;overwrite old skew table addr (dpc 10-14-83)
jr good$exit ;exit
;
;try read of VT180 single sided double density 48 tpi
;
try$vt180:
mvi a,0ffh ; Set last track to FF
sta old$track ; to force HOME on disk
xra a ;acc = zero
sta fddd1-1 ;type byte = 0 so single side 48 tpi
sta tpi ;force tpi to be 48
sta trk ; Track=0
sta sect ;sector = 0
lxi d,fddd1 ;get address of xdph
call read$label ;perform read
jrz its$kaypro ;
;
; It's 48TPI. Assume VT180 till proven otherwise.
;
mvi a,0ffh
sta old$track
lxi h,vt180$dpb ;get addr of VT180 addr
shld fddd1dpb ;patch dpb addr so points to VT180's
lxi h,rainbow$skew ;(Same skew as RAINBOW)
shld fddd1 ;set translate table
mvi a,1 ;Sector=1
sta sect ;
call read$label
jrz good$exit ;
notok1:
mvi a,2 ;single side 96 tpi
sta fddd1-1 ;reset type field
lxi h,nomatch ; Error, print message
call ?pmsg ;
mvi a,0ffh ;FF for error
jr login$exit ;
;
; Assume KAYPRO
;
its$kaypro:
lxi h,kayprodpb ; Get addr of KAYPRO dpb
shld fddd1dpb ; Store
lxi h,0 ; No xlate
shld fddd1 ; Set translate table
good$exit:
xra a ; A=0 if good
login$exit:
ora a ; Set flags
ret
ours$check:
;
; Check that disk has our "signature"
;
push d ;save xdph
lxi b,0003 ; count =3
lxi h,datbf ; Point to signature on disk
lxi d,signature ; and our recognized one
ours$21:
ldax d ; get signature char
cci ; compare
inx d ; increment de
jrnz ours$90 ; jump if no match
jpe ours$21 ; if bc<>0, keep going
mov a,m ; Get next character
cpi '0' ; Is it '0'?
jrz ours$30 ; If yes, it's ok
cpi '1' ; Is it '1'?
jrnz ours$90 ; If not, it's bad
;
; Set up fields in TYPE field as follows:
;
; Bit 0 : 1=2 sides
; 0=1 side
; Bit 1 : 1=96 TPI
; 0=48 TPI
; Bits 2 - 7 : unused
;
ours$30:
pop d ; Restore address of XDPH
dcx d ; Point to type field
lxi h,datbf+no$of$heads ;
mov a,m ; Get # sides
dcr a ; Decrement
mov b,a ; Save in B
lxi h,datbf+tpi$offset ; Get tpi from label
mov a,m ;
ora a ; clear carry
ral ; Move to bit 1
ora b ; OR with # sides
stax d ; store in TYPE field
;
; Move label info into appropriate DPH
;
lxi b,15 ; # bytes to move
lxi h,datbf+32 ; FROM address
lxi d,dpbd0 ; Assume drive 0
lda @rdrv ; check it
ora a ;
jrz ours$67 ; Jump if drive 0
lxi d,dpbd1 ; Set to drive 1
ours$67:
ldir ; move
inx h ; skip skew
ldi ; Then move in 2 more
ldi ;
xra a ; Ret w/Z flag set
ret
ours$90:
pop d ;
ret
no$of$heads equ 53 ;Offset into label
tpi$offset equ 11 ;
signature db 'FMT'
nomatch db '***WARNING - Disk format not recognized***',13,10,0
page
; disk READ and WRITE entry points.
; these entries are called with the following arguments:
; relative drive number in @rdrv (8 bits)
; absolute drive number in @adrv (8 bits)
; disk transfer address in @dma (16 bits)
; disk transfer bank in @dbnk (8 bits)
; disk track address in @trk (16 bits)
; disk sector address in @sect (16 bits)
; pointer to XDPH in <DE>
; they transfer the appropriate data, perform retries
; if necessary, then return an error code in <A>
DSEG
fd$read:
call read$setup ; Set up for read
jr rw$common
fd$write:
call write$setup ; Set up for write
rw$common: ; seek to correct track (if necessary),
; and issue 1797 command.
shld operation$name ; save message for errors
mov b,a ; put read or write into B
xra a ; Clear A
sta login$flag ; (Not logging in)
lda @sect ; Get sector
sta sect ; save it
lhld @dma
shld dmaadr
lda no$of$sides ; Get # sides
ora a ; Check it
jrz rw$26 ; Jump if single-sided
lda @trk ; get track
ani 01h ; low bit = side
add a ;
add a ;
add a ; *8
rw$26 ora b ; OR in w/read or write
sta disk$command ; save 1797 command
page
;
; Will need to know info on last access to disk. Move info into
; old$track
;
lxi h,fd0$access ; FROM address
lded @rdrv ; Get relative drive
xra a ; Clear a
mov d,a ; Use it to clear d
dad d ; add
lxi d,old$track ; TO address
fd$20 ldi ; move in track
call set$up$8255 ; Set up 8255 command
page
more$retries:
mvi c,04 ; allow 04 retries
retry$operation:
push b ; save retry counter
;
; Check to see whether the motor is being turned on cold, or
; whether it is still on from a previous access.
;
call start$motor$timer ; Start timing
in p$disk$bits ; Get previous status
push psw ; save it
lda select$mask ; Get new status
out p$disk$bits ; Send out (turns on motor)
pop psw ; Get prev status
ani 0100$0000b ; Was motor on
jrz check$track ; bit 6=0 means it was on
;
; Motor was off. Wait 800 ms for it to come up to speed.
;
lxi h,892
wait$0
xra a
wait$1
dcr a
jnz wait$1 ; Delay 896 uS
dcx h ;
mov a,l ;
ora h ;
jrnz wait$0 ;
;
; Check current track against desired one.
;
check$track
;
; Access track
;
lda trk ! lxi h,old$track ! cmp m
jrz same$track ; if same track, dont seek
;
; New track (different from last one accessed)
;
new$track: ;
lda old$track ; get last track accessed
out p$disk$track ; send it out
cpi 0ffh ; first access?
cz home ; if yes, home
lda trk ; point to desired track
call seeker ; and seek
lda trk ; get track
sta old$track ; store as last track accessed
jr get$sect ; Now, access sector.
;
; At same track as last access
;
same$track:
out p$disk$track ; give 1797 track
;
; Now, Get sector.
;
get$sect:
lda sect
out p$disk$sector ;and sector
;
; Save track info for drive
;
lxi h,fd0$access ; TO address
lded @rdrv ; get current drive
xra a ; Clear a
mov d,a ; Use it to clear d
dad d ;
lxi d,old$track ; FROM address
xchg ;
ldi ; move in track
JMP SEL$DMA
CSEG
;
; Select DMA bank for transfer
;
SEL$DMA:
IF BANKED
lda @dbnk ; Get DMA bank
call ?bank ; Go to it
ENDIF
;
; Now, set up for NMI to do command. NMI will use alternate regs
; so set them up with the apprpriate values.
;
exaf ; exchange af and af
exx ; and other prime regs
push h ; save h (really h')
push b ; save b (really b')
push psw ; save a (really a')
mvi c,p$disk$data ; load c with port address
lhld dmaadr ; get buffer address
exaf ; put into prime regs
exx ;
lda disk$command ; get 1797 command
call exec$command ; Start. Wait for IREQ & read status
sta disk$status ; save status for error messages
exx ; save regs for a second
exaf ;
pop psw ; restore a (really a')
pop b ; restore b (really b')
pop h ; restore h (really h')
exx ; put back into prime regs
exaf ; put af back too
;
; Operation completed. Status is in location disk$status.
;
IF BANKED
xra a ; Reselect bank 0
call ?bank ;
ENDIF
pop b ; recover retry counter
lda disk$status ; get status
ora a ; check status.
jz fd$exit ; If no error, exit
JMP CHK$ERROR
DSEG
;
; Check error type. If write protect violation or time out,
; or not ready, don't bother retrying.
;
CHK$ERROR:
ani 1100$0000b ; Not ready or write protect?
jnz fd$error ; jump if yes
lda disk$status ; get status again
ani 0001$0000b ; see if record not found error
cnz seeker ; if rec not found, may need to force seek
dcr c ; decrement retry count
jz fd$error ; If failed 4 times, exit
mov a,c ;
cpi 02 ; If <2 retries
jp retry$operation ; then retry
;
; If >2 retries have already been done, force a head restore. This
; is done by seeking track 5, then restore to assure head travel.
; This should cure persistent lint on the heads as well as seek
; errors.
;
mvi a,5 ; track 5
call seeker ; seek it
call home ; home
lda trk ; Get track back
Call seeker ; seek it
jmp retry$operation ; then retry
page
;
; Error Handling...
;
FD$ERROR:
;
; suppress error message if BDOS is returning errors to application...
;
lda login$flag ; Logging in?
ora a ;
jrnz hard$err$1 ; If yes, don't print msg
lda @ermde
cpi 0FFh
jrz hard$error
;
; Had permanent error, print message like:
;
; BIOS Err on d: T-nn, S-mm, <operation> <type>, Retry ?
;
call ?pderr ; print message header
lhld operation$name ! call ?pmsg ; last function
; then, messages for all indicated error bits
lda disk$status ; get status byte from last error
cpi 0ffh ; time out error?
jrz err$timeout ; special message
lxi h,error$table ; point at table of message addresses
errm1:
mov e,m ! inx h ! mov d,m ! inx h ; get next message address
add a ! push psw ; shift left and push residual bits with status
xchg ! cc ?pmsg ! xchg ; print message, saving table pointer
pop psw ! jrnz errm1 ; if any more bits left, continue
errm2 lxi h,error$msg ! call ?pmsg ; print "<BEL>, Retry (Y/N) ? "
call u$conin$echo ; get operator response
cpi 'Y' ! jz more$retries ; Yes, then retry 10 more times
;
; Error noted. Ask user if disk should be relogged.
;
hard$error:
lda disk$status ; Get error value
ani 0100$0000b ; Write protected?
jrnz hard$err$1 ; Jump if yes
lxi h,New$disk$msg ; Ask if new disk
call ?pmsg ;
call U$conin$echo ; Get user response
cpi 'Y' ; YES?
jrnz hard$err$1 ; Jump if not
mvi a,0ffh ; Else, force login
jmp fd$exit ; then exit
hard$err$1: ; otherwise,
lda disk$status ; Get status byte
cpi 0100$0000b ; Write protect error?
mvi a,1 ; return hard error to BDOS
jnz fd$exit ; Not write protect error
inr a ; Write protect error
jmp fd$exit
err$timeout
lxi h,time$out ; point to timeout message
call ?pmsg ; Display message
jr errm2 ; Ask for user response
page
DSEG
;
; Subroutine to seek to home track
;
home:
mvi a,08h ; home, hld, no verify
jmp seek$20 ;
;
; Seeker
; This routine seeks a track in A
;
seeker:
push psw ; Save destination track
lda tpi ; Get TPI
ora a ; Is it 48?
jrnz seek$15 ; Jump if not
;
; Must double track info for 48tpi disks
;
in p$disk$track ; Read in current track
ora a ; Clear carry
ral ; *2
out p$disk$track ; Set track reg
pop psw ; Get desired track
ora a ; clear carry
ral ; *2
jr seek$16 ; Seek it
seek$15:
pop psw ; Restore track
seek$16:
out p$disk$data ; send out track
mvi a,018h ; seek, hld, verify
;
; Routines for seek and home
;
seek$20
out p$disk$control ; send command from A
;
; Delay at least 24 uSec after command
;
mvi a,09 ;
seek$delay
dcr a ;.
jnz seek$delay ;
;
; Wait for completion
;
seek$30
in p$disk$control ; get status byte
rrc ; Ready?
jc seek$30 ; loop until ready
;
; Return with A= status byte
;
in p$disk$control ; read in status
;
; Delay 18mS to allow for settling time
;
lxi d,01400h ;
seek$delay2
dcr e ;
jnz seek$delay2 ; 896 uS
dcr d ;
jnz seek$delay2 ; 20*896=18 mS
push psw ; save status
lda trk ; Get trk
out p$disk$track ; Save in track reg
pop psw ; Restore status
ret
page
CSEG
;
; This routine actually executes the read or write command. An initial
; read (or write) is sent out to the controller. Since we are not
; doing multiple sectors, an interrupt will be generated at the end of
; the command. This invokes the NMI set up at the beginning of the
; routine. Either an INI or OUTI will be performed, depending on whether
; a read or write is invoked. This lets us read in an entire sector
; more efficiently.
;
exec$command:
out p$disk$control ; send 1797 command
;
; Wait at least 24 usec after command before doing anything.
;
mvi a,09
exec$delay
dcr a
jnz exec$delay ; (delays 31 usec)
;
; Now, wait for completion, but TIME OUT after 1.2 seconds.
;
lxi d,60000+1
exec$0
in p$disk$control ; get status
rrc ; check not ready flag
jnc exec$success ; if 0, successful completion
inx d ; (waste time)
dcx d ;
inx d ;
dcx d ;
dcx d ; decrement count
mov a,d ;
ora e ;
adi 0ffh ;
jc exec$0 ; 20 us*60000=1.2 seconds
;
; Time out. Set A=0FFH and exit.
;
mvi a,0d0h ; Reset FDC
out p$disk$control ; send it out
mvi a,0ffh ; Set time out flag
jr exec$exit
;
; Successful completion. Return w/status byte in A.
exec$success
in p$disk$control ; Get status byte
exec$exit
push psw ; Save status
call start$motor$timer ; Start timer
pop psw ;
ret
page
;
; Start up motor timer
;
Start$motor$timer:
di ; DI while changing counter
mvi a,127 ; Start counter
sta motor$counter ;
mvi a,all$ints ; Turn on Vert int
out p$clk$portb ; (it will count down for us)
ei ; EI
RET
;
; Exit from routine.
; Must set up interrupts appropriately.
;
fd$exit:
push psw ; save status
in p$disk$track ; read track
out p$disk$data ; write out data
;
; Restore nmi locations
;
IF BANKED
lda @dbnk ; Point to bank
call ?bank ; Switch to it
ENDIF
lxi d,nmi ; Point at nmi
lxi h,nmibuf ; and at save buffer
lxi b,8 ; load count
ldir ; move saved data back
IF BANKED
lda @cbnk ; Point back to code bank
call ?bank ; Switch back
ENDIF
in p$disk$bits ; Get disk bits
ori 0000$1111b ; Deselect drive
out p$disk$bits ; Send out
mvi a,0d0h ;
out p$disk$control ; Reset disk controller
pop psw
ora a ; set flags
ret ; and exit!!
DSEG
;
; Set up for reading the label
;
read$label:
push d ; Save addr of XDPH
call read$setup ; set up for read
shld operation$name ; store for error msg
sta disk$command ; as command to execute
mvi a,0ffh
sta login$flag
mvi c,3 ;allow only 3 retries
call retry$operation ; do it
pop d ;restore xdph
ret
CSEG
read$setup:
IF BANKED
lda @dbnk
call ?bank
ENDIF
call nmi$setup ; set up nmi (invarient part)
lxi h,0a2edh ; "INI"
shld nmi+2 ; store read portion
IF BANKED
lda @cbnk
call ?bank
ENDIF
lxi h,read$msg ; point at " Read "
mvi a,82h ; 1797 read
ret
write$setup:
IF BANKED
lda @dbnk
call ?bank ; Switch banks
ENDIF
call nmi$setup ; set up nmi (invarient part)
lxi h,0a3edh ; "OUTI"
shld nmi+2 ; store write portion
IF BANKED
lda @cbnk ; Get CBANK back
call ?bank ; restore it
ENDIF
lxi h,write$msg ; point at " Write "
mvi a,0A2h ; 1797 write
ret
;
; Set up NMI
; NMI (non-maskable interrupt) occurs at fixed location 0066h.
; During normal run time NMI does not occur, so these locations
; are normal RAM. During diskette data transfers, NMI is
; triggered by the diskette Data Request signal, so the NMI
; locations are temporarily overlayed by intructions to process
; the interrupt.
;
nmi equ 0066h
nmi$setup:
mvi a,fdc$reset ; Reset FDC
out p$disk$control ;
dcx d ; Point to TYPE field of XDPH
ldax d ; Get value
push psw ; (save)
ani 1 ; Mask # sides
sta no$of$sides ; store
pop psw ; Get type
ani 0000$0010b ; Mask TPI field
rar ; Rotate into bit 0
sta TPI ; store
lxi d,nmibuf ; point to storage area
lxi h,nmi ; original values
lxi b,8 ; set up length
ldir ; save them away
lxi h,0d908h ; "EX AF,AF'"
shld nmi ; beginning of nmi
shld nmi+4 ; also, end of nmi
lxi h,045edh ; "RETN"
shld nmi+6 ;
mvi a,3 ; Enable FDC interrupt
out p$8255$control ;
ret
CSEG
;
; Floppy completion interrupt.
;
firq: push psw ; save A
in p$disk$control ; relieve interrupt
IF REAL$1050
mvi a,int$initial
out int$port ; Init int port
ENDIF
pop psw
ei
ret
CSEG
;
; Vertical retrace interrupt.
; Counts down until MOTOR$COUNTER=0, at which point it
; turns off the motor and masks out this interrupt.
;
VERT$INT:
push psw ; Save A
push h ; and H
out vert$clear ; Clear interrupt
lxi h,motor$counter ; Point to counter
dcr m ; Decrement
jrnz vert$exit ; Do nothing till it hits 0
in p$disk$bits ; Get 8255 bits
ori 0100$0000b ; Turn off motor
out p$disk$bits ; Send it back out
mvi a,int$mask ; Mask out this interrupt
out p$clk$portb ;
vert$exit:
mvi a,int$initial ; Reinit interrupt controller
out int$port ;
pop h ; Restore H
pop psw ; and A
ei ; Reenable interrupts
ret ; and return
page
DSEG
u$conin$echo: ; get console input, echo it
; and shift to upper case
call ?const ! ora a ! jrz u$c1 ; see if any char already struck
call ?conin ! jr u$conin$echo ; yes, eat it and try again
u$c1:
call ?conin ! push psw
mov c,a ! call ?cono
pop psw ! cpi 'a' ! rc
sui 'a'-'A' ; make upper case
push psw ; Save character
lxi h,cr$lf ; Send out CR LF
call ?pmsg ;
pop psw ; restore character
ret
;
; Set up misc 8255 command
;
; Bit 7=0; Bit 6=0 means turn motor on.
; Set up drive select in bits 0-3.
set$up$8255:
lda @rdrv ; get relative drive
inr a ; increment
cpi 03 ;
jm fd$40 ;
ani 06 ;
add a ; Double it
fd$40 xri 0Fh ; bit=0 => on
mov b,a ; save in b
;
; Set up Bit 4 (side select).
; If double sided, consecutive tracks use alternate sides
; of the disk
;
lda @trk ; Get track
sta trk ; store it
lda no$of$sides ; Get # sides
ora a ; Is it double sided?
jrz fsel$1 ; Jump if not
lda @trk ; get track
push psw ; save it
rar ; rotate into place
sta trk ; store
pop psw ; restore track
ani 1 ; mask off other bits
jrz fsel$1 ; If side 0, ok
mvi a,010h ; Else, set side 1 bit
ora b ; or w/b-reg
mov b,a ; save in b again
;
; Set up bit 5 for write precompensation. (Write precomp is
; necessary for outer tracks, so check against constant specified
; by disk manufacturer.)
;
fsel$1 lda trk ; get track
lxi h,precomp$limit ; and precomp limit
cmp m ; is it
jrc fsel$2 ; jump if yes
mvi a,020h ; set write pre-comp
ora b ; or with other bits
mov b,a ; store back in b
;
; All 8 bits of 8255 command have been set up. Save them.
;
fsel$2 mov a,b ; get b reg values
sta select$mask ; save 8255 bits
ret
page
CSEG
IF REAL$1050
ivect2 equ 0fff8h ;
ELSE
ivect2 equ 0ffe2h ; address of ivect+2
ENDIF
nmibuf ds 8 ; buffer to hold prev contents of nmi
disk$command ds 1 ; current wd1797 command
select$mask ds 1 ; current drive select code
old$track db 0ffh ; last track seeked to
dmaadr ds 2
sect ds 1
trk ds 1 ; track for current operation
disk$status ds 1 ; last error status code for messages
precomp$limit db 43
no$of$sides db 0 ; # sides
tpi db 0
motor$counter db 0
login$flag db 0
fd0$access
fd0$trk db 0ffh ; last track accessed on A
fd1$trk db 0ffh ; last track accessed on B
DSEG
; error message components
cr$lf db 13,10,0
read$msg db ', Read',0
write$msg db ', Write',0
operation$name dw read$msg
; table of pointers to error message strings
; first entry is for bit 7 of 1797 status byte
error$table dw b7$msg
dw b6$msg
dw b5$msg
dw b4$msg
dw b3$msg
dw b2$msg
dw b1$msg
dw b0$msg
b7$msg db ' Not ready,',0
b6$msg db ' Protect,',0
b5$msg db ' Fault,',0
b4$msg db ' Record not found,',0
b3$msg db ' CRC,',0
b2$msg db ' Lost data,',0
b1$msg db ' DREQ,',0
b0$msg db ' Busy,',0
time$out db ' Time out,',0
error$msg db ' Retry (Y/N) ? ',0
new$disk$msg: db 'Initialize disk system (Y/N) ? ',0
END
public @dtbl extrn fddd0,fddd1 ; extrn winc0 cseg @dtbl dw fddd0 ; A drive = floppy dw fddd1 ; B drive = floppy dw 0 ; C drive dw 0 ; D drive dw 0 ; E drive dw 0 ; F drive ; dw winc0 ; G drive dw 0 ; G drive dw 0,0,0,0,0,0,0,0,0 ; drives H-P non-existant end
; ECHOVERS RSX
pstring equ 9 ; string print function
cr equ 0dh
lf equ 0ah
;
; RSX PREFIX STRUCTURE
;
db 0,0,0,0,0,0 ; room for serial number
jmp ftest ; begin of program
next db 0c3H ; jump
dw 0 ; next module in line
prev: dw 0 ; previous module
remov: db 0ffh ; remove flag set
nonbnk: db 0
db 'ECHOVERS'
space: ds 3
ftest: ; is this function 12?
mov a,c
cpi 12
jz begin ; yes - intercept
jmp next ; some other function
begin:
lxi h,0
dad sp ;save stack
shld ret$stack
lxi sp,loc$stack
mvi c,pstring
lxi d,test$msg ; print message
call next ; call BDOS
lhld ret$stack ; restore user stack
sphl
lxi h,0031h ; return version number = 0031h
ret
test$msg:
db cr,lf,'**** ECHOVERS **** $'
ret$stack:
dw 0
ds 32 ; 16 level stack
loc$stack:
end
public endtag dseg endtag equ $
PUBLIC INT$VECT ; ; This module is a dummy module which is used ; to assure that the locations starting at ; 0FFF0h are reserved for interrupt vectors ASEG org 0fff0h INT$VECT: ds 2 ; Expansion interface A ds 2 ; Expansion interface B ds 2 ; Display processor interface ds 2 ; Vertical Clock ds 2 ; Floppy disk interface ds 2 ; Keyboard interface ds 2 ; Winchester interface ds 2 ; Async Interface end
EXTRN ENDTAG ; Label sector for disks for the Visual 1050 X EQU 0 ;ANCHOR 0 /SIGNATURE ;------------------------------------------------------------------------------ ; The following field said "FMT0" on the Amigo-formatted diskettes ;------------------------------------------------------------------------------ DB 'FMT1' ;ANCHOR 4 /FORMAT DW 512 ;SECTOR SIZE DB 10 ;# SECTORS DB 1 ;# HEADS DW 80 ;# TRACKS DB 3 ;TRANSPARENT SKEW ;------------------------------------------------------------------------------ ; The following field (TPI) was not used on FMT0. Since the byte was unused, ; it had a value of 0. A value of 0 will be interpreted to mean 48 TPI. ; A value of 1 will be interpreted to mean 96 TPI. ;------------------------------------------------------------------------------ DB 1 ; TPI DB X,X,X,X DB X,X,X,X ;ANCHOR 20 /O.S. ; (Bytes 20-27 used by COPYSYS to generate bootable system) DW 2080h ;LOAD BEGIN ADDRESS DW ENDTAG-2080h ;LOAD LENGTH DW 2080h ;JUMP ADDRESS DW 128 ;BYTE OFFSET TO SYSTEM DB 1 ;# HEADS DB X,X,X ;RESERVED ;ANCHOR 32 ;Copy of CP/M Plus DPB DW 40 ;# 128 BYTE RECORDS/TRACK DB 4 ;LOG2(2048/128) DB 15 ;(2048/128)-1 DB 1 ; EXM=1 DW 194 ;(390K/2048)-1 (HIGHEST BLOCK #) DW 127 ;(DIRS-1) DB 11000000B,00000000B ;2048/32=64 DIRS/BLOCK. DIRS/64=2BLOCK 2 BIT DW 32 ;(DIRS)/4 (GIVES CSV SIZE) DW 2 ;NUMBER OF SYSTEM TRACKS DB 1 ; non-transparent skew (this field not in DPB) DB 2 ;Physical Record shift factor DB 3 ;Physical Record Mask ;(HOST-MONITOR) DW 2 ;TRACK ORIGIN DB 0 ;HEAD ORIGIN DB 1 ;# HEADS DB X ; ;---------------------------------------------------------------------------- ; The following byte was unused on under FMT0. We will use it to tell ; how sides are to be read on a double-sided disk. A 0 will mean ; alternating heads. ;---------------------------------------------------------------------------- DB X ;ORDER (Not applicable since this is single sided disk) ;ANCHOR 56 /OPTIONS DB 0 ;STEP RATE (0=FAST..3=SLOW) ;--------------------------------------------------------------------------- ; The followwing field was unused in FMT0. In FMT1, it will be used to ; indicate the write precomp track. ;--------------------------------------------------------------------------- DB 0ffh ;Write precomp DB X,X,X,X,X,X,X,X, X,X,X,X,X,X,X,X,X,X DB X,X,X,X,X,X,X,X,X,X, X,X,X,X,X,X,X,X,X,X DB X,X,X,X,X,X,X,X,X,X, X,X,X,X,X,X,X,X,X,X DB X,X,X,X,X,X,X,X,X,X, X,X ;ANCHOR 128 END
title 'Boot loader module for CP/M 3.0' true equ -1 false equ not true public ?init,?ldccp,?rlccp,?time public nint, ivect extrn @civec, @covec, @aivec, @aovec, @lovec, @bnkbf extrn ?pmsg,?conin extrn @cbnk,?bnksl extrn aux$in$ptr, aux$out$ptr, kb$buf$in maclib buf maclib modebaud maclib ports maclib z80 bdos equ 5 dseg ; init done from banked memory ?init: di ; disable interrupts lxi h,08000h ; hl <= device 0 shld @covec ; DISPLY = console output ; ; Set up interrupt vectors. Begin by pointing them all at a null ; interrupt routine so that every interrupt is satisfied in some way. ; lxi d,nint ; get address of null vector lxi h,ivect ; point to ivect table ini$vec: mov m,e ; store low byte of NINT inr l ; increment hl mov m,d ; store hi byte of NINT inr l ; increment hl jrnz ini$vec ; do for all IF REAL$1050 mvi a,0000$0000b ; Disable all interrupts out p$clk$portb ; Send it out mvi a,int$initial ; out int$port ENDIF ei ; reenable interrupts ret ?ldccp: ?rlccp: ?time: ret ; ; Null interrupt handler ; cseg NINT: IF REAL$1050 push psw mvi a,int$initial out int$port pop psw ENDIF ei ret aseg org 0ffe0h ivect ds 32 ; 32-byte interrupt vector end
title 'Diskette Handler Module'
; CP/M-80 Version 3 -- Modular BIOS
; Disk I/O Module
; Port Address Equates
maclib vislib
maclib ports
maclib modebaud
; CP/M 3 Disk definition macros
maclib cpm3
; Z80 macro library instruction definitions
maclib Z80
; Disk drive dispatching tables for linked BIOS
public fddd0,fddd1
public u$conin$echo, error$table
; Variables containing parameters passed by BDOS
extrn @adrv,@rdrv
extrn @dma,@trk,@sect
extrn @dbnk,@cbnk
; System Control Block variables
extrn @ermde ; BDOS error mode
; Utility routines in standard BIOS
extrn ?wboot ; warm boot vector
extrn ?pmsg ; print message @<HL> up to 00, saves <BC> & <DE>
extrn ?pdec ; print binary number in <A> from 0 to 99.
extrn ?pderr ; print BIOS disk error header
extrn ?conin,?cono ; con in and out
extrn ?const ; get console status
extrn ivect
extrn ?bank
cr equ 13
lf equ 10
bell equ 7
page
; Extended Disk Parameter Headers (XPDHs)
cseg
dw fd$write
dw fd$read
dw fd$login
dw fd$init
db 0 ; relative drive zero
db 2 ; TYPE field
fddd0:
dw trandd ; Translate table address
db 0,0,0,0,0,0,0,0,0 ; Bdos scratch area
db 0 ; Media flag
dw dpbd0 ; Disk parameter block
dw 0fffeh ; Checksum vector
dw 0fffeh ; Allocation vector
dw dirbcb ;
dw datbcb ;
dw 0fffeh ; Hash
db 0 ; Hash bank
dw fd$write
dw fd$read
dw fd$login
dw fd$init
db 1 ; relative drive one
db 02 ; TYPE field
fddd1:
dw trandd ; Translate table address
db 0,0,0,0,0,0,0,0,0 ; Bdos scratch area
db 0 ; Media flag
dw dpbd1 ; Disk parameter block
dw 0fffeh ; Checksum vector
dw 0fffeh ; Allocation vector
dw dirbcb
dw datbcb
dw 0fffeh ; Hash
db 0 ; Hash bank
cseg ; DPB must be resident
dpbd0
dw 0028h ; #128 byte records/track
db 04,0fh ; block shift and mask
db 1 ; extent mask
dw 194 ; maximum block number
dw 127 ; maximum dir entry #
db 0C0h,00h ; alloc vector for directory
dw 32 ; checksum size
dw 2 ; offset for sys tracks
db 2,3 ; physical sector size shift
dpbd1
dw 0028h ; #128 byte records/track
db 04,0fh ; block shift and mask
db 1 ; extent mask
dw 194 ; maximum block number
dw 127 ; maximum dir entry #
db 0C0h,00h ; alloc vector for directory
dw 32 ; checksum size
dw 2 ; offset for sys tracks
db 2,3 ; physical sector size shift
dseg
;
; Directory buffer control block
;
dirhead dw dirbcb
dirbcb: db 0ffh ; DRV - Drive with record in buf
db 0,0,0 ; Rec #
db 0 ; wflg
db 0 ; Scratch byte
dw 0 ; Track
dw 0 ; Sector
dw dirbf ; Buffer address
db 0 ; Bank
dw 0 ; Link
dirbf: ds 512
cseg
;
; Data buffer control block
;
dathead dw datbcb
datbcb: db 0ffh ; Drive
db 0,0,0 ; Record #
db 0 ; Wflg
db 0 ; Scratch byte
dw 00 ; Track
dw 0 ; Sector
dw datbf ; Buffer address
db 0 ; Bank
dw 0 ; Link
datbf: ds 512
trandd skew 10,1,1
page
; Disk I/O routines for standardized BIOS interface
; Initialization entry point.
; called for first time initialization.
fd$init:
mvi a,fdc$reset ;reset
out p$disk$control ;do it
ret
page
fd$login:
; This entry is called when a logical drive is about to
; be logged into for the purpose determining what type
; of disk is in use.
; It may adjust the parameters contained in the disk
; parameter header pointed at by <DE>
; The value of XDPH-1 (TYPE field) is adjusted to reflect
; the type of disk. This is determined by reading the
; disk label.
push d ; Save address of XDPH
lda @ermde ; Get error mode
sta save$mode ; save it
mvi a,0ffh ; Force to not display msgs
sta @ermde ; store
lxi h,datbf ; point to dma area
shld dmaadr ; store
lxi h,fd0$access ; assume A drive
lded @rdrv ; Get drive
xra a ; a=0
mov d,a ; Clear D
dad d ; Point to correct flag
pop d ; Restore address of XDPH
login$10:
mvi a,0ffh ; flag=ff
mov m,a ; reset last track
lda @rdrv ; Get relative drive
inr a ; increment
xri 0fh ; bit=0 => on
sta select$mask ; save command
mvi a,0ffh ; Clear "last" flags
sta old$track ;
xra a ; a=0
sta trk ; Going to Track 0
inr a ; a=1
sta sect ; Sector 1
;
; Set up for reading the label
;
push d ; Save addr of XDPH
call nmi$setup ; set up nmi (invarient part)
lxi h,0a2edh ; "INI"
shld nmi+2 ; store read portion
lxi h,read$msg ; Point to "Read"
shld operation$name ; store for error msg
mvi a,82h ; Store read
sta disk$command ; as command to execute
call more$retries ; do it
jrnz bad$exit ; Jump if problem
;
; Check that disk has our "signature"
;
lxi b,0003 ; count =3
lxi h,datbf ; Point to signature on disk
lxi d,signature ; and our recognized one
back ldax d ; get signature char
cci ; compare
inx d ; increment de
jrnz notok ; jump if no match
jpe back ; if bc<>0, keep going
mov a,m ; Get next character
cpi '0' ; Is it '0'?
jrz login$30 ; If yes, it's ok
cpi '1' ; Is it '1'?
jrnz notok ; If not, it's bad
;
; Set up fields in TYPE field as follows:
;
; Bit 0 : 1=2 sides
; 0=1 side
; Bit 1 : 1=96 TPI
; 0=48 TPI
; Bit 2 : unused
; Bit 3 : unused
; Bit 4 : unused
; Bit 5 : unused
; Bits 6-7: step rate
;
login$30:
pop d ; Restore address of XDPH
dcx d ; Point to type field
lxi h,datbf+no$of$heads ;
mov a,m ; Get # sides
dcr a ; Decrement
mov b,a ; Save in B
lxi h,datbf+tpi$offset ; Get tpi from label
mov a,m ;
ora a ; clear carry
ral ; Move to bit 1
ora b ; OR with # sides
mov b,a ; Save in B
lxi h,datbf+step$offset ; Get step rate
mov a,m ;
db 0fh ; Into bits 6&7
db 0fh ;
ora b ; OR with others
stax d ; store in TYPE field
;
; Move label info into appropriate DPH
;
lxi b,15 ; # bytes to move
lxi h,datbf+32 ; FROM address
lxi d,dpbd0 ; Assume drive 0
lda @rdrv ; check it
ora a ;
jz login$67 ; Jump if drive 0
lxi d,dpbd1 ; Set to drive 1
login$67:
ldir ; move
inx h ; skip skew
ldi ; Then move in 2 more
ldi ;
jr login$exit ; exit
notok:
pop b ; Restore stack
lxi h,nomatch ; Error, print message
call ?pmsg
jr login$exit
bad$exit:
pop b
login$exit:
lda save$mode ; Get saved value of @ermde
sta @ermde ; restore it
xra a ; Clear A
ret
save$mode ds 1
no$of$heads equ 53 ;Offset into label
step$offset equ 56 ;
tpi$offset equ 11 ;
signature db 'FMT'
nomatch db '***WARNING - Disk format not recognized***',13,10,0
page
cseg
; disk READ and WRITE entry points.
; these entries are called with the following arguments:
; relative drive number in @rdrv (8 bits)
; absolute drive number in @adrv (8 bits)
; disk transfer address in @dma (16 bits)
; disk transfer bank in @dbnk (8 bits)
; disk track address in @trk (16 bits)
; disk sector address in @sect (16 bits)
; pointer to XDPH in <DE>
; they transfer the appropriate data, perform retries
; if necessary, then return an error code in <A>
fd$read:
call nmi$setup ; set up nmi (invarient part)
lxi h,0a2edh ; "INI"
shld nmi+2 ; store read portion
lxi h,read$msg ; point at " Read "
mvi a,82h ; 1797 read
jr rw$common
fd$write:
call nmi$setup ; set up nmi (invarient part)
lxi h,0a3edh ; "OUTI"
shld nmi+2 ; store write portion
lxi h,write$msg ; point at " Write "
mvi a,0A2h ; 1797 write
rw$common: ; seek to correct track (if necessary),
; and issue 1797 command.
shld operation$name ; save message for errors
mov b,a ; put read or write into B
lda @sect ; Get sector
sta sect ; save it
lhld @dma
shld dmaadr
lda no$of$sides ; Get # sides
ora a ; Check it
jrz rw$26 ; Jump if single-sided
lda @trk ; get track
ani 01h ; low bit = side
add a ;
add a ;
add a ; *8
rw$26 ora b ; OR in w/read or write
sta disk$command ; save 1797 command
page
;
; Will need to know info on last access to disk. Move info into
; old$track
;
lxi h,fd0$access ; FROM address
lded @rdrv ; Get relative drive
xra a ; Clear a
mov d,a ; Use it to clear d
dad d ; add
lxi d,old$track ; TO address
fd$20 ldi ; move in track
;
; Set up misc 8255 command
;
; Bit 7=0; Bit 6=0 means turn motor on.
; Set up drive select in bits 0-3.
lda @rdrv ; get relative drive
inr a ; increment
cpi 03 ;
jm fd$40 ;
ani 06 ;
add a ; Double it
fd$40 xri 0Fh ; bit=0 => on
mov b,a ; save in b
;
; Set up Bit 4 (side select).
; If double sided, consecutive tracks use alternate sides
; of the disk
;
lda @trk ; Get track
sta trk ; store it
lda no$of$sides ; Get # sides
ora a ; Is it double sided?
jrz fsel$1 ; Jump if not
lda @trk ; get track
push psw ; save it
rar ; rotate into place
sta trk ; store
pop psw ; restore track
ani 1 ; mask off other bits
jrz fsel$1 ; If side 0, ok
mvi a,010h ; Else, set side 1 bit
ora b ; or w/b-reg
mov b,a ; save in b again
;
; Set up bit 5 for write precompensation. (Write precomp is
; necessary for outer tracks, so check against constant specified
; by disk manufacturer.)
;
fsel$1 lda trk ; get track
lxi h,precomp$limit ; and precomp limit
cmp m ; is it
jrc fsel$2 ; jump if yes
mvi a,020h ; set write pre-comp
ora b ; or with other bits
mov b,a ; store back in b
;
; All 8 bits of 8255 command have been set up. Save them.
;
fsel$2 mov a,b ; get b reg values
sta select$mask ; save 8255 bits
page
more$retries:
mvi c,10 ; allow 10 retries
retry$operation:
push b ; save retry counter
;
; Check to see whether the motor is being turned on cold, or
; whether it is still on from a previous access.
;
call start$motor$timer ; Start timing
in p$disk$bits ; Get previous status
push psw ; save it
lda select$mask ; Get new status
out p$disk$bits ; Send out (turns on motor)
pop psw ; Get prev status
ani 0100$0000b ; Was motor on
jrz check$track ; bit 6=0 means it was on
;
; Motor was off. Wait 800 ms for it to come up to speed.
;
lxi h,892
wait$0
xra a
wait$1
dcr a
jnz wait$1 ; Delay 896 uS
dcx h ;
mov a,l ;
ora h ;
jrnz wait$0 ;
;
; Check current track against desired one.
;
check$track
;
; Access track
;
lda trk ! lxi h,old$track ! cmp m
jrz same$track ; if same track, dont seek
;
; New track (different from last one accessed)
;
new$track: ;
lda old$track ; get last track accessed
out p$disk$track ; send it out
cpi 0ffh ; first access?
cz home ; if yes, home
lda trk ; point to desired track
call seeker ; and seek
lda trk ; get track
sta old$track ; store as last track accessed
jr get$sect ; Now, access sector.
;
; At same track as last access
;
same$track:
out p$disk$track ; give 1797 track
;
; Now, Get sector.
;
get$sect:
lda sect
out p$disk$sector ;and sector
;
; Save track info for drive
;
lxi h,fd0$access ; TO address
lded @rdrv ; get current drive
xra a ; Clear a
mov d,a ; Use it to clear d
dad d ;
lxi d,old$track ; FROM address
xchg ;
ldi ; move in track
;
; Now, set up for NMI to do command. NMI will use alternate regs
; so set them up with the apprpriate values.
;
exaf ; exchange af and af
exx ; and other prime regs
push h ; save h (really h')
push b ; save b (really b')
push psw ; save a (really a')
mvi c,p$disk$data ; load c with port address
lhld dmaadr ; get buffer address
exaf ; put into prime regs
exx ;
lda disk$command ; get 1797 command
call exec$command ; Start. Wait for IREQ & read status
sta disk$status ; save status for error messages
exx ; save regs for a second
exaf ;
pop psw ; restore a (really a')
pop b ; restore b (really b')
pop h ; restore h (really h')
exx ; put back into prime regs
exaf ; put af back too
;
; Operation completed. Status is in location disk$status.
;
pop b ; recover retry counter
lda disk$status ; get status
ora a ; check status.
jz fd$exit ; If no error, exit
;
; Check error type. If write protect violation or time out,
; or not ready, don't bother retrying.
;
ani 1100$0000b ; Not ready or write protect?
jrnz fd$error ; jump if yes
lda disk$status ; get status again
ani 0001$0000b ; see if record not found error
cnz seeker ; if rec not found, may need to force seek
dcr c ; decrement retry count
jrz fd$error ; If failed 10 times, exit
mov a,c ;
cpi 06 ; If <4 retries
jp retry$operation ; then retry
;
; If >4 retries have already been done, force a head restore. This
; is done by seeking track 5, then restore to assure head travel.
; This should cure persistent lint on the heads as well as seek
; errors.
;
mvi a,5 ; track 5
call seeker ; seek it
call home ; home
lda trk ; Get track back
Call seeker ; seek it
jmp retry$operation ; then retry
page
;
; Error Handling...
;
FD$ERROR:
;
; suppress error message if BDOS is returning errors to application...
;
lda @ermde
cpi 0FFh
jrz hard$error
;
; Had permanent error, print message like:
;
; BIOS Err on d: T-nn, S-mm, <operation> <type>, Retry ?
;
call ?pderr ; print message header
lhld operation$name ! call ?pmsg ; last function
; then, messages for all indicated error bits
lda disk$status ; get status byte from last error
cpi 0ffh ; time out error?
jz err$timeout ; special message
lxi h,error$table ; point at table of message addresses
errm1:
mov e,m ! inx h ! mov d,m ! inx h ; get next message address
add a ! push psw ; shift left and push residual bits with status
xchg ! cc ?pmsg ! xchg ; print message, saving table pointer
pop psw ! jnz errm1 ; if any more bits left, continue
errm2 lxi h,error$msg ! call ?pmsg ; print "<BEL>, Retry (Y/N) ? "
call u$conin$echo ; get operator response
cpi 'Y' ! jz more$retries ; Yes, then retry 10 more times
hard$error: ; otherwise,
lda disk$status ; Get status byte
cpi 0100$0000b ; Write protect error?
mvi a,1 ; return hard error to BDOS
jnz fd$exit ; Not write protect error
inr a ; Write protect error
jmp fd$exit
err$timeout
lxi h,time$out ; point to timeout message
call ?pmsg ; Display message
jr errm2 ; Ask for user response
page
;
; Subroutine to seek to home track
;
home:
lda step$rate ; Get step rate
home$0
ori 08h ; home, hld, no verify
jmp seek$20 ;
;
; Seeker
; This routine seeks a track in A at the given step rate
;
seeker:
push psw ; Save destination track
lda tpi ; Get TPI
ora a ; Is it 48?
jrnz seek$15 ; Jump if not
;
; Must double track info for 48tpi disks
;
in p$disk$track ; Read in current track
ora a ; Clear carry
ral ; *2
out p$disk$track ; Set track reg
pop psw ; Get desired track
ora a ; clear carry
ral ; *2
jr seek$16 ; Seek it
seek$15:
pop psw ; Restore track
seek$16:
out p$disk$data ; send out track
lda step$rate ; Get step rate
ori 018h ; seek, hld, verify
;
; Routines for seek and home
;
seek$20
out p$disk$control ; send command from A
;
; Delay at least 24 uSec after command
;
mvi a,09 ;
seek$delay
dcr a ;.
jnz seek$delay ;
;
; Wait for completion
;
seek$30
in p$disk$control ; get status byte
rrc ; Ready?
jc seek$30 ; loop until ready
;
; Return with A= status byte
;
in p$disk$control ; read in status
;
; Delay 18mS to allow for settling time
;
lxi d,01400h ;
seek$delay2
dcr e ;
jnz seek$delay2 ; 896 uS
dcr d ;
jnz seek$delay2 ; 20*896=18 mS
push psw ; save status
lda trk ; Get trk
out p$disk$track ; Save in track reg
pop psw ; Restore status
ret
page
;
; This routine actually executes the read or write command. An initial
; read (or write) is sent out to the controller. Since we are not
; doing multiple sectors, an interrupt will be generated at the end of
; the command. This invokes the NMI set up at the beginning of the
; routine. Either an INI or OUTI will be performed, depending on whether
; a read or write is invoked. This lets us read in an entire sector
; more efficiently.
;
exec$command:
out p$disk$control ; send 1797 command
;
; Wait at least 24 usec after command before doing anything.
;
mvi a,09
exec$delay
dcr a
jnz exec$delay ; (delays 31 usec)
;
; Now, wait for completion, but TIME OUT after 1.2 seconds.
;
lxi d,60000+1
exec$0
in p$disk$control ; get status
rrc ; check not ready flag
jnc exec$success ; if 0, successful completion
inx d ; (waste time)
dcx d ;
inx d ;
dcx d ;
dcx d ; decrement count
mov a,d ;
ora e ;
adi 0ffh ;
jc exec$0 ; 20 us*60000=1.2 seconds
;
; Time out. Set A=0FFH and exit.
;
mvi a,0d0h ; Reset FDC
out p$disk$control ; send it out
mvi a,0ffh ; Set time out flag
jr exec$exit
;
; Successful completion. Return w/status byte in A.
exec$success
in p$disk$control ; Get status byte
exec$exit
push psw ; Save status
call start$motor$timer ; Start timer
pop psw ;
ret
page
;
; Start up motor timer
;
Start$motor$timer:
RET
;
; Exit from routine.
; Must set up interrupts appropriately.
;
fd$exit:
push psw ; save status
in p$disk$track ; read track
out p$disk$data ; write out data
;
; Restore nmi locations
;
lxi d,nmi ; Point at nmi
lxi h,nmibuf ; and at save buffer
lxi b,8 ; load count
ldir ; move saved data back
mvi a,0d0h ;
out p$disk$control ; Reset disk controller
pop psw
ora a ; set flags
ret ; and exit!!
;
; Set up NMI
; NMI (non-maskable interrupt) occurs at fixed location 0066h.
; During normal run time NMI does not occur, so these locations
; are normal RAM. During diskette data transfers, NMI is
; triggered by the diskette Data Request signal, so the NMI
; locations are temporarily overlayed by intructions to process
; the interrupt.
;
nmi equ 0066h
nmi$setup:
mvi a,fdc$reset ; Reset FDC
out p$disk$control ;
dcx d ; Point to TYPE field of XDPH
ldax d ; Get value
push psw ; (save)
ani 1 ; Mask # sides
sta no$of$sides ; store
pop psw ; Get TYPE again
push psw ; Save
db 07h ; Rotate 6&7into 0&1
db 07h ;
ani 0000$0011b ; Mask bits
sta step$rate ; store step rate
pop psw ; Get type
ani 0000$0010b ; Mask TPI field
rar ; Rotate into bit 0
sta TPI ; store
lxi d,nmibuf ; point to storage area
lxi h,nmi ; original values
lxi b,8 ; set up length
ldir ; save them away
lxi h,0d908h ; "EX AF,AF'"
shld nmi ; beginning of nmi
shld nmi+4 ; also, end of nmi
lxi h,045edh ; "RETN"
shld nmi+6 ;
mvi a,3 ; Enable FDC interrupt
out p$8255$control ;
ret
cseg
u$conin$echo: ; get console input, echo it, and shift to upper case
call ?const ! ora a ! jrz u$c1 ; see if any char already struck
call ?conin ! jr u$conin$echo ; yes, eat it and try again
u$c1:
call ?conin ! push psw
mov c,a ! call ?cono
pop psw ! cpi 'a' ! rc
sui 'a'-'A' ; make upper case
ret
cseg
IF REAL$1050
ivect2 equ 0fff8h ;
ELSE
ivect2 equ 0ffe2h ; address of ivect+2
ENDIF
nmibuf ds 8 ; buffer to hold prev contents of nmi
disk$command ds 1 ; current wd1797 command
select$mask ds 1 ; current drive select code
old$track db 0ffh ; last track seeked to
dmaadr ds 2
sect ds 1
trk ds 1 ; track for current operation
disk$status ds 1 ; last error status code for messages
fd0$access
fd0$trk db 0ffh ; last track accessed on A
fd1$trk db 0ffh ; last track accessed on B
; error message components
read$msg db ', Read',0
write$msg db ', Write',0
operation$name dw read$msg
; table of pointers to error message strings
; first entry is for bit 7 of 1797 status byte
error$table dw b7$msg
dw b6$msg
dw b5$msg
dw b4$msg
dw b3$msg
dw b2$msg
dw b1$msg
dw b0$msg
b7$msg db ' Not ready,',0
b6$msg db ' Protect,',0
b5$msg db ' Fault,',0
b4$msg db ' Record not found,',0
b3$msg db ' CRC,',0
b2$msg db ' Lost data,',0
b1$msg db ' DREQ,',0
b0$msg db ' Busy,',0
time$out db ' Time out,',0
error$msg db ' Retry (Y/N) ? ',0
precomp$limit db 43
no$of$sides db 0 ; # sides
step$rate db 0
tpi db 0
motor$counter db 0
end
title 'Root module of relocatable BIOS for CP/M 3.0'
; version 1.0 15 Sept 82
true equ -1
false equ not true
banked equ false
; Copyright (C), 1982
; Digital Research, Inc
; P.O. Box 579
; Pacific Grove, CA 93950
; This is the invariant portion of the modular BIOS and is
; distributed as source for informational purposes only.
; All desired modifications should be performed by
; adding or changing externally defined modules.
; This allows producing "standard" I/O modules that
; can be combined to support a particular system
; configuration.
cr equ 13
lf equ 10
bell equ 7
ctlQ equ 'Q'-'@'
ctlS equ 'S'-'@'
ccp equ 0100h ; Console Command Processor gets loaded into the TPA
cseg ; GENCPM puts CSEG stuff in common memory
; variables in system data page
extrn @covec,@civec,@aovec,@aivec,@lovec ; I/O redirection vectors
extrn @mxtpa ; addr of system entry point
extrn @bnkbf ; 128 byte scratch buffer
; user defined character I/O routines
extrn ?ci,?co,?cist,?cost ; each take device in <B>
extrn ?cinit ; (re)initialize device in <C>
extrn @ctbl ; physical character device table
; disk communication data items
extrn @dtbl ; table of pointers to XDPHs
public @adrv,@rdrv,@trk,@sect ; parameters for disk I/O
public @dma,@dbnk,@cnt ; '' '' '' ''
; memory control
public @cbnk ; current bank
extrn ?xmove,?move ; select move bank, and block move
extrn ?bank ; select CPU bank
; clock support
extrn ?init
extrn ?time ; signal time operation
; general utility routines
public ?pmsg,?pdec ; print message, print number from 0 to 65535
public ?pderr ; print BIOS disk error message header
maclib modebaud ; define mode bits
; External names for BIOS entry points
public ?boot,?wboot,?const,?conin,?cono,?list,?auxo,?auxi
public ?home,?sldsk,?sttrk,?stsec,?stdma,?read,?write
public ?lists,?sctrn
public ?conos,?auxis,?auxos,?dvtbl,?devin,?drtbl
public ?mltio,?flush,?mov,?tim,?bnksl,?stbnk,?xmov
; BIOS Jump vector.
; All BIOS routines are invoked by calling these
; entry points.
?boot: jmp boot ; initial entry on cold start
?wboot: jmp wboot ; reentry on program exit, warm start
?const: jmp const ; return console input status
?conin: jmp conin ; return console input character
?cono: jmp conout ; send console output character
?list: jmp list ; send list output character
?auxo: jmp auxout ; send auxilliary output character
?auxi: jmp auxin ; return auxilliary input character
?home: jmp home ; set disks to logical home
?sldsk: jmp seldsk ; select disk drive, return disk parameter info
?sttrk: jmp settrk ; set disk track
?stsec: jmp setsec ; set disk sector
?stdma: jmp setdma ; set disk I/O memory address
?read: jmp read ; read physical block(s)
?write: jmp write ; write physical block(s)
?lists: jmp listst ; return list device status
?sctrn: jmp sectrn ; translate logical to physical sector
?conos: jmp conost ; return console output status
?auxis: jmp auxist ; return aux input status
?auxos: jmp auxost ; return aux output status
?dvtbl: jmp devtbl ; return address of device def table
?devin: jmp ?cinit ; change baud rate of device
?drtbl: jmp getdrv ; return address of disk drive table
?mltio: jmp multio ; set multiple record count for disk I/O
?flush: jmp flush ; flush BIOS maintained disk caching
?mov: jmp ?move ; block move memory to memory
?tim: jmp ?time ; Signal Time and Date operation
?bnksl: jmp bnksel ; select bank for code execution and default DMA
?stbnk: jmp setbnk ; select different bank for disk I/O DMA operations.
?xmov: jmp ?xmove ; set source and destination banks for one operation
jmp 0 ; reserved for future expansion
jmp 0 ; reserved for future expansion
jmp 0 ; reserved for future expansion
; BOOT
; Initial entry point for system startup.
dseg ; this part can be banked
boot:
mvi c,15 ; initialize all 16 character devices
c$init$loop:
push b ! call ?cinit ! pop b
dcr c ! jp c$init$loop
call ?init
lxi b,16*256+0 ! lxi h,@dtbl ; init all 16 logical disk drives
d$init$loop:
push b ; save remaining count and abs drive
mov e,m ! inx h ! mov d,m ! inx h ; grab @drv entry
mov a,e ! ora d ! jz d$init$next ; if null, no drive
push h ; save @drv pointer
xchg ; XDPH address in <HL>
dcx h ! dcx h ! mov a,m ! sta @RDRV ; get relative drive code
mov a,c ! sta @ADRV ; get absolute drive code
dcx h ; point to init pointer
mov d,m ! dcx h ! mov e,m ; get init pointer
xchg ! call ipchl ; call init routine
pop h ; recover @drv pointer
d$init$next:
pop b ; recover counter and drive #
inr c ! dcr b ! jnz d$init$loop ; and loop for each drive
jmp boot$1
cseg ; following in resident memory
boot$1:
call set$jumps
;
;
; call ?ldccp ; fetch CCP for first time
; jmp ccp
ret
; WBOOT
; Entry for system restarts.
wboot:
call set$jumps ; initialize page zero
;
; call ?rlccp ; reload CCP
; jmp ccp ; then reset jmp vectors and exit to ccp
ret
set$jumps:
if banked
mvi a,1 ! call ?bnksl
endif
mvi a,JMP
sta 0 ! sta 5 ; set up jumps in page zero
lxi h,?wboot ! shld 1 ; BIOS warm start entry
lhld @MXTPA ! shld 6 ; BDOS system call entry
ret
ds 64
boot$stack equ $
; DEVTBL
; Return address of character device table
devtbl:
lxi h,@ctbl ! ret
; GETDRV
; Return address of drive table
getdrv:
lxi h,@dtbl ! ret
; CONOUT
; Console Output. Send character in <C>
; to all selected devices
conout:
lhld @covec ; fetch console output bit vector
jmp out$scan
; AUXOUT
; Auxiliary Output. Send character in <C>
; to all selected devices
auxout:
lhld @aovec ; fetch aux output bit vector
jmp out$scan
; LIST
; List Output. Send character in <C>
; to all selected devices.
list:
lhld @lovec ; fetch list output bit vector
out$scan:
mvi b,0 ; start with device 0
co$next:
dad h ; shift out next bit
jnc not$out$device
push h ; save the vector
push b ; save the count and character
not$out$ready:
call coster ! ora a ! jz not$out$ready
pop b ! push b ; restore and resave the character and device
call ?co ; if device selected, print it
pop b ; recover count and character
pop h ; recover the rest of the vector
not$out$device:
inr b ; next device number
mov a,h ! ora l ; see if any devices left
jnz co$next ; and go find them...
ret
; CONOST
; Console Output Status. Return true if
; all selected console output devices
; are ready.
conost:
lhld @covec ; get console output bit vector
jmp ost$scan
; AUXOST
; Auxiliary Output Status. Return true if
; all selected auxiliary output devices
; are ready.
auxost:
lhld @aovec ; get aux output bit vector
jmp ost$scan
; LISTST
; List Output Status. Return true if
; all selected list output devices
; are ready.
listst:
lhld @lovec ; get list output bit vector
ost$scan:
mvi b,0 ; start with device 0
cos$next:
dad h ; check next bit
push h ; save the vector
push b ; save the count
mvi a,0FFh ; assume device ready
cc coster ; check status for this device
pop b ; recover count
pop h ; recover bit vector
ora a ; see if device ready
rz ; if any not ready, return false
inr b ; drop device number
mov a,h ! ora l ; see if any more selected devices
jnz cos$next
ori 0FFh ; all selected were ready, return true
ret
coster: ; check for output device ready, including optional
; xon/xoff support
mov l,b ! mvi h,0 ; make device code 16 bits
push h ; save it in stack
dad h ! dad h ! dad h ; create offset into device characteristics tbl
lxi d,@ctbl+6 ! dad d ; make address of mode byte
mov a,m ! ani mb$xonxoff
pop h ; recover console number in <HL>
jz ?cost ; not a xon device, go get output status direct
lxi d,xofflist ! dad d ; make pointer to proper xon/xoff flag
call cist1 ; see if this keyboard has character
mov a,m ! cnz ci1 ; get flag or read key if any
cpi ctlq ! jnz not$q ; if its a ctl-Q,
mvi a,0FFh ; set the flag ready
not$q:
cpi ctls ! jnz not$s ; if its a ctl-S,
mvi a,00h ; clear the flag
not$s:
mov m,a ; save the flag
call cost1 ; get the actual output status,
ana m ; and mask with ctl-Q/ctl-S flag
ret ; return this as the status
cist1: ; get input status with <BC> and <HL> saved
push b ! push h
call ?cist
pop h ! pop b
ora a
ret
cost1: ; get output status, saving <BC> & <HL>
push b ! push h
call ?cost
pop h ! pop b
ora a
ret
ci1: ; get input, saving <BC> & <HL>
push b ! push h
call ?ci
pop h ! pop b
ret
; CONST
; Console Input Status. Return true if
; any selected console input device
; has an available character.
const:
lhld @civec ; get console input bit vector
jmp ist$scan
; AUXIST
; Auxiliary Input Status. Return true if
; any selected auxiliary input device
; has an available character.
auxist:
lhld @aivec ; get aux input bit vector
ist$scan:
mvi b,0 ; start with device 0
cis$next:
dad h ; check next bit
mvi a,0 ; assume device not ready
cc cist1 ; check status for this device
ora a ! rnz ; if any ready, return true
inr b ; drop device number
mov a,h ! ora l ; see if any more selected devices
jnz cis$next
xra a ; all selected were not ready, return false
ret
; CONIN
; Console Input. Return character from first
; ready console input device.
conin:
lhld @civec
jmp in$scan
; AUXIN
; Auxiliary Input. Return character from first
; ready auxiliary input device.
auxin:
lhld @aivec
in$scan:
push h ; save bit vector
mvi b,0
ci$next:
dad h ; shift out next bit
mvi a,0 ; insure zero a (nonexistant device not ready).
cc cist1 ; see if the device has a character
ora a
jnz ci$rdy ; this device has a character
inr b ; else, next device
mov a,h ! ora l ; see if any more devices
jnz ci$next ; go look at them
pop h ; recover bit vector
jmp in$scan ; loop til we find a character
ci$rdy:
pop h ; discard extra stack
jmp ?ci
; Utility Subroutines
ipchl: ; vectored CALL point
pchl
?pmsg: ; print message @<HL> up to a null
; saves <BC> & <DE>
push b
push d
pmsg$loop:
mov a,m ! ora a ! jz pmsg$exit
mov c,a ! push h
call ?cono ! pop h
inx h ! jmp pmsg$loop
pmsg$exit:
pop d
pop b
ret
?pdec: ; print binary number 0-65535 from <HL>
lxi b,table10! lxi d,-10000
next:
mvi a,'0'-1
pdecl:
push h! inr a! dad d! jnc stoploop
inx sp! inx sp! jmp pdecl
stoploop:
push d! push b
mov c,a! call ?cono
pop b! pop d
nextdigit:
pop h
ldax b! mov e,a! inx b
ldax b! mov d,a! inx b
mov a,e! ora d! jnz next
ret
table10:
dw -1000,-100,-10,-1,0
?pderr:
lxi h,drive$msg ! call ?pmsg ; error header
lda @adrv ! adi 'A' ! mov c,a ! call ?cono ; drive code
lxi h,track$msg ! call ?pmsg ; track header
lhld @trk ! call ?pdec ; track number
lxi h,sector$msg ! call ?pmsg ; sector header
lhld @sect ! call ?pdec ; sector number
ret
; BNKSEL
; Bank Select. Select CPU bank for further execution.
bnksel:
sta @cbnk ; remember current bank
jmp ?bank ; and go exit through users
; physical bank select routine
xofflist db -1,-1,-1,-1,-1,-1,-1,-1 ; ctl-s clears to zero
db -1,-1,-1,-1,-1,-1,-1,-1
dseg ; following resides in banked memory
; Disk I/O interface routines
; SELDSK
; Select Disk Drive. Drive code in <C>.
; Invoke login procedure for drive
; if this is first select. Return
; address of disk parameter header
; in <HL>
seldsk:
mov a,c ! sta @adrv ; save drive select code
mov l,c ! mvi h,0 ! dad h ; create index from drive code
lxi b,@dtbl ! dad b ; get pointer to dispatch table
mov a,m ! inx h ! mov h,m ! mov l,a ; point at disk descriptor
ora h ! rz ; if no entry in table, no disk
mov a,e ! ani 1 ! jnz not$first$select ; examine login bit
push h ! xchg ; put pointer in stack & <DE>
lxi h,-2 ! dad d ! mov a,m ! sta @RDRV ; get relative drive
lxi h,-6 ! dad d ; find LOGIN addr
mov a,m ! inx h ! mov h,m ! mov l,a ; get address of LOGIN routine
call ipchl ; call LOGIN
pop h ; recover DPH pointer
not$first$select:
ret
; HOME
; Home selected drive. Treated as SETTRK(0).
home:
lxi b,0 ; same as set track zero
; SETTRK
; Set Track. Saves track address from <BC>
; in @TRK for further operations.
settrk:
mov l,c ! mov h,b
shld @trk
ret
; SETSEC
; Set Sector. Saves sector number from <BC>
; in @sect for further operations.
setsec:
mov l,c ! mov h,b
shld @sect
ret
; SETDMA
; Set Disk Memory Address. Saves DMA address
; from <BC> in @DMA and sets @DBNK to @CBNK
; so that further disk operations take place
; in current bank.
setdma:
mov l,c ! mov h,b
shld @dma
lda @cbnk ; default DMA bank is current bank
; fall through to set DMA bank
; SETBNK
; Set Disk Memory Bank. Saves bank number
; in @DBNK for future disk data
; transfers.
setbnk:
sta @dbnk
ret
; SECTRN
; Sector Translate. Indexes skew table in <DE>
; with sector in <BC>. Returns physical sector
; in <HL>. If no skew table (<DE>=0) then
; returns physical=logical.
sectrn:
mov l,c ! mov h,b
mov a,d ! ora e ! rz
xchg ! dad b ! mov l,m ! mvi h,0
ret
; READ
; Read physical record from currently selected drive.
; Finds address of proper read routine from
; extended disk parameter header (XDPH).
read:
lhld @adrv ! mvi h,0 ! dad h ; get drive code and double it
lxi d,@dtbl ! dad d ; make address of table entry
mov a,m ! inx h ! mov h,m ! mov l,a ; fetch table entry
push h ; save address of table
lxi d,-8 ! dad d ; point to read routine address
jmp rw$common ; use common code
; WRITE
; Write physical sector from currently selected drive.
; Finds address of proper write routine from
; extended disk parameter header (XDPH).
write:
lhld @adrv ! mvi h,0 ! dad h ; get drive code and double it
lxi d,@dtbl ! dad d ; make address of table entry
mov a,m ! inx h ! mov h,m ! mov l,a ; fetch table entry
push h ; save address of table
lxi d,-10 ! dad d ; point to write routine address
rw$common:
mov a,m ! inx h ! mov h,m ! mov l,a ; get address of routine
pop d ; recover address of table
dcx d ! dcx d ; point to relative drive
ldax d ! sta @rdrv ; get relative drive code and post it
inx d ! inx d ; point to DPH again
pchl ; leap to driver
; MULTIO
; Set multiple sector count. Saves passed count in
; @CNT
multio:
sta @cnt ! ret
; FLUSH
; BIOS deblocking buffer flush. Not implemented.
flush:
xra a ! ret ; return with no error
; error message components
drive$msg db cr,lf,bell,'BIOS Error on ',0
track$msg db ': T-',0
sector$msg db ', S-',0
; disk communication data items
@adrv ds 1 ; currently selected disk drive
@rdrv ds 1 ; controller relative disk drive
@trk ds 2 ; current track number
@sect ds 2 ; current sector number
@dma ds 2 ; current DMA address
@cnt db 0 ; record count for multisector transfer
@dbnk db 0 ; bank for DMA operations
cseg ; common memory
@cbnk db 0 ; bank for processor operations
end
title 'bank & move module for CP/M3 linked BIOS' cseg public ?move,?xmove,?bank extrn @cbnk extrn ?pmsg maclib z80 maclib vislib maclib ports ?bank: ?xmove: ; 1050 can't perform interbank moves ret ?move: xchg ; we are passed source in DE and dest in HL ldir ; use Z80 block move instruction xchg ; need next addresses in same regs ret end
public @dtbl extrn fddd0 cseg @dtbl dw fddd0 ; A drive = floppy dw 0 ; B drive = floppy dw 0,0,0,0 ; C-F not available dw 0 ; G drive = winchester dw 0,0,0,0,0,0,0,0,0 ; drives H-P non-existant end
; equates for mode byte bit fields
mb$input equ 0000$0001b ; device may do input
mb$output equ 0000$0010b ; device may do output
mb$in$out equ mb$input+mb$output
mb$soft$baud equ 0000$0100b ; software selectable
; baud rates
mb$serial equ 0000$1000b ; device may use protocol
mb$xon$xoff equ 0001$0000b ; XON/XOFF protocol
; enabled
baud$none equ 0 ; no baud rate associated
; with this device
baud$50 equ 1 ; 50 baud
baud$75 equ 2 ; 75 baud
baud$110 equ 3 ; 110 baud
baud$134 equ 4 ; 134.5 baud
baud$150 equ 5 ; 150 baud
baud$300 equ 6 ; 300 baud
baud$600 equ 7 ; 600 baud
baud$1200 equ 8 ; 1200 baud
baud$1800 equ 9 ; 1800 baud
baud$2400 equ 10 ; 2400 baud
baud$3600 equ 11 ; 3600 baud
baud$4800 equ 12 ; 4800 baud
baud$7200 equ 13 ; 7200 baud
baud$9600 equ 14 ; 9600 baud
baud$19200 equ 15 ; 19.2k baud
; 8251 Modes
;
mode$s2 equ 1000$0000b ; stop bit high
mode$s1 equ 0100$0000b ; stop bit low
mode$ep equ 0010$0000b ; even parity
mode$pen equ 0001$0000b ; parity enable
mode$12 equ 0000$1000b ; char length high
mode$11 equ 0000$0100b ; char length low
mode$b2 equ 0000$0010b ; baud rate factor high
mode$b1 equ 0000$0001b ; baud rate factor low
com$eh equ 1000$0000b ; enter hunt mode
com$ir equ 0100$0000b ; internal reset
com$rts equ 0010$0000b ; request to send
com$er equ 0001$0000b ; error reset
com$sbrk equ 0000$1000b ; send break
com$rxe equ 0000$0100b ; receive enable
com$dtr equ 0000$0010b ; data terminal ready
com$txe equ 0000$0001b ; transmit enable
; some common values
;
mode$eight equ 0000$1100b ; eight data bits
mode$onestop equ 0100$0000b ; one stop bit
mode$div16 equ 0000$0010b ; 16x baud rate factor
mode$div64 equ 0000$0011b ; 64x baud rate factor
mode$baudmask equ 0000$0011b ; to get baud bits from port c
mode$default equ mode$onestop or mode$eight or mode$div16
; one stop, eight data, no parity, 16x
com$default equ com$rts or com$rxe
; request to send and receive enable
; port c base baud rate definitions
;
misc$base01200 equ 0000$0000b ; port c 1200 baud
misc$base02400 equ 0000$0100b ; port c 2400 baud
misc$base19200 equ 0000$1000b ; port c 19200 baud
misc$base09600 equ 0000$1100b ; port c 9600 baud
misc$baudmask equ 0000$1100b ; mask for port c base baud rate
;
; Display 8255 Modes
;
disp$model equ 0B4H ;mode: A=1&IN, CH=OT, b=1&OT, CL=XX
disp$set2c equ 005H ;set bit 2 of port c
disp$set4c equ 009H ;set bit 4 of port c
disp$set6c equ 00DH ;set bit 6 of port c
disp$set7c equ 00FH ;set bit 7 of port c
;
; Keyboard 8251 mode bytes
;
kb$default equ mode$onestop or mode$eight or mode$div16
; one stop, eight data, no parity, 16x
kb$ir equ 0100$0000b ; internal reset
kb$sig equ com$er or com$rxe or com$txe
; err reset, RxE, TxE
kb$click equ 0101$0101b ; keyboard click
;
; Miscellaneous 8255
;
lpt$mode0 equ 088H ; Mode:A=0&OT, CH=IN, B=0&OT, c=OT
lpt$baud equ 0000$1100b ; 1200 baud
motor$on equ 0eh ; Motor on
motor$off equ 0fh ; Motor off
;
; Floppy disk controller modes
;
fdc$reset equ 1101$0000b ; Reset
title 'bank & move module for CP/M3 linked BIOS' cseg public ?move,?xmove,?bank extrn @cbnk extrn ?pmsg maclib z80 maclib vislib maclib ports ?xmove: ret ?move: xchg ; we are passed source in DE and dest in HL ldir ; use Z80 block move instruction xchg ; need next addresses in same regs ret ; ; Select memory bank. Enter routine with desired bank in A ; register. Preserve all registers except A. ; ?bank: if banked ; ral ; rotate into 1&2 ori 01h ; set bit 0 on out p$bank$select ; Select bank endif ret ; return end
; PORTS.LIB ; I/O Port addresses for Visual 1050 ; ; Copyright (c) Visual Technology, 1983 ; ; Revision history: ; 01 (IRN) Initial- based on cpm/3 for Ontel Amigo ; real$1050 equ true keyboard$1050 equ true banked equ true ; Boot Mode flip-flop ; p$boot equ 0D0h ; Boot prom enable/disable p$bank$select equ 0d0h ; Select bank ; ; Interrupt vectors ; int$port equ 0C0h int$initial equ 010h int$mask equ 1110$1111b ; All on except VERT all$ints equ 1111$1111b ; All interrupts enabled vert$clear equ 0A0h ; Clear vert interrupt ; ; Display section 8255 parallel interface ; p$disp$in equ 84h ; port a from display p$disp$out equ 85h ; port b to display p$disp$c equ 86h ; port c from/to display p$disp$control equ 87h ; control port for display clear$6502 equ 0b0h ; Keyboard 8251 - keyboard input, loudspeaker output ; p$kb$data equ 88h ; in= kb data, out= loudspeaker data p$kb$control equ 89h ; kb control/status ; RS-232 8251 ; p$aux1$data equ 8Ch ; rs-232 xmit/recv data p$aux1$control equ 8Dh ; rs-232 control/status ; Miscellaneous 8255 ; p$disk$bits equ 90h ; diskette bits p$printer equ 91h ; printer bits p$misc equ 92h ; miscellaneous bits p$8255$control equ 93h ; 8255 control port ; MB8877 Floppy disk formatter/controller ; p$disk$control equ 94h ; mb8877 status/command p$disk$track equ 95h ; mb8877 track p$disk$sector equ 96h ; mb8877 sector p$disk$data equ 97h ; mb8877 data ; Winchester Interface to Xebec Controller ; p$winch$data equ 0E0h ; Winchester data p$winch$control equ 0E1h ; Winchester control ; ; Real time clock ; p$clk$porta equ 09Ch ; Port A p$clk$portb equ 09Dh ; Port B p$clk$portc equ 09Eh ; Port C p$clk$control equ 09Fh ; Control port add$write$hi equ 09h add$write$lo equ 08h read$hi equ 0DH read$lo equ 0CH write$hi equ 0BH write$lo equ 0AH rtc$select equ 0fh rtc$read equ 91h rtc$write equ 81h ; Display masks ; disp$ready$rcv equ 0000$0001b ; ready to receive disp$ready$xmit equ 0000$1000b ; ready to xmit disp$req$rcv equ 0100$0000b ; req rcv from display disp$req$xmit equ 1000$0000b ; req xmit to display disp$portc$cons equ 0001$0100b ; port c constants ; Lpt masks ; misc$lpt$strobe equ 0000$0001b ; handshake strobe misc$lpt$avail equ 0010$0000b ; lpt available misc$lpt$nobusy equ 0001$0000b ; finished printing
; ; Private BIOS call ; ; When called, A is a subfunction identifier. (0 is illegal) ; Values in other registers depend upon particular subfunction. ; public Priv$call maclib Z80 extrn ?pmsg dseg PRIV$CALL: ora a ; Check value in A jrz bad$exit$2 ; Jump if 0 (illegal) cpi 01 ; Is it 1? jrnz priv$80 ; Jump if not ; ; A=1 is a private call which gives the user access to flags located ; in memory. Register values are as follows: ; ; C = # of byte to be accessed ; B =0FFH if setting a byte ; =0FEH if setting a word ; =000H if a GET operation ; (else, ignored) ; DE = word value to be set ; or D = byte value to be set ; ; Upon exit, A or HL will contain the byte or word on a GET operation. ; push b mov a,c ; Get OFFSET CPI MAX ; > MAX? JP BAD$EXIT ; Yes => error lxi h,bytetbl ; Point to bytetbl mvi b,0 ; Clear B DAD B ; Add to get address within tbl pop Psw ; Move B into A ORA A ; Check SET JRZ GET$op ; 0 => Get operation inr a ; Increment jrz set$byte ; FF => Set Byte inr a ; Increment jrnz bad$exit2 ; If not FE, error ; ; Set word in bytetbl to VALUE ; Set$word: mov m,e ; Move Move lo byte into table inx h ; Increment mov m,d ; Move hi byte into table jr priv$exit ; Exit ; ; Set byte in Bytetbl to VALUE. ; Set$byte: mov m,d ; Store it jr priv$exit ; ; ; Get operation. Move low byte into A and L, move high byte into H. ; GET$op: Mov a,m ; Get low byte inx h ; increment mov h,m ; Move high byte to H mov l,a ; Move low byte to L jr priv$exit ; exit ; ; Other functions can be implemented here: ; Priv$80: jr priv$exit ; ; Error exit ; Bad$exit: pop b bad$exit$2: ; ; Exit point ; Priv$exit: ret BYTETBL: DB 0 DB 0 DB 0 DB 0 DB 0 DB 0 DB 0 DB 0 DB 0 DB 0 MAX EQU $-BYTETBL end
;***************************************************
;* *
;* sample random access program for cp/m 3 *
;* *
;***************************************************
org 100h ;base of tpa
;
reboot equ 0000h ;system reboot
bdos equ 0005h ;bdos entry point
;
coninp equ 1 ;console input function
conout equ 2 ;console output function
pstring equ 9 ;print string until '$'
rstring equ 10 ;read console buffer
version equ 12 ;return version number
openf equ 15 ;file open function
closef equ 16 ;close function
makef equ 22 ;make file function
readr equ 33 ;read random
writer equ 34 ;write random
wrtrzf equ 40 ;write random zero fill
parsef equ 152 ;parse function
;
fcb equ 005ch ;default file control block
ranrec equ fcb+33 ;random record position
ranovf equ fcb+35 ;high order (overflow) byte
buff equ 0080h ;buffer address
;
cr equ 0dh ;carriage return
lf equ 0ah ;line feed
;
;***************************************************
;* *
;* load SP, set-up file for random access *
;* *
;***************************************************
lxi sp,stack
;
; version 3.1?
mvi c,version
call bdos
cpi 31h ;version 3.1 or better?
jnc versok
; bad version, message and go back
lxi d,badver
call print
jmp reboot
;
versok:
; correct version for random access
mvi c,openf ;open default fcb
rdname: lda fcb+1
cpi ' '
jnz opfile
lxi d,entmsg
call print
call parse
jmp versok
opfile: lxi d,fcb
call bdos
inr a ;err 255 becomes zero
jnz ready
;
; cannot open file, so create it
mvi c,makef
lxi d,fcb
call bdos
inr a ;err 255 becomes zero
jnz ready
;
; cannot create file, directory full
lxi d,nospace
call print
jmp reboot ;back to ccp
;
;***************************************************
;* *
;* loop back to "ready" after each command *
;* *
;***************************************************
;
ready:
; file is ready for processing
;
call readcom ;read next command
shld ranrec ;store input record#
lxi h,ranovf
mov m,c ;set ranrec high byte
cpi 'Q' ;quit?
jnz notq
;
; quit processing, close file
mvi c,closef
lxi d,fcb
call bdos
inr a ;err 255 becomes 0
jz error ;error message, retry
jmp reboot ;back to ccp
;
;***************************************************
;* *
;* end of quit command, process write *
;* *
;***************************************************
notq:
; not the quit command, random write?
cpi 'W'
jnz notw
;
; this is a random write, fill buffer until cr
lxi d,datmsg
call print ;data prompt
mvi c,127 ;up to 127 characters
lxi h,buff ;destination
rloop: ;read next character to buff
push b ;save counter
push h ;next destination
call getchr ;character to a
pop h ;restore counter
pop b ;restore next to fill
cpi cr ;end of line?
jz erloop
; not end, store character
mov m,a
inx h ;next to fill
dcr c ;counter goes down
jnz rloop ;end of buffer?
erloop:
; end of read loop, store 00
mvi m,0
;
; write the record to selected record number
mvi c,writer
lxi d,fcb
call bdos
ora a ;error code zero?
jnz error ;message if not
jmp ready ;for another record
;
;
;********************************************************
;* *
;* end of write command, process write random zero fill *
;* *
;********************************************************
notw:
; not the quit command, random write zero fill?
cpi 'F'
jnz notf
;
; this is a random write, fill buffer until cr
lxi d,datmsg
call print ;data prompt
mvi c,127 ;up to 127 characters
lxi h,buff ;destination
rloop1: ;read next character to buff
push b ;save counter
push h ;next destination
call getchr ;character to a
pop h ;restore counter
pop b ;restore next to fill
cpi cr ;end of line?
jz erloop1
; not end, store character
mov m,a
inx h ;next to fill
dcr c ;counter goes down
jnz rloop1 ;end of buffer?
erloop1:
; end of read loop, store 00
mvi m,0
;
; write the record to selected record number
mvi c,wrtrzf
lxi d,fcb
call bdos
ora a ;error code zero?
jnz error ;message if not
jmp ready ;for another record
;
;***************************************************
;* *
;* end of write commands, process read *
;* *
;***************************************************
notf:
; not a write command, read record?
cpi 'R'
jnz error ;skip if not
;
; read random record
mvi c,readr
lxi d,fcb
call bdos
ora a ;return code 00?
jnz error
;
; read was successful, write to console
call crlf ;new line
mvi c,128 ;max 128 characters
lxi h,buff ;next to get
wloop:
mov a,m ;next character
inx h ;next to get
ani 7fh ;mask parity
jz ready ;for another command if 00
push b ;save counter
push h ;save next to get
cpi ' ' ;graphic?
cnc putchr ;skip output if not
pop h
pop b
dcr c ;count=count-1
jnz wloop
jmp ready
;
;***************************************************
;* *
;* end of read command, all errors end-up here *
;* *
;***************************************************
;
error:
lxi d,errmsg
call print
jmp ready
;
;***************************************************
;* *
;* utility subroutines for console i/o *
;* *
;***************************************************
getchr:
;read next console character to a
mvi c,coninp
call bdos
ret
;
putchr:
;write character from a to console
mvi c,conout
mov e,a ;character to send
call bdos ;send character
ret
;
crlf:
;send carriage return line feed
mvi a,cr ;carriage return
call putchr
mvi a,lf ;line feed
call putchr
ret
;
parse:
;read and parse filespec
lxi d,conbuf
mvi c,rstring
call bdos
lxi d,pfncb
mvi c,parsef
call bdos
ret
;
print:
;print the buffer addressed by de until $
push d
call crlf
pop d ;new line
mvi c,pstring
call bdos ;print the string
ret
;
readcom:
;read the next command line to the conbuf
lxi d,prompt
call print ;command?
mvi c,rstring
lxi d,conbuf
call bdos ;read command line
; command line is present, scan it
mvi c,0 ;start with 00
lxi h,0 ; 0000
lxi d,conlin;command line
readc: ldax d ;next command character
inx d ;to next command position
ora a ;cannot be end of command
rz
; not zero, numeric?
sui '0'
cpi 10 ;carry if numeric
jnc endrd
; add-in next digit
push psw
mov a,c ;value = ahl
dad h
adc a ;*2
push a ;save value * 2
push h
dad h ;*4
adc a
dad h ;*8
adc a
pop b ;*2 + *8 = *10
dad b
pop b
adc b
pop b ;+digit
mov c,b
mvi b,0
dad b
aci 0
mov c,a
jnc readc
jmp readcom
endrd:
; end of read, restore value in a
adi '0' ;command
cpi 'a' ;translate case?
rc
; lower case, mask lower case bits
ani 101$1111b
ret ;return with value in chl
;
;***************************************************
;* *
;* string data area for console messages *
;* *
;***************************************************
badver:
db 'sorry, you need cp/m version 3$'
nospace:
db 'no directory space$'
datmsg:
db 'type data: $'
errmsg:
db 'error, try again.$'
prompt:
db 'next command? $'
entmsg:
db 'enter filename: $'
;
;***************************************************
;* *
;* fixed and variable data area *
;* *
;***************************************************
conbuf: db conlen ;length of console buffer
consiz: ds 1 ;resulting size after read
conlin: ds 32 ;length 32 buffer
conlen equ $-consiz
;
pfncb:
dw conlin
dw fcb
;
ds 32 ;16 level stack
stack:
end
title 'System Control Block Definition for CP/M3 BIOS'
public @civec, @covec, @aivec, @aovec, @lovec, @bnkbf
public @crdma, @crdsk, @vinfo, @resel, @fx, @usrcd
public @mltio, @ermde, @erdsk, @media, @bflgs
public @date, @hour, @min, @sec, ?erjmp, @mxtpa
scb$base equ 0FE00H ; Base of the SCB
@CIVEC equ scb$base+22h ; Console Input Redirection
; Vector (word, r/w)
@COVEC equ scb$base+24h ; Console Output Redirection
; Vector (word, r/w)
@AIVEC equ scb$base+26h ; Auxiliary Input Redirection
; Vector (word, r/w)
@AOVEC equ scb$base+28h ; Auxiliary Output Redirection
; Vector (word, r/w)
@LOVEC equ scb$base+2Ah ; List Output Redirection
; Vector (word, r/w)
@BNKBF equ scb$base+35h ; Address of 128 Byte Buffer
; for Banked BIOS (word, r/o)
@CRDMA equ scb$base+3Ch ; Current DMA Address
; (word, r/o)
@CRDSK equ scb$base+3Eh ; Current Disk (byte, r/o)
@VINFO equ scb$base+3Fh ; BDOS Variable "INFO"
; (word, r/o)
@RESEL equ scb$base+41h ; FCB Flag (byte, r/o)
@FX equ scb$base+43h ; BDOS Function for Error
; Messages (byte, r/o)
@USRCD equ scb$base+44h ; Current User Code (byte, r/o)
@MLTIO equ scb$base+4Ah ; Current Multi-Sector Count
; (byte,r/w)
@ERMDE equ scb$base+4Bh ; BDOS Error Mode (byte, r/o)
@ERDSK equ scb$base+51h ; BDOS Error Disk (byte,r/o)
@MEDIA equ scb$base+54h ; Set by BIOS to indicate
; open door (byte,r/w)
@BFLGS equ scb$base+57h ; BDOS Message Size Flag (byte,r/o)
@DATE equ scb$base+58h ; Date in Days Since 1 Jan 78
; (word, r/w)
@HOUR equ scb$base+5Ah ; Hour in BCD (byte, r/w)
@MIN equ scb$base+5Bh ; Minute in BCD (byte, r/w)
@SEC equ scb$base+5Ch ; Second in BCD (byte, r/w)
?ERJMP equ scb$base+5Fh ; BDOS Error Message Jump
; (word, r/w)
@MXTPA equ scb$base+62h ; Top of User TPA
; (address at 6,7)(word, r/o)
end
;solution.lib ; Standard equates ; true equ 0ffh ; self explanatory false equ 0 ; ditto
; @CHK MACRO USED FOR CHECKING 8 BIT DISPLACMENTS ; @CHK MACRO ?DD ;; USED FOR CHECKING RANGE OF 8-BIT DISP.S IF (?DD GT 7FH) AND (?DD LT 0FF80H) 'DISPLACEMENT RANGE ERROR - Z80 LIB' ENDIF ENDM LDX MACRO ?R,?D @CHK ?D DB 0DDH,?R*8+46H,?D ENDM LDY MACRO ?R,?D @CHK ?D DB 0FDH,?R*8+46H,?D ENDM STX MACRO ?R,?D @CHK ?D DB 0DDH,70H+?R,?D ENDM STY MACRO ?R,?D @CHK ?D DB 0FDH,70H+?R,?D ENDM MVIX MACRO ?N,?D @CHK ?D DB 0DDH,36H,?D,?N ENDM MVIY MACRO ?N,?D @CHK ?D DB 0FDH,36H,?D,?N ENDM LDAI MACRO DB 0EDH,57H ENDM LDAR MACRO DB 0EDH,5FH ENDM STAI MACRO DB 0EDH,47H ENDM STAR MACRO DB 0EDH,4FH ENDM LXIX MACRO ?NNNN DB 0DDH,21H DW ?NNNN ENDM LXIY MACRO ?NNNN DB 0FDH,21H DW ?NNNN ENDM LDED MACRO ?NNNN DB 0EDH,5BH DW ?NNNN ENDM LBCD MACRO ?NNNN DB 0EDH,4BH DW ?NNNN ENDM LSPD MACRO ?NNNN DB 0EDH,07BH DW ?NNNN ENDM LIXD MACRO ?NNNN DB 0DDH,2AH DW ?NNNN ENDM LIYD MACRO ?NNNN DB 0FDH,2AH DW ?NNNN ENDM SBCD MACRO ?NNNN DB 0EDH,43H DW ?NNNN ENDM SDED MACRO ?NNNN DB 0EDH,53H DW ?NNNN ENDM SSPD MACRO ?NNNN DB 0EDH,73H DW ?NNNN ENDM SIXD MACRO ?NNNN DB 0DDH,22H DW ?NNNN ENDM SIYD MACRO ?NNNN DB 0FDH,22H DW ?NNNN ENDM SPIX MACRO DB 0DDH,0F9H ENDM SPIY MACRO DB 0FDH,0F9H ENDM PUSHIX MACRO DB 0DDH,0E5H ENDM PUSHIY MACRO DB 0FDH,0E5H ENDM POPIX MACRO DB 0DDH,0E1H ENDM POPIY MACRO DB 0FDH,0E1H ENDM EXAF MACRO DB 08H ENDM EXX MACRO DB 0D9H ENDM XTIX MACRO DB 0DDH,0E3H ENDM XTIY MACRO DB 0FDH,0E3H ENDM LDI MACRO DB 0EDH,0A0H ENDM LDIR MACRO DB 0EDH,0B0H ENDM LDD MACRO DB 0EDH,0A8H ENDM LDDR MACRO DB 0EDH,0B8H ENDM CCI MACRO DB 0EDH,0A1H ENDM CCIR MACRO DB 0EDH,0B1H ENDM CCD MACRO DB 0EDH,0A9H ENDM CCDR MACRO DB 0EDH,0B9H ENDM ADDX MACRO ?D @CHK ?D DB 0DDH,86H,?D ENDM ADDY MACRO ?D @CHK ?D DB 0FDH,86H,?D ENDM ADCX MACRO ?D @CHK ?D DB 0DDH,8EH,?D ENDM ADCY MACRO ?D @CHK ?D DB 0FDH,8EH,?D ENDM SUBX MACRO ?D @CHK ?D DB 0DDH,96H,?D ENDM SUBY MACRO ?D @CHK ?D DB 0FDH,96H,?D ENDM SBCX MACRO ?D @CHK ?D DB 0DDH,9EH,?D ENDM SBCY MACRO ?D @CHK ?D DB 0FDH,9EH,?D ENDM ANDX MACRO ?D @CHK ?D DB 0DDH,0A6H,?D ENDM ANDY MACRO ?D @CHK ?D DB 0FDH,0A6H,?D ENDM XORX MACRO ?D @CHK ?D DB 0DDH,0AEH,?D ENDM XORY MACRO ?D @CHK ?D DB 0FDH,0AEH,?D ENDM ORX MACRO ?D @CHK ?D DB 0DDH,0B6H,?D ENDM ORY MACRO ?D @CHK ?D DB 0FDH,0B6H,?D ENDM CMPX MACRO ?D @CHK ?D DB 0DDH,0BEH,?D ENDM CMPY MACRO ?D @CHK ?D DB 0FDH,0BEH,?D ENDM INRX MACRO ?D @CHK ?D DB 0DDH,34H,?D ENDM INRY MACRO ?D @CHK ?D DB 0FDH,34H,?D ENDM DCRX MACRO ?D @CHK ?D DB 0DDH,035H,?D ENDM DCRY MACRO ?D @CHK ?D DB 0FDH,35H,?D ENDM NEG MACRO DB 0EDH,44H ENDM IM0 MACRO DB 0EDH,46H ENDM IM1 MACRO DB 0EDH,56H ENDM IM2 MACRO DB 0EDH,5EH ENDM BC EQU 0 DE EQU 2 HL EQU 4 IX EQU 4 IY EQU 4 DADC MACRO ?R DB 0EDH,?R*8+4AH ENDM DSBC MACRO ?R DB 0EDH,?R*8+42H ENDM DADX MACRO ?R DB 0DDH,?R*8+09H ENDM DADY MACRO ?R DB 0FDH,?R*8+09H ENDM INXIX MACRO DB 0DDH,23H ENDM INXIY MACRO DB 0FDH,23H ENDM DCXIX MACRO DB 0DDH,2BH ENDM DCXIY MACRO DB 0FDH,2BH ENDM BIT MACRO ?N,?R DB 0CBH,?N*8+?R+40H ENDM SETB MACRO ?N,?R DB 0CBH,?N*8+?R+0C0H ENDM RES MACRO ?N,?R DB 0CBH,?N*8+?R+80H ENDM BITX MACRO ?N,?D @CHK ?D DB 0DDH,0CBH,?D,?N*8+46H ENDM BITY MACRO ?N,?D @CHK ?D DB 0FDH,0CBH,?D,?N*8+46H ENDM SETX MACRO ?N,?D @CHK ?D DB 0DDH,0CBH,?D,?N*8+0C6H ENDM SETY MACRO ?N,?D @CHK ?D DB 0FDH,0CBH,?D,?N*8+0C6H ENDM RESX MACRO ?N,?D @CHK ?D DB 0DDH,0CBH,?D,?N*8+86H ENDM RESY MACRO ?N,?D @CHK ?D DB 0FDH,0CBH,?D,?N*8+86H ENDM JR MACRO ?N DB 18H,?N-$-1 ENDM JRC MACRO ?N DB 38H,?N-$-1 ENDM JRNC MACRO ?N DB 30H,?N-$-1 ENDM JRZ MACRO ?N DB 28H,?N-$-1 ENDM JRNZ MACRO ?N DB 20H,?N-$-1 ENDM DJNZ MACRO ?N DB 10H,?N-$-1 ENDM PCIX MACRO DB 0DDH,0E9H ENDM PCIY MACRO DB 0FDH,0E9H ENDM RETI MACRO DB 0EDH,4DH ENDM RETN MACRO DB 0EDH,45H ENDM INP MACRO ?R DB 0EDH,?R*8+40H ENDM OUTP MACRO ?R DB 0EDH,?R*8+41H ENDM INI MACRO DB 0EDH,0A2H ENDM INIR MACRO DB 0EDH,0B2H ENDM IND MACRO DB 0EDH,0AAH ENDM INDR MACRO DB 0EDH,0BAH ENDM OUTI MACRO DB 0EDH,0A3H ENDM OUTIR MACRO DB 0EDH,0B3H ENDM OUTD MACRO DB 0EDH,0ABH ENDM OUTDR MACRO DB 0EDH,0BBH ENDM RLCR MACRO ?R DB 0CBH, 00H + ?R ENDM RLCX MACRO ?D @CHK ?D DB 0DDH, 0CBH, ?D, 06H ENDM RLCY MACRO ?D @CHK ?D DB 0FDH, 0CBH, ?D, 06H ENDM RALR MACRO ?R DB 0CBH, 10H+?R ENDM RALX MACRO ?D @CHK ?D DB 0DDH, 0CBH, ?D, 16H ENDM RALY MACRO ?D @CHK ?D DB 0FDH, 0CBH, ?D, 16H ENDM RRCR MACRO ?R DB 0CBH, 08H + ?R ENDM RRCX MACRO ?D @CHK ?D DB 0DDH, 0CBH, ?D, 0EH ENDM RRCY MACRO ?D @CHK ?D DB 0FDH, 0CBH, ?D, 0EH ENDM RARR MACRO ?R DB 0CBH, 18H + ?R ENDM RARX MACRO ?D @CHK ?D DB 0DDH, 0CBH, ?D, 1EH ENDM RARY MACRO ?D @CHK ?D DB 0FDH, 0CBH, ?D, 1EH ENDM SLAR MACRO ?R DB 0CBH, 20H + ?R ENDM SLAX MACRO ?D @CHK ?D DB 0DDH, 0CBH, ?D, 26H ENDM SLAY MACRO ?D @CHK ?D DB 0FDH, 0CBH, ?D, 26H ENDM SRAR MACRO ?R DB 0CBH, 28H+?R ENDM SRAX MACRO ?D @CHK ?D DB 0DDH, 0CBH, ?D, 2EH ENDM SRAY MACRO ?D @CHK ?D DB 0FDH, 0CBH, ?D, 2EH ENDM SRLR MACRO ?R DB 0CBH, 38H + ?R ENDM SRLX MACRO ?D @CHK ?D DB 0DDH, 0CBH, ?D, 3EH ENDM SRLY MACRO ?D @CHK ?D DB 0FDH, 0CBH, ?D, 3EH ENDM RLD MACRO DB 0EDH, 6FH ENDM RRD MACRO DB 0EDH, 67H ENDM