
	.title	'DSC/3, DSC/4 HiNet and CP/M BIOS'
	.sbttl	'HiNet Test Master Bios'
version	=	1	; CP/M version number
revision=	437	; DMS revision number
patch	=	'C'	; Patch ID (normally blank)
;
;----------
;
;  	      Network system
;	      --------------
; Network master	     Network station
; --------------	     ---------------
; A>ddt cpm2D2.com	     A>ddt cpm2E1.com
; -ma00 2000 100	     -ma00 2000 100
; -imast2.hex		     -islave2.hex
; -r4D00		     -r3A00
; -^C			     -^C
; A>save 76 master2.com	     A>save 57 station2.com
; A>wrun0 master2.com 5 69   A>wrun0 station2.com 2 0F
; A>wrun0 mastboot.com 2 1
;----------
;----------
; CP/M 2.2 conditional assembly options:
;
version	=	2		; new version number
revision=	revision-200	; new revision number
;
;
NETboot	==	0	
MASTopt	==	0	
FLOPopt ==	0	
NETopt  ==	0	
HARDshar==	0
HARDopt	==	0
;----------
; Misc. conditional assembly options (0 to select):
;
AHEADopt==	0	; console type-ahead
TIMERopt==	0	; real-time clock interrupts
;
	.pabs
	.phex
	.page
;----------
; Addresses of BIOS, BDOS, CCP, and important tables:
lenBIOS	=	800h	; length of BIOS kernal
lenBDOS	==	0E00h	; length of BDOS
lenCCP	==	800h	; length of CCP
;
; Extra mem for alloc and check vects (CP/M 2.2 only)
	.ife	NETboot,      [lenBIOS = lenBIOS+600h]
; Extra mem for disk device drivers
Š	.ife	FLOPopt,      [lenBIOS = lenBIOS+600h]
	.ife	HARDopt,      [lenBIOS = lenBIOS+200h]
	.ife	NETopt,       [lenBIOS = lenBIOS+400h]
			       lenBIOS = lenBIOS+100h
	.ife	MASTop!HARDsh,[lenBIOS = lenBIOS+1300h]
;
; Extra mem for cold boot code
	.ife	MASTopt,      [lenBOOT = 800h]
;
; Important CP/M addresses
lenCPM	==	lenCCP+lenBDOS
BIOS	==	64*1024-lenBIOS	; address of BIOS
BDOS	==	BIOS-lenBDOS+6	; address of BDOS
CPM	==	BIOS-lenCPM	; address of CCP
TPA	==	100h	; transient program area
wrBUFF	==	0FF00h	; floppy write buffer
rdBUFF	==	0FE00h	; floppy read buffer
netBUFF ==	0FA00h	; network read buffer
;
; Table addresses for hard disk network master
.ife	MASTopt!HARDshar,[numusr   == 32
		  	  USERlist == 0FC00h
		  	  numlock  == 32
		  	  LOCKlist == 0FA00h
		  	  MASTbuf  == 0F600h
			  SPOOLlist== 0F500h
		  	  MASTstac == 0F500h]
;----------
; Important constants
;
; Control characters
cntlC	==	03h	; <control-C>
cntlP	==	10h	; <control-P>
cntlQ	==	11h	; <control-Q>
cntlS	==	13h	; <control-S>
cntlZ	==	1Ah	; <control-Z>
cr	==	0Dh	; <return>
lf	==	0Ah	; <linefeed>
bell	==	07h	; <bell>
backsp	==	08h	; <backspace>
rubout	==	7Fh	; <rubout>
;----------
; Device types
SD	==	0<6	; single density map
DD	==	1<6	; double density map
HARD	==	2<6	; hard disk map
NET	==	3<6	; network map
;----------
; Transmit and receive bits
RxRDY	==	0	; receiver ready bit
TxRDY	==	2	; transmitter ready bit
;----------
; Floppy-related constants
RETRY	==	10	; number of I/O error retries
onprecmp==	22	; track to turn on part precomp
maxprec	==	59	; track to turn on full precomp
Š;----------
; Timing constants
loadset	==	1E00h	; head load settle time (50 ms)
stepset	==	900h	; head step settle time (15 ms)
unloadHEAD ==   62	; unload head if inactive 1 sec
$halfms ==	50h	; 1/2 ms delay in SENDNET
$1ms	==	10h	; 1ms delay in RECNET
$16ms	==	100h	; 16ms delay in RECNET
$1sec	==	4000h	; 1sec delay in RECNET
$4sec	==	0	; 4sec delay in RECNET
wakeup  ==  	1	; wakeup net master this often
sendboot==      62	; freq of boot broadcast
sendlog	==	20	; 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     /
month	==	44h	; month    /
day	==	45h	; day     /  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
Mode	==	4Eh	; system mode bits
errors	==	4Fh	; I/O errors since cold boot
;----------
; BIOS high-memory contents:
	.loc	0FFF0h
DEFmode:.byte	1	; default mode bits
FLOPmdl:.byte	0	; 0 for Shugart 800, 1 for 850
hertz:	.byte	62	; clock hertz rate
baud1:	.byte	45h,2	; port 1 default 500K baud
baud23: .byte	45h,13	; ports 2,3 default 9600 baud
IOBYTE:	.byte	54h	; default value of IOBYTE
COLDbas:
	.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
;----------
; Mode bits
;
; Single user, floppy boot only
modeTRY	==	0	; 0 = dont retry floppy errors
			; 1 = retry floppy errors
;
; HiNet Master only
modeMSG	==	1	; 0 = print USER error messages
			; 1 = dont print USER messages
modeSPL	==	2	; 0 = automatic spool mode
			; 1 = manual spool mode
ŠmodePRT ==	3	; 0 = spool on serial printer
			; 1 = spool on parallel printer
modeWAIT==	4	; 0 = ready when spooling done
			; 1 = wait when spooling done
	.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
PPSTROBE==	00h	; Parallel port 2 strobe
;----------
; 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
	jmp	LISTST	; list status
	jmp	SECTRAN	; sector translate
;----------
Š; Interrupt vectors
	.loc	BIOS+40h ; vectors stored at fixed loc.
vectors:
SIO1vect:
	.word	INTerr	; port 1
	.word	INTerr	
	.ife	NETopt,[.word RECfirst; SDLC first char
			.word REClast ; SDLC last char]
	.ifn	NETopt,[.word INTerr
			.word INTerr]
	.word	INTerr	; port 0
	.word	INTerr
	.ife	AHEADo,[.word PORTint ; receiver ready
			.word PORTint ; parity error]
	.ifn	AHEADo,[.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:
	.word	INTerr
;----------
; Non-standard jump vectors:
	.loc	BIOS+5Dh ; vectors stored at fixed loc.
;----------
; For network system:
	.ife	NETopt,[
	jmp	clrDDbuf; flush floppy write buffer
	jmp	NETlock	; HiNet lock
	jmp	CPMMAP	; logical to physical map
	jmp	NETunloc; HiNet unlock
	jmp	SETBYT	; set I/O byte count
	rst	0	; --- not used ---
	.word	NETmsg	; point to net command
			; (used by ASSIGN)
	jmp	SENDnet	; send a block on the net
	jmp	RECnet	; receive a block from the net
LOCALtime:	jmp	INTERUPT ; do local timer int
	jmp	PORTUout; user printer driver
	]
;----------
;----------
; Type-ahead buffer
	.ife	AHEADopt,[
typeahead ==	31	; type-ahead length
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
read1HARD==	15h	; read 1K
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
NETdtn:	.byte	0	; destination
NETsrc:	.byte	0	; source
NETdesc:
NETdsk:	.byte	0	; drive number
NETtrk:	.word	0	; track number
NETsec:	.byte	0	; sector number
NETdma:	.word	0	; DMA address
	.loc	NETcom+1
NETnam:	.blkb	8	; unit name
NETpsw:	.blkb	6	; unit password
NETassn	==	NETnam	; put unit assignment here
lencom	==	15	; length of HiNet command
	]
;----------
; More interrupt vectors
SIO2vect:
	.word	INTerr	; port 3
	.word	INTerr
	.word	INTerr
	.word	INTerr
	.word 	INTerr
	.word   INTerr
	.word	INTerr
	.word	INTerr
;----------
; Network master command and status
	.ife	MASTopt,[
MASTsnd:.byte	0	; message sent to user
MASTcom:.byte	0	; command or message from user
MASTdtn:.byte	0	; destination
MASTsrc:.byte	0	; source
MASTdesc:
MASTdsk:.byte	0	; disk
MASTtrk:.word	0	; track
MASTsec:.byte	0	; sector
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 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)
	]
;----------
; Current CP/M information
cpmDESC:	
cpmDSK:	.byte	0FFh	; disk   from CPM
cpmTRK:	.word	0	; track  from CPM
cpmSEC:	.byte	0	; sector from CPM
cpmDMA: .word	80h	; DMA address from CPM
cpmBYT:	.word	80h	; LENGTH of transfer from CPM
;----------
; The cold boot code is stored in one of two places,
; depending upon the boot device:
;
; If booting a floppy disk system or a floppy network
; master, the cold boot code is overlaid with the
; floppy I/O buffers.
;
; If booting a hard disk system or a network system
; the cold boot code is located below the BIOS.
;
	.ife	NETboot, [.loc BIOS-lenBOOT]
;----------
; Prepare for interrupts
CBOOT:
	di
	lxi	SP,80h
	mvi	A,(vectors>8)&0FFh
	stai		; setup interrupt register
	im2
;----------
; 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
	lda	DEFmode	
	sta	Mode	; default mode bits
	lda	hertz
	sta	ticsec	; ticks per second
;----------
; Initialize timer and network user number
	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
	call	clrsbuf	; clear status buffer
;----------
; Print CP/M message
	lxi	H,CPMmsg
	call	PRTMSG
;----------
; Initialize port 1
	lxi	H,baud1
	lxi	B,2<8+CTC1
	outir		; set baud rate for port 1
	lda	baud1+1
	cpi	2	; if 500Khz, then RS-422
	jrz	..port2	; so don't initialize RS-232
	lxi	H,rs232
	lxi	B,rs232$<8+SIO1BC
	outir		; program SIO for port 1
;----------
; Initialize port 2
..port2:
	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
;----------
; Initialize SIO interrupt vectors
	mvi	A,2
	out	SIO1BC
	mvi	A,SIO1vect&0FFh
	out	SIO1BC	; setup SIO-1 vectors
	mvi	A,2
	out	SIO2BC
	mvi	A,SIO2vect&0FFh
