	.title	'DSC/3, DSC/4 CP/M 1.4 BIOS'  
	.sbttl	'NETWORK SLAVE DRIVE B'
version	==	1	; CP/M version number
revision==	432	; DMS revision number
;----------
; DSC/3, DSC/4 NETWORK TEST BIOS
;
;	       Network station
;	       ---------------
; Floppy network	     Harddisk network
; --------------	     ----------------
; A>ddt cpmF8.com	     A>ddt cpmF8.com
; -ibios.hex     	     -ma00 1f00 100
; -r3000		     -ibios.hex
; -^C			     -r2200
; A>save 47 fstation.com     -^C
; A>sysgen		     A>save 33 hstation.com
; 			     A>wrun0 hstation.com 2 3F
;----------
; Conditional assembly options:
;  0 to select, 1 to not select
; 
AHEADopt==	0	; console type-ahead
BACKopt	==	0	; backspace
FRONTopt==	0	; front-panel interrupts
TIMERopt==	0	; real-time clock interrupts
SELECT	==  	3
	.ife	SELECT-3,[
FLOPboot==	1	
NETboot	==	0	
HARDboot==	1	
MASTopt	==	1	
FLOPopt	==	1	
NETopt	==	0	
HARDopt	==	1	
FLOPshar==	0
HARDshar==	1-FLOPshar
	]
	.pabs
	.phex
;----------
; Data and address definitions:
;
Msize	==	64	; memory size (Kbytes)
.ife	SELECT-3,[ lenBIOS = 0800h]
lenCPM	==	1500h	; length of CCP + BDOS
lenBOOT	==	400h	; length of network CBOOT code
BIOS	==	Msize*1024-lenBIOS ; base of the BIOS
DDToff	==	1F00h-BIOS ; used for DDT sysgen
CBASE	==	(Msize-16)*1024+512-lenBIOS
CPM	==	CBASE+2900h ; base of the CCP
BDOS	==	CBASE+3106h ; base of the BDOS
TPA	==	100h	; transient program area
wrbuffer==	0FF00h	; floppy write buffer
rdbuffer==	0FE00h	; floppy read buffer
.ife	HARDshar,[numusr   == 32
		  userlist == 0FC00h
		  numlock  == 16
		  locklist == 0FB00h
		  MASTbuf  == 0FA80h
		  MASTstac == 0FA80h]
.ife	FLOPshar,[numusr   == 4
		  userlist == 0FDC0h
		  MASTbuf  == 0FD40h
		  MASTstac == 0FD40h]
cntlC	==	03h	; <control-C>
cr	==	0Dh	; <return>
lf	==	0Ah	; <linefeed>
bell	==	07h	; <bell>
backsp	==	08h	; <backspace>
cntlP	==	10h	; <ctrl-P>
rubout	==	7Fh	; <rubout>
RxRDY	==	0	; receiver ready bit
TxRDY	==	2	; transmitter ready bit
RETRY	==	10	; number of I/O error retries
onprecmp==	22	; turn on pre-compensation
maxprec	==	59	; turn off S/L bit
loadset	==	1500h	; head load settle time (35 ms)
stepset	==	600h	; head step settle time (10 ms)
typeahead ==	31	; type-ahead length
unloadHEAD ==  60	; unload head if inactive 1 sec
fastrec	==     2	; master receive time-out
slowrec ==     240	; slave receive time-out
wakeup  ==  	1	; wakeup net master this often
sendboot==      60/wakeup; freq of boot broadcast
sendlog	==      15/wakeup; freq of login poll
;----------
; BIOS low-memory contents:
ticks	==	40h	; 1/60ths \
secs	==	41h	; seconds  \  Time since
mins	==	42h	; minutes  /  cold boot
hrs	==	43h	; hours   /
day	==	44h	; day   \
month	==	45h	; month -  Set by SETTIME
year	==	46h	; year  /
NETusr	==	47h	; network user number
ticsec	==	48h	; ticks per second
LOCKstat==	49h	; HiNet lock status
LOCKadr	==	4Ah	; address of HiNet lock
retries	==	4Eh	; retries on a CRC error
errors	==	4Fh	; CRC errors since cold boot
;----------
; BIOS high-memory contents:
	.loc	0FFF1h
FLOPmdl:.byte	0	; 0 for Shugart 800, 1 for 850
hertz:	.byte	60	; clock hertz rate
baud1:	.byte	45h,2	; port 1 default 500 Khz
baud23: .byte	45h,13	; ports 2,3 default 9600 baud
IOBYTE:	.byte	54h	; default value of IOBYTE
BIOSbas:
	.ife	FLOPboot&HARDboot,[.word BIOS ]
	.ife	NETboot,	  [.word CBOOT]
CPMbase:.word	CPM	; CP/M base address
	.byte	version*10+revision/100
	.byte	revision@100
serial:	.word	0	; PROM serial number
	.reloc
;----------
; Port definitions:
DMA	==	38h
CTC0	==	30h	; CTC channel 0 (port 0)
CTC1	==	31h	; CTC channel 1 (port 1)
CTC2	==	32h	; CTC channel 2 (ports 2,3)
CTC3	==	33h	; CTC channel 3 (clock)
SIO1AC	==	2Ah	; SIO-1 channel A, control
SIO1AD	==	28h	; SIO-1 channel A, data -port 0
SIO1BC	==	2Bh	; SIO-1 channel B, control
SIO1BD	==	29h	; SIO-1 channel B, data -port 1
SIO2AC	==	22h	; SIO-2 channel A, control
SIO2AD	==	20h	; SIO-2 channel A, data -port 2
SIO2BC	==	23h	; SIO-2 channel B, control
SIO2BD	==	21h	; SIO-2 channel B, data -port 3
PIOAC	==	0Ah	; PIO channel A, control
PIOAD	==	08h	; PIO channel A, data
PIOBC	==	0Bh	; PIO channel B, control
PIOBD	==	09h	; PIO channel B, data
HARDP	==	01h	; Hard disk parallel port
CENTP	==	02h	; Centronix parallel port
FLOPP	==	18h	; Floppy DMA channel
FLOPSR	==	10h	; Floppy status register
FLOPDR	==	11h	; Floppy data register
SETMAP	==	03h	; Set memory map register
STOPFLOP==	03h	; Stop floppy controller
OFFPROM ==	02h	; De-activate DSC/3 PROM
ONMAP	==	00h	; Activate DSC/4 memory map
;----------
; Standard CP/M entry points
	.loc	BIOS
	jmp	CBOOT	; cold boot
	jmp	WBOOT	; warm boot
	jmp	CONST	; console status
	jmp	CONIN	; console input
	jmp	CONOUT	; console output
	jmp	LIST	; list output
	jmp	PUNCH	; punch output
	jmp	READER	; reader input
	jmp	HOME	; home drive
	jmp	SELDSK	; select drive
	jmp	SETTRK	; select track
	jmp	SETSEC	; select sector
	jmp	SETDMA	; set DMA address
	jmp	READ	; read disk
	jmp	WRITE	; write disk
