	.title	'DSC/3, DSC/4 CP/M 1.4 BIOS'  
	.sbttl	'BIOS.ASM'
version	==	1	; CP/M version number
revision==	432	; DMS revision number
;----------
; DSC/3, DSC/4 CP/M BIOS
;
; Written by Peter Kavaler  July-December 1980,
;  with assistance from Robert Kavaler
;
; Update history:
;
; 1.40 (09/01/80) Initial release
; 1.41  (10/01)   Added S/L bit to pre-comp
;       	  Network always at 500 Khz
;       	  Network disk shared by master
; 1.42            Clear DD buffer at offMOTOR
;		  LOGIN code added to master
; 1.421 (10/20)   SENDNET timing problem fixed
; 1.422 (10/22)   Floppy sharing added
;		  Harddisk ASSIGN added
; 1.423 (10/28)   IOBYTE added
;		  Centronix supported
;		  ASSIGN names added
;		  User names added
; 1.424 (11/04)   Changed floppy head settle times
; 1.425 (11/10)   ADDS printer supported
;		  User name table lookup added
;		  Master BIOS expanded by 1K
; 1.426 (11/11)   Added default configuration table
;		  Added boot from floppy drive 1
; 1.427 (11/24)   Added boot from hard disk
;		  Master does real warmboot
;		  Floppy and hard status in fixed loc
; 1.428 (12/04)   Master user must login
; 		  Retry SECT, MADR errors
;		  Fixed 850 bug (headno missing)
; 1.429 (12/09)   Allowed DSC/4 on network
;		  Added hard disk timeout
;		  Handle CNTL-P with ADDS AUX port
; 1.430 (12/17)   Floppy network master can warmboot
;		  Remove ASSIGN code from BIOS
;		  Hertz rate in high core
; 1.431 (01/14)   Make compatible with CP/M 2.201
;		  Optional seek rate 3ms for 850
;		  Add hog protocol to network
;		  More extensive WHO info
; 1.432 (01/23)   Change network bootstrap procedure
;		  Add record locking
;		  User dependent IOBYTE
	.page
;----------
; Six different standard versions of CP/M can be
; generated by assembling this BIOS, depending upon
; which conditional assembly options are chosen.
; The procedure for generating a complete operating
; system for each case is outlined below.
;
;	     Stand-alone system
;	     ------------------
; Floppy boot		     Harddisk boot
; -----------		     -------------
; A>ddt cpmF1.com	     A>ddt cpmF1.com
; -ibios.hex		     -ma00 1f00 100
; -r2e00		     -ibios.hex
; -ifboot.hex		     -r2500
; -r7900		     -^C
; -^C			     A>save 36 hardcpm.com
; A>sysgen		     A>wrun0 hardcpm.com 1 39
;			     A>wrun0 hboot.com 2 1
;
;              Network master
;	       --------------
; Floppy network	     Harddisk network
; --------------	     ----------------
; A>ddt cpmEF.com	     A>ddt cpmE9.com
; -ibios.hex		     -ma00 1f00 100
; -r3000		     -ibios.hex
; -ifboot.hex		     -r3100
; -r7900		     -^C
; -^C			     A>save 48 hmaster.com
; A>save 47 fmaster.com	     A>wrun0 hmaster.com 1 21
; A>sysgen		     A>wrun0 hboot.com 2 1
;
;	       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
;----------
	.page
;----------
; 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	=\ "
Enter 1 for stand-alone system
      2 for network master
      3 for network station "
	.ife	SELECT-1,[
FLOPboot=\ "
Enter 0 for floppy boot
      1 for harddisk boot "
HARDboot==	1-FLOPboot
NETboot	==	1	
MASTopt	==	1	
FLOPopt	==	0	
NETopt	==	0	
HARDopt	==	0	
FLOPshar==	1	
HARDshar==	1	
	]
	.ife	SELECT-2,[
FLOPboot==	1	
NETboot	==	0	
HARDboot==	1	
MASTopt	==	0	
FLOPopt ==	0	
NETopt  ==	0	
                 	
FLOPshar=\ "
Enter 0 for floppy network
      1 for harddisk network "
HARDshar==	1-FLOPshar
HARDopt	==	HARDshar
	]
	.ife	SELECT-3,[
FLOPboot==	1	
NETboot	==	0	
HARDboot==	1	
MASTopt	==	1	
FLOPopt	==	1	
NETopt	==	0	
HARDopt	==	1	
FLOPshar=\ "
Enter 0 for floppy network
      1 for harddisk network "
HARDshar==	1-FLOPshar
	]
	.page
	.pabs
	.phex
;----------
; Data and address definitions:
;
Msize	==	64	; memory size (Kbytes)
.ife	SELECT-1,[ lenBIOS = 0F00h]
.ife	(SELECT-2)!HARDshar,[ lenBIOS = 1700h]
.ife	(SELECT-2)!FLOPshar,[ lenBIOS = 1100h]
.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
	.page
;----------
; 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
	.page
;----------
; 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
	.page
;----------
; 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
	]
;----------
; Floppy disk controller command and status
	.ife	FLOPopt,[
rdSDFLOP==	6	; read single density floppy
wrSDFLOP==	5	; write single density floppy
rdDDFLOP==	6+40h	; read double density floppy
wrDDflop==	5+40h	; write double density floppy
FLOPcom:.byte	0	; readFLOP or writeFLOP here
FLOPdsk:.byte	0       ; curDSK
FLOPtrk:.byte   0       ; curTRK
FLOPsid:.byte	0	; head address
FLOPsec:.byte	0       ; curSEC
FLOPden:.byte	00h	; 128 byte sector
	.byte	1Ah	; last sector number
FLOPgap:.byte	07h	; gap length
	.byte	128	; sector size
FLOPstat:.blkb	7	; floppy controller status
	]
;----------
; Harddisk controller command and status
	.ife	HARDopt,[
readHARD==	11h	; read a sector
writeHARD==	12h	; write a sector
selHARD	==	13h	; select a unit
assnHARD==	17h	; assign a unit
HARDcom:.byte	0	; hard disk command
HARDdsk:
HARDsec:.byte	0	; sector or drive number
HARDtrk:.word	0	; track number
	.byte	0,0,RETRY,0 ; -not used-
HARDstat:.blkb	8	; harddisk controller status
	]
;----------
; 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
	]
;----------
; Network master command and status
	.ife	MASTopt,[
MASTsnd:.byte	0	; message sent to user
MASTcom:.byte	0	; command or message from user
        .byte	0	; -not used-
MASTdesc:
MASTdsk:.byte	0	; disk
MASTtrk:.byte	0	; track
MASTsec:.byte	0	; sector
MASTbyt:.word	0	; byte count
MASTdma:.word	0	; DMA address
	.loc	MASTsnd+1
MASTnum:.blkb	1	; user number
MASTtim:.blkb	7	; login time
	.loc	MASTcom+1
MASTnam:.blkb	8	; user name
MASTpsw:.blkb	6	; user password
	]
;----------
; 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
;----------
; Current floppy buffer information
	.ife	FLOPopt,[
iobDESC:
iobDSK:	.byte	0FFh	; disk   for I/O operation
iobTRK:	.byte	0FFh	; track  for I/O operation
iobPSEC:.byte	0	; sector for I/O operation 
iobSEC:	.byte	0	; sector for I/O operation(log)
;
rdbDESC:
rdbDSK:	.byte	0FFh	; disk   of read buffer
rdbTRK:	.byte	0FFh	; track  of read buffer
rdbPSEC:.byte	0	; sector of read buffer (phys)
;
wrbDESC:
wrbDSK:	.byte	0FFh	; disk   of write buffer
wrbTRK:	.byte	0FFh	; track  of write buffer
wrbPSEC:.byte	0	; sector of write buffer (phys)
wrbSEC:	.byte	0	; sector of write buffer (log)
	]
	.page
;----------
; 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-1,	  [.loc rdbuffer]
	.ife	(SELECT-2)!FLOPsh,[.loc rdbuffer]
	.ife	(SELECT-2)!HARDsh,[.loc BIOS-lenBOOT]
	.ife	 SELECT-3,	  [.loc BIOS-lenBOOT]
;----------
; Prepare for interrupts
CBOOT:
	mvi	A,(vectors>8)&0FFh
	stai		; setup interrupt register
	im2
	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 timer and network user number
	.ife	FLOPboot&HARDboot&MASTopt,[
	lxi	H,ticks
	mvi	B,7
init0:	mvi	M,0	; set to zero
	inx	H
	djnz	init0
	mvi	A,0FFh	; default network user number
	sta	NETusr
	]
;----------
; Print CP/M message
	.ifn	SELECT-3,[
	lxi	H,CPMmsg
	call	PRTMSG
	]
;----------
; Initialize port 1
	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
;----------
; Specify floppy seek rate
	.ife	FLOPopt,[
	lda	FLOPmdl
	ora	A	; seek rate 8ms for Shugart 800
	jrz	..specify
	mvi	A,0DFh	; seek rate 3ms for Shugart 850
	sta	specFLOP+1
..specify:
	lxi	H,specFLOP
	call	COMMAND	; specify floppy facts
	]
;----------
; 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,[
	mvi	A,PIOvect&0FFh ; interrupt vector
	out	PIOAC
	mvi	A,10110111b ; enable interrupts
	out	PIOAC
	mvi	A,01111111b ; front-panel/HLT
	out	PIOAC
	]