Š	out	SIO2BC	; setup SIO-2 vectors
;----------
; Specify floppy seek rate
	.ife	FLOPopt,[
	in	FLOPSR
	cpi	80h	; check whether floppy exists
	jrnz	..noflop; skip specify if no floppy
	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
..noflop:
	]
;----------
; If timer-option has been selected, enable
; real-time interrupts
	.ife	TIMERopt,[
	lda	hertz
	cpi	62
	jrz	..62hz
;
; External user-selectable hertz rate
	mvi	A,0C5h	; enable real-time interrupts
	out	CTC3
	mvi	A,1	; interrupt at power hertz rate
	out	CTC3
	jmpr	..ok
;
; Internal fixed hertz rate (62 hertz)
..62hz: mvi	A,0A5h	; enable real-time interrupts
	out	CTC3
	mvi	A,252	; interrupt at 4Mhz/(256*252)
	out	CTC3
..ok:	mvi	A,CTCvect&0FFh ; interrupt vector
	out	CTC0	; must send to channel zero
	]
;----------
; If type-ahead option selected, enable receiver ints
	.ife	AHEADopt,[
	mvi	A,11h	; write SIO register 1
	out	SIO1AC
	mvi	A,00011000b ; enable receiver interrupt
	out	SIO1AC
	mvi	A,11h	; write to channel B
	out	SIO1BC
	mvi	A,00000100b ; status affects vector
	out	SIO1BC
	]
	ei		; interrupts OK now
	.page