;----------
; Interrupt vectors
	.loc	(.+15)&0FFF0h ; force at multiple of 16
vectors:
SIO1vect:
	.word	INTerr	; channel B
	.word	INTerr	
	.ife	NETopt,[.word RECfirst; SDLC first char
			.word REClast ; SDLC last char]
	.ifn	NETopt,[.word INTerr
			.word INTerr]
	.word	INTerr	; channel A
	.word	INTerr
	.ife	AHEADo,[.word PORTint; receiver ready]
	.ifn	AHEADo,[.word INTerr]
	.word	INTerr	
SIO2vect:
	.word	INTerr	; channel B
	.word	INTerr
	.word	INTerr
	.word	INTerr
	.word	INTerr	; channel A
	.word	INTerr
	.word	INTerr
	.word	INTerr
CTCvect:
	.word	INTerr
	.word	INTerr
	.word	INTerr
	.ife	TIMERo,[.word TIMERint; real-time clk]
	.ifn	TIMERo,[.word INTerr]
DMAvect:.word	INTerr	; whoever uses DMA sets this up
PIOvect:
	.ife	FRONTo,[.word FRONTint; front-panel]
	.ifn	FRONTo,[.word INTerr]
	.word	INTerr	; spare room for more vectors
	.word	INTerr
;----------
; Non-standard jump vectors:
	jmp	NETlock	; network lock
	jmp	CPMMAP	; get logical to physical map
	jmp	NETunloc; network unlock
	jmp	SETBYT	; set I/O byte count
	.ife	NETopt,  [jmp  NETmsg;  point to comnd]
	.ifn	NETopt,  [jmp  CALLerr]
	.ife	NETopt,  [jmp  SENDnet; send a block
			  jmp  RECnet ; receive a block
	  .ifn	MASTopt, [jmp  NACKpoll;don't ack poll
			  jmp  ACKpoll; ack polls]
	  .ife	MASTopt, [jmp  CALLerr
			  jmp  CALLerr]]
	.ifn	NETopt,  [jmp  CALLerr
			  jmp  CALLerr
			  jmp  CALLerr
	      		  jmp  CALLerr]
	jmp	PORTNout; user printer driver