;----------
; If master option has been selected, clear various
; tables, then wait for local user to login
	.ife	MASTopt,[
;
; Clear user name list
	lxi	H,userlist
	mvi	M,20h	; give current users one chance
	lxi	D,userlist+1	; to stay logged in
	lxi	B,16*numusr-1
	ldir
	sub	A	
	sta	userlist; dont touch local user
	sta	NETusr	; master userno = 0
;
; Clear lock list
	.ife	HARDshar,[
	lxi	H,locklist
	mvi	M,0FFh
	lxi	D,locklist+1
	lxi	B,16*numlock-1
	ldir
	]
;
; It's OK to wake the master now
	mvi	A,wakeupMAST
	sta	timeMAST
;
; Ask master user for his login name
	.ife	HARDshar,[
..askname:
	call	askname
	mvi	A,loginNET
	sta	NETcom
	call	NETreq
	lda	NETassn
	ora	A	; A = 0 if login accepted
	jrz	..logged
	mvi	C,bell	; ring bell if login denied
	call	outchr
	jmpr	..askname
..logged:
	]
	]
;----------
; If type-ahead option has been selected, enable
; receiver interrupts for port 0
	.ife	AHEADopt,[
	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
	]
;----------
; Assign default disks on hard disk network
	.ife	HARDshar,[
	lxi	H,DSK0def
	mvi	C,0
	call	ASSdef	; CP/M 'A' drive
	lxi	H,DSK0def+8
	mvi	C,1
	call	ASSdef	; CP/M 'B' drive
	lxi	H,DSK0def+16
	mvi	C,2
	call	ASSdef	; CP/M 'C' drive
	lxi	H,DSK0def+24
	mvi	C,3
	call	ASSdef	; CP/M 'D' drive
	lda	IOBdef
	sta	3	; default IOBYTE
	]
;----------
; Assign default disks on hard disk stand-alone system
	.ife	HARDboot,[
	mvi	C,0FFh
	call	SELDSK
	call	HOME
	mvi	C,121	; first sector of alloc table
	call	SETSEC
	lxi	B,DSK0def
	call	SETDMA
	call	READ	; read beginning of alloc table
	lxi	H,DSK0def+17
	mvi	C,0
	call	ASSdef	; CP/M 'A' drive
	lxi	H,DSK0def+33
	mvi	C,1
	call	ASSdef	; CP/M 'B' drive
	lxi	H,DSK0def+49
	mvi	C,2
	call	ASSdef	; CP/M 'C' drive
	lxi	H,DSK0def+65
	mvi	C,3
	call	ASSdef	; CP/M 'D' drive
	]
;----------
; Jump to the warm-boot entry point
	jmp	WBOOT
;----------
; Assign default disk partitions
;
; Regs in:   HL = pointer to partition name
;	     C  = unit number
; Regs out:  none
; Destroyed: A, BC, DE, HL
	.ife	HARDshar&HARDboot,[
ASSdef:
	push	B
	push	H
	call	SELDSK; needed for calling CPMMAP
	pop	H
	push	H
	lxi	D,DSKdef
	lxi	B,8
	ldir		; put disk name in safe place
	lxi	H,DSKdef
	call	ASSIGN; returns unit in B, size in C
	mov	A,B
	cpi	0FFh
	jrz	..ret	; return if not found
	call	CPMMAP; get pointer to disk map
	mov	M,B	; overwrite map byte
	inx	H
	xchg		; DE = address of disk map
	mov	A,C
	dcr	A	; A = 0,1,2,3,or 4
	rlc
	rlc
	rlc
	mov	C,A	; C = 0,8,16,24,or 32
	mvi	B,0
	lxi	H,DSKtab
	dad	B	; compute disk table address
	lxi	B,8
	ldir		; overwrite disk map
;
; Move partition name into BIOS
	pop	H
	pop	B
	xchg
	mvi	A,27	; compute address of name
	sub	C
	mvi	B,0
	mov	C,A	; subtract unitno from 27
	dad	B
	xchg
	lxi	B,8	; move 8 bytes
	ldir
	ret
..ret:	pop	H
	pop	B
	ret
;----------
; ASSIGN a hard disk partition
;  Regs in:  HL = address of partition name
;
; If network master, then let the BIOS handle it
ASSIGN:
	.ife	MASTopt,[
	lxi	D,NETnam
	lxi	B,14
	ldir
	mvi	A,assnNET
	sta	NETcom	; set command byte last
	call	NETreq	; wait for master to wakeup
	lbcd	NETassn	; B = unitno, C = size
	mvi	A,NET
	ora	B	; make it a network partition
	mov	B,A
	ret
	]
;----------
; If network station, then assign thru network
	.ife	(1-MASTopt)!NETboot,[
	lxi	D,BOOTnam; setup command in boot area
	lxi	B,14
	ldir
	mvi	A,assnNET
	sta	BOOTcom
..poll:	lxi	H,BOOTmsg
	lxi	B,1
	lda	NETusr
	call	RECNET
	lda	BOOTmsg
	cpi	poll
	jrnz	..poll
	lxi	H,BOOTcom; send assign request
	lxi	B,15
	sub	A
	call	SENDNET
	lxi	H,BOOTassn; get assignment from master
	lxi	B,4
	lda	NETusr
	call	RECNET
	lbcd	BOOTassn; B = unitno, C = size
	mvi	A,NET	; make a network unit
	ora	B
	mov	B,A
	ret
	]
;----------
; If booting from harddisk, assign directly
	.ife	HARDboot,[
	push	H
	mvi	A,assnHARD
	call	CMDHARD	; send assign command to disk
	pop	H
	mvi	B,14
	call	SENDHARD; send parition name and psw
	call	RESHARD	; get unit number and size
	lbcd	HARDstat
	mvi	A,HARD
	ora	B	; turn it into a harddisk unit
	mov	B,A
	ret
	]
;----------
; Default disk table (for sizes from 256K to 4M bytes)
DSKtab:	.byte	128, 63,  3,  7,255,0C0h, 0, 0Ch
	.byte	128, 95,  4, 15,255,0C0h, 0, 0Ch
	.byte	128,127,  5, 31,255,080h, 0, 0Ch
	.byte	128,191,  6, 63,255,080h, 0, 0Ch
	.byte	128,254,  7,127,255,080h, 0, 0Ch
DSK0def	==	TPA	; default disk names
IOBdef	==	TPA+32	; default IOBYTE
DSKdef: .blkb	8
	.byte	0,0,0,0,0,0; ignore password
	]
;----------
; CP/M cold-boot message
CPMmsg:
.ife	MASTopt,[.ascii	[cr][lf]'HiNet Master  ']
.ifn	MASTopt,[.ascii	[cr][lf]'CP/M  ']
	.byte	version+'0','.',revision/100+'0'
	.byte	(revision@100)/10+'0'
	.byte	(revision@100)@10+'0'
	.ife	MASTopt!HARDshar,[
	.asciz	[cr][lf][lf]'Login please ... ']
	]
	.byte	0
;----------
; 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
;----------
; Floppy specify command
	.ife	FLOPopt,[
specFLOP:.byte	3	; specify command
	.byte	8Fh	; seek rate = 3ms or 8ms
			; 35ms delay after head load
	.byte	02h	; 10ms delay after seek
	.byte	endcom
	]
;----------
; 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:
	lxi	SP,80h	; setup a stack pointer
	mvi	A,(vectors>8)&0FFh
	stai		; setup interrupt register
	im2
	ei		; enable interrupts
	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
	.ife	(1-MASTopt),[call ACKpoll;answer polls]
	.ifn	FLOPshar,[
	mvi	A,clrlockNET
	sta	NETcom	; clear all active locks
	call	NETreq
	]
..wboot:
	]
;----------
; Warm boot from floppy
	.ife	FLOPboot,[
	call	RESULT	; flush floppy controller
	call	clrDDbf	; flush double-density buffer
	mvi	C,0FFh
	call	SELDSK	; select boot disk
	call	HOME	; home boot drive
	mvi	C,3	; start at sector 3
	call	SETSEC
	lxi	B,CPM
	call	SETDMA	; start at base of CP/M
	lxi	B,24*128 ; bytes on track 0
	call	SETBYT
	call	READ
	mvi	C,1
	call	SETTRK	; move to track 1
	mvi	C,1
	call	SETSEC	; start at sector 1
	lxi	B,CPM+24*128
	call	SETDMA
	lxi	B,lenCPM-24*128 ; bytes on track 1
	call	SETBYT
	call	READ
	lxi	B,128	; default DMA length
	call	SETBYT
	lda	4
	mov	C,A
	jmp	CPM
	]
;----------
; Warm boot from harddisk or hardisk network
	.ife	HARDboot&HARDshar,[
	.ife	(1-MASTopt)!(1-HARDboot),[call ACKpoll]
	.ife	FLOPopt,[
	call	RESULT	; flush floppy controller
	call	clrDDbf	; flush double-density buffer
	]
	mvi	C,0FFh
	call	SELDSK	; select boot disk
	.ife	MASTopt&HARDboot,[ mvi  C,1]; master
	.ifn	MASTopt&HARDboot,[ mvi  C,2]; station
	call	SETTRK
	.ife	NETboot, [mvi C,129-(lenBOOT-CPM)/128]
	.ife	HARDboot,[mvi C,129-(0-CPM)/128]
	call	SETSEC
	lxi	B,128
	call	SETBYT
	lxi	H,CPM
	mvi	B,lenCPM/128 ; read CP/M
	call	READBLK
	lda	4
	mov	C,A	; get current disk number
	jmp	CPM
	]