;-----------------------------------------------------
;  M A S T E R   I N I T I A L I Z A T I O N
;-----------------------------------------------------
; If master option has been selected, clear various
; tables, then wait for local user to login
Š	.ife	MASTopt,[
;
; Restart master if front-panel int during cold boot
	mvi	A,0C3h
	sta	30h	; setup jump to goMAST
	lxi	H,goMAST
	shld	31h
;
; Clear user name list
	lxi	D,USERlist
	mvi	B,numusr
..init:
	push	B
	lxi	H,inituser
	lxi	B,16
	ldir
	pop	B
	djnz	..init
	sub	A	
	sta	userlist; dont touch local user
	sta	NETusr
;
; Clear lock list
	.ife	HARDshar,[
	lxi	H,locklist
	mvi	M,0FFh
	lxi	D,locklist+1
	lxi	B,16*numlock-1
	ldir
;
; Clear spool list
	lxi	H,SPOOLlist
	mvi	M,0E5h
	lxi	D,SPOOLlist+1
	lxi	B,16*lenSPOOL
	ldir
;----------
; It's OK to wake the master now
goMAST:
	lxi	SP,80h
	mvi	A,wakeupMAST
	sta	timeMAST
	sub	A
	sta	upMAST
;
; Ask for date
	.ife	HARDshar,[
..daterr:
	lxi	H,DATEmsg
	call	PRTMSG
	lxi	H,BOOTdate
	mvi	M,cr
	mvi	E,1	; echo string
	call	INMSG
Š	mvi	M,cr
	lxi	H,BOOTdate
	mov	A,M
	cpi	cr	; abort if RETURN
	jz	ASKlog
	call	getnum	; get month
	cpi	1
	jrc	..daterr
	cpi	13
	jrnc	..daterr
	sta	BOOTdate+month
	mov	A,M	; check delimiter
	cpi	'/'
	jrnz	..daterr
	inx	H	
	call	getnum	; get day
	cpi	1
	jrc	..daterr
	cpi	32
	jrnc	..daterr
	sta	BOOTdate+day
	mov	A,M	; check delimiter
	cpi	'/'
	jrnz	..daterr
	inx	H	
	call	getnum	; get year
	cpi	80
	jrc	..daterr
	cpi	100
	jrnc	..daterr
	sta	BOOTdate+year
	mov	A,M	; check for RETURN at end
	cpi	cr
	jrnz	..daterr
;
; Ask for time
..timerr:
	lxi	H,TIMEmsg
	call	PRTMSG
	lxi	H,BOOTdate
	mvi	M,cr
	mvi	E,1
	call	INMSG
	mvi	M,cr
	lxi	H,BOOTdate
	call	getnum	; get hours
	cpi	24
	jrnc	..timerr
	sta	BOOTdate+hrs
	mov	A,M	; check delimiter
	cpi	':'
	jrnz	..timerr
	inx	H
	call	getnum	; get minutes
	cpi	60
	jrnc	..timer
Š	sta	BOOTdate+mins
	mov	A,M	; check for RETURN at end
	cpi	cr
	jrnz	..timerr
	sub	A
	sta	BOOTdate+secs
	di
	lxi	H,BOOTdate+secs
	lxi	D,secs
	lxi	B,6
	ldir		; set time
	ei
;
; Ask master user for his login name
ASKlog:
	lxi	H,LOGmsg
	call	PRTMSG
..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:
	]
	]
;----------
; Initialize type-ahead buffer
	.ife	HARDshar,[
	di
	lda	INLENdef
	ani	typeahead
	sta	nxtin	; number of chars
	mvi	A,typeahead
	sta	nxtout
	lxi	H,INBUFdef
	lxi	D,PORT0buf
	lxi	B,typeahead
	ldir		; type-ahead chars
	ei
	]
;----------
; 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
	
;----------
; Jump to the warm-boot entry point
	lxi	H,WBOOT
	push	H
	reti		; clear mysterious interrupt
;----------
; Assign default disk partitions
;
; Regs in:   HL = pointer to partition name
;	     C  = unit number
; Regs out:  none
; Destroyed: A, BC, DE, HL
	.ife	HARDshar,[
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
	cpi	0FFh
	jrnz	..assign ; jump if assign successful
;
; Check for floppy assignment
	.ife	HARDshar,[
	lda	DSKdef	; density indicator
	cpi	'S'	; single density
	mvi	C,SDtab
	jrz	..flop
	cpi	'D'	; double density
	mvi	C,DDtab
	jrnz	..ret
..flop:	lda	DSKdef+1; unit number (0-7)
	sui	'0'
	jrc	..ret
	cpi	8
	jrnc	..ret
	mov	B,A	; save unit number
	mov	A,C
	cpi	SDtab
	jrz	..assign
	set	6,B	; set double density bit
Š	]
	.ifn	HARDshar,[
	jmpr	..ret	; don't bother if hard boot
	]
;
; Process network, hard disk, or floppy assignment
..assign:
	
	call	CPMMAP	; get pointer to disk map
	push	H	; save address of map byte
	mov	M,B	; overwrite map byte
	inx	H
	xchg		; DE = address of map byte
	dcr	C
	mov	A,C	; A = 0,1,2,3,4,5,6,or 7
	rlc
	rlc
	rlc
	rlc
	sub	C
	mov	C,A	; C = 0,15,30,45,60,75,90,105
	mvi	B,0
	lxi	H,DSKtab
	dad	B	; compute disk table address
	lxi	B,15
	ldir		; overwrite disk map
;
; Move partition name into BIOS
	pop	H	; get address of map byte
	lxi	D,8
	ora	A
	dsbc	D	; compute destination of name
	pop	D	; get source of name
	pop	B	; throw away unit number
	xchg
	lxi	B,8	; move the name
	ldir
	ret
;
; Can't assign, so set map byte to "unassigned"
..ret:	pop	H
	pop	B
	call	CPMMAP
	mvi	M,0FFh	; unassign this partition
	ret
;----------
; ASSIGN a hard disk partition
;  Regs in:  HL = address of partition name
;
; If network master, then let the BIOS handle it
upmast:	.byte	0FFh	; 0 if master is active
ASSIGN:
	lda	upMAST
	ora	A	; if master not up yet,
	jrnz	..hdass	;  then assign directly
	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 booting from harddisk, assign directly
..hdass:
	push	H
	mvi	A,assnHARD
	call	CMDHARD	; send assign command to disk
	pop	H
	lxi	B,14
	call	SENDHARD; send partition 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
;----------
; Scratch areas
DSK0def	==	TPA	; default disk names
IOBdef	==	TPA+32	; default IOBYTE
INLENdef==	TPA+33	; length of type-ahead buffer
INBUFdef==	TPA+34	; default type-ahead buffer
;
DSKdef: .ascii	"PRTSPOOL" ; assignment work area
	.byte	0,0,0,0,0,0; ignore password
;---------
	.ife	MASTopt,[
;
; Twiddle thumb(s) while waiting for hard disk int
BURN.SP:.word	0
	.word	BURNcpu
BURNcpu:jmpr	.
;----------
; Get 2 digit number from input buffer
;  Regs in:  HL = address of input buffer
getnum:
	call	getdig	; get first digit
	cpi	0FFh
	rz		; return if illegal digit
	rlc
	mov	B,A
	rlc
	rlc
	add	B	; muliply first digit by 10
	mov	B,A
	call	getdig
	cpi	0FFh
	rz		; return if illegal digit
	add	B
	ret
;----------
; Get 1 digit from input buffer
getdig:
	mov	A,M
	inx	H
	sui	'0'
	jrc	..numerr
	cpi	10
	rc
..numerr:
	mvi	A,0FFh
	ret
	]
;----------
; CP/M 2.2 disk parameter table
DSKtab:
; 256K
	.word	128	; sectors per track
	.byte	3,7,0	; blk shift, blk and ext mask
	.word	255	; number of blocks - 1
	.word	63	; maximum directory number
	.byte	0C0h,0	; bits indicate directory blks
Š	.word   16	; check vector size
	.word	0	; number of op sys tracks
; 512K
	.word	128	; sectors per track
	.byte	4,15,0	; blk shift, blk and ext mask
	.word	255	; number of blocks - 1
	.word	127	; maximum directory number
	.byte	0C0h,0	; bits indicate directory blks
	.word   32	; check vector size
	.word	0	; number of op sys tracks
; 1M
	.word	128	; sectors per track
	.byte	4,15,0	; blk shift, blk and ext mask
	.word	511	; number of blocks - 1
	.word	255	; maximum directory number
	.byte	0F0h,0	; bits indicate directory blks
	.word	64	; check vector size
	.word	0	; number of op sys tracks
; 2M
	.word	128	; sectors per track
	.byte	4,15,0	; blk shift, blk and ext mask
	.word	1023	; number of blocks - 1
	.word	511	; maximum directory number
	.byte	0FFh,0	; bits indicate directory blks
	.word	128	; check vector size
	.word	0	; number of op sys tracks
; 4M
	.word	128	; sectors per track
	.byte	4,15,0	; blk shift, blk and ext mask
	.word	2047	; number of blocks - 1
	.word	1023	; maximum directory number
	.word	0FFFFh	; bits indicate directory blks
	.word	256	; check vector size
 	.word	0	; number of op sys tracks
; 8M
	.word	128	; sectors per track
	.byte	5,31,1	; blk shift, blk and ext mask
	.word	2047	; number of blocks - 1
	.word	1023	; maximum directory number
	.byte	0FFh,0	; bits indicate directory blks
	.word	256	; check vector size
	.word	0	; number of op sys tracks
;
	.ife	HARDshar,[
SDtab	==	7
	.word	26	; sectors per track
	.byte	3,7,0	; blk shift, blk and ext mask
	.word	242	; number of blocks - 1
	.word	63	; maximum directory number
	.byte	0C0h,0	; bits indicate directory blks
	.word	16	; check vector size
	.word	2	; number of op sys tracks
DDtab	==	8
	.word	52	; sectors per track
	.byte	4,15,0	; blk shift, blk and ext mask
Š	.word	242	; number of blocks - 1
	.word	127	; maximum directory number
	.byte	0C0h,0	; bits indicate directory blks
	.word	32	; check vector size
	.word	2	; number of op sys tracks
	]
	]
;----------
; CP/M cold-boot message
CPMmsg:
.ife	MASTopt,[.ascii	[cr][lf]'HiNet Master  ']
	.byte	version+'0','.',revision/100+'0'
	.byte	(revision@100)/10+'0'
	.byte	(revision@100)@10+'0'
	.byte	patch
crlf:	.ascii 	[cr][lf]
	.byte	0
	.ife	MASTopt!HARDshar,[
DATEmsg:.asciz	[cr][lf]'Enter Date  MM/DD/YY - '
TIMEmsg:.asciz	[cr][lf]'Enter Time  HH:MM - '
LOGmsg:	.asciz	[cr][lf][lf]'Login please ... '
	]
	.ife	MASTopt,[
inituser:.ascii [0]'Unknown        '
	]
;----------
; 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,00000100b; no interrupts
rs232$	==	.-rs232
;----------
; 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	goes here
BOOTcom	==	8000h
BOOTnum	==	BOOTcom+1
BOOTclk	==	BOOTcom+2
BOOTnam	==	BOOTcom+1
BOOTpsw	==	BOOTcom+9
BOOTmsg	==	BOOTcom+15
BOOTassn==	BOOTnam
BOOTdate==	BOOTcom
	.reloc
;----------
; 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
	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
	call    clrsbuf
;----------
; Warm boot from harddisk network
	.ife	HARDshar,[
	.ifn	MASTopt,[call ACKpoll]
	mvi	A,clrlockNET
	sta	NETcom
	call	NETreq	; clear all active locks
	.ife	FLOPopt,[call clrDDbuf]
	mvi	C,0FFh
	call	SELDSK	; select boot disk
..nsect	==	(lenBOOT-CPM)/128
	.ife	MASTopt,[
	.ifg	..nsect-128, [..track == 5
			      ..sect  == 1-..nsect]
			     [..track == 6
		    	      ..sect  == 129-..nsect]
	]
	.ifn	MASTopt,     [..track == 2
			      ..sect  == 129-..nsect]
	lxi	B,..track
	call	SETTRK
	mvi	C,..sect
	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
Š	]
;----------

;----------
; 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	NETboot,[
READBLK:
	push	B	; save sector count
	shld	cpmDMA
	call	READ	; read one sector
	lxi	H,cpmSEC
	inr	M	; increment sector number
	mvi	A,129
	cmp	M	; check for sector too big
	jrnz	..ok
	mvi	M,1	; reset to sector 1
	lxi	H,cpmTRK
	inr	M	; increment track number
..ok:	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:
..conin:
	call	doCONIO
	.word	PORT0in	; TTY:	(default)
	.word	PORT2in	; CRT:
Š	.word	PORT1in	; BAT:
	.word	PORT3in	; UC1:
;----------
; Console output
;
; The following silly code is needed so that backspace
; is handled properly with CP/M 1.4.  The code isn't
; needed for CP/M 2.2. 
CONOUT:
	
FORCEout:
	call	doCONIO
	.word	PORT0out; TTY:	(default)
	.word	PORT2out; CRT:
	.word	PORT1out; BAT:
	.word	PORT3out; UC1:
;----------
; List output
LIST:
	lda	3
	rlc
	rlc
	rlc
	ani	7	; allow 8 LST: assignments
	call	DISPATCH
	.word   PORT0out
	.word	PORT3out
	.word	PORT2out; CRT:	(default)
	.word	PORT1out
	.word	PORTPout; LPT:
	.word	CALLerr
	.word	PORTUout; UL1:
        .word 	CALLerr
;----------
; List status
LISTST:
	sub	A	; indicates not ready
	ret
;----------
; Punch output
PUNCH:
	lda	3
	rrc
	rrc
	rrc
	rrc
	ani	1	; allow 2 PUN: assignments
	call	DISPATCH
	.word	PORT0out; TTY:
	.word	PORT3out; PTP:	(default)
;----------
Š; Reader input
READER:
	lda	3
	rrc
	rrc
	ani	1	; allow 2 RDR: assignments
	call	DISPATCH
	.word	PORT0in	; TTY:
	.word	PORT3in	; PTR:	(default)
;----------
; I/O dispatch routines
doCONIO:
	lda	3
	ani	3	; allow 4 CON: assignments
DISPATCH:
	rlc
	xthl
	mov	E,A
	mvi	D,0
	dad	D	
	mov	A,M	; get jump vector
	inx	H
	mov	H,M
	mov	L,A
	push	H
	pop	D
	xthl	; go to appropriate routine
	ret
	.ife	AHEADopt,[
;----------
; Port 0 interrupt
PORT.SP:.word	0	; user's SP
	.byte	76h,76h,76h,76h,76h,76h,76h,76h
	.byte	76h,76h,76h,76h,76h,76h,76h,76h
PORTstack:
PORTint:
	push	PSW
	.ife	MASTopt,[
	lda	LOCALflag
	ora	A
	jrz	..saveregs
	]
	ei
..saveregs:
	sspd	PORT.SP	; save user registers
	lxi	SP,PORTstack
	push	H
	push	D
	push	B
	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	FORCEout
..ret:
	pop	B
	pop	D
	pop	H
	lspd	PORT.SP
	pop	PSW
	ei
	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	CONST	; 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
	]
	;----------

	ret
;----------
; Port 0 output
PORT0out:
	
PORT0force:
	in	SIO1AC
	ani	1<TxRDY
	jrz	PORT0force
	mov	A,C
	out	SIO1AD
	ret
;----------
; 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
	out	CENTP
	in	PPSTROBE
	ret
; Select disk drive
SELDSK:
	
	mov	A,C
	cpi	0FFh	; check for boot disk
	jrz	..ok
	cpi	4	; check for bad disk selection
	jrc	..ok
	lxi	H,0	; return HL = 0 to CP/M
	ret
..ok:	sta	cpmDSK	; save current disk number
	call	cpmMAP	; get pointer to disk map
	xchg		; return pointer to DPB
	ret
;----------
; Set track
SETTRK:
	sbcd	cpmTRK	; save current track number
	ret
;----------
; Set sector
SETSEC:
	mov	A,C
	sta	cpmSEC	; save current sector number
	ret
;----------
; Set DMA address
SETDMA:
	sbcd	cpmDMA	; save current DMA address
	ret
;----------
; Set DMA size
SETBYT:
	sbcd	cpmBYT	; save current DMA size
	ret
;----------
; Sector translate
SECTRAN:
	push	D	; save translation table ptr
	call	cpmMAP	; get current map byte
	pop	H	; restore translation table ptr
	ani	0C0h
	jrz	..sd	; jump if current disk is SD
	mov	H,B	; otherwise, compute sector+1
	mov	L,C
Š	inx	H
	ret
..sd:	dad	B	; single density translation
	mov	L,M
	mvi	H,0
	ret
;----------
; Home to track 0
HOME:	lxi	H,0
	shld	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
;----------
; Read disk
READ:
	call	cpmMAP	; determine device type
	cpi	0FFh
	jz	CALLerr	; error if unassigned
	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
	cpi	0FFh
	jz	CALLerr	; error if unassigned
	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
	.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
	cmp	M
	rnz
	lda	iobTRK	; test TRACK byte
	inx	H
	cmp	M
	rnz
	lda	iobPSEC	; compare physical sector
	inx	H
	cmp	M
	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	Mode
	bit	ModeTRY,A
	jrz	..noretry
;
; Normally, retry 10 times before printing error msg
	mvi	D,RETRY+1; 10 retries on any error
Š	push	D
	jmpr	tryf
;
; If using FDTEST, don't retry errors
..noretry:
	mvi	D,1	; 0 retries on any error
	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
	sta	FLOPbusy; floppy is busy
	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
	lxi	B,FLOPstat+3; point to THS
	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
	jmp	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
chkENDT:bit	7,A
	jrz	chkDENS
	dcr	D
ENDTerr:cz	IOERR	; read beyond end-of-track
	jmp	retryf
chkDENS:bit	0,A
	jrz	chkORUN
	dcr	D
DENSerr:cz	IOERR	; missing ID address mark
	jmp	retryf
;
; Non-recoverable errors
chkORUN:bit	4,A
ORUNerr:cnz	IOERR	; overrun (DMA failure)
	bit	1,A
PROTerr:cnz	IOERR	; write-protected
	call	IOERR
;----------
; Handle the transfer-done interrupt from the DMA chip
FLOPbusy:.byte	0	; 0FFh if busy, 0 if not busy
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
	sta	FLOPbusy; floppy not busy anymore
	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
	call	WAITDR	; wait for first status byte
	rnc		; return if no status bytes
	lda	FLOPbusy
	ora	A	; dont reset DMA if int occured
	jrz	..getstat
	mvi	A,0C3h	; reset DMA so that errors
	out	DMA	;  are handled OK
	sub	A
	sta	lockbyte; unlock the DMA chip
	sta	FLOPbusy
..getstat:
	in	FLOPDR	; get a result byte
	mov	M,A	; store it away
	inx	H
	call	WAITDR
	rnc
	jmpr	..getstat
;----------
; 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
curMOTOR:.byte	0FFh	; current drive
onMOTOR:
	push	PSW	; save new drive number
	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
	pop	PSW	; get new drive number
	mov	B,A
	lda	curMOTOR; get old drive number
	cmp	B
	jrnz	..delay	; force delay if old <> new
	mov	A,C
	ora	A	; if old time not zero,
	rnz		; then head already loaded
..delay:mov	A,B
	sta	curMOTOR; save current drive number
	lxi	B,loadset; delay for head load settle
	jmp	DELAY
;----------
; idleMOTOR - the motor can be turned off now
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
;----------
; 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
	]
	.ife	HARDopt,[
;----------
; Hard disk read
HARDrd:	
	.ifn	MASTopt, [call HARDprep]
	.ife	MASTopt, [jmp  CALLerr ]
;
; Regs in: HL = DMA address
;	   DE = track number     (0-511)
;	   C  = sector number	 (1-128)
;	   A  = unit number (0-63)
;
HARDr:	push	H	; save DMA address
	call	HARDrw
	mvi	A,readHARD
	call	CMDhard	; send read command
	call	REShard ; get result status
	pop	H
	lxi	B,128
Š	call	REChard	; get data bytes
	sub	A
	ret
;----------
; Hard disk read 1K
;
; Regs in: HL = DMA address
;	   DE = track number     (0-511)
;	   C  = sector number	 (1-128)
;	   A  = unit number (0-63)
;
HARD1:	push	H	; save DMA address
	call	HARDrw
	mvi	A,read1HARD
	call	CMDhard	; send read command
	call	REShard ; get result status
	pop	H
	lxi	B,1024
	call	REChard	; get data bytes
	sub	A
	ret
;----------
; Hard disk write
HARDwr:	
	.ifn	MASTopt, [call HARDprep]
	.ife	MASTopt, [jmp  CALLerr ]
;
; Regs in: HL = DMA address
;	   DE = track number     (0-511)
;	   C  = sector number	 (1-128)
;	   A  = unit number (0-63)
;
HARDw:	push	H	; save DMA address
	call	HARDrw
	mvi	A,writeHARD
	call	CMDhard	; send write command
	pop	H
	lxi	B,128
	call	SENDhard; send data bytes
	call	REShard	; get result status
	sub	A
	ret
;----------
; Read/write common code
ŠHARDrw:
	sta	HARDdsk	; store unit number
	push	B	; save sector
	push	D	; save track
	mvi	A,selHARD
	call	CMDhard	; select the unit
	pop	D	; restore track
	pop	B	; restore sector
	lxi	H,HARDsec
	mov	M,C	; hard disk sector
	inx	H
	mov	M,E	; hard disk track (low byte)
	inx	H
	mov	M,D	; hard disk track (high byte)
	ret
;----------
; Transmit a block to the hard disk
;  Regs in:   HL = block address
;             BC = byte count
;  Regs out:  none
;  Destroyed: A, BC, DE, HL
SENDHARD:
	shld	DMAHSadr  ; DMA address
	dcx	B
	sbcd	DMAHSsize ; DMA length
	lxi	H,DMAHdone
	shld	DMAvect	  ; DMA interrupt vector
	mvi	A,4
	out	PIOAD	; multiplex hard disk to DMA
	lxi	H,DMAHSprog
	lxi	B,DMAH$<8+DMA
	di		; no ints until local user ok
	outir		; let the DMA fly
	jmpr	GOlocal
	]
;----------
; Receive a block from the hard disk
;  Regs in:   HL = block address
;	      BC = byte count
;  Regs out:  none
;  Destroyed: A, BC, DE, HL
RECHARD:
	shld	DMAHRadr  ; DMA address
	dcx	B
	sbcd	DMAHRsize ; DMA length
	lxi	H,DMAHdone
	shld	DMAvect   ; DMA interrupt vector
	mvi	A,5
	out	PIOAD	; multiplex hard disk to DMA
	lxi	H,DMAHRprog
	lxi	B,DMAH$<8+DMA
	di		; no ints until local user ok
	outir
;----------
; Option to assemble local user code
LOCALopt==	0 
	.ife	LOCALopt,[
;----------
; Give the CPU to the local user until DMA done
GOlocal:
	jmp	retUSER
;---------
; Return here when DMA is done
DMAHdone:
	call	saveUSER
	mvi	A,0C3h
	out	DMA	; turn off DMA
	ei
 	ret		; return to caller
	]
;----------
; 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	; timeout if no (or bad) disk
	mov	A,B
	ora	C
	jrnz	..3	; jump if we're still OK
	dcr	A	; clear zero flag
	call	HARFerr	; harddisk is dead
..3:	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
	lxi	B,8
	jmp	SENDHARD
;----------
; Receive status info from the hard disk
;  Regs in:   none
;  Regs out:  A = error status
;  Destroyed: A, BC, HL
RESHARD:
	lxi	H,HARDstat
	lxi	B,8
	call	RECHARD
	lda	HARDstat+7
	ora	A
	rz
	lxi	B,HARDstat+1; point to THS
	bit	7,A
HARCerr:cnz	IOERR	; command error
	bit	6,A
HARDerr:cnz	IOERR	; data error
	bit	5,A
HARSerr:cnz	IOERR	; sector error
	bit	4,A
HARFerr:cnz	IOERR	; fault error
	call	IOERR	; unknown error
;----------
; DMA commands
	.ife	MASTopt,[
DMAHSprog:
	.byte	0C3h	; master reset		     2D
	.byte	0C7h	; reset port A		     2D
	.byte	0CBh	; reset port B		     2D
Š	.byte	79h	; write
DMAHSadr:.word	0	; filled by SENDNET or RECstart
DMAHSsiz:.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	HARDP	; port B
	.byte	12h	; interrupt at end of block
	.byte	DMAvect&0FFh ; interrupt vector
	.byte	92h	; active low
	.byte	0CFh	; load starting address      2C
	.byte	5	; write
	.byte	0CFh	; load starting address	     1A
	.byte	0ABh	; enable interrupts	     2D
	.byte	87h	; start DMA
DMAH$	==	.-DMAHSprog
	]
;
DMAHRprog:
	.byte	0C3h	; master reset		     2D
	.byte	0C7h	; reset port A		     2D
	.byte	0CBh	; reset port B		     2D
	.byte	7Dh	; read
DMAHRadr:.word	0	; filled by SENDNET or RECstart
DMAHRsiz:.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	HARDP	; port B
	.byte	12h	; interrupt at end of block
	.byte	DMAvect&0FFh ; interrupt vector
	.byte	9Ah	; active high
	.byte	0CFh	; load starting address      2C
	.byte	1	; read
	.byte	0CFh	; load starting address	     1A
	.byte	0ABh	; enable interrupts	     2D
	.byte	87h	; start DMA
	]
	]
	.ife	NETopt,[
;----------
; Network read
NETrd:
	mvi     C,readNET
	jmpr	Network
;----------
; Network write
NETwr:
	mvi	C,writeNET
;----------
; Network read/write
Network:
	.ife	HARDshar,[
	call	cpmMAP
	ani	3Fh
Š	sta	NETdsk	; set disk
	mov	A,C
	lxi	H,cpmTRK
	lxi	D,NETtrk
	lxi	B,5
	ldir		; set track, sect, DMA address
	sta	NETcom	; set command byte
	]
;----------
; See if desired sector is in local network buffer
	
	jmpr	NETreq	; process the I/O request
	;----------
; 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:
..wait:	lda	NETcom	; wait for master to wake up
	ora	A	;  and process the command
	jrnz	..wait
	ret
	.page
	.ife	MASTopt,[
;------------------------------------------------------
;  H I N E T   M A S T E R
;------------------------------------------------------
MASTusr:.byte	0	; user being polled now
deadusr:.byte	numusr	; "dead" user number
BOOTusr	==	254	; boot pseudo user number
LOGusr	==	253	; login pseudo user number
MIMICusr==	252	; mimic pseudo user number
boottim:.byte	1	; boot broadcast counter
logtime:.byte	1	; login poll counter
MASTER:
;
; Save user registers and lock DMA chip
	call	saveUSER
	lxi	SP,MASTstack-6
	call	lockDMA	; reserve the DMA chip
	ei		; interrupts OK now
;------------
; Poll each active user (poll bad users each 1/2 sec)
	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	; don't poll if status = 0
	jrz	nxtpoll
	inr	A	; logout if 8 polls in 4 secs
	ani	1Fh	;  have not been answered
	jrnz	..nack
..poll:
	mvi	A,poll	; poll the current user
	mvi	E,0	; no delay
	call	SENDUSR
	call	RECCMD	; wait for a response
	jrnc	ACKed
	call	curUSR
;----------
; Logout this user if countdown = 0
..nack:
	dcr	M	; countdown for logout
	.ife	HARDshar,[
	jrnz	nxtpoll
	call	MMclrlock; clear locks (if any)
	]
	jmpr	nxtpoll
;----------
; Poll was acknowledged with pollack or request
ACKed:	
	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
	sui	whoNET
	jrc	..bad1
	cpi	..lentab
	jrnc	..bad1
	call	..dispatch
	jmpr	nxtpoll	; return here if error
..dispatch:
	call	DISPATCH
..table:
	.word	Mwho	; who
	.word	Mread	; read
	.word	Mwrite	; write
	.word	..bad	; login
	.word	..bad	; start spool
	.word	Mread1	; read 1K
	.word	..bad	; stop spool
	.word	Massign	; assign
Š	.word	Mhog	; hog
	.word	Mlock	; lock
	.word	Munlock	; unlock
	.word	Mclrlock; clear locks
	.word	..bad	; spool
..lentab=	(.-..table)/2
;
..bad1:	call	NETerA
	jmpr	nxtpoll
;
..bad:	push	D
	pop	H
	call	PRTADD
	call	NETerr
;
; Poll next user, if any remain
nxtpoll:
	lda	MASTusr
	dcr	A	; decrement user number
	jnz	nxtusr
;---------
; Poll one "dead" user per pass
	lda	deadusr
	dcr	A
	jrnz	..poll
	mvi	A,numusr-1
..poll:	sta	deadusr
	sta	MASTusr
	call	curUSR
	mov	A,M
	ora	A
	jrnz	..local	; don't poll if active
	mvi	A,poll
	mvi	E,0	; no delay
	call	SENDUSR
	call	RECCMD	; get command from net user
	jnc	ACKed	; process request, if any
;----------
; See if the local user has anything for us to do
..local:
	sub	A
	sta	MASTusr	; set local user number
	lda	NETcom	; check local user's command
	ora	A
	jrz	clrreq	; jump if nothing to do
	lxi	H,NETcom; get local user's command
	lxi	D,MASTcom
	lxi	B,lencom
	ldir
	lxi	H,userlist
	call	savereq	; update entry in WHO table
	sui	whoNET
	jrc	..bad1
	cpi	..lentab
Š	jrnc	..bad1
	call	..dispatch
	jmpr	clrreq	; return here if error
..dispatch:
	call	DISPATCH
..table:
	.word	..bad	; who
	.word	Lread	; read
	.word	Lwrite	; write
	.word	Llogin	; login
	.word	..bad	; start spool
	.word	..bad	; read 1K
	.word	..bad	; stop spool
	.word	Lassign	; assign
	.word	..bad	; hog
	.word	Llock	; lock
	.word	Lunlock	; unlock
	.word	Lclrlock; clear locks
	.word	..bad	; spool
..lentab=	(.-..table)/2
;
..bad1:	call	NETerA
	jmpr	clrreq
..bad:	push	D
	pop	H
	call	PRTADD
	call	NETerr
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	..poll
	mvi	M,sendBOOT
	lxi	H,NET1strap
	lxi	B,len1strap
	mvi	E,0	; no delay
	mvi	A,BOOTusr; pseudo user number
	call	SENDNET
;----------
; Poll mimic and login pseudo users every 1/3 sec
..poll:
	lxi	H,logtime
	dcr	M	; poll if counter = 0
	jrnz	..spool
	mvi	M,sendlog
	call	CHKmimic; poll mimic
	call	CHKlogin; check for new logins
;----------
; Return control to local user for average 1/120 sec
..spool:
	di		; no ints until user running
	sub	A	; unlock the DMA chip
	sta	lockbyte	
MASTret:
	mvi	A,wakeupMAST; delay a bit before
	sta	timeMAST;      calling MASTER again
	jmpr	retUSER
	.page
;-----------------------------------------------------
Š;  C O N T E X T   S W I T C H   R O U T I N E S
;-----------------------------------------------------
USER.SP:.word	BURN.SP
MAST.SP:.word	0
LOCALflag:.byte	0	; 0 if in local user, 1 if mast
;----------
; Save user registers
saveUSER:
	xthl
	sspd	USER.SP
	lxi	SP,MASTstack
	push	D
	push	B
	push	PSW
	lspd	MAST.SP
	mvi	A,0FFh
	sta	LOCALflag
	pchl
;----------
; Restore user registers
retUSER:
	sub	A
	sta	LOCALflag
	sspd	MAST.SP
	in	PIOAD
	ani	80h	; return to user if no INT
	jrz	..ret
..wait:
	in	PIOAD
	ani	80h	; wait until INT released
	jrnz	..wait
	lxi	SP,MASTstack-6
	pop	PSW
	pop	B
	pop	D
	lspd	USER.SP
	pop	H
	ei
	jmp	30h	; go to DDT when INT released
..ret:
	lxi	SP,MASTstack-6
	pop	PSW
	pop	B
	pop	D
	lspd	USER.SP
	pop	H
	ei
	ret
	.page
;-----------------------------------------------------
; L O G I N   N E W   U S E R
;-----------------------------------------------------
prevusr:.byte	numusr-1; start scanning at begin
;
; Poll login user - if answered, then log'im in
CHKlogin:
	mvi	A,poll
Š	mvi	E,0	; no delay
	call	SENDLUSR; poll "login" user
	lxi	H,MASTcom
	lxi	B,lencom
	lxi	D,$1ms
	sub	A
	call	RECNET	; wait for response
	bit	7,A	; check for time-out
	jrnz	..ack
;
; Nobody (or everybody!) wants to login
       	mvi	A,lognack
	call	SENDLUSR; prospective logins must retry
	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
	lda	prevusr	; start scanning at last login
..look:	inr	A
	cpi	numusr
	jrnz	..ok
	mvi	A,1	; restart scan at beginning
..ok:	push	PSW
	sta	MASTusr
	call	curUSR	; point to userlist entry
	mov	A,M
	ora	A	; if zero, then available
	jrz	..found
	pop	PSW
	lxi	H,prevusr
	cmp	M	; see if all entries checked
	jrnz	..look	; jump if not all checked
;
; Deny login if name not found or no room in list
..deny:	mvi	A,logdeny
	call	SENDLUSR
	ret
;
; Put user in table, and send number and login time
..found:
	pop	PSW
	sta	prevusr	; save current user number
	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	E,$halfms
	mvi	A,LOGusr
	call	SENDNET
;
; Give the user the rest of the bootstrap code
	lxi	H,NET2strap
	lxi	B,len2strap
	lda	MASTnum
	mvi	E,$halfms*2 ; extra delay for DSC/4
	call	SENDNET
	ret
;
	.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,3	; start at track 3
	shld	MASTtrk
	mvi	A,1	; sector 1
	sta	MASTsec
;
; Search next sector for match with user name
..nsect:lxi	H,MASTbuf
	call	MMread	; 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
	sta	MASTsec	; read from sector 1,2,3,...128
	lxi	H,4	; track 4
	shld	MASTtrk
	lxi	H,MASTbuf
	call	MMread	; 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
;	     A  = user number
newuser:
	mvi	M,0FFh	; log 'im
	sta	MASTnum	; give userno to user
	.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
	.page
;------------------------------------------------------
; P R O C E S S   L O C A L   U S E R   R E Q U E S T
;------------------------------------------------------
Lread:	
	lhld	NETdma  ; read directly into user area
	call	MMread 
	ret
;----------
Lwrite:	
	lhld	NETdma  ; write directly from user area
	call	MMwrite
Š	ret
;----------
	.ife	HARDshar,[
Lassign:
	lxi	H,MASTnam; point to name and password
	call	MMassign
	sbcd	NETassn	; store assignment value
	sded	NETassn+2
	ret
;----------
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
	sub	A
	call	curUSR
	jmp	newuser	; enter name in user table
;----------
Llock:
	call	MMlock
	sta	LOCKstat; store status for user
	ret
;----------
Lunlock:
	call	MMunlock
	sta	LOCKstat; store status for user
	ret
;----------
Lclrlock:
Š	call	MMclrlock
	ret
	]
	.page
;------------------------------------------------------
; P R O C E S S   R E M O T E   U S E R   R E Q U E S T
;------------------------------------------------------
; Process a read request
Mread:
	lxi	H,MASTbuf
	call	MMread	; read sector from disk
	lxi	H,MASTbuf
	lxi	B,128
	lda	MASTusr
	mvi	E,0	; no delay necessary
	call	SENDNET	; send data to user
	lxi	D,$1ms
	call	RECUSR	; receive ack from user
	cc	NETerr
	cpi	datack
	cnz	NETerA
	ret
;----------
; Process a write request
Mwrite:
	mvi	A,mesack
	mvi	E,$halfms
	call	SENDUSR	; ack reception of command
	lxi	H,MASTbuf
	lxi	B,128
	lxi	D,$16ms
	sub	A
	call	RECNET	; receive data from user
	bit	7,A
	cz	NETerA
	ani	60h	; check for CRC or overrun err
	cnz	NETerA	
	mvi	A,datack
	mvi	E,$halfms
	call	SENDUSR	; ack data
	lxi	H,MASTbuf
	call	MMwrite	; write the data to the disk
	ret
	.ife	HARDshar,[
;----------
; Process a read 1K request
Mread1:
	lxi	H,MASTbuf
	call	MMrd1	; read sector from disk
	lxi	H,MASTbuf
	lxi	B,1024
	mvi	E,0	; no delay necessary
	lda	MASTusr
	call	SENDNET	; send data to user
	lxi	D,$1ms
Š	call	RECUSR	; receive ack from user
	cc	NETerr
	cpi	datack
	cnz	NETerA
	ret
;----------
; Process a hog request
Mhog:
	mvi	A,hogack; tell user he can hog the net
	mvi	E,$halfms
	call	SENDUSR	; for at most 1/60th sec
	lxi	D,$16ms
	call	RECUSR
	cc	NETerr	; error if user has pigged out
	ret
;----------
; Process an assign request
Massign:
	lxi	H,MASTnam
	call	MMassign; let MASTas do the work
	lxi	H,HARDstat
	lxi	B,4
	mvi	E,0	; no delay necessary
	lda	MASTusr	
	call	SENDNET	; send results to user
	ret
;----------
; Process a who request
Mwho:	
	lxi	H,USERlist
	lxi	B,numusr*16
	mvi	E,$halfms
	lda	MASTusr
	call	SENDNET	; send user table
	lxi	H,SPOOLlist
	lxi	B,lenSPOOL*16
	mvi	E,$halfms*2 ; extra delay for DSC/4
	lda	MASTusr
	call	SENDNET	; send spool table
	ret
	;----------
; Process a lock request
Mlock:
	call	MMlock
	mvi	E,$halfms
	call	SENDUSR	; send status to user
	ret
;----------
; Process an unlock request
Munlock:
	call	MMunlock
	mvi	E,$halfms
	call	SENDUSR	; send status to user
	ret
;----------
; Process a clear-lock request
Mclrlock:
	call	MMclrlock
	ret
	]
	.page
;-----------------------------------------------------
;  H I N E T  R E A D - W R I T E - A S S I G N
;-----------------------------------------------------
; Master disk read
;  Regs in:   HL = buffer address
;  Regs out:  none
;  Destroyed: A, BC, DE, HL
MMread:
	.ife	HARDshar,[
	lda	MASTdsk	; unit number
	lded	MASTtrk	; track number
	lbcd	MASTsec	; sector number
	jmp	HARDr	; let HARDr do the rest
;----------
; Master disk read 1K
MMrd1:
	lda	MASTdsk	; unit number
	lded	MASTtrk	; track number
	lbcd	MASTsec	; sector number
	jmp	HARD1	; let HARD1 do the rest
	]
;----------
; Master disk write
;  Regs in:   HL = buffer address
Š;  Regs out:  none
;  Destroyed: A, BC, DE, HL
MMwrite:
	.ife	HARDshar,[
	lda	MASTdsk	; unit number
	lded	MASTtrk	; track number
	lbcd	MASTsec	; sector number
	push	H
	call	HARDw	; let HARDw do the work
	pop	H
	ret
	]
	;----------
; Assign hard disk partition
;  Regs in:   HL = address of unit name and password
;  Regs out:  BC = unit and size
;	      DE = control byte
MMassign:
	.ife	HARDshar,[
	push	H	; save address of name
	mvi	A,assnHARD
	call	CMDhard	; send command to hard disk
	pop	H
	lxi	B,14
	call	SENDHARD; send name and password
	call	RESHARD	; get disk assignment
	lbcd	HARDstat; B = unit, C = size
	lded	HARDstat+2
	ret
	]
	.page
;-----------------------------------------------------
;  M I M I C   R O U T I N E S
;-----------------------------------------------------
; Poll mimic user every 1/3 sec
CHKmimic:
	.ife	HARDshar,[
	lxi	H,MASTsnd
	mvi	M,poll
	lxi	B,1
	mvi	E,0	; no delay necessary
	mvi	A,MIMICusr
	call	SENDNET	; poll mimic pseudo user
	lxi	D,$1ms
	call	RECUSR	; wait for an answer
	lda	MIMstat
	jrc	..no	; jump if timeout
	mvi	A,8	; poll up to 8 times in 4 secs
..no:	ora	A	;  before calling it quits
	rz
	dcr	A
	sta	MIMstat
	ret
MIMstat:.byte	0	; mimicker status (0 if dead)
	]
	.page
Š	.ife	HARDshar,[
;-------------------------------------------------
;  R E C O R D   L O C K I N G   R O U T I N E S
;-------------------------------------------------
;
; Process a network lock request
;  Regs out: A = lock status
MMlock:
	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
MMunlock:
	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
	ralò	B
	slar	C
	ralr	B
	slar	C
	ralr	B
	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
MMclrlock:
	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 entries
	ret
	]
	.page
	lenSPOOL==	16	; length of a spool table entry
;
;--------------------------------------------------
;  M A S T E R   U T I L I T Y   R O U T I N E S
;--------------------------------------------------
;
; 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	E,$halfms
	mvi	A,LOGusr
Š	jmp	SENDNET
;----------
; Receive a message from a network user
;  Regs in:   DE = timeout counter
;  Regs out:  A  = message received
;	      carry flag set if user timed-out
;  Destroyed: A, BC, DE, HL
RECUSR:
	lxi	H,MASTsnd
	lxi	B,1
	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	MASTsnd
	ret
;----------
; Receive a command from a network user
;  Regs out:  A  = first byte of command
;	      carry flag set if user timed-out
;  Destroyed: A, BC, DE, HL
RECCMD:
	lxi	H,MASTcom
	lxi	B,lencom
	lxi	D,$1ms
	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
;----------
; 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
;----------
; Handle a network error - print an error message;
; then resume the MASTER by polling the next user.
Err1:	.asciz	'*** User '
Err2: 	.asciz	' error '
Err3:	.asciz	[cr][lf]
NETerA:
	push	H
	call	PRTBYT
	pop	H
NETerr:
	lda	Mode
	bit	ModeMSG,A ; print user error messages
	jrnz	..print ; ONLY if mode bit is high
	pop	H	; throw away caller's address
	jmpr	..ret	; don't print error message
..print:
	lxi	H,Err1
	call	PRTMSG	; print beginning of message
	lda	MASTusr
	call	PRTBYT	; print user number (in hex)
	lxi	H,Err2
	call	PRTMSG	; print end of message
	pop	H	; get callers address
	call	PRTADD
	lxi	H,Err3
	call	PRTMSG	; print cr, lf
;
; Continue processing network requests
..ret:	
	lxi	H,errors
	inr	M	; increment error count
	ret
	.page
;------------------------------------------------------
; H I N E T   S T A T I O N   B O O T  -  P H A S E  2
;------------------------------------------------------
; Read cold boot code and BIOS into low core;
; then move them to high core and execute cold boot.
;
;	    partitions,IOBYTE,nxtin,type-ahead
lendef	==	32	+1	+1	+31
off2set	==	.-TPA
NET2strap:
DSKSdef:.blkb	lendef	; default disk configuration
Š;
; Store the current time
	lxi	H,BOOTclk; store current time
	lxi	D,ticks
	lxi	B,7
	ldir
;
; Get the BIOS from the net.
..cboot:
	lxi	D,$4sec
	call	RECMSG
	cpi	poll	; wait for poll
	jrnz	..cboot
	lxi	H,BOOTrd-off2set
	lxi	B,lencom
	mvi	E,$halfms
	sub	A
	call	SENDNET	; send read request to master
;
; Read CBOOT and BIOS backwards
	lhld	BOOTadr-off2set
	.ifå	HARDshar¬ [lxé B,1024]
	ora	A
	dsbc	B
	shld	BOOTadr-off2set	; update DMA address
	lxi	D,$4sec
        lda	NETusr
	call	RECNET		; get BIOS chunk
	mvi	E,$halfms
	mvi	A,datack
	call	SENDMSG		; acknowledge reception
;
; Prepare for next sector
	lda	BOOTsec-off2set	
	.ife	HARDshar, [sui 8]
	sta	BOOTsec-off2set	; update sector number
	lhld	COLDbas-8000h
	res	7,H
	lded	BOOTadr-off2set
	ora	A
	dsbc	D	; check if entire BIOS in core
	jrc	..cboot	; if not, jump to read more
;
; Relocate CBOOT and BIOS to high core
	lxi	H,0
	lded	COLDbas-8000h
	dsbc	D
	mov	B,H
	mov	C,L
	mov	H,D
	mov	L,E
	res	7,H
	ldir		; use block move 
;
; Jump to the cold boot code (its right below the BIOS)
	lhld	COLDbas
Š	pchl
;----------
; Boot commands
	.ife	HARDshar,[
BOOTrd:	.byte	read1NET; read 1K at a time
	.byte	0	; destination
	.byte	0	; source
	.byte	0	; disk
	.word	2	; track
BOOTsec:.byte	121	; sector
	]
BOOTadr:.word	8000h	; BIOS load address
	.page
;
;------------------------------------------------------
; H I N E T   S T A T I O N   B O O T  -  P H A S E  1
;------------------------------------------------------
; The following code 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	[cr][lf]'HiNet '
	.byte	version+'0','.',revision/100+'0'
	.byte	(revision@100)/10+'0'
	.byte	(revision@100)@10+'0'
	.byte	patch
Š	.ife	HARDshar,[
	.asciz	[cr][lf]'Login please'
NAMEmsg:.asciz	[cr][lf]'Name: '
PSWmsg:	.asciz	[cr][lf]'Password: '
	]
;----------
; 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
;
;Try to log in with the PROM serial # as user name
;(Turnkey login procedure)
	call	cvSER		;Convert serial # to ASCII and put at BOOTcom
	mvi	A,LOGusr
	sta	NETusr		;Tell INMSG that we're not master
..tlagn:
	call	..poll		;wait for poll, and try to log in
	cpi	logack
	jrz	..login		;Jump if we're logged in
	cpi	lognack
	jrnz	..notTK		;Jump if serial # not a user
	call	..delay		;Random delay if someone else tried to log in
	jmpr	..tlagn		;Jump to try again
;
; Tell the user that the HiNet is up
..notTK:
	lxi	H,HiNETmsg
	call	OUTMSG
..askname:
	.ife	HARDshar,[call askname]
;
; Try to log in with user name supplied from console
..ologin:			;Original login procedure
	call	..poll
	cpi	logack
	jrz	..login		;Jump if we are logged in
	cpi	lognack
	jrnz	..oops		;Jump if invalid user name or psw
	cz	..delay		;Arrive here if CRC net error -
	jmpr	..ologin	;so loop back after a delay and try again
..oops:
	mvi	C,bell
	call	outchr		;Send a beep to user
	jmpr	..askname
;
; Wait for poll of user 253; then request login
..poll:
	lxi	D,$4sec
	call	RECMSG
	cpi	poll
	jrnz	..poll
Š	lxi	H,BOOTcom
	mvi	M,loginNET
	lxi	B,lencom
	mvi	E,$halfms
	sub	A
	call	SENDNET
;
; Get response to login request
	lxi	H,BOOTcom
	lxi	B,9
	lxi	D,$4sec
	mvi	A,LOGusr
	call	RECNET
	lda	BOOTcom
	ret
;
; Delay random period of time, then retry
..delay:ldar		; use refresh register
	ani	7	; (its a good random seed)
	inr	A	; Make it non-zero, user doesn't have all day.
	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
	ret
;
; Get user number
..login:lda	BOOTnum	; get user number
	sta	NETusr	; store it for BIOS
;
; Get the rest of the bootstrap code
	lxi	H,TPA
	lxi	B,len2strap
	lxi	D,$4sec
	call	RECNET
	bit	7,A
	jrz	..poll
	jmp	TPA+lendef
;
; Convert the lo nybble of reg. A to an ASCII hex digit
; Regs. in: A contains value to convert.
; Regs. out: A contains ASCII digit.
; Destroyed: A
cvDIG:
	ani	0fh		;Leave only the 4 LSB
	adi	30h
	cpi	3ah
	rc			;Return if char is 0 thru 9
	adi	07h		;Convert to A thru F otherwise
	ret
;
; Convert a byte in A to 2 ASCII hex digits in HL.
; L is the 1st char (MSD), H is the 2nd (LSD).
; Regs. in: A is the char.
; Regs. out:HL contains ASCII digits.
; Regs. destroyed: A, HL
Šbyt2asc:
	push	psw		;save the byte
	call	cvDIG		;convert the lo nybble
	mov	H,A		;store the ASCII digit in H
	pop	psw		;get the byte back
	rar
	rar
	rar
	rar			;shift hi nybble to lo nybble
	call	cvDIG		;convert the nybble
	mov	L,A		;put it in L
	ret			;return
;
; Convert the PROM serial number to a 4 byte ASCII string in the login string.
; Regs. in : none
; Regs. out: none
; Regs. destroyed: A, HL
cvSER:
	call	INITname	;Blank out user name and psw
	push	X		;The PROM serial # is in IX
	pop	H		;Get it in HL
	push	H		;Save it back on stack
	mov	a,l		;convert the 1ST byte to ASCII
	call	byt2asc		;do it
	shld	BOOTnam		;store it in the login string
	pop	h		;restore the serial #
	mov	a,h
	call	byt2asc		;convert the 2ND byte to ASCII
	shld	BOOTnam+2	;store in the login string
	ret
;----------
; 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
OUTMSG:	mov	A,M
	ora	A	; string ended by zero byte
	rz
	mov	C,A
	call	outchr
	inx	H
	jmpr	OUTMSG
	.ife	HARDshar,[
;
;Initialize user name and psw
INITname:
	lxi	H,BOOTnam
	mvi	M,' '	; fill name and psw with blanks
Š	lxi	D,BOOTnam+1
	lxi	B,13
	ldir
	ret
;
;----------
; Ask for user name and password
askname:
	call	INITname	; Blank the user name and psw
	lxi	H,NAMEmsg
	call	OUTMSG	; ask for name
	lxi	H,BOOTnam
	mvi	E,1	; echo chars
	call	INMSG	; get name
	lxi	H,PSWmsg
	call	OUTMSG
	lxi	H,BOOTpsw
	mvi	E,0	; don't echo chars
	call	INMSG	; 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
INMSG:
	mov	D,L	; remember start of string
..nxtchr:
	lda	NETusr
	ora	A	
	jrnz	..conin	; jump if station
	push	H
	push	D
	call	CONIN	; if master, do normal CONIN
	pop	D
	pop	H
	jmpr	..ok
..conin:in	SIO1AC	; if station, do direct CONIN
	ani	1<RxRDY
	jrz	..conin
	in	SIO1AD
	ani	7Fh	; mask out parity bit
..ok:	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	..nxtchr; don't backspace too far
	dcx	H
	mvi	M,' '	; overwrite char with blank
Š	push	h	; Save the addr we're putting the input at.
	lxi	H,BACKmsg
	call	OUTMSG	; Output to console: backspace, space, backspace
	pop	h	; Restore the addr.
	jmpr	..nxtchr
;
..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	..nxtchr
;
BACKmsg:
	.byte	08h,20h,08h,0	;Erase the last char typed
;
	]
	]		; end of MASTER code
	.ife	NETopt,[
;----------
; Transmit a block on the network
;  Regs in:   HL = block address
;   	      BC = byte count
;	      E  = delay time (master only)
;	      A  = user number
;  Regs out:  none
;  Destroyed: A, BC, DE, HL
SENDNET: 
	.ifn	MASTopt,[
	mvi	E,$halfms ; delay at least 1/2 msec
	]
	inr	E	; delay so that receiver has
..delay:dcr	E	; time to prepare for message
	jrnz	..delay	
	mov	D,A	; save user number
	shld	DMANSadr; store block address
	dcx	B	; DMA chip wants real size - 1
	sbcd	DMANSsize
	mov	A,C
	ora	B
	jrz	..send1
;
; Send more than one byte
	lxi	H,DMATdone; setup the DMA vector
	shld	DMAvect
	mvi	A,1	; multiplex SIO1B to DMA
	out	PIOAD
	lxi	H,DMANSprog; program the DMA chip
	lxi	B,DMAN$<8+DMA
	outir
	lxi	H,SENDprog
	lxi	B,SEND$<8+SIO1BC
	outir
	mvi	B,9	; force min 3 leading flags
	djnz	.
Š	mov	A,D
	di
	out	SIO1BD	; send user number
	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:
	lxi	H,SENDprog
	lxi	B,SEND$<8+SIO1BC
	outir
	mvi	B,9	; force min 3 leading flags
	djnz	.
	lbcd	DMANSadr
	ldax	B
	mov	E,A	; get message byte
	mvi	C,SIO1BD
	di
	outp	D	; send userno
	mvi	A,11010000b; reset underrun/EOM status
	out	SIO1BC	; reset underrun status
..3:	in	SIO1BC
	bit	TxRDY,A	; wait for transmitter empty
	jrz	..3
	outp	E	; send message byte
;
; Handle the end of the transmission
..fin:	ei
..4:	in	SIO1BC	; wait for transmit underrun
	bit	6,A
	jrz	..4
..5:	in	SIO1BC	; wait for CRC complete
	bit	2,A
	jrz	..5
	mvi	B,10h	; force min 3 closing flags
	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
;	      DE = timeout count (master only)
;	      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
;  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 (see RECNET)
;  Destroyed: BC, HL
NACKpoll:
	ei		; make sure SIO can interrupt
	lxi	H,RECstat; point to receiver status
	mvi	M,1	; tell REClast to not ack polls
	.ifn	MASTopt,[
	lxi	D,$4sec; if slave, timeout is 4 sec
	]
;
; Wait for block received from network cable
..wait:	mvi	B,8	; inner loop count
..loop:	mov	A,M	; check receiver status
	bit	4,A
	rnz		; return if block received
	djnz	..loop
	dcx	D	; decrement timeout count
	mov	A,E
	ora	D
	jrnz	..wait	; fall through if timeout
	call	REClast	; pretend we received a block
	sub	A	; return "timeout" status
	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	DMANRadr; DMA address
	dcx	B	
	sbcd	DMANRsize; DMA length = real length-1
	mov	A,C
	ora	B
	jrz	..prog
;
; Receive more than one byte
	lxi	H,DMARdone; setup the DMA vector
	shld	DMAvect
	mvi	A,1	; multiplex SIO1B to DMA
Š	out	PIOAD
	lxi	H,DMANRprog; 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.SP:	.word	0
REC.HL:	.word	0
	.ifn	MASTopt,[
	.byte	76h,76h,76h,76h,76h,76h,76h,76h
	.byte	76h,76h,76h,76h,76h,76h,76h,76h
	.byte	76h,76h,76h,76h,76h,76h,76h,76h
RECstck:
	]
;----------
; SDLC receive interrupt on first char
RECfirst:
	push	PSW
	in	SIO1BD	; flush user number
	lda	DMANRsize; if len = 0, don't use DMA
	ora	A
	jrnz	..DMA
	nop		; delay needed sometimes
	in	SIO1BD	; get message byte
	shld	REC.HL
	lhld	DMANRadr; 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
	lda	DMANRsize; 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
	mvi	A,1	; get receive status
	out	SIO1BC
	in	SIO1BC
	ani	11100000b; mask out relevant bits
	set	4,A
	sta	RECstat
	mvi	A,18h
	out	SIO1BC	; reset the SIO1B chip
	pop	PSW
	reti
;----------
; 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
;----------
; 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
startNET==	14h	; start spool command
read1NET==	15h	; read 1K command
stopNET ==	16h	; stop spool command
assnNET	==	17h	; assign command
hogNET  ==	18h	; hog command
lockNET	==	19h	; lock command
unlockNE==	1Ah	; unlock command
clrlockN==	1Bh	; clearlocks command
ŠspoolNET==	1Ch	; spool command
;------------------------------------------------
; Network protocols:
;
;		MASTER	   SLAVE
;               ------     -----                 
; Poll:   	poll   --> 
;		       <-- pollack
; -----------------------------------------------
; Write:	poll   -->
;		       <-- writeNET
;		mesack -->
;		       <-- DATA
;		datack -->
; -----------------------------------------------
; Read:		poll   -->
;		       <-- readNET
;		DATA   -->
;		       <-- datack
; -----------------------------------------------
; Login:	poll   -->
;		       <-- loginNET
;		logack -->
;	       (logdeny)
; -----------------------------------------------
; Assign:       poll   -->
;		       <-- assignNET
;		DATA   -->
;------------------------------------------------
; Hog:		poll   -->
;		       <-- hogNET
;		hogack -->
;	       (hogdeny).
;		       <-- pollack
;------------------------------------------------
; SIO 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
RECable:.byte	    11111101b ; enable receiver
REC$	==	.-RECprog
;----------
; DMA commands
DMANSprog:
	.byte	0C3h	; master reset		     2D
	.byte	0C7h	; reset port A		     2D
	.byte	0CBh	; reset port B		     2D
	.byte	79h	; write
DMANSadr:.word	0	; filled by SENDNET or RECstart
DMANSsiz:.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
	.byte	writeDMA; write
	.byte	0CFh	; load starting address	     1A
	.byte	0ABh	; enable interrupts	     2D
DMAN$	==	.-DMANSprog
	]
;
DMANRprog:
	.byte	0C3h	; master reset		     2D
	.byte	0C7h	; reset port A		     2D
	.byte	0CBh	; reset port B		     2D
	.byte	7Dh	; read
DMANRadr:.word	0	; filled by SENDNET or RECstart
DMANRsiz:.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
	.byte	readDMA	; read
	.byte	0CFh	; load starting address	     1A
	.byte	0ABh	; enable interrupts	     2D
	]