;----------
; Type-ahead buffer
	.ife	AHEADopt,[
nxtin:	.byte	0	; next input char
nxtout:	.byte	typeahead ; next output char
	.loc	(.+typeahead)&(#typeahead)
PORT0buf:
	.blkb	typeahead+1
	]
;----------
; Network station command and status
	.ife	NETopt,[
NETmsg:	.byte	0	; response byte
NETcom: .byte	0	; command byte
	.byte	0	; -not used-
NETdsk:	.byte	0	; drive number
NETtrk:	.byte	0	; track number
NETsec:	.byte	0	; sector number
NETbyt:	.word	0	; byte count
lencom	==	.-NETcom
	.loc	NETcom+1
NETnam:	.blkb	8	; unit name
NETpsw:	.blkb	6	; unit password
NETassn	==	NETnam	; put unit assignment here
lenlog	==	.-NETcom
maxcom	==	lenlog
	]
;----------
; Current CP/M information
cpmDESC:	
cpmDSK:	.byte	0FFh	; disk   from CPM
cpmTRK:	.byte	0FFh	; track  from CPM
cpmSEC:	.byte	0	; sector from CPM
cpmBYT:	.word	80h	; LENGTH of transfer from CPM
cpmDMA: .word	80h	; DMA address from CPM
;----------
; The cold boot code is stored in one of two places,
; depending upon the boot device:
;
; If booting a stand-alone system or a floppy network
; master, the cold boot code is overlaid with the
; floppy I/O buffers.
;
; If booting a hard disk network system, or a floppy
; network station,  the cold boot code is located 
; immediately below the BIOS.
;
	.ife	 SELECT-3,	  [.loc BIOS-lenBOOT]
;----------
; Prepare for interrupts
CBOOT:	CALL	PRTPC
	mvi	A,(vectors>8)&0FFh
	stai		; setup interrupt register
	im2
	CALL	PRTPC
	ei
;----------
; Initialize serial no, IOBYTE, drive, error count
	sixd	serial	; store serial number
	lda	IOBYTE
	sta	3	; initialize IOBYTE
	sub	A
	sta	4	; active drive = 0
	sta	errors
	mvi	A,RETRY	; number of error retries
	sta	retries
	lda	hertz
	sta	ticsec	; ticks per second
;----------
; Initialize port 1
	CALL	PRTPC
	lxi	H,baud1
	lxi	B,2<8+CTC1
	outir		; set baud rate for port 1
	lxi	H,rs232
	lxi	B,rs232$<8+SIO1BC
	outir		; program SIO for port 1
;----------
; Initialize port 2
	lxi	H,baud23
	lxi	B,2<8+CTC2
	outir		; set baud rates for ports 2,3
	lxi	H,rs232
	lxi	B,rs232$<8+SIO2AC
	outir		; program SIO for port 2
;----------
; Initialize port 3
	lxi	H,rs232
	lxi	B,rs232$<8+SIO2BC
	outir		; program SIO for port 3
;----------
; If timer-option has been selected, enable
; real-time interrupts
	.ife	TIMERopt,[
	mvi	A,0C5h	; enable real-time interrupts
	out	CTC3
	mvi	A,1	; interrupt every 1/60 sec
	out	CTC3
	mvi	A,CTCvect&0FFh ; interrupt vector
	out	CTC0	; must send to channel zero
	]
;----------
; If front-panel option has been selected, enable
; front-panel interrupts
	.ife	FRONTopt,[
	CALL	PRTPC
	mvi	A,PIOvect&0FFh ; interrupt vector
	out	PIOAC
	mvi	A,10110111b ; enable interrupts
	out	PIOAC
	mvi	A,01111111b ; front-panel/HLT
	out	PIOAC
	]
;----------
; If type-ahead option has been selected, enable
; receiver interrupts for port 0
	.ife	AHEADopt,[
	CALL	PRTPC
	lxi	H,aheadprog      ; setup channel B
	lxi	B,ahead$<8+SIO1BC; interrupt vector
	outir		; (it is shared with ch A)
	mvi	A,11h	; write SIO register 1
	out	SIO1AC
	mvi	A,00011000b ; enable receiver interrupt
	out	SIO1AC
	]
;----------
; Jump to the warm-boot entry point
	jmp	WBOOT
;----------
; SIO command strings
rs232: 	.byte	    18h	      ; channel reset
	.byte	14h,01001100b ; x16 clock, 2 stop bits
	.byte	 3,11100001b ; receiver enable
	.byte	 5,11101010b ; transmitter enable
	.byte	11h,0	      ; no interrupts
rs232$	==	.-rs232
;
aheadp:	.byte	    18h       ; channel reset
	.byte	  2,SIO1vect&0FFh ; interrupt vector
	.byte	11h,00000100b ; status affects vector
ahead$	==	.-aheadprog
;----------
; Network login command
BOOTcom	==	8000h
BOOTnum	==	BOOTcom+1
BOOTclk	==	BOOTcom+2
BOOTnam	==	BOOTcom+1
BOOTpsw	==	BOOTcom+9
BOOTmsg	==	BOOTcom+15
BOOTassn==	BOOTnam
	.reloc
	.page
;----------
; Warm boot
WBOOT:
	CALL	PRTPC
	lxi	SP,80h	; setup a stack pointer
	mvi	A,(vectors>8)&0FFh
	stai		; setup interrupt register
	im2
	CALL	PRTPC
	ei		; enable interrupts
	CALL	PRTPC
	mvi	A,0C3h	; setup jump to WBOOT at 0,1,2
	sta	0
	lxi	H,BIOS+3
	shld	1
	.ife	FRONTopt,[
	sta	30h	; setup jump to WBOOT at 30-32
	shld	31h
	]
	sta	5	; setup jump to BDOS at 5,6,7
	lxi	H,BDOS
	shld	6
	.ife	NETopt,[
	lda	NETusr
	inr	A
	jrz	..wboot	; jump if not logged in
	CALL	PRTPC
	.ife	(1-MASTopt),[call ACKpoll;answer polls]
..wboot:
	]
;----------
; Warm boot from floppy network
	CALL	PRTPC
	.ifn	MASTopt,[call ACKpoll]
	.ife	MASTopt,[ mvi C,0]; master on drive 0
	.ifn	MASTopt,[ mvi C,1]; slave on drive 1
	call	SELDSK
	CALL	PRTPC
	call	HOME
	mvi	C,3
	CALL 	PRTPC
	call	SETSEC
	lxi	B,128
	call	SETBYT
	lxi	H,CPM
	CALL	PRTPC
	mvi	B,24	; read 24 sectors from track 0
	call	READBLK
	CALL	PRTPC
	mvi	C,1
	call	SETTRK
	CALL	PRTPC
	mvi	C,1
	call	SETSEC
	CALL	PRTPC
	mvi	B,lenCPM/128-24; read rest of CP/M
	call	READBLK
	lda	4
	mov	C,A
	CALL	PRTPC
	jmp	CPM
	]
;----------
; Read consecutive sectors from a disk
;  Regs in:   HL = DMA address
;	      B  = number of sectors to read
;  Regs out:  none
;  Destroyed: A, BC, DE, HL
	.ife	HARDboot&NETboot,[
READBLK:
	push	B	; save sector count
	shld	cpmDMA
	call	READ	; read one sector
	lxi	H,cpmSEC
	inr	M	; increment sector number
	lhld	cpmDMA
	lxi	D,128
	dad	D	; increment DMA address
	pop	B	; restore sector count
	djnz	READBLK
	ret
	]
;----------
; IOBYTE implementation:
;
;	     LST: PUN: RDR: CON:
;           ---------------------
; Defaults: | 01 | 01 | 01 | 00 |
;           ---------------------
;----------
; Console status
CONST:
	call	doCONIO
	.word	PORT0st	; TTY:	(default)
	.word	PORT2st	; CRT:
	.word	PORT1st	; BAT:
	.word	PORT3st	; UC1:
;----------
; Console input
CONIN:
	.ife	BACKopt,[
	call	xconin
	sta	inchar	; save input character
	cpi	backsp
	rnz
	mvi	A,rubout; convert ^H to rubout
	ret
inchar: .byte	0
	]
xconin:
	call	doCONIO
	.word	PORT0in	; TTY:	(default)
	.word	PORT2in	; CRT:
	.word	PORT1in	; BAT:
	.word	PORT3in	; UC1:
;******************************************************
;THIS IS A SPECIAL PRINT ROUTINE TO TRACK PROGRAM FLOW
;
	PRTPC:	XTHL
		SHLD	..HERE
		XTHL
		PUSH	PSW
		PUSH	H
		PUSH	D
		PUSH	B
		MVI	C,'-'
		CALL	CONOUT
		LHLD	..HERE
		MOV	A,H
		CALL	PRTBYT
		LHLD	..HERE
		MOV	A,L
		CALL	PRTBYT
		MVI	C,'-'
		CALL	CONOUT
		MVI	C,0DH
		CALL	CONOUT
		MVI	C,0AH
		CALL	CONOUT
		POP	B
		POP	D
		POP	H
		POP	PSW
		RET
	..HERE:	.WORD	0000H
		;
		;
	PRTBYT:	PUSH	PSW
		RRC
		RRC
		RRC
		RRC
		CALL	..NIB
		POP	PSW
	..NIB:	ANI	0FH
		ADI	'0'
		CPI	'9'+1
		JRC	..OUT
		ADI	'A'-('9'+1)
	..OUT:	MOV	C,A
		JMP	CONOUT
	
;----------
; Console output
CONforce:
	jmpr	xconout ; force a char to the console