;----------
; Warm boot from floppy network
	.ife	FLOPshar,[
	.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	HOME
	mvi	C,3
	call	SETSEC
	lxi	B,128
	call	SETBYT
	lxi	H,CPM
	mvi	B,24	; read 24 sectors from track 0
	call	READBLK
	mvi	C,1
	call	SETTRK
	mvi	C,1
	call	SETSEC
	mvi	B,lenCPM/128-24; read rest of CP/M
	call	READBLK
	lda	4
	mov	C,A
	jmp	CPM
	]
	.page
;----------
; 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
	]
	.page
;----------
; 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:
;----------
; 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
	.page
	.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
	]
	.ifn	AHEADopt,[
;----------
; Console status
PORT0st:
	in	SIO1AC	; check port 0 status
	ani	1<RxRDY	; check for receiver ready
	rz
	mvi	A,0FFh	; CP/M wants A = 0FFh if yes
	ret
;----------
; Console input
PORT0in:
	call	PORT0st
	jrz	PORT0in
	in	SIO1AD	; read the character
	ani	7Fh	; mask out the parity bit
	]
;----------
; 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
	.ifn	SELECT-3,[
;-------
; Port 1 status
PORT1st:
	in	SIO1BC
	ani	1<RxRDY
	rz
	mvi	A,0FFh
	ret
;----------
; Port 1 input
PORT1in:
	call	PORT1st
	jrz	PORT1in
	in	SIO1BD
	ani	7Fh
	ret
;----------
; Port 1 output
PORT1out:
	in	SIO1BC
	ani	1<TxRDY
	jrz	PORT1out
	mov	A,C
	out	SIO1BD
	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)
	]
	.page
;----------
; 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
	.page
;----------
; Home to track 0
HOME:	sub	A	; A = 0
	sta	cpmTRK	; set track to 0
	.ife	FLOPopt,[
	call	cpmMAP
	ani	80h
	rnz		; return if not floppy
;----------
; Recalibrate a drive
reHOME:
	lda	cpmDSK
	sta	iobDSK	; iob is used in iobMAP
	lxi	H,homeFLOP+1
;----------
; Seek a track (used by HOME and READ/WRITE)
SEEK:	push	H
	call	iobMAP	; get current disk map
	pop	H
	ani	3Fh	; mask out unit number
	mov	M,A	; store into command
	dcx	H
	call	onMOTOR	; turn on the drive motor
	mov	D,H	; save ptr to seek command
	mov	E,L
	mvi	B,0
..seek:	call	COMMAND	; seek
..sense:lxi	H,IsenseFLOP
	call	COMMAND	; sense interrupt status
	call	RESULT
	mov	H,D	; restore ptr to seek command
	mov	L,E
	lda	FLOPstat; get first result byte
	bit	3,A	; check ready bit
	jrnz	..seek	; jump if not ready
	bit	5,A	; check seek-done bit
	jrnz	..done
	mvi	B,1	; set seek flag
	jmpr	..sense
..done:	ani	0D0h	; check if seek ok
SEEKerr:cnz	IOERR
	call	idleMOTOR ; turn off motor in 1 second
	mov	A,B
	ora	A	; return if over same track
	rz
	lxi	B,stepset; delay 10 ms for step settle
;----------
; Delay loop (delay count in BC)
DELAY:	dcx	B
	mov	A,B
	ora	C
	jrnz	DELAY
	]
	ret
	.page
;----------
; Read disk
READ:
	call	cpmMAP	; determine device type
	ani	0C0h	; mask out for device
	.ife	FLOPopt,[
	cpi	SD	; check for single density
	jrz	SDrd
	cpi	DD	; check for double density
	jz	DDrd
	]
	.ife	NETopt,[
	cpi	NET	; check for network
 	jz	NETrd
	]
	.ife	HARDopt,[
	cpi	HARD	; check for hard disk
 	jz	HARDrd
	]
	jmp	CALLerr ; if none of the above - error