;
	.ife	TIMERopt,[
;----------
; Process a timer interrupt
;
TIME.SP:.word	0
	.byte	76h,76h,76h,76h,76h,76h,76h,76h
	.byte	76h,76h,76h,76h,76h,76h,76h,76h
	.byte	76h,76h,76h,76h,76h,76h,76h,76h
	.byte	76h,76h,76h,76h,76h,76h,76h,76h
ŠTIMEstack:
TIMERint:
	push	PSW
	.ife	MASTopt,[
	lda	LOCALflag
	ora	A
	jrz	..saveregs
	]
	ei
..saveregs:
	sspd	TIME.SP
	lxi	SP,TIMEstack
	push	H
	push	D
	push	B
;
; Update fractions of a second
..uptime:
	lxi	H,ticks
	lda	ticsec	; 60 for U.S., 50 for Europe
	inr	M
	cmp	M
	jrnz	..timeout
	mvi	M,0
	inx	H
;
; Jump to local user once per second
	.ife	MASTopt,[
	call	LOCALtime
	lxi	H,secs
	]
;
; Update seconds
	mvi	A,60
	inr	M
	cmp	M
	jrnz	..timeout
	mvi	M,0
	inx	H
;
; Update minutes
	inr	M
	sub	M
	jrnz	..timeout
	mov	M,A
	inx	H
;
; Update hours
	mvi	A,24
	inr	M
	sub	M
	jrnz	..timeout
	mov	M,A
;
; Update days
	inx	H
	inx	H
Š	inr	M
;
; Decrement each active down-counter, and call each
; time-out routine whose counter has reached zero.
..timeout:
	lxi	H,counters
;
; 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
	jmpr	..ret
..1:	inx	H
	]