CONOUT:
	.ife	BACKopt,[
	lda	inchar  ; check if input char was ^H
	cpi	backsp
	jrnz	xconout
	mov	C,A	; if so, backspace
	call	xconout
	mvi	C,' '	; wipe out previous char
	call	xconout
	mvi	C,backsp; backspace onto blank
	]
xconout:
	call	doCONIO
	.word	PORT0out; TTY:	(default)
	.word	PORT2out; CRT:
	.word	PORT1out; BAT:
	.word	PORT3out; UC1:
;----------
; List output
LIST:
	lda	3
	rlc
	rlc
	rlc
	call	doIO
	.ife	SELECT-3,[.word PORTAout; TTY:]
	.ifn	SELECT-3,[.word PORT0out; TTY:]
	.word	PORT2out; CRT:	(default)
	.word	PORTPout; LPT:
	.word	PORTNout; UL1:
;----------
; Punch output
PUNCH:
	lda	3
	rrc
	rrc
	rrc
	call	doIO
	.word	PORT0out; TTY:
	.word	PORT3out; PTP:	(default)
	.word	PORT1out; UP1:
	.word	PORT2out; UP2:
;----------
; Reader input
READER:
	lda	3
	rrc
	call	doIO
	.word	PORT0in	; TTY:
	.word	PORT3in	; PTR:	(default)
	.word	PORT1in	; UR1:
	.word	PORT2in	; UR2:
;----------
; I/O dispatch routines
doCONIO:
	lda	3
	rlc
doIO:
	ani	06h	; compute jump vector address
	xthl
	mov	E,A
	mvi	D,0
	dad	D	
	mov	A,M	; get jump vector
	inx	H
	mov	H,M
	mov	L,A
	xthl
	ret		; go to appropriate routine
	.ife	AHEADopt,[
;----------
; Port 0 interrupt
PORTint:
	ei
	push	PSW	; save registers
	push	B
	push	D
	push	H
	in	SIO1AD	; get the character
	ani	7Fh	; mask out parity bit
	mov	C,A
	lhld	nxtin	; L = nxtin, H = nxtout
	mov	A,L
	cmp	H	; check for nxtin=nxtout
	jrz	..full  ; if yes, buffer is full
	mvi	H,0
	lxi	D,PORT0buf
	dad	D	; point to next spot in buffer
	mov	M,C	; store char in buffer
	inr	A	; increment nxtin
	ani	typeahead ; use modulo arithmetic
	sta	nxtin	; save new value of nxtin
	jmpr	..ret
..full:
	mvi	C,bell	; ring bell if buffer full
	call	CONforce
..ret:
	pop	H
	pop	D
	pop	B
	pop	PSW
	reti
;----------
; Port 0 status - type-ahead option
PORT0st:
	ei		; make sure interrupts enabled
	lhld	nxtin	; L = nxtin, H = nxtout
	mov	A,H
	inr	A	; increment nxtout
	ani	typeahead ; use modulo arithmetic
	mov	B,A	; save nxtout+1 for CONIN
	sub	L	; zero if nxtout+1=nxtin
	rz		; return if no char available
	mvi	A,0FFh	; CP/M wants A = 0FFh if yes
	ret
;----------
; Port 0 input - type-ahead option
PORT0in:
	call	PORT0st	; wait for character
	jrz	PORT0in
	mov	L,B	; add nxtout+1 to PORT0buf
	mvi	H,0
	lxi	D,PORT0buf
	dad	D	; point to char in buffer
	mov	A,M	; get the character
	lxi	H,nxtout
	mov	M,B	; update the nxtout pointer
	]
;----------
; If network station, then intercept cntl-P
; and turn on ADDS AUX port
	.ife	SELECT-3,[
	cpi	cntlP
	rnz
	mov	B,A	; save input character
	lda	3	; check IOBYTE for LST device
	ani	0C0h
	mov	A,B	; restore input character
	rnz		; return if not AUX port
	lda	cntlPflg
	ora	A
	cma		; switch cntl-P flag
	sta	cntlPflg
	jrnz	..offAUX
	mvi	C,12h	; turn on AUX port
	call	AUXon
	jmpr	..ret
..offAUX:
	mvi	C,14h	; turn off AUX port
	call	AUXoff
..ret:	mvi	A,cntlP	; return cntl-P to BDOS
	]
	ret
;----------
; Port 0 output
PORT0out:
	.ife	SELECT-3,[
	lda	3	; check IOBYTE for LST device
	ani	0C0h
	jrnz	PORT0force; jump if not AUX port
	lda	cntlPflg; if cntl-P active, then
	ora	A	;  output char immediately
	jrnz	PORT0force
	lda	prntflg
	ora	A
	jrz	PORT0force; jump if print flag off
AUXoff:
	push	B
	mvi	C,1Bh	; send all chars to CRT
	call	PORT0force
	mvi	C,34h
	call	PORT0force
	pop	B
	sub	A	; turn off print flag
	sta	prntflg
	mvi	A,3
	out	SIO1AC
	mvi	A,0C1h
	out	SIO1AC	; turn off auto-enables
	]
PORT0force:
	in	SIO1AC
	ani	1<TxRDY
	jrz	PORT0force
	mov	A,C
	out	SIO1AD
	ret
	.ife	SELECT-3,[
;----------
; Port 1 not available on network station
PORT1st:
PORT1in:
PORT1out:
	jmp	CALLerr
	]
;----------
; Port 2 status
PORT2st:
	in	SIO2AC
	ani	1<RxRDY
	rz
	mvi	A,0FFh
	ret
;----------
; Port 2 input
PORT2in:
	call	PORT2st
	jrz	PORT2in
	in	SIO2AD
	ani	7Fh
	ret
;----------
; Port 2 output
PORT2out:
	in	SIO2AC
	ani	1<TxRDY
	jrz	PORT2out
	mov	A,C
	out	SIO2AD
	ret
;----------
; Port 3 status
PORT3st:
	in	SIO2BC
	ani	1<RxRDY
	rz
	mvi	A,0FFh
	ret
;----------
; Port 3 input
PORT3in:
	call	PORT3st
	jrz	PORT3in
	in	SIO2BD
	ani	7Fh
	ret
;----------
; Port 3 output
PORT3out:
	in	SIO2BC
	ani	1<TxRDY
	jrz	PORT3out
	mov	A,C
	out	SIO2BD
	ret