;----------
; Write disk
WRITE:	call	cpmMAP	; determine device type
	ani	0C0h	; mask for device type
	.ife	FLOPopt,[
	cpi	SD	; check for single density
	jrz	SDwr
	cpi	DD	; check for double density
	jz	DDwr
	]
	.ife	NETopt,[
	cpi	NET	; check for network
 	jz	NETwr
	]
	.ife	HARDopt,[
	cpi	HARD	; check for hard disk
 	jz	HARDwr
	]
	jmp	CALLerr ; if none of the above - error
	.page
	.ife	FLOPopt,[
;----------
; Single Density read
SDrd:
	lxi	D,7Dh<8+readDMA ; set up params
	mvi	B,rdSDFLOP
	jmpr	SDfloppy
;---------
; Single Density write
SDwr:
	lxi	D,79h<8+writeDMA ; set up params
	mvi	B,wrSDFLOP
;----------
; Single Density Floppy (Read and Write common)
SDfloppy:
	lhld	cpmDMA	; set DMA address
	shld	DMAFadr
	lhld	cpmBYT  ; set DMA size
	dcx	H	; DMA wants size - 1
	shld	DMAFsize
	mvi	A,0	; set FLOPPY density
	sta	FLOPden
	mvi	A,7	; set FLOPPY gap
	sta	FLOPgap
	lhld	cpmDESC	; iobDESC <- cpmDESC (3)
	shld	iobDESC
	lda	cpmSEC
	sta	iobPSEC	; physical sector is cpmSEC
	jmp	Floppy
;----------
; Double Density read
DDrd:
	lda	cpmTRK	; track 0 DD is SD
	ora	A
	jrz	SDrd
	lda	cpmBYT	; 256+ byte read operation?
	ora	A	; (low byte = 0?)
	jz	DDrdspec
	lded	cpmDESC ; is sector in wrbuffer?
	lhld	wrbDESC
	ora	A
	dsbc	D
	jrnz	..1
	lda	cpmSEC
	mov	B,A
	lda	wrbSEC
	sub	B
	jrnz	..1	; if not then read from disc
	lded	cpmDMA	; else just get it from wrbuf
	lxi	H,wrbuffer
	lxi	B,128
	ldir
	ret
;
..1:	call	convIC	; set up iobDESC
	call	DDrdflop ; read the disc
	push	PSW	; save I/O status
	lda	iobSEC	; move 128 byte block from
	rrc		; get rightmost bit
	lxi	H,rdbuffer ; set rdbuffer high or low
	jrc	..2	; depending on leftmost bit
	lxi	H,rdbuffer+128
..2:	lded	cpmDMA	; move rdbuffer to curDMA
	lxi	B,128	
	ldir
	pop	PSW	; restore I/O status
	ret
;----------
; Double Density write
DDwr:
	lda	cpmTRK	; track 0 DD is SD
	ora	A
	jrz	SDwr
	lda	cpmBYT	; check for 256+ byte operation
	ora	A
	jz	DDwrspec
	lda	cpmSEC	; check for high-low sector
	rrc
	jrnc	DDwrhigh
DDwrlow:
	call	clrDDbf	; flush wrbuffer
	call	convIC	; wrbDESC <- cpmDESC
	lhld	iobDESC	; through iobDESC
	shld	wrbDESC
	lhld	iobDESC+2
	shld	wrbDESC+2
	lhld	cpmDMA	; write 128 bytes to low wrb
	lxi	D,wrbuffer
	lxi	B,128
	ldir
	sub	A	; return A = 0
	ret
DDwrhigh:
	call	convIC  ; iob is now cpm
	lxi	H,wrbDESC ; is low already in buffer?
	call	cmpIOB
	jrz	..1
	call	clrDDbf	; if not flush wrbuffer
	call	convIC	; read low wrbuffer from disc
	call	DDrdflop ; for read-modify-write
	lxi	H,rdbuffer ; modify low 128 bytes
	lxi	D,wrbuffer
	lxi	B,128
	ldir
..1:	lhld	cpmDMA	; move in upper 128 bytes
	lxi	D,wrbuffer+128
	lxi	B,128
	ldir
	call	DDwrflop ; write out the buffer
	ret		
;----------
; Flush (clear) the Double Density wrbuffer
clrDDbf:
	lda	wrbPSEC	; is buffer full?
	ora	A
	rz		; return if empty
	lxi	H,wrbDESC ; otherwise read-modify-write
	lxi	D,iobDESC ; first set iobDESC
	ldi
	ldi
	ldi
	call	DDrdflop ; read
	lxi	H,rdbuffer+128 ; modify
	lxi	D,wrbuffer+128
	lxi	B,128
	ldir
	call	DDwrflop ; write
	ret
;----------
; Compare 3 bytes, (HL) and iobDESC
cmpIOB:
	lda	iobDSK	; test DISK byte
	mov	B,M
	cmp	B
	rnz
	lda	iobTRK	; test TRACK byte
	inx	H
	mov	B,M
	cmp	B
	rnz
	lda	iobPSEC	; compare physical sector
	inx	H
	mov	B,M
	cmp	B
	ret
;----------
; Move cpmDESC to iobDESC and convert PSEC for DD
convIC:
	lhld	cpmDESC	; two bytes at a time
	shld	iobDESC
	lda	cpmSEC	; store sector
	sta	iobSEC
	adi	1	; and calculate PSEC
	srlr	A
	sta	iobPSEC ; and store it
	ret
;----------
; Double Density special 256+ byte read
DDrdspec:
	lxi	D,7Dh<8+readDMA ; load r/w params
	mvi	B,rdDDFLOP
	jmpr	DDspec
;----------
; Double Density special 256+ byte write
DDwrspec:
	lxi	D,79h<8+writeDMA ; load r/w params
	mvi	B,wrDDFLOP
;
; Double Density special 256+ byte read/write
DDspec:
	lhld	cpmBYT	; set DMA size
	dcx	H	; DMA wants size - 1
	shld	DMAFsize
	lhld	cpmDMA	; set DMA address
	shld	DMAFadr
	call	convIC	; set up iobDESC
	jmpr	DDsflop	; jump to middle of DDfloppy
;----------
; Special read double density for buffer
DDrdflop:
	lxi	H,rdbDESC ; is buffer in memory?
	call	cmpIOB
	jrnz	..1
	mvi	A,0	; if so, return 0
	ret
..1:	lxi	H,iobDESC ; otherwise load rdbDESC
	lxi	D,rdbDESC ; with iob to reflect new
	ldi		  ; rdbuffer
	ldi
	ldi
	lxi	D,7Dh<8+readDMA ; set up params
	mvi	B,rdDDFLOP ; for double density read
	lxi	H,rdbuffer ; set DMA address
	shld	DMAFadr
	jmpr	DDfloppy
DDwrflop:
	lxi	H,rdbDESC ; is rdbuffer the same as
	call	cmpIOB	  ; wrbuffer
	jrnz	..1
	lxi	H,wrbuffer ; if so update rdbuffer
	lxi	D,rdbuffer
	lxi	B,256
	ldir
..1:	lxi	H,0
	shld	wrbPSEC	; set wrbuffer empty
	lxi	D,79h<8+writeDMA ; load params for
	mvi	B,wrDDFLOP ; double density write
	lxi	H,wrbuffer ; set DMA address at wrbuf
	shld	DMAFadr
;----------
; Double Density Floppy (Read and Write common)
DDfloppy:
	lxi	H,255	; set DMA size 
	shld	DMAFsize
DDsflop:mvi	A,1	; set FLOPPY density
	sta	FLOPden
	mvi	A,0Eh	; set FLOPPY gap
	sta	FLOPgap
;----------
; Floppy read/write -  single/double density
;
; Setup the DMA chip
Floppy:
	mov	A,E
	sta	DMAFc2	; read/write command
	mov	A,D
	sta	DMAFc1	; read/write command also
;
; Setup the floppy read/write command
	mov	A,B
	sta	FLOPcom	; read/write command
	lda	iobPSEC	; set sector number
	sta	FLOPsec
	lda	retries	; retry on CRC errors only
	inr	A	; tries = retries + 1
	mov	D,A	; current retry count in D
	push	D
	jmpr	tryf
;
; Recalibrate and retry if any error encountered
retryf:	inr	M
	push	D
	call	reHOME
;
tryf:	call	iobMAP	; get current disk map
	ani	3Fh	; mask out unit number
	sta	FLOPdsk	; drive number
	rrc
	rrc
	ani	1
	sta	FLOPsid	; side number
	lda	iobTRK	; track number
	sta	FLOPtrk	
;
; Seek for I/O track
	lxi	H,seekFLOP+2 ; move track into command
	mov	M,A
	dcx	H
	call	SEEK	; go to tracks 1-76
;
; Make sure that the head is loaded
	call	iobMAP
	ani	3Fh	; mask out current drive no.
	call	onMOTOR
;
; Set up the DMA chip
	call	lockDMA	; reserve the DMA chip
	lxi	H,DMAFdone
	shld	DMAvect	; setup the DMA vector
	sub	A
	out	PIOAD	; multiplex floppy to DMA
	lxi	H,DMAFprog
	lxi	B,DMAF$<8+DMA
	outir		; program the DMA chip
;
; Execute the floppy command
	lxi	H,FLOPcom
	mvi	A,endcom
	sta	FLOPstat; mark end of floppy command
	call	COMMAND
	ei		; interrupts OK now
	call	RESULT
	call	idleMOTOR; don't need motor anymore
	pop	D	; restore retry count
	lda	FLOPstat; get first result byte
	ani	0C0h	; mask out top 2 bits
	rz		; return to CP/M if no errors
;
; Check for recoverable errors
	lxi	H,errors
	lda	FLOPstat+2
	bit	5,A
	jrz	chkTRAC
	dcr	D
DATAerr:cz	IOERR	; data CRC error
	jmpr	retryf
chkTRAC:bit	4,A
	jrz	chkMADR
	dcr	D
TRACerr:cz	IOERR	; on wrong track
	jmpr	retryf
chkMADR:bit	0,A
	jrz	chkID
	dcr	D
MADRerr:cz	IOERR	; missing data address mark
	jmpr	retryf
chkID:	lda	FLOPstat+1
	bit	5,A
	jrz	chkSECT
	dcr	D
IDerr:	cz	IOERR	; ID CRC error
	jmp	retryf
chkSECT:bit	2,A
	jrz	chkENDT
	dcr	D
SECTerr:cz	IOERR	; cannot find sector
	jmp	retryf
;
; Non-recoverable errors
chkENDT:bit	7,A
ENDTerr:cnz	IOERR	; read beyond end-of-track
	bit	4,A
ORUNerr:cnz	IOERR	; overrun (DMA failure)
	bit	1,A
PROTerr:cnz	IOERR	; write-protected
	bit	0,A
DENSerr:cnz	IOERR	; missing ID address mark
	call	IOERR
;----------
; Handle the transfer-done interrupt from the DMA chip
DMAFdone:
	push	PSW
	in	STOPFLOP; reset the floppy chip
	mvi	A,0C3h
	out	DMA	; reset the DMA chip
	sub	A
	sta	lockbyte; unlock the DMA chip
	pop	PSW
	ei
	ret
;----------
; Send a command to the floppy controller
;  Regs in:   HL = address of command string
;  Regs out:  none
;  Destroyed: A, HL
COMMAND:
	mov	A,M
	cpi	endcom	
	rz		; return if end-of-command
	call	WAITDR
SYNCerr:cc	IOERR	; direction is wrong
	mov	A,M
	out	FLOPDR	; send a byte to controller
	inx	H	; point to next byte
	jmpr	COMMAND
;----------
; Collect a result from the floppy controller
;  Regs in:   none
;  Regs out:  none
;  Destroyed: A, HL
RESULT:
	lxi	H,FLOPstat
readDR:	call	WAITDR
	rnc
	mvi	A,0C3h	; reset DMA so that multisector
	out	DMA	; CRC errors handled properly
	sub	A
	sta	lockbyte; unlock the DMA chip
	in	FLOPDR	; get a result byte
	mov	M,A	; store it away
	inx	H
	jmpr	readDR
;----------
; Wait until floppy data register is ready
;  Regs in:   none
;  Regs out:  A = status register, shifted left by 1
WAITDR:
	in	FLOPSR
	rlc
	jrnc	WAITDR
	rlc
	ret
;----------
; onMOTOR - turn on a drive motor and pre-compensate
;  Regs in:   A = drive number
;  Regs out:  none
;  Destroyed: A, BC
onMOTOR:
	mov	B,A
	lda	iobTRK	; check current track number
	cpi	onprecmp; turn on pre-comp?
	jrc	..1
	set	5,B	; bit 5 = pre-comp
	cpi	maxprec	; turn off S/L bit?
	jrnc	..1
	set	4,B	; bit 4 = S/L
..1:	set	2,B	; bit 2 = head load
	di		; disallow timer interrupt
	lda	timeMOTOR
	mov	C,A
	mov	A,B
	out	PIOBD
	sub	A	; force motor on until idle
	sta	timeMOTOR
	ei		; interrupts OK now
	mov	A,C
	ora	A	; if old time not zero,
	rnz		; then head already loaded
	lxi	B,loadset; delay for head load settle
	jmp	DELAY
;----------
; idleMOTOR - the motor can be turned off now
;  Regs in:   none
;  Regs out:  none
;  Destroyed: A
idleMOTOR:
	mvi	A,unloadHEAD ; wait 60 clock ticks,
	sta	timeMOTOR;  then turn off the motor
	ret
;----------
; offMOTOR - turn off all drive motors
;
; This routine is called from TIMERint.  It is called
;  when the floppy motor counter reaches zero.
offMOTOR:
	call	clrDDbuf; clear double density buffer
	mvi	A,0
	out	PIOBD	; unload the head
	ret
	.page
;----------
; Floppy controller commands
endcom	==	0FFh	; command terminator
homeFLOP:
	.byte	7	; recalibrate command
	.byte	0	; curDSK
	.byte	endcom
seekFLOP:
	.byte	15	; seek command
	.byte	0	; curDSK
	.byte	0	; curTRK
	.byte	endcom
IsenseFLOP:
	.byte	8	; sense interrupt status
	.byte	endcom
;----------
; DMA command
DMAFprog:
	.byte	0C3h	; master reset		2D
	.byte	0C7h	; reset port A		2D
	.byte	0CBh	; reset port B		2D
DMAFc1: .byte	0	; filled by Floppy	1A
DMAFadr:.word	0	; filled by Floppy
DMAFsiz:.word	0	; filled by Floppy
	.byte	14h	; port A inc, memory    1B
	.byte	28h	; port B fixed, I/O	1B
	.byte	95h	; byte mode		2B
	.byte	FLOPP 	; port B 
	.byte	12h	; interrupt at end of block
	.byte	DMAvect&0FFh ; interrupt vector
	.byte	9Ah	; stop at end of block 
	.byte	0CFh	; load starting address 2C
DMAFc2: .byte	0	; filled by Floppy 	2D
	.byte	0CFh	; load starting address 1A
	.byte	0ABh	; enable interrupts	2D
	.byte	87h	; enable DMA		2D
DMAF$	==	.-DMAFprog
	]
	.page
	.ife	HARDopt,[
;----------
; Hard disk read
HARDrd:	call	HARDprep
HARDr:	push	H	; save DMA address
	call	HARDrw
	mvi	A,readHARD
	call	CMDhard	; send read command
	call	REShard ; get result status
	pop	H
	mvi	B,128
	jmpr	REChard	; get data bytes
;----------
; Hard disk write
HARDwr:	call	HARDprep
HARDw:	push	H	; save DMA address
	call	HARDrw
	mvi	A,writeHARD
	call	CMDhard	; send write command
	pop	H
	mvi	B,128
	call	SENDhard; send data bytes
	jmpr	REShard	; get result status
;----------
; Prepare hard disk parameters
HARDprep:
	call	cpmMAP
	ani	3Fh	; get unit number
	lded	cpmTRK	; get track and sector
	lhld	cpmDMA	; get DMA address
	ret
;----------
; Read/write common code
HARDrw:
	sta	HARDdsk	; store unit number
	mvi	A,selHARD
	call	CMDhard	; select the unit
	lxi	H,HARDsec
	mov	M,D	; store sector
	inx	H
	mov	M,E	; store track
	ret
;----------
; Transmit a block to the hard disk
;  Regs in:   HL = block address
;             B  = byte count
;  Regs out:  none
;  Destroyed: A, BC, HL
SENDHARD:
	mvi	C,HARDP
..1:	in	PIOAD
	bit	3,A
	jrnz	..1
	outi
	jrnz	..1
	ret
;----------
; Receive a block from the hard disk
;  Regs in:   HL = block address
;	      B  = byte count
;  Regs out:  none
;  Destroyed: A, BC, HL
RECHARD:
	mvi	C,HARDP
..1:	in	PIOAD
	bit	4,A
	jrz	..1
	ini
	jrnz	..1
	sub	A	; CP/M wants A = 0
	ret
;----------
; Send a command to the hard disk
;  Regs in:   A = command byte
;  Regs out:  none
;  Destroyed: A, BC, HL
cmdHARD:
	sta	HARDcom
	lxi	B,0	; keep activity count
..1:	in	HARDP	; clear status
	mvi	A,51h	; "request to send"
	out	HARDP
..2:	dcx	B
	mov	A,B
	ora	C
	jrz	HARDerr	; jump if harddisk is dead
	in	PIOAD	; wait for HDC send
	bit	4,A
	jrz	..2
	in	HARDP	; check if "clear to send"
	cpi	52h
	jrnz	..1	; if not, retry
	lxi	H,HARDcom ; send the command
	mvi	B,8
	jmpr	SENDHARD
;----------
; Receive status info from the hard disk
;  Regs in:   none
;  Regs out:  A = error status
;  Destroyed: A, BC, HL
RESHARD:
	lxi	H,HARDstat
	mvi	B,8
	call	RECHARD
	lda	HARDstat+7
	ora	A
	rz
HARDerr:call	IOERR
	]
	.page
	.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.
	]
	.ifn	FLOPshar,[
	call	cpmMAP	; get current disk map
	ani	3Fh	; mask out unit number
	]
	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:
	.ife	MASTopt,[
..wait:	lda	NETcom	; wait for master to wake up
	ora	A	;  and process the command
	jrnz	..wait
	ret
	]
	.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
	.ifn	FLOPshar,[
	cpi	lockNET
	jrz	Nlock
	cpi	unlockNET
	jrz	Nunlock
	cpi	clrlockNET
	jrz	Nclrlock
	]
	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
	.ifn	FLOPshar,[
Nlock:
Nunlock:
	lxi	H,NETcom
	lxi	B,maxcom
	sub	A
	call	SENDNET	; send lock to master
	call	RECMSG	; get status
	sta	LOCKstat; store status for user
	jmpr	NETret
;----------
; Send a clear-locks request to the master
Nclrlock:
	lxi	H,NETcom
	lxi	B,1
	sub	A
	call	SENDNET	; send clear-lock to 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
	]
	]
	.page
	.ife	MASTopt,[
;----------
; Network Master polling loop
;
; For now, assume that every poll must be answered
; with "pollack", "readNET", or "writeNET".  Also
; users are numbered from 1 to "numusr".
MASTusr:.byte	0	; user being polled now
BOOTusr	==	254	; boot pseudo user number
LOGusr	==	253	; login pseudo user number
boottim:.byte	1	; boot broadcast counter
logtime:.byte	1	; login poll counter
MAST.SP:.word	0	; user's SP
MASTER:
;
; Avoid overwriting the user's stack by setting up
; our own stack.
	push	PSW
	sspd	MAST.SP
	lxi	SP,MASTstack
	push	B
	push	D
	push	H
	.ife	FRONTopt,[
	mvi	A,00000011b ; disable front-panel ints
	out	PIOAC
	]
	call	lockDMA	; reserve the DMA chip
	ei		; allow interrupts immediately
;
; A user can be in one of three states:
;
; (1) Logged out.
; (2) Logged in.
; (3) Waiting to logout.
;
; Every user starts at (1).  State (2) is entered by
; executing the LOGIN command, or by booting up from
; the network.  State (3) is entered when a user fails
; to respond to a poll.  If the user fails to respond
; to 8 consequtive polls, one every 1/2 second, then
; he enters state (1), and his user number can be
; used by someone else.
;
	mvi	A,numusr-1;check users from high to low
nxtusr:
	sta	MASTusr	; store current user number
	call	curUSR	; get current activity counter
	mov	A,M
	ora	A
	jrz	nxtpoll	; activity = 0 if logged out
	inr	A
	jrz	..poll	; activity = 0FFh if logged in
	mov	A,M	; logout if 8 polls in 4 secs
	ani	1Fh	;  have not been answered
	jrnz	..nack
..poll:
	mvi	A,poll	; poll the current user
	call	SENDUSR
	call	RECUSR	; wait for a response
	jrnc	..ack
	call	curUSR	; no response to poll, so ...
..nack:	dcr	M	;  start counting for logout
	.ife	HARDshar,[
	cz	MASTclr	; clear locks if logged out
	]
	jmpr	nxtpoll
..ack:	call	curUSR
	mvi	M,0FFh	; keep user logged in
	cpi	pollack
	jrz	nxtpoll	; check for poll acknowledge
;
; Process HiNet request
	call	savereq	; update entry in WHO table
	cpi	readNET
	jz	Mread	; check for read request
	cpi	writeNET
	jz	Mwrite	; check for write request
	.ife	HARDshar,[
	cpi	assnNET	; check for assign request
	jz	Massign
	cpi	whoNET  ; check for who request
	jz	Mwho
	cpi	hogNET
	jz	Mhog	; check for hog request
	cpi	lockNET	; check for lock request
	jz	Mlock
	cpi	unlockNET; check for unlock request
	jz	Munlock
	cpi	clrlockNET; check for clearlock request
	jz	Mclrlock
	]
	call	NETerr	; illegal request
;
; Poll next user, if any remain
nxtpoll:
	lda	MASTusr
	dcr	A	; decrement user number
	jrnz	nxtusr
	sta	MASTusr	; local user number is zero
;
; See if the local user has anything for us to do
	lda	NETcom	; check local user's command
	ora	A
	jz	clrreq	; jump if nothing to do
	lxi	H,NETcom; get local user's command
	lxi	D,MASTcom
	lxi	B,maxcom
	ldir
	lxi	H,userlist
	call	savereq	; update entry in WHO table
	cpi	readNET	; check for READ command
	jrz	Lread
	cpi	writeNET; check for WRITE command
	jrz	Lwrite
	.ife	HARDshar,[
	cpi	assnNET
	jrz	Lassign
	cpi	loginNET
	jrz	Llogin
	cpi	lockNET
	jrz	Llock
	cpi	unlockNET
	jrz	Lunlock
	cpi	clrlockNET
	jrz	Lclrlock
	]
	call	NETerr	; illegal request
	jmpr	clrreq
Lread:	lhld	cpmDMA  ; read directly into user area
	call	MASTrd 
	jmpr	clrreq
Lwrite:	lhld	cpmDMA  ; write directly from user area
	call	MASTwr
	jmpr	clrreq
	.ife	HARDshar,[
Lassign:lxi	H,MASTnam; point to name and password
	call	MASTas
	sbcd	NETassn	; store assignment value
	sded	NETassn+2
	jmpr	clrreq
Llogin:	lxi	H,BOOTnam
	lxi	D,MASTnam
	lxi	B,14
	ldir
	call	chkusr
	sta	NETassn	; A = 0 if ok
	lxi	H,MASTbuf
	lxi	D,DSK0def
	lxi	B,lendef
	ldir
	call	curUSR
	mvi	B,numusr+1
	call	newuser	; enter name in user table
	jmpr	clrreq
Llock:
	call	MASTlock
	sta	LOCKstat; store status for user
	jmpr	clrreq
Lunlock:
	call	MASTunlock
	sta	LOCKstat; store status for user
	jmpr	clrreq
Lclrlock:
	call	MASTclr
	]
clrreq:
	sub	A	; clear local user's request
	sta	NETcom
;
; Broadcast the bootstrap code once per second
	lxi	H,boottime
	dcr	M	; broadcast if counter = 0
	jrnz	login
	mvi	M,sendBOOT
	lxi	H,NET1strap
	lxi	B,len1strap
	mvi	A,BOOTusr; pseudo user number
	call	SENDNET
;
; See if anyone wants to login
login:
	lxi	H,logtime
	dcr	M	; poll if counter = 0
	jnz	..ret
	mvi	M,sendlog
	mvi	A,poll
	call	SENDLUSR; poll "login" user
	lxi	H,MASTcom
	lxi	B,lenlog
	sub	A
	call	RECNET	; wait for response
	cpi	90h
	jrz	..ack
;
; Nobody (or everybody!) wants to login
       	mvi	A,lognack
	call	SENDLUSR; prospective logins must retry
	jmp	..ret
;
; Check user name and password
..ack:	
	.ife	HARDshar,[
	call	chkusr
	ora	A
	jrnz	..deny
;
; User name ok, so move defaults to NET2strap
	lxi	H,MASTbuf
	lxi	D,DSKSdef
	lxi	B,lendef
	ldir
	]
;
; Find room in the current user list
	lxi	H,userlist
	mvi	B,numusr
	lxi	D,16
..look:	dad	D
	mov	A,M
	ora	A
	jrz	..found	; jump if empty spot found
	djnz	..look
;
; Deny login if name not found or no room in list
..deny:	mvi	A,logdeny
	call	SENDLUSR
	jmpr	..ret
;
; Put user in table, and send number and login time
..found:
	call	newuser	; put user in table
	lxi	H,ticks
	lxi	D,MASTtim
	lxi	B,7
	ldir		; login time
	lxi	H,MASTsnd
	mvi	M,logack
	lxi	B,9
	mvi	A,LOGusr
	call	SENDNET
	mvi	B,20h
..delay:dcr	B	; delay for DSC/4
	jrnz	..delay
;
; Give the user the rest of the bootstrap code
	lxi	H,NET2strap
	lxi	B,len2strap
	lda	MASTnum
	call	SENDNET
;
; All users have been satisfied for now, so delay
; awhile before invoking the MASTER again
..ret:
	sub	A	; unlock the DMA chip
	sta	lockbyte	
	pop	H
	pop	D
	pop	B
	lspd	MAST.SP
	mvi	A,wakeupMAST; delay a bit before
	sta	timeMAST;      calling MASTER again
	.ife	FRONTopt,[
	mvi	A,10000011b ; re-enable front-panel ints
	out	PIOAC
	]
	pop	PSW
	ret		; return to the user
	.ife	HARDshar,[
;----------
; Check user table for name and password
;  Regs out:   A = 0 if name found
;	       A not 0 if name not found
chkusr:	
	lxi	H,MASTnam; move user name to safe place
	lxi	D,usernam
	lxi	B,14
	ldir
	sub	A	; read user table from disk 0
	sta	MASTdsk
	lxi	H,1<8+3	; start at track 3, sector 1
	shld	MASTtrk
;
; Search next sector for match with user name
..nsect:lxi	H,MASTbuf
	call	MASTrd	; read sector
	lxi	H,MASTbuf
..nname:lxi	D,usernam
	lxi	B,14<8	; compare 14 chars
..cmp14:ldax	D
	cmp	M
	jrz	..equal	; jump if chars are equal
	mvi	C,1	; set no-match flag
..equal:inx	H
	inx	D
	djnz	..cmp14	; check all 14 chars
	mov	A,C
	ora	A
	jrz	..match	; if C = 0, then match found
	inx	H
	inx	H	; move to next name in table
	mov	A,L
	cpi	(MASTbuf+128)&0FFh
	jrnz	..nname
;
; No match found, so search next sector of table
	lda	MASTsec
	cpi	16	; search 16 sectors (128 names)
	rz		; deny login if name not found
	inr	A
	sta	MASTsec
	jmpr	..nsect
;
; Get default configuration table from track 4
..match:lda	MASTsec	; A = 1,2,3,...16
	dcr	A
	rlc
	rlc
	rlc
	mov	B,A	; B = 0,8,16,...120
	mov	A,L
	adi	(MASTbuf&0FFh)+2 ; A = 16,32,48,...128
	rrc
	rrc
	rrc
	rrc		; A = 1,2,3,...8
	add	B	; A = 1,2,3,...128
	mov	H,A	; read from sector 1,2,3,...128
	mvi	L,4	; track 4
	shld	MASTtrk
	lxi	H,MASTbuf
	call	MASTrd	; get default configuration
	sub	A	; A = 0 means name OK
	ret
usernam:.blkb	14
	]
;----------
; Assign the user a position in the user table
;  Regs in:  HL = pointer to available table entry
;	     B  = "inverse" of user number
newuser:
	mvi	M,0FFh	; log 'im
	mvi	A,numusr+1
	sub	B	; compute user number
	sta	MASTnum
	.ife	HARDshar,[
	inx	H
	xchg
	lxi	H,usernam
	lxi	B,8
	ldir		; save user name
	lxi	H,secs
	lxi	B,3
	ldir		; save login time
	]
	ret
;----------
; Save time of most recent HiNet request
;  Regs in:  HL = pointer to user table entry
savereq:
	.ife	HARDshar,[
	lxi	D,12	; point to time in WHO table
	dad	D
	xchg
	lxi	H,secs	; get time from low core
	lxi	B,3	; save secs, mins, hrs
	ldir
	stax	D	; store request for WHO command
	]
	ret
;----------
; Process a read request
Mread:
	lxi	H,MASTbuf
	call	MASTrd	; read sector from disk
..1:	lxi	H,MASTbuf
	lbcd	MASTbyt
	lda	MASTusr
	call	SENDNET	; send data to user
	call	RECUSR	; receive ack from user
	cc	NETerr
	cpi	nack
	jrz	..1
	cpi	datack
	cnz	NETerr
	jmp	nxtpoll
;----------
; Process a write request
Mwrite:
	mvi	A,mesack
	call	SENDUSR	; ack reception of command
..1:	lxi	H,MASTbuf
	lbcd	MASTbyt
	sub	A
	call	RECNET	; receive data from user
	bit	7,A
	cz	NETerr
	ani	60h	; if CRC or overrun error,
	jrz	..2	;  then ask for re-transmit
	mvi	A,nack
	call	SENDUSR
	jmpr	..1
..2:	mvi	A,datack
	call	SENDUSR	; ack data
	lxi	H,MASTbuf
	call	MASTwr	; write the data to the disk
	jmp	nxtpoll
;----------
; Process a hog request
	.ife	HARDshar,[
Mhog:
	mvi	A,hogack; tell user he can hog the net
	call	SENDUSR	; for at most 1/60th sec
	call	RECUSR
	cc	NETerr	; error if user has pigged out
	jmp	nxtpoll	; resume polling the next user
;----------
; Process an assign request
Massign:
	lxi	H,MASTnam
	call	MASTas	; let MASTas do the work
	lxi	H,HARDstat
	lxi	B,4
	lda	MASTusr	
	call	SENDNET	; send results to user
	jmp	nxtpoll
;----------
; Process a who request
Mwho:	
	lxi	H,userlist
	lxi	B,numusr*16
	lda	MASTusr
	call	SENDNET
	jmp	nxtpoll
;----------
; Process a lock request
Mlock:
	call	MASTlock
	call	SENDUSR	; send status to user
	jmp	nxtpoll
;----------
; Process an unlock request
Munlock:
	call	MASTunlock
	call	SENDUSR	; send status to user
	jmp	nxtpoll
;----------
; Process a clear-lock request
Mclrlock:
	call	MASTclr
	jmp	nxtpoll
	]
;----------
; Master disk read
;  Regs in:   HL = buffer address
;  Regs out:  none
;  Destroyed: A, BC, DE, HL
MASTrd:
	.ife	HARDshar,[
	lda	MASTdsk	; unit number
	lded	MASTtrk	; track and sector number
	jmp	HARDr	; let HARDr do the rest
	]
	.ife	FLOPshar,[
	call	saveMAST
	call	DDrd	; read double-density
	jmpr	restMAST
	]
;----------
; Master disk write
;  Regs in:   HL = buffer address
;  Regs out:  none
;  Destroyed: A, BC, DE, HL
MASTwr:
	.ife	HARDshar,[
	lda	MASTdsk	; unit number
	lded	MASTtrk	; track and sector number
	jmp	HARDw	; let HARDw do the rest
	]
	.ife	FLOPshar,[
	call	saveMAST
	call	DDwr
	jmpr	restMAST
;	
; Save master user's parameters
saveMAST:
	shld	MASTdma	; store buffer address
	lxi	H,cpmDESC
	lxi	D,saveDESC
	lxi	B,lencom
	ldir		; save master user's parameters
	lxi	H,mastDESC
	lxi	D,cpmDESC
	lxi	B,lencom
	ldir		; replace with slave's params
	ret
;
; Restore master user's parameters
restMAST:
	lxi	H,saveDESC
	lxi	D,cpmDESC
	lxi	B,lencom
	ldir
	ret
saveDESC:.blkb	lencom
	]
;----------
; Assign hard disk partition
;  Regs in:   HL = address of unit name and password
;  Regs out:  BC = unit and size
;	      DE = control byte
	.ife	HARDshar,[
MASTas:
	push	H	; save address of name
	mvi	A,assnHARD
	call	CMDhard	; send command to hard disk
	pop	H
	mvi	B,14
	call	SENDHARD; send name and password
	call	RESHARD	; get disk assignment
	lbcd	HARDstat; B = unit, C = size
	lded	HARDstat+2
	ret
;----------
; Process a network lock request
;  Regs out: A = lock status
MASTlock:
	call	LOOKlock; search table for lock
	ora	A
	jrnz	..newlock; jump if not found
	mvi	A,lockdeny; found, so deny the request
	ret
..newlock:
	lxi	H,locklist; check free space in table
	lxi	D,16
	mvi	B,numlock
..look:	mov	A,M	; see if entry in use
	cpi	0FFh
	jrz	..store	; jump if its available
	dad	D
	djnz	..look
	mvi	A,locknack; no space available
	ret
..store:
	lda	MASTusr
	mov	M,A	; save user number
	inx	H
	lxi	D,MASTcom+1
	lxi	B,lenlock
	xchg
	ldir		; move lock to locklist
	mvi	A,lockack; acknowledge
	ret
;----------
; Process an unlock request
;  Regs out: A = lock status
MASTunlock:
	call	LOOKlock
	ora	A
	jrz	..oldlock; jump if lock found
	mvi	A,locknack; lock not found, so error
	ret
..oldlock:
	mvi	M,0FFh	; clear the lock
	mvi	A,lockack
	ret
;----------
; Search the lock table for match with lock string
;  Regs out: A  = 0    if match found
;	     A  = 0FFh if not match found
;	     HL = address of match
LOOKlock:
;
; Check for lock string too short or too long
	lda	MASTcom+1
	ora	A	; error if length = 0
	jrz	..error
	cpi	lenlock+1; error if length > lenlock
	jrc	..ok
..error:pop	H	; pop return address
	mvi	A,locknack
	ret
;
; Fill lock string with blanks
..ok:	lxi	H,MASTcom+2
	lxi	D,MASTcom+1
	mvi	B,0
	mov	C,A	
	ldir		; shift to left by one
	mov	C,A
	mvi	A,lenlock
	sub	C
	jrz	..start
	xchg
..bfill:mvi	M,' '
	inx	H
	dcr	A
	jrnz	..bfill
;
; Start searching at the beginning of the locklist
..start:lxi	H,locklist
	mvi	C,numlock
;
; Check whether entry is in use
..look:	mvi	B,lenlock
	mov	A,M	; see if entry is in use
	inx	H
	cpi	0FFh
	jrz	..noteq	; jump if not in use
	lxi	D,MASTcom+1
;
; Compare strings
..cmp:	ldax	D	; compare lock string
	cmp	M	; with table entry
	jrnz	..noteq
	inx	H
	inx	D
	djnz	..cmp
;
; Match found, so compute address of match and return
	lxi	H,locklist+numlock*16
	slar	C
	slar	C
	slar	C
	slar	C
	ralr	B	; multiply BC by 16
	dsbc	B	; compute address of match
	sub	A	; match found
	ret
;
; Match not found, so continue search to end of table
..noteq:mvi	D,0
	mov	E,B
	inx	D
	inx	D
	dad	D	; compute address of next entry
	dcr	C
	jrnz	..look
	mvi	A,0FFh	; match not found
	ret
;----------
; Clear current user's locks from locklist
MASTclr:
	lda	MASTusr
	lxi	H,locklist
	lxi	D,16
	mvi	B,numlock
..loop:	cmp	M	; check for match with userno
	jrnz	..repeat
	mvi	M,0FFh	; if match, then delete entry
..repeat:dad	D
	djnz	..loop	; check all 16 entries
	ret
	]
;---------
; Send a message to a network user
;  Regs in:   A = message to be sent
;  Regs out:  none
;  Destroyed: A, BC, DE, HL
SENDUSR:
	lxi	H,MASTsnd
	mov	M,A
	lxi	B,1
	lda	MASTusr
	jmp	SENDNET
;----------
; Send a message to the user who is trying to login
;  Regs in:   A = message to be sent
;  Regs out:  none
;  Destroyed: A, BC, DE, HL
SENDLUSR:
	lxi	H,MASTsnd
	mov	M,A
	lxi	B,1
	mvi	A,LOGusr
	jmp	SENDNET
;----------
; Receive a message from a network user
;  Regs in:   none
;  Regs out:  A  = message received
;	      carry flag set if user timed-out
;  Destroyed: A, BC, DE, HL
RECUSR:
	lxi	H,MASTcom
	lxi	B,maxcom
	sub	A
	call	RECNET
	stc		; set carry if time-out
	bit	7,A	; check for time-out
	jrz	..1
	cmc		; clear carry if no time-out
..1:	lda	MASTcom
	ret
;----------
; Get the user list pointer for the current user
;  Regs in:   none
;  Regs out:  HL = pointer to activity counter
;  Destroyed: DE
curUSR:
	lxi	D,userlist
	lhld	MASTusr	; L = current user number
	mvi	H,0
	dad	H	; multiply HL by 16
	dad	H
	dad	H
	dad	H
	dad	D	; compute address of activity
	ret
;----------
; Handle a network error - print an error message;
; then resume the MASTER by polling the next user.
Err1:	.asciz	'*** User '
Err2: 	.asciz	' error'[cr][lf]
NETerr:
	pop	H	; throw away caller's address
	lxi	H,Err1
	call	PRTMSG	; print beginning of message
	lda	MASTusr
	adi	'0'
	mov	C,A
	call	CONforce; print user number
	lxi	H,Err2
	call	PRTMSG	; print end of message
	jmp	nxtpoll	; continue polling next user
	.page
;----------
; Network bootstrap code - phase 2
;
; Read cold boot code and BIOS into low core;
; then move them to high core and execute cold boot.
;
lendef	==	33	; length of default config info
off2set	==	.-TPA
NET2strap:
DSKSdef:.blkb	lendef	; default disk configuration
..cboot:call	RECMSG
	cpi	poll	; wait for poll
	jrnz	..cboot
	lxi	H,BOOTrd-off2set
	lxi	B,lencom
	sub	A
	call	SENDNET	; send read request to master
;
; Read CBOOT and BIOS backwards, 128 bytes at a time
	lhld	BOOTadr-off2set
	lxi	B,128
	ora	A
	dsbc	B
	shld	BOOTadr-off2set
        lda	NETusr
	call	RECNET
;
; Read next set of bytes if needed
	mvi	A,datack
	call	SENDMSG
	lxi	H,BOOTsec-off2set
	dcr	M
	lhld	BOOTadr-off2set
	lded	BIOSbas-8000h
	res	7,D	; clear high bit of address
	ora	A
	dsbc	D
	jrnz	..cboot
;
; Relocate CBOOT and BIOS to high core
	lhld	BOOTadr-off2set
	lded	BIOSbas-8000h
..reloc:mov	A,M	; source
	stax	D	; destination
	inx	H
	inx	D
	mov	A,D
	ora	E
	jrnz	..reloc
;
; Jump to the cold boot code (its right below the BIOS)
	lhld	BIOSbas
	pchl
;----------
; Boot commands
BOOTrd: .byte	readNET	; read command
	.byte	0	; not used
	.ife	HARDshar,[
	.byte	0	; disk
	.byte	2	; track
BOOTsec:.byte	128	; sector
	]
	.ife	FLOPshar,[
	.byte	1	; disk
	.byte	1	; track
BOOTsec:.byte	52	; sector
	]
	.word	128	; byte count
BOOTadr:.word	8000h	; BIOS load address
;----------
; Network bootstrap code - phase 1
;
; This program is broadcast on the network
; once per second.  To log in, a slave must load
; this program into location 9000h, and execute it.
off1set	==	.-9000h	; bootstrap executes at 9000h
len1strap==	380h	; length expected by PROM
len2strap==	.-NET2strap
NET1strap:
;
; Setup DSC/4 memmap, keep pages 0 and 9 in local mem
	lixd	3FEh	; get serial number
	lxi	H,memmap-off1set
	lxi	B,0<8+SETMAP
..map:	outi
	mvi	A,11h
	add	B
	mov	B,A
	jrnz	..map
	in	ONMAP	; activate DSC/4 memory map
	in	OFFPROM	; disable DSC/3 PROM
;
; Move remainder of boot code to high core
	lxi	H,9000h
	lxi	D,NET1strap
	lxi	B,len1strap
	ldir
	jmp	BOOT	; execute rest of bootstrap
;
; DSC/4 memory map
memmap:	.byte	0FFh, 0F0h, 0F1h, 0F2h ; pages F,0,1,2
	.byte	0F3h, 0F4h, 0F5h, 0F6h ; pages 3,4,5,6
	.byte	0F7h, 0F8h, 000h, 0FAh ; pages 7,8,9,A
	.byte	0FBh, 0FCh, 0FDh, 0FEh ; pages B,C,D,E
HiNETmsg:
	.ascii	[1Bh][34h][0Ch][cr][lf]'HiNet '
	.byte	version+'0','.',revision/100+'0'
	.byte	(revision@100)/10+'0'
	.byte	(revision@100)@10+'0'
	.ife	HARDshar,[
	.asciz	[cr][lf][lf]'Login please ...'
NAMEmsg:.asciz	[cr][lf]'  Name: '
PSWmsg:	.asciz	[cr][lf]'  Password: '
	]
	.ife	FLOPshar,[.byte 0]
;----------
; Relocated bootstrap code
BOOT:
	lxi	B,90h<8+SETMAP
	mvi	A,0F9h
	outp	A
	lxi	SP,80h	; setup a stack
	mvi	A,(vectors>8)&0FFh
	stai		; setup interrupt register
	im2
	mvi	A,45h	; set counter mode
	out	CTC1
	mvi	A,2	; 2 = 1Mhz/baudrate
	out	CTC1
;
; Tell the user that the HiNet is up
	lxi	H,HiNETmsg
	call	outstr
..askname:
	.ife	HARDshar,[call askname]
;
; Wait for poll of user 253; then request login
..poll:	lxi	H,NETusr; store user number at 47h
	mvi	M,LOGusr
	call	RECMSG
	cpi	poll
	jrnz	..poll
	lxi	H,BOOTcom
	mvi	M,loginNET
	lxi	B,lenlog
	sub	A
	call	SENDNET
;
; Get response to login request
	lxi	H,BOOTcom
	lxi	B,9
	mvi	A,LOGusr
	call	RECNET
	lda	BOOTcom
	cpi	logack
	jrz	..login	; good news - we've logged in
	cpi	lognack
	jrz	..delay	; bad news - didn't get through
	mvi	C,bell
	call	outchr	; worst news - bad name or psw
	jmpr	..askname
;
; Delay random period of time, then retry
..delay:ldar		; use refresh register
	ani	7	; (its a good random seed)
	mov	B,A
..outer:lxi	H,0	; delay about 400ms per pass
..inner:dcx	H
	mov	A,H
	ora	L
	jrnz	..inner
	djnz	..outer	; low 3 bits of R = pass count
	jmpr	..poll
;
; Get user number and current time
..login:lda	BOOTnum	; get user number
	sta	NETusr	; store it for BIOS
	lxi	H,BOOTclk; store current time
	lxi	D,ticks
	lxi	B,7
	ldir
;
; Get the rest of the bootstrap code
	lxi	H,TPA
	lxi	B,len2strap
	call	RECNET
	cpi	90h
	jrnz	..poll
	jmp	TPA+lendef
;----------
; Output a character to the console
;  Regs in:  C = character
outchr: in	SIO1AC
	ani	1<TxRDY
	jrz	outchr
	mov	A,C
	out	SIO1AD
	ret
;----------
; Output a string to the console
;  Regs in:   HL = pointer to string
outstr:	mov	A,M
	ora	A	; string ended by zero byte
	rz
	mov	C,A
	call	outchr
	inx	H
	jmpr	outstr
	.ife	HARDshar,[
;----------
; Ask for user name and password
askname:
	lxi	H,BOOTnam
	mvi	M,' '	; fill name and psw with blanks
	lxi	D,BOOTnam+1
	lxi	B,13
	ldir
	lxi	H,NAMEmsg
	call	outstr	; ask for name
	lxi	H,BOOTnam
	mvi	E,1	; echo chars
	call	instr	; get name
	lxi	H,PSWmsg
	call	outstr
	lxi	H,BOOTpsw
	mvi	E,0	; don't echo chars
	call	instr	; get password
	mvi	C,cr
	call	outchr
	mvi	C,lf
	jmpr	outchr
;----------
; Input a string from the console
;  Regs in:   HL = pointer to string
;	      E  = 0 means don't echo string
;		   1 means echo string
instr:
	mov	D,L	; remember start of string
..wait:	in	SIO1AC
	ani	1<RxRDY
	jrz	..wait
	in	SIO1AD
	ani	7Fh	; mask out parity bit
	cpi	cr
	rz
	mov	C,A	; save the char
	bit	0,E
	jrz	..save	; jump to avoid echo
	cpi	rubout
	jrz	..back
	cpi	backsp
	jrnz	..echo	; jump to echo
..back:	mov	A,D
	cmp	L
	jrz	..wait	; don't backspace too far
	dcx	H
	mvi	M,' '	; overwrite char with blank
	mvi	C,backsp
	call	outchr	; backspace
	mvi	C,' '
	call	outchr	; space over previous char
	mvi	C,backsp
	call	outchr	; backspace
	jmpr	..wait
;
..echo:	call	outchr	; echo the char
..save:	mov	A,C
	cpi	'a'	; convert lower to upper case
	jm	..store
	cpi	'z'+1
	jp	..store
	sui	'a'-'A'
..store:mov	M,A
	inx	H
	jmpr	..wait
	]
	]		; end of MASTER code
	.page
	.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
	.page
