	.TITLE	IOB6120 I/O Board extension ROM

;   Copyright (C) 2003 by J. Kearney, Bolton, Massachusetts
;   Portions adapted from and Copyrighted (C) 2001-2003
;   by R. Armstrong, Milpitas, California.
;
;   This program is free software; you can redistribute it and/or modify
; it under the terms of the GNU General Public License as published by
; the Free Software Foundation; either version 2 of the License, or
; (at your option) any later version.
;
;   This program is distributed in the hope that it will be useful, but
; WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
; or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
; for more details.
;
;   You should have received a copy of the GNU General Public License along
; with this program; if not, write to the Free Software Foundation, Inc.,
; 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
;

	.HM6120
	.STACK  PAC1, POP1, PPC1, RTN1
	.NOWARN	F

	.TITLE	Edit History

;
;  100		-- Beginning of recorded history (end of alpha development)
;
;  101		-- Added FPGA VHDL version display
;
;  102		-- Added disassembler, X and XP commands, hooked HELP and
;                  TYPEIR.
;
;  103		-- Removed rev 102 changes and incorporated them into BTS6120
;
;  104		-- added clock/calendar functions and commands
;
VERSION=104

	.Title Constants and other definitions

;
; Configuration constants
;

BTSMINVERSION=233	; minimum version of BTS6120 that's acceptable
CFPARITION=7700		; CF partitions start here

;
; SBC6120 Memory map control for panel RAM access
;
		;   DIRECT	INDIRECT
		;   --------	--------
MM0=6400	;   EPROM	RAM	(automatically selected by a RESET)
MM1=6401	;   RAM		EPROM	(used during system initialization)
MM2=6402	;   RAM		RAM	(used almost all the time)
MM3=6403	;   RAM		DISK	(used to access RAM disk only)

;
; SBC6120 RAMdisk address register access
;

LDAR=6410

;
; SBC6120 IOTs to control serial port address
;

PRISLU=6412
SECSLU=6413
KCC=6032	; Clear receive flag and AC
TSF=6041	; Skip if the console transmit flag is set
TLS=6046	; Load AC into transmit buffer and clear the flag

;
; Mapping of EMAs to subsystems
;

CDF_ROM	   = CDF 1
CDF_CF	   = CDF 3
CDF_FPGA   = CDF 5
CDF_STATUS = CDF 7

CDF_BTS0   = CDF 0
CXF_BTS0   = CXF 0
CDF_BTS1   = CDF 1
CXF_BTS1   = CXF 1
CDF_BTS2   = CDF 2
CXF_BTS2   = CXF 2
CDF_HERE   = CDF 3
CIF_HERE   = CIF 3
CXF_HERE   = CXF 3

;
; CompactFlash registers
;

CF_DATA	= 0			; ADDRESS OF DATA REGISTER
CF_ERR	= 1			; ADDRESS OF ERROR REGISTER
CF_FEAT	= 1			; ADDRESS OF FEATURES REGISTER
CF_CNT	= 2			; ADDRESS OF SECTOR COUNT REGISTER
CF_LB0	= 3			; ADDRESS OF LB0-7 / SECTOR NUMBER REGISTER
CF_LB1	= 4			; ADDRESS OF LB8-15 / LOW CYLINDER REGISTER
CF_LB2	= 5			; ADDRESS OF LB16-23 / HIGH CYLINDER REGISTER
CF_LB3	= 6			; ADDRESS OF LB24-27 / HEAD/DRIVE REGISTER
CF_STAT	= 7			; ADDRESS OF STATUS REGISTER
CF_CMD	= 7			; ADDRESS OF COMMAND REGISTER

; IDE status register (REGSTS) bits...
STSBSY=0200	; busy
STSRDY=0100	; device ready
STSDF= 0040	; device fault
STSDSC=0020	; device seek complete
STSDRQ=0010	; data request
STSCOR=0004	; corrected data flag
STSERR=0001	; error detected

; IDE command codes (or at least the ones we use!)...
CMDIDD=354	; identify device
CMDRDS=040	; read sectors with retry
CMDWRS=060	; write sectors with retry

;
; Bit definitions on status port
;

STAT_INIT=0002
STAT_DONE=0001
STAT_JUMPER=0010
STAT_CFDETECT=0020
STAT_CFRDY=0040

;
; FPGA IOTs
;

FVERSION=6422
FUNLOCK=6423
FSETCON=6424
FREPROG=6425
TESTSIG=6426
TESTBUS=6427

KEY=5253

;
; Vector table locations.  Encoded as (Offset * 2) + Field_Number
;

; Field 0 vectors
VRET0F2	=  0.
VUPC	=  2.			; V User PC
VUAC	=  4.			; V User AC
VUFLAGS	=  6.			; V User Flags
VSYSIN9	=  8.
VCONOUT	= 10.
VCONIN	= 12.
VOUTCHR	= 14.
VINCHRS	= 16.
VTOCT4	= 18.
VCRLF	= 20.
VSPACMP	= 22.
VSPACM0	= 24.
VBACKUP	= 26.
VEOLTST	= 28.
VEOLNXT	= 30.
VGET	= 32.
VOCTNW	= 34.
VRANGE	= 36.
VRESTA	= 38.
VCOMERR	= 40.
VTDECNW	= 42.
VNODISK	= 44.
VTYPEIR	= 46.			; V  print the current IR
VUIR	= 48.			; V  user IR
VADDR	= 50.			; V  start address returned from RANGE
VADDRFLD= 52.			; V  start field returned from RANGE
VHIGH	= 54.			; V  end address returned from RANGE
VHIGHFLD= 56.			; V  end field returned from RANGE
VPARMAP	= 58.			; V  partition map
; Field 1 vectors
VRET1F2	=  1.
VFUNTBL	= 3.			; V  table of ROM functions
VCMDTBL	= 5.			; V  table of monitor commands
VDISKRD	= 7.			; H  read IDE
VDISKWR	= 9.			; H  write IDE
VDKPART	= 13.			; V  partition number for DISKRD/WR
VDKRBN	= 15.			; V  block number for DISKRD/WR
VBUFPTR	= 17.			; V  offset for DISKRD/WR
VBUFCDF	= 19.			; V  field for DISKRD/WR
VBUFPNL	= 21.			; V  memory space for DISKRD/WR
VDKSIZE	= 23.			; V  size of IDE attached drive