;----------
; List output on Centronix printer
PORTPout:
	in	PIOAD
	bit	6,A
	jrnz	PORTPout
	mov	A,C
	ori	80h
	out	CENTP
	ani	7Fh
	out	CENTP
	ori	80h
	out	CENTP
	ret
	.ife	SELECT-3,[
;----------
; List output on ADDS printer
PORTAout:
	lda	cntlPflg
	ora	A	
	rnz		; return if cntl-P active
	lda	prntflg
	ora	A
	jrnz	PORT0force; jump if printer flag on
	push	B
	mvé	C,1Bh	; send all chars to AUX port
	call	PORT0force
	mvi	C,33h
	call	PORT0force
	pop	B
	mvi	A,0FFh	; turn on printer flag
	sta	prntflg
AUXon:
	mvi	A,3
	out	SIO1AC	; turn on auto-enables
	mvi	A,0E1h
	out	SIO1AC
	jmpr	PORT0force
prntflg:.byte	0	; print flag (off initially)
cntlPflg:.byte	0	; cntl-P flag(off initially)
	]
;----------
; Select disk drive
SELDSK:
	mov	A,C
	.ifn	FLOPshar,[
	cpi	0FFh	; check for boot disk
	jrz	..ok
	]
	ani	03h	; mask out disk number
..ok:	sta	cpmDSK	; store it in cpmDSK
	call	cpmMAP	; get pointer to disk map
	inx	H	; point to overlay string
	lxi	D,BDOS+34h ; address in CP/M to overlay
	lxi	B,7	; number of bytes to overlay
	ldir
	mvi	E,15h	; overlay CP/M instruction
	mov	A,M	;  at BDOS+15h
	stax	D	; instr is 'MOV C,M' or 'INR C'
	ret
;----------
; Set track
SETTRK:
	mov	A,C	
	sta	cpmTRK	; store it in cpmTRK
	ret
;----------
; Set sector
SETSEC:
	mov	A,C
	sta	cpmSEC	; store it in cpmSEC
	ret
;----------
; Set DMA address
SETDMA:
	sbcd	cpmDMA
	ret
;----------
; Set DMA size
SETBYT:
	sbcd	cpmBYT
	ret
;----------
; Home to track 0
HOME:	sub	A	; A = 0
	sta	cpmTRK	; set track to 0
	ret
;----------
; Read disk
READ:
	call	cpmMAP	; determine device type
	ani	0C0h	; mask out for device
	.ife	NETopt,[
	cpi	NET	; check for network
 	jz	NETrd
	]
	jmp	CALLerr ; if none of the above - error