;
; Invoke the network master every 1/60 second
	.ife	MASTopt,[
	mov	A,M
	ora	A
	jrz	..ret
	dcr	M
	jrnz	..ret
	lda	lockbyte; delay master until
	ora	A	; DMA chip is available
	jrz	..mast
	inr	M	; wait another clock tick
	jmpr	..ret
..mast:	pop	B
	pop	D
	lspd	TIME.SP
	pop	PSW
	lxi	H,MASTER
	push	H
	lhld	TIMEstack-2; restore HL
	reti		; goto network master code
	]
;
; Restore the user's registers and return
..ret:	
	pop	B
	pop	D
	pop	H
	lspd	TIME.SP
	pop	PSW
	ei
	reti
;
; Come here once per second
	.ife	MASTopt,[
INTERUPT:
Š	ret
	]
;
; Event down-counters are stored here
counters:
	.ife	FLOPopt, [timeMOTOR: .byte 0]
	.ife	MASTopt, [timeMAST:  .byte 0]
	]
	
;----------
; Print a variable length message
;  Regs in:   HL = address of message (ended by null)
PRTMSG:	
	mov	A,M	; get a byte
	ora	A	; if null, then return
	rz
	mov	C,A	; put it where CONOUT wants it
	lda	Mode
	bit	ModeMSG,A
	cnz	Bufsave
	call	FORCEout; output the byte
	inx	H	; point to next byte
	jmpr	PRTMSG