;
; OPR microinstructions that load the AC with various special constants...
;
NL0000=CLA			; all models
NL0001=CLA IAC         		; all models
NL0002=CLA CLL CML     RTL	; all models
NL2000=CLA CLL CML     RTR	; all models
NL3777=CLA CLL     CMA RAR	; all models
NL4000=CLA CLL CML     RAR	; all models
NL5777=CLA CLL     CMA RTR	; all models
NL7775=CLA CLL     CMA RTL	; all models
NLM3=NL7775			; all models
NL7776=CLA CLL     CMA RAL	; all models
NLM2=NL7776			; all models
NL7777=CLA         CMA		; all models
NLM1=NL7777			; all models
NL0003=CLA STL IAC RAL		; PDP-8/I and later
NL0004=CLA CLL IAC RTL		; PDP-8/I and later
NL0006=CLA STL IAC RTL		; PDP-8/I and later
NL6000=CLA STL IAC RTR		; PDP-8/I and later
NL0100=CLA IAC BSW		; PDP-8/E and later
NL0010=CLA IAC R3L		; HM6120 only

	.Title Variables
	.FIELD 3

	.ORG	0000

BTSVER:	.BLOCK	1
F0VECTOR: .BLOCK 1
F1VECTOR: .BLOCK 1
F2VECTOR: .BLOCK 1
POP0T3:	.BLOCK	1
POP1T3:	.BLOCK	1
POP2T3:	.BLOCK	1

	.ORG	0010		; autoindex variables

ARGPTR:	.BLOCK	1
F0PATCH: .BLOCK	1
F1PATCH: .BLOCK	1
F2PATCH: .BLOCK	1
AX0:	.BLOCK	1
AX1:	.BLOCK	1
AX2:	.BLOCK	1
ASCP:	.BLOCK	1		; pointer for TASCIZ

	.ORG	0020

RAMPTR:	.BLOCK 1
PUSHAC:	.BLOCK	1
DESTA:	.BLOCK	1

; pointers to BTS6120 stuff we want to have available
; these will be set up by INITVECT ...
PRESTA:	.BLOCK	1		; monitor restart vector (field 0)
PDKSIZE: .BLOCK	1
PPARMAP: .BLOCK	1
PDKPART: .BLOCK	1
PDKRBN:	.BLOCK	1
PBUFPTR: .BLOCK	1
PBUFCDF: .BLOCK	1
PBUFPNL: .BLOCK	1
PDISKWR: .BLOCK	1
PDISKRD: .BLOCK	1
PNODISK: .BLOCK	1
; .. to here

X0:	.BLOCK	1
X1:	.BLOCK	1
X2:	.BLOCK	1
X3:	.BLOCK	1

FPGAERR: .BLOCK	1
CFSIZE:	.BLOCK	1		; 0 if not present, else MB

BUFSIZ:	.BLOCK	1
CFPART:	.BLOCK	1
CFRBN:	.BLOCK	1

RWRET:	.BLOCK	1
CFTO:	.BLOCK	2

CKINIT:	.DATA	-1		; -1=not inited, 0=missing, 1=present
CCSET:	.DATA	0, 0, 0		; workspace for clock set function


	.TITLE Page Zero Utility Functions

	.ORG	0100

GETV:	0
	CLL
	RAR			; low bit into Link
	SZL
	 JMP	GETV1
GETV0:	TAD	F0VECTOR	; add base
	DCA	X0		; save vector address and clear AC
	CDF_BTS0
	TAD	@X0
	CDF_HERE
	JMP	@GETV
GETV1:	TAD	F1VECTOR	; add base
	DCA	X0		; save vector address and clear AC
	CDF_BTS1
	TAD	@X0
	CDF_HERE
	JMP	@GETV