;----------
; Write disk
WRITE:	call	cpmMAP	; determine device type
	ani	0C0h	; mask for device type
	.ife	NETopt,[
	cpi	NET	; check for network
 	jz	NETwr
	]
	jmp	CALLerr ; if none of the above - error
	.ife	NETopt,[
;----------
; Network read
NETrd:
	mvi	C,readNET
	jmpr	Network
;----------
; Network write
NETwr:
	mvi	C,writeNET
;----------
; Network read/write
Network:
	.ife	FLOPshar,[
	lda	cpmDSK	; get current disk no.
	]
	sta	NETdsk	; drive number
	lhld	cpmTRK	; track and sector number
	shld	NETtrk
	lhld	cpmBYT
	shld	NETbyt	; number of bytes
	mov	A,C
	sta	NETcom	; network command
	jmpr	NETreq
;----------
; Network lock
NETlock:
	mvi	A,lockNET
	jmpr	Lockwork
;----------
; Network unlock
NETunlock:
	mvi	A,unlockNET
;----------
; Network lock/unlock
lenlock	==	13	; maximum lock length
Lockwork:
	lhld	LOCKadr	; point to lock string
	lxi	D,NETcom+1
	lxi	B,lenlock+1
	ldir		; move lock to network command
	sta	NETcom
;----------
; Wait for a poll
NETreq:
	.ifn	MASTopt,[
	call	lockDMA	; reserve the DMA chip
..retry:call	NACKpoll
	bit	7,A	; check for time-out
	jrnz	..ok
	lxi	H,..wait
	call	PRTMSG	; tell user we timed out
	call	ACKpoll	; prepare for another poll
	jmpr	..retry	; wait some more
..wait:	.asciz	[cr][lf]'*** Waiting'
;----------
; Process the current network command
..ok:
	lda	NETcom	; get command
	cpi	readNET
	jrz	Nread
	cpi	writeNET
	jrz	Nwrite
	jmpr	NETret	; should NEVER get here
;----------
; Get a sector from the master
Nread:
	lxi	H,NETcom
	lxi	B,lencom
	sub	A
	call	SENDNET ; send request to master
..1:	lhld	cpmDMA	; data address
	lbcd	cpmBYT
	lda	NETusr
	call	RECNET  ; receive data from master
	bit	7,A
	jrz	Nread	; lost data - try again
	ani	60h
	jrz	..2	; CRC or receiver over-run
	lxi	H,errors
	inr	M	; increment CRC error count
	mvi	A,nack	; data not received, so nack
	call	SENDMSG
	jmpr	..1
..2:	mvi	A,datack
	call	SENDMSG	; acknowledge reception
	jmpr	NETret
;----------
; Send a sector to the master
Nwrite:
	lxi	H,NETcom
	lxi	B,lencom
	sub	A
	call	SENDNET	; send request to master
	call	RECMSG	; wait for request-received ack
..1:	lhld	cpmDMA	; data address
	lbcd	cpmBYT
	sub	A 
	call	SENDNET	; send data to master
	call	RECMSG	; wait for data-received ack
	cpi	datack
	jrz	NETret	; if acked, then all done
	cpi	nack
	jrnz	Nwrite	; lost ack - try again
	lxi	H,errors
	inr	M	; increment CRC error count
	jmpr	..1
;----------
; Send a lock/unlock request to the master
;----------
; Resume answering polls, then return to caller
NETret: call	ACKpoll	; answer polls again
	sub	A	; CP/M wants A = 0
	sta	lockbyte; release the DMA chip
	ret
	]
	]
	.ife	NETopt,[
;----------
; Transmit a block on the network
;  Regs in:   HL = block address
;   	      BC = byte count
;	      A  = user number
;  Regs out:  none
;  Destroyed: A, BC, DE, HL
SENDNET:
	mvi	E,40h	; assure receiver at least
..delay:dcr	E	; 400 microsecs to prepare
	jrnz	..delay
	mov	D,A	; save user number
	shld	DMANadr	; store block address
	dcx	B	; DMA chip wants real size - 1
	sbcd	DMANsize
	mov	A,C
	ora	B
	jrz	..send1
;
; Send more than one byte
	mvi	A,79h	; program DMA chip to send
	sta	DMANc1
	mvi	A,writeDMA
	sta	DMANc2
	lxi	H,DMATdone; setup the DMA vector
	shld	DMAvect
	mvi	A,1	; multiplex SIO1B to DMA
	out	PIOAD
	lxi	H,DMANprog; program the DMA chip
	lxi	B,DMAN$<8+DMA
	outir
	di		; we can't be interrupted here
	lxi	H,SENDprog
	lxi	B,SEND$<8+SIO1BC
	outir
	mov	A,D
	out	SIO1BD
	mvi	A,87h	; enable the DMA chip
	out	DMA
	mvi	A,11010000b; reset underrun/EOM status
	out	SIO1BC
	jmpr	..fin
;
; Send one byte (DMA chip isn't needed)
..send1:
	mov	A,D	; get user number
	lded	DMANadr	; point to message byte
	di		; we can't be interrupted here
	lxi	H,SENDprog
	lxi	B,SEND$<8+SIO1BC
	outir
	nop		; needed on DSC/3
	out	SIO1BD	; transmit user number
	mvi	A,11010000b; reset underrun/EOM status
	out	SIO1BC
	ldax	D
	out	SIO1BD	; send 1 message byte
;
; Handle the end of the transmission
..fin:	ei		; interrupts OK now
..1:	in	SIO1BC	; wait for transmit underrun
	bit	6,A
	jrz	..1
..2:	in	SIO1BC	; wait for CRC complete
	bit	2,A
	jrz	..2
	mvi	B,0Ah	; delay for closing flag
	djnz	.
	mvi	A,18h
	out	SIO1BC	; reset the SIO chip
	ret
;----------
; Send a message to the master
;  Regs in:   A = message to be sent
;  Regs out:  none
;  Destroyed: A, BC, DE, HL
SENDMSG:
	lxi	H,NETmsg
	mov	M,A
	lxi	B,1
	sub	A	; master no = 0
	jmp	SENDNET
;----------
; Receive a block from the network
;  Regs in:   HL = block address
;	      BC = maximum byte count
;	      A  = user number
;  Regs out:  A  = error status
;		   bit 7 = end of frame (0 if time-out)
;		   bit 6 = CRC error
;		   bit 5 = receiver overrun
;		   bit 4 = block received (always 1)
;  Destroyed: A, BC, DE, HL
RECNET:
	call	RECbegin; program the SIO chip
;----------
; Wait for next block from network, and dont ack polls
;  Regs in:   none
;  Regs out:  A = error status
;  Destroyed: HL
NACKpoll:
	ei		; make sure SIO can interrupt
	.ifn	MASTopt,[ mvi A,slowrec]
	sta	timeREC
	lxi	H,RECstat
	mvi	M,1	; suppress pollack
..wait:	mov	A,M
	bit	4,A	; wait for block received
	jrz	..wait
	lxi	H,timeREC
	mvi	M,0	; reset timeout counter
	ret
;----------
; Program the SIO to acknowledge polls from the master
;  Regs in:   none
;  Regs out:  none
ACKpoll:
	sub	A	; acknowledge polls
	sta	RECstat
	lxi	H,NETmsg; put poll here
	lxi	B,1	; receive one byte only
	lda	NETusr	; poll must be addressed to us
;----------
; Program the SIO chip to receive a block
RECbegin:
	sta	RECadr	; station address
	shld	DMANadr	; DMA address
	dcx	B	
	sbcd	DMANsize; DMA length = real length-1
	mov	A,C
	ora	B
	jrz	..prog
;
; Receive more than one byte
	mvi	A,7Dh	; program DMA to read
	sta	DMANc1
	mvi	A,readDMA
	sta	DMANc2
	lxi	H,DMARdone; setup the DMA vector
	shld	DMAvect
	mvi	A,1	; multiplex SIO1B to DMA
	out	PIOAD
	lxi	H,DMANprog; program the DMA chip
	lxi	B,DMAN$<8+DMA
	outir
..prog:
	lxi	H,RECfirst; setup interrupt vectors
	shld	SIO1vect+4
	lxi	H,REClast
	shld	SIO1vect+6
	lxi	H,RECprog; program the SIO chip
	lxi	B,REC$<8+SIO1BC
	outir
	ei		; make sure interrupts enabled
	ret
;----------
; Receive a message from the master
;  Regs in:   none
;  Regs out:  A = message received
;  Destroyed: BC, DE, HL
RECMSG:
	lxi	H,NETmsg
	lxi	B,1
	lda	NETusr
	call	RECNET
	lda	NETmsg
	ret
;----------
; Receive status and count, and temp storage for regs
RECstat:.byte	0FFh	; receiver status
REC.BC:	.word	0	
REC.DE:	.word	0
REC.HL:	.word	0
;----------
; SDLC receive interrupt on first char
RECfirst:
	push	PSW
	in	SIO1BD	; flush user number
	lda	DMANsize; if len = 0, don't use DMA
	ora	A
	jrnz	..DMA
	in	SIO1BD	; get message byte
	shld	REC.HL
	lhld	DMANadr	; get buffer address
	mov	M,A	; put message into buffer
	lhld	REC.HL
	in	SIO1BD	; flush first CRC byte
	jmpr	..ret
..DMA:	mvi	A,87h	; enable DMA
	out	DMA
..ret:	pop	PSW
	ei
	reti
;----------
; SDLC receive interrupt on last char
REClast:
	ei
	push	PSW	; save user's registers
	shld	REC.HL	
	sded	REC.DE
	sbcd	REC.BC
	lda	DMANsize; check whether DMA was used
	ora	A
	jrz	..fin
	mvi	A,0C3h	; deprogram the DMA chip
	out	DMA
..fin:
	in	SIO1BD	; throw away second CRC byte
	lda	RECstat	; get pollack request bit
	mov	B,A
	mvi	A,1	; get receive status
	out	SIO1BC
	in	SIO1BC
	ani	11100000b; mask out relevant bits
	set	4,A	; set "block received" bit
	sta	RECstat
	mvi	A,18h	; reset the SIO1B chip
	out	SIO1BC
	.ifn	MASTopt,[
	bit	0,B
	jrnz	..ret	; return if no pollack desired
	mvi	A,pollack
	call	SENDMSG	; send a poll-acknowledge
	call	ACKpoll	; program the SIO to wait
	]
..ret:
	lbcd	REC.BC	; restore user's registers
	lded	REC.DE
	lhld	REC.HL
	pop	PSW
	reti
;----------
; If the receiver times-out, we come here
RECtimeout:
	jmpr	REClast
;----------
; DMA transmit complete interrupt
DMATdone:
	push	PSW
	mvi	A,0C3h	; reset the DMA chip
	out	DMA
	pop	PSW
;******	CALL	PRTPC
	ei
	ret
;----------
; DMA receive complete interrupt
DMARdone:
	push	PSW
	shld	REC.HL
	mvi	A,0C3h	; reset the DMA chip
	out	DMA
	mvi	A,21h	; now interrupt on every char
	out	SIO1BC
	mvi	A,11110100b
	out	SIO1BC
	lxi	H,RECflush; setup new interrupt vector
	shld	SIO1vect+4
	lhld	REC.HL
	pop	PSW
;******	CALL	PRTPC
	ei
	ret
;----------
; SDLC receive interrupt on every char
RECflush:
	push	PSW
	in	SIO1BD	; flush this char away
	pop	PSW
	ei
	reti
;----------
; Network messages and commands
poll	==	'P'	; poll from master
pollack	==	'A'	; poll-received ack
datack	==	'D'	; data received ack
mesack	==	'M'	; message received ack
nack	==	'N'	; negative acknowledge
logack	==	'L'	; login acknowledge
lognack ==	'N'	; login conflict
logdeny	==	'D'	; login denied
lockack	==	0	; lock acknowledge
locknack==	2	; lock error
lockdeny==	1	; lock denied
hogack	==	'H'	; hog acknowledge
hogdeny	==	'D'	; hog denied
whoNET	==	10h	; who command
readNET	==	11h	; read command
writeNET==	12h	; write command
loginNET==	13h	; login command
assnNET	==	17h	; assign command
hogNET	==	18h	; hog command
lockNET	==	19h	; lock command
unlockNE==	1Ah	; unlock command
clrlockN==	1Bh	; clearlocks command
;------------------------------------------------
;----------
; SDLC commands
SENDprog:
	.byte	    00011000b ; channel reset
	.byte	  3,00100000b ; auto enables
	.byte	14h,00100000b ; SDLC mode
	.byte	11h,11000100b ; no interrupts
	.byte	  5,11101011b ; transmitter enable
	.byte	    10000000b ; reset CRC generator
SEND$	==	.-SENDprog
;
RECprog:
	.byte	    00011000b ; channel reset
	.byte	  2,SIO1vect&0FFh ; interrupt vector
	.byte	  4,00100000b ; SDLC mode
	.byte	15h,10000000b ; transmit disable
	.byte	  3,11111100b ; receiver info
	.byte	6
RECadr:	.byte	0	      ; user number
	.byte	  7,01111110b ; flag byte
	.byte	11h,11101100b ; interrupt on first byte
	.byte	23h,11111101b ; enable receiver
REC$	==	.-RECprog
;
DMANprog:
	.byte	0C3h	; master reset		     2D
	.byte	0C7h	; reset port A		     2D
	.byte	0CBh	; reset port B		     2D
DMANc1:	.byte	0	; filled by SENDNET or RECstart
DMANadr:.word	0	; filled by SENDNET or RECstart
DMANsiz:.word	0	; filled by SENDNET or RECstart
	.byte	14h	; port A inc, memory	     1B
	.bytå	28è	» porô Â fixed¬ I/Ï	     1B
	.byte	95h	; byte mode		     2B
	.byte	SIO1BD	; port B
	.byte	12h	; interrupt at end of block
	.byte	DMAvect&0FFh ; interrupt vector
	.byte	92h	; stop at end of block
	.byte	0CFh	; load starting address      2C
DMANc2:	.byte	0	; filled by SENDNET or RECstart
	.byte	0CFh	; load starting address	     1A
	.byte	0ABh	; enable interrupts	     2D
DMAN$	==	.-DMANprog
	]
	.ife	TIMERopt,[
;----------
; Process a timer interrupt
;
; Should get an interrupt every 1/60 second
TIMERint:
	ei		; re-enable interrupts
	push	PSW	; save user's registers
	shld	TIME.HL
	sded	TIME.DE
	sbcd	TIME.BC
	lxi	H,ticks
;
; Update fractions of a second
	lda	ticsec	; 60 for U.S., 50 for Europe
	inr	M
	cmp	M
	jrnz	downcnt
	mvi	M,0
	inx	H
;
; Update seconds
	mvi	A,60
	inr	M
	cmp	M
	jrnz	downcnt
	mvi	M,0
	inx	H
;
; Update minutes
	inr	M
	sub	M
	jrnz	downcnt
	mov	M,A
	inx	H
;
; Update hours
	mvi	A,24
	inr	M
	sub	M
	jrnz	downcnt
	mov	M,A
;
; Decrement each active down-counter, and call each
; time-out routine whose counter has reached zero.
downcnt:
	lxi	H,timers
;
; Unload the floppy disk head after 1 seconds
; of inactivity
;
; Terminate SDLC-receive processing if we are waiting
; for a message from a user, but the user has not
; sent the message within the expected period of time
	.ife	NETopt,[
	mov	A,M
	ora	A
	jrz	..2
	dcr	M
	jrnz	..2
	call	RECtimeout
	lxi	H,timeREC; restore HL
..2:	inx	H
	]
;
; Restore the user's registers and return
..3:	pop	PSW
..ret:	lhld	TIME.HL
	lded	TIME.DE
	lbcd	TIME.BC
	reti
TIME.HL:.word	0
TIME.DE:.word	0
TIME.BC:.word	0
;
; Event down-counters are stored here
timers:
	.ife	NETopt,  [timeREC:   .byte 0]
	]
	.ife	FRONTopt,[
;----------
; On a front-panel interrupt, jump to location 30h.
; This will cause a warm boot if the CCP is running,
; or will return to DDT if it is running.  Any program
; can intercept front-panel interrupts by changing
; the jump intruction at location 30h.
FRONTint:
	CALL	PRTPC
	in	PIOAD	; read interrupt status
	ani	80h	; if not high, then must be HLT
	jrz	HALTint
	lxi	H,..1
	push	H
	CALL	PRTPC
	ei
	reti
..1:	pop	H	; HL = address of interrupt
	rst	6
;
; If a HALT intruction is executed, print an error
; message and resume at the next instruction
HALTint:
HALTerr:lxi	H,HALTerr+3
	push	H
	lxi	H,IOERR
	push	H
	CALL	PRTPC
	ei
	reti
	]
;----------
; Utility routines
;
; Print a message
;  Regs in:   HL = address of message (ended by null)
;  Regs out:  none
;  Destroyed: A, HL
PRTMSG:
	mov	A,M	; get a byte
	ora	A	; if null, then return
	rz
	mov	C,A	; put it where CONOUT wants it
	call	CONforce; output the byte
	inx	H	; point to next byte
	jmpr	PRTMSG
;----------
; Lock the DMA chip (prevent anyone else from using
;		     it until current user is finished)
;  Regs in:   none
;  Regs out:  none
;  Destroyed: A
readDMA	==	1	; read using DMA
writeDMA==	5	; write using DMA
lockbyte:.byte	0	; initially unlocked
lockDMA:
	lda	lockbyte
	ora	A	; if locked, then wait until
	jrnz	lockDMA	; current user unlocks it
	di		; no interrupts until DMA setup
	cma		; lock the DMA chip
	sta	lockbyte
	ret
;----------
; Select memory page (DSC/4 only)
;
; Regs in:   B = logical page (0 to F) x 10h
;	     C = physical page
;		 bit 7 = 1 for external memory
;		         0 for local memory
;		 bits 6 to 0 = physical page number
;		 (bits 6 to 4 are low-true)
;
; Example:  to map logical addresses 2000 to 2FFF into
;	    physical addresses 3000 to 3FFF, use:
;
;	    lxi	 B,20F3h
;	    call SELMEM
SELMEM:
	mov	A,C
	mvi	C,SETMAP
	outp	A	; A on data, BC on address bus
	ret
;----------
; Handle an I/O error
;
; Print a four-letter error code, and then wait
; for the user to type <return> or <control-C>.
;
ermsg1:	.asciz	[bell][cr][lf]'*** '
ermsg2:	.asciz	' error'
IOERR:
	pop	D	; compute address of caller
	dcx	D
	dcx	D
	dcx	D
;
; Search the error table for the address of the caller
	lxi	H,errtab
	mvi	B,numerr
findadr:
	mov	A,M	; get lower byte of address
	cmp	E
	inx	H
	mov	A,M	; get upper byte of address
	inx	H	; point to ASCII string
	jrnz	..1	; jump if no match
	cmp	D
	jrz	foundadr; jump if match
..1:	inx	H	; pointer to next address
	inx	H
	inx	H
	inx	H
	djnz	findadr	; check next address, if any
;
; If the retry count in low core equals zero, 
; then the error code number will be returned in RA.  
; Diagnostic programs should set the retry count to 0,
; so that errors can be automatically intercepted
; and diagnosed.
foundadr:
;
;
; Print an error message
prterr:
	push	H
	lxi	H,ermsg1; print heading message
	call	PRTMSG
	pop	H
	mvi	B,4	; print a four-letter code
..1:	mov	C,M
	call	CONforce
	inx	H
	djnz	..1
	lxi	H,ermsg2
	call	PRTMSG	; print trailing message
;
; Wait for the user to hit <return> or <control-C>
waituser:
	call	CONIN
	cpi	cntlC
	jz	WBOOT
	sui	cr
	rz
	jmpr	waituser
;
; Error code table
errtab:
	.ife	FRONTopt,[
	.word	HALTerr	; HLT instruction executed
	.ascii	'HALT'
	]
	.word	INTerr	; unexpected interrupt
	.ascii	'INT '
	.word	CALLerr	; unexpected BIOS call
	.ascii	'CALL'
numerr	==	(.-errtab)/6
	.ascii	'I/O '	; default error code
;----------
; Handle an unexpected interrupt
INTerr:	call	IOERR
;----------
; Handle an invalid BIOS call
CALLerr:call	IOERR
;----------
; Get current disk map
;  Regs in:   none
;  Regs out:  HL = pointer to current disk map
;	      A  = high order 2 bits: device type
;		   low order 6 bits:  unit number
;  Destroyed: D
cpmMAP:
	lda	cpmDSK	; get cpm DISK
	.ife	FLOPopt,[
	jmpr	map1
iobMAP:
	lda	iobDSK	; get I/O disk number
map1:
	]
	mov	D,A
	rlc
	rlc
	rlc
	add	D	; A = disk number * 9
	mvi	H,0
	mov	L,A
	lxi	D,DSKMAP
	dad	D	; point to map of current disk
	mov	A,M	; get first map entry
	ret
;----------
; Default logical to physical disk maps
;  (1) physical device and unit number
;  (2) sectors per track
;  (3) maximum directory number
;  (4) logarithm of sectors per block (3 means 2**3)
;  (5) sectors per block - 1
;  (6) number of blocks - 1
;  (7) bits indicate blocks for directory
;  (8) number of operating system tracks
;  (9) 4Eh for CP/M sector mapping, 0C0h otherwise
SD	==	0<6
DD	==	1<6
HARD	==	2<6
NET	==	3<6
DSKBOOT:
	.ife	FLOPshar,[ .byte 0     ]; not used
DSKMAPº	
	.ife	FLOPshar,[
; (floppy network) (1),(2),(3),(4),(5),(6),(7),(8),(9)
DSK0:	.byte	NET +0, 52,127,  4, 15,242,0C0h, 2, 0Ch
DSK1:	.byte	NET +1, 52,127,  4, 15,242,0C0h, 2, 0Ch
DSK2:	.byte	NET +4, 52,127,  4, 15,242,0C0h, 2, 0Ch
DSK3:	.byte	NET +5, 52,127,  4, 15,242,0C0h, 2, 0Ch
	]
;----------
; Network spooler or user printer driver
PORTNout:jmp	CALLerr
.end