;----------
; Print a fixed length message
;  Regs in:  HL = address of message
; 	     B  = length of message
PRTSTR:
	mov	C,M	; get a byte
	lda	Mode
	bit	ModeMSG,A
	cnz	Bufsave
	call	FORCEout; print it
	inx	H	; point to next byte
	djnz	PRTSTR	; loop until all bytes printed
	ret
;----------
; Print a byte in hex format
;  Regs in:  A = byte to be printed
PRTBYT:
	push	PSW
	rrc
	rrc
	rrc
	rrc
	call	..nib	; print upper nibble
	pop	PSW	; print lower nibble
..nib:	ani	0Fh
	adi	'0'	; convert 0-9 to hex
	cpi	'9'+1
	jrc	..out
	adi	'A'-('9'+1); convert 10-15 to hex
..out:	mov	C,A
	lda	Mode
	bit	ModeMSG,A
	cnz	Bufsave
	jmp	FORCEout
Š;
PRTADD:	mov	A,H
	push	H
	call	PRTBYT
	pop	H
	mov	A,L
	call	PRTBYT
	ret
;
;
clrsbuf:
	lxi	H,6000h
	shld	Buffad
	mvi	M,20h
	lxi	D,6001h
	lxi	B,1fffh
	ldir
	mvi	A,0
	sta	ERRORS
	ret