;----------
; 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
	.ife	MASTopt,[ mvi A,fastrec]
	.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
	.page
;----------
; 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
	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
	ei
	ret
;----------
; SDLC receive interrupt on every char
RECflush:
	push	PSW
	in	SIO1BD	; flush this char away
	pop	PSW
	ei
	reti
	.page
;----------
; 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
;------------------------------------------------
; Network protocols:
;
;		MASTER	   SLAVE
;               ------     -----                 
; Poll:   	poll   --> 
;		       <-- pollack
; -----------------------------------------------
; Write:	poll   -->
;		       <-- writeNET
;		mesack -->
;		       <-- DATA
;		datack -->
;		(nack)
; -----------------------------------------------
; Read:		poll   -->
;		       <-- readNET
;		DATA   -->
;		       <-- datack
;			   (nack)
; -----------------------------------------------
; Login:	poll   -->
;		       <-- loginNET
;		logack -->
;	       (logdeny)
; -----------------------------------------------
; Assign:       poll   -->
;		       <-- assignNET
;		DATA   -->
;------------------------------------------------
; Hog:		poll   -->
;		       <-- hogNET
;		hogack -->
;	       (hogdeny).
;		       <-- pollack
;------------------------------------------------
	.page
;----------
; 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
	]
	.page
	.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
	.ife	FLOPopt,[
	mov	A,M
	ora	A
	jrz	..1
	dcr	M
	jrnz	..1
	lda	lockbyte; delay off motor until
	ora	A	; DMA chip is available
	jrz	..offm
	inr	M	; wait another clock tick
	jmpr	..1
..offm: call	offMOTOR; also flush buffer
	lxi	H,timeMOTOR; restore HL
..1:	inx	H
	]