;   This two routines will allow a routine in field 3 to simulate a .PUSHJ
; to a routines in field 0 or 1. The contents of the AC are preserved both
; ways across the call.
;
;CALL:
;	JMS	PJ0F3 or PJ1F3	; cross field call "PUSHJ to 0 From 2"
;	 <addr>			; address of a routine in field 1
;	<return here>		; with the AC preserved across the call
;
PJ0F3:	0			; call here with a JMS instruction
	DCA	PUSHAC		; save the caller's AC for a minute
	CDF_HERE
	TAD	@PJ0F3		; then get caller's argument
	DCA	DESTA		; that's the address of the routine to call
	TAD	PJ0F3		; now get caller's return address
	IAC			; and skip over the argument
	.PUSH			; put that on the stack
	CLA			; (PUSH doesn't clear the AC!)
	TAD	POP0T3		; the field 0 routine will return to
	.PUSH			; ...
	CLA			; ...
	TAD	PUSHAC		; restore the original AC contents
	CXF_BTS0		; call with IF = DF = 0
	JMP	@DESTA		; and go to the code in field 0

PJ1F3:	0			; call here with a JMS instruction
	DCA	PUSHAC		; save the caller's AC for a minute
	CDF_HERE
	TAD	@PJ1F3		; then get caller's argument
	DCA	DESTA		; that's the address of the routine to call
	TAD	PJ1F3		; now get caller's return address
	IAC			; and skip over the argument
	.PUSH			; put that on the stack
	CLA			; (PUSH doesn't clear the AC!)
	TAD	POP1T3		; the field 1 routine will return to
	.PUSH			; ...
	CLA			; ...
	TAD	PUSHAC		; restore the original AC contents
	CXF_BTS1		; call with IF = DF = 1
	JMP	@DESTA		; and go to the code in field 1

	.Title Initialization
;
; Extension ROM initialization entry point.  Called with AC containing the
; number of parameter words following the call.  This code is slightly
; tricky in that it is overlaid onto the variable area, and so care must
; be taken not to set any variables at locations that have not been executed yet

; also important: this entry point is called from page 1, and the parameters
; of course are there

	; this stuff overlays the first couple of variables
	.ORG	0

	.SIXBIT	/SBC6/		; 2 word signature, soon to be overwritten
IOBIN0: 0
	JMP	@[IOBIN1]

	.PAGE	1

CHKSUM:	.BLOCK	1

; first, build the return address and set up our parameter pointer

IOBIN1:	TAD	IOBIN0		; AC=num params, so now points past end of them
	.PUSH			; ... which is our return address

	NL7777
	TAD	IOBIN0		; get start of params - 1 for autoincr
	DCA	ARGPTR

; second, copy the parameters

	TAD	@ARGPTR		; fetch version number
	DCA	BTSVER		; stash it away for now

	TAD	@ARGPTR		; fetch and save F0VECTOR
	DCA	F0VECTOR
	TAD	@ARGPTR		; fetch and save F0PATCH-1
	DCA	F0PATCH

	TAD	@ARGPTR		; fetch and save F1VECTOR
	DCA	F1VECTOR
	TAD	@ARGPTR		; fetch and save F1PATCH-1
	DCA	F1PATCH

	TAD	@ARGPTR		; fetch and save F2VECTOR
	DCA	F2VECTOR
	TAD	@ARGPTR		; fetch and save F2PATCH-1
	DCA	F2PATCH

	CDF_BTS0
	TAD	@F0VECTOR	; get 1st vector to .POPJ hook
	DCA	POP0T3		; so that we can call Field 0 routines
	CDF_BTS1
	TAD	@F1VECTOR	; get 1st vector to .POPJ hook
	DCA	POP1T3		; so that we can call Field 1 routines
	CDF_BTS2
	TAD	@F2VECTOR	; get 1st vector to .POPJ hook
	DCA	POP2T3		; so that we can call Field 2 routines

; now call the various initializations.  If anything fails, return an
; error code to BTS6120

	PRISLU			; just in case

	TAD	BTSVER		; get back the version
	TAD	[-BTSMINVERSION] ; check it against our minimum requirement
	SMA CLA
	 JMP	 IOBIN2		; okay, same or newer than that
	NL0003			; error code 0003 - bad BTS version
	JMP	XINIT

IOBIN2:
	.PUSHJ	@[INITVECT]	; link calls into BTS from this code

	CDF_BTS0		; access Field 0

	; n.b. autoincr AX0 was set up by INITVECT to point to SYSINI9-1
	TAD	[CXF_HERE]		; set up CXF / JMP @[.+1] / INI9HOOK
	DCA	@AX0

	NL0002			; compute address of constant INI9HOOK
	TAD	AX0
	AND	[0177]		; make into a JMP @.+1
	TAD	[5600]
	DCA	@AX0

	TAD	[INI9HOOK]	; and store the address
	DCA	@AX0

	CDF_HERE

	.PUSHJ	@[INITCFC]	; initialize CF
	.PUSHJ	@[CFHOOKS]	; hook up CFC

	.PUSHJ	@[IFPGA]	; initialize FPGA
	SZA			; on error, return
	 JMP	 XINIT

	.PUSHJ	@[ICONS]	; initialize console

	NL0001			; successful init, return code 1

; return to our regularly scheduled initialization
XINIT:	CXF_BTS2
	.POPJ

	.PAGE

;
; IFPGA: initialize FPGA
;
IFPGA:	CLA			; set up error return
	DCA	FPGAERR		; (assume no error)

	MM3			; access IOB address space

	CDF_STATUS		; check the FPGA INIT signal
	TAD	@0
	AND	[STAT_INIT]
	SNA CLA			; if it's low,
	 JMP	 FPGAE4		; ... FPGA not ready for bitstream

; copy fields 8..31 of the ROM to the FPGA

	TAD	[-24.]		; 24 fields
	DCA	X0
	TAD	[8.]		; first DAR field
	DCA	X1
	DCA	X2		; starting at offset 0

PROGPG: CDF_STATUS		; check for early finish
	TAD	@0
	AND	[STAT_DONE]
	SZA			; if it's high,
	 JMP	 FPGADC		; ... FPGA thinks it's configured

	TAD	X1		; set ROM field
	LDAR

PROGRM:	CDF_ROM			; fetch byte from ROM
	TAD	@X2
	CDF_FPGA		; send to FPGA
	DCA	@0

	ISZ	X2		; next in field
	 JMP	 PROGRM

	ISZ	X1		; next field
	ISZ	X0		; last field?
	 JMP	 PROGPG		; no, do next field

	CDF_STATUS		; check the FPGA DONE signal
	TAD	@0
	AND	[STAT_DONE]
	SNA			; if it's low,
	 JMP	 FPGAE5		; ... bitstream did not work

; now we do a self-test using IOTs that the FPGA should now be
; implementing

; the first test uses the TESTSIG IOT to make sure that SKIP, C0, and C1
; are properly connected and behave as expected

FPGADC:	NL0004			; ping C0 only
	TESTSIG
	 JMP	 .+2		; should be no skip
	JMP	 FPGAE6
	SZA			; AC should have been cleared
	 JMP	 FPGAE7

	NL0002			; ping C1 only
	TESTSIG
	 JMP	 .+2		; should be no skip
	JMP 	FPGAE6
	TAD	[-5252]		; 5252 should have been read
	SZA
	 JMP	 FPGAE8

	NL0001			; ping SKIP only
	TESTSIG
	 JMP	 FPGAE6		; should skip
	TAD	[-0001]		; AC should be untouched
	SZA
	 JMP	 FPGAE8

; the second test uses the TESTBUS IOT to make sure that all 12 data bus
; lines are connected and behave as expected

	TAD	[4000]
BUSTST:	DCA	X1
	TAD	X1
	TESTBUS			; should return complement
	TAD	X1		; now should be 7777
	IAC
	SZA
	 JMP	 FPGAE9
	TAD	X1     		; check next bit
	RTR
	SNA			; until whole word tested
	 JMP	 BUSTST

	CLA			; everything appears OK!
	JMP	RFPGA

FPGAE9:	ISZ	FPGAERR		; bus connection failure
FPGAE8:	ISZ	FPGAERR		; C1 failure
FPGAE7:	ISZ	FPGAERR		; C0 failure
FPGAE6:	ISZ	FPGAERR		; skip failure
FPGAE5:	ISZ	FPGAERR		; DONE inactive
FPGAE4:	ISZ	FPGAERR         ; INIT signal active
	NL0003
	TAD	FPGAERR
RFPGA:	MM2
	.POPJ

; Initialize console.  If the IOT1 GAL supports changing the SBC serial address,
; and the jumper is present, swap the the VT52 and the serial port.
; We can tell if the GAL has been updated because the PRISLU IOT will also
; set AC=0.

ICONS:  STA 			; make sure AC is non-zero
	PRISLU			; reset to primary address
	SZA CLA
	 .POPJ			; return if the AC did not clear

	TAD	[KEY]		; set the VT52 to the secondary address
	FUNLOCK                 ; in case jumper was removed w/o power off
	TAD	[3637]		; => ports 36/37
	FSETCON

	MM3			; get the status port
	CDF_STATUS
	CLA
	TAD	@0
	MM2

	AND	[STAT_JUMPER]	; get the jumper
	SZA			; and if it's not installed (1)
	 .POPJ			 ; ... leave the console at the serial port

; jumper is installed, switch console to VT52

	SECSLU			; set SBC6120 serial console to 36/37
	TAD	[KEY]
	FUNLOCK
	TAD	[0304]		; set VT52 to ports 03/04
	FSETCON

	.POPJ			; done!

	.PAGE

;
; INITVECT: copy vectors that the extension ROM needs from
; the tables found in BTS6120
;
INITVECT: TAD	[PATCHTAB-1]
	DCA	AX1
	CDF_HERE
INVE1:	TAD	@AX1
	SNA
	 .POPJ
	DCA	AX2
	TAD	@AX1
	JMS	GETV
	DCA	@AX2
	JMP	INVE1

;
; INI9HOOK: BTS6120 will jump here once it has done its basic
; initialization and printed the banner and device information.
; It will do this because the first phase of IOB initialization
; patched in a jump to here.
;
INI9HOOK:
	CDF_HERE

	CLA
	TAD	[CFCMSG-1]	; say
	.PUSHJ	@[TASCIZ]	; "CFC: "

	TAD	CFSIZE		; check size
	SZA
	 JMP	 SHOWCF
	TAD	[CNPMSG-1]	; say "No CF Card"
	JMP	CFEMG		; but since we can hot plug we
				; will use a more neutral term

SHOWCF:	TAD	[-1]
	JMS	PJ0F3
PTDEC:	0
	TAD	[CSZMSG-1]	; say
	.PUSHJ	@[TASCIZ]	; "MB - "

	DCA	@[SECBUF+94.]	; make ID string ASCIZ
	TAD	[SECBUF+54.-1]	; and print it

CFEMG:	.PUSHJ	@[TASCIZ]
	.PUSHJ	@[TCRLF]

	CLA			; is the FPGA alive?
	TAD	FPGAERR
	SZA
	 JMP	INI9B		; no, only perform inits that don't use it

	TAD	[SAMSG-1]	; say
	.PUSHJ	@[TASCIZ]	; "IOB: ROM V"
	TAD	[VERSION]
	JMS	PJ0F3		; then the version
PTOCT4:	0
	TAD	[S2MSG-1]	; say
	.PUSHJ	@[TASCIZ]	; ", FPGA V"
	FVERSION		; IOT to get FPGA version
	JMS	PJ0F3		; and print that
PTOCT4B: 0
	.PUSHJ	@[TCRLF]

	.PUSHJ	@[INITCC]	; check for/init the clock/calendar
	SPA SNA CLA		; skip if AC > 0, clear AC
	 JMP	 NOCCP		 ; -1 or 0, not present

	.PUSHJ	@[HOOKCMDS]	; add DA command, H hook

	TAD	[CCPMSG-1]	; say
	.PUSHJ	@[TASCIZ]	; "C/C: "
	.PUSHJ	@[DSPCC]	; then the date/time
	.PUSHJ	@[TCRLF]
NOCCP:

	;*** add other FPGA-related inits here ***
	
INI9B:	ISZ	PRESTA		; since it's a hook, the stored value is x-1
	CXF_BTS0		; but we just want to use it as a jmp vector
	JMP	@PRESTA		; and start the monitor

;
; TCRLF: print a newline
;
TCRLF:	JMS	PJ0F3
PCRLF:	0			; patched with BTS6120 CRLF routine
	.POPJ

;
; TASCIZ: print an ASCII string terminated by 0
;
TASCIZ:	DCA	ASCP		; save the pointer to the string
TASCI1:	TAD	@ASCP		; and get the first character
	SNA			; is this the end of the string ??
	 .POPJ 			; yes -- quit now
	JMS	PJ0F3		; no -- type this character
POUTCHR: 0			; patched with BTS6120 print char routine
	JMP	TASCI1		; and then loop until the end

	.PAGE
;
; CFHOOKS: intercept DISKRD and DISKWR so that we can seamlessly
; access the CF card via a partition number mapping
;
CFHOOKS:
	CDF_BTS1

	TAD	PDISKRD
	DCA	AX0
	TAD	[DISKRDHOOK]
	.PUSHJ	INTCPT
	DCA	PDISKRD

	TAD	PDISKWR
	DCA	AX0
	TAD	[DISKWRHOOK]
	.PUSHJ	INTCPT
	DCA	PDISKWR

; Also disable NODISK so that we can do our own checking
; otherwise the BTS6120 disk commands would balk if there
; was no IDE drive installed. It would be nice if we could
; check here, but NODISK is called before the partition
; number has been parsed.

	CDF_BTS0

	TAD	PNODISK
	DCA	AX0
	TAD	[CLA CLL]
	DCA	@AX0
	TAD	[6225] ; [.POPJ]
	DCA	@AX0

	CDF_HERE
	.POPJ

;
; INTCPT: patch one routine that has 3 words at AX0+1 with the address in AC
;
INTCPT:	.PUSH
	CLA
	TAD	[CIF_HERE]	; set up CIF / JMP @[.+1] / <AC>
	DCA	@AX0

	NL0002			; compute address of constant
	TAD	AX0
	AND	[0177]		; make into a JMP @.+1
	TAD	[5600]
	DCA	@AX0

	.POP			; and store the address
	DCA	@AX0

	TAD	AX0		; and return next code address
	IAC
	.POPJ

	.PAGE

;
; INITCFC: check for a drive.  This is only used at startup to print
; a nice message, really, since it's also allowed to hot-plug a CF
; card.
;

INITCFC:
	CLA			; start out assuming that it's not present

;	MM3
;	CDF_CF
;	TAD	[340]		; set drive
;	DCA	@[CF_LB3]
;	TAD	[020]		; recalibrate
;	DCA	@[CF_CMD]

	DCA	CFSIZE
	DCA	CFPART		; zero partition and block
	DCA	CFRBN

	JMS	@[CFRW]		; call the generic CF I/O routine
	CMDIDD			; with the IDE identify command
	RDTRANS			; and the Read loop

	SZL			; successful?
	 .POPJ			;  no - could not identify

	NLM2			; aka [-2]
	TAD	@[SECBUF+10.]	; make sure sector size is 512
	SZA
	 .POPJ

;   Drives report the total number of LBA addressable sectors in words
; 60 and 61.  Sectors are 512 bytes, so simply dividing this value by 2048
; gives us the total drive size in Mb.  This code patches together twelve
; bits out of the middle of this doubleword, after throwing away the least
; significant 11 bits to divide by 2048.  This allows us to determine the
; size of drives up to 4Gb in a single 12 bit word.
	TAD	@[SECBUF+120.]	; get the high byte of the low word
	RAR			; throw away the 3 least significant
	RTR			; ...
	AND	[37]		; keep just 5 bits from this byte
	DCA	CFSIZE		; save it for a minute
	TAD	@[SECBUF+123.]	; get the low byte of the high word
	RTL			; left justify the seven MSBs of it
	RTL			; ...
	RAL			; ...
	AND	[7740]		; ...
	TAD	CFSIZE		; put together all twelve bits
	DCA	CFSIZE

	.POPJ

	.TITLE Disk Read and Write
;
; DISKRDHOOK: all calls to DISKRD in BTS6120 come here instead.  Here we
; either return to the original IDE code or use the new CF code, depending
; on the partition number.  If a call is made on a non-existent IDE drive,
; we return an error code rather than bombing out to the monitor as used to
; happen.  That was based on the idea that if you had booted, you knew
; that a drive must exist.  Now, there is hot plugging and there is also
; the possibility of booting from one drive and trying to access the other,
; so an error return is preferable.
; n.b. note that the method of storing 12-bit words in 2 8-bit bytes is
; defined by BTS6120 so that we can interoperate with the IDE drive,
; for example copying partitions to and fro. 
;
DISKRDHOOK:
	DCA	BUFSIZ		; save size
	CLA CLL
	TAD	@PDKPART	; get partition and see if it's
	TAD	[-CFPARITION]	; in the CF zone
	SNL
	 JMP	 IDERD		; nope, must be for SBC IDE

	DCA	CFPART		; save partition number on CF

	TAD	@PDKRBN
	DCA	CFRBN

	JMS	@[CFRW]		; call generic CF I/O routine
	CMDRDS			; with IDE read command
	RDTRANS			; and read loop

	SZL			; error?
	 JMP	 DRDXIT		 ; yes - could not read

; we have a sector in SECBUF, now assemble words from it into the destination

	CDF_BTS1

	TAD	[SECBUF-1]	; from the sector buffer
	DCA	AX0

	TAD	@PBUFPTR	; to the passed-in destination
	DCA	AX1

	TAD	@PBUFCDF	; in the passed-in field
	DCA	CDFRD

	TAD	@PBUFPNL	; and the passed in main/panel space
	IAC
	SNA
	 TAD	 [SPD-CPD+1]	; make CPD or SPD from it
	TAD	[CPD-1]
	DCA	RDPD

	CDF_HERE
RDXFR:	TAD	@AX0		; make words from the bytes in the buffer
	BSW
	CLL RTL
	TAD	@AX0
CDFRD:	0
RDPD:	0
	DCA	@AX1		; and store them in the user's buffer
	SPD
	CDF_HERE
	ISZ	BUFSIZ
	 JMP	 RDXFR

	CDF_BTS1
	TAD	AX1		; update the output pointer
	DCA	@PBUFPTR	; for multi-block reads

	CLA CLL			; and return OK
DRDXIT:	CXF_BTS1
	.POPJ

IDERD:	CLA			; make sure an IDE drive exists
	TAD	@PDKSIZE
	SNA CLA
	 JMP	 @[NODISK]	; no, size was 0

	TAD	BUFSIZ		; chain to the original BTS6120
	CXF_BTS1		; DISKRD
	JMP	@PDISKRD

; RDTRANS: low-level read loop from CF to SECBUF
RDTRANS: 0
RDLP:	MM3
	CDF_CF
	TAD	@[CF_DATA]	; flip bytes as they come in
	MQL			; due to the way the CF 8-bit
	TAD	@[CF_DATA]	; interface works
	MM2
	CDF_HERE
	DCA	@RAMPTR
	ISZ	RAMPTR
	MQA
	DCA	@RAMPTR
	ISZ	RAMPTR
	 JMP	 RDLP
	JMP	@RDTRANS

	.PAGE

;
; DISKRDHOOK: all calls to DISKWE in BTS6120 come here instead.  All
; the discussion about DISKRD applies here too.
;
DISKWRHOOK:
	DCA	BUFSIZ		; save size
	CLA CLL
	TAD	@PDKPART	; get partition and see if it's
	TAD	[-CFPARITION]	; in the CF zone
	SNL
	 JMP	 IDEWR		; nope, must be for SBC IDE

	DCA	CFPART		; save partition number on CF

	TAD	@PDKRBN
	DCA	CFRBN

; we want to assemble a sector in SECBUF from words in the source buffer

	CDF_BTS1

	TAD	@PBUFPTR	; from the user buffer
	DCA	AX0

	TAD	@PBUFCDF	; in the user field
	DCA	WRXFR

	TAD	@PBUFPNL	; in the user memory area (panel/main)
	IAC
	SNA
	 TAD	 [SPD-CPD+1]
	TAD	[CPD-1]
	DCA	WRPD

	TAD	[SECBUF-1]	; to the sector buffer
	DCA	AX1

WRXFR:	0
WRPD:	0
	TAD	@AX0		; get the buffer word
	MQL
	SPD
	CDF_HERE
	MQA
	BSW
	RTR
	AND	[17]
	DCA	@AX1		; and break it into bytes
	MQA
	DCA	@AX1
	ISZ	BUFSIZ
	 JMP	 WRXFR

	CDF_BTS1
	TAD	AX0		; update the destination pointer
	DCA	@PBUFPTR	; for multi-block writes

	JMS	@[CFRW]		; call the generic CF I/O routine
	CMDWRS			; with the write sector command
	WRTRANS			; and the write loop

DWRXIT:	CXF_BTS1		; and return status returned from CFRW
	.POPJ

IDEWR:	CLA			; make sure an IDE drive exists
	TAD	@PDKSIZE
	SZA CLA
	 JMP	 HAVWDSK	 ; yes, size was non-zero

NODISK:	STA STL			; no, return -1 error
	CXF_BTS1
	.POPJ

HAVWDSK:TAD	BUFSIZ		; chain to the original BTS6120
	CXF_BTS1		; DISKWR
	JMP	@PDISKWR

; WRTRANS: write loop from SECBUF to the CF card
WRTRANS: 0
WRLP:	TAD	@RAMPTR		; reverse byte order during
	MQL			; transfer to match 8-bit access
	ISZ	RAMPTR		; method
	TAD	@RAMPTR
	MM3
	CDF_CF
	DCA	@[CF_DATA]
	MQA
	DCA	@[CF_DATA]
	MM2
	CDF_HERE
	ISZ	RAMPTR
	 JMP	WRLP
	JMP	@WRTRANS

	.PAGE

; CFRW: generic CF driver code.
;   entry: JMS, CFPART, CFRBN
;	inline parameters:
;	  Command	CF (IDE) command
;	  Handler	routine (JMS) to call for sector transfer
;			  called with SPD / MM2 / CDF_HERE
;			  should return with same
;   exit: L=AC=0 on success, L=1 AC=code on error

CFRW:	0			; call w/ JMS, follow by cmd, transfer vector

	NL0002			; get return address
	TAD	CFRW
	DCA	RWRET

; make sure a card is present

	MM3			; access status port
	CDF_STATUS

	TAD	@[0]
	AND	[STAT_CFDETECT]	; check the CF insert line
	SNA
	 JMP	HAVECF
	NL7777			; Error 7777 - no CF card
	JMP	RWERR1

; wait for CF ready

HAVECF:	CDF_CF
	TAD	[-100.]
	DCA	CFTO+1
	DCA	CFTO		; init timeout counter
WTRDY1:	TAD	@[CF_STAT]
	AND	[STSRDY+STSBSY]	; test the RDY & BSY flags
	TAD	[-STSRDY]	; wait for RDY set and BSY clear
	SNA CLA			; well?
	 JMP	 RDY1		 ; yes, ready!

	ISZ	CFTO
	 JMP	 WTRDY1
	ISZ	CFTO+1
	 JMP	 WTRDY1

	NLM2
	JMP	RWERR1
RDY1:
; set LBA
	TAD	CFRBN		; get the lower 12 bits of of the LBA
	DCA	@[CF_LB0]	; set the low LBA byte to b0..b7
	TAD	CFRBN		; get it again
	BSW			; shift right eight bits
	RTR			; so that b8..b11 are in b0..b3
	AND	[17]		; isolate them
	MQL			; save temporarily
	TAD	CFPART		; get the disk partition number
	AND	[17]		; isoloate the 4 lsb's
	CLL RTL			; shift them up into the 4 msb's
	RTL			; ...
	MQA			; and combine with the 4 msb's of the block
	DCA	@[CF_LB1]	; set the middle LBA byte to that

	TAD	CFPART		; get the disk partition number again
	RTR			; get the 8 msb's into b0..b7
	RTR			; ...
	DCA	@[CF_LB2]	; and store as the high LBA byte

	TAD	[340]		; select the master drive, 512 byte sectors,
	DCA	@[CF_LB3]	; LBA mode, and store in highest LBA byte
	; there are more block number bits in there, but we can't use them

	NL0001			; read or write 1 sector only
	DCA	@[CF_CNT]

; issue command

	MM2
	CDF_HERE
	TAD	@CFRW		; get the command
	MM3
	CDF_CF
	DCA	@[CF_CMD]	; issue it

; wait for DRQ

WTDRQ1:	TAD	@[CF_STAT]
	CLL RAR			; test the error bit (AC11)
	SZL			;
	 JMP	 RWERR
	RAL			; no - restore the original status value
	AND	[STSDRQ]	; and test the DRQ flag
	TAD	[-STSDRQ]	; wait for DRQ set
	SZA CLA			; well?
	 JMP	 WTDRQ1		 ; no, no data is waiting or waited for

; call transfer loop

HAVEDRQ: TAD	[SECBUF]
	DCA	RAMPTR

	MM2
	CDF_HERE
	ISZ	CFRW		; point to transfer function vector
	TAD	@CFRW
	DCA	CFRW
	JMS	@CFRW		; and call it

; wait for ready again (important for write, but doesn't hurt read)

	MM3
	CDF_CF
WTRDY2:	TAD	@[CF_STAT]
	CLL RAR			; test the error bit (AC11)
	SZL			;
	 JMP	 RWERR
	RAL			; no - restore the original status value
	AND	[STSBSY+STSRDY]	; and test the BUSY and RDY flags
	TAD	[-STSRDY]	; wait for BUSY clear and RDY set
	SZA CLA			; well?
	 JMP	 WTRDY2		; no, not yet

	CLA CLL			; everything ok
RWRET0:	MM2
	CDF_HERE
	JMP	@RWRET

RWERR:	CLA
	TAD	@[CF_ERR]
RWERR1:	STL			; there was an error - AC is code
	JMP	RWRET0

	.TITLE Command Extensions
	.PAGE

; HOOKCMDS: extend the BTS6120 commands.
; This function does 2 things:
;  1. Hooks the H command vector so that Help can be extended
;  2. Add new commands at the end of the command list

HOOKCMDS:
	CLA
	TAD	BTSHELP		; INITVECT put CMDTBL here
	DCA	X1		; now pointing at H(elp) entry

	CDF_BTS1
	ISZ	BTSHELP		; 1. move ptr to vector part
	TAD	@BTSHELP	; get current vector
	DCA	BTSHELP		; and save it

	TAD	[IOBHELP]	; change to our new Help implementation
	.PUSHJ	@[PATCHCMD]

EMPCMD: ISZ	X1		; search table for empty slots at end
	ISZ	X1
	TAD	@X1		; get cmd text
	SZA CLA			; 0 indicates end
	 JMP	 EMPCMD

	TAD	[NEWCMDS-1]
	DCA	AX1

ADDCMD: CDF_HERE
	TAD	@AX1		; get command text
	SNA			; end of list?
	 .POPJ			 ; yes - return
	CDF_BTS1
	DCA	@X1		; store in command table
	CDF_HERE
	TAD	@AX1		; and hook in implementation
	.PUSHJ	PATCHCMD
	JMP	ADDCMD

NEWCMDS:
	.SIXBIT	/DA/
	CMDDA
	0

; PATCHCMD: generates a stub function in the BTS6120 Field 0 patch area.
; Used to add commands to CMDTBL.
; Inputs:
;	AC 	Field 2 function to be jumped to
;	X1      pointer in current field. [pointer+1] will be set to the stub
;		address.
; Outputs:
;	X1	incremented by 2
; Side effects:
;	F0PATCH	3 words used
;	DF=1
;	AC=0

PATCHCMD:
	.PUSH			; save the function address
	ISZ	X1		; move pointer to cmd vector part
	NL0002			; generate address of stub in patch area
	TAD	F0PATCH		; note that commands are in Field 0 even though
	CDF_BTS1
	DCA	@X1		; the table is in Field 1.
	ISZ	X1		; move pointer to next cmd string part
	.POP

	CDF_BTS0
	DCA	@F0PATCH	; store function address in patch area
	TAD	[CXF_HERE]	; stub starts with CXF 2
	DCA	@F0PATCH
	NLM1            	; then a jump through the function address,
	TAD	F0PATCH		; which is 2 back (but now 1 because of preincr)
	AND	[177]		; make into a JMP @
	TAD	[5600]
	DCA	@F0PATCH	; and add to stub
	CDF_BTS1
	.POPJ

; DA Command - display or set date and time
;	DA			display date/time
;	DA mm/dd/yy hh:mm:ss	set date/time
;	  (format can be changed by editing the CTDTB table)
;

CMDDA:	JMS	PJ0F3		; get the next character
PSPACMP: 0
	SNA CLA			; is it the end of line?
	 JMP	 DSPTD		 ; yes - just print date

	JMS	PJ0F3		; backup input pointer so GET gets first
PBACKUP: 0			; parameter character

	JMS	@[CCRMW]	; unprotect clock registers
	 CNOWP			;
	JMS	@[CCRMW]	; stop the clock so that race conditions
	 CSTOP			; don't mess us up

	TAD	[CTDTB-1]	; point to start of format table
	DCA	AX2

; process next digit group
NXTDG:	TAD	@AX2		; get the register address
	SNA
	 JMP	 CCSDN
	DCA	CCSET		; save it in parameter block
	TAD	@AX2		; get separator character
	.PUSHJ	@[PRSBCD]	; parse a number
	DCA	CCSET+2		; finish parameter block
	JMS	@[CCRMW]	; update the register
	 CCSET			; n.b. the clock will be started when writing
				; the seconds register because b7=0
	JMP	NXTDG		; do next group

CCSDN:	JMS	@[CCRMW]	; protect clock registers
	 CNOWP			; and fall through into display

DSPTD:  TAD	[TDMSG-1]
	.PUSHJ	@[TASCIZ]
	.PUSHJ	@[DSPCC]
	.PUSHJ	@[TCRLF]
	CXF_BTS0
	.POPJ

BTSHELP: 0

; IOBHELP: Replacement Help command.  First calls the original, then
; prints our new help texts.

IOBHELP:
	TAD	BTSHELP		; get original help routine
	DCA	BTSHLP2
	JMS	PJ0F3		; and call it
BTSHLP2: 0

	TAD	[HLPLST-1]	; point to the list of help messages
	DCA	AX2		; in an auto index register
HLPLP:	TAD	@AX2		; get the next help message
	SNA			; end of list?
	 JMP	 HLPDN		; yes - we can quit now
	.PUSHJ	@[TASCIZ]
	.PUSHJ	@[TCRLF]	; and finish the line
	JMP	HLPLP		; keep typing until we run out of strings
HLPDN:	CXF	0		; and return to command interpreter
	.POPJ

	.TITLE Clock/Calendar Support
	.PAGE

CCCM=6140
CCSF=6141
CCWD=6142
CCRD=6143

; INITCC: check for a clock/calendar chip, initialize it if found

INITCC:	TAD	CKINIT
	SMA
	 .POPJ			 ; already inited, return 0 or 1

	ISZ	CKINIT		; set to 0 = 'missing'

	JMS	CCRMW		; clear write protect
	 CNOWP

	JMS     CCRMW		; write test value
	 CCTEST
	TAD	@[CCTEST+0]	; get the register
	CCCM			; issue command
	CCSF			; and wait for FPGA to process it
	 JMP .-1
	CCRD			; get the data
	CIA
	TAD	@[CCTEST+2]	; compare to the written value
	SZA CLA			; did it match?
	 .POPJ			 ; no, return 0

	JMS	CCRMW		; write protect
	 CWP

	ISZ	CKINIT		; set flag to 1 = "present"
	NL0001			; return 1
	.POPJ

; DISPCC: display the date and time, then CR, LF

DSPCC:	CLA
	TAD     [CTDTB-1]
	DCA	AX1
	JMP	TIMLP
TPRF:	.PUSHJ	COUT
TIMLP:	TAD	@AX1
	CCCM
	CCSF			; wait for ready
	 JMP .-1
	CCRD
	.PUSHJ	TBCD2
	TAD	@AX1
	SZA
	 JMP	 TPRF
	.POPJ

TBCD2:	.PUSH
	RTR
	RTR
	.PUSHJ	COUTD
	.POP
COUTD:	AND	[17]
	TAD	["0"]
COUT:	JMS	PJ0F3
POUTC1:	 0
	.POPJ

CCRMW:	0
	NLM1			; correction for auto-incr
	TAD	@CCRMW		; get arg ptr
	DCA	AX1		; and put in auto-incr
	ISZ	CCRMW		; skip arg on return

	TAD	@AX1		; get the register
	.PUSH			; save a copy
	CCCM			; issue command
	CCSF			; and wait for FPGA to process it
	 JMP .-1
	CCRD			; get the data
	AND	@AX1		; mask ..
	TAD	@AX1		; .. and set
	CCWD			; put it in output register
	.POP			; get command back
	AND	[7776]		; change to a write
	CCCM			; issue it
	CCSF			; and wait for FPGA to process it
	 JMP .-1

	CLA
	JMP	@CCRMW

; PRSBCD - parse the sequence {digit}[{digit}]{terminator} from input
PRSBCD:	CIA
	DCA	X2		; save -terminator
	JMS	NEXT		; get first digit (must be digit)
	JMS	DIGIT
	MQL			; save it in MQ
	JMS	NEXT		; get terminator or next digit
	.PUSH
	TAD	X2		; compare to terminator
	SNA			; matched?
	 JMP	 TERM		 ; yes - return one digit in 1's position
	ACL			; put 1st digit into 10's position
	CLL RTL
	RTL
	MQL
	.POP			; recover current character
	JMS	DIGIT		; and convert to binary
	MQA			; combine with 10's
	MQL			; save it for a moment
	JMS	NEXT		; get what should be the terminator
	TAD	X2		; compare to expected terminator
	SZA			; matched?
	 JMP	 DIGERR		 ; no - error
	SKP
TERM:	.POP
	ACL			; get back the value
	.POPJ

NEXT:	0
	JMS	PJ0F3
PGET:	0			; get next character from input
	JMP	@NEXT

DIGIT:	0
	TAD	[-"0"]		; change to binary
	SPA			; was it before "0"?
	 JMP	 DIGERR		 ; yes, error
	.PUSH			; save it temporarily
	TAD	[-10.]		; was it above "9"?
	SNA
	 JMP	DIGERR		 ; yes, error
	.POP			; restore binary value
	JMP	@DIGIT

DIGERR:	JMS	PJ0F3
PCOMERR: 0

	.TITLE Tables, Messages
	.PAGE

; PATCHTAB: table of vectors that we want to fetch at startup and patch
; in our code.  Format is (destination-1), vector_number.
; While we are free here to mix field 0 and 1 vector numbers, the actual
; call to the function must use PJ0F3 or PJ1F3 as appropriate.
PATCHTAB:
	; general patches
	.DATA	AX0-1, VSYSIN9
	.DATA	PRESTA-1, VRESTA
	.DATA	POUTCHR-1, VOUTCHR
	.DATA	PCRLF-1, VCRLF
	.DATA	PTDEC-1, VTDECNW
	.DATA	PRESTA-1, VRESTA
	.DATA	PTOCT4-1, VTOCT4
	.DATA	PTOCT4B-1, VTOCT4

	; commands, clock/calendar patches
	.DATA	BTSHELP-1, VCMDTBL
	.DATA	POUTC1-1, VOUTCHR
	.DATA	PSPACMP-1, VSPACMP
	.DATA	PBACKUP-1, VBACKUP
	.DATA	PGET-1, VGET
	.DATA	PCOMERR-1, VCOMERR

	; CF patches
	.DATA	PDKSIZE-1, VDKSIZE
	.DATA	PPARMAP-1, VPARMAP
	.DATA	PDKPART-1, VDKPART
	.DATA	PDKRBN-1, VDKRBN
	.DATA	PBUFPTR-1, VBUFPTR
	.DATA	PBUFCDF-1, VBUFCDF
	.DATA	PBUFPNL-1, VBUFPNL
	.DATA	PDISKWR-1, VDISKWR
	.DATA	PDISKRD-1, VDISKRD
	.DATA	PNODISK-1, VNODISK

	.DATA	0

; clock/calendar
CCTEST:	.DATA	375,   0, 325	; test value 325 to ram 30
CNOWP:	.DATA   217, 177,   0	; clear b7 of write-protect reg
CWP:	.DATA   217, 177, 200	; set b7 of write-protect reg
CSTOP:	.DATA	201, 177, 200	; stop clock

; American MM/DD/YY HH:MM:SS
CTDTB:	.DATA   211, "/", 207, "/", 213, " "
	.DATA	205, ":", 203, ":", 201, 0, 0
; Euro DD-MM-YY HH:MM:SS
;CTDTB:	.DATA   207, "-", 211, "-", 213, " "
;	.DATA	205, ":", 203, ":", 201, 0, 0
; Asian YY-MM-DD HH:MM:SS
;CTDTB:	.DATA   213, "-", 211, "-", 207, " "
;	.DATA	205, ":", 203, ":", 201, 0, 0

; Strings
SAMSG:	.ASCIZ	"IOB: ROM V"
S2MSG:	.ASCIZ	", FPGA V"
CCPMSG:	.ASCIZ	"C/C: "
CFCMSG:	.ASCIZ	"CFC: "
CNPMSG:	.ASCIZ	"No CF Card"
CSZMSG:	.ASCIZ	"MB - "
CCNMSG:	.ASCIZ	"Not installed"
TDMSG:	.ASCIZ	"Current date and time is "

HLPLST:	.DATA	HELP0-1, HELP1-1, HELP2-1, 0
HELP0:	.ASCIZ	""
HELP1:	.ASCIZ	"EXTENSION ROM COMMANDS"
HELP2:	.ASCIZ	"DA [MM/DD/YY HH:MM:SS]\t\t-> Display or set date and time"
	.PAGE

	.TITLE Sector Buffer
	; Sector buffer - 512 words
	.PAGE   34
	; Don't move this... the last word has to be at the end of the field
	; so that ISZ detects its end

SECBUF: .BLOCK  128.
	.PAGE
	.BLOCK  128.
	.PAGE
	.BLOCK  128.
	.PAGE
	.BLOCK  128.

	.END