;
Buffad: .word	6000h
;
Bufsave:
	push	H
	lda	Buffad+1
	cpi	80h
	jrz	..cont
	lhld	Buffad
	mov	M,C
	inx	H
	shld	Buffad
..cont:	pop	H
	ret
;----------
; Lock the DMA chip (prevent anyone else from using
;		     it until current user is finished)
readDMA	==	1	; read using DMA
writeDMA==	5	; write using DMA
lockbyte:.byte	0	; initially unlocked
lockDMA:
	ei		; must allow int while waiting
..wait:	lda	lockbyte
	ora	A	; if locked, then wait until
	jrnz	..wait	; current user unlocks it
	.ife	MASTopt, [di]; no ints until DMA going
	cma		; lock the DMA chip
	sta	lockbyte
	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 T'
hmsg:	.asciz	' H'
smsg:	.asciz	' S'
IOERR:
	pop	D	; compute address of caller
	push	B	; save pointer to THS
	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.  
; FDTEST sets 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
	call	PRTSTR	; print a four-letter code
	lxi	H,ermsg2
	call	PRTMSG	; print trailing message
;
; Print track, head, sector number
	pop	B	; restore pointer to THS
	ldax	B
	push	B
	call	PRTBYT	; print track
	lxi	H,hmsg
	call	PRTMSG
	pop	B
	inx	B
	ldax	B
	push	B
	call	PRTBYT	; print head
	lxi	H,smsg
	call	PRTMSG
	pop	B
	inx	B
	ldax	B
	call	PRTBYT	; print sector