;
; 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
	]
;
; Invoke the network master every 1/60 second
	.ife	MASTopt,[
	mov	A,M
	ora	A
	jrz	..3
	dcr	M
	jrnz	..3
	pop	PSW	; restore user's PSW
	lxi	H,MASTER
	push	H
	jmpr	..ret	; jump to network master
	]
;
; 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	FLOPopt, [timeMOTOR: .byte 0]
	.ife	NETopt,  [timeREC:   .byte 0]
	.ife	MASTopt, [timeMAST:  .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:
	in	PIOAD	; read interrupt status
	ani	80h	; if not high, then must be HLT
	jrz	HALTint
	lxi	H,..1
	push	H
	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
	ei
	reti
	]
	.page
;----------
; 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
	.page
;----------
; 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:
;
; (Used only by FDTEST right now)
	.ife	FLOPboot,[
	lda	retries
	ora	A
	jrnz	prterr  ; if not zero, ask the user
	lxi	D,errtab-4
	dsbc	D	; compute offset from "errtab"
	mov	A,L
	rrc		; divide by 2
	ret		; return 3, 6, 9, ...
	]
;
; 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	FLOPopt,[
	.word	SEEKerr	; equipment check
	.ascii	'SEEK'
	.word	SYNCerr	; FDC wants to send, not receiv
	.ascii	'SYNC'
	.word	DATAerr	; DATA CRC error
	.ascii	'DATA'
	.word	TRACerr	; head positioned on wrong trck
	.ascii	'TRAC'
	.word	ENDTerr	; access beyond end-of-track
	.ascii	'ENDT'
	.word	IDerr	; ID CRC error
	.ascii	' ID '
	.word	ORUNerr	; FDC not serviced in time
	.ascii	'ORUN'
	.word	SECTerr	; sector cannot be found
	.ascii	'SECT'
	.word	PROTerr	; disk write-protected
	.ascii	'PROT'
	.word	DENSerr	; missing ID address mark
	.ascii	'DENS'
	.word	MADRerr ; missing DATA address mark
	.ascii	'MADR'
	]
	.ife	HARDopt,[
	.word	HARDerr	; hard disk error
	.ascii	'HARD'
	]
	.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:
	]
	.ifn	FLOPshar,[
	cpi	0FFh	; check for boot disk
	jrnz	map2
	lda	DSKBOOT	; return map byte for boot disk
	ret
map2:
	]
	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
	.page