;
; Wait for the user to hit <return> or <control-C>
waituser:
	call	CONIN
	cpi	cntlC
Š	jz	0
	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	HARCerr ; hard disk command error
	.ascii	'HARC'
	.word	HARDerr	; hard disk data error
	.ascii	'HARD'
	.word	HARFerr	; hard disk fault error
	.ascii	'HARF'
	.word	HARSerr	; hard disk sector error
	.ascii	'HARS'
	]
numerr	==	(.-errtab)/6
	.ascii	'I/O '	; default error code
;----------
; Handle an unexpected interrupt
INTmsg:	.asciz	[bell][cr][lf]'*** INT error'
INTerr:	
	push	H
	push	D
	push	B
	push	PSW
	lxi	H,INTmsg
	call	PRTMSG
	pop	PSW
	pop	B
Š	pop	D
	pop	H
	ei
	reti
;----------
; Handle an invalid BIOS call
CALLmsg:.asciz	[bell][cr][lf]'*** CALL error'
CALLerr:
	lxi	H,CALLmsg
	call	PRTMSG
	jmp	waituser
;----------
; 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: DE
cpmMAP:
	lda	cpmDSK	; get current disk number
	jmpr	map1
iobMAP:
	lda	iobDSK	; get current flop disk number
map1:	cpi	0FFh	; check for boot disk
	jrnz	map2
	lda	DSKBOOT	; return map byte for boot disk
	ret
map2:
	rlc
	rlc
	rlc
	rlc		; A = disk number * 16
	mvi	H,0
	mov	L,A
	lxi	D,DSKMAP
	dad	D	; HL = disk map address
	push	H	; save disk map address
	lxi	D,10
	dad	D	; find pointer to disk params
	mov	E,M
	inx	H
	mov	D,M
	dcx	D
	ldax	D	; get map byte
	pop	H	; restore disk map address
	xchg
Š	ret
;----------
; Default logical to physical disk maps
;
;----------
; Warm boot disk
DSKBOOT:
	.ife	NETboot , [.byte NET+0 ]
;----------
; Drives A to D
DSKMAP:
DSK0:	
	.word	sectab	; sector translate table
	.byte	0,0,0,0,0,0 ; scratch area
	.word	dirbuf	; directory buffer
	.word	DSKpb0	; disk parameter block
	.word	check0	; check vector
	.word	alloc0	; allocation vector
DSK1:
	.word	sectab	; sector translate table
	.byte	0,0,0,0,0,0 ; scratch area
	.word	dirbuf	; directory buffer
	.word	DSKpb1	; disk parameter block
	.word	check1	; check vector
	.word	alloc1	; allocation vector
DSK2:
	.word	sectab	; sector translate table
	.byte	0,0,0,0,0,0 ; scratch area
	.word	dirbuf	; directory buffer
	.word	DSKpb2	; disk parameter block
	.word	check2	; check vector
	.word	alloc2	; allocation vector
DSK3:
	.word	sectab	; sector translate table
	.byte	0,0,0,0,0,0 ; scratch area
	.word	dirbuf	; directory buffer
	.word	DSKpb3	; disk parameter block
	.word	check3	; check vector
	.word	alloc3	; allocation vector
;----------
; Default floppy disk parameter blocks
;----------
	.word	lencv0	; max check vector
	.word	lenav0	; max alloc vector
	.blkb	8	; disk name
	.byte	DD+0	; default disk assignment
DSKpb0:	.word	52	; sectors per track
	.byte	4,15,0	; blk shift, blk and ext mask
	.word	242	; number of blocks - 1
	.word	127	; maximum directory entry
	.byte	0C0h,0	; bits indicate directory blks
	.word	32	; check vector size
	.word	2	; number of op sys tracks
;----------
	.word	lencv1	; max check vector
Š	.word	lenav1	; max alloc vector
	.blkb	8	; disk name
	.byte	DD+1	; default disk assignment
DSKpb1:	.word	52	; sectors per track
	.byte	4,15,0	; blk shift, blk and ext mask
	.word	242	; number of blocks - 1
	.word	127	; maximum directory entry
	.byte	0C0h,0	; bits indicate directory blks
	.word	32	; check vector size
	.word	2	; number of op sys tracks
;----------
	.word	lencv2	; max check vector
	.word	lenav2	; max alloc vector
	.blkb	8	; disk name
	.byte	SD+1	; default disk assignment
DSKpb2:	.word	26	; sectors per track
	.byte	3,7,0	; blk shift, blk and ext mask
	.word	242	; number of blocks - 1
	.word	63	; maximum directory entry
	.byte	0C0h,0	; bits indicate directory blks
	.word	16	; check vector size
	.word	2	; number of op sys tracks
;----------
	.word	lencv3	; max check vector
	.word	lenav3	; max alloc vector
	.blkb	8	; disk name
	.byte	DD+1	; default disk assignment
DSKpb3:	.word	52	; sectors per track
	.byte	4,15,0	; blk shift, blk and ext mask
	.word	242	; number of blocks - 1
	.word	127	; maximum directory entry
	.byte	0C0h,0	; bits indicate directory blks
	.word	32	; check vector size
	.word	2	; number of op sys tracks
;----------
; Sector translation table for single density floppy
sectab:	.byte	1,7,13,19,25,5,11,17,23,3,9,15,21
	.byte	2,8,14,20,26,6,12,18,24,4,10,16,22
;----------
; Size of check and allocation vectors
	.ife	NETboot,[
lencv0	==	256	; max 1024 directory entries
lenav0	==	256	; max 2048 blocks
lencv1	==	256	; max 1024 directory entries
lenav1	==	256	; max 2048 blocks
lencv2	==	256	; max 1024 directory entries
lenav2	==	256	; max 2048 blocks
lencv3	==	256	; max 1024 directory entries
lenav3	==	256	; max 2048 blocks
	]
;
; Storage for check and alloc vectors
check0: .blkb	lencv0
alloc0: .blkb	lenav0
check1: .blkb	lencv1
Šalloc1: .blkb	lenav1
check2: .blkb	lencv2
alloc2: .blkb	lenav2
check3: .blkb	lencv3
alloc3: .blkb	lenav3
;
; Storage for directory
dirbuf:	.blkb	128	; directory buffer
;----------
; Default custom printer driver
;
; Diablo printer driver (compatible with any printer
;			 which uses XON/XOFF protocol)
PORTUout:
	lxi	H,..flag
	in	SIO2AC	; see if printer trying to
	ani	1<RxRDY	;  tell us something
	jrz	..chkstat
	in	SIO2AD	; should be cntlS or cntlQ
	ani	7Fh
	cpi	cntlQ
	jrnz	..chkcntl
	mvi	M,0FFh	; its OK to send
..chkcntl:
	cpi	cntlS
	jrnz	..chkstat
	mvi	M,0	; its not OK to send
..chkstat:
	in	SIO2AC
	ani	1<TxRDY	; see if transmitter ready
	ana	M	;  and also cntlQ active
	jrz	PORTUout; retry if not
	mov	A,C
	out	SIO2AD	; print char
	ret
..flag:	.byte	0FFh	; initially OK to send
.end