;----------
; 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	FLOPboot,[ .byte DD+0  ]
	.ife	HARDboot,[ .byte HARD+0]
	.ife	HARDshar,[ .byte NET+0 ]
	.ife	FLOPshar,[ .byte 0     ]; not used
DSKMAPº	
	.ifå	FLOPboot,[
; (floppy system)  (1),(2),(3),(4),(5),(6),(7),(8),(9)
DSK0:	.byte	DD  +0, 52,127,  4, 15,242,0C0h, 2, 0Ch
DSK1:	.byte	DD  +1, 52,127,  4, 15,242,0C0h, 2, 0Ch
DSK2:	.byte	SD  +1, 26, 63,  3,  7,242,0C0h, 2, 4Eh
DSK3:	.byte	DD  +1, 52,127,  4, 15,242,0C0h, 2, 0Ch
	]
	.ife	HARDboot,[
; (hard system)    (1),(2),(3),(4),(5),(6),(7),(8),(9)
DSK0:	.byte	HARD+1,128, 63,  3,  7,255,0C0h, 0, 0Ch
DSK1:	.byte	HARD+1,128, 63,  3,  7,255,0C0h, 0, 0Ch
DSK2:	.byte	DD  +0, 52,127,  4, 15,242,0C0h, 2, 0Ch
DSK3:	.byte	DD  +1, 52,127,  4, 15,242,0C0h, 2, 0Ch
	]
	.ife	HARDshar,[
; (hard network)   (1),(2),(3),(4),(5),(6),(7),(8),(9)
DSK0:	.byte	NET +1,128, 63,  3,  7,255,0C0h, 0, 0Ch
DSK1:	.byte	NET +1,128, 63,  3,  7,255,0C0h, 0, 0Ch
DSK2:	.byte	NET +1,128, 63,  3,  7,255,0C0h, 0, 0Ch
DSK3:	.byte	NET +1,128, 63,  3,  7,255,0C0h, 0, 0Ch
	]
	.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
	]
	.ifn	FLOPshar,[
DSKnam:	.ascii	'unit 01 unit 01 unit 01 unit 01  ']
;----------
; Network spooler or user printer driver
PORTNout:jmp	CALLerr
.end
