;
;==================================================================================================
;   HBIOS
;==================================================================================================
;
; THIS FILE CONTAINS THE HBIOS IMAGE THAT IS INTENDED TO RUN IN A DEDICATED RAM BANK.  THE CODE IS
; CONSTRUCTED SUCH THAT IT CAN BE LAUNCHED IN A VARIETY OF MODES AND INSTALL ITSELF.  A SMALL 512
; BYTE PROXY IS PLACED AT THE TOP OF CPU MEMORY (FE00H-FFFFH).	THIS PROXY CODE ALLOWS CODE
; RUNNING FROM ANY BANK TO INVOKE HBIOS FUNCTIONS.  NORMALLY, ANY BANK THAT RUNS CODE WOULD SETUP
; THE RST 8 VECTOR TO POINT TO THE PROXY INVOKE ENTRY POINT AT FFF0H.  CALLS VIA THE PROXY INVOKE
; ENTRY POINT TRANSPARENTLY SWAP IN THE HBIOS BANK, PERFORM THE REQUESTED FUNCTION, AND RETURN
; WITH THE ORIGINAL BANK ACTIVE.  THE CODE USING HBIOS FUNCTIONS DOES NOT NEED TO BE AWARE OF
; THE BANK SWITCHING THAT OCCURS.
;
; THIS FILE CAN BE COMPILED TO BOOT IN ONE OF 3 MODES (ROM, APPLICATION, OR IMAGE) AS DESCRIBED
; BELOW.  WHEN COMPILED, YOU MUST DEFINE EXACTLY ONE OF THE FOLLOWING MACROS:
;
; - ROMBOOT: BOOT FROM A ROM BANK
;
;   WHEN ROMBOOT IS DEFINED, THE FILE IS ASSEMBLED TO BE IMBEDDED AT THE START OF A ROM
;   ASSUMING THAT THE CPU WILL START EXECUTION AT ADDRESS 0.  AFTER PERFORMING MINIMAL
;   SYSTEM INITIALIZATION, THE IMAGE OF THE RUNNING ROM BANK IS COPIED TO A RAM BANK
;   CREATING A SHADOW COPY IN RAM.  EXECUTION IS THAN TRANSFERRED TO THE RAM SHADOW COPY.
;   THIS IS ESSENTIAL BECAUSE THE HBIOS CODE DOES NOT SUPPORT RUNNING IN READ ONLY MEMORY
;   (EXCEPT FOR THE INITIAL LAUNCHING CODE).
;
; - APPBOOT: BOOT FROM A CP/M STYLE APPLICATION FILE
;
;   WHEN APPBOOT IS DEFINED, THE FILE IS ASSEMBLED AS A CP/M APPLICATION ASSUMING
;   THAT IT WILL BE LOADED AT 100H BY THE CP/M (OR COMPATIBLE) OS.  NOTE THAT IN
;   THIS CASE IT IS ASSUMED THAT AN OS IMAGES FILE IS APPENDED TO THE END OF THE
;   HBIOS APPLICATION BINARY.  THE APPENDED OS IMAGES ARE COPIED TO THE AUX RAM
;   BANK AND LAUNCHED AFTER HBIOS HAS INSTALLED ITSELF.
;
; - IMGBOOT: BOOT FROM AN IMAGE FILE THAT HAS BEEN PLACED IN THE USER BANK
;
;   NOTE: THIS BOOT MODE IS DEPRECATED.
;
;   WHEN IMGBOOT IS DEFINED, THE FILE IS ASSEMBLED SUCH THAT IT CAN BE PRELOADED
;   INTO THE RAM USER BANK BY AN EXTERNAL PROCESS THAT SUBSEQUENTLY LAUNCHES
;   THE CODE AT ADDRESS 0.  THE MOST COMMON EXAMPLE OF THIS IS THE UNA FSFAT
;   TOOL WHICH CAN LOAD AN IMAGE FROM A DOS FAT FILESYSTEM PROVIDING A SIMPLE
;   WAY TO LOAD A TEST COPY OF HBIOS.  AS IS THE CASE WITH APPBOOT, IT IS ASSUMED
;   THAT AN OS IMAGES FILE IS APPENDED TO THE END OF THE IMAGE AND IS LAUNCHED
;   AFTER HBIOS IS INSTALLED.
;
; INCLUDE FILE NESTING:
;
; - std.asm
;   - ../ver.inc
;   - build.inc
;     - Config/<plt>_std.asm
;       - cfg_<plt>.asm
;         - cfg_MASTER.asm
;           - hbios.inc
;   - [z180.inc]
;   - [z280.inc]
;   - [eipc.inc]
; - util.asm
; - time.asm
; - bcd.asm
; - decode.asm
; - encode.asm
; - [xio.asm]
; - [mio.asm]
; - [unlzsa2s.asm]
; - <drivers...>.asm
; - <fonts...>.asm
;
; MEMORY LAYOUT:
;
; DESCRIPTION			START	LENGTH
; ----------------------------- ------- -------
; Page Zero			0x0000	0x0100
; HBIOS Control Block		0x0100	0x0100
; Proxy Image			0x0200	0x0200
; Entry Vectors / Stack		0x0400	0x0100
; Interrupt Vector Table	0x0500	Varies
; System Initialization		Varies	Varies
; Function Dispatching		Varies	Varies
; Z280 Int Vector Table         Varies	Varies
; System API                    Varies	Varies
; Internal Functions            Varies	Varies
; Utility Functions             Varies	Varies
; Print Summary Function        Varies	Varies
; Hardware Drivers              Varies	Varies
; Font Data                     Varies	Varies
; HBIOS Data                    Varies	Varies
;
; AUXILIARY CONTROL REGISTER
; --------------------------
;
;	SBC/MK4		N8		MBC VDP		MBC PSG		RPH		DUO
;	------------	------------	------------	------------	------------	------------
; D7			~ROM_ENABLE					~ROM_ENABLE
; D6			TTL1_RTS					TTL1_RTS
; D5	PSG_GPIO	PSG_GPIO			PSG_GPIO	~STATUS_LED	PSG_GPIO
; D4	~PSG_RES	~PSG_RES			~PSG_RES	ROM_A19		~PSG_RES
; D3	STATUS_LED	STATUS_LED	VDP_LED		PSG_LED		ROM_A18		PSG_LED
; D2	VDP_A14		VDP_A14						ROM_A17		VDP_LED
; D1	~VDP_SYN	~VDP_SYN					ROM_A16
; D0	~VDP_RES	~VDP_RES	VDP_RES				ROM_A15		VDP_RES
;
; PORT	SCG:0x9C	0x94		VDP:0x92	PSG:0xA2	0x80		MEDIA:0xA6
;
#DEFINE	HBIOS
;
; IF BUILDING FULL BOOT ROM, INCLUDE INFO MACROS
;
#IFDEF ROMBOOT
  #DEFINE BNKINFO
  #DEFINE MEMINFO
  #DEFINE DEVINFO
  #DEFINE SYSINFO
#ENDIF
;
; INCLUDE GENERIC STUFF
;
#INCLUDE "std.asm"
;
; MAKE SURE EXACTLY ONE OF ROMBOOT, APPBOOT, IMGBOOT IS DEFINED.
;
MODCNT		.EQU	0
BOOTMODE	.EQU	0
;
#IFDEF ROMBOOT
BOOTMODE	.SET	BM_ROMBOOT
MODCNT		.SET	MODCNT + 1
#ENDIF
;
#IFDEF APPBOOT
BOOTMODE	.SET	BM_APPBOOT
MODCNT		.SET	MODCNT + 1
#ENDIF
;
#IFDEF IMGBOOT		; *** DEPRECATED ***
BOOTMODE	.SET	BM_IMGBOOT
MODCNT		.SET	MODCNT + 1
#ENDIF
;
#IF (MODCNT != 1)
	.ECHO	"*** ERROR: PLEASE DEFINE ONE AND ONLY ONE OF ROMBOOT, APPBOOT, IMGBOOT!!!\n"
	!!!	; FORCE AN ASSEMBLY ERROR
#ENDIF
;
; CONTROLS PRINTING OF DEVICE INFORMATION IN ASSEMBLY OUTPUT
;
#IFDEF DEVINFO
  #DEFINE DEVECHO .ECHO
#ELSE
  #DEFINE DEVECHO \;
#ENDIF
;
; CONTROLS PRINTING OF MEMORY USAGE INFORMATION IN ASSEMBLY OUTPUT
;
#IFDEF MEMINFO
  #DEFINE MEMECHO .ECHO
#ELSE
  #DEFINE MEMECHO \;
#ENDIF
;
SUPCTS	.EQU	FALSE		; SUPPRESS CTS DURING HBIOS BOOT
;
; HELPER MACROS
;
; SET FRONT PANEL LEDS
;
#IF (FPLED_ENABLE)
  #DEFINE FPLEDS(N)	PUSH	AF
  #DEFCONT \		LD	A,N
  #IF (FPLED_INV)
    #DEFCONT \		XOR	$FF	; INVERT BITS IF NEEDED
  #ENDIF
  #DEFCONT \		EZ80_IO
  #DEFCONT \		OUT	(FPLED_IO),A
  #DEFCONT \		POP	AF
#ELSE
  #DEFINE		FPLEDS(N)	\;
#ENDIF
;
; SET DIAGNOSTIC LEDS
;
; SCxxx: LED Port=0x0E, bit 2, inverted, dedicated port (LEDMODE_SC)
; SC7xx/SC5xx: LED Port=0x0E, bit 0, inverted, dedicated port (LEDMODE_STD)
; TinyZ80: LED Port=0x6E, bit 0, inverted, dedicated port (LEDMODE_STD)
; Z80-512K: LED Port=0x6E, bit 0, inverted, dedicated port (LEDMODE_STD)
; MBC: LED Port=0x70, bits 1-0, normal, shared w/ RTC port (LEDMODE_RTC)
; DUO: LED Port=0x94, bits 1-0, normal, shared w/ RTC port (LEDMODE_RTC)
; S100: LED Port = $0E, bit 2, inverted, dedicated port (LEDMODE_SC)
; NABU: LED Port = $00, bits 5-3, normal, shared w/ control port (LEDMODE_NABU)
;
#IF (LEDENABLE)
  #IF (LEDMODE == LEDMODE_STD)
    #DEFINE DIAG(N)	PUSH	AF
    #DEFCONT \		LD	A,~N
    #DEFCONT \		OUT	(LEDPORT),A
    #DEFCONT \		POP	AF
  #ENDIF
  #IF (LEDMODE == LEDMODE_SC)
    #DEFINE DIAG(N)	PUSH	AF
    #DEFCONT \		LD	A,+(((~N) << 2) & %00000100)
    #DEFCONT \		OUT	(LEDPORT),A
    #DEFCONT \		POP	AF
  #ENDIF
  #IF (LEDMODE == LEDMODE_RTC)
    #DEFINE DIAG(N)	PUSH	AF
    #DEFCONT \		LD	A,(HB_RTCVAL)
    #DEFCONT \		AND	%11111100
    #DEFCONT \		OR	(N & %00000011)
    #DEFCONT \		LD	(HB_RTCVAL),A
    #DEFCONT \		OUT	(LEDPORT),A
    #DEFCONT \		POP	AF
  #ENDIF
  #IF (LEDMODE == LEDMODE_NABU)
    #DEFINE DIAG(N)	PUSH	AF
    ;#DEFCONT \		LD	A,+((N << 3) & %00011000)
    #DEFCONT \		LD	A,+((N << 3) & %00111000)
    #DEFCONT \		OUT	(LEDPORT),A
    #DEFCONT \		POP	AF
  #ENDIF
#ELSE
  #DEFINE DIAG(N)	\;
#ENDIF
;
; HANDLE SYSTEM CHECK ERRORS
;
#DEFINE SYSCHKERR(HB_ERR) \
  #DEFCONT \	CALL	SYSCHKA
  #DEFCONT \	LD	A,HB_ERR
  #DEFCONT \	OR	A
;
; THE HB_EI AND HB_DI MACROS ARE USED TO GENERATE THE APPROPRIATE
; INTERRUPT ENABLE/DISABLE CODE DEPENDING ON THE INTERRUPT MODE
; BEING USED.
;
#IF (INTMODE == 0)
  ; NO INTERRUPT HANDLING
  #DEFINE	HB_DI	;
  #DEFINE	HB_EI	;
#ELSE
  #IF (CPUFAM == CPU_Z280)
    #IF (INTMODE == 3)
      ; Z280 MODE 3 INTERRUPT HANDLING (INTA, C/T 0, & UART RCVR ENABLED)
      #DEFINE	HB_DI	DI
      #DEFINE	HB_EI	EI	$0B
    #ELSE
      ; Z280 MODE 1/2 INTERRUPT HANDLING
      #DEFINE	HB_DI	DI
      #DEFINE	HB_EI	EI
    #ENDIF
  #ELSE
    #DEFINE	HB_DI	DI
    #DEFINE	HB_EI	EI
  #ENDIF
#ENDIF
;
; CONSISTENCY CHECKS
;
#IF (INTMODE > 3)
	.ECHO	"*** ERROR: INVALID INTMODE SETTING!!!\n"
	!!!	; FORCE AN ASSEMBLY ERROR
#ENDIF
;
#IF (INTMODE == 3)
  #IF (CPUFAM != CPU_Z280)
	.ECHO	"*** ERROR: INTMODE 3 REQUIRES Z280 FAMILY CPU!!!\n"
	!!!	; FORCE AN ASSEMBLY ERROR
  #ENDIF
  #IF (MEMMGR != MM_Z280)
	.ECHO	"*** ERROR: INTMODE 3 REQUIRES Z280 MEMORY MANAGER!!!\n"
	!!!	; FORCE AN ASSEMBLY ERROR
  #ENDIF
#ENDIF
;
#IF (MEMMGR == MM_Z280)
  #IF (INTMODE != 3)
	.ECHO	"*** ERROR: Z280 MEMORY MANAGER REQUIRES INTMODE 3!!!\n"
	!!!	; FORCE AN ASSEMBLY ERROR
  #ENDIF
  #IF (CPUFAM != CPU_Z280)
	.ECHO	"*** ERROR: Z280 MEMORY MANAGER REQUIRES Z280 FAMILY CPU!!!\n"
	!!!	; FORCE AN ASSEMBLY ERROR
  #ENDIF
#ENDIF
;
; CONVERT ROMWBW LOGICAL BANK ID TO PHYSICAL 32K BANK OFFSET
;
#DEFINE PBANK(X) (((X >> 7) * (RAMBIAS / 32)) + (X & $7F))
;
; CONVERT ROMWBW LOGICAL BANK ID TO Z280 PHYSICAL BANK (4K) OFFSET
;
#DEFINE Z2_BANK(X) (PBANK(X) << 3)
;
; RTC LATCH
;
; MANY OF THE ROMWBW SYSTEMS USE A LATCH PORT PRIMARILY FOR BIT
; BANGING A DS-1302 RTC.  HOWEVER, SINCE THE RTC ONLY NEEDS A COUPLE
; BITS, THE OTHER BITS OF THE LATCH ARE FREQUENTY USED FOR OTHER
; PURPOSES (LEDS, SD CARD BIT BANGING, ETC.).  SEE DSRTC.ASM FOR
; A SUMMARY OF THE WAY THE RTC LATCH BITS ARE USED IN THE VARIOUS
; ROMWBW SYSTEMS.  IT IS CRITICAL THAT WHEN MANIPULATING THE RTC
; LATCH THAT BITS ARE NOT FLIPPED INADVERTENTLY.  THE RTC LATCH IS
; TYPICALLY WRITE-ONLY, SO WE NEED TO MAINTAIN A SHADOW COPY.
; THE SHADOW COPY IS CALLED HB_RTCVAL AND IS DECLARED AT THE END OF
; THIS FILE IN THE DATA AREA.
;
; INITIALIZING THE HB_RTCVAL SHADOW IS TRICKY BECAUSE DIFFERENT BITS
; ARE MANAGED IN DIFFERENT DRIVERS.  TO HANDLE THIS,
; THE RTCDEF EQUATE IS INITIALIZED HERE AND UPDATED BY DRIVER INCLUDES
; THAT SHARE THE RTC LATCH.  AS EACH DRIVER FILE IS INCLUDED, IT CAN
; USE .SET TO MODIFY THE DEFAULT VALUE OF ANY BITS THEY OWN.
; SINCE RTCDEF IS CHANGED *AFTER* IT NEEDS TO BE USED BY THE CODE, IT
; SHOULD NOT BE USED DIRECTLY TO SET THE LATCH.  INSTEAD, THE FINAL VALUE
; OF RTCDEF IS USED TO INITIALIZE A STORAGE BYTE CALLED RTCDEFVAL AT
; THE END OF THIS FILE.  SO (RTCDEFVAL) CAN BE USED ANYWHERE IN
; HBIOS.ASM TO ACCESS THE FINAL RTCDEF VALUE.  IN MOST PLACES, THE
; SHADOW COPY (RTCVAL) SHOULD BE USED TO GET THE CURRENT VALUE OF THE
; LATCH AND MAINTAIN ALL BIT CHANGES.
;
RTCDEF	.EQU	0			; INIT DEF RTC LATCH VALUE
;
; THE SC126 HAS AN I2C CIRCUIT AND THERE IS NO ASSOCAITED
; DRIVER, SO WE SET THAT BIT HERE.  IT IS SET FOR ALL OF THE SCXXX
; SYSTEMS, BUT IS UNUSED ON ALL BUT THE SC126.  IT DOES NO HARM.
;
#IF (PLATFORM == PLT_SCZ180)
RTCDEF	.SET	RTCDEF | %00000001	; SC128 I2C SCL BIT
#ENDIF
;
; MBC PLATFORM IMPLEMENTS DYNAMIC SPEED SWITCH ON RTC LATCH
; BIT 3.  SET THE BIT TO LOW SPEED AS DEFAULT HERE.
;
#IF ((CPUSPDCAP==SPD_HILO) & (PLATFORM==PLT_MBC))
RTCDEF	.SET	RTCDEF & ~%00001000	; INITIAL SPEED LOW
#ENDIF
;
; DUODYNE PLATFORM IMPLEMENTS DYNAMIC SPEED SWITCH ON RTC LATCH
; BIT 3.  SET THE BIT TO LOW SPEED AS DEFAULT HERE.
;
#IF ((CPUSPDCAP==SPD_HILO) & (PLATFORM==PLT_SBC))
RTCDEF	.SET	RTCDEF | %00001000	; INITIAL SPEED LOW
#ENDIF
;
; EMIT FRONT PANEL CONFIGURATION TO ASSEMBLY OUTPUT
;
#IF (FPLED_ENABLE | FPSW_ENABLE)
	DEVECHO	"FP: "
  #IF (FPLED_ENABLE)
	DEVECHO	"LEDIO="
	DEVECHO	FPLED_IO
  #ENDIF
  #IF (FPLED_ENABLE & FPSW_ENABLE)
	DEVECHO	", "
  #ENDIF
  #IF (FPSW_ENABLE)
	DEVECHO	"SWIO="
	DEVECHO	FPSW_IO
  #ENDIF
	DEVECHO	"\n"
#ENDIF

#INCLUDE "ez80instr.inc"

;
;==================================================================================================
; Z80 PAGE ZERO, VECTORS, ETC.
;==================================================================================================
;
	.ORG	0
;
HB_PGZERO_BEG	.EQU	$
;
#IFNDEF	APPBOOT
;
	.FILL	(000H - $),0FFH		; RST 0
	JP	HB_START
	.DB	0			; SIG PTR STARTS AT $0004
	.DW	ROM_SIG
	.FILL	(008H - $),0FFH		; RST 8
	JP	HB_INVOKE		; INVOKE HBIOS FUNCTION
	.FILL	(010H - $),0FFH		; RST 10
	RET
	.FILL	(018H - $),0FFH		; RST 18
	RET
	.FILL	(020H - $),0FFH		; RST 20
	RET
	.FILL	(028H - $),0FFH		; RST 28
	RET
	.FILL	(030H - $),0FFH		; RST 30
	RET
	.FILL	(038H - $),0FFH		; RST 38 / IM1 INT
  #IF (INTMODE == 1)
	CALL	HBX_INT			; HANDLE IM1 INTERRUPTS
	.DB	$10 << 2		; USE SPECIAL VECTOR #16
  #ELSE
	RET				; RETURN W/ INTS DISABLED
  #ENDIF
	.FILL	(066H - $),0FFH		; NMI
	RETN
;
	.FILL	(070H - $),0FFH		; SIG STARTS AT $80
;
ROM_SIG:
	.DB	$76, $B5		; 2 SIGNATURE BYTES
	.DB	1			; STRUCTURE VERSION NUMBER
	.DB	7			; ROM SIZE (IN MULTIPLES OF 4KB, MINUS ONE)
	.DW	NAME			; POINTER TO HUMAN-READABLE ROM NAME
	.DW	AUTH			; POINTER TO AUTHOR INITIALS
	.DW	DESC			; POINTER TO LONGER DESCRIPTION OF ROM
	.DB	0, 0, 0, 0, 0, 0	; RESERVED FOR FUTURE USE; MUST BE ZERO
;
NAME	.DB	"ROMWBW v", BIOSVER, ", ", CONFIG, ", ", TIMESTAMP, 0
AUTH	.DB	"WBW",0
DESC	.DB	"Copyright (C) 2024, Wayne Warthen, GNU GPL v3", 0
;
	.FILL	($100 - $),$FF		; PAD REMAINDER OF PAGE ZERO
;
#ENDIF
;
	.ORG	$100
;
HB_PGZERO_END	.EQU	$
;
;==================================================================================================
;   HBIOS CONFIGURATION BLOCK (HCB)
;==================================================================================================
;
		.ORG	HCB_LOC
;
HB_HCB_BEG	.EQU	$
;
HCB:
		JP	HB_START
;
CB_MARKER	.DB	'W',~'W'	; MARKER
CB_VERSION	.DB	RMJ << 4 | RMN	; FIRST BYTE OF VERSION INFO
		.DB	RUP << 4 | RTP	; SECOND BYTE OF VERSION INFO
;
CB_PLATFORM	.DB	PLATFORM
CB_CPUMHZ	.DB	CPUMHZ
CB_CPUKHZ	.DW	CPUKHZ
CB_RAMBANKS	.DB	RAMSIZE / 32
CB_ROMBANKS	.DB	ROMSIZE / 32
;
CB_BOOTVOL	.DW	0		; BOOT VOLUME IS UNIT/SLICE, SET BY LOADER
CB_BOOTBID	.DB	0		; BOOT BANK ID, SET BY LOADER
CB_SERDEV	.DB	0		; PRIMARY SERIAL UNIT IS UNIT #0 BY FIAT
CB_CRTDEV	.DB	$FF		; PRIMARY CRT UNIT, $FF UNTIL AFTER HBIOS INIT
CB_CONDEV	.DB	$FF		; CONSOLE UNIT, $FF UNTIL AFTER HBIOS INIT
;
CB_DIAGLVL	.DB	DIAGLVL		; ROMWBW HBIOS DIAGNOSTIC LEVEL
CB_BOOTMODE	.DB	BOOTMODE	; HBIOS BOOTMODE
;
; MEMORY MANAGEMENT VARIABLES START AT $20
;
		.FILL	(HCB + $20 - $),0
;
CB_HEAP		.DW	0
CB_HEAPTOP	.DW	0
;
; STANDARD BANK ID'S START AT $D8. DEFAULT VALUES FOR 512KB SYSTEM WITH NO RESERVED BANKS
;
		.FILL	(HCB + $D8 - $),0
;
CB_BIDCOM	.DB	BID_COM
CB_BIDUSR	.DB	BID_USR
CB_BIDBIOS	.DB	BID_BIOS
CB_BIDAUX	.DB	BID_AUX
CB_BIDRAMD0	.DB	BID_RAMD0
CB_RAMD_BNKS	.DB	RAMD_BNKS
CB_BIDROMD0	.DB	BID_ROMD0
CB_ROMD_BNKS	.DB	ROMD_BNKS
CB_BIDAPP0	.DB	BID_APP0
CB_APP_BNKS	.DB	APP_BNKS
;
	.FILL	(HCB + HCB_SIZ - $),0	; PAD REMAINDER OF HCB
;
HB_HCB_END	.EQU	$
;
;==================================================================================================
;   HBIOS UPPER MEMORY PROXY (RELOCATED TO RUN IN TOP 2 PAGES OF CPU RAM)
;==================================================================================================
;
; THE FOLLOWING CODE IS RELOCATED TO THE TOP OF MEMORY TO HANDLE INVOCATION DISPATCHING
;
HB_PROXY_BEG	.EQU	$
;
	.FILL	(HBX_IMG - $)		; FILL TO START OF PROXY IMAGE START
	.ORG	HBX_LOC			; ADJUST FOR RELOCATION
;
; MEMORY LAYOUT:
;
;   HBIOS PROXY CODE		$FE00 (256 BYTES)
;   INTERRUPT VECTORS		$FF00 (32 BYTES, 16 ENTRIES)
;   INTERRUPT HANDLER STUBS	$FF20 (64 BYTES)
;   HBIOS PROXY CODE		$FF60 (64 BYTES)
;   HBIOS PROXY COPY BUFFER	$FFA0 (64 BYTES)
;   HBIOS PROXY MGMT BLOCK	$FFE0 (32 BYTES)
;
; DEFINITIONS
;
HBX_BUFSIZ	.EQU	$40		; INTERBANK COPY BOUNCE BUFFER SIZE
;
; HBIOS IDENTIFICATION DATA BLOCK
;
HBX_IDENT:
	.DB	'W',~'W'		; MARKER
	.DB	RMJ << 4 | RMN		; FIRST BYTE OF VERSION INFO
	.DB	RUP << 4 | RTP		; SECOND BYTE OF VERSION INFO
;
; HBIOS ENTRY FOR RST 08 PROCESSING
;
; NOTE: THE SIZE OF HBX_TMPSTK (TYPICALLY 20 BYTES) IS INSUFFICIENT FOR
; FREERTOS IF AN INTERRUPT STRIKES WHILE THE TEMPORARY STACK IS ACTIVE.
; BELOW, HBX_BUF HAS BEEN USURPED TO PROVIDE A LARGER TEMP STACK TO
; ACCOMMODATE FREERTOS.  HBX_BUF IS ONLY USED AS A BOUNCE BUFFER, SO IT'S
; USE WILL NEVER OVERLAP WITH BELOW.
;
; WARNING: HBX_INVOKE IS *NOT* REENTRANT!
;
HBX_INVOKE:
;
#IF (INTMODE == 0)
	; IF SOMETHING IN USERLAND ENABLES INTERRUPTS AND WE ARE NOT
	; CONFIGURED TO USE THEM, THEN SHUT THEM BACK OFF AGAIN FOR
	; SAFETY.
	DI
#ENDIF
;
#IF (HBIOS_MUTEX == TRUE)
	PUSH	HL			; SAVE HL
	LD	HL,HB_LOCK		; POINT TO LOCK
	SRA	(HL)			; TEST/ACQUIRE MUTEX LOCK
	JR	C,$-2			; KEEP TRYING ON FAILURE
	POP	HL			; RESTORE HL
#ENDIF
;
#IF (MEMMGR == MM_Z280)
;
	LD	A,(HB_CURBNK)		; GET CURRENT BANK
	LD	(HB_INVBNK),A		; SAVE INVOCATION BANK
;
	LD	A,BID_BIOS		; HBIOS BANK
	LD	(HB_CURBNK),A		; SET AS CURRENT BANK
;
	SC	HB_DISPATCH
;
	PUSH	AF
	LD	A,(HB_INVBNK)
	LD	(HB_CURBNK),A
	POP	AF
;
#ELSE
;
	LD	(HBX_INVSP),SP		; SAVE ORIGINAL STACK FRAME
	LD	SP,HBX_BUF_END		; BORROW HBX_BUF FOR TEMP STACK
;
	LD	A,(HB_CURBNK)		; GET CURRENT BANK
	LD	(HB_INVBNK),A		; SAVE INVOCATION BANK
;
	LD	A,BID_BIOS		; HBIOS BANK
	CALL	HBX_BNKSEL		; SELECT IT
	LD	SP,HB_STACK		; NOW USE FULL HBIOS STACK IN HBIOS BANK
;
	CALL	HB_DISPATCH		; CALL HBIOS FUNCTION DISPATCHER
;
	LD	SP,HBX_BUF_END		; BORROW HBX_BUF FOR TEMP STACK
	PUSH	AF			; SAVE AF (FUNCTION RETURN)
;
	LD	A,(HB_INVBNK)		; LOAD ORIGINAL BANK
	CALL	HBX_BNKSEL		; SELECT IT
	POP	AF			; RESTORE AF
	LD	SP,0			; RESTORE ORIGINAL STACK FRAME
HBX_INVSP	.EQU	$ - 2
;
#ENDIF
;
#IF (HBIOS_MUTEX == TRUE)
	PUSH	HL			; SAVE HL
	LD	HL,HB_LOCK		; POINT TO LOCK
	LD	(HL),$FE		; RELEASE MUTEX LOCK
	POP	HL			; RESTORE HL
#ENDIF
;
	RET				; RETURN TO CALLER
;
;;::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
;; BNKSEL - Switch Memory Bank to Bank in A.
;;   AF is destroyed, all other registers are preserved.
;;::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
;
HBX_BNKSEL:
	; IF AN INTERRUPT OCCURS DURING THE BANK SWITCH CODE,
	; THE BANK WILL BE SET TO (CURBNK) AS THE INTERRUPT
	; RETURNS.  SO, IT IS IMPORTANT THAT (HB_CURBNK) BE
	; SET AS THE FIRST STEP TO AVOID ISSUES IF AN INTERRUPT
	; OCCURS DURING PROCESSING.
	LD	(HB_CURBNK),A		; RECORD NEW CURRENT BANK
;
HBX_BNKSEL_INT:
;
#IF (MEMMGR == MM_SBC)
  #IF (INTMODE == 1)
	; THIS BIT OF ABSURDITY HANDLES A RARE (BUT FATAL) SITUATION
	; WHERE AN IM1 INTERRUPT OCCURS BETWEEN SETTING THE RAM AND
	; ROM SELECTORS.  BRACKETING THE INSTRUCTIONS WITH DI/EI
	; IS CONTRAINDICATED BECAUSE THIS ROUTINE IS CALLED BY
	; OTHER ROUTINES THAT MUST CONTROL INT ENABLE AT A HIGHER
	; LEVEL.  THE FOLLOWING TECHNIQUE ENSURES THAT YOU ALWAYS
	; SWITCH DIRECTLY FROM THE PREVIOUS BANK TO THE TARGET BANK
	; WITHOUT AN "ERRANT" BANK BEING ACTIVE BETWEEN THE TWO
	; BANK SELECTION I/O INSTRUCTIONS.  THE TECHNIQUE IS ONLY
	; NEEDED WHEN USING INT MODE 1 BECAUSE THAT MODE REQUIRES
	; PAGE ONE TO HAVE A VALID INT HANDLER WHENEVER INTS ARE
	; ENABLED.
	OR	A			; SET FLAGS
	JP	P,HBX_ROM		; BIT 7 INDICATES RAM
  #ENDIF
	OUT	(MPCL_RAM),A		; SET RAM PAGE SELECTOR
HBX_ROM:
	OUT	(MPCL_ROM),A		; SET ROM PAGE SELECTOR
	RET				; DONE
#ENDIF
;
#IF (MEMMGR == MM_Z2)

#IF (CPUFAM == CPU_EZ80)
	BIT	7,A			; BIT 7 SET REQUESTS RAM PAGE
	JR	Z,HBX_ROM		; NOT SET, SELECT ROM PAGE
	RES	7,A			; RAM PAGE REQUESTED: CLEAR ROM BIT
	ADD	A,16			; ADD 16 x 32K - RAM STARTS FROM 512K
;
HBX_ROM:
	RLCA				; TIMES 2 - GET 16K PAGE INSTEAD OF 32K
	OUT_NN_A(MPGSEL_0)		; BANK_0: 0K - 16K
	INC	A			;
	OUT_NN_A(MPGSEL_1)		; BANK_1: 16K - 32K
	RET				; DONE

#ELSE

	BIT	7,A			; BIT 7 SET REQUESTS RAM PAGE
	JR	Z,HBX_ROM		; NOT SET, SELECT ROM PAGE
	RES	7,A			; RAM PAGE REQUESTED: CLEAR ROM BIT
  #IF (PLATFORM == PLT_DUO)
	ADD	A,64			; ADD 64 x 32K - RAM STARTS FROM 2048K
  #ELSE
	ADD	A,ROMSIZE / 32		; STARTING RAM BANK NUMBER OFFSET
  #ENDIF
;
HBX_ROM:
	RLCA				; TIMES 2 - GET 16K PAGE INSTEAD OF 32K
 	EZ80_IO()
	OUT	(MPGSEL_0),A		; BANK_0: 0K - 16K
	INC	A			;
	EZ80_IO()
	OUT	(MPGSEL_1),A		; BANK_1: 16K - 32K
  #IF (CPUFAM == CPU_Z280)
	PCACHE
  #ENDIF
	RET				; DONE
#ENDIF
#ENDIF
;
#IF (MEMMGR == MM_N8)
	BIT	7,A			; TEST BIT 7 FOR RAM VS. ROM
	JR	Z,HBX_ROM		; IF NOT SET, SELECT ROM PAGE
;
HBX_RAM:
	AND	%00011111		; AVOID WRAPPING BITS
	RLCA				; SCALE SELECTOR TO
	RLCA				; ... GO FROM Z180 4K PAGE SIZE
	RLCA				; ... TO DESIRED 32K PAGE SIZE
	OUT0	(Z180_BBR),A		; WRITE TO BANK BASE
	LD	A,N8_DEFACR | 80H	; SELECT RAM BY SETTING BIT 7
	OUT0	(N8_ACR),A		; ... IN N8 ACR REGISTER
	RET				; DONE
;
HBX_ROM:
	OUT0	(N8_RMAP),A		; BANK INDEX TO N8 RMAP REGISTER
	XOR	A			; ZERO ACCUM
	OUT0	(Z180_BBR),A		; ZERO BANK BASE
	LD	A,N8_DEFACR		; SELECT ROM  BY CLEARING BIT 7
	OUT0	(N8_ACR),A		; ... IN N8 ACR REGISTER
	RET				; DONE
#ENDIF
;
#IF (MEMMGR == MM_Z180)
	RLCA				; RAM FLAG TO CARRY FLAG AND BIT 0
	JR	NC,HBX_BNKSEL1		; IF NC, WANT ROM PAGE, SKIP AHEAD
	XOR	%00100001		; SET BIT FOR HI 512K, CLR BIT 0
HBX_BNKSEL1:
	AND	%00111111		; AVOID WRAPPING BITS
	RLCA				; CONTINUE SHIFTING TO SCALE SELECTOR
	RLCA				; FOR Z180 4K PAGE -> DESIRED 32K PAGE
	OUT0	(Z180_BBR),A		; WRITE TO BANK BASE
	RET				; DONE
#ENDIF
;
#IF (MEMMGR == MM_Z280)
	PUSH	BC			; SAVE BC
	PUSH	HL			; SAVE HL
	LD	B,$00			; FIRST USER PDR
	SC	Z280_BNKSEL		; SYSCALL
	POP	HL			; RESTORE HL
	POP	BC			; RESTORE BC
	RET				; DONE
#ENDIF
;
#IF (MEMMGR == MM_ZRC)
	BIT	7,A			; BIT 7 SET REQUESTS RAM PAGE
	JR	Z,HBX_ROM		; NOT SET, SELECT ROM PAGE
	RES	7,A			; RAM PAGE REQUESTED: CLEAR ROM BIT
	ADD	A,ROMSIZE / 32		; STARTING RAM BANK NUMBER OFFSET
;
HBX_ROM:
	OUT	($1F),A			; HCS WRITE TO THE BANK CONTROL REGISTER
	RET				; DONE
#ENDIF
;
#IF (MEMMGR == MM_MBC)
;
  #IF (INTMODE == 1)
	LD	(HBX_MMA),A		; SAVE ACCUM
	LD	A,I			; GET INT CTL REG
	HB_DI				; DISABLE INTS
	PUSH	AF			; SAVE INT CTL REG
	LD	A,(HBX_MMA)		; RESTORE ACCUM
  #ENDIF
;
        OR      A                       ; SET FLAGS
        JP      P,HBX_ROM               ; BIT 7 INDICATES RAM
        OUT     (MPCL_ROM),A            ; ENSURE ROM PAGE OUT OF MEMORY BEFORE SWITCH
	; SEE MBC BANK SELECT MASK SETUP ROUTINE ABOVE
HBX_MBCMSK .EQU	$+1			; FORCE TOP 32K		; MASK POPULATED
	XOR	%00000000		; TO BE IN FIRST CHIP	; DURING INITIALIZATION
        OUT     (MPCL_RAM),A            ; SET RAM PAGE SELECTOR
	JR	HBX_RAMX
HBX_ROM:
        OUT     (MPCL_RAM),A            ; ENSURE RAM PAGE OUT OF MEMORY BEFORE SWITCH
        OUT     (MPCL_ROM),A            ; SET ROM PAGE SELECTOR
;
HBX_RAMX:
;
  #IF (INTMODE == 1)
	POP	AF			; RESTORE INT CTL REG
	JP	PO,$+4			; WERE INTS DISABLED AT ENTRY?
	EI				; *** DO NOT USE HB_EI HERE ***
	LD	A,(HBX_MMA)		; RESTORE INCOMING ACCUM
  #ENDIF
;
	RET
;
HBX_MMA	.DB	0			; TEMPORARY STORAGE FOR REG A
#ENDIF
;
#IF (MEMMGR == MM_RPH)
	BIT	7,A			; TEST BIT 7 FOR RAM VS. ROM
	JR	Z,HBX_ROM		; IF NOT SET, SELECT ROM PAGE
;
HBX_RAM:
	AND	%00011111		; AVOID WRAPPING BITS
	RLCA				; SCALE SELECTOR TO
	RLCA				; ... GO FROM Z180 4K PAGE SIZE
	RLCA				; ... TO DESIRED 32K PAGE SIZE
	OUT0	(Z180_BBR),A		; WRITE TO BANK BASE
	LD	A,RPH_DEFACR | 80H	; SELECT RAM BY SETTING BIT 7
	OUT0	(RPH_ACR),A		; ... IN RPH ACR REGISTER
	RET				; DONE
;
HBX_ROM:
	OR	RPH_DEFACR		; COMBINE WITH DEFAULT BITS
	OUT0	(RPH_ACR),A		; BANK INDEX TO RPH ACR REGISTER
	XOR	A			; ZERO ACCUM
	OUT0	(Z180_BBR),A		; ZERO BANK BASE
	RET				; DONE
#ENDIF
;
#IF (MEMMGR == MM_MON)
;
; CURRENTLY ASSUMES FIRST 16 PAGES ARE RAM FOLLOWED BY 16 PAGES OF ROM.
; SO, WE MAP HBIOS BANKS $00-$0F (ROM SELECT) TO $10-$%1F AND HBIOS
; BANKS $80-$8F (RAM SELECT) TO $00-$0F.
;
	BIT	7,A			; BIT 7 SET REQUESTS RAM PAGE
	JR	Z,HBX_ROM		; NOT SET, SELECT ROM PAGE
	RES	7,A			; RAM PAGE REQUESTED: CLEAR ROM BIT
	OUT	($FF),A			; DO IT
	RET				; AND DONE
;
HBX_ROM:
	ADD	A,$10			; OFFSET INTO ROM BANKS
	OUT	($FF),A			; DO IT
	RET				; DONE
#ENDIF
;
#IF (MEMMGR == MM_Z280)
;
; REG A HAS BANK ID, REG B HAS INITIAL PDR TO PROGRAM
; REGISTERS AF, BC, HL DESTROYED
;
Z280_BNKSEL:
	;; *DEBUG*
	;CALL	PC_LBKT
	;CALL	PRTHEXBYTE
	;CALL	PC_RBKT

	; SELECT I/O PAGE $FE (SAVING PREVIOUS VALUE)
	LD	C,Z280_IOPR		; REG C POINTS TO I/O PAGE REGISTER
	LDCTL	HL,(C)			; GET CURRENT I/O PAGE
	PUSH	HL			; SAVE IT
	LD	L,$FF			; NEW I/O PAGE
	LDCTL	(C),HL			; IMPLEMENT
;
	; CONVERT BANK ID TO TOP 12 BITS OF PHYSICAL ADDRESS
	; WITH $0A IN THE LOW ORDER NIBBLE:
	; BANK ID: R000 BBBB
	; PDR: R000 0BBB B000 1010 (RCBUS)
	; PDR: 0000 RBBB B000 1010 (ZZ80MB)
;
	MULTU	A,$80			; HL=0R00 0BBB B000 0000
	BIT	6,H			; RAM BIT SET?
	JR	Z,Z280_BNKSEL2		; IF NOT, ALL DONE
	RES	6,H			; OTHERWISE, REMOVE RAM BIT
	LD	A,RAMBIAS >> 6		; RAM OFFSET (TOP 8 BITS)
	OR	H			; RECOMBINE
	LD	H,A			; AND PUT BACK IN H
;
Z280_BNKSEL2:
;
	; SET LOW NIBBLE
	LD	A,$0A			; VALUE FOR LOW NIBBLE
	ADD	HL,A			; ADD HL,A ; HL=0000 RBBB B000 1010
;
	; POINT TO FIRST PDR TO PROGRAM
	LD	A,B			; INITIAL PDR TO PROG
	OUT	(Z280_MMUPDRPTR),A	; SET THE PDR POINTER
;
	; PROGRAM 8 PDRS
	LD	C,Z280_MMUBLKMOV	; PDR BLOCK MOVE PORT
	;LD	B,8			; PROGRAM 8 PDRS
	LD	A,$10			; PDR VALUE INCREMENT
Z280_BNKSEL3:
	; PROGRAM 8 PDR VALUES
	; LOOP UNROLLED FOR SPEED
	OUTW	(C),HL			; WRITE VALUE
	ADD	HL,A			; BUMP VALUE
	OUTW	(C),HL			; WRITE VALUE
	ADD	HL,A			; BUMP VALUE
	OUTW	(C),HL			; WRITE VALUE
	ADD	HL,A			; BUMP VALUE
	OUTW	(C),HL			; WRITE VALUE
	ADD	HL,A			; BUMP VALUE
	OUTW	(C),HL			; WRITE VALUE
	ADD	HL,A			; BUMP VALUE
	OUTW	(C),HL			; WRITE VALUE
	ADD	HL,A			; BUMP VALUE
	OUTW	(C),HL			; WRITE VALUE
	ADD	HL,A			; BUMP VALUE
	OUTW	(C),HL			; WRITE VALUE
	ADD	HL,A			; BUMP VALUE
	;DJNZ	Z280_BNKSEL3		; DO ALL PDRS
;
	; RESTORE I/O PAGE
	LD	C,Z280_IOPR		; REG C POINTS TO I/O PAGE REGISTER
	POP	HL			; RECOVER ORIGINAL I/O PAGE
	LDCTL	(C),HL
;
	RET
;
Z280_BNKSEL_LEN	.EQU	$ - Z280_BNKSEL
;
#ENDIF
;
; Z280 SYSCALL VECTOR ENTRY POINT.  TAKES STACK PARAMETER AS A BRANCH
; ADDRESS AND CALLS IT.  ALLOWS ANY USER MODE CODE TO CALL INTO AN
; ARBITRARY LOCATION OF SYSTEM MODE CODE.
;
#IF (MEMMGR == MM_Z280)
Z280_SYSCALL:
	EX	(SP),HL
	LD	(Z280_SYSCALL_GO + 1),HL
	POP	HL
Z280_SYSCALL_GO:
	CALL	$FFFF			; PARM SET ABOVE
	RETIL				; RETURN FROM INT
#ENDIF
;
;::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
; Copy Data - Possibly between banks.  This resembles CP/M 3, but
;  usage of the HL and DE registers is reversed.
; Caller MUST preset HBX_SRCBNK and HBX_DSTBNK.
; IM1/IM2 interrupts are disabled during HBX_BNKCPY.
; Enter:
;	 HL = Source Address
;	 DE = Destination Address
;	 BC = Number of bytes to copy
; Exit : None
; Uses : AF,BC,DE,HL
;
;::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
;
HBX_BNKCPY:
#IF (MEMMGR == MM_Z280)
	SC	Z280_BNKCPYX		; SYSCALL TO BNKCPYX
	RET
;
IOPRSAV	.DW	0			; TEMP STORAGE FOR IOPR
;
#ELSE
  #IF (CPUFAM == CPU_Z280)
	PUSH	HL
	PUSH	BC
	LD	C,Z280_MSR
	LDCTL	HL,(C)
	POP	BC
	EX	(SP),HL
	HB_DI
  #ELSE
	LD	A,I
	HB_DI
	PUSH	AF
  #ENDIF
	LD	(HBX_BC_SP),SP		; PUT STACK
	LD	SP,HBX_TMPSTK		; ... IN HI MEM

	LD	A,(HB_CURBNK)		; GET CURRENT BANK
	PUSH	AF			; AND SAVE TO RESTORE LATER
	PUSH	BC			; CUR LEN -> (SP)
;
HBX_BC_LOOP:
	EX	(SP),HL			; HL := CUR LEN, (SP) := CUR SRC
	LD	BC,HBX_BUFSIZ		; SET BC TO BOUNCE BUFFER SIZE
	OR	A			; CLEAR CARRY FLAG
	SBC	HL,BC			; CUR LEN := CUR LEN - BBUF SIZE
	JR	C,HBX_BC_LAST		; END GAME, LESS THAN BBUF BYTES LEFT
	EX	(SP),HL			; HL := CUR SRC, (SP) := REM LEN
	CALL	HBX_BC_ITER		; DO A FULL BBUF SIZE CHUNK
	JR	HBX_BC_LOOP		; AND REPEAT TILL DONE
;
HBX_BC_LAST:
	; HL IS BETWEEN -(BBUF SIZE) AND -1, BC = BBUF SIZE
	OR	A			; CLEAR CARRY
	ADC	HL,BC			; HL := REM LEN (0 - 127)
	EX	(SP),HL			; HL := CUR SRC, (SP) := REM LEN
	POP	BC			; BC := REM LEN
	CALL	NZ,HBX_BC_ITER		; DO FINAL CHUNK, IFF > 0 BYTES
	POP	AF			; RECOVER ORIGINAL BANK
	CALL	HBX_BNKSEL		; SWITCH

	LD	SP,$FFFF		; RESTORE STACK
HBX_BC_SP	.EQU	$ - 2		; ... TO ORIGINAL VALUE
  #IF (CPUFAM == CPU_Z280)
	EX	(SP),HL			; SAVE HL, RECOVER MSR
	PUSH	BC			; SAVE BC
	LD	C,Z280_MSR
	LDCTL	(C),HL
	POP	BC			; RECOVER BC
	POP	HL			; RECOVER HL
  #ELSE
	POP	AF
	JP	PO,$+4
	EI				; *** DO NOT USE HB_EI HERE ***
  #ENDIF
	RET
;
HBX_BC_ITER:
	; HL = SRC ADR, DE = DEST ADR, BC = LEN
	PUSH	BC			; SAVE COPY LEN
	PUSH	DE			; FINAL DEST ON STACK
	LD	DE,HBX_BUF		; SET DEST TO BUF
	LD	A,(HB_SRCBNK)		; GET SOURCE BANK
	CALL	HBX_BNKSEL		; SWITCH TO SOURCE BANK
	LDIR				; HL -> BUF (DE), BC BYTES, HL UPDATED SRC ADR
	POP	DE			; DE := FINAL DEST
	POP	BC			; GET LEN BACK IN BC
	PUSH	HL			; SAVE UPDATED SRC ADR
	LD	HL,HBX_BUF		; SET SRC ADR TO BUF
	LD	A,(HB_DSTBNK)		; GET DEST BANK
	CALL	HBX_BNKSEL		; SWITCH TO DEST BANK
	LDIR				; BUF (HL) -> DE, BC BYTES, DE UPDATED DEST ADR
	POP	HL			; RECOVER UPDATED SRC ADR
	; HL = UPDATED SRC, DE = UPDATED DEST, BC = 0
	RET
#ENDIF
;
; CALL A ROUTINE IN ANOTHER BANK.
; CALLER MUST ENSURE STACK IS ALREADY IN HIGH MEMORY AND HAS ADEQUATE SPACE.
; IF IM1 INTERRUPTS ARE POSSIBLE, CALLER MUST EITHER DISABLE THEM PRIOR TO
; BNKCALL OR MAKE SURE THAT PAGE ZERO IN TARGET BANK IS PREPARED FOR THEM.
; ON INPUT A=TARGET BANK, IX=TARGET ADDRESS
;
HBX_BNKCALL:
;
#IF (MEMMGR == MM_Z280)
	CP	BID_BIOS		; CALLING HBIOS?
	JR	NZ,HBX_BNKCALL3		; NOPE, DO NORMAL PROCESSING
	SC	HBX_BNKCALL2		; SYSCALL TO BNKCALL2
	RET				; THEN RETURN
;
HBX_BNKCALL2:
	HB_EI				; INTS ARE OK
	LD	(HBX_BNKCALL_GO+1),IX	; SETUP DEST ADR
	PCACHE				; CRITICAL!!!
HBX_BNKCALL_GO:
	JP	$FFFF			; DO THE REAL WORK AND RETURN
#ENDIF
;
HBX_BNKCALL3:
	LD	(HBX_BNKCALL_BNK+1),A	; STUFF TARGET BANK TO CALL INTO CODE BELOW
	LD	(HBX_BNKCALL_ADR+1),IX	; STUFF ADDRESS TO CALL INTO CODE BELOW
	LD	A,(HB_CURBNK)		; GET CURRENT BANK
	PUSH	AF			; SAVE FOR RETURN
HBX_BNKCALL_BNK:
	LD	A,$FF			; LOAD BANK TO CALL ($FF OVERLAID AT ENTRY)
	CALL	HBX_BNKSEL		; ACTIVATE THE NEW BANK
HBX_BNKCALL_ADR:
	CALL	$FFFF			; CALL ROUTINE ($FFFF IS OVERLAID ABOVE)
	EX	(SP),HL			; SAVE HL AND GET BANK TO RESTORE IN HL
	PUSH	AF			; SAVE AF
	LD	A,H			; BANK TO RESTORE TO A
	CALL	HBX_BNKSEL		; RESTORE IT
	POP	AF			; RECOVER AF
	POP	HL			; RECOVER HL
	RET
;
; PEEK & POKE ROUTINES
;   ADDRESS IN HL, BANK IN D, VALUE IN/OUT IN E, A IS TRASHED
;
; THESE ROUTINES ARE NOT INTENDED TO BE CALLED DIRECTLY -- THEY ARE
; HELPERS FOR THE HBIOS API AND ARE CALLED BY HBIOS BANK CODE.  THE
; HBIOS BANK CODE BRACKETS THE USE OF THESE ROUTINES WITH DI/EI IF
; NECESSARY FOR THE CURRENT INTERRUPT MODE.
;
; NOTE: THE SIZE OF HBX_TMPSTK (TYPICALLY 20 BYTES) IS INSUFFICIENT FOR
; FREERTOS IF AN INTERRUPT STRIKES WHILE THE TEMPORARY STACK IS ACTIVE.
; BELOW, HBX_BUF HAS BEEN USURPED TO PROVIDE A LARGER TEMP STACK TO
; ACCOMMODATE FREERTOS.  HBX_BUF IS ONLY USED AS A BOUNCE BUFFER, SO IT'S
; USE WILL NEVER OVERLAP WITH BELOW.
;
HBX_PEEK:
	LD	(HBX_PPSP),SP		; SAVE ORIGINAL STACK FRAME
	;;;LD	SP,HBX_BUF_END		; BORROW HBX_BUF FOR TEMP STACK
	LD	SP,HBX_BUF_END - $20	; BORROW HBX_BUF FOR TEMP STACK
	LD	A,(HB_CURBNK)
	PUSH	AF
	LD	A,D
	CALL	HBX_BNKSEL
	LD	E,(HL)
	JR	HBX_PPRET
;
HBX_POKE:
	LD	(HBX_PPSP),SP		; SAVE ORIGINAL STACK FRAME
	;;;LD	SP,HBX_BUF_END		; BORROW HBX_BUF FOR TEMP STACK
	LD	SP,HBX_BUF_END - $20	; BORROW HBX_BUF FOR TEMP STACK
	LD	A,(HB_CURBNK)
	PUSH	AF
	LD	A,D
	CALL	HBX_BNKSEL
	LD	(HL),E
;
HBX_PPRET:
	POP	AF
#IF (MEMMGR == MM_Z280)
	LD	A,(HB_INVBNK)		; SPECIAL CASE FOR Z280 MEM MGR
#ENDIF
	CALL	HBX_BNKSEL
	LD	SP,0			; RESTORE ORIGINAL STACK FRAME
HBX_PPSP	.EQU	$ - 2
	RET
;
; PRIVATE STACK AT END OF HBIOS CODE
; OCCUPIES SPACE BEFORE IVT
;
#IF (MEMMGR != MM_Z280)
;
; HEATH FRONT PANEL WORK SPACE (4 BYTES)
;
  #IF (H8PENABLE)
H8P_MEMLOC:
	.DW	0
H8P_MEMVAL:
	.DB	0
H8P_MEMCPY:
	LD	HL,(H8P_TICCNT)
	LD	($000B),HL
	LD	HL,(H8P_MEMLOC)
	LD	A,(HL)
	LD	(H8P_MEMVAL),A
	RET
  #ENDIF
;
HBX_INTSTKSIZ	.EQU	$FF00 - $
		MEMECHO	"HBIOS INT STACK space: "
		MEMECHO	HBX_INTSTKSIZ
		MEMECHO	" bytes.\n"
		.FILL	HBX_INTSTKSIZ,$FF
HBX_INTSTK	.EQU	$
;
  #IF (HBX_INTSTKSIZ < 22)
	.ECHO	"*** ERROR: INTERRUPT STACK IS TOO SMALL!!!\n"
	!!!	; FORCE AN ASSEMBLY ERROR
  #ENDIF
;
#ENDIF
;
; HBIOS INTERRUPT MODE 2 SLOT ASSIGNMENTS (SEE STD.ASM)
;
; #	Z80/Z280	Z180            MBC		DUO		NABU
; ---	--------------	--------------  --------------	--------------	--------------
; 0	CTC0A		INT1 -+               -+	      -+	HCCARCV -+
; 1	CTC0B		INT2  |                |	       |	HCCASND  |
; 2	CTC0C		TIM0  |                | IM2	       | IM2	NABUKB	 | IM2
; 3	CTC0D		TIM1  |                | INT	       | INT	VDP	 | INT
; 4	UART0		DMA0  | Z180	UART0  | VEC	UART0  | VEC	OPTCRD0  | VEC
; 5	UART1		DMA1  | CPU	UART1  | GEN	UART1  | GEN	OPTCRD1  | GEN
; 6			CSIO  |                |	       |	OPTCRD2  |
; 7	SIO0		SER0  |               -+	      -+	OPTCRD3 -+
; 8	SIO1		SER1 -+         SIO0		SIO0
; 9	PIO0A		PIO0A           SIO1		SIO1
; 10	PIO0B		PIO0B           PIO0A		PIO0A
; 11	PIO1A		PIO1A           PIO0B		PIO0B
; 12	PIO1B		PIO1B           CTC0A		CTC0A
; 13			SIO0            CTC0B		CTC0B
; 14			SIO1            CTC0C		CTC0C
; 15					CTC0D		CTC0D
;
	; IVT MUST START AT PAGE BOUNDARY
	ALIGN($100)
;
HBX_IVT:
	.DW	HBX_IV00
	.DW	HBX_IV01
	.DW	HBX_IV02
	.DW	HBX_IV03
	.DW	HBX_IV04
	.DW	HBX_IV05
	.DW	HBX_IV06
	.DW	HBX_IV07
	.DW	HBX_IV08
	.DW	HBX_IV09
	.DW	HBX_IV0A
	.DW	HBX_IV0B
	.DW	HBX_IV0C
	.DW	HBX_IV0D
	.DW	HBX_IV0E
	.DW	HBX_IV0F
;
HBX_IVTCNT	.EQU	($ - HBX_IVT) / 2
;
HBX_IV00:	CALL HBX_INT \ .DB $00 << 2
HBX_IV01:	CALL HBX_INT \ .DB $01 << 2
HBX_IV02:	CALL HBX_INT \ .DB $02 << 2
HBX_IV03:	CALL HBX_INT \ .DB $03 << 2
HBX_IV04:	CALL HBX_INT \ .DB $04 << 2
HBX_IV05:	CALL HBX_INT \ .DB $05 << 2
HBX_IV06:	CALL HBX_INT \ .DB $06 << 2
HBX_IV07:	CALL HBX_INT \ .DB $07 << 2
HBX_IV08:	CALL HBX_INT \ .DB $08 << 2
HBX_IV09:	CALL HBX_INT \ .DB $09 << 2
HBX_IV0A:	CALL HBX_INT \ .DB $0A << 2
HBX_IV0B:	CALL HBX_INT \ .DB $0B << 2
HBX_IV0C:	CALL HBX_INT \ .DB $0C << 2
HBX_IV0D:	CALL HBX_INT \ .DB $0D << 2
HBX_IV0E:	CALL HBX_INT \ .DB $0E << 2
HBX_IV0F:	CALL HBX_INT \ .DB $0F << 2
;
HBX_INT:	; COMMON INTERRUPT ROUTING CODE
;
#IF (INTMODE > 0)
;
  #IF (MEMMGR == MM_Z280)
;
; THIS CODE ASSUMES Z280 IM 3.  IM 1 AND IM 2 ON Z280
; DO NOT SAVE MSR AT INTERRUPT MAKING IT VIRTUALLY IMPOSSIBLE
; TO RETURN FROM THE INTERRUPT TO THE CORRECT MODE (SYSTEM
; OR USER).  THIS IS BECAUSE THERE IS NO WAY TO KNOW WHETHER
; SYSTEM OR USER MODE WAS ACTIVE AT THE TIME OF THE INTERRUPT.
;
	EX	(SP),HL			; SAVE HL AND GET INT JP TABLE OFFSET

	; SAVE STATE (HL SAVED PREVIOUSLY ON ORIGINAL STACK FRAME)
	PUSH	AF			; SAVE AF
	PUSH	BC			; SAVE BC
	PUSH	DE			; SAVE DE
	PUSH	IY			; SAVE IY
;
	; HANDLE INT VIA JP TABLE IN HBIOS
	LD	L,(HL)			; OFFSET INTO JP TABLE FOR THIS INT
	LD	H,HB_IVT >> 8		; MSB OF HBIOS INT JP TABLE
	CALL	JPHL			; CALL HANDLER VIA INT JP TABLE
;
	; RESTORE STATE
	POP	IY			; RESTORE IY
	POP	DE			; RESTORE DE
	POP	BC			; RESTORE BC
	POP	AF			; RESTORE AF
	POP	HL			; RESTORE HL
;
	; BURN THE REASON CODE
	EX	(SP),HL			; HL TO STK, RC TO HL
	POP	HL			; RESTORE HL
;
	CALL	HBX_RETI		; RETI FOR Z80 PERIPHERALS
	RETIL
;
HBX_RETI:
	RETI
;
  #ELSE
;
; COMMON INTERRUPT DISPATCHING CODE
; SETUP AND CALL HANDLER IN BIOS BANK
;
	EX	(SP),HL			; SAVE HL AND GET INT JP TABLE OFFSET
;
	LD	(HBX_INT_SP),SP		; SAVE ORIGINAL STACK FRAME
	LD	SP,HBX_INTSTK		; USE DEDICATED INT STACK FRAME IN HI MEM
;
	; SAVE STATE (HL SAVED PREVIOUSLY ON ORIGINAL STACK FRAME)
	PUSH	AF			; SAVE AF
	PUSH	BC			; SAVE BC
	PUSH	DE			; SAVE DE
	PUSH	IY			; SAVE IY
;
	LD	A,BID_BIOS		; HBIOS BANK
	CALL	HBX_BNKSEL_INT		; SELECT IT
;
	LD	L,(HL)			; OFFSET INTO JP TABLE FOR THIS INT
	LD	H,HB_IVT >> 8		; MSB OF HBIOS INT JP TABLE
;
	CALL	JPHL			; CALL HANDLER VIA INT JP TABLE
;
	LD	A,(HB_CURBNK)		; GET PRE-INT BANK
	CALL	HBX_BNKSEL		; SELECT IT
;
  #IF (H8PENABLE)
	CALL	H8P_MEMCPY
  #ENDIF
;
	; RESTORE STATE
	POP	IY			; RESTORE IY
	POP	DE			; RESTORE DE
	POP	BC			; RESTORE BC
	POP	AF			; RESTORE AF
;
	LD	SP,$FFFF		; RESTORE ORIGINAL STACK FRAME
HBX_INT_SP	.EQU	$ - 2
;
	POP	HL			; RESTORE HL
;

#IF (CPUFAM == CPU_EZ80)
	RET.L				; INTERRUPTS WILL BE ENABLED BY BY EZ80 FIRMWARE
					; CAN THEY BE ENABLED HERE - DOES THAT RISK RE-ENTRANT OF THE HANDLER?
#ELSE
	HB_EI				; ENABLE INTERRUPTS
	RETI				; AND RETURN
#ENDIF

;
  #ENDIF
;
#ELSE
;
	RET
;
#ENDIF
;
; SMALL TEMPORARY STACK FOR USE BY HBX_BNKCPY
;
HBX_TMPSTKSIZ	.EQU	(HBX_XFC - HBX_BUFSIZ - $)
		MEMECHO	"HBIOS TEMP STACK space: "
		MEMECHO	HBX_TMPSTKSIZ
		MEMECHO	" bytes.\n"
		.FILL	HBX_TMPSTKSIZ,$CC
HBX_TMPSTK	.EQU	$
;
; INTERBANK COPY BOUNCE BUFFER (64 BYTES)
;
; N.B., THIS BUFFER IS ALSO USED AS A TEMPORARY STACK BY INVOKE, PEEK, AND POKE.
; THEREFORE, THIS BUFFER *CANNOT* BE USED TO PASS DATA OUTSIDE OF
; HBIOS FUNCTION CALLS.
;
HBX_BUF		.FILL	HBX_BUFSIZ,0
HBX_BUF_END	.EQU	$
;
; HBIOS PROXY MGMT BLOCK (TOP 32 BYTES)
;
	.DB	BID_BOOT		; (+0) HB_CURBNK: CURRENTLY ACTIVE LOW MEMORY BANK ID
	.DB	$FF			; (+1) HB_INVBNK: BANK ACTIVE AT TIME OF HBIOS CALL INVOCATION
	.DW	0			; (+2) HB_SRCADR: BNKCPY SOURCE ADDRESS
	.DB	BID_USR			; (+4) HB_SRCBNK: BNKCPY SOURCE BANK ID
	.DW	0			; (+5) HB_DSTADR: BNKCPY DESTINATION ADDRESS
	.DB	BID_USR			; (+7) HB_DSTBNK: BNKCPY DESTINATION BANK ID
	.DW	0			; (+8) HB_CPYLEN: BNKCPY LENGTH
	.DW	0			; (+10) RESERVED FOR OPTIONAL TICK CTR, PLATFORM DEPENDENT
	.DW	0			; (+12) RESERVED FOR FUTURE HBIOS USE
	.DB	0			; (+14) SHADOW VALUE FOR RTC LATCH PORT
	.DB	$FE			; (+15) HB_LOCK: HBIOS MUTEX LOCK
	JP	HBX_INVOKE		; (+16) HB_INVOKE: FIXED ADR ENTRY FOR HBX_INVOKE (ALT FOR RST 08)
	JP	HBX_BNKSEL		; (+19) HB_BNKSEL: FIXED ADR ENTRY FOR HBX_BNKSEL
	JP	HBX_BNKCPY		; (+22) HB_BNKCPY: FIXED ADR ENTRY FOR HBX_BNKCPY
	JP	HBX_BNKCALL		; (+25) HB_BNKCALL: FIXED ADR ENTRY FOR HBX_BNKCALL
	.DW	HBX_IDENT		; (+28) ADDRESS OF HBIOS PROXY START (DEPRECATED)
	.DW	HBX_IDENT		; (+30) HB_IDENT: ADDRESS OF HBIOS IDENT INFO DATA BLOCK
;
	.FILL	MEMTOP - $		; FILL TO END OF MEMORY (AS NEEDED)
	.ORG	HBX_IMG + HBX_SIZ	; RESTORE ORG
;
HB_PROXY_END	.EQU	$
;
;==================================================================================================
;   ENTRY VECTORS (JUMP TABLE) AND INTERNAL PROCESSING STACK
;==================================================================================================
;
HB_ENTRY_BEG	.EQU	$
HB_ENTRYTBL	.EQU	$
;
	JP	HB_START		; HBIOS INITIALIZATION
	JP	HB_DISPATCH		; VECTOR TO DISPATCHER
	JP	PRTSUM
	; !!! DO NOT ADD ADDTIONAL VECTORS HERE WITHOUT
	; CHECKING W/ WAYNE !!!
;
HB_STKSIZ	.EQU	$100 - ($ & $FF)
;
		.FILL	HB_STKSIZ,$FF	; USE REMAINDER OF PAGE FOR HBIOS STACK
HB_STACK	.EQU	$		; TOP OF HBIOS STACK
HB_ENTRY_END	.EQU	$
;
;==================================================================================================
;   INTERRUPT VECTOR TABLE (MUST START AT PAGE BOUNDARY!!!)
;==================================================================================================
;
; IM2 INTERRUPTS ARRIVE HERE AFTER BANK SWITCH TO HBIOS BANK
; THE LIST OF JP TABLE ENTRIES MATCHES THE IM2 VECTORS ONE FOR
; ONE.  ANY CALL TO THE PRIMARY IVT (HBX_IVT) WILL BE MAPPED TO
; THE CORRESPONDING JP TABLE ENTRY BELOW AFTER THE BANK SWITCH.
;
; NOTE THAT THE LIST IS INITIALLY FILLED WITH CALLS TO HB_BADINT.
; IT IS INTENDED THAT HARDWARE DRIVERS WILL DYNAMICALLY OVERLAY
; THE ADDRESS PORTION OF THE APPROPRIATE JP TO POINT TO THE
; DESIRED INTERRUPT HANDLER DURING THE DRIVERS INITIALIZATION.
;
; NOTE THAT EACH ENTRY HAS A FILLER BYTE OF VALUE ZERO.  THIS BYTE
; HAS NO FUNCTION.  IT IS JUST USED TO MAKE ENTRIES AN EVEN 4 BYTES.
;
HB_INTVEC_BEG	.EQU	$
;
HB_IVT:
HB_IVT00:	JP	HB_BADINT \ .DB 0
HB_IVT01:	JP	HB_BADINT \ .DB 0
HB_IVT02:	JP	HB_BADINT \ .DB 0
HB_IVT03:	JP	HB_BADINT \ .DB 0
HB_IVT04:	JP	HB_BADINT \ .DB 0
HB_IVT05:	JP	HB_BADINT \ .DB 0
HB_IVT06:	JP	HB_BADINT \ .DB 0
HB_IVT07:	JP	HB_BADINT \ .DB 0
HB_IVT08:	JP	HB_BADINT \ .DB 0
HB_IVT09:	JP	HB_BADINT \ .DB 0
HB_IVT0A:	JP	HB_BADINT \ .DB 0
HB_IVT0B:	JP	HB_BADINT \ .DB 0
HB_IVT0C:	JP	HB_BADINT \ .DB 0
HB_IVT0D:	JP	HB_BADINT \ .DB 0
HB_IVT0E:	JP	HB_BADINT \ .DB 0
HB_IVT0F:	JP	HB_BADINT \ .DB 0
HB_IVT10:	JP	HB_IM1INT \ .DB 0
;
; IM1 INTERRUPTS ARRIVE HERE AFTER BANK SWITCH TO HBIOS BANK
; LIST OF IM1 INT CALLS IS BUILT DYNAMICALLY BELOW
; SEE HB_ADDIM1 ROUTINE
; EACH ENTRY WILL LOOK LIKE:
;	CALL	XXXX			; CALL INT HANDLER
;	RET	NZ			; RETURN IF HANDLED
;
; NOTE THAT THE LIST IS INITIALLY FILLED WITH CALLS TO HB_BADINT.
; AS THE TABLE IS POPULATED, THE ADDRESS OF HB_BADINT IS OVERLAID
; WITH THE ADDRESS OF A REAL INTERRUPT HANDLER.
;
; THERE IS ROOM FOR 8 ENTRIES PLUS A FINAL CALL TO HB_BADINT.
;
HB_IM1INT:
	CALL HB_BADINT \ RET NZ
	CALL HB_BADINT \ RET NZ
	CALL HB_BADINT \ RET NZ
	CALL HB_BADINT \ RET NZ
	CALL HB_BADINT \ RET NZ
	CALL HB_BADINT \ RET NZ
	CALL HB_BADINT \ RET NZ
	CALL HB_BADINT \ RET NZ
	CALL HB_BADINT \ RET NZ
;
HB_IM1CNT	.DB	0		; NUMBER OF ENTRIES IN CALL LIST
HB_IM1MAX	.DB	8		; MAX ENTRIES IN CALL LIST
HB_IM1PTR	.DW	HB_IM1INT	; POINTER FOR NEXT IM1 ENTRY
;
HB_INTVEC_END	.EQU	$
;
;==================================================================================================
;   SYSTEM INITIALIZATION
;==================================================================================================
;
HB_SYSINIT_BEG	.EQU	$
;
HB_START:
;
#IFDEF APPBOOT
	; THE CODE TO PREPARE FOR AN APPBOOT IS "HIDDEN" IN HB_WRKBUF.
	; WE ARE OPERATING ON THE (MINIMAL) BDOS STACK, BUT THAT
	; SHOULD BE FINE FOR THIS LIMITED ACTIVITY.
	CALL	HB_APPBOOT		; PREPARE APP BOOT
	RET	NZ			; RETURN ON ERROR
;
#ENDIF
;
HB_RESTART:
;
	DI				; NO INTERRUPTS
#IF (CPUFAM != CPU_EZ80)
	IM	1			; INTERRUPT MODE 1
#ENDIF
;
#IFDEF APPBOOT
;
	; IF THIS IS AN APPLICATION BOOT, WE CAPTURE THE CURRENT BANK ID
	; AND UPDATE THE PROXY IMAGE.  LATER, WHEN THE PROXY IMAGE IS COPIED
	; TO IT'S RUNNING LOCATION AT TOP OF RAM, THE CORRECT HB_CURBNK
	; VALUE WILL BE INSTALLED. NOTE: THE ADDRESSES IN THE PROXY
	; IMAGE ARE FOR IT'S RUNNING LOCATION, SO WE NEED TO USE *MATH*
	; TO DERIVE THE LOCATION OF HB_CURBNK IN THE IMAGE.
	LD	A,(HB_CURBNK)
	LD	(HB_CURBNK - HBX_LOC + HBX_IMG),A
;
#ENDIF
;
#IF ((PLATFORM == PLT_DUO) & TRUE)
	; THIS ARBITRARY DELAY SEEMS TO HELP DUODYNE CPU V1.0 SYSTEMS
	; STARTUP CLEANLY.  DOUDYNE CPU V1.1 INTRODUCES A RESET
	; SUPERVISOR AND THIS DELAY IS UNNECESSARY.  WE DON'T KNOW
	; IF WE ARE ON A V1.1 THOUGH, SO WE ALWAYS DO THE DELAY.
	LD	HL,0
BOOTWAIT:
	DEC	HL
	LD	A,H
	OR	L
	JR	NZ,BOOTWAIT
#ENDIF
;
; EARLY RTC LATCH BYTE INITIALIZATION
; FOR SOME PLATFORMS THIS CONTROLS HI/LO SPEED CIRCUIT
; NOTE: WE WANT TO USE (RTCDEFVAL) HERE, BUT THE Z2 MEMORY
; MANAGER STARTS UP WITH THE FIRST 16K OF ROM MAPPED TO ALL
; 4 16K BANKS OF CPU SPACE.  SO, IF RTCDEVFAL IS LOCATED AFTER
; PAST 16K, WE DON'T HAVE ACCESS TO IT.  FOR NOW, WE JUST USE
; RTCDEF WHICH IS SUBOPTIMAL, BUT PROBABLY DOES NOT CAUSE ANY
; PROBLEMS.
;
	;LD	A,(RTCDEFVAL)		; GET DEFAULT VALUE
	LD	A,RTCDEF		; DEFAULT VALUE
	EZ80_IO()
	OUT	(RTCIO),A		; SET IT
;
#IF (PLATFORM == PLT_N8)
	LD	A,N8_DEFACR		; ENSURE N8 ACR
	OUT0	(N8_ACR),A		; ... REGISTER IS INITIALIZED
#ENDIF
;
#IF (PLATFORM == PLT_RPH)
	LD	A,RPH_DEFACR		; ENSURE RPH ACR
	OUT0	(RPH_ACR),A		; ... REGISTER IS INITIALIZED
#ENDIF

;
; INITIALIZE DIAGNOSTIC AND/OR FRONT PANEL LED(S) TO INDICATE THE
; SYSTEM IS ALIVE. WE HAVE NO RAM AT THIS TIME, SO WE CANNOT USE
; THE NORMAL DIAG() OR FPLEDS() MACROS WHICH DEPEND UPON A STACK.
; SO, JUST HACK THE VALUES IN PLACE.
;
#IF (FPLED_ENABLE)
  #IF (FPLED_INV)
	LD	A,~DIAG_01
  #ELSE
	LD	A,DIAG_01
  #ENDIF
;
	EZ80_IO()
	OUT	(FPLED_IO),A

#ENDIF
;

#IF (LEDENABLE)
  #IF ((LEDMODE == LEDMODE_STD) | (LEDMODE == LEDMODE_SC))
	XOR	A			; LED IS INVERTED, TURN IT ON
  #ENDIF
  #IF (LEDMODE == LEDMODE_RTC)
	; CAN'T USE (RTCDEFVAL) YET, SEE COMMENTS ABOVE
	;LD	A,(RTCDEFVAL)		; DEFAULT LATCH VALUE
	LD	A,RTCDEF | %00000001	; LED 0 ON
  #ENDIF
  #IF (LEDMODE == LEDMODE_NABU)
	LD	A,%00001000		; LOW LED BIT ONLY
  #ENDIF
	OUT	(LEDPORT),A
#ENDIF
;
; INITIALIZE SP
;
; WARNING: ALTHOUGH WE ARE INITIALIZING SP HERE, IT IS NOT YET
; SAFE TO PUSH VALUES TO THE STACK BECAUSE SOME PLATFORMS WILL
; NOT YET HAVE RAM MAPPED TO THE UPPER 32K YET!
;
	LD	SP,HBX_LOC		; SETUP INITIAL STACK JUST BELOW HBIOS PROXY
;
; Z280 BARE METAL INIT
;
#IF (CPUFAM == CPU_Z280)
	; CLEAR THE MASTER STATUS REGISTER
	LD	C,Z280_MSR		; MASTER STATUS REGISTER
	LD	HL,$0000		; SYS MODE, NO INTERRUPTS
	LDCTL	(C),HL			; DO IT
;
	; SET MAXIMUM I/O WAIT STATES FOR NOW
	LD	C,Z280_BTCR		; BUS TIMING AND CONTROL REGISTER
	LD	HL,$0033		; 3 I/O WAIT STATES ADDED
	LDCTL	(C),HL			; DO IT
;
	; SELECT I/O PAGE $FF FOR INTERNAL SYSTEM REGISTER ACCESS
	LD	L,$FF			; MMU AND DMA PAGE I/O REG IS $FF
	LD	C,Z280_IOPR		; REG C POINTS TO I/O PAGE REGISTER
	LDCTL	(C),HL			; DO IT
;
	; DISABLE MEMORY REFRESH CYCLES
	LD	A,$08			; REFRESH DISABLED
	OUT	(Z280_RRR),A		; DO IT
;
	; INITIALIZE CACHE CONTROL REGISTER
	LD	A,$20			; CACHE INSTRUCTIONS, NOT DATA
	OUT	(Z280_CCR),A		; DO IT
;
	; INITIALIZE TRAP CONTROL REGISTER
	LD	A,$00			; ALLOW USER I/O, NO EPU, NO STK WARN
	OUT	(Z280_TCR),A		; DO IT
;
  #IF (MEMMGR == MM_Z280)
;
	; BEFORE ENABLING MMU W/ USER & SYSTEM PAGE TRANSLATION,
	; WE INITIALIZE ALL PDRS.  HOWEVER, FOR AN APP
	; BOOT, THE LOW RAM PDRS ARE ALREADY CORRECT AND SHOULD BE
	; LEFT ALONE.
;
	; INITIALIZE ALL OF THE USER PAGE DESCRIPTORS WITH BLOCK MOVE
    #IFDEF APPBOOT
	LD	A,$08			; FIRST USER PDR IN HI MEM
    #ELSE
	LD	A,$00			; FIRST USER PDR
    #ENDIF
	OUT	(Z280_MMUPDRPTR),A	; SET THE PDR POINTER
	LD	HL,Z280_BOOTPDRTBL	; START OF PDR VALUES TABLE
	LD	C,Z280_MMUBLKMOV	; PDR BLOCK MOVE PORT
	LD	B,Z280_PDRCNT		; NUMBER OF PDR ENTRIES TO PROG
	OTIRW				; OTIRW PROGS PDRS SEQUENTIALLY
;
	; INITIALIZE ALL OF THE SYSTEM PAGE DESCRIPTORS WITH BLOCK MOVE
    #IFDEF APPBOOT
	LD	A,$18			; FIRST SYSTEM PDR IN HI MEM
    #ELSE
	LD	A,$10			; FIRST SYSTEM PDR
    #ENDIF
	OUT	(Z280_MMUPDRPTR),A	; SET THE PDR POINTER
	LD	HL,Z280_BOOTPDRTBL	; START OF PDR VALUES TABLE
	LD	C,Z280_MMUBLKMOV	; PDR BLOCK MOVE PORT
	LD	B,Z280_PDRCNT		; NUMBER OF PDR ENTRIES TO PROG
	OTIRW				; OTIRW PROGS PDRS SEQUENTIALLY
;
	; ENABLE MMU (SYSTEM AND USER TRANSLATION)
	LD	C,Z280_MMUMCR		; MMU MASTER CONTROL REGISTER
	LD	HL,$BBFF		; ENABLE USER & SYSTEM TRANSLATE
	OUTW	(C),HL
;
	JR	Z280_INITZ		; JUMP TO CODE CONTINUATION
;
	ALIGN(2)			; WORD ALIGN THE PDR TABLE
;
Z280_BOOTPDRTBL:
    #IFNDEF APPBOOT
	; LOWER 32 K (BANKED)
	.DW	((Z2_BANK(BID_BOOT) + 0) << 4) | $A
	.DW	((Z2_BANK(BID_BOOT) + 1) << 4) | $A
	.DW	((Z2_BANK(BID_BOOT) + 2) << 4) | $A
	.DW	((Z2_BANK(BID_BOOT) + 3) << 4) | $A
	.DW	((Z2_BANK(BID_BOOT) + 4) << 4) | $A
	.DW	((Z2_BANK(BID_BOOT) + 5) << 4) | $A
	.DW	((Z2_BANK(BID_BOOT) + 6) << 4) | $A
	.DW	((Z2_BANK(BID_BOOT) + 7) << 4) | $A
    #ENDIF
	; UPPER 32 K (COMMON)
	.DW	((Z2_BANK(BID_COM) + 0) << 4) | $A
	.DW	((Z2_BANK(BID_COM) + 1) << 4) | $A
	.DW	((Z2_BANK(BID_COM) + 2) << 4) | $A
	.DW	((Z2_BANK(BID_COM) + 3) << 4) | $A
	.DW	((Z2_BANK(BID_COM) + 4) << 4) | $A
	.DW	((Z2_BANK(BID_COM) + 5) << 4) | $A
	.DW	((Z2_BANK(BID_COM) + 6) << 4) | $A
	.DW	((Z2_BANK(BID_COM) + 7) << 4) | $A
;
Z280_PDRCNT	.EQU	($ - Z280_BOOTPDRTBL) / 2
;
Z280_INITZ:
;
  #ENDIF
;
	; RESTORE I/O PAGE TO $00 FOR NORMAL USER I/O SPACE
	LD	L,$00			; NORMAL I/O REG IS $00
	LD	C,Z280_IOPR		; REG C POINTS TO I/O PAGE REGISTER
	LDCTL	(C),HL
;
#ENDIF
;
; Z180 BARE METAL INIT
;
#IF (CPUFAM == CPU_Z180)
	; SET BASE FOR CPU IO REGISTERS
	; DO NOT USE Z180_ICR FROM Z180.INC BECAUSE THE ICR
	; IS NOT YET AT THE RUNNING LOCATION.  AT RESET, THE Z180
	; REGISTER BASE I/O ADDRESS IS ZERO, SO INITIALLY, ICR IS
	; AT $3F.
	LD	A,Z180_BASE
	OUT0	($3F),A		; AT RESET, ICR IS AT $3F

	; DISABLE REFRESH
	XOR	A
	OUT0	(Z180_RCR),A

	; MASK OFF TIMER INTERRUPTS
	XOR	A
	OUT0	(Z180_TCR),A
	OUT0	(Z180_ITC),A

	; SET DEFAULT CPU CLOCK MULTIPLIERS (XTAL / 2)
	;
	; BILL MCMULLEN REPORTED THAT CMR NEEDS TO BE SET PRIOR TO CCR
	; WHEN USING A CPU FREQUENCY (PHI) THAT IS XTAL * 2.
	; HERE WE ARE SETTING CPU FREQUENCY TO XTAL / 2, BUT JUST
	; FOR GOOD MEASURE, CMR IS SET PRIOR TO CCR BELOW.
	; https://www.retrobrewcomputers.org/forum/index.php?t=msg&th=316&goto=5045&#msg_5045
	XOR	A
	OUT0	(Z180_CMR),A
	OUT0	(Z180_CCR),A

	; SET DEFAULT WAIT STATES
	LD	A,$F0
	OUT0	(Z180_DCNTL),A

  #IF ((MEMMGR == MM_Z180) | (MEMMGR == MM_N8) | (MEMMGR == MM_RPH))
	; Z180 MMU SETUP
	LD	A,$80
	OUT0	(Z180_CBAR),A		; SETUP FOR 32K/32K BANK CONFIG
;#IFDEF ROMBOOT
;	XOR	A
;	OUT0	(Z180_BBR),A		; BANK BASE = 0
;#ENDIF
	LD	A,(RAMSIZE + RAMBIAS - 64) >> 2
	OUT0	(Z180_CBR),A		; COMMON BASE = LAST (TOP) BANK
;
	; SET DEFAULT CSIO SPEED (INTERNAL CLOCK, SLOW AS POSSIBLE)
	LD	A,Z180_CNTR_DEF		; DIV 1280, 14KHZ @ 18MHZ CLK
	OUT0	(Z180_CNTR),A
  #ENDIF
;
#ENDIF
;
; EIPC BARE METAL INIT
;
#IF (EIPCENABLE)
	LD	A,(EIPC_WDT_CONST | EIPC_HALT_RUN | EIPC_WDT_P2_22)
	OUT	(EIPC_WDTMR),A		; CLEAR WDTE BIT (DISABLE WATCHDOG)
	LD	A,EIPC_DIS_WDT		; DISABLE WDT - SECOND KEY
	OUT 	(EIPC_WDTCR),A
	LD	A,EIPC_WCR		; SET SYSTEM CONTROL REGISTER POINTER
					; (SCRP) TO POINT TO WAIT STATE
	OUT	(EIPC_SCRP),A		; CONTROL REGISTER (WCR)
	LD	A,(EIPC_IO_0WS | EIPC_MEM_OWS | EIPC_OCF_0WS | EIPC_INT_0WS | EIPC_CHAIN_0WS)
	OUT	(EIPC_SCDP),A		; NO WAIT STATES
	LD	A,EIPC_MCR		; SET SCRP TO POINT TO MISCELLANEOUS
	OUT	(EIPC_SCRP),A		; CONTROL REGISTER (MCR)
	LD	A,EIPC_CLKDIV1		; DIVIDE CLOCK BY 1, /CS0 DISABLE
	OUT	(EIPC_SCDP),A		; SET SYSTEM CONTROL DATA PORT (SCDP)
#ENDIF
;
;--------------------------------------------------------------------------------------------------
; PLATFORM MEMORY MANAGEMENT INITIALIZATION
;--------------------------------------------------------------------------------------------------
;
; INITIALIZE MEMORY MANAGERS AS NEEDED TO ADDRESS BOOT ROM IN LOW 32K
; AND COMMON RAM IN HIGH 32K.  SETUP MMU FOR BANKING IN LOWER 32K.
;
; Z180 MINI-ITX MADNESS TO INITIALIZE THE PPIO. WE HAVE THE MAIN RAM AT
; $8000 AND ROM AT $0 AT THIS POINT AND THE Z180 MMU SET UP. NOW
; GET THE 82C55 PROGRAMMED.
;
#IF (PLATFORM == PLT_EPITX)
	; THE 82C55 IS BRAINDEAD AND FLIPS OUTPUT LINES TO 0 WHEN WE SET
	; THE MODE. WE BOOT WITH THE ROM ENABLED BUT THE RESET WILL ENABLE
	; LOW RAM. SOME MENTAL BACKFLIPS REQUIRED TO MAKE THIS WORK
	LD	HL,BOOTFLIP
	LD	DE,$8000
	LD	BC,$10
	LDIR
	JP	$8000
;
BOOTFLIP:
	; SET THE MODE. ALSO CLEARS ALL THE OUTPUT BITS SO WE BLIP THE
	; I2C, KEYBOARD ETC BUT NOBODY WILL CARE. HOWEVER WE ALSO FLIP
	; TO ALL RAM MODE HENCE THIS IS EXECUTED HIGH
	; A OUT B IN C HIGH IN C LOW IN
	LD	A,$8B
	OUT	($43),A
	LD	A,$FF
	OUT	($40),A
	JP	ROMRESUME
;
ROMRESUME:
	; THIS WILL GLITCH EXTRAM ON SO WE MUST NOW BE IN ROM
	LD	A,$8A		; C LOW NOW OUTPUT
	OUT	($43),A
	LD	A,$FF
	OUT	($42),A		; EXTRAM OFF, RAM BACK IN, SPI 7
	; AND DONE. MODE REMAINS THIS WAY FOREVER
;
#ENDIF
;
; SBC AND MBC MMU INITIALIZATION
;
#IF ((MEMMGR == MM_SBC) | (MEMMGR == MM_MBC))
	; SET PAGING REGISTERS
  #IFDEF ROMBOOT
	XOR	A
	OUT	(MPCL_RAM),A		; REMOVE RAM FIRST!
	OUT	(MPCL_ROM),A		; SELECT ROM PAGE 0
  #ENDIF
#ENDIF
;
; ZETA 2 AND DUO MMU INITIALIZATION
;
; ZETA 2 MMU USES 4 16K PAGES TO MAP PHYSICAL MEMORY TO CPU MEMORY.
; HBIOS USES THE LOWER 2 16K PAGES FOR BANKING AND UPPER 2 16K PAGES
; FOR COMMON.  NORMALLY, A ZETA 2 BASED SYSTEM WILL CONTAIN 512K OF
; PHYSICAL ROM FOLLOWED BY 512K OF PHYSICAL RAM.  DUO USES A PHYSICAL
; ADDRESS SPACE OF 4096K WITH THE FIRST 2048K AS ROM AND THE FOLLOWING
; 2048K AS RAM.  THE SIZE OF ROM AND RAM CAN VARY FOR DUO, BUT THE
; RAM BOUNDARY IS ALWAYS AT 2048K.
;
#IF (MEMMGR == MM_Z2)
;
  #IFDEF ROMBOOT
	; IF THIS IS A ROM BOOT, SETUP THE FIRST 2 16K MMU REGISTERS
	; TO MAP THE LOWEST 32K OF PHYSICAL ROM TO THE LOW 32K OF
	; CPU ADDRESS SPACE (BANKING AREA).  THE FIRST 16K MAPPING IS
	; REDUNDANT BECAUSE WE ARE ALREADY RUNNING IN THIS AREA.  THE
	; MAPPING OF THE SECOND 16K IS CRITICAL BECAUSE ALL ZETA 2
	; MMU REGISTERS WILL BE 0 AT RESET!

	XOR	A
	EZ80_IO()
	OUT	(MPGSEL_0),A		; PROG FIRST 16K MMU REGISTER
	INC	A
	EZ80_IO()
	OUT	(MPGSEL_1),A		; PROG SECOND 16K MMU REGISTER
  #ENDIF
;
  #IF (PLATFORM == PLT_DUO)
	; DUO HAS VARIABLE RAM SIZE.  RAM ALWAYS STARTS AT 2048K.
	; SETUP COMMON RAM FOR HIGHEST 32K OF RAM BASED ON TOTAL RAM.
	LD	A,128 + (RAMSIZE / 16) - 2
  #ELSE
	; NORMAL ZETA 2 SYSTEM HAS FIXED 512K OF RAM.  SETUP COMMON
	; FOR TOP 32K OF THIS.
	;LD	A,64 - 2
	LD	A,((ROMSIZE + RAMSIZE) / 16) - 2
  #ENDIF
;
	EZ80_IO()
	OUT	(MPGSEL_2),A		; PROG THIRD 16K MMU REGISTER
	INC	A
	EZ80_IO()
	OUT	(MPGSEL_3),A		; PROG FOURTH 16K MMU REGISTER
	; ENABLE PAGING
	LD	A,1
	EZ80_IO()
	OUT	(MPGENA),A		; ENABLE MMU NOW
;
  #IF (PLATFORM == PLT_FZ80)
	; REMOVE FPGA ROM MONITOR FROM THE CPU ADDRESS SPACE
	LD	A,%00000010
	OUT	($07),A
  #ENDIF
#ENDIF

;
;--------------------------------------------------------------------------------------------------
; PROXY INSTALLATION
;--------------------------------------------------------------------------------------------------
;
; AT THIS POINT, RAM SHOULD BE AVAILABLE IN THE COMMON BANK
; (TOP 32K).
;
; WE USE THE TWO BYTES IMMEDIATELY BELOW THE PROXY TO STORE A COUPLE
; VALUES TEMPORARILY BECAUSE WE MAY BE OPERATING IN ROM AT THIS POINT.
; (HBX_LOC - 1) = BATCOND, (HBX_LOC - 2) = LOADBANK
; THERE IS NOTHING ON THE STACK AT THIS POINT SO, HERE, WE JUST RESET
; THE STACK TO HBX_LOC - 2.
;
	LD	SP,HBX_LOC - 2
;
; CHECK BATTERY BACKUP STATUS BEFORE WE TOUCH RAM (UPPER MEMORY)
;
; IF A DS1210 POWER CONTROLLER IS INSTALLED AND BATTERY BACKUP IS NOT INSTALLED
; OR IS LESS THAN 2V THEN THE DS1210 WILL BLOCK THE *SECOND* RAM ACCESS.
; FAILURE TO COMPLETE TWO RAM ACCESSES BEFORE INSTALLING PROXY WILL RESULT
; IN THE ROM ID BYTES NOT BEING COPIED CORRECTLY AND CP/M APPLICATIONS
; WILL NOT START CORRECTLY WHEN THEY CHECK THE ROM ID VERSION BYTES.
; THE BATTERY CONDITION VALUE IS TEMPORARILY STORED AT HBX_LOC - 1
; BECAUSE WE ARE CURRENTLY RUNNING IN ROM.  AFTER WE TRANSITION HBIOS
; TO RAM, THE VALUE IS MOVED TO IT'S REAL LOCATION AT HB_BATCOND.
; IF THERE IS NO DS1210 IN THE SYSTEM, THE CODE BELOW DOES NO HARM.
;
	LD	HL,HBX_LOC - 1		; POINT TO BYTE
	XOR	A			; ZERO MEANS LOW BAT
	LD	(HL),A			; FIRST RAM ACCESS
	INC	A			; 1 MEANS BAT OK
	LD	(HL),A			; SECOND RAM ACCESS (BLOCKED IF BATTERY ISSUE)
;
; INSTALL PROXY IN UPPER MEMORY
;
	LD	DE,HBX_LOC		; RUNNING LOCATION OF PROXY
	LD	HL,HBX_IMG		; LOCATION OF PROXY IMAGE
	LD	BC,HBX_SIZ		; SIZE OF PROXY
	LDIR				; COPY IT
;
; NOTIFICATION THAT WE HAVE COMPLETED HARDWARE INIT.
;
	FPLEDS(DIAG_02)
;
;--------------------------------------------------------------------------------------------------
; S100 MONITOR LAUNCH
;--------------------------------------------------------------------------------------------------
;
; S100 ROM CONTAINS A HARDWARE LEVEL MONITOR IN BANK ID 3 OF ROM.
; IF PORT $75 BIT 1 IS SET (SET IS ZERO), THEN WE IMMEDIATELY
; TRANSITION TO THIS MONITOR.  PRIOR TO THE TRANSITION, WE ALSO
; CHECK THE VALUE IN THE Z180 RELOAD REGISTER LOW.  IF IT IS ASCII 'W',
; THEN IT MEANS THE S100 MONITOR IS ATTEMPTING TO REBOOT INTO ROMWBW
; HBIOS AND WE ABORT THE TRANSITION TO THE S100 MONITOR.
;
#IF ((PLATFORM == PLT_S100) & TRUE)
	; CHECK S100 BOARD DIP SWITCH, BIT 1
	IN	A,($75)			; READ SWITCHES
	BIT	1,A			; CHECK BIT 1
	JR	NZ,S100MON_SKIP		; IF NOT SET, CONT ROMWBW BOOT
;
	; CHECK RELOAD REGISTER LOW FOR SPECIAL VALUE
	IN0	A,(Z180_RLDR1L)		; GET RELOAD REG 1 LOW
	CP	'W'			; CHECK FOR SPECIAL VALUE
	JR	Z,S100MON_SKIP		; IF SO, DO ROMWBW BOOT
;
	; LAUNCH S100 MONITOR FROM ROM BANK 3
	LD	A,BID_IMG2		; S100 MONITOR BANK
	LD	IX,0			; EXECUTION RESUMES HERE
	CALL	HBX_BNKCALL		; CONTINUE IN RAM BANK, DO NOT RETURN
	HALT				; WE SHOULD NOT COME BACK HERE!
;
S100MON_SKIP:
	; RESTORE DEFAULT RELOAD REGISTER VALUE (PROBABLY NOT NEEDED)
	XOR	A
	OUT0	(Z180_RLDR1L),A
#ENDIF
;
;--------------------------------------------------------------------------------------------------
; RTC LATCH INITIALIZATION
;--------------------------------------------------------------------------------------------------
;
; WE CAN NOW DO THE REAL INITIALIZATION OF THE RTC LATCH BASED ON
; (RTCDEFVAL).  AT THIS POINT WE SHOULD HAVE ACCESS TO THE ROM LOCATION
; WHERE RTCDEFVAL IS STORED AND THE PROXY IS INSTALLED IN UPPER RAM
; WHERE WE WILL STORE THE WORKING SHADOW COPY (HB_RTCVAL).
; SEE COMMENTS ABOVE REGARDING THE FUNKY WAY THAT THE RTCDEFVAL IS
; CREATED.
;

	LD	A,(RTCDEFVAL)
	LD	(HB_RTCVAL),A
	EZ80_IO()
	OUT	(RTCIO),A		; SET IT
	DIAG(1)				; REAPPLY CURRENT DIAG LED SETUP
;
;--------------------------------------------------------------------------------------------------
; DYNAMIC RAM SIZER (IN DEVELOPMENT)
;--------------------------------------------------------------------------------------------------
;
#IFDEF SIZERAM
;
; THIS IS WHERE WE PROBE FOR THE ACTUAL NUMBER OF RAM
; BANKS AVAILABLE IN THE SYSTEM.  THE PROBE CODE NEEDS
; TO BE COPIED TO AND RUN FROM THE COMMON RAM BANK.
;

  #IF (MEMMGR == MM_MBC)
	; MBC REQUIRES A BANK SELECT MASK TO BE SETUP IN THE MBC
	; BANK SELECT ROUTINE.  HOWEVER, THE MASK IS DERIVED FROM THE
	; TOTAL SIZE OF THE RAM IN THE SYSTEM (SEE MBC BANK SELECT
	; MASK SETUP BELOW).  SO, WE HAVE A CATCH-22
	; HERE FOR MBC.  THE DYNAMIC RAM SIZING REQUIRES THE THE MASK
	; AND THE MASK SETUP REQUIRES THE TOTAL RAM SIZE.  SO, FOR MBC,
	; WE CAN'T DO DYNAMIC RAM SIZING.
;
	.ECHO	"*** ERROR: DYNAMIC RAM SIZING NOT POSSIBLE FOR MBC!!!\n"
	!!!	; FORCE AN ASSEMBLY ERROR
  #ENDIF
;
	LD	DE,$F000
	LD	HL,RS_IMAGE
	LD	BC,RS_LEN
	LDIR
	CALL	RS_START
	LD	($FFEA),A		; STASH HERE TO PRINT LATER
;
#ENDIF
;
;--------------------------------------------------------------------------------------------------
; MBC BANK SELECT MASK SETUP
;--------------------------------------------------------------------------------------------------
;
; THE MBC RAM BOARD CAN CONTAIN 1 OR 2 RAM CHIPS.  THE COMMON RAM BANK IS
; FIXED BY HARDWARE TO BE THE TOP 32K OF THE *FIRST* RAM CHIP.  WHEN THERE
; ARE 2 RAM CHIPS INSTALLED, THE HARDWARE WILL THUS PLACE THE COMMON RAM IN
; THE MIDDLE OF PHYSICAL RAM.  HBIOS REQUIRES THAT THE COMMON RAM BANK BE
; MAPPED TO THE VERY LAST 32K OF PHYSICAL RAM.  THIS IS REQUIRED SO THAT
; THE RAM DISK BANKS CAN BE SEQUENTIAL.  TO WORK AROUND THIS, WE USE AN
; XOR MASK THAT IS APPLIED DURING BANK SELECT.  THIS MASK WILL FLIP THE
; HIGH ORDER BANK SELECT BIT (WHEN 2 RAM CHIPS ARE USED) SO THAT THE TWO
; RAM CHIPS WIND UP "REVERSED" AND THE FIXED COMMON BANK WINDS UP AT THE
; END OF THE RAM BANKS.  THE MASK IS SETUP HERE BASED ON THE NUMBER OF RAM
; CHIPS AND THEIR SIZE.  NOTE THAT THE NUMBER OF RAM CHIPS IS INFERRED BY
; THE TOTAL RAM SIZE.  A SINGLE CHIP WILL BE EITHER 128K OR 512K.  IF THE
; TOTAL RAM SIZE OF THE SYSTEM IS 256K OR 1M, THEN THERE MUST BE TWO CH
; IPS.  THE RESULTING BANK SELECT MASK IS INSERTED INTO THE MBC BANK
; SELECT ROUTINE.
;
#IF (MEMMGR == MM_MBC)
;
	; ALTHOUGH DYNAMIC SYSTEM RAM SIZING IS NOT POSSIBLE FOR MBC
	; (SEE COMMENTS ABOVE), WE ARE STILL DOING THE MASK SETUP
	; DYNAMICALLY.  THIS IS SIMPLY IN CASE WE EVER FIND A WAY TO
	; DYNAMICALLY SIZE THE RAM IN AN MBC SYSTEM.
	;
	; 128K:  %00000000		; 1 CHIP, FLIP NO BITS
	; 256K:  %00000100		; 2 CHIPS, 8 BANKS, FLIP BIT 2
	; 512K:  %00000000		; 1 CHIP, FLIP NO BITS
	; 1024K: %00010000		; 2 CHIPS, 32 BANKS, FLIP BIT 4
	;
	; IF NUMBER OF RAMBANKS DETECTED FOR MBC IS 4 (128KB) OR
	; 16 (512KB) THEN ZERO THE BANK MASK, OTHERWISE CALCULATE
	; THE BANK MASK AS BANKS/2.
;
	LD	A,(CB_RAMBANKS)
	LD	E,A
	LD	A,%11101011
	AND	E
	JR	Z,MBC_SINGLE
	RRA
MBC_SINGLE:
	LD	(HBX_MBCMSK),A
;
#ENDIF
;
;--------------------------------------------------------------------------------------------------
; HBIOS TRANSITION TO RAM
;--------------------------------------------------------------------------------------------------
;
; COPY OURSELVES TO HBIOS BANK IN RAM
;
	LD	A,(HB_CURBNK)		; GET CURRENT BANK ID
	LD	(HBX_LOC - 2),A		; SAVE THE LOAD BANK
;
	; CHECK TO SEE IF WE ARE ALREADY RUNNING IN THE HBIOS RAM
	; BANK AND SKIP THE COPY IF SO (DON'T COPY OVER OURSELVES).
	; THIS SITUATION OCCURS ON A ROMLESS STARTUP OR WHEN DOING A
	; FULL RESTART OF A SYSTEM USING THE EXISTING HBIOS COPY.
	; NOTE THAT THIS TEST WORKS BECAUSE BID_BIOS == BID_BOOT
	; IN THESE SCENARIOS.
	CP	BID_BIOS		; SAME AS BIOS BANK?
	JR	Z,HB_START1		; IF SO, SKIP
;
	LD	(HB_SRCBNK),A		; CURRENT BANK IS SOURCE
	LD	A,BID_BIOS		; GET BIOS BANK ID
	LD	(HB_DSTBNK),A		; ... AND MAKE IT THE DESTINATION
	LD	HL,0			; START FROM ADDRESS ZERO
	LD	DE,0			; SAME FOR DESTINATION
	LD	BC,$8000		; COPY ENTIRE 32KB BANK
#IF (MEMMGR == MM_Z280)
	; WE CANNOT USE HBX_BNKCPY FOR Z280 BECAUSE HBX_BNKCPY WILL
	; SYSCALL Z280_BNKCPY.  SYSCALL IS NOT SAFE YET BECAUSE THE
	; Z280 IVT ADDRESS HAS NOT BEEN SETUP.
	CALL	Z280_BNKCPY		; HANDLE Z280 SPECIAL
#ELSE
	CALL	HBX_BNKCPY		; ELSE NORMAL BANK COPY
#ENDIF
;
; TRANSITION TO HBIOS IN RAM BANK
;
#IF (MEMMGR == MM_Z280)
	; Z280 NEEDS TO BE HANDLED SPECIAL BECAUSE WE ARE SWITCHING
	; THE SYSTEM MODE BANK, NOT THE NORMAL USER MODE BANK.
	LD	A,BID_BIOS
	LD	B,$10			; FIRST SYSTEM PDR
	CALL	Z280_BNKSEL
	JR	HB_START1
#ELSE
	; JUST DOING A BANK CALL TO THE RAM BANK BANK.  IF THIS IS A
	; ROMLESS BOOT OR AN IN-PLACE HBIOS RESTART, WE ARE ALREADY
	; RUNNING IN BID_BIOS BANK.  HOWEVER, THIS WILL DO NO HARM.
	LD	A,BID_BIOS		; BIOS BANK ID
	LD	IX,HB_START1		; EXECUTION RESUMES HERE
	CALL	HBX_BNKCALL		; CONTINUE IN RAM BANK, DO NOT RETURN
	HALT				; WE SHOULD NOT COME BACK HERE!
#ENDIF
;
; EXECUTION RESUMES HERE AFTER SWITCH TO RAM BANK
;
HB_START1:				; BNKCALL ARRIVES HERE, BUT NOW RUNNING IN RAM BANK
;
	; WE RESET THE STACK HERE BECAUSE WE ARE NOT GOING TO RETURN
	; FROM THE BNKCALL.  REMEMBER THAT WE STORED A COUPLE BYTES
	; RIGHT BELOW HBX_LOC, SO THE STACK IS SET TO START JUST BELOW
	; THAT.
	LD	SP,HBX_LOC - 2		; RESET STACK
;
	; NOTIFY THAT WE MADE THE TRANSITION!
	FPLEDS(DIAG_03)
	DIAG(2)
;
	; RECOVER DATA PASSED PRIOR TO RAM TRANSITION
	; (HBX_LOC - 1) = BATCOND
	POP	HL			; POP 2 BYTES
	LD	A,H			; GET FIRST BYTE PUSHED
	LD	(HB_BATCOND),A		; ... AND SAVE AS BAT COND
;
#IF FALSE
;
; POPULATE THE CRITICAL RAM BANK NUMBERS.
;
; ASSUME THAT CB_RAMBANKS IS THE NUMBER OF 32K RAM BANKS THAT HAS BEEN SET EITHER
; AT ASSEMBLY TIME OR BY PROBING THE ACTUAL AVAILABLE MEMORY (NOT IMPLEMENTED YET).
;
	LD	A,(CB_RAMBANKS)			; CALCULATE TOP RAMBANK
	ADD	A,BID_RAM0			; AS FIRST RAMBANK +
	DEC	A				; #RAMBANKS - 1
;
	LD	HL,CB_BIDCOM
	LD	B,4
CB_IDS:	LD	(HL),A				; POPULATE CB_BIDCOM
	INC	HL				; POPULATE CB_BIDUSR
	DEC	A				; POPULATE CB_BIDBIOS
	DJNZ	CB_IDS				; POPULATE CB_BIDAUX
;
	LD	A,(CB_BIDUSR)
	LD	(HB_SRCBNK),A			; POPULATE HB_SRCBNK
	LD	(HB_DSTBNK),A			; POPULATE HB_DSTBNK
;
	LD	A,BID_RAM0			; POPULATE CB_BIDRAMD0	; START RAMBANK
	LD	(HL),A
	INC	HL
;
	LD	A,(CB_RAMBANKS)			; POPULATE CB_BIDRAMDN	; END RAMBANK
	DEC	A
	SUB	TOT_RAM_RB
	LD	(HL),A
;
#ENDIF
;
;--------------------------------------------------------------------------------------------------
; INTERRUPT MANAGEMENT SETUP
;--------------------------------------------------------------------------------------------------
;
; SETUP INTERRUPT VECTOR TABLE ADDRESS(ES) AND TRANSITION TO
; OPERATING INTERRUPT MODE.  NOTE THAT INTERRUPTS REMAIN
; DISABLED AT THIS POINT.
;
#IF (CPUFAM != CPU_EZ80)
#IF ((INTMODE == 2) | ((INTMODE == 1) & (CPUFAM == CPU_Z180)))
	; SETUP Z80 IVT AND INT MODE 2
	LD	A,HBX_IVT >> 8		; SETUP HI BYTE OF IVT ADDRESS
	LD	I,A			; ... AND PLACE IT IN I REGISTER

  #IF (CPUFAM == CPU_Z180)
	; SETUP Z180 IVT
	XOR	A			; SETUP LO BYTE OF IVT ADDRESS
	OUT0	(Z180_IL),A		; ... AND PLACE IN Z180 IL REGISTER
  #ENDIF

  #IF (INTMODE == 2)
	IM	2			; SWITCH TO INT MODE 2
  #ENDIF
#ENDIF
;
#IF (MEMMGR == MM_Z280)
	; NOW POINT TO RAM COPY OF Z280 INT/TRAP TABLE
	; HL IS TOP 16 BITS OF PHYSICAL ADDRESS OF IVT
	; IVT *MUST* BE ON A 4K BOUNDARY
	LD	C,Z280_VPR
	LD	HL,0 + (((PBANK(BID_BIOS) << 15) + Z280_IVT) >> 8)
	LDCTL	(C),HL
#ENDIF
;
#IF (INTMODE == 3)
;
	; SETUP Z280 INT A FOR VECTORED INTERRUPTS
	LD	HL,%0010000000000000
	LD	C,Z280_ISR
	LDCTL	(C),HL
;
	; TRANSITION TO INTERRUPT MODE 3
	IM	3
;
#ENDIF
#ENDIF
;
;--------------------------------------------------------------------------------------------------
; APPLICATION BOOT INITIALIZATION
;--------------------------------------------------------------------------------------------------
;
; IF APPBOOT, WE NEED TO FIX UP A FEW THINGS IN PAGE ZERO AND
; COPY THE PAYLOAD CONTAINING ROMLDR, MONITOR, AND ZSDOS TO AUX BANK.
;
#IFDEF APPBOOT
;
	;;; SHOULD THIS BE DONE FOR AN HBIOS RESTART IN PLACE???
	; MAKE SURE RST 08 VECTOR IS RIGHT
	LD	A,$C3
	LD	($0008),A
	LD	HL,HB_INVOKE
	LD	($0009),HL
;
	; MAKE SURE IM1 INT VECTOR IS RIGHT
  #IF (INTMODE == 1)
	; CALL	HBX_INT			; HANDLE IM1 INTERRUPTS
	; .DB	$10 << 2		; USE SPECIAL VECTOR #16
	LD	A,$CD			; CALL OPCODE
	LD	($0038),A
	LD	HL,HBX_INT		; ADDRESS
	LD	($0039),HL
	LD	A,$10 << 2		; IM1 VECTOR
	LD	($003B),A
  #ELSE
	; RETI ($ED, $4D) IF NON-INTERRUPT MODE
	LD	HL,$0038
	LD	(HL),$ED
	INC	HL
	LD	(HL),$4D
  #ENDIF
;
	; CHECK TO SEE IF THIS IS AN IN-PLACE RESTART.  IF SO,
	; WE NEED TO SKIP THE COPY OF THE CONCATENATED OS IMAGES BELOW.
	LD	A,(HB_RAMFLAG)		; GET THE FLAG
	OR	A			; TEST IT
	JR	NZ,HB_START2		; IF SET, SKIP
	DEC	A			; SET FLAG
	LD	(HB_RAMFLAG),A		; SAVE IT
;
	; FOR AN APPLICATION BOOT, WE ALSO COPY THE CONCATENATED OS
	; IMAGES TO THE AUX BANK WHERE WE WILL JUMP TO ROMLDR LATER.
	LD	A,(HB_CURBNK)		; GET CURRENT BANK ID
	LD	(HB_SRCBNK),A		; SETUP SOURCE BANK
	LD	A,BID_AUX		; DEST BANK IS AUX BANK
	LD	(HB_DSTBNK),A		; SETUP DESTINATION BANK
	LD	HL,HB_END		; COPY FROM END OF HBIOS
	LD	DE,0			; TO START OF TARGET BANK
	LD	BC,$8000		; COPY ENTIRE 32KB BANK
	CALL	HBX_BNKCPY		; ELSE NORMAL BANK COPY
	JR	HB_START2		; CONTINUE
;
;;; RELOCATE THIS DATA FIELD
HB_RAMFLAG	.DB	$00
;
#ENDIF
;
HB_START2:
;
;--------------------------------------------------------------------------------------------------
; GENERAL HBIOS INITIALIZATION
;--------------------------------------------------------------------------------------------------
;
; CLEAR DISPATCH TABLE ENTRIES
;
	XOR	A			; ZERO
	LD	(CIO_CNT),A		; CIO DEVICES
	LD	(DIO_CNT),A		; DIO DEVICES
	LD	(VDA_CNT),A		; VDA DEVICES
	LD	(SND_CNT),A		; SND DEVICES
	LD	(RTC_DISPACT),A		; RTC DEVICE
	LD	(DSKY_DISPACT),A	; DSKY DEVICE
	LD	HL,RTC_DISPERR		; DEFAULT RTC DISPADR
	LD	(RTC_DISPADR),HL	; SET IT
	LD	HL,DSKY_DISPERR		; DEFAULT DSKY DISPADR
	LD	(DSKY_DISPADR),HL	; SET IT
;
; INITIALIZE SOME HCB ENTRIES
;
	OR	$FF			; $FF TO ACCUM
	LD	(CB_CRTDEV),A		; RESET CRT DEVICE
;
; CLEAR INTERRUPT VECTOR TABLES
;
; THIS IS REALLY ONLY REQUIRED ON A RESTART, BUT IT DOESN'T HURT TO
; DO IT ALL THE TIME.
;
	LD	HL,HB_IVT + 1		; FIRST VECTOR (IM2)
	LD	B,16			; CLEAR 16 VECTORS
	CALL	HB_CLRIVT		; DO IT
	LD	HL,HB_IM1INT + 1	; FIRST VECTOR (IM1)
	LD	B,8			; CLEAR 8 VECTORS
	CALL	HB_CLRIVT		; DO IT
	XOR	A			; ZERO ACCUM
	LD	(HB_IM1CNT),A		; ... TO CLEAR IM1 VECTOR CNT
	LD	HL,HB_IM1INT		; POINTER TO START OF IM1 IVT
	LD	(HB_IM1PTR),HL		; ... TO CLEAR IM1 PTR

	LD	HL,HB_TICK
	LD	(VEC_TICK + 1),HL
	LD	HL,HB_SECOND
	LD	(VEC_SECOND + 1),HL

	JR	HB_CLRIVT_Z		; DONE, JUMP OVER SUBROUTINE
;
HB_CLRIVT:
	LD	(HL),HB_BADINT & $FF
	INC	HL
	LD	(HL),HB_BADINT >> 8
	INC	HL
	INC	HL
	INC	HL
	DJNZ	HB_CLRIVT
	RET
;
HB_CLRIVT_Z:
;
; INITIALIZE HEAP STORAGE
;
	; INITIALIZE POINTERS
	LD	HL,HB_END		; HEAP FOLLOWS HBIOS CODE
	LD	(CB_HEAP),HL		; INIT HEAP BASE ADDRESS
	LD	(CB_HEAPTOP),HL		; INIT HEAP TOP ADDRESS
	; CLEAR HEAP
	LD	BC,BNKTOP - HB_END	; MAX SIZE OF HEAP
	LD	A,$FF			; FILL WITH $FF
	CALL	FILL			; DO IT
;
	FPLEDS(DIAG_04)
;
;--------------------------------------------------------------------------------------------------
; CPU TYPE DISCOVERY
;--------------------------------------------------------------------------------------------------
;
; SOME OF THIS CODE IS DERIVED FROM UNA BY JOHN COFFMAN
;
;	0: Z80
;	1: Z80180 - ORIGINAL Z180 (EQUIVALENT TO HD64180)
;	2: Z8S180 - ORIGINAL S-CLASS, REV. K, AKA SL1960, NO ASCI BRG
;	3: Z8S180 - REVISED S-CLASS, REV. N, W/ ASCI BRG
;	4: Z8280
;       5: eZ80
;
	LD	HL,0		; L = 0 MEANS Z80
;
#IF (CPUFAM == CPU_Z180)
;
	; TEST FOR ORIGINAL Z180 USING MLT
	LD	DE,$0506	; 5 X 6
	MLT	DE		; DE = 30 IF Z180
	LD	A,E		; CHECK IF MULTIPLY HAPPENED
	CP	30
	JR	NZ,HB_CPU1	; IT IS A Z80 IF != 30
	INC	L		; FLAG Z80180 OR BETTER
;
	; TEST FOR OLDER S-CLASS (REV K)
	IN0	A,(Z180_CCR)	; SUPPOSEDLY ONLY ON S-CLASS
	INC	A		; FF -> 0
	JR	Z,HB_CPU1
	INC	L		; FLAG Z8S180 REV K (SL1960) OR BETTER
;
	; TEST FOR NEWER S-CLASS (REV N)
	; ON OLDER S-CLASS, ASCI TIME CONSTANT REG DOES NOT EXIST
	; AND WILL ALWYAS READ BACK AS $FF
	OUT0	(Z180_ASTC1L),D	; D = 0 AT THIS POINT
	IN0	A,(Z180_ASTC1L)	; ASCI TIME CONSTANT REG
	INC	A		; FF -> 0
	JR	Z,HB_CPU1
	INC	L		; FLAG Z8S180 REV N W/ ASCI BRG
;
#ENDIF
;
#IF (CPUFAM == CPU_Z280)
;
	; TEST FOR Z280 PER ZILOG DOC
	LD	A,$40		; INITIALIZE THE OPERAND
	.DB	$CB,$37		; THIS INSTRUCTION WILL SET THE S FLAG
				; ON THE Z80 CPU AND CLEAR THE S FLAG
				; ON THE Z280 MPU.
	JP	M,HB_CPU1	; IF Z80, SKIP AHEAD
	LD	L,4		; WE ARE Z280
;
#ENDIF
;
HB_CPU1:
	LD	A,L
	LD	(HB_CPUTYPE),A
;
;--------------------------------------------------------------------------------------------------
; EARLY DRIVER INITIALIZATION
;--------------------------------------------------------------------------------------------------
;
; SOME DRIVERS NEED TO BE CALLED AS EARLY AS WE CAN ONE AN OPERATING
; ENVIRONMENT IS ESTABLISHED.
;
#IF (CPUFAM == CPU_EZ80)
	; THIS WILL RE-ASSIGN HB_CPUTYPE
	CALL	EZ80_PREINIT
#ENDIF
#IF (SN76489ENABLE)
	; SN76489 CHIP GENERATES UGLY NOISE AFTER HARDWARE RESET.
	; WE CALL THIS DRIVER'S PREINIT ASAP TO SHUT OFF THE NOISE.
	CALL	SN76489_PREINIT
#ENDIF
#IF (DSRTCENABLE)
	; THE DSRTC NEEDS TO BE INITIALIZED IN ORDER TO PERFROM THE
	; CPU SPEED DETECTION BELOW.
	CALL	DSRTC_PREINIT
#ENDIF
;
;--------------------------------------------------------------------------------------------------
; DSKY INITIALIZATION AND ANNOUNCEMENT
;--------------------------------------------------------------------------------------------------
;
;;;#IF (DSKYENABLE)
#IF (ICMENABLE)
	CALL	ICM_PREINIT
#ENDIF
#IF (PKDENABLE)
	CALL	PKD_PREINIT
#ENDIF
;
	;;;; ANNOUNCE OURSELVES ON DSKY
	;;;LD	HL,MSG_HBVER + 5
	;;;LD	A,(DSKY_HEXMAP + RMJ)
	;;;OR	$80
	;;;LD	(HL),A
	;;;INC	HL
	;;;LD	A,(DSKY_HEXMAP + RMN)
	;;;OR	$80
	;;;LD	(HL),A
	;;;INC	HL
	;;;LD	A,(DSKY_HEXMAP + RUP)
	;;;LD	(HL),A
	;;;LD	HL,MSG_HBVER
	;;;LD	B,BF_DSKYSHOWSEG
	;;;CALL	DSKY_DISPATCH
;;;#ENDIF
#IF (LCDENABLE)
	CALL	LCD_PREINIT
#ENDIF
#IF (H8PENABLE)
	CALL	H8P_PREINIT
#ENDIF
#IF (GM7303ENABLE)
	CALL GM7303_PREINIT
#ENDIF
;
	FPLEDS(DIAG_05)
;
; INIT OSCILLATOR SPEED FROM CONFIG
;
#IF (CPUFAM != CPU_EZ80)
	LD	HL,CPUOSC / 1000	; OSC SPD IN KHZ
	LD	(HB_CPUOSC),HL		; INIT HB_CPUOSC DEFAULT
;
	; ATTEMPT DYNAMIC CPU SPEED DERIVATION
	; NOTE THAT FOR PLATFORMS WITH SOFTWARE SELECTABLE CPU SPEED,
	; THIS IS BEING DONE WITH THE CPU SPEED SET TO THE LOWEST
	; POSSIBLE SETTING.  THE FINAL CPU SPEED WILL BE ADJUSTED
	; LATER.
;
	CALL	HB_CPUSPD		; DYNAMIC CPU SPEED DETECTION
	JR	NZ,HB_CPU2		; SKIP AHEAD IF FAILED
;
	; RECORD THE UPDATED CPU OSCILLATOR SPEED
;
#IF ((CPUFAM == CPU_Z180) | (CPUSPDCAP == SPD_HILO) | (PLATFORM=PLT_HEATH))
	; SPEED MEASURED WILL BE HALF OSCILLATOR SPEED
	; SO RECORD DOUBLE THE MEASURED VALUE
	; FOR HEATH, WE ARE ASSUMING THAT THE CPU SPEED DIVISOR WAS
	; PREVIOUSLY SET TO $01 MEANING HALF OF OSCILLATOR SPEED.
	SLA	L
	RL	H
#ENDIF
;
	LD	(HB_CPUOSC),HL		; RECORD MEASURED SPEED
;
HB_CPU2:
#ENDIF
;
;--------------------------------------------------------------------------------------------------
; FINALIZE OPERATING CPU SPEED
;--------------------------------------------------------------------------------------------------
;
; TRANSITION TO FINAL DESIRED CPU SPEED FOR THOSE PLATFORMS
; THAT SUPPORT SOFTWARE SELECTABLE CPU SPEED.  UPDATE CB_CPUKHZ
; IN HCB AS WE DO THIS.
;
	LD	HL,(HB_CPUOSC)
#IF ((CPUSPDCAP==SPD_HILO) & (PLATFORM==PLT_MBC))
  #IF (CPUSPDDEF==SPD_HIGH)
	; SET HIGH SPEED VIA RTC LATCH
	LD	A,(HB_RTCVAL)
	OR	%00001000		; SET HI SPEED BIT
	LD	(HB_RTCVAL),A		; SAVE SHADOW
	OUT	(RTCIO),A		; IMPLEMENT
	; HL IS ALREADY CORRECT FOR FULL SPEED OPERATION
  #ELSE
	; ADJUST HL TO REFLECT HALF SPEED OPERATION
	SRL	H		; ADJUST HL ASSUMING
	RR	L		; HALF SPEED OPERATION
  #ENDIF
#ENDIF
;
#IF (PLATFORM == PLT_HEATH)
	; ADJUST HL TO REFLECT HALF SPEED OPERATION
	SRL	H		; ADJUST HL ASSUMING
	RR	L		; HALF SPEED OPERATION
#ENDIF
;
#IF ((CPUSPDCAP==SPD_HILO) & (PLATFORM==PLT_SBC))
  #IF (CPUSPDDEF==SPD_HIGH)
	; SET HIGH SPEED VIA RTC LATCH
	LD	A,(HB_RTCVAL)
	AND	~%00001000		; CLEAR HI SPEED BIT
	LD	(HB_RTCVAL),A		; SAVE SHADOW
	OUT	(RTCIO),A		; IMPLEMENT
	; HL IS ALREADY CORRECT FOR FULL SPEED OPERATION
  #ELSE
	; ADJUST HL TO REFLECT HALF SPEED OPERATION
	SRL	H		; ADJUST HL ASSUMING
	RR	L		; HALF SPEED OPERATION
  #ENDIF
#ENDIF
;
#IF (CPUFAM == CPU_Z180)
;
	LD	HL,(HB_CPUOSC)	; INIT HL TO CPU OSC FREQ (KHZ)
;
	; ADJUST HL TO REFLECT HALF SPEED OPERATION
	SRL	H		; ADJUST HL ASSUMING
	RR	L		; HALF SPEED OPERATION
;
  #IF (Z180_CLKDIV >= 1)
	LD	A,(HB_CPUTYPE)	; GET CPU TYPE
	CP	2		; Z8S180 REV K OR BETTER?
	JR	C,HB_CPU3	; IF NOT, NOT POSSIBLE!
	; SET CLOCK DIVIDE TO 1 RESULTING IN FULL XTAL SPEED
	LD	A,$80
	OUT0	(Z180_CCR),A
	; ADJUST HL TO REFLECT FULL SPEED OPERATION
	SLA	L
	RL	H
  #ENDIF
;
  #IF (Z180_CLKDIV >= 2)
	LD	A,(HB_CPUTYPE)	; GET CPU TYPE
	CP	3		; Z8S180 REV N OR BETTER?
	JR	C,HB_CPU3	; IF NOT, NOT POSSIBLE!
	; SET CPU MULTIPLIER TO 1 RESULTING IN XTAL * 2 SPEED
	; ALSO SET CCR AGAIN BECAUSE OF REPORTS THAT CCR
	; *MUST* BE SET AFTER CMR.
	LD	A,$80
	OUT0	(Z180_CMR),A	; CPU MULTIPLIER
	OUT0	(Z180_CCR),A	; CLOCK DIVIDE
	; ADJUST HL TO REFLECT DOUBLE SPEED OPERATION
	SLA	L
	RL	H
  #ENDIF
;
HB_CPU3:
#ENDIF
;
#IF (CPUFAM == CPU_Z280)
;
	; Z280 ALWAYS HALVES THE INPUT OSCILLATOR TO DERIVE
	; ACTUAL CPU SPEED.
	; ADJUST HL TO REFLECT HALF SPEED OPERATION
	SRL	H		; ADJUST HL ASSUMING
	RR	L		; HALF SPEED OPERATION
#ENDIF
;
; HL SHOULD NOW HAVE FINAL CPU RUNNING SPEED IN KHZ.
; UPDATE CB_CPUMHZ/CB_CPUKHZ WITH THIS VALUE.
;
	LD	(CB_CPUKHZ),HL		; UPDATE CPUKHZ
	LD	DE,1000			; SET UP TO DIV BY 1000 FOR MHZ
	CALL	DIV16			; BC=CPU MHZ, HL=REMAINDER
	LD	DE,500			; SET UP TO ROUND UP
	XOR	A			; IF WITHIN 500 KHZ
	SBC	HL,DE			; REMAINDER - 500
	CCF				; COMPLEMENT CF
	ADC	A,C			; C -> A; ADD CF FOR ROUNDING
	LD	(CB_CPUMHZ),A		; SAVE IT
;
;--------------------------------------------------------------------------------------------------
; FINALIZE OPERATING WAIT STATES
;--------------------------------------------------------------------------------------------------
;
; SET OPERATING WAIT STATE CONFIGURATION ON SYSTEMS THAT SUPPORT IT
;
#IF (CPUFAM == CPU_Z180)
;
	; SET FINAL DESIRED WAIT STATES PER CONFIG
	LD	A,0 + (Z180_MEMWAIT << 6) | (Z180_IOWAIT << 4)
	OUT0	(Z180_DCNTL),A
;
#ENDIF
;
#IF (CPUFAM == CPU_Z280)
;
	; SET FINAL DESIRED WAIT STATES PER CONFIG
	; BUS TIMING AND CONFIGURATION REGISTER
	LD	C,Z280_BTCR		; BUS TIMING AND CONTROL REG
	LDCTL	HL,(C)
	LD	A,L			; PUT IN A
	AND	%00111100		; CLEAR DC,HM, AND IO FIELDS
	OR	Z280_INTWAIT << 6	; SET INT ACK WAIT STATE BITS (DC)
	OR	Z280_MEMHIWAIT << 2	; SET HIGH 8MB WAIT STATE BITS (HM)
	OR	Z280_IOWAIT		; SET I/O WAIT STATE BITS
	LD	L,A			; BACK TO L
	LDCTL	(C),HL			; DO IT
;
	; BUS TIMING AND INITIALIZATION REGISTER
	LD	C,Z280_BTIR		; BUS TIMING AND INIT REG
	LDCTL	HL,(C)
	LD	A,L			; PUT IN A
	AND	%11110011		; CLEAR LM FIELD
	OR	Z280_MEMLOWAIT << 2	; SET LOW 8MB WAIT STATE BITS
	LD	L,A			; BACK TO L
	LDCTL	(C),HL			; DO IT
;
#ENDIF
;
;--------------------------------------------------------------------------------------------------
; SK Z80-512K CLOCK INITIALIZATION
;--------------------------------------------------------------------------------------------------
;
#IF (SKZENABLE)
;
;;; LOCATION OF THIS CODE???
;
	; SET THE SK Z80-512K UART CLK2 DIVIDER AS
	; CONFIGURED.  NOTE THAT THIS IMPLICITLY
	; CLEARS THE WATCHDOG BIT.  THE WATCHDOG
	; WILL BE ENABLED LATER IF CONFIGURED.
	LD	A,SKZDIV	; GET DIVIDER CODE
	OUT	($6D),A		; IMPLEMENT IT
;
#ENDIF
;
;--------------------------------------------------------------------------------------------------
; INITIALIZE SPEED-COMPENSATED DELAY FUNCTIONS
;--------------------------------------------------------------------------------------------------
;
;;; LOCATION OF THIS CODE???
;
#IF (CPUFAM != CPU_EZ80)
	LD	A,(CB_CPUMHZ)		; CPU SPEED TO ACCUM AND INIT
	CALL	DELAY_INIT		; .. SPEED COMPENSATED DELAY
#ENDIF
;
;--------------------------------------------------------------------------------------------------
; SYSTEM TIMER INITIALIZATION
;--------------------------------------------------------------------------------------------------
;
#IF (PLATFORM == PLT_SBC)
;
  #IF (HTIMENABLE)			; SIMH TIMER
;
    #IF (INTMODE == 1)
	LD	HL,HB_TIMINT
	CALL	HB_ADDIM1		; ADD TO IM1 CALL LIST
    #ENDIF
;
  #ENDIF
;
#ENDIF
;
#IF (KIOENABLE)
	CALL	KIO_PREINIT
#ENDIF
;
#IF (CTCENABLE)
	CALL	CTC_PREINIT
#ENDIF
;
#IF (PLATFORM == PLT_NABU)
	CALL	NABU_PREINIT
#ENDIF
;
#IF (CPUFAM == CPU_Z180)
;
  #IF (INTMODE > 0)
;
	; FOR NOW, JUST ENABLE THE INT0 PIN WHICH IS GENERALLY
	; EQUIVALENT TO Z80 INTERRUPTS.
	LD	A,$01			; INT0 ENABLED, INT1-2 DISABLED
	OUT0	(Z180_ITC),A		; WRITE TO INT/TRAP CONTROL REGISTER
;
    #IF (Z180_TIMER)
;
	; SETUP Z180 TIMER0 INTERRUPT VECTOR IN IVT
	LD	HL,HB_TIMINT
	LD	(IVT(INT_TIM0)),HL	; Z180 TIMER 0

	; SETUP PERIODIC TIMER INTERRUPT ON TIMER 0
	; *** THIS ASSUMES A TICKFREQ OF 50HZ!!! ***
;
#IF (TICKFREQ != 50)
	.ECHO "TICKFREQ *MUST* BE 50 FOR Z180 TIMER\n"
	!!!
#ENDIF
;
	; Z180 PRESCALES THE COUNTER BY 20 SO,
	; RLDR = CPU CLK / 20 / TICKFREQ
	; IF WE ASSUME TICKFREQ = 50, WE CAN SIMPLIFY TO
	; RLDR = CPU CLK / 1000
	; IF WE DIVIDE BOTH SIDES BY 1000, WE CAN USE
	; CPUKHZ VALUE AND SIMPLIFY TO
	; RLDR = CPUKHZ
	XOR	A			; ALL BITS ZERO
	OUT0	(Z180_TCR),A		; ... INHIBITS TIMER OPERATION
	LD	HL,(CB_CPUKHZ)		; 50HZ = 18432000 / 20 / 50 / X, SO X = CPU KHZ
	OUT0	(Z180_TMDR0L),L		; INITIALIZE TIMER 0 DATA REGISTER
	OUT0	(Z180_TMDR0H),H
	DEC	HL			; RELOAD OCCURS *AFTER* ZERO
	OUT0	(Z180_RLDR0L),L		; INITIALIZE TIMER 0 RELOAD REGISTER
	OUT0	(Z180_RLDR0H),H
	LD	A,%00010001		; ENABLE TIMER0 INT AND DOWN COUNTING
	OUT0	(Z180_TCR),A
;
    #ENDIF
;
  #ENDIF
;
#ENDIF
;
#IF (CPUFAM == CPU_Z280)
;
  #IF (MEMMGR == MM_Z280)
;
    #IF (Z280_TIMER)
;
Z280_TC	.EQU	CPUOSC / 4 / 50 / 2	; TIME CONSTANT
;
	LD	HL,Z280_TIMINT
	LD	(Z280_IVT+$16),HL	; Z280 T/C VECTOR
;
	; SELECT I/O PAGE $FE
	LD	C,Z280_IOPR		; I/O PAGE REGISTER
	LDCTL	HL,(C)			; GET CURRENT I/O PAGE
	PUSH	HL			; SAVE IT
	LD	L,$FE			; I/O PAGE $FE
	LDCTL	(C),HL
;
	LD	A,%10100000		; CONFIG: C, RE, IE
	OUT	(Z280_CT0_CFG),A	; SET C/T 0
	LD	HL,CPUOSC / 50 / 16	; TIME CONSTANT & COUNTER
	LD	C,Z280_CT0_TC		; SET C/T 0
	OUTW	(C),HL
	LD	C,Z280_CT0_CT		; SET C/T 0
	OUTW	(C),HL
	LD	A,%11100000		; CMD: EN, GT
	OUT	(Z280_CT0_CMDST),A	; SET C/T 0
;
	; RESTORE I/O PAGE
	LD	C,Z280_IOPR		; I/O PAGE REGISTER
	POP	HL			; RESTORE I/O PAGE
	LDCTL	(C),HL
;
    #ENDIF
;
  #ENDIF
;
#ENDIF
;
	FPLEDS(DIAG_06)
;
;--------------------------------------------------------------------------------------------------
; PRE-CONSOLE INITIALIZATION
;--------------------------------------------------------------------------------------------------
;
#IF (WBWDEBUG == USEMIO)		; BUFFER OUTPUT UNTIL
	CALL	MIO_INIT		; WE GET TO BOOT MESSAGE
#ENDIF
;
#IF FALSE
;
; TEST DEBUG ***************************************************************************************
;
	CALL	NEWLINE
	CALL	REGDMP
;
; TEST DEBUG ***************************************************************************************
;
#ENDIF
;
; PLATFORM SPECIFIC CODE FOR DETECTING RECOVERY MODE SWITCH
;
#IF (BT_REC_TYPE != BT_REC_NONE)
  #IF (BT_REC_TYPE == BT_REC_FORCE)
	LD	A,1			; SET FOR RECOVERY MODE
	LD	(HB_BOOT_REC),A		; SAVE FOR LATER
  #ENDIF
  #IF ((PLATFORM == PLT_SBC) | (PLATFORM == PLT_MBC))
    #IF (BT_REC_TYPE == BT_REC_SBC01)
	LD	A,%00100000		; DISABLE RTC AND
	OUT	(RTCIO),A		; DRQ DRIVER READ
	IN	A,(RTCIO)		; BIT 0 (DRQ).
	CPL				; PULLED HIGH
	AND	1			; IS RECOVERY MODE
	LD	(HB_BOOT_REC),A		; SAVE FOR LATER
    #ENDIF
    #IF (BT_REC_TYPE == BT_REC_SBC1B)
	IN	A,(RTCIO)		; RTC PORT, BIT 6 HAS THE
	BIT	6,A			; STATE OF CONFIG JUMPER
	LD	A,1			; JUMPER INSTALLED
	JR	Z,SAVE_REC_M		; IS RECOVERY MODE
	LD	A,0
SAVE_REC_M:
	LD	(HB_BOOT_REC),A		; SAVE FOR LATER
    #ENDIF
    #IF (BT_REC_TYPE == BT_REC_SBCRI)
	IN	A,($68 + 6)		; UART_MSR MODEM
	BIT	6,A			; STATUS REGISTER
	LD	A,0			; BIT 6
	JR	Z,SAVE_REC_M		; IS RECOVERY MODE
	LD	A,1
SAVE_REC_M:
	LD	(HB_BOOT_REC),A		; SAVE FOR LATER
    #ENDIF
  #ENDIF
#ENDIF
;
	LD	DE,HB_PCINITTBL		; POINT TO PRECONSOLE INIT TABLE
	LD	B,HB_PCINITTBLLEN	; NUMBER OF ENTRIES
;
#IF (BT_REC_TYPE != BT_REC_NONE)
	LD	A,(HB_BOOT_REC)		; IF WE ARE IN RECOVERY MODE
	OR	A			; POINT TO THE RECOVER MODE
	JR	Z,NOT_REC_M0		; INITIALIZATION TABLE
	LD	B,HB_PCINITRLEN
	LD	DE,HB_PCINIT_REC
NOT_REC_M0:
;
#ENDIF
;
	; CYCLE THROUGH THE INITIALIZATION TABLE CALLING THE PRE-INIT
	; ENTRY POINT OF ALL DRIVERS.
	CALL	CALLLIST		; PROCESS THE PRE-INIT CALL TABLE
;
#IF FALSE
;
; TEST DEBUG ***************************************************************************************
;
	CALL	NEWLINE
	CALL	REGDMP
;
; TEST DEBUG ***************************************************************************************
;
#ENDIF
;
	FPLEDS(DIAG_07)
	DIAG(3)
;
;--------------------------------------------------------------------------------------------------
; BOOT DELAY
;--------------------------------------------------------------------------------------------------
;
; IF CONFIGURED, AN ARBITRARY BOOT DELAY IS IMPLEMENTED HERE.  THIS IS
; TYPICALLY USED TO DELAY ACCESSING DEVICES THAT WILL NOT BE READY.
;
#IF (BOOT_DELAY > 100)
	.ECHO	"*** ERROR: INVALID BOOT_DELAY (BOOT_DELAY > 100)!!!\n"
	!!!	; FORCE AN ASSEMBLY ERROR
#ENDIF
;
#IF (BOOT_DELAY > 0)
	LD	B,BOOT_DELAY * 2	; SCALE TO 1/2 SEC
HB_BOOTDLY:
	CALL	LDELAY			; 1/2 SECOND DELAY
	DJNZ	HB_BOOTDLY		; LOOP TILL DONE
#ENDIF
;
;--------------------------------------------------------------------------------------------------
; ACTIVATE BOOT CONSOLE
;--------------------------------------------------------------------------------------------------
;
; PRIOR TO THIS POINT, CONSOLE I/O WAS NOT AVAILABLE UNLESS DIRECTED TO DEBUG OUTPUT I.E. XIO
; NOW THAT HBIOS IS READY, SET THE CONSOLE UNIT TO ACTIVATE CONSOLE I/O
; VIA HBIOS.
;
	XOR	A			; FAILSAFE VALUE FOR BOOT CONSOLE DEVICE
	LD	(CB_CONDEV),A		; SAVE IT
;
	LD	A,(CIO_CNT)		; GET COUNT OF CHAR DEVICES
	CP	BOOTCON	+ 1		; COUNT - (DEVICE + 1)
	JR	C,HB_CONRDY		; IF TOO HIGH, JUST USE FAILSAFE
	LD	A,BOOTCON		; GET REQUESTED CONSOLE DEV
	LD	(CB_CONDEV),A		; SAVE IT
;
HB_CONRDY:
;
; SUPPRESS HARDWARE FLOW CONTROL TEMPORARILY, IF NEEDED.  THIS IS
; GENERALLY NOT USED ANYMORE BECAUSE THE UART DRIVER NOW CHECKS FOR
; A VALID CTS SIGNAL AND ADJUSTS AS NEEDED.
;
#IF (SUPCTS)
;
; MOST SERIAL PORTS ARE CONFIGURED WITH HARDWARE FLOW CONTROL ENABLED.
; IF THERE IS A PROBLEM WITH THE CTS SIGNAL, THEN OUTPUT TO THE CONSOLE
; WILL BE STALLED WHICH CAN LEAD A USER TO THINK THE SYSTEM IS TOTALLY
; DEAD WHEN, IN FACT, IT IS JUST WAITING FOR CTS TO BE ASSERTED.  ALSO,
; IF THE USER IS BOOTING TO A CRT DEVICE AND DISCONNECTS THE CONSOLE
; SERIAL PORT, THE SYSTEM WILL WAIT FOR RTS AND NEVER BOOT.  SO, HERE
; WE SAVE THE ACTIVE CONSOLE CONFIGURATION, THEN TURN OFF HARDWARE
; FLOW CONTROL.  THE ORIGINAL CONFIGURATION WILL BE RESTORED BELOW
; PRIOR TO LAUNCING THE ROM LOADER.
;
	; RETRIEVE THE CONFIG FROM THE CONSOLE PORT
	LD	B,BF_CIOQUERY		; HBIOS QUERY CIO CONFIG
	LD	A,(CB_CONDEV)		; GET CONSOLE DEVICE
	LD	(HB_BOOTCONSAV),A	; SAVE IT FOR LATER
	LD	C,A			; BOOT CONSOLE TO C
	CALL	HB_DISPATCH		; INTERNAL HBIOS CALL
	LD	(HB_CONCFGSAV),DE	; SAVE CONFIG
	RES	5,D			; CLEAR RTS BIT
	LD	B,BF_CIOINIT		; HBIOS CIO INIT
	LD	A,(CB_CONDEV)		; GET CONSOLE DEVICE
	LD	C,A			; BOOT CONSOLE TO C
	CALL	HB_DISPATCH		; INTERNAL HBIOS CALL
;
#ENDIF
;
#IF (WBWDEBUG == USEMIO)		; OUTPUT ANY CACHED DEBUG TEXT
	LD	HL,MIOOUTPTR
	LD	E,(HL)
	INC	HL
	LD	D,(HL)
	INC	HL
NXTMIO:	LD	A,(HL)
	CALL	COUT
	INC	HL
	LD	A,L
	CP	E
	JR	NZ,NXTMIO
	LD	A,H
	CP	D
	JR	NZ,NXTMIO
;	CALL	WRITESTR		; WRITESTR WILL WORK WILL ONLY PRINT UP TO FIRST $
#ENDIF
;
#IF FALSE
;
; TEST DEBUG ***************************************************************************************
;
	CALL	NEWLINE2
	PRTS("DEBUG+IM1INT$")
	LD	DE,HB_IM1INT
	CALL	DUMP_BUFFER
;
; TEST DEBUG ***************************************************************************************
;
#ENDIF
;
;--------------------------------------------------------------------------------------------------
; ANNOUNCE HBIOS
;--------------------------------------------------------------------------------------------------
;
; DISPLAY A BANNER ON THE BOOT CONSOLE NOW.  NOTE THAT WE INTENTIONALLY
; LEAVE INTERRUPTS DISABLED UNTIL A BIT LATER.  SINCE INTERRUPTS CAN
; DESTABILIZE A SYSTEM, IT IS DIAGNOSTICALLY USEFUL TO GET SOMETHING
; DISPLAYED BEFORE INTRODUCING INTERRUPTS.  IF THE SYSTEM CRASHES
; AFTER DISPLAYING THE BANNER, INTERRUPT INTEGRITY SHOULD BE SUSPECTED.
;
	PRTX(STR_BANNER)
;
; DISPLAY HBIOS MUTEX ENABLED MESSAGE
;
#IF (HBIOS_MUTEX == TRUE)
	CALL	NEWLINE
	CALL	PRTSTRD
	.TEXT	"HBIOS MUTEX ENABLED$"
#ENDIF
;
; DISPLAY RECOVERY MODE MESSAGE
;
#IF (BT_REC_TYPE != BT_REC_NONE)
	LD	A,(HB_BOOT_REC)		; IF WE ARE IN RECOVERY MODE
	OR	A			; DISPLAY RECOVERY MODE MESSAGE
	JR	Z,NOT_REC_M2
	CALL	NEWLINE
	CALL	PRTSTRD
	.TEXT	"RECOVERY MODE$"
#ENDIF
NOT_REC_M2:
;
	FPLEDS(DIAG_08)
;
#IF FALSE
	LD	DE,$0123
	LD	HL,$4567
	LD	B,BF_DSKYSHOWHEX
	CALL	DSKY_DISPATCH
	CALL	LDELAY
	CALL	LDELAY
	LD	DE,$89AB
	LD	HL,$CDEF
	LD	B,BF_DSKYSHOWHEX
	CALL	DSKY_DISPATCH
	CALL	LDELAY
	CALL	LDELAY
#ENDIF
;
;--------------------------------------------------------------------------------------------------
; IO PORT SCAN
;--------------------------------------------------------------------------------------------------
;
#IF FALSE
;
PSCN:
	LD	C,0			; IO PORT NUMBER
	LD	B,0			; LOOP COUNTER
	CALL	NEWLINE
PSCN1:
	CALL	NEWLINE
	LD	A,C
	CALL	PRTHEXBYTE
	CALL	PC_COLON
	CALL	PC_SPACE
	CALL	DELAY
	LD	A,C
	LD	(PSCNX),A
PSCNX	.EQU	$ + 1
	IN	A,(0)
	CALL	PRTHEXBYTE
	CALL	PC_COMMA
	PUSH	BC
	LD	B,0
	IN	A,(C)
	POP	BC
	CALL	PRTHEXBYTE
	INC	C
	DJNZ	PSCN1
;
#ENDIF
;
;--------------------------------------------------------------------------------------------------
; CPU SPEED DETECTION ALIGNMENT TEST
;--------------------------------------------------------------------------------------------------
;
#IF FALSE
;
; IF ENABLED, THE CPU SPEED TEST WILL BE REPEATED INDEFINITELY.  THIS
; IS USED TO ADJUST THE SPEED DETECTION LOOP.
;
HB_SPDTST:
	CALL	HB_CPUSPD		; CPU SPEED DETECTION
	CALL	NEWLINE
	LD	HL,(CB_CPUKHZ)
	CALL	PRTD3M			; PRINT AS DECIMAL WITH 3 DIGIT MANTISSA
	JR	HB_SPDTST
;
#ENDIF
;
;--------------------------------------------------------------------------------------------------
; ENABLE INTERRUPTS
;--------------------------------------------------------------------------------------------------
;
	HB_EI				; INTERRUPTS SHOULD BE OK NOW
;
;--------------------------------------------------------------------------------------------------
; DISPLAY PLATFORM INFORMATION
;--------------------------------------------------------------------------------------------------
;
	CALL	NEWLINE2
	PRTX(STR_PLATFORM)
;
	LD	A,(HB_CPUTYPE)		; GET CPU TYPE
	LD	DE,HB_CPU_STR		; DISPLAY IT
	CALL	PRTIDXDEA
;
	PRTS(" @ $")
	LD	HL,(CB_CPUKHZ)
	CALL	PRTD3M			; PRINT AS DECIMAL WITH 3 DIGIT MANTISSA
	PRTS("MHz$")
;
#IF (CPUFAM == CPU_Z180)
	PRTS(" IO=0x$")
	LD	A,Z180_BASE
	CALL	PRTHEXBYTE
#ENDIF
;
#IF (CPUFAM == CPU_Z280)
	CALL	PRTSTRD
	.TEXT	", BUS @ $"
	LD	C,Z280_BTIR		; BUS TIMING AND CTL REG
	LDCTL	HL,(C)
	LD	A,L			; MOVE TO A
	AND	%00000011		; ISOLATE CS FIELD
	LD	HL,(CB_CPUKHZ)		; GET CPU SPEED
	CP	%00000001		; BUS @ 1/1
	JR	Z,HB_Z280BUS		; GOT IT, SHOW IT
	SRL	H			; DIVIDE
	RR	L			; ... BY 2
	CP	%00000000		; BUS @ 1/2
	JR	Z,HB_Z280BUS		; GOT IT, SHOW IT
	SRL	H                       ; DIVIDE
	RR	L                       ; ... BY 2
	CP	%00000010		; BUS @ 1/4
	JR	Z,HB_Z280BUS		; GOT IT, SHOW IT
	PRTS("???$")			; INVALID VALUE
	JR	HB_Z280BUS1		; CONTINUE
HB_Z280BUS:
	CALL	PRTD3M			; PRINT AS DECIMAL WITH 3 DIGIT MANTISSA
HB_Z280BUS1:
	PRTS("MHz$")			; SUFFIX
#ENDIF

#IF (CPUFAM == CPU_EZ80)
	CALL	EZ80_RPT_FIRMWARE
#ENDIF

;
;--------------------------------------------------------------------------------------------------
; DISPLAY CPU CONFIGURATION
;--------------------------------------------------------------------------------------------------
;
	CALL	NEWLINE
;
; DISPLAY MEMORY TIMINGS
;
#IF (CPUFAM == CPU_EZ80)
	CALL	EZ80_RPT_TIMINGS

#ELSE
#IF (CPUFAM == CPU_Z280)
	LD	A,Z280_MEMLOWAIT
	CALL	PRTDECB
	LD	A,'/'
	CALL	COUT
	LD	A,Z280_MEMHIWAIT
	CALL	PRTDECB
	CALL	PRTSTRD
	.TEXT	" MEM W/S, $"
#ELSE
	XOR	A
  #IF (CPUFAM == CPU_Z180)
	LD	A,Z180_MEMWAIT
  #ENDIF
	CALL	PRTDECB
	CALL	PRTSTRD
	.TEXT	" MEM W/S, $"
#ENDIF
#ENDIF ; CPUFAM = CPU_EZ80
;
; DISPLAY I/O TIMINGS
;
#IF (CPUFAM == CPU_EZ80)
	; ALREADY REPORTED BY DRIVER
#ELSE
	LD	A,1
#IF (CPUFAM == CPU_Z180)
	LD	A,Z180_IOWAIT + 1
#ENDIF
#IF (CPUFAM == CPU_Z280)
	LD	A,Z280_IOWAIT + 1
#ENDIF
	CALL	PRTDECB
	CALL	PRTSTRD
	.TEXT	" I/O W/S$"
#IF (CPUFAM == CPU_Z280)
	CALL	PRTSTRD
	.TEXT	", $"
	LD	A,Z280_INTWAIT
	CALL	PRTDECB
	CALL	PRTSTRD
	.TEXT	" INT W/S$"
#ENDIF
#ENDIF //CPUFAM = CPU_EZ80
;
; DISPLAY INTERRUPT MODE
;
#IF (INTMODE > 0)
	CALL	PRTSTRD
	.TEXT	", INT MODE $"
	LD	A,INTMODE
	CALL	PRTDECB
#ENDIF
;
	CALL	PRTSTRD
	.TEXT	", $"
	CALL	PRTSTRD
#IF (MEMMGR == MM_NONE)
	.TEXT	"NONE$"
#ENDIF
#IF (MEMMGR == MM_SBC)
	.TEXT	"SBC$"
#ENDIF
#IF (MEMMGR == MM_Z2)
	.TEXT	"Z2$"
#ENDIF
#IF (MEMMGR == MM_N8)
	.TEXT	"N8$"
#ENDIF
#IF (MEMMGR == MM_Z180)
	.TEXT	"Z180$"
#ENDIF
#IF (MEMMGR == MM_Z280)
	.TEXT	"Z280$"
#ENDIF
#IF (MEMMGR == MM_ZRC)
	.TEXT	"ZRC$"
#ENDIF
#IF (MEMMGR == MM_MBC)
	.TEXT	"MBC$"
#ENDIF
#IF (MEMMGR == MM_RPH)
	.TEXT	"RPH$"
#ENDIF
	CALL	PRTSTRD
	.TEXT	" MMU$"
;
;--------------------------------------------------------------------------------------------------
; DISPLAY MEMORY CONFIGURATION
;--------------------------------------------------------------------------------------------------
;
	CALL	NEWLINE
	LD	HL,ROMSIZE
	CALL	PRTDEC
	CALL	PRTSTRD
	.TEXT	"KB ROM, $"
;
	LD	HL,(CB_RAMBANKS)	; GET NUMBER OF BANKS IN L
	LD	H,0			; CALCULATE RAM SIZE
	ADD	HL,HL
	ADD	HL,HL			; X4
	ADD	HL,HL			; X8
	ADD	HL,HL			; X16
	ADD	HL,HL			; X32
;
	CALL	PRTDEC
	CALL	PRTSTRD
	.TEXT	"KB RAM$"
;
	CALL	PRTSTRD
	.TEXT	", HEAP=0x$"
	LD	HL,BNKTOP - HB_END
	CALL	PRTHEXWORDHL
;
#IFDEF SIZERAM
;
	CALL	PRTSTRD
	.TEXT	", RAMBANKS=0x$"
	LD	A,($FFEA)
	CALL	PRTHEXBYTE
#ENDIF
;
#IFDEF TESTING
;
	CALL	PRTSTRD
	.TEXT	", RTCDEF=0x$"
	LD	A,(RTCDEFVAL)
	CALL	PRTHEXBYTE
;
#ENDIF
;
#IF 0
;
; DIAGNOSTIC DISPLAY OF BANK IDS IN HCB
;
	CALL	PRTSTRD
	.TEXT	", BANK IDS:$"
	LD	DE,CB_BIDCOM
	LD	A,8
	CALL	PRTHEXBUF
;
#ENDIF
;
#IF (CPUFAM == CPU_Z280)
	CALL	NEWLINE
	PRTS("Z280: $")
	PRTS("MSR=$")
	LD	C,Z280_MSR		; MASTER STATUS REGISTER
	LDCTL	HL,(C)
	CALL	PRTHEXWORDHL
	CALL	PC_SPACE
	PRTS("ISR=$")
	LD	C,Z280_ISR		; INTERRUPT STATUS REGISTER
	LDCTL	HL,(C)
	CALL	PRTHEXWORDHL
	CALL	PC_SPACE
	PRTS("BTCR=$")
	LD	C,Z280_BTCR		; BUS TIMING AND CONTROL REGISTER
	LDCTL	HL,(C)
	LD	A,L
	CALL	PRTHEXBYTE
	CALL	PC_SPACE
	PRTS("BTIR=$")
	LD	C,Z280_BTIR		; BUS TIMING AND CONTROL REGISTER
	LDCTL	HL,(C)
	LD	A,L
	CALL	PRTHEXBYTE
	CALL	PC_SPACE
	PRTS("CCR=$")
	LD	C,Z280_CCR		; CACHE CONTROL REGISTER
	LDCTL	HL,(C)
	LD	A,L
	CALL	PRTHEXBYTE
	CALL	PC_SPACE
	PRTS("TCR=$")
	LD	C,Z280_TCR		; CACHE CONTROL REGISTER
	LDCTL	HL,(C)
	LD	A,L
	CALL	PRTHEXBYTE
#ENDIF
;
;--------------------------------------------------------------------------------------------------
; ROM CHECKSUM VERIFICATION
;--------------------------------------------------------------------------------------------------
;
#IFDEF ROMBOOT
  #IF (ROMSIZE > 0)
;
; ROM CHECKSUM VERIFICATION
; EACH OF THE FIRST 4 ROM BANKS HAS A CHECKSUM INJECTED SUCH THAT
; A COMPUTED CHECKSUM ACROSS THE ENTIRE BANK SHOULD ALWAYS BE ZERO
;
HB_ROMCK:
	CALL	NEWLINE
	PRTS("ROM VERIFY:$")
;
	; COPY CHECKSUM ROUTINE TO UPPER RAM
	LD	HL,HB_CKBNK
	LD	DE,$F000
	LD	BC,HB_CKBNKSIZ
	LDIR
;
	; TEST FIRST 4 BANKS OF ROM
	LD	B,4			; 4 BANKS
	LD	C,0			; STARTING AT BANK 0
HB_ROMCK1:
	PUSH	BC			; SAVE LOOP CONTROL
	CALL	$F000			; TEST THE BANK
	CALL	PC_SPACE		; FORMATTING
	CALL	PRTHEXBYTE		; PRINT RESULT
	POP	BC			; RESTORE LOOP CONTROL
	OR	A			; SET FLAGS
	JR	NZ,HB_ROMCK2		; HANDLE FAILURE
	INC	C			; NEXT BANK
	DJNZ	HB_ROMCK1		; LOOP FOR BANKS
	PRTS(" PASS$")			; DISPLAY SUCCESS
	JR	HB_ROMCKZ		; CONTINUE BOOT
HB_ROMCK2:
	PRTS(" FAIL$")			; DISPLAY ERROR
	JR	HB_ROMCKZ		; CONTINUE BOOT
;
; VERIFY ROM CHECKSUM BANK SPECIFIED IN REG C
; THIS MUST BE COPIED TO UPPER RAM TO RUN
; INTERRUPTS ARE DISABLED SINCE PAGE ZERO VECTOR WILL BE
; SWAPPED OUT.  ASSUMES THAT INTERRUPTS ARE ENABLED AT ENTRY.
;
HB_CKBNK:
	HB_DI				; SUPPRESS INTERRUPTS
	LD	A,(HB_CURBNK)		; GET CURRENT BANK
	LD	E,A			; SAVE IN E
	LD	A,C			; BANK TO TEST
	CALL	HBX_BNKSEL		; SELECT BANK IT
	LD	HL,$7FFF		; START AT BANK END
	LD	BC,1			; DECREMENT VALUE
	XOR	A			; ZERO ACCUM
HB_CKBNK1:
    #IF (MEMMGR == MM_Z280)
	LD	D,A			; WORKING VALUE TO D
	LDUD	A,(HL)			; GRAB NEXT BYTE FROM USER SPACE
	ADD	A,D			; ADD NEXT BYTE
    #ELSE
	ADD	A,(HL)			; ADD NEXT BYTE
    #ENDIF
	OR	A			; CLEAR CARRY
	SBC	HL,BC			; DECREMENT
	JR	NC,HB_CKBNK1		; LOOP TILL DONE
	PUSH	AF			; SAVE RESULT
	LD	A,E			; BANK TO RESTORE
	CALL	HBX_BNKSEL		; RESTORE ORIG BANK
	POP	AF			; RECOVER RESULT
	HB_EI				; ALLOW INTERRUPTS AGAIN
	RET				; AND DONE
;
HB_CKBNKSIZ	.EQU	$-HB_CKBNK	; SIZE OF ROUTINE
;
HB_ROMCKZ:
;
  #ENDIF
;
#ENDIF
;
;--------------------------------------------------------------------------------------------------
; LOW RAM BATTERY MESSAGE
;--------------------------------------------------------------------------------------------------
;
#IF (BATCOND)
	LD	A,(HB_BATCOND)
	OR	A
	LD	DE,STR_LOWBAT
	CALL	Z,WRITESTR
#ENDIF
;
;--------------------------------------------------------------------------------------------------
; FINAL DEVICE INITIALIZATION
;--------------------------------------------------------------------------------------------------
;
	CALL	NEWLINE

#IF (BT_REC_TYPE != BT_REC_NONE)
	LD	A,(HB_BOOT_REC)		; IF WE ARE IN RECOVERY MODE
	OR	A			; POINT TO THE RECOVER MODE
	JR	Z,NOT_REC_M1		; INITIALIZATION TABLE
	LD	B,HB_INITRLEN
	LD	DE,HB_INIT_REC
	JR	IS_REC_M1
#ENDIF

NOT_REC_M1:
	LD	B,HB_INITTBLLEN
	LD	DE,HB_INITTBL
IS_REC_M1:
	CALL	CALLLIST
;
;--------------------------------------------------------------------------------------------------
; WATCHDOG ACTIVATION
;--------------------------------------------------------------------------------------------------
;
; IF WATCHDOG FUNCTIONALITY IS REQUESTED, CHECK TO MAKE SURE
; WE ARE GETTING INTERRUPTS.  IF SO, ENABLE THE WATCHDOG.
;
#IF (WDOGMODE != WDOG_NONE)
	CALL	NEWLINE
	PRTS("WDOG: $")
	PRTS("MODE=$")

  #IF (WDOGMODE == WDOG_EZZ80)
	PRTS("EZZ80$")
  #ENDIF
  #IF (WDOGMODE == WDOG_SKZ)
	PRTS("SKZ$")
  #ENDIF
;
	PRTS(" IO=0x$")
	LD	A,WDOGIO
	CALL	PRTHEXBYTE
;
  #IF (WDOGMODE == WDOG_SKZ)
	; SKZ WATCHDOG IS DISABLED EARLY IN BOOT PROCESS
	; HERE, WE ONLY NEED TO ENABLE IT, IF APPROPRIATE
	LD	HL,(HB_TICKS)		; GET LOW WORD
	LD	A,H			; CHECK FOR
	OR	L			; ... ZERO
	JR	Z,HB_WDOFF		; SKIP IF NOT TICKING
	IN	A,($6D)			; GET PORT VALUE
	SET	5,A			; SET THE WATCHDOG ENABLE BIT
	OUT	($6D),A			; ACTIVATE WATCHDOG
  #ENDIF
;
	PRTS(" ENABLED$")
	JR	HB_WDZ
;
HB_WDOFF:
	PRTS(" DISABLED$")
;
HB_WDZ:
;
#ENDIF
;
;--------------------------------------------------------------------------------------------------
; HEAP CURB INITIALIZATION
;--------------------------------------------------------------------------------------------------
;
; RECORD HEAP CURB AT THE CURRENT VALUE OF HEAP TOP.  HEAP CURB
; MARKS THE POINT IN THE HEAP AFTER WHICH MEMORY IS RELEASED
; WHEN AN HBIOS RESET IS PEFORMED.
;
	LD	HL,(CB_HEAPTOP)
	LD	(HEAPCURB),HL
;
;--------------------------------------------------------------------------------------------------
; FINAL CONSOLE ACTIVATION
;--------------------------------------------------------------------------------------------------
;
; ON SOME SYSTEMS, THE OPERATING CONSOLE IS DIFFERENT THAT THE BOOT
; CONSOLE.  FOR EXAMPLE, IF A VIDEO CONSOLE IS DESIRED.  A VIDEO
; CONSOLE CANNOT BE USED AS A BOOT CONSOLE BECAUSE IT WILL NOT BE
; INITIALIZED EARLY ENOUGH.  SO, IF DESIRED, WE SWITCH TO THE FINAL
; RUNNING CONSOLE HERE.
;
	LD	A,(CB_CONDEV)		; GET CURRENT CONSOLE
	LD	(HB_NEWCON),A		; AND INIT NEW CONSOLE VAR
;
#IF CRTACT
;
; BIOS IS CONFIGURED TO AUTO ACTIVATE CRT DEVICE.  FIRST,
; CHECK TO SEE IF WE HAVE A VALID CRT DEVICE TO USE.
;
	LD	A,(CB_CRTDEV)		; GET THE CRT DEVICE
	INC	A			; INCREMENT TO TEST FOR $FF
	JR	Z,INITSYS3		; IF NO CRT DEVICE, BYPASS CONSOLE SWITCH
;
; IF PLATFORM HAS A CONFIG JUMPER, CHECK TO SEE IF IT IS JUMPERED.
; IF SO, BYPASS SWITCH TO CRT CONSOLE (FAILSAFE MODE)
;
  #IF ((PLATFORM == PLT_SBC) | (PLATFORM == PLT_ZETA) | (PLATFORM == PLT_ZETA2) | (PLATFORM == PLT_MBC))
	IN	A,(RTCIO)		; RTC PORT, BIT 6 HAS STATE OF CONFIG JUMPER
	BIT	6,A			; BIT 6 HAS CONFIG JUMPER STATE
	JR	Z,INITSYS3		; Z=SHORTED, BYPASS CONSOLE SWITCH
  #ENDIF
;
  #IF (PLATFORM == PLT_S100)
	IN	A,($75)			; GET IO BYTE
	AND	%00000001		; ISOLATE CONSOLE BIT
	JR	NZ,INITSYS3		; NOT SET, BYPASS CONSOLE SWITCH
  #ENDIF
;
  #IF (PLATFORM == PLT_FZ80)
	; IOBYTE: XXXXXVVC
	;              00-	FORCE ONBOARD VGA/PS2 KBD (FV)
	;              --1	FORCE PROPELLER CONSOLE (SCON)
	;              110	NORMAL USB SERIAL BOOT
	;
	; WE ASSUME THAT THE ONBOARD VGA (FV) IS ALWAYS DETECTED AND
	; WILL BE THE CURRENT CRTDEV.  SCON IS ASSUMED TO BE THE
	; DEVICE AT CRTDEV + 1.  THESE ARE REASONABLE ASSUMPTIONS
	; UNLESS THE DRIVER DETECTION OR DRIVER ORDER IS CHANGED.
	IN	A,($36)			; GET IO BYTE
	AND	%00000110		; ISOLATE BITS
	JR	Z,HB_CRTACT		; FORCE ONBOARD CRT
	IN	A,($36)			; GET IO BYTE
	AND	%00000001		; ISOLATE BIT
	JR	Z,INITSYS3		; NORMAL USB SERIAL BOOT
	LD	A,(CB_CRTDEV)		; GET CRT DEV
	INC	A			; SWITCH FROM FV -> SCON
	LD	(CB_CRTDEV),A		; SAVE IT AND DO CONSOLE SWITCH
  #ENDIF
;
HB_CRTACT:
	LD	A,(CB_CRTDEV)		; GET CRT DISPLAY DEVICE
	LD	(HB_NEWCON),A		; AND QUEUE TO SWITCH
;
#ENDIF
;
#IF (FPSW_ENABLE)
;
; IF WE HAVE FRONT PANEL SWITCHES, THIS IS THE RIGHT PLACE TO HANDLE
; ANY CONSOLE CHANGE REQUESTS.  THE FRONT PANEL HAS TWO SWITCHES
; RELATED TO THIS: 1) CRT/SER, AND 2) SEC/PRI.  IF CRT/SER IS SET,
; THEN WE SWITCH TO THE CRT DEVICE (IF THERE IS ONE).  IF THE SEC/PRI
; SWITCH IS SET, THEN WE ATTEMPT TO USE THE SECOND SERIAL OR CRT
; DEVICE.
;
	PRTS("\r\nFP: IO=0x$")
	LD	A,FPSW_IO
	CALL	PRTHEXBYTE
;
	CALL	FP_DETECT
;
	; IF FP DOESN'T EXIST, BAIL OUT.
	LD	A,(FPSW_ACTIVE)		; GET FP EXISTENCE FLAG
	OR	A			; SET FLAGS
	JR	NZ,HB_FP1		; IF WE HAVE ONE, CONTINUE
;
	; HANDLE NO FP
	PRTS(" NOT PRESENT$")
	JR	HB_FPZ
;
HB_FP1:
	; WE NOW BELIEVE WE HAVE A VALID SWITCH SETTINGS VALUE.
	; CHECK FOR CRT SWITCH VALUE AND SWITCH TO CRT IF SET.
	; NOTE THAT CB_CRTDEV WILL BE ZERO IF THERE IS NO CRT DEVICE
	; IN THE SYSTEM, SO WE DON'T NEED TO CHECK FOR THE EXISTENCE
	; OF A CRT DEVICE -- IT WILL JUST FAILBACK TO FIRST SERIAL
	; PORT.
	PRTS(" SWITCHES=0x$")		; TAG
	CALL	FP_GETSWITCHES		; GET SWITCH SETTINGS
	CALL	PRTHEXBYTE		; DISPLAY VALUE
	LD	B,A			; SAVE IN REG B
	AND	SW_CRT			; TEST CRT BIT
	JR	Z,HB_FP2		; SKIP AHEAD IF NOT SET
	LD	A,(CB_CRTDEV)		; GET CRT DISPLAY DEVICE
	CP	$FF			; $FF MEANS NO CRT PRESENT
	JR	Z,HB_FP2		; BYPASS IF SO
	LD	(HB_NEWCON),A		; QUEUE NEW CONSOLE UNIT
;
HB_FP2:
	; IF SEC SWITCH IS SET, WE WANT TO BUMP TO SECONDARY
	; CRT OR SERIAL DEVICE. IF AN OVERRIDE IS SPECIFIED USING
	; SECCON, USE THAT, OTHERWISE INCREMENT THE DEFAULT UNIT.
	; THIS SHOULD WORK ASSUMING NORMAL ORDERING OF THE
	; CHARACTER DEVICE UNITS.
	LD	A,B			; RECOVER SWITCH SETTINGS
	AND	SW_SEC			; TEST SEC BIT
	JR	Z,HB_FPZ		; IF NOT SET, THEN ALL DONE
;
	LD	A,(CIO_CNT)		; GET CHAR UNIT COUNT
	LD	B,A			; MOVE TO B
	LD	A,SECCON		; GET SEC CONSOLE SETTING
	CP	$FF			; $FF MEANS USE INCREMENT
	JR	NZ,HB_FP3		; BYPASS IF NOT $FF
;
	; INCREMENT CONSOLE UNIT
	LD	A,(HB_NEWCON)		; GET NEW CONSOLE UNIT
	INC	A			; BUMP TO SECONDARY
;
HB_FP3:
	; MAKE SURE NEW CONSOLE UNIT DOES NOT EXCEED THE HIGHEST
	; CHAR UNIT IN SYSTEM.
	CP	B			; A (UNIT) >= B (CNT)?
	JR	NC,HB_FPZ		; ABORT IF UNIT TOO HIGH
	LD	(HB_NEWCON),A		; UPDATE NEW CONSOLE UNIT
;
HB_FPZ:
;
#ENDIF
;
INITSYS3:
;
#IF (SUPCTS)
;
; RESTORE BOOT CONSOLE CONFIGURATION
;
	CALL	LDELAY			; ALLOW SERIAL PORT TO FLUSH
	LD	B,BF_CIOINIT		; HBIOS CIO INIT
	LD	A,(HB_BOOTCONSAV)	; ORIGINAL BOOT CONSOLE DEVICE
	LD	C,A			; BOOT CONSOLE TO C
	LD	DE,(HB_CONCFGSAV)	; SAVED ORIGINAL CONSOLE CFG
	CALL	HB_DISPATCH		; INTERNAL HBIOS CALL
;
#ENDIF
;
; IF WE ARE GOING TO SWITCH CONSOLES, IT IS IMPLEMENTED HERE.  A
; MESSAGE IS PRINTED ON THE OLD CONSOLE INDICATING WHERE THE NEW
; CONSOLE IS AND THE NEW CONSOLE RECEIVES AN HBIOS BANNER.
;
	LD	A,(HB_BOOTCON)		; GET ORIGINAL BOOT CONSOLE DEV
	LD	C,A			; PUT IN C
	LD	A,(HB_NEWCON)		; GET NEW CONSOLE DEVICE
	CP	C			; COMPARE
	JR	Z,INITSYS3A		; NO CHANGE, BYPASS
;
	LD	DE,STR_CONSOLE		; CONSOLE CHANGE NOTIFY
	CALL	WRITESTR		; PRINT IT
	LD	A,(HB_NEWCON)		; GET NEW CONSOLE UNIT NUM
	CALL	PRTDECB			; PRINT UNIT NUM
	LD	(CB_CONDEV),A		; IMPLEMENT NEW CONSOLE!
	LD	DE,STR_BANNER		; POINT TO BANNER
	CALL	NZ,WRITESTR		; OUTPUT IF CONSOLE MOVED
;
;--------------------------------------------------------------------------------------------------
; PRINT DEVICE SUMMARY
;--------------------------------------------------------------------------------------------------
;
INITSYS3A:
	CALL	PRTSUM			; PRINT UNIT/DEVICE SUMMARY TABLE
;
;--------------------------------------------------------------------------------------------------
; DIAGNOSTIC ROUTINES
;--------------------------------------------------------------------------------------------------
;
; DIGANOSTIC ROUTINE TO EXERCISE THE Z280 BNKCPY CODE
;
#IF FALSE
;
	CALL	NEWLINE
	CALL	NEWLINE
	CALL	NEWLINE

	; SRC & DEST BELOW BND
	CALL	NEWLINE
	LD	HL,$4000
	LD	DE,$5000
	LD	BC,$3000
	CALL	NEWLINE
	CALL	REGDMP
	CALL	Z280_BNKCPYX

	; SRC & DEST ABOVE BND
	CALL	NEWLINE
	LD	HL,$8000
	LD	DE,$9000
	LD	BC,$1000
	CALL	NEWLINE
	CALL	REGDMP
	CALL	Z280_BNKCPYX

	; SRC CROSSOVER
	CALL	NEWLINE
	LD	HL,$7000
	LD	DE,$9000
	LD	BC,$2000
	CALL	NEWLINE
	CALL	REGDMP
	CALL	Z280_BNKCPYX

	; DEST CROSSOVER
	CALL	NEWLINE
	LD	HL,$9000
	LD	DE,$7000
	LD	BC,$2000
	CALL	NEWLINE
	CALL	REGDMP
	CALL	Z280_BNKCPYX

	; DOUBLE CROSSOVER
	CALL	NEWLINE
	LD	HL,$7800
	LD	DE,$7000
	LD	BC,$2000
	CALL	NEWLINE
	CALL	REGDMP
	CALL	Z280_BNKCPYX

	; DOUBLE CROSSOVER SINGLE BYTES
	CALL	NEWLINE
	LD	HL,$7FFE
	LD	DE,$7FFF
	LD	BC,$0500
	CALL	NEWLINE
	CALL	REGDMP
	CALL	Z280_BNKCPYX

	CALL	NEWLINE
	CALL	NEWLINE
	CALL	NEWLINE
;
#ENDIF
;
; DIAGNOSTIC ROUTINE TO PLAY SERIES OF NOTES
;
#IF FALSE
;
	LD	HL,0
	CALL	DBG_NOTE
	LD	HL,48
	CALL	DBG_NOTE
	LD	HL,204
	CALL	DBG_NOTE
	LD	HL,268
	CALL	DBG_NOTE
	LD	HL,436
	CALL	DBG_NOTE
;
	JP	INITSYS4
;
DBG_NOTE:
	PUSH	HL
	CALL	NEWLINE
	PRTS("AY: $")
	CALL	PRTDEC16
	PRTS("=$")
	CALL	AY_NOTE
	LD	HL,(AY_PENDING_PERIOD)
	CALL	PRTDEC16
	POP	HL
;
	PRTS(" SN: $")
	CALL	PRTDEC16
	PRTS("=$")
	CALL	SN7_NOTE
	LD	HL,(SN7_PENDING_PERIOD)
	CALL	PRTDEC16
;
	RET
;
#ENDIF
;
	; MAKE A LITTLE NOISE...
	LD	B,BF_SNDBEEP		; HBIOS BEEP FUNCTION
	LD	C,0			; SOUND UNIT 0
	LD	A,(SND_CNT)		; GET SOUND UNIT COUNT
	OR	A			; CHECK FOR ZERO
	CALL	NZ,HB_DISPATCH		; DO IT IF WE HAVE A SOUND UNIT
;
;--------------------------------------------------------------------------------------------------
; TRANSITION TO USER LAND
;--------------------------------------------------------------------------------------------------
;
INITSYS4:
;
; IF Z280, WE NEED TO SWITCH TO USER MODE NOW.
;
#IF (MEMMGR == MM_Z280)
	; LEAVE SYSTEM MODE STACK POINTING TO THE RIGHT PLACE
	LD	SP,HB_STACK		; DEDICATED HBIOS STACK LOC
;
	; ACTIVATE THE CORRECT USER MODE BANK
	LD	A,(HB_CURBNK)		; GET CURRENT BANK
	CALL	HBX_BNKSEL		; DO IT
;
	; PRESET THE USER MODE STACK
	LD	HL,HBX_LOC		; USER STACK JUST BELOW PROXY
	LDCTL	USP,HL			; DO IT
;
	; SWITCH TO USER MODE NOW
	LD	C,Z280_MSR		; MASTER STATUS REGISTER
	LD	HL,$4000 | $0B		; USER MODE W/ NORMAL INT MASK
	LDCTL	(C),HL			; DO IT
#ENDIF
;
	DIAG(0)				; CLEAR BOOT DIAG LED(S)
	FPLEDS(DIAG_00)			; CLEAR FP LEDS
;
; CHAIN TO LOADER
;
#IFDEF ROMBOOT
	LD	A,BID_IMG0		; CHAIN TO OS IMAGES BANK
#ELSE
	LD	A,BID_AUX		; CHAIN TO AUX BANK
#ENDIF
	LD	IX,0			; ENTER AT ADDRESS 0
	CALL	HBX_BNKCALL		; GO THERE
	HALT				; WE SHOULD NEVER COME BACK!
;
;--------------------------------------------------------------------------------------------------
;   TABLE OF RECOVERY MODE INITIALIZATION ENTRY POINTS
;--------------------------------------------------------------------------------------------------
;
; USE "CALLDUMMY" IF NO ENTRY REQUIRED
;
#IF (BT_REC_TYPE != BT_REC_NONE)
;
HB_PCINIT_REC:
;
  #IF ((PLATFORM == PLT_SBC) | (PLATFORM == PLT_MBC))
	.DW	UART_PREINIT
;	.DW	CALLDUMMY
  #ENDIF
;
HB_PCINITRLEN	.EQU	(($ - HB_PCINIT_REC) / 2)
;
HB_INIT_REC:
;
  #IF ((PLATFORM == PLT_SBC) | (PLATFORM == PLT_MBC))
	.DW	UART_INIT
	.DW	MD_INIT
	.DW	PPIDE_INIT
  #ENDIF
;
HB_INITRLEN	.EQU	(($ - HB_INIT_REC) / 2)
;
#ENDIF
;
;--------------------------------------------------------------------------------------------------
;   TABLE OF PRE-CONSOLE INITIALIZATION ENTRY POINTS
;--------------------------------------------------------------------------------------------------
;
HB_PCINITTBL:
;
#IF (SSERENABLE)
	.DW	SSER_PREINIT
#ENDIF
#IF (ASCIENABLE)
	.DW	ASCI_PREINIT
#ENDIF
#IF (Z2UENABLE)
	.DW	Z2U_PREINIT
#ENDIF
#IF (UARTENABLE)
	.DW	UART_PREINIT
#ENDIF
#IF (DUARTENABLE)
	.DW	DUART_PREINIT
#ENDIF
#IF (SIOENABLE)
	.DW	SIO_PREINIT
#ENDIF
#IF (EZ80UARTENABLE)
	.DW	EZUART_PREINIT
#ENDIF
#IF (ACIAENABLE)
	.DW	ACIA_PREINIT
#ENDIF
#IF (UFENABLE)
	.DW	UF_PREINIT
#ENDIF
#IF (TMSENABLE)
	.DW	TMS_PREINIT
#ENDIF
	.DW	TERM_PREINIT		; ALWAYS DO THIS ONE
#IF (PIOENABLE)
	.DW	PIO_PREINIT
#ENDIF
#IF (PIO_4P | PIO_ZP)
	.DW	PIO_PREINIT
#ENDIF
;
HB_PCINITTBLLEN	.EQU	(($ - HB_PCINITTBL) / 2)
;
;==================================================================================================
;   TABLE OF INITIALIZATION ENTRY POINTS
;==================================================================================================
;
HB_INITTBL:
;
#IF (KIOENABLE)
	.DW	KIO_INIT
#ENDIF
#IF (CTCENABLE)
	.DW	CTC_INIT
#ENDIF
#IF (PCFENABLE)
	.DW	PCF_INIT
#ENDIF
;;;#IF (DSKYENABLE)
#IF (ICMENABLE)
	.DW	ICM_INIT
#ENDIF
#IF (PKDENABLE)
	.DW	PKD_INIT
#ENDIF
;;;#ENDIF
#IF (LCDENABLE)
	.DW	LCD_INIT
#ENDIF
#IF (H8PENABLE)
	.DW	H8P_INIT
#ENDIF
#IF (GM7303ENABLE)
	.DW	GM7303_INIT
#ENDIF
#IF (PLATFORM == PLT_NABU)
	.DW	NABU_INIT
#ENDIF
#IF (AY38910ENABLE)
	.DW	AY38910_INIT		; AUDIBLE INDICATOR OF BOOT START
#ENDIF
#IF (SN76489ENABLE)
	.DW	SN76489_INIT
#ENDIF
#IF (YM2612ENABLE)
	.DW	YM2612_INIT
#ENDIF
#IF (SPKENABLE)
	.DW	SP_INIT			; AUDIBLE INDICATOR OF BOOT START
#ENDIF
#IF (SSERENABLE)
	.DW	SSER_INIT
#ENDIF
#IF (ASCIENABLE)
	.DW	ASCI_INIT
#ENDIF
#IF (Z2UENABLE)
	.DW	Z2U_INIT
#ENDIF
#IF (UARTENABLE)
	.DW	UART_INIT
#ENDIF
#IF (DUARTENABLE)
	.DW	DUART_INIT
#ENDIF
#IF (SIOENABLE)
	.DW	SIO_INIT
#ENDIF
#IF (EZ80UARTENABLE)
	.DW	EZUART_INIT
#ENDIF
#IF (ACIAENABLE)
	.DW	ACIA_INIT
#ENDIF
#IF (UFENABLE)
	.DW	UF_INIT
#ENDIF
#IF (DSRTCENABLE)
	.DW	DSRTC_INIT
#ENDIF
#IF (DS1501RTCENABLE)
	.DW	DS1501RTC_INIT
#ENDIF
#IF (BQRTCENABLE)
	.DW	BQRTC_INIT
#ENDIF
#IF (SIMRTCENABLE)
	.DW	SIMRTC_INIT
#ENDIF
#IF (INTRTCENABLE)
	.DW	INTRTC_INIT
#ENDIF
#IF (DS7RTCENABLE)
	.DW	DS7RTC_INIT
#ENDIF
#IF (DS5RTCENABLE)
	.DW	DS5RTC_INIT
#ENDIF
#IF (RP5RTCENABLE)
	.DW	RP5RTC_INIT
#ENDIF
#IF (EZ80RTCENABLE)
	.DW	EZ80RTC_INIT
#ENDIF
#IF (CPUFAM == CPU_EZ80)
	; INITALISE ONE OF THE SUPPORTED SYSTEM TIMER TICKS DRIVERS
	.DW	EZ80_TMR_INIT
#ENDIF
#IF (VDUENABLE)
	.DW	VDU_INIT
#ENDIF
#IF (CVDUENABLE)
	.DW	CVDU_INIT
#ENDIF
#IF (VGAENABLE)
	.DW	VGA_INIT
#ENDIF
#IF (GDCENABLE)
	.DW	GDC_INIT
#ENDIF
#IF (TMSENABLE)
	.DW	TMS_INIT
#ENDIF
#IF (EFENABLE)
	.DW	EF_INIT
#ENDIF
#IF (VRCENABLE)
	.DW	VRC_INIT
#ENDIF
#IF (FVENABLE)
	.DW	FV_INIT
#ENDIF
#IF (SCONENABLE)
	.DW	SCON_INIT
#ENDIF
#IF (LPTENABLE)
	.DW	LPT_INIT
#ENDIF
#IF (PIOENABLE)
	.DW	PIO_INIT
#ENDIF
#IF (PIO_4P | PIO_ZP)
	.DW	PIO_INIT
#ENDIF
#IF (DMAENABLE)
	.DW	DMA_INIT
#ENDIF
#IF (MDENABLE)
	.DW	MD_INIT
#ENDIF
#IF (FDENABLE)
	.DW	FD_INIT
#ENDIF
#IF (RFENABLE)
	.DW	RF_INIT
#ENDIF
#IF (IDEENABLE)
	.DW	IDE_INIT
#ENDIF
#IF (PPIDEENABLE)
	.DW	PPIDE_INIT
#ENDIF
#IF (SDENABLE)
	.DW	SD_INIT
#ENDIF
#IF (HDSKENABLE)
	.DW	HDSK_INIT
#ENDIF
#IF (PPAENABLE)
	.DW	PPA_INIT
#ENDIF
#IF (IMMENABLE)
	.DW	IMM_INIT
#ENDIF
#IF (SYQENABLE)
	.DW	SYQ_INIT
#ENDIF
#IF (CHENABLE)
	.DW	CH_INIT
#ENDIF
#IF (PRPENABLE)
	.DW	PRP_INIT
#ENDIF
#IF (PPPENABLE)
	.DW	PPP_INIT
#ENDIF
#IF (ESPENABLE)
	.DW	ESP_INIT
#ENDIF
;
HB_INITTBLLEN	.EQU	(($ - HB_INITTBL) / 2)
;
HB_SYSINIT_END	.EQU	$
;
;==================================================================================================
; BIOS FUNCTION DISPATCHER
;==================================================================================================
;
HB_DISP_BEG	.EQU	$
;
;--------------------------------------------------------------------------------------------------
; HIGH LEVEL FUNCTION DISPATCHER
;--------------------------------------------------------------------------------------------------
;
; JUMP TO FUNCTION GROUP SPECIFIC DISPATCHER.  THE FUNCTION GROUP
; IS BASED ON THE TOP NIBBLE OF THE FUNCTION NUMBER.
;
;   ENTRY: B=FUNCTION
;
HB_DISPATCH:
;
#IF (MEMMGR == MM_Z280)
	; FOR Z280 MEMMGR, WE DISPATCH VIA THE Z280 SYSCALL.
	; THE SYSCALL MECHANISM WILL DISABLE INTERRUPTS.  IN
	; GENERAL, INTERRUPTS ARE OK DURING API PROCESSING,
	; SO ENABLE THEM HERE.
	HB_EI
#ENDIF
;
; STACK INTEGRITY DIAGNOSTIC CHECK
;
#IF FALSE	; *DEBUG* START
;
	CALL	HB_DISPATCH1	; DO THE WORK
;
	; CHECK STACK INTEGRITY
	PUSH	AF
	LD	A,(HB_STACK - HB_STKSIZ + $08)
	CP	$FF
	SYSCHKERR(ERR_INTERNAL)
	LD	A,$FF
	LD	(HB_STACK - HB_STKSIZ + $08),A
	POP	AF
	RET
;
HB_DISPATCH1:
;
#ENDIF	; *DEBUG* END
;
	LD	A,B			; REQUESTED FUNCTION IS IN B
	CP	BF_CIO + $10		; $00-$0F: CHARACTER I/O
	JP	C,CIO_DISPATCH
	CP	BF_DIO + $10		; $10-$1F: DISK I/O
	JP	C,DIO_DISPATCH
	CP	BF_RTC + $10		; $20-$2F: REAL TIME CLOCK (RTC)
	JP	C,RTC_DISPATCH
	CP	BF_DSKY + $10		; $30-$3F: DSKY
	JP	C,DSKY_DISPATCH
	CP	BF_VDA + $10		; $40-$4F: VIDEO DISPLAY ADAPTER
	JP	C,VDA_DISPATCH
	CP	BF_SND + $10		; $50-$5F: SOUND DRIVERS
	JP	C,SND_DISPATCH
	; GAP TO E0
	CP	BF_EXT			; SKIP TO BF_EXT VALUE AT $E0
	JR	C,HB_DISPERR		; ERROR IF LESS THAN BF_EXT
	CP	BF_EXT + $10		; $E0-$EF: EXTENDED
	JP	C,EXT_DISPATCH
	JP	SYS_DISPATCH		; OTHERWISE SYS CALL
	; FALL THRU
;
HB_DISPERR:
	SYSCHKERR(ERR_NOFUNC)
	RET
;
;--------------------------------------------------------------------------------------------------
; CHARACTER I/O DEVICE FUNCTION DISPATCHER
;--------------------------------------------------------------------------------------------------
;
; ROUTE CALL TO SPECIFIED CHARACTER I/O DRIVER
;   B: FUNCTION
;   C: UNIT NUMBER
;
CIO_DISPATCH:
	BIT	7,C			; CHECK FOR SPECIAL UNIT CODE
	CALL	NZ,CIO_SPECIAL		; IF SO, HANDLE IT

	PUSH	IY			; SAVE INCOMING IY

	LD	IY,CIO_TBL		; POINT IY TO START OF CIO TABLE
	CALL	HB_DISPCALL		; GO TO GENERIC API CALL CODE

	POP	IY			; RESTORE IY
	RET				; AND DONE
;
; SPECIAL HANDLING FOR DEDICATED UNIT CODES
;
CIO_SPECIAL:
	; FOR NOW, ONLY SPECIAL CODE IS A CONSOLE REQUEST
	; SO JUST SWAP IN ACTIVE CONSOLE UNIT
	LD	A,(CB_CONDEV)		; GET ACTIVE CONSOLE
	LD	C,A			; OVERLAY UNIT CODE IN C
	RET				; AND REJOIN MAIN DISPATCH FLOW
;
; ADD AN ENTRY TO THE CIO UNIT TABLE (SEE HB_ADDENT FOR DETAILS)
;
CIO_ADDENT:
	LD	HL,CIO_TBL		; POINT TO CIO TABLE
	JP	HB_ADDENT		; ... AND GO TO COMMON CODE
;
; HBIOS CHARACTER DEVICE UNIT TABLE
;
; TABLE IS BUILT DYNAMICALLY BY EACH DRIVER DURING INITIALIZATION.
; THE TABLE IS PREFIXED BY TWO BYTES.  TABLE - 1 CONTAINS THE CURRENT
; NUMBER OF ENTRIES.  TABLE - 2 CONTAINS THE MAXIMUM NUMBER OF ENTRIES.
; TABLE - 3 CONTAINS THE NUMBER OF CIO FUNCTION IDS
; EACH ENTRY IS DEFINED AS:
;
;	WORD	DRIVER FUNCTION TABLE ADDRESS
;	WORD	UNIT SPECIFIC DATA (TYPICALLY A DEVICE INSTANCE DATA ADDRESS)
;
CIO_FNCNT	.EQU	7		; NUMBER OF CIO FUNCS (FOR RANGE CHECK)
CIO_MAX		.EQU	32		; UP TO 32 UNITS
CIO_SIZ		.EQU	CIO_MAX * 4	; EACH ENTRY IS 4 BYTES
;
		.DB	CIO_FNCNT	; CIO FUNCTION COUNT (FOR RANGE CHECK)
		.DB	CIO_MAX		; MAX ENTRY COUNT TABLE PREFIX
CIO_CNT		.DB	0		; ENTRY COUNT PREFIX
CIO_TBL		.FILL	CIO_SIZ,0	; SPACE FOR ENTRIES
;
; CRT TYPE CHAR DEVICES CALL THIS TO REGISTER THAT THEY WANT TO BE THE
; DEFAULT CRT DEVICE.  THIS ROUTINE WILL SET CB_CRTDEV WHEN CALLED THE
; FIRST TIME.  SUBSEQUENT CALLS ARE IGNORED.  THIS ENSURES THAT THE
; *FIRST* CRT DEVICE WINS.
;
CIO_SETCRT:
	PUSH	AF			; SAVE INCOMING CRT DEV NUM
	LD	A,(CB_CRTDEV)		; GET CURRENT CRT DEV NUM
	INC	A			; $FF -> $00
	JR	NZ,CIO_SETCRT_Z		; IF ALREADY SET, LEAVE IT ALONE
	POP	AF			; RESTORE AF
	LD	(CB_CRTDEV),A		; SAVE CRT DEV NUM
	RET				; AND DONE
;
CIO_SETCRT_Z:
	POP	AF			; RESTORE AF
	RET				; AND DONE
;
;--------------------------------------------------------------------------------------------------
; DISK I/O DEVICE FUNCTION DISPATCHER
;--------------------------------------------------------------------------------------------------
;
; ROUTE CALL TO SPECIFIED DISK I/O DRIVER
;   B: FUNCTION
;   C: UNIT NUMBER
;
DIO_DISPATCH:
;
#IF FALSE	; *DEBUG* START
;
	; DUMP INCOMING CALL
	CALL	NEWLINE
	PRTS("DIO>$")
	CALL	REGDMP			; DUMP REGS, NONE DESTROYED
;
	; DO THE ACTUAL DISPATCH PROCESSING
	CALL	DIO_DISPCALL
;
	; DUMP CALL RESULTS AND RETURN
	CALL	NEWLINE
	PRTS("DIO<$")
	CALL	REGDMP			; DUMP REGS, NONE DESTROYED
	RET
;
#ENDIF	; *DEBUG* END
;
DIO_DISPCALL:
	PUSH	IY			; SAVE INCOMING IY

	LD	IY,DIO_TBL		; POINT IY TO START OF DIO TABLE
	CALL	HB_DISPCALL		; GO TO GENERIC API CALL CODE

	POP	IY			; RESTORE IY
	RET				; AND DONE
;
; ADD AN ENTRY TO THE DIO UNIT TABLE
;
DIO_ADDENT:
	LD	HL,DIO_TBL		; POINT TO DIO TABLE
	JP	HB_ADDENT		; ... AND GO TO COMMON CODE
;
; HBIOS DISK DEVICE UNIT TABLE
;
; TABLE IS BUILT DYNAMICALLY BY EACH DRIVER DURING INITIALIZATION.
; THE TABLE IS PREFIXED BY TWO BYTES.  TABLE - 1 CONTAINS THE CURRENT
; NUMBER OF ENTRIES.  TABLE - 2 CONTAINS THE MAXIMUM NUMBER OF ENTRIES.
; TABLE - 3 CONTAINS THE NUMBER OF DIO FUNCTION IDS
; EACH ENTRY IS DEFINED AS:
;
;	WORD	DRIVER FUNCTION TABLE ADDRESS
;	WORD	UNIT SPECIFIC DATA (TYPICALLY A DEVICE INSTANCE DATA ADDRESS)
;
DIO_FNCNT	.EQU	12		; NUMBER OF DIO FUNCS (FOR RANGE CHECK)
DIO_MAX		.EQU	16		; UP TO 16 UNITS
DIO_SIZ		.EQU	DIO_MAX * 4	; EACH ENTRY IS 4 BYTES
;
		.DB	DIO_FNCNT	; DIO FUNCTION COUNT (FOR RANGE CHECK)
		.DB	DIO_MAX		; MAX ENTRY COUNT TABLE PREFIX
DIO_CNT		.DB	0		; ENTRY COUNT PREFIX
DIO_TBL		.FILL	DIO_SIZ,0	; SPACE FOR ENTRIES
;
;--------------------------------------------------------------------------------------------------
; DISK READ HELPER
;--------------------------------------------------------------------------------------------------
;
;;; RELOCATE THIS CODE???
;
; IMPLEMENTS MULTI SECTOR READS AND I/O TO/FROM
; BANKED RAM VIA BOUNCE BUFFER
;
; ON ENTRY:
;  TOS=READ FN ADR
;  HL=BUF ADR
;  E=SEC COUNT
;  D=BUF BANK ID
;
HB_DSKREAD:
;
	; THE ACTUAL SECTOR READ FUNCTION ADDRESS IS ON TOS, SAVE IT
	EX	(SP),HL			; SAVE HL TO TOS, HL := READ FN ADR
	LD	(HB_DSKFNADR),HL	; IMBED IN CALL OP BELOW
	POP	HL			; RECOVER HL
;
	LD	(HB_DSKCMD),BC		; SAVE HBIOS FUNC & UNIT
;
#IF (FPLED_ENABLE & FPLED_DSKACT)
	; SAVE DISK UNIT NUMBER BIT MASK
	LD	A,C			; GET DISK UNIT NUMBER
	LD	B,A			; PUT IN B FOR LOOP COUNTER
	INC	B			; LOOP ONE EXTRA TIME TO HANDLE UNIT=0
	XOR	A			; START WITH ACCUM ZERO
	SCF				; ... AND CF SET
HB_DSKREAD0:
	RLA				; ROTATE BIT
	DJNZ	HB_DSKREAD0		; ... UNTIL IN PROPER LOCATION
	LD	(HB_DSKBIT),A		; SAVE IT FOR DIAGNOSTICS
#ENDIF
;
#IF TRUE
	; CHECK TO SEE IF INTER-BANK I/O NEEDED.
	BIT	7,H			; TGT BUF IN UPPER 32K?
	JP	NZ,HB_DSKIO		; IF SO, NON-BANKED
	LD	A,D			; GET TGT BANK
	CP	BID_BIOS		; BIOS BANK?
	JP	Z,HB_DSKIO		; IF SO, NON-BANKED
#ENDIF
;
#IF TRUE
	; RAM BANK RANGE CHECK
	LD	A,D			; GET TGT BANK
	CP	BID_RAMN		; BANK IN RANGE 0-N?
	CALL	NC,PANIC		; IF >N, PANIC
#ENDIF
;
	; SAVE TGT BUF BNK/ADR
	LD	(HB_IOBUF),HL
	LD	A,D
	LD	(HB_IOBNK),A
;
	; SETUP READ AND LOOP COUNT
	LD	B,E			; SEC LOOP COUNTER
	LD	C,0			; SEC COMPLETE COUNTER
;
HB_DSKREAD1:
	LD	HL,HB_WRKBUF		; USE WORK BUF REAL I/O
;
	; CALL READ FN
	CALL	HB_DSKFN		; READ ONE SECTOR
;
	; IF FAIL, RETURN ERR
	JR	NZ,HB_DSKREADX		; BAIL OUT ON ERROR
;
	; BNKCPY SEC DATA TO REAL BANK/BUF & INC BUF ADR
	PUSH	BC			; SAVE COUNTERS
	LD	A,(HB_IOBNK)		; DEST BANK
	LD	(HB_DSTBNK),A		; ... TO PROXY
	LD	A,BID_BIOS		; SRC BANK
	LD	(HB_SRCBNK),A		; ... TO PROXY
	LD	BC,512			; COPY 512 BYTES (1 SEC)
	LD	DE,(HB_IOBUF)		; TGT BUFFER ADR
	LD	HL,HB_WRKBUF		; SOURCE BUFFER
	CALL	HBX_BNKCPY		; DO BANK COPY
	LD	(HB_IOBUF),DE		; SAVE UPDATED TGT BUF ADR
	POP	BC			; RESTORE COUNTERS
;
	; INC READ COUNT
	INC	C			; BUMP SEC READ COUNT
	DJNZ	HB_DSKREAD1		; LOOP AS NEEDED
	XOR	A			; SIGNAL SUCCESS
;
HB_DSKREADX:
	LD	HL,(HB_IOBUF)		; NEXT BUF ADR
	JR	HB_DSKIOX		; DONE
;
;--------------------------------------------------------------------------------------------------
; DISK WRITE HELPER
;--------------------------------------------------------------------------------------------------
;
; IMPLEMENTS MULTI SECTOR WRITES AND I/O TO/FROM
; BANKED RAM VIA BOUNCE BUFFER
;
; TOS=WRITE FN ADR
; HL=BUF ADR
; E=SEC COUNT
; D=BUF BANK ID
;
HB_DSKWRITE:
;
	; THE ACTUAL SECTOR READ FUNCTION ADDRESS IS ON TOS, SAVE IT
	EX	(SP),HL			; SAVE HL TO TOS, HL := READ FN ADR
	LD	(HB_DSKFNADR),HL	; IMBED IN CALL OP BELOW
	POP	HL			; RECOVER HL
;
	LD	(HB_DSKCMD),BC		; SAVE HBIOS FUNC & UNIT
;
#IF (FPLED_ENABLE & FPLED_DSKACT)
	; SAVE DISK UNIT NUMBER BIT MASK
	LD	A,C			; GET DISK UNIT NUMBER
	LD	B,A			; PUT IN B FOR LOOP COUNTER
	INC	B			; LOOP ONE EXTRA TIME TO HANDLE UNIT=0
	XOR	A			; START WITH ACCUM ZERO
	SCF				; ... AND CF SET
HB_DSKWRITE0:
	RLA				; ROTATE BIT
	DJNZ	HB_DSKWRITE0		; ... UNTIL IN PROPER LOCATION
	LD	(HB_DSKBIT),A		; SAVE IT FOR DIAGNOSTICS
#ENDIF
;
#IF TRUE
	; CHECK TO SEE IF INTER-BANK I/O NEEDED.
	BIT	7,H			; TGT BUF IN UPPER 32K?
	JP	NZ,HB_DSKIO		; IF SO, NON-BANKED
	LD	A,D			; GET TGT BANK
	CP	BID_BIOS		; BIOS BANK?
	JP	Z,HB_DSKIO		; IF SO, NON-BANKED
#ENDIF
;
#IF TRUE
	; RAM BANK RANGE CHECK
	LD	A,D			; GET TGT BANK
	CP	BID_RAMN		; BANK IN RANGE 0-N?
	CALL	NC,PANIC		; IF >N, PANIC
#ENDIF
;
	; SAVE TGT BUF BNK/ADR
	LD	(HB_IOBUF),HL
	LD	A,D
	LD	(HB_IOBNK),A
;
	; SETUP WRITE AND LOOP COUNT
	LD	B,E			; SEC LOOP COUNTER
	LD	C,0			; SEC COMPLETE COUNTER
;
HB_DSKWRITE1:
	; BNKCPY SEC DATA TO WORK BANK/BUF & INC BUF ADR
	PUSH	BC			; SAVE COUNTERS
	LD	A,BID_BIOS		; DEST BANK
	LD	(HB_DSTBNK),A		; ... TO PROXY
	LD	A,(HB_IOBNK)		; SRC BANK
	LD	(HB_SRCBNK),A		; ... TO PROXY
	LD	BC,512			; COPY 512 BYTES (1 SEC)
	LD	DE,HB_WRKBUF		; TGT BUFFER ADR
	LD	HL,(HB_IOBUF)		; SOURCE BUFFER
	CALL	HBX_BNKCPY		; DO BANK COPY
	LD	(HB_IOBUF),HL		; SAVE UPDATED SRC BUF ADR
	POP	BC			; RESTORE COUNTERS
;
	; CALL WRITE FN
	LD	HL,HB_WRKBUF		; WRITE FROM WORK BUFFER
	CALL	HB_DSKFN		; WRITE ONE SECTOR
;
	; IF FAIL, RETURN ERR
	JR	NZ,HB_DSKWRITEX		; BAIL OUT ON ERROR
;
	; INC WRITE COUNT
	INC	C			; BUMP SEC WRITE COUNT
	DJNZ	HB_DSKWRITE1		; LOOP AS NEEDED
	XOR	A			; SIGNAL SUCCESS
;
HB_DSKWRITEX:
	LD	HL,(HB_IOBUF)		; NEXT BUF ADR
	JR	HB_DSKIOX		; DONE
;
;--------------------------------------------------------------------------------------------------
; NON-BANKED DISK READ/WRITE
;--------------------------------------------------------------------------------------------------
;
HB_DSKIO:
;
	; SETUP LOOP COUNT
	LD	B,E			; SEC LOOP COUNTER
	LD	C,0			; SEC COMPLETE COUNTER
;
HB_DSKIO1:
	; CALL READ/WRITE FN
	CALL	HB_DSKFN		; READ/WRITE ONE SECTOR
;
	; IF FAIL, RETURN ERR
	JR	NZ,HB_DSKIOX		; BAIL OUT ON ERROR
;
	; INC SECTOR COUNT
	INC	C			; BUMP SEC READ/WRITE COUNT
	DJNZ	HB_DSKIO1		; LOOP AS NEEDED
	XOR	A			; SIGNAL SUCCESS
;
HB_DSKIOX:
	LD	E,C			; WRITE COUNT TO E
	OR	A			; SET RESULT FLAGS
	RET				; DONE
;
;--------------------------------------------------------------------------------------------------
; INVOKE DRIVER DISK I/O FUNCTION
;--------------------------------------------------------------------------------------------------
;
HB_DSKFN:
	PUSH	BC			; SAVE COUNTERS
#IF (FPLED_ENABLE & FPLED_DSKACT)
	LD	A,(HB_DSKBIT)		; LOAD UNIT DISK BIT MASK
	CALL	FP_SETLEDS		; DISPLAY ON FP LEDS
#ENDIF
#IF (LEDENABLE & LEDDISKIO)
	DIAG(1)				; BIT 0 FOR TINY Z80 & MBC, BIT 2 FOR SCXXX
#ENDIF
	LD	E,1			; ONE SECTOR
HB_DSKFNADR	.EQU	$+1
	CALL	PANIC			; READ ONE SECTOR
#IF (FPLED_ENABLE & FPLED_DSKACT)
	FPLEDS($00)			; CLEAR FP LEDS
#ENDIF
#IF (LEDENABLE & LEDDISKIO)
	DIAG(0)
#ENDIF
	POP	BC			; RESTORE COUNTERS
	RET				; RETURN
;
HB_DSKBIT	.DB	0		; ACTIVE DISK UNIT
HB_IOBUF	.DW	0		; CURRENT IO BUFFER ADR
HB_IOBNK	.DB	0		; CURRENT IO BUFFER BANK ID
HB_DSKCMD:
HB_DSKUNIT	.DB	0		; CURRENT DISK UNIT
HB_DSKFUNC	.DB	0		; CURRENT DISK FUNCTION
HB_DSKADR	.FILL	4,0		; CURRENT DISK BLOCK ADDRESS
;
;--------------------------------------------------------------------------------------------------
; DSKY DISK ACTIVITY MONITOR
;--------------------------------------------------------------------------------------------------
;
; THIS FUNCTION IS CALLED BY DISK DRIVERS JUST PRIOR TO
; THE START OF A DISK I/O OPERATION.
;
; THE CURRENT DISK UNIT NUMBER WILL BE DISPLAYED IN THE FIRST
; 2 SEG DISPLAYS.  THE LOWER 24 BITS OF THE SECTOR WILL BE
; DISPLAYED IN THE LAST 6 SEG DISPLAYS.
;
; A DOT IS DISPLAYED TO SEPARATE THE UNIT NUMBER FROM THE ADDRESS
; DISPLAY.  ALSO, A TRAILING DOT IS DISPLAYED IF THE I/O FUNCTION
; IS A WRITE.
;
;   HL: ADDRESS OF 32-BIT SECTOR NUMBER (LITTLE-ENDIAN)
;   ALL REGISTERS PERSERVED
;
#IF (DSKYDSKACT)
HB_DSKACT:
;
	; SAVE EVERYTHING
	PUSH	AF
	PUSH	BC
	PUSH	DE
	PUSH	HL
;
	; COPY VALUE TO LOCAL HB_DSKADR
	CALL	LD32
;
HB_DSKACT1:
	LD	BC,HB_DSKADR
	CALL	ST32
;
	LD	B,BF_DSKYEVENT
	LD	C,DSKY_EVT_DSKACT
	CALL	DSKY_DISPATCH
;
	; CLEAN UP AND GO AWAY
	POP	HL
	POP	DE
	POP	BC
	POP	AF
	JR	HB_DSKACT_Z		; DONE
;
; THIS IS THE CHS VARIANT OF THE ABOVE.  THIS IS USED BY CHS ORIENTED
; DISK DRIVERS (BASICALLY JUST FLOPPY).
;
; THE CURRENT DISK UNIT NUMBER WILL BE DISPLAYED IN THE FIRST
; 2 SEG DISPLAYS.  THE TRACK, HEAD, AND SECTOR WILL BE DISPLAYED IN
; THE LAST 6 SEG DISPLAYS
;
;   HL: ADDRESS OF CYL,HD,SEC IN THE FORMAT CCSH
;   ALL REGISTERS PRESERVED
;
HB_DSKACTCHS:
	PUSH	AF
	PUSH	BC
	PUSH	DE
	PUSH	HL
	CALL	LD32			; DE:HL = HSCC
	; MAP HSCC -> CCHS
	EX	DE,HL
	JR	HB_DSKACT1
;
HB_DSKACT_Z:
	RET
;
#ELSE
;
HB_DSKACT:
HB_DSKACTCHS:
	RET
;
#ENDIF
;
;--------------------------------------------------------------------------------------------------
; REAL TIME CLOCK DEVICE DISPATCHER
;--------------------------------------------------------------------------------------------------
;
; ROUTE CALL TO REAL TIME CLOCK DRIVER
;   B: FUNCTION
;
RTC_DISPATCH:
	PUSH	HL			; SAVE INCOMING HL
	LD	HL,(RTC_DISPADR)	;
	EX	(SP),HL
	RET
;
RTC_DISPERR:
	SYSCHKERR(ERR_NOHW)
	RET
;
; SET RTC DISPATCH ADDRESS, USED BY RTC DRIVERS DURING INIT
; BC HAS ADDRESS OF DISPATCH ADDRESS
; WILL ONLY SAVE THE FIRST ADDRESS SET
;
RTC_SETDISP:
	LD	A,(RTC_DISPACT)		; GET ACTIVE FLAG
	OR	A			; IS IT ACTIVE?
	RET	NZ			; ABORT IF ALREADY ACTIVE
	LD	(RTC_DISPADR),BC	; SAVE THE ADDRESS
	OR	$FF			; FLAG ACTIVE VALUE
	LD	(RTC_DISPACT),A		; SAVE IT
	RET				; AND DONE
;
RTC_DISPADR	.DW	RTC_DISPERR	; RTC DISPATCH ADDRESS
RTC_DISPACT	.DB	0		; SET WHEN DISPADR SET
;
;--------------------------------------------------------------------------------------------------
; DSKY DEVICE DISPATCHER
;--------------------------------------------------------------------------------------------------
;
; ROUTE CALL TO DSKY DRIVER
;   B: FUNCTION
;
DSKY_DISPATCH:
	PUSH	HL			; SAVE INCOMING HL
	LD	HL,(DSKY_DISPADR)	;
	EX	(SP),HL
	RET
;
DSKY_DISPERR:
	SYSCHKERR(ERR_NOHW)
	RET
;
; SET DSKY DISPATCH ADDRESS, USED BY DSKY DRIVERS DURING INIT
; BC HAS ADDRESS OF DISPATCH ADDRESS
; WILL ONLY SAVE THE FIRST ADDRESS SET
;
DSKY_SETDISP:
	LD	A,(DSKY_DISPACT)	; GET ACTIVE FLAG
	OR	A			; IS IT ACTIVE?
	RET	NZ			; ABORT IF ALREADY ACTIVE
	LD	(DSKY_DISPADR),BC	; SAVE THE ADDRESS
	OR	$FF			; FLAG ACTIVE VALUE
	LD	(DSKY_DISPACT),A	; SAVE IT
	RET				; AND DONE
;
DSKY_DISPADR	.DW	DSKY_DISPERR	; DSKY DISPATCH ADDRESS
DSKY_DISPACT	.DB	0		; SET WHEN DISPADR SET
;
;==================================================================================================
;   VIDEO DISPLAY ADAPTER DEVICE DISPATCHER
;==================================================================================================
;
; ROUTE CALL TO SPECIFIED VDA DEVICE DRIVER
;   B: FUNCTION
;   C: UNIT NUMBER
;
VDA_DISPATCH:
	PUSH	IY			; SAVE INCOMING IY

	LD	IY,VDA_TBL		; POINT IY TO START OF DIO TABLE
	CALL	HB_DISPCALL		; GO TO GENERIC API CALL CODE

	POP	IY			; RESTORE IY
	RET				; AND DONE
;
; ADD AN ENTRY TO THE VDA UNIT TABLE (SEE HB_ADDENT FOR DETAILS)
;
VDA_ADDENT:
	LD	HL,VDA_TBL		; POINT TO VDA TABLE
	JP	HB_ADDENT		; ... AND GO TO COMMON CODE
;
; HBIOS VIDEO DEVICE UNIT TABLE
;
; TABLE IS BUILT DYNAMICALLY BY EACH DRIVER DURING INITIALIZATION.
; THE TABLE IS PREFIXED BY TWO BYTES.  TABLE - 1 CONTAINS THE CURRENT
; NUMBER OF ENTRIES.  TABLE - 2 CONTAINS THE MAXIMUM NUMBER OF ENTRIES.
; TABLE - 3 CONTAINS THE NUMBER OF CIO FUNCTION IDS
; EACH ENTRY IS DEFINED AS:
;
;	WORD	DRIVER FUNCTION TABLE ADDRESS
;	WORD	UNIT SPECIFIC DATA (TYPICALLY A DEVICE INSTANCE DATA ADDRESS)
;
VDA_FNCNT	.EQU	16		; NUMBER OF VDA FUNCS (FOR RANGE CHECK)
VDA_MAX		.EQU	16		; UP TO 16 UNITS
VDA_SIZ		.EQU	VDA_MAX * 4	; EACH ENTRY IS 4 BYTES
;
		.DB	VDA_FNCNT	; VDA FUNCTION COUNT (FOR RANGE CHECK)
		.DB	VDA_MAX		; MAX ENTRY COUNT TABLE PREFIX
VDA_CNT		.DB	0		; ENTRY COUNT PREFIX
VDA_TBL		.FILL	VDA_SIZ,0	; SPACE FOR ENTRIES
;
;--------------------------------------------------------------------------------------------------
;   SOUND ADAPTER DEVICE DISPATCHER
;--------------------------------------------------------------------------------------------------
;
; ROUTE CALL TO SPECIFIED SOUND DEVICE DRIVER
;   B: FUNCTION
;   C: UNIT NUMBER
;
SND_DISPATCH:
	PUSH	IY			; SAVE INCOMING IY
;
	LD	IY, SND_TBL		; POINT IY TO START OF DIO TABLE
	CALL	HB_DISPCALL		; GO TO GENERIC API CALL CODE
;
	POP	IY			; RESTORE IY
	RET				; AND DONE
;
; ADD AN ENTRY TO THE SND UNIT TABLE (SEE HB_ADDENT FOR DETAILS)
;
SND_ADDENT:
	LD	HL, SND_TBL		; POINT TO SND TABLE
	JP	HB_ADDENT		; ... AND GO TO COMMON CODE
;
; HBIOS VIDEO DEVICE UNIT TABLE
;
; TABLE IS BUILT DYNAMICALLY BY EACH DRIVER DURING INITIALIZATION.
; THE TABLE IS PREFIXED BY TWO BYTES.  TABLE - 1 CONTAINS THE CURRENT
; NUMBER OF ENTRIES.  TABLE - 2 CONTAINS THE MAXIMUM NUMBER OF ENTRIES.
; TABLE - 3 CONTAINS THE NUMBER OF SND FUNCTION IDS
; EACH ENTRY IS DEFINED AS:
;
;	WORD	DRIVER FUNCTION TABLE ADDRESS
;	WORD	UNIT SPECIFIC DATA (TYPICALLY A DEVICE INSTANCE DATA ADDRESS)
;
SND_FNCNT	.EQU	9		; NUMBER OF SND FUNCS (FOR RANGE CHECK)
SND_MAX		.EQU	5		; UP TO 5 UNITS
SND_SIZ		.EQU	SND_MAX * 4	; EACH ENTRY IS 4 BYTES
;
		.DB	SND_FNCNT	; SND FUNCTION COUNT (FOR RANGE CHECK)
		.DB	SND_MAX		; MAX ENTRY COUNT TABLE PREFIX
SND_CNT		.DB	0		; ENTRY COUNT PREFIX
SND_TBL		.FILL	SND_SIZ,0	; SPACE FOR ENTRIES
;
;--------------------------------------------------------------------------------------------------
;   SPEAKER BEEP ROUTINE
;--------------------------------------------------------------------------------------------------
;
; ROUTINE TO BEEP A SOUND UNIT
; THE SOUND DRIVERS CAN DEFER THEIR BEEP FUNCTION TO THIS FUNCTION
; ON ENTRY, B = SOUND UNIT
; WHICH CHANNEL SHOULD BE USED?  IS THERE A GOOD DEFAULT CHANNEL?
;
SND_BEEP:
	; RESET THE SOUND DEVICE TO START
	LD	B,$50			; SOUND RESET FUNCTION
	CALL	SND_BEEP_DISP		; DO IT
;
	; SET VOLUME TO MAX
	LD	B,$51			; VOLUME
	LD	L,$FF			; MAX
	CALL	SND_BEEP_DISP		; DO IT
;
	; SET NOTE TO PLAY
	LD	B,$53			; SELECT NOTE
	LD	HL,244			; B5 (CLOSE TO 1 KHZ)
	CALL	SND_BEEP_DISP		; DO IT
;
	; START PLAYING THE SOUND
	LD	B,$54			; PLAY SOUND
	LD	D,0			; CHANNEL 0
	CALL	SND_BEEP_DISP		; DO IT
;
	; WAIT A BIT FOR SOUND TO PLAY
	LD	DE,23436		; PLAY FOR 1/3 SECOND
	CALL	VDELAY			; WAIT WHILE TONE IS PLAYED
;
	LD	B,$50			; SOUND RESET FUNCTION
	CALL	SND_BEEP_DISP		; DO IT
;
	XOR	A			; SIGNAL SUCCESS
	RET				; DONE
;
SND_BEEP_DISP:
	; CALL SOUND DISPATCHER PRESERVING BC ACROSS CALL
	PUSH	BC
	CALL	SND_DISPATCH
	POP	BC
	RET
;
;--------------------------------------------------------------------------------------------------
;   EXTENDED FUNCTION DISPATCHER
;--------------------------------------------------------------------------------------------------
;
;   B: FUNCTION
;
EXT_DISPATCH:
	LD	A,B			; GET REQUESTED FUNCTION
	AND	$0F			; ISOLATE SUB-FUNCTION
	JP	Z,EXT_SLICE		; $E0
	DEC	A
	JP	HB_DISPERR		; ERROR COULD NOT FIND FUNCTION
;
;--------------------------------------------------------------------------------------------------
;   SYSTEM FUNCTION DISPATCHER
;--------------------------------------------------------------------------------------------------
;
;   B: FUNCTION
;
SYS_DISPATCH:
	LD	A,B			; GET REQUESTED FUNCTION
	AND	$0F			; ISOLATE SUB-FUNCTION
	JP	Z,SYS_RESET		; $F0
	DEC	A
	JP	Z,SYS_VER		; $F1
	DEC	A
	JP	Z,SYS_SETBNK		; $F2
	DEC	A
	JP	Z,SYS_GETBNK		; $F3
	DEC	A
	JP	Z,SYS_SETCPY		; $F4
	DEC	A
	JP	Z,SYS_BNKCPY		; $F5
	DEC	A
	JP	Z,SYS_ALLOC		; $F6
	DEC	A
	JP	Z,SYS_FREE		; $F7
	DEC	A
	JP	Z,SYS_GET		; $F8
	DEC	A
	JP	Z,SYS_SET		; $F9
	DEC	A
	JP	Z,SYS_PEEK		; $FA
	DEC	A
	JP	Z,SYS_POKE		; $FB
	DEC	A
	JP	Z,SYS_INT		; $FC
	DEC	A
	JP	HB_DISPERR		; ERROR COULD NOT FIND FUNCTION
;
HB_DISP_END	.EQU	$
;
;==================================================================================================
;   Z280 INTERRUPT VECTOR TABLE
;==================================================================================================
;
HB_Z280IVT_BEG	.EQU	$
;
; THE Z280 IVT MUST BE ON A 4K BOUNDARY.  IT HAS BEEN LOCATED
; HERE IN AN EFFORT TO MINIMIZE WASTED SPACE.  THERE SHOULD BE
; A LITTLE LESS THAN 4K OF CODE ABOVE.
;
#IF (MEMMGR == MM_Z280)
;
Z280_IVT_SLACK_ORG	.EQU	$
	ALIGN($1000)
Z280_IVT_SLACK		.EQU	$ - Z280_IVT_SLACK_ORG
	.ECHO	"Z280 IVT SLACK = "
	.ECHO	Z280_IVT_SLACK
	.ECHO	" bytes.\n"
;
Z280_IVT:
	.DW	0, 0			; RESERVED
	.DW	0			; NMI MSR
	.DW	0			; NMI VECTOR
	.DW	$0000			; INT A MSR
	.DW	Z280_BADINT		; INT A VECTOR
	.DW	$0000			; INT B MSR
	.DW	Z280_BADINT		; INT B VECTOR
	.DW	$0000			; INT C MSR
	.DW	Z280_BADINT		; INT C VECTOR
	.DW	$0000			; COUNTER/TIMER 0 MSR
	.DW	Z280_BADINT		; COUNTER/TIMER 0 VECTOR
	.DW	$0000			; COUNTER/TIMER 1 MSR
	.DW	Z280_BADINT		; COUNTER/TIMER 1 VECTOR
	.DW	0, 0			; RESERVED
	.DW	$0000			; COUNTER/TIMER 2 MSR
	.DW	Z280_BADINT		; COUNTER/TIMER 2 VECTOR
	.DW	$0000			; DMA CHANNEL 0 MSR
	.DW	Z280_BADINT		; DMA CHANNEL 0 VECTOR
	.DW	$0000			; DMA CHANNEL 1 MSR
	.DW	Z280_BADINT		; DMA CHANNEL 1 VECTOR
	.DW	$0000			; DMA CHANNEL 2 MSR
	.DW	Z280_BADINT		; DMA CHANNEL 2 VECTOR
	.DW	$0000			; DMA CHANNEL 3 MSR
	.DW	Z280_BADINT		; DMA CHANNEL 3 VECTOR
	.DW	$0000			; UART RECEIVER MSR
	.DW	Z280_BADINT		; UART RECEIVER VECTOR
	.DW	$0000			; UART TRANSMITTER MSR
	.DW	Z280_BADINT		; UART TRANSMITTER VECTOR
	.DW	$0000			; SINGLE STEP TRAP MSR
	.DW	Z280_SSTEP		; SINGLE STEP TRAP VECTOR
	.DW	$0000			; BREAK ON HALT TRAP MSR
	.DW	Z280_BRKHLT		; BREAK ON HALT TRAP VECTOR
	.DW	$0000			; DIVISION EXCEPTION TRAP MSR
	.DW	Z280_DIVEXC		; DIVISION EXCEPTION TRAP VECTOR
	.DW	$0000			; STACK OVERFLOW WARNING TRAP MSR
	.DW	Z280_STKOVR		; STACK OVERFLOW WARNING TRAP VECTOR
	.DW	$0000			; ACCESS VIOLATION TRAP MSR
	.DW	Z280_ACCVIO		; ACCESS VIOLATION TRAP VECTOR
	.DW	$0000			; SYSTEM CALL TRAP MSR
	.DW	Z280_SYSCALL		; SYSTEM CALL TRAP VECTOR
	.DW	$0000			; PRIVILEGED INSTRUCTION TRAP MSR
	.DW	Z280_PRIVINST		; PRIVILEGED INSTRUCTION TRAP VECTOR
	.DW	$0000			; EPU <- MEMORY EXTENDED INSTRUCTION TRAP MSR
	.DW	$0000			; EPU <- MEMORY EXTENDED INSTRUCTION TRAP VECTOR
	.DW	$0000			; MEMORY <- EPU EXTENDED INSTRUCTION TRAP MSR
	.DW	$0000			; MEMORY <- EPU EXTENDED INSTRUCTION TRAP VECTOR
	.DW	$0000			; A <- EPU EXTENDED INSTRUCTION TRAP MSR
	.DW	$0000			; A <- EPU EXTENDED INSTRUCTION TRAP VECTOR
	.DW	$0000			; EPU INTERNAL OPERATION EXTENDED INSTRUCTION TRAP MSR
	.DW	$0000			; EPU INTERNAL OPERATION EXTENDED INSTRUCTION TRAP VECTOR
	.DW	0, 0			; RESERVED
	.DW	0, 0			; RESERVED
	; PROGRAM COUNTER VALUES FOR NMI/INTA (16)
	.DW	HBX_IV00
	.DW	HBX_IV01
	.DW	HBX_IV02
	.DW	HBX_IV03
	.DW	HBX_IV04
	.DW	HBX_IV05
	.DW	HBX_IV06
	.DW	HBX_IV07
	.DW	HBX_IV08
	.DW	HBX_IV09
	.DW	HBX_IV0A
	.DW	HBX_IV0B
	.DW	HBX_IV0C
	.DW	HBX_IV0D
	.DW	HBX_IV0E
	.DW	HBX_IV0F
;
	; THE REMAINDER OF THE Z280 IVT IS TRUNCATED HERE BECAUSE IT
	; TAKES A BUNCH OF SPACE AND IS NOT USED.  WE SUPPORT ONLY
	; 16 VECTORED INTERRUPTS AND THEY MUST BE CONNECTED TO INTA.
;
#ENDIF
;
HB_Z280IVT_END	.EQU	$
;
;==================================================================================================
; EXTENSION API FUNCTIONS
;==================================================================================================
;
HB_EXTAPI_BEG	.EQU	$
;
;--------------------------------------------------------------------------------------------------
; SLICE CALCULATE - GET DISK EXTENDED HARD DISK MEDIA INFORMATION
;--------------------------------------------------------------------------------------------------
;   This function is specificly intended for Hard Drives, where it will scan
;   the partition table and return a Media ID, including hd1k (MID_HDNEW).
;   It will also return the absolute LBA offset of the first sector in the slice
;   If the slice number is invalid (wont fit) the Status will return an error
;   If the Unit is not a hard disk the Media ID will be returned and slice ignored.
;--------------------------------------------------------------------------------------------------
;   ENTRY:
;     B: FUNCTION       0xE0   EXT_SLICE
;     D: DISK UNIT, preferably for a hard disk.
;     E: SLICE, ignored if media is not a hard disk
;   RETURNS:
;     A: STATUS, -6 (parameter out of range) - if Slice is invalid
;	Other errors Include ERR_NOUNIT, ERR_NOMEDIA,
;	If any error is raised the other return values are undefined
;     B: DEVICE ATTRIBUTES, as reported by DIODEVICE
;     C: MEDIAID, including MID_HDNEW if hd1k partition is found
;     DEHL: LBAOFFSET, of Slice if valid, 0 otherwise.
;--------------------------------------------------------------------------------------------------
;
SLICE_SLICE	.DB	0		; SLICE ARGUMENT (E)
SLICE_UNIT	.DB	0		; UNIT ARGUMENT  (D)
SLICE_DEVATT	.DB	0		; DEVICE ATTRIBUTES
SLICE_MID	.DB	0		; DISCOVERED MEDIAID
;
SLICE_WRKSTA	.EQU	$
SLICE_LBAOFF	.FILL 4,0		; START OF PARTITION / SLICE (SECTORS)
SLICE_LBASIZ	.FILL 4,0		; SIZE OF MEDIA / PARTITION (SECTORS)
SLICE_SPS	.DW	0		; DISCOVERED SECTORS PER SLICE (16BIT)
SLICE_FND	.DB	0		; DID WE FIND A NON CPM PARTITION
SLICE_WRKSIZ	.EQU	$ - SLICE_WRKSTA ; SIZE OF WORKING DATA
;
EXT_SLICE:
	; SAVE CALL ARGUMENTS
	LD	(SLICE_SLICE),DE	; STORES DE -> SLICE/UNIT PARAMETERS

	; READ DEVICE INFORMATION USING DIODEVICE FUNCTION
	LD	A,(SLICE_UNIT)
	LD	C,A			; UNIT ID PARAMETER INTO C
	LD	B,BF_DIODEVICE		; DRIVER FUNCTION = DISK MEDIA
	CALL	DIO_DISPATCH		; CALL DIO TO GET DEVICE ATTRIBUTES
	RET	NZ			; ABORT ON ERROR
;
	; DEVICE ATTRIBUTES IN C
	LD	A,C
	LD	(SLICE_DEVATT),A	; STORE DEVICE ATTRIBUTES
;
	; DETERMINE MEDIA IN DRIVE USING DIOMEDIA FUNCTION
	LD	A,(SLICE_UNIT)
	LD	C,A			; UNIT ID PARAMETER INTO C
	LD	E,1			; ENABLE MEDIA CHECK/DISCOVERY
	LD	B,BF_DIOMEDIA		; DRIVER FUNCTION = DISK MEDIA
	CALL	DIO_DISPATCH		; CALL DIO TO GET MEDIAID (RESULT IN E)
	RET	NZ			; ABORT ON ERROR
;
	; CHECK MEDIA TYPE, ONLY HD IS APPLICABLE
	LD	A,E			; RESULTANT MEDIA ID TO ACCUM
	LD	(SLICE_MID),A		; INIT VALUE, MAY BE USED LATER
	OR	A			; SET FLAGS
	JR	Z,EXT_SLICE1A		; BAIL IF NO MEDIA
	CP	MID_HD			; IS IT A HARD DISK
	JR	Z,EXT_SLICE1B		; IS HD, CONTINUE TO PROCESS HD
;
	; NOT A HARD DISK, CHECK SLICE = 0
	LD	A,(SLICE_SLICE)		; GET THE SLICE
	OR	A			; SET FLAGS
	JP	NZ,EXT_SLICE5C		; SLICE NOT ZERO - SIGNAL ERROR AND RETURN
;
EXT_SLICE1A:
	; RETURN MEDIA ID (NOT HD) WITH SUCCESS
	LD	DE,0			; LBA VALUE WILL BE ZERO
	LD	HL,0
	JP	EXT_SLICE6A		; RETURN SUCCESS
;
EXT_SLICE1B:
	; FOUND HD, NEED TO PROCESS MBR / PART TABLE
	; CLEAR WORKING STORAGE
	LD	HL,SLICE_WRKSTA		; HL SET TO FIRST BYTE
	LD	(HL),0			; CLEAR FIRST BYTE
	LD	D,H
	LD	E,L
	INC	DE			; DE SET TO SECOND BYTE
	LD	BC,SLICE_WRKSIZ - 1	; NUMBER OF LDIR COPIES
	LDIR				; BLOCK COPY TO CLEAR WORKING STORAGE
;
	; SEEK TO SECTOR ZERO (MBR TABLE)
	LD	B,BF_DIOSEEK		; SEEK FUNCTION
	LD	A,(SLICE_UNIT)		; GET UNIT
	LD	C,A			; PUT IN C
	LD	DE,$8000		; LBA SECTOR ZERO
	LD	HL,0			; ASSUME LBA ACCESS FOR NOW
	CALL	DIO_DISPATCH		; DO IT
	RET	NZ			; ABORT ON ERROR
;
	; READ SECTOR ZERO (MBR TABLE)
	LD	B,BF_DIOREAD		; READ FUNCTION
	LD	A,(SLICE_UNIT)		; GET UNIT
	LD	C,A			; PUT IN C
	LD	A,(HB_CURBNK)		; GET CURRENT BANK ID
	LD	D,A			; PUT IN D
	LD	E,1			; SECTOR COUNT
	LD	HL,HB_WRKBUF		; IO BUFFER TO USE
	CALL	DIO_DISPATCH		; DO IT
	RET	NZ			; ABORT ON ERROR
;
	; CHECK MBR OF DISK TO SEE IF IT HAS A PARTITION TABLE.
	LD	HL,HB_WRKBUF		; DSKBUF ADR
	LD	DE,$1FE			; OFFSET TO SIGNATURE
	ADD	HL,DE			; POINT TO SIGNATURE
	LD	A,(HL)			; GET FIRST BYTE
	CP	$55			; CHECK FIRST BYTE
	JR	NZ,EXT_SLICE3C		; NO MATCH, NO PART TABLE
	INC	HL			; NEXT BYTE
	LD	A,(HL)			; GET SECOND BYTE
	CP	$AA			; CHECK SECOND BYTE
	JR	NZ,EXT_SLICE3C		; NO MATCH, NO PART TABLE
;
	; FOUND PARTITION TABLE - LOOP AND PROCESS PARTITION TABLE
	LD	HL,HB_WRKBUF		; DSKBUF ADR
	LD	DE,$1BE+4		; OFFSET OF FIRST ENTRY PART TYPE
	ADD	HL,DE			; POINT TO IT
	LD	B,4			; FOUR ENTRIES IN PART TABLE LOOP
EXT_SLICE2A:
	LD	A,(HL)			; GET PART TYPE
	LD	DE,4
	ADD	HL,DE			; MOVE HL FWD TO GET TO LBA OFFSET
	CP	$2E			; CP/M PARTITION?
	JR	Z,EXT_SLICE3B		; HD1K, GRAB THE LBA OFFSET
	CP	$00			; IS ANOTHER PARTITION TYPE, NOT CPM
	JR	NZ,EXT_SLICE3A		; OTHER VALID PART TYPE
EXT_SLICE2B:
	LD	DE,12			; REMAINING SIZE TO GET TO NEXT PARTITION
EXT_SLICE2C:
	ADD	HL,DE			; BUMP TO NEXT PARTITION ENTRY - TYPE
	DJNZ	EXT_SLICE2A		; LOOP THRU TABLE
	JR	EXT_SLICE3C		; READ ALL - NO CP/M PARTITION FOUND
;
EXT_SLICE3A
	; FOUND OTHER (NOT CPM) PARTITION
	LD	A,(SLICE_FND)		; HAVE WE ALREADY FOUND PROCESSED NON CPM
	OR	A			; PARTITION, AND CAPTURED ITS START SECTOR, SO
	JR	NZ,EXT_SLICE2B		; IGNORE AND CONTINUTE TO NEXT PARTITION ENTRY
;
; NOTE THERE SLIGHT ISSUE HERE THAT WE ONLY CONSIDER THE FIRST NON-CPM PARTITION
; TO GET THE UPPER SIZE OF MEDIA, IDEALLY WE WOULD CONSIDER ALL, AND TAKE THE LOWEWST
; STARTING SECTOR - THIS IS A COMPRIMISE - OUT OF SEQUENCE PARTITIONS ARE UNLIKELY.
;
	PUSH	BC			; SAVE IT, BEING USED IN PARTITION LOOP
        LD      BC,4  			; IF NOT ALREADY SET - COPY 4 BYTES
        LD      DE,SLICE_LBASIZ		; FROM PARTION LBA OFFSET (HL) - TO WORKING LBA SIZE (DE)
        LDIR    			; COPY 4 BYTES
        POP	BC			; RESTORE
;
	LD	A,$FF
	LD	(SLICE_FND),A		; SET FOUND FLAG, SO DONT PROCESS ANY OTHERS
	LD	DE,8			; AND INC HL BY 8 TO GET TO NEXT PARITION
	JR	EXT_SLICE2C		; CONTINUE TO NEXT PARTITION
;
EXT_SLICE3B:
	; FOUND CP/M (HD1K) PARTITION - RECORD THIS
	LD	A,MID_HDNEW		; DISCOVERED HD1K MEDIA
	LD	(SLICE_MID),A		; STORE IT
	LD	BC,SPS_HD1K		; DISCOVERED HD1K MEDIA
	LD	(SLICE_SPS),BC		; STORE IT
;
	; CAPTURE THE LBA OFFSET AND SECTOR COUNT FROM PARTITION
	; HL POINTS TO PART LBA OFFSET FIELD OF PART ENTRY
	LD	DE,SLICE_LBAOFF		; LOC TO STORE OFFSET AND SIZE
	LD	BC,8			; 8 BYTES - LBA OFFSET AND SIZE
	LDIR				; COPY IT
;
	JR	EXT_SLICE4A		; CONTINUE AND COMPUTE THE SLICE
;
EXT_SLICE3C:
	; NO PARTITION TABLE FOUND / NO CPM PARTITION
	LD	A,(SLICE_SLICE)		; IF SLICE = 0, WE BOOT THE DISK ITSELF. IGNORE SLICE(S)
	OR      A           		; SET FLAGS FOR SLICE ARGUMENT, IF SLICE==0
	JR	Z,EXT_SLICE5Z		; BYPASS ALL CALCS / CHECKS - JUST BOOT THE DISK

	; BOOT SLICE WITH LEGACY SPS
	LD	BC,SPS_HD512		; WITH HD512 SECTORS PER SLICE
	LD	(SLICE_SPS),BC		; STORE IT

	; DID WE FIND AN OTHER (NOT CPM) PARTITION
	LD	A,(SLICE_FND)		; HAVE WE ALREADY FOUND PROCESSED NON CPM
	OR	A			; PARTITION, AND CAPTURED ITS START SECTOR, SO
	JR	NZ,EXT_SLICE4A		; MEDIA SIZE IS KNOWN BASED ON START OF PARTITION

	; FIND THE PHYSICAL CAPCITY OF THE MEDIA CALL (DIOCAP)
	LD      B,BF_DIOCAP    		; HBIOS FUNC: TO GET DISK LBA CAPACITY
        LD      A,(SLICE_UNIT)
	LD      C,A            		; PUT DISK UNIT IN C FOR FUNC CALL
	CALL	DIO_DISPATCH		; DO IT - RETURNS SIZE in DE:HL
	RET	NZ			; ABORT ON ERROR

	; UPDATE LBA SIZE FROM MEDIA SIZE
	LD	(SLICE_LBASIZ),HL	; LOWER ORDER BYTES - HL
	EX	DE,HL
	LD	(SLICE_LBASIZ+2),HL	; HIGHER ORDER BYTES - DE
;
EXT_SLICE4A:
	; COMPUTE THE START SECTOR (RELATIVE) FOR SLICE -> DE:HL
	LD      HL,0			; STARTING SECTOR NUMBER
	LD      DE,0			; ASSUMING A 0 OFFSET, SO CAN COMPARE TO SIZE
	LD	BC,(SLICE_SPS)
	LD	A,(SLICE_SLICE)
	OR      A           		; SLICE NUMBER - SET FLAGS TO CHECK LOOP CTR
	JR      Z,EXT_SLICE5A 		; NOTHING TO COUNT
EXT_SLICE4B:
	ADD     HL,BC       		; ADD ONE SLICE (SPS) TO LOW WORD
	JR      NC,EXT_SLICE4C 		; CHECK FOR CARRY
	INC     DE          		; IF SO, BUMP HIGH WORD
EXT_SLICE4C:
	DEC     A           		; DEC LOOP (SLICE) COUNTER
	JR      NZ,EXT_SLICE4B		; AND LOOP
;
EXT_SLICE5A:
	; DE:HL NOW CONTAINS THE STARTING SECTOR FOR SLICE
	PUSH    HL			; SAVE THE SECTOR OFFSET (SPS * SLICE NUMBER)
	PUSH    DE
;
	ADD     HL,BC			; ADD SPS, GET REQUIRED CAPCITY (UPPER SECTOR)
	JR      NC,EXT_SLICE5B
	INC     DE
EXT_SLICE5B:
	; DEHL HAS THE REQUIRED NUMBER OF SECTORS (ON MEDIA) FOR THE SLICE
	PUSH	DE			; SAVE DSK_REQ (MSW)
	PUSH	HL			; SAVE DSK_REQ (LSW)
;
	; CHECK DSK_CAPACITY >= CAP_REQUIRED, CF SET ON OVERFLOW
	; NO NEED SAVE ACTUAL RESULT
	OR	A			; CLEAR CARRY FOR SBC
	LD	HL,(SLICE_LBASIZ+0)	; CAPACITY LSW
	POP	DE			; REQUIRED LSW
	SBC	HL,DE			; CAPACITY - REQUIRED (LSW)
	LD	HL,(SLICE_LBASIZ+2)	; CAPAITY MSW
	POP	DE			; REQUIRED MSW
	SBC	HL,DE			; CAPACITY - REQUIRED (MSW)
;
	; POP STARTING OFSETT SECTOR
	POP     DE
	POP     HL
;
	; REQUIRE - CAPACITY -> GENERATES BORROW IF CAPITY > REQUIREMENT
	JR      NC,EXT_SLICE6 		; IF WE HAVE ENOUGH CAPACITY
;
EXT_SLICE5C:
	; SLICE WONT FIT - STOP AND RETURN ERROR
	LD	DE,0
	LD	HL,0			; EMTY OFFSET IN DEHL
	LD	A,(SLICE_DEVATT)
	LD	B,A			; DEVICE ATTRIBUTES IN B
	LD	A,(SLICE_MID)
	LD	C,A			; RETURN MEDIA ID IN C
	LD	A,ERR_RANGE		; OTHERWISE SIGNAL NOT ENOUGH CAPACITY
	OR      A
	RET
;
EXT_SLICE5Z:
	LD      HL,0			; BOOT THE DISK TO LBA = 0
	LD      DE,0			;
;
EXT_SLICE6:
	; FINAL CALC AND RETURN SUCCESS
	; ADD PARTITION OFFSET (START) TO DEHL TO GET ABSOLUTE SLICE OFFSET
	LD      BC,(SLICE_LBAOFF+0)	; LSB OF LBA OFFSET
	ADD     HL,BC			; ADD LSB OFFSET
	EX      DE,HL			; FLIP DE INTO HL
	LD      BC,(SLICE_LBAOFF+2)	; MSB OF LBA OFFSET
	ADC     HL,BC			; ADD MSB
	EX      DE,HL			; FLIP BACK DE:HL AS SLICE OFFSET
;
EXT_SLICE6A:
	; SLICE FITS - RETURN CORRECTLY
	LD	A,(SLICE_DEVATT)
	LD	B,A			; DEVICE ATTRIBUTES IN B
	LD	A,(SLICE_MID)
	LD	C,A			; RETURN MEDIA ID IN C
	XOR     A			; CLEAR FLAGS
	RET				; RETUNING DE:HL AND C
;
;--------------------------------------------------------------------------------------------------
;
HB_EXTAPI_END	.EQU	$
;
;==================================================================================================
;   SYSTEM API FUNCTIONS
;==================================================================================================
;
HB_SYSAPI_BEG	.EQU	$
;
;--------------------------------------------------------------------------------------------------
; SYSTEM RESET
;--------------------------------------------------------------------------------------------------
;
; RESTART SYSTEM
; SUBFUNCTION IN C
;
SYS_RESET:
	LD	A,C			; GET REQUESTED SUB-FUNCTION
	CP	BF_SYSRES_INT
	JR	Z,SYS_RESINT
	CP	BF_SYSRES_WARM
	JR	Z,SYS_RESWARM
	CP	BF_SYSRES_COLD
	JR	Z,SYS_RESCOLD
	CP	BF_SYSRES_USER
	JR	Z,SYS_RESUSER
	SYSCHKERR(ERR_NOFUNC)
	RET
;
; SOFT RESET HBIOS, RELEASE HEAP MEMORY NOT USED BY HBIOS
;
SYS_RESINT:
;
	; RESET THE HEAP
	LD	HL,(HEAPCURB)		; GET HBIOS HEAP THRESHOLD
	LD	(CB_HEAPTOP),HL		; RESTORE HEAP TOP
;
	XOR	A
	RET
;
; GO BACK TO ROM BOOT LOADER
;
SYS_RESWARM:
;
	CALL	SYS_RESINT		; HBIOS INTERNAL RESET
;
#IF (MEMMGR == MM_Z280)
	JP	INITSYS4
#ELSE
	; PERFORM BANK CALL TO OS IMAGES BANK IN ROM
	LD	SP,HBX_LOC		; STACK JUST BELOW HBIOS PROXY
  #IFDEF APPBOOT
	LD	A,BID_AUX		; IF APPBOOT, CHAIN TO AUX BANK
  #ELSE
	LD	A,BID_IMG0		; ELSE CHAIN TO OS IMAGES BANK
  #ENDIF
	LD	IX,0			; ENTER AT ADDRESS 0
	CALL	HBX_BNKCALL		; GO THERE
	HALT				; WE SHOULD NEVER COME BACK!
#ENDIF
;
; RESTART SYSTEM AS THOUGH POWER HAD JUST BEEN TURNED ON
;
SYS_RESCOLD:
;
#IFDEF APPBOOT
	JP	HB_RESTART
#ELSE
;
; MAKE ROM BOOT BANK ACTIVE IN LOW SYS MEM
;
  #IF (MEMMGR == MM_Z280)
	; FOR Z280, NEED TO REMAP THE LOW 32K IN SYSTEM MODE AND
	; CONTINUE AT ADDRESS ZERO.  WE CANNOT RETURN HERE AFTER THE
	; BNKSEL IS DONE BECAUSE THE SYSTEM BANK HAS BEEN CHANGED!
	; SO, WE PRESET THE STACK TO CAUSE A JUMP TO THE RESTART
	; ADDRESS ON RETURN FROM THE BNKSEL.  SLICK, RIGHT?
	DI				; KILL INTERRUPTS
	LD	SP,HBX_LOC		; STACK IN HIGH MEMORY
	LD	HL,HB_RESTART		; RESUME AT RESTART ADDRESS
	PUSH	HL			; ... IS PRESET ON STACK
	LD	A,BID_BOOT		; BANK TO LAUNCH RESTART
	LD	B,$10			; FIRST SYS PDR
	JP	Z280_BNKSEL		; DO IT AND RESUME FROM STACK
  #ELSE
	; FOR OTHER THAN Z280, JUST DO AN INTERBANK CALL TO THE
	; RESTART ADDRESS.
	DI
	LD	SP,HBX_LOC		; STACK JUST BELOW HBIOS PROXY
	LD	A,BID_BOOT		; BANK TO LAUNCH RESTART
	LD	IX,HB_RESTART		; RESUME AT RESTART ADDRESS
	CALL	HB_BNKCALL		; DOES NOT RETURN
  #ENDIF
#ENDIF
;
; HOOK CALLED WHEN A USERLAND RESET IS INVOKED, TYPICALLY VIA A JUMP
; TO CPU ADDRESS $0000
;
; CREDIT TO PHILLIP STEVENS FOR SUGGESTING AND SIGNIFICANT CONTRIBUTIONS
; TO THE Z180 INVALID OPCODE TRAP ENHANCEMENT.
;
SYS_RESUSER:
;
#IF (CPUFAM == CPU_Z180)
;
; IF Z180 CPU, CHECK FOR INVALID OPCODE FLAG AND HANDLE IF SO
;
	IN0	A,(Z180_ITC)		; GET ITC REGISTER
	XOR	$80			; PRECLEAR TRAP BIT
	RET	M			; IF TRAP BIT NOT SET, DONE
;
	; HANDLE INVALID OPCODE
	DEC	HL			; BACK UP TO OPCODE START
	BIT	6,A			; CHECK UFO BIT (2 BYTE OPCODE)
	JR	Z,SYS_RESUSER1		; IF NOT, ALL SET
	DEC	HL			; OTHERWISE, BACK UP 1 MORE BYTE
;
SYS_RESUSER1:
	OUT0	(Z180_ITC),A		; SAVE IT
;
	CALL	PRTSTRD			; PRINT ERROR TAG
	.TEXT	"\r\n\r\n+++ INVALID Z180 OPCODE @$"
	CALL	PRTHEXWORDHL		; PRINT OPCODE ADDRESS
	PRTS("H:$")			; FORMATTING
;
	LD	B,8			; SHOW 8 BYTES
SYS_RESUSER2:
	PUSH	BC			; SAVE BC
	PUSH	HL			; SAVE HL
	LD	A,(HB_INVBNK)		; GET BYTE FROM INVOKING BANK
	LD	D,A			; PUT IN D
	CALL	SYS_PEEK		; PEEK TO GET BYTE VALUE
	LD	A,E			; PUT IN A
	CALL	PC_SPACE		; FORMATTING
	CALL	PRTHEXBYTE		; DISPLAY BYTE
	POP	HL			; RECOVER HL
	POP	BC			; RECOVER BC
	INC	HL			; NEXT BYTE
	DJNZ	SYS_RESUSER2		; LOOP TIL DONE
	JP	NEWLINE			; FORMATTING & EXIT
;
#ENDIF
;
	; RESET ACTIVE VIDEO DISPLAY ATTACHED TO EMULATOR
	CALL	TERM_RESET
;
	RET				; ELSE RETURN WITH USER RESET VECTOR IN HL
;
;--------------------------------------------------------------------------------------------------
; SYSTEM VERSION
;--------------------------------------------------------------------------------------------------
;
; GET THE CURRENT HBIOS VERSION
;   ON INPUT, C=0
;   RETURNS VERSION IN DE AS BCD
;     D: MAJOR VERION IN TOP 4 BITS, MINOR VERSION IN LOW 4 BITS
;     E: UPDATE VERION IN TOP 4 BITS, PATCH VERSION IN LOW 4 BITS
;     L: PLATFORM ID
;
SYS_VER:
	LD	DE,0 | (RMJ << 12) | (RMN << 8) | (RUP << 4) | RTP
	LD	L,PLATFORM
	XOR	A
	RET
;
;--------------------------------------------------------------------------------------------------
; SET BANK
;--------------------------------------------------------------------------------------------------
;
; SET ACTIVE MEMORY BANK AND RETURN PREVIOUSLY ACTIVE MEMORY BANK
;   NOTE THAT IT GOES INTO EFFECT AS HBIOS FUNCTION IS EXITED
;   HERE, WE JUST SET THE CURRENT BANK
;   CALLER MUST ESTABLISH UPPER MEMORY STACK BEFORE INVOKING THIS FUNCTION!
;
SYS_SETBNK:
#IF (MEMMGR == MM_Z280)
	; FOR Z280 MEMMGR, WE ARE IN SYSTEM MODE HERE, SO WE CAN UPDATE
	; THE USER MODE BANK WITHOUT IMPACTING THE RUNNING CODE.  IT
	; WILL TAKE EFFECT UPON RETURN TO USER MODE.
	LD	A,(HB_INVBNK)		; GET PREVIOUS BANK
	PUSH	AF			; SAVE IT
	LD	A,C			; NEW BANK TO A
	LD	(HB_INVBNK),A		; UPDATE INVBNK
	LD	B,$00			; FIRST USER PDR
	CALL	Z280_BNKSEL		; DO IT
	POP	AF			; RECOVER PREV BANK
	LD	C,A			; PREVIOUS BANK TO C
	XOR	A			; SIGNAL SUCCESS
	RET				; DONE
#ELSE
	PUSH	HL			; SAVE INCOMING HL
	LD	HL,HB_INVBNK		; POINT TO HBIOS INVOKE BANK ID ADDRESS
	LD	A,(HL)			; GET EXISTING BANK ID TO A
	LD	(HL),C			; UPDATE INVOKE BANK TO NEW BANK ID
	LD	C,A			; PUT PREVIOUS BANK ID IN C FOR RETURN
	POP	HL			; RESTORE ORIGINAL HL
	XOR	A			; SIGNAL SUCCESS
	RET				; DONE
#ENDIF
;
;--------------------------------------------------------------------------------------------------
; GET BANK
;--------------------------------------------------------------------------------------------------
;
SYS_GETBNK:
	LD	A,(HB_INVBNK)		; GET THE ACTIVE MEMORY BANK
	LD	C,A			; MOVE TO C
	XOR	A			; SIGNAL SUCCESS
	RET
;
;--------------------------------------------------------------------------------------------------
; SETUP INTERBANK COPY
;--------------------------------------------------------------------------------------------------
;
; SET BANKS AND LENGTH FOR INTERBANK MEMORY COPY (BNKCPY)
; ENTRY: E=SOURCE BANK ID
;	 D=DEST BANK ID
;	 HL=COPY LENGTH (IN BYTES)
;
SYS_SETCPY:
	LD	A,E
	LD	(HB_SRCBNK),A		; RECORD THE SOURCE BANK
	LD	A,D
	LD	(HB_DSTBNK),A		; RECORD THE DESTINATION BANK
	LD	(HB_CPYLEN),HL		; RECORD THE COPY LENGTH
	XOR	A
	RET
;
;--------------------------------------------------------------------------------------------------
; PERFORM INTERBANK COPY
;--------------------------------------------------------------------------------------------------
;
; PERFORM MEMORY COPY POTENTIALLY ACROSS BANKS
; ENTRY: HL=SOURCE ADDRESS
;	 DE=DESTINATION ADDRESS
; NOTE: SRC/DEST BANK & COPY LENGTH MUST BE SET VIA SETCPY
;
SYS_BNKCPY:
	PUSH	HL			; SAVE INCOMING HL
	LD	HL,(HB_CPYLEN)		; HL := COPY LEN (SAVED IN SETCPY)
	EX	(SP),HL			; RESTORE HL & SET (SP) TO COPY LEN
	POP	BC			; BC := COPY LEN
	CALL	HB_BNKCPY
	XOR	A
	RET
;
;--------------------------------------------------------------------------------------------------
; ALLOCATE HEAP SPACE
;--------------------------------------------------------------------------------------------------
;
; ALLOCATE HL BYTES OF MEMORY FROM HBIOS HEAP
; RETURNS POINTER TO ALLOCATED MEMORY IN HL
; ON SUCCESS RETURN A == 0, AND Z SET
; ON FAILURE A <> 0 AND NZ SET AND HL TRASHED
; ALL OTHER REGISTERS PRESERVED
;
SYS_ALLOC:
	JP	HB_ALLOC
;
;--------------------------------------------------------------------------------------------------
; FREE HEAP SPACE
;--------------------------------------------------------------------------------------------------
;
; FREE HEAP MEMORY BY SIMPLY RELEASING ALL
; MEMORY BEYOND POINTER IN HL.
; ON SUCCESS RETURN A == 0, AND Z SET
; ON FAILURE A <> 0 AND NZ SET AND HL TRASHED
; ALL OTHER REGISTERS PRESERVED
;
SYS_FREE:
	SYSCHKERR(ERR_NOTIMPL)		; NOT YET IMPLEMENTED
	RET
;
;--------------------------------------------------------------------------------------------------
; GET SYSTEM INFORMATION
;--------------------------------------------------------------------------------------------------
;
; GET SYSTEM INFORMATION
; ITEM TO RETURN INDICATED IN C
;
SYS_GET:
	LD	A,C			; GET REQUESTED SUB-FUNCTION
	CP	BF_SYSGET_CIOCNT
	JP	Z,SYS_GETCIOCNT
	CP	BF_SYSGET_CIOFN
	JP	Z,SYS_GETCIOFN
	CP	BF_SYSGET_DIOCNT
	JP	Z,SYS_GETDIOCNT
	CP	BF_SYSGET_DIOFN
	JP	Z,SYS_GETDIOFN
	CP	$12		; LEFT FOR BACKWRD COMPATABILITY
	JP	Z,EXT_SLICE	; FUNCTION MOVED TO TOP LEVEL $E0
	; REMOVING THE ABOVE CAUSED UPGRADE ISSUES FOR EARLY ADOPTERS
	; SINCE OS BOOT LOADERS DEPEND ON IT. WITHOUT CAN LEAVE OS
	; UNBOOTABLE AND MIGRATION HARDER - Oct 2024
	CP	BF_SYSGET_RTCCNT
	JP	Z,SYS_GETRTCCNT
	CP	BF_SYSGET_DSKYCNT
	JP	Z,SYS_GETDSKYCNT
	CP	BF_SYSGET_VDACNT
	JP	Z,SYS_GETVDACNT
	CP	BF_SYSGET_VDAFN
	JP	Z,SYS_GETVDAFN
	CP	BF_SYSGET_SNDCNT
	JP	Z, SYS_GETSNDCNT
	CP	BF_SYSGET_SNDFN
	JP	Z,SYS_GETSNDFN
	CP	BF_SYSGET_TIMER
	JP	Z,SYS_GETTIMER
	CP	BF_SYSGET_SECS
	JP	Z,SYS_GETSECS
	CP	BF_SYSGET_BOOTINFO
	JP	Z,SYS_GETBOOTINFO
	CP	BF_SYSGET_CPUINFO
	JP	Z,SYS_GETCPUINFO
	CP	BF_SYSGET_MEMINFO
	JP	Z,SYS_GETMEMINFO
	CP	BF_SYSGET_BNKINFO
	JP	Z,SYS_GETBNKINFO
	CP	BF_SYSGET_CPUSPD
	JP	Z,SYS_GETCPUSPD
	CP	BF_SYSGET_PANEL
	JP	Z,SYS_GETPANEL
	CP	BF_SYSGET_APPBNKS
	JP	Z,SYS_GETAPPBNKS
	SYSCHKERR(ERR_NOFUNC)		; SIGNAL ERROR
	RET
;
; GET SERIAL UNIT COUNT
;
SYS_GETCIOCNT:
	LD	A,(CIO_CNT)		; GET DEVICE COUNT (FIRST BYTE OF LIST)
	LD	E,A			; PUT IT IN E
	XOR	A			; SIGNALS SUCCESS
	RET
;
; GET SERIAL UNIT API FN ADR AND DATA ADR
;   ENTRY:
;     D: FUNCTION
;     E: UNIT
;   RETURNS:
;     HL: FUNCTION ADDRESS
;     DE: DATA BLOB ADDRESS
;
SYS_GETCIOFN:
	BIT	7,E			; CHECK FOR SPECIAL UNIT CODE
	CALL	NZ,SYS_GETCIOFN1	; IF SO, HANDLE IT
	LD	IY,CIO_TBL		; POINT TO UNIT TABLE
	JP	SYS_GETFN		; GO TO COMMON CODE
;
SYS_GETCIOFN1:
	LD	A,(CB_CONDEV)		; UNIT $80 -> CONSOLE UNIT
	LD	E,A			; REPLACE UNIT VALUE IN C
	RET				; AND BACK TO REGULAR FLOW
;
;
; GET DISK UNIT COUNT
;
SYS_GETDIOCNT:
	LD	A,(DIO_CNT)		; GET DEVICE COUNT (FIRST BYTE OF LIST)
	LD	E,A			; PUT IT IN E
	XOR	A			; SIGNALS SUCCESS
	RET
;
; GET DISK UNIT API FN ADR AND DATA ADR
;   ENTRY:
;     D: FUNCTION
;     E: UNIT
;   RETURNS:
;     HL: FUNCTION ADDRESS
;     DE: DATA BLOB ADDRESS
;
SYS_GETDIOFN:
	LD	IY,DIO_TBL		; POINT TO UNIT TABLE
	JP	SYS_GETFN		; GO TO COMMON CODE
;
; GET RTC UNIT COUNT
;
SYS_GETRTCCNT:
	LD	E,0			; ASSUME 0 RTC DEVICES
	LD	A,(RTC_DISPACT)		; IS RTC ACTIVE?
	OR	A			; SET FLAGS
	JR	Z,SYS_GETRTCCNT1	; IF NONE, DONE
	INC	E			; SET ONE DEVICE
SYS_GETRTCCNT1:
	XOR	A			; SIGNALS SUCCESS
	RET
;
; GET DSKY UNIT COUNT
;
SYS_GETDSKYCNT:
	LD	E,0			; ASSUME 0 RTC DEVICES
	LD	A,(DSKY_DISPACT)	; IS DSKY ACTIVE?
	OR	A			; SET FLAGS
	JR	Z,SYS_GETDSKYCNT1	; IF NONE, DONE
	INC	E			; SET ONE DEVICE
SYS_GETDSKYCNT1:
	XOR	A			; SIGNALS SUCCESS
	RET
;
; GET VIDEO UNIT COUNT
;
SYS_GETVDACNT:
	LD	A,(VDA_CNT)		; GET DEVICE COUNT (FIRST BYTE OF LIST)
	LD	E,A			; PUT IT IN E
	XOR	A			; SIGNALS SUCCESS
	RET
;
; GET VIDEO UNIT API FN ADR AND DATA ADR
;   ENTRY:
;     D: FUNCTION
;     E: UNIT
;   RETURNS:
;     HL: FUNCTION ADDRESS
;     DE: DATA BLOB ADDRESS
;
SYS_GETVDAFN:
	LD	IY,VDA_TBL		; POINT TO UNIT TABLE
	JP	SYS_GETFN		; GO TO COMMON CODE
;
; GET SOUND UNIT COUNT
;
SYS_GETSNDCNT:
	LD	A,(SND_CNT)		; GET DEVICE COUNT (FIRST BYTE OF LIST)
	LD	E,A			; PUT IT IN E
	XOR	A			; SIGNALS SUCCESS
	RET
;
; GET SOUND UNIT API FN ADR AND DATA ADR
;   ENTRY:
;     D: FUNCTION
;     E: UNIT
;   RETURNS:
;     HL: FUNCTION ADDRESS
;     DE: DATA BLOB ADDRESS
;
SYS_GETSNDFN:
	LD	IY,SND_TBL		; POINT TO UNIT TABLE
	JP	SYS_GETFN		; GO TO COMMON CODE
;
; SHARED CODE TO COMPLETE A FUNCTION LOOKUP
; ENTRY:
;   IY: DISPATCH FUNCTION TABLE
;   D: FUNCTION ID
;   E: UNIT NUMBER
; EXIT:
;   HL: DRIVER FUNCTION ADDRESS
;   DE: DRIVER UNIT DATA ADDRESS
;
SYS_GETFN:
	LD	A,D			; GET FUNC NUM FROM D
	LD	B,A			; AND PUT IN B
	LD	A,E			; GET UNIT NUM FROM E
	LD	C,A			; AND PUT IN C
	CALL	HB_DISPCALC		; CALC FN ADR & BLOB ADR
	PUSH	IY			; MOVE DATA ADR
	POP	DE			; ... TO DE
	RET				; AF STILL HAS RESULT OF CALC
;
#IF ((CPUFAM == CPU_EZ80) & (EZ80TIMER == EZ80TMR_FIRM))
; IMPLEMENTED IN EZ80DRV.ASM
;
#ELSE
;
; GET TIMER
;   RETURNS:
;     DE:HL: TIMER VALUE (32 BIT)
;
SYS_GETTIMER:
	LD	HL,HB_TICKS
	HB_DI
	CALL	LD32
	HB_EI
	LD	C, TICKFREQ
	XOR	A
	RET
#ENDIF
;
#IF ((CPUFAM == CPU_EZ80) & (EZ80TIMER == EZ80TMR_FIRM))
; IMPLEMENTED IN EZ80DRV.ASM
;
#ELSE
;
; GET SECONDS
;   RETURNS:
;     DE:HL: SECONDS VALUE (32 BIT)
;     C: NUM TICKS WITHIN CURRENT SECOND
;
SYS_GETSECS:
	LD	HL,HB_SECS
	HB_DI
	CALL	LD32
	LD	A,(HB_SECTCK)
	HB_EI
	NEG				; CONVERT DOWNCOUNTER TO UPCOUNTER
	ADD	A,TICKFREQ
	LD	C,A
	XOR	A
	RET
#ENDIF
;
; GET BOOT INFORMATION
;   RETURNS:
;     L: BOOT BANK ID
;     DE: BOOT DISK VOLUME (UNIT/SLICE)
;
SYS_GETBOOTINFO:
	LD	A,(CB_BOOTBID)
	LD	L,A
	LD	DE,(CB_BOOTVOL)
	XOR	A
	RET
;
; GET CPU INFORMATION
;   RETURNS:
;     H: Z80 CPU VARIANT
;     L: CPU SPEED IN MHZ
;     DE: CPU SPEED IN KHZ
;
SYS_GETCPUINFO:
	LD	A,(HB_CPUTYPE)
	LD	H,A
	LD	A,(CB_CPUMHZ)
	LD	L,A
	LD	DE,(CB_CPUKHZ)
	LD	BC,(HB_CPUOSC)
	XOR	A
	RET
;
; GET MEMORY INFORMATION
;   RETURNS:
;     D: COUNT OF ROM BANKS
;     E: COUNT OF RAM BANKS
;
SYS_GETMEMINFO:
	LD	A,(CB_ROMBANKS)
	LD	D,A
	LD	A,(CB_RAMBANKS)
	LD	E,A
	XOR	A
	RET
;
; GET BANK CONFIGURATION INFORMATION
;   RETURNS:
;     D: HBIOS BANK ID
;     E: USER BANK ID
;     H: FIRST APP BANK ID
;     L: APP BANK COUNT
;
SYS_GETBNKINFO:
	LD	A,(CB_BIDBIOS)
	LD	D,A
	LD	A,(CB_BIDUSR)
	LD	E,A
	XOR	A
	RET
;
; GET SYSTEM CPU SPEED PERFORMANCE ATTRIBUTES
;   RETURNS:
;     L: CLOCK MULT (0:HALF, 1:FULL, 2: DOUBLE)
;     D: MEMORY WAIT STATES
;     E: I/O WAIT STATES
;
SYS_GETCPUSPD:
;
#IF (((PLATFORM == PLT_SBC) | (PLATFORM == PLT_MBC)) & (CPUSPDCAP==SPD_HILO))
	LD	A,(HB_RTCVAL)
  #IF (PLATFORM == PLT_SBC)
	XOR	%00001000		; SBC SPEED BIT IS INVERTED
  #ENDIF
	BIT	3,A
	LD	L,0			; ASSUME HALF SPEED
	JR	Z,SYS_GETCPUSPD1
	LD	L,1
SYS_GETCPUSPD1:
	LD	DE,$FFFF		; UNKNOWN WAIT STATES
;
	XOR	A
	RET
#ENDIF
;
#IF (PLATFORM == PLT_HEATH)
	LD	A,(H8P_SPEED)		; GET HEATH SPEED BITS SHADOW
	XOR	$03			; CONVERT TO HBIOS VALUE
	LD	L,A			; PUT IN L FOR RETURN
	LD	DE,$FFFF		; UNKNOWN WAIT STATES
;
	XOR	A
	RET
#ENDIF
;
#IF (CPUFAM == CPU_Z180)
	LD	HL,0			; INIT CPU SPEED TO HALF
	LD	A,(HB_CPUTYPE)		; LOAD CPUTYPE
	CP	2			; S-CLASS OR ABOVE?
	JR	C,SYS_GETCPUSPD1	; IF NOT, NO CCR/CMR
;
	; GET CCR BIT
	IN0	A,(Z180_CCR)		; GET CLOCK CONTROL
	RLCA				; ROTATE BIT TO BIT 0
	AND	%00000001		; ISOLATE IT
	LD	L,A			; SAVE IN L
;
	LD	A,(HB_CPUTYPE)		; LOAD CPUTYPE
	CP	3			; REV. N?
	JR	C,SYS_GETCPUSPD1	; IF NOT, NO CMR
;
	; GET CMR BIT
	IN0	A,(Z180_CMR)		; GET CLOCK MULTIPLIER
	RLCA				; ROTATE BIT TO BIT 0
	AND	%00000001		; ISOLATE IT
	LD	H,A			; SAVE IN H
;
SYS_GETCPUSPD1:
	; CALC FINAL MULTIPLIER TO L
	XOR	A			; CLEAR ACCUM
	ADD	A,H			; ADD IN CMR BIT
	ADD	A,L			; ADD IN CCR BIT
	LD	L,A			; SAVE RESULT IN L
	; DCNTL = MMII????
	IN0	A,(Z180_DCNTL)		; GET WAIT STATES
	RLCA				; ROTATE MEM WS BITS
	RLCA				; ... TO LOW BITS
	PUSH	AF			; SAVE FOR NOW
	AND	%00000011		; ISOLATE BITS
	LD	D,A			; PUT IN D
	POP	AF			; RECOVER A
	RLCA				; ROTATE I/O WS BITS
	RLCA				; ... TO LOW BITS
	AND	%00000011		; ISOLATE BITS
	INC	A			; ADD 1 FOR BUILT-IN WS
	LD	E,A			; PUT IN E
;
	XOR	A
	RET
#ENDIF
;
	OR	$FF
	RET
;
; GET FRONT PANEL SWITCH VALUES BYTE
;   RETURNS:
;     L: SWITCH VALUES BYTE
;
SYS_GETPANEL:
;
#IF (FPSW_ENABLE)
	LD	A,(FPSW_ACTIVE)		; FP SWITCHES ACTIVE?
	OR	A			; SET FLAGS
	JR	Z,SYS_GETPANEL1		; HANDLE NOT EXISTS
	CALL	FP_GETSWITCHES		; READ SWITCHES
	LD	H,0			; FOR FUTURE
	LD	L,A			; PUT SWITCHES VALUE IN L
	XOR	A			; SIGNAL SUCCESS
	RET				; DONE
#ENDIF
SYS_GETPANEL1:	; HANDLE NON-EXISTENT FRONT PANEL
	LD	HL,0			; ZERO RESULT VALUE
	LD	A,ERR_NOHW		; NO HARDWARE ERR
	OR	A			; SET FLAGS
	RET				; DONE
;
; GET APPLICATION BANK INFORMATION
;   RETURNS:
;     H: FIRST APP BANK ID
;     L: APP BANK COUNT
;     E: BANK SIZE (IN 256-BYTE PAGES)
;
SYS_GETAPPBNKS:
	LD	A,(CB_BIDAPP0)		; FIRST BANK ID
	LD	H,A
	LD	A,(CB_APP_BNKS)		; NUMBER OF BANKS
	LD	L,A
	LD	E,$80			; (256 * $80) = 32KB
;
	XOR	A
	RET
;
;--------------------------------------------------------------------------------------------------
; SET SYSTEM PARAMETERS
;--------------------------------------------------------------------------------------------------
;
; SET SYSTEM PARAMETERS
; PARAMETER(S) TO SET INDICATED IN C
;
SYS_SET:
	LD	A,C			; GET REQUESTED SUB-FUNCTION
	CP	BF_SYSSET_TIMER
	JP	Z,SYS_SETTIMER
	CP	BF_SYSSET_SECS
	JP	Z,SYS_SETSECS
	CP	BF_SYSSET_BOOTINFO
	JP	Z,SYS_SETBOOTINFO
	CP	BF_SYSSET_CPUSPD
	JP	Z,SYS_SETCPUSPD
	CP	BF_SYSSET_PANEL
	JP	Z,SYS_SETPANEL
	SYSCHKERR(ERR_NOFUNC)		; SIGNAL ERROR
	RET
;
; SET BOOT INFORMATION
;   ON ENTRY:
;     L: BOOT BANK ID
;     DE: BOOT DISK VOLUME (UNIT/SLICE)
;
SYS_SETBOOTINFO:
	LD	A,L
	LD	(CB_BOOTBID),A
	LD	(CB_BOOTVOL),DE
	XOR	A
	RET
;
#IF ((CPUFAM == CPU_EZ80) & (EZ80TIMER == EZ80TMR_FIRM))
; IMPLEMENTED IN EZ80DRV.ASM
;
#ELSE
;
; SET TIMER
;   ON ENTRY:
;     DE:HL: TIMER VALUE (32 BIT)
;
SYS_SETTIMER:
	LD	BC,HB_TICKS
	HB_DI
	CALL	ST32
	HB_EI
	XOR	A
	RET
#ENDIF
#IF ((CPUFAM == CPU_EZ80) & (EZ80TIMER == EZ80TMR_FIRM))
; IMPLEMENTED IN EZ80DRV.ASM
;
#ELSE
;
; SET SECS
;   ON ENTRY:
;     DE:HL: SECONDS VALUE (32 BIT)
;
SYS_SETSECS:
	LD	BC,HB_SECS
	HB_DI
	CALL	ST32
	HB_EI
	XOR	A
	RET
#ENDIF
;
; SET SYSTEM CPU SPEED ATTRIBUTES
;   ON ENTRY:
;     L: CLOCK MULT (0:HALF, 1:FULL, 2: DOUBLE, 3: QUAD)
;     D: MEMORY WAIT STATES
;     E: I/O WAIT STATES
;
SYS_SETCPUSPD:
;
#IF (((PLATFORM == PLT_SBC) | (PLATFORM == PLT_MBC)) & (CPUSPDCAP==SPD_HILO))
;
; NOTE: WAIT STATE SETTINGS ARE IGNORED FOR Z80
;
	LD	A,L			; CLK SPD TO ACCUM
	CP	$FF			; NO CHANGE?
	JR	Z,SYS_SETCPUSPD3	; DONE IF SO
	LD	C,%00000000		; HALF SPEED
	CP	0
	JR	Z,SYS_SETCPUSPD1
	LD	C,%00001000		; FULL SPEED
	CP	1
	JR	Z,SYS_SETCPUSPD1
	JR	SYS_SETCPUSPD_ERR	; SPD NOT SUPPORTED
SYS_SETCPUSPD1:
	LD	A,(HB_RTCVAL)
	AND	~%00001000		; CLEAR SPEED BIT
	OR	C			; IMPLEMENT NEW SPEED BIT
#IF (PLATFORM == PLT_SBC)
	; SBC SPEED BIT IS INVERTED, ADJUST IT
	LD	A,C
	XOR	%00001000
	LD	C,A
#ENDIF
	LD	(HB_RTCVAL),A		; SAVE IN SHADOW REGISTER
	OUT	(RTCIO),A		; UPDATE HARDWARE REGISTER
;
	; UPDATE THE CURRENT CPU SPEED IN HCB!
	LD	A,L
	LD	HL,(HB_CPUOSC)		; ASSUME FULL SPEED
	CP	1			; CHECK FOR 1 (FULL SPEED)
	JR	Z,SYS_SETCPUSPD2	; IF SO, ALL DONE
	; ADJUST HL TO REFLECT HALF SPEED OPERATION
	SRL	H			; ADJUST HL ASSUMING
	RR	L			; HALF SPEED OPERATION
;
SYS_SETCPUSPD2:
;
; HL SHOULD NOW HAVE FINAL CPU RUNNING SPEED IN KHZ.
; UPDATE CB_CPUMHZ/CB_CPUKHZ WITH THIS VALUE.
;
	LD	(CB_CPUKHZ),HL		; UPDATE CPUKHZ
	LD	DE,1000			; SET UP TO DIV BY 1000 FOR MHZ
	CALL	DIV16			; BC=CPU MHZ, HL=REMAINDER
	LD	DE,500			; SET UP TO ROUND UP
	XOR	A			; IF WITHIN 500 KHZ
	SBC	HL,DE			; REMAINDER - 500
	CCF				; COMPLEMENT CF
	ADC	A,C			; C -> A; ADD CF FOR ROUNDING
	LD	(CB_CPUMHZ),A		; SAVE IT
;
#IF (CPUFAM != CPU_EZ80)
	;  REINIT DELAY ROUTINE
	LD	A,(CB_CPUMHZ)		; CPU SPEED TO ACCUM AND INIT
	CALL	DELAY_INIT		; .. SPEED COMPENSATED DELAY
#ENDIF
;
SYS_SETCPUSPD3:
	XOR	A
	RET
#ENDIF
;
#IF (PLATFORM == PLT_HEATH)
	; PORT $30:
	;   0=16MHZ, 1=8MHZ, 2=4MHZ, 3=2MHZ
	LD	A,L			; REQUESTED SPEED TO ACCUM
	XOR	$03			; CONVERT TO HEATH BITS
	AND	$03			; ONLY 2 LS BITS
;
H8P_SETSPD:	; INVOKED BY H8P.ASM WHEN SPEED CHANGED VIA FRONT PANEL
	OUT	(H8P_SPDIO),A		; DO IT
	LD	(H8P_SPEED),A		; UPDATE FP SHADOW
;
	; UPDATE CPUKHZ/CPMHZ
	LD	HL,(HB_CPUOSC)		; START WITH OSC VALUE IN KHZ
	LD	B,A			; USE BITS FOR LOOP COUNT
	OR	A			; CHECK FOR ZERO
	JR	Z,SYS_SETCPUSPD2	; IF SO, SKIP ADJUSTMENT LOOP
SYS_SETCPUSPD1:
	SRL	H			; DIVIDE
	RR	L			; ... BY TWO
	DJNZ	SYS_SETCPUSPD1		; LOOP AS NEEDED
;
SYS_SETCPUSPD2:
;
; HL SHOULD NOW HAVE FINAL CPU RUNNING SPEED IN KHZ.
; UPDATE CB_CPUMHZ/CB_CPUKHZ WITH THIS VALUE.
;
	LD	(CB_CPUKHZ),HL		; UPDATE CPUKHZ
	LD	DE,1000			; SET UP TO DIV BY 1000 FOR MHZ
	CALL	DIV16			; BC=CPU MHZ, HL=REMAINDER
	LD	DE,500			; SET UP TO ROUND UP
	XOR	A			; IF WITHIN 500 KHZ
	SBC	HL,DE			; REMAINDER - 500
	CCF				; COMPLEMENT CF
	ADC	A,C			; C -> A; ADD CF FOR ROUNDING
	LD	(CB_CPUMHZ),A		; SAVE IT
;
#IF (CPUFAM != CPU_EZ80)
	;  REINIT DELAY ROUTINE
	LD	A,(CB_CPUMHZ)		; CPU SPEED TO ACCUM AND INIT
	CALL	DELAY_INIT		; .. SPEED COMPENSATED DELAY
#ENDIF
;
	XOR	A			; SIGNAL SUCCESS
	RET
#ENDIF
;
#IF (CPUFAM == CPU_Z180)
	; VERIFY THAT REQUESTED SETTINGS ARE ALLOWED BY HARDWARE
	LD	A,L			; GET SPEED REQUESTED
	CP	$FF			; NO CHANGE?
	JR	Z,SYS_SETCPUSPD0A	; SKIP CHECK
	LD	A,(HB_CPUTYPE)		; 1=ORIG, 2=REVK, 3=REVN
	INC	L			; 1=HALF,2=FULL,3=DOUBLE
	CP	L			; TOO HIGH FOR CPU TYPE?
	JP	C,SYS_SETCPUSPD_ERR	; CPU CAN'T DO SPD MULT
	DEC	L			; RESTORE ORIG REQUEST
SYS_SETCPUSPD0A:
	LD	A,D			; MEM WS
	CP	$FF			; NO CHANGE?
	JR	Z,SYS_SETCPUSPD0B	; SKIP CHECK
	CP	4			; TOO HIGH?
	JP	NC,SYS_SETCPUSPD_ERR	; >3 IS TOO HIGH
SYS_SETCPUSPD0B:
	LD	A,D			; I/O WS
	CP	$FF			; NO CHANGE?
	JR	Z,SYS_SETCPUSPD0C	; SKIP CHECK
	CP	4			; TOO HIGH?
	JP	NC,SYS_SETCPUSPD_ERR	; >3 IS TOO HIGH
SYS_SETCPUSPD0C:
;
	PUSH	DE			; SAVE WAIT STATES FOR NOW
	; BEFORE IMPLEMENTING THE NEW CPU SPEED, WE SWITCH THE
	; WAIT STATES TO MAXIMUM BECAUSE WE MAY BE IMPLEMENTING
	; SLOWER WAIT STATES REQUIRED BY THE NEW SPEED.  WE SAVE
	; THE ORIGINAL WAIT STATES REGISTER VALUE ON STACK
	IN0	A,(Z180_DCNTL)		; GET CURRENT REGISTER VALUE
	LD	E,A			; PUT IN E
	PUSH	DE			; SAVE FOR LATER
	OR	%11110000		; MAX WAIT STATES
	OUT0	(Z180_DCNTL),A		; DO IT
;
	LD	A,L			; NEW CLK SPD TO ACCUM
	CP	$FF			; NO CHANGE?
	JR	Z,SYS_SETCPUSPD2B	; IF SO, SKIP TO WAIT STATES
;
	LD	B,0			; B HAS BIT FOR CMR
	LD	C,0			; C HAS BIT FOR CCR
	CP	2			; DOUBLE SPEED?
	JR	C,SYS_SETCPUSPD1	; <2?, SKIP AHEAD
	LD	B,%10000000		; SET CMR BIT
SYS_SETCPUSPD1:
	CP	1			; FULL SPEED?
	JR	C,SYS_SETCPUSPD2	; <1?, SKIP AHEAD
	LD	C,%10000000		; SET CCR BIT
SYS_SETCPUSPD2:
;
	; IMPLEMENT THE NEW CPU SPEED
	IN0	A,(Z180_CMR)
	AND	~%10000000
	OR	B
	OUT0	(Z180_CMR),A
	IN0	A,(Z180_CCR)
	AND	~%10000000
	OR	C
	OUT0	(Z180_CCR),A
;
	; UPDATE THE CURRENT CPU SPEED IN HCB!
	LD	A,L			; SETTING TO A
	LD	HL,(HB_CPUOSC)		; START WITH CPU OSC VALUE
	; ADJUST HL TO REFLECT HALF SPEED OPERATION
	SRL	H			; ADJUST HL ASSUMING
	RR	L			; HALF SPEED OPERATION
	OR	A			; CHECK FOR HALF SPEED
	JR	Z,SETCPUSPD2A		; IF SO, DONE
	; ADJUST HL TO REFLECT FULL SPEED OPERATION
	SLA	L
	RL	H
	CP	1			; CHECK FOR FULL SPEED
	JR	Z,SETCPUSPD2A		; IF SO DONE
	; ADJUST HL TO REFLECT DOUBLE SPEED OPERATION
	SLA	L
	RL	H
;
SETCPUSPD2A:
;
; HL SHOULD NOW HAVE FINAL CPU RUNNING SPEED IN KHZ.
; UPDATE CB_CPUMHZ/CB_CPUKHZ WITH THIS VALUE.
;
	LD	(CB_CPUKHZ),HL		; UPDATE CPUKHZ
	LD	DE,1000			; SET UP TO DIV BY 1000 FOR MHZ
	CALL	DIV16			; BC=CPU MHZ, HL=REMAINDER
	LD	DE,500			; SET UP TO ROUND UP
	XOR	A			; IF WITHIN 500 KHZ
	SBC	HL,DE			; REMAINDER - 500
	CCF				; COMPLEMENT CF
	ADC	A,C			; C -> A; ADD CF FOR ROUNDING
	LD	(CB_CPUMHZ),A		; SAVE IT
;
SYS_SETCPUSPD2B:
	; NOW IMPLEMENT ANY WAIT STATE CHANGES.
	POP	HL			; INIT L WITH ORIG DCNTL VALUE
	POP	DE			; RECOVER WAIT STATES
	LD	A,D			; GET MEM WS
	CP	$FF			; SKIP?
	JR	Z,SYS_SETCPUSPD3	; IF SO, GO AHEAD
	AND	%00000011		; JUST TWO BITS
	RRCA				; MEM WS IS TOP TWO BITS
	RRCA
	LD	H,A			; MOVE WS BITS TO H
	LD	A,L			; CUR VALUE TO A
	AND	%00111111		; MASK OFF MEM WS BITS
	OR	H			; SET NEW MEM WS BITS
	LD	L,A			; BACK TO L
;
SYS_SETCPUSPD3:
;
	LD	A,E			; GET I/O WS
	CP	$FF			; SKIP?
	JR	Z,SYS_SETCPUSPD4	; IF SO, GO AHEAD
	DEC	A			; ADJUST FOR BUILT-IN I/O WS
	AND	%00000011		; JUST TWO BITS
	RRCA				; I/O WS IS BITS 5-4
	RRCA
	RRCA
	RRCA
	LD	H,A			; MOVE WS BITS TO H
	LD	A,L			; CUR VALUE TO A
	AND	%11001111		; MASK OFF I/O WS BITS
	OR	H			; SET NEW I/O WS BITS
	LD	L,A			; BACK TO L
;
SYS_SETCPUSPD4:
	LD	A,L			; WORKING VALUE TO A
	OUT0	(Z180_DCNTL),A		; IMPLEMENT NEW VALUE
;
#IF (CPUFAM != CPU_EZ80)
	;  REINIT DELAY ROUTINE
	LD	A,(CB_CPUMHZ)		; CPU SPEED TO ACCUM AND INIT
	CALL	DELAY_INIT		; .. SPEED COMPENSATED DELAY
#ENDIF
;
  #IF ((INTMODE == 2) & (Z180_TIMER))
	; THE Z180 TIMER IS BASED ON CPU SPEED.  SO HERE
	; WE RECOMPUTE THE TIMER CONSTANTS BASED ON THE NEW SPEED.
	XOR	A			; ALL BITS ZERO
	OUT0	(Z180_TCR),A		; ... INHIBITS TIMER OPERATION
	LD	HL,(CB_CPUKHZ)		; 50HZ = 18432000 / 20 / 50 / X, SO X = CPU KHZ
	OUT0	(Z180_TMDR0L),L		; INITIALIZE TIMER 0 DATA REGISTER
	OUT0	(Z180_TMDR0H),H
	DEC	HL			; RELOAD OCCURS *AFTER* ZERO
	OUT0	(Z180_RLDR0L),L		; INITIALIZE TIMER 0 RELOAD REGISTER
	OUT0	(Z180_RLDR0H),H
	LD	A,%00010001		; ENABLE TIMER0 INT AND DOWN COUNTING
	OUT0	(Z180_TCR),A
  #ENDIF
;
  #IF (ASCIENABLE)
	; RESET THE ASCI PORTS IN CASE SPEED CHANGED!
	; N.B., THIS WILL FAIL IF THE CURRENT BAUD RATE
	; IS IMPOSSIBLE TO IMPLEMENT AT THE NEW CPU SPEED!!!
	LD	DE,-1
	LD	IY,ASCI0_CFG
	CALL	ASCI_INITDEV
	LD	DE,-1
	LD	IY,ASCI1_CFG
	CALL	ASCI_INITDEV
  #ENDIF
;
	XOR	A
	RET
#ENDIF
;
SYS_SETCPUSPD_ERR:
	OR	$FF			; NOT SUPPORTED
	RET
;
; SET FRONT PANEL LEDS
;   ON ENTRY:
;     L: LED VALUES BYTE
;
SYS_SETPANEL:
;
#IF (FPLED_ENABLE)
	LD	A,L
	CALL	FP_SETLEDS
	XOR	A
	RET
#ELSE
	LD	A,ERR_NOHW		; NO HARDWARE ERR
	OR	A			; SET FLAGS
	RET
#ENDIF
;
;--------------------------------------------------------------------------------------------------
; INTERBANK MEMORY PEEK
;--------------------------------------------------------------------------------------------------
;
; RETURN A BYTE OF MEMORY FROM SPECIFIED BANK
; ENTRY: D=BANK ID, HL=ADDRESS
; RETURN: E=BYTE VALUE
;
; IF WE ARE USING INTERRUPT MODE 1, WE NEED TO PREVENT INTERRUPTS
; BECAUSE THE LOW MEMORY BANK CONTAINING THE IM1 VECTOR WILL PROBABLY
; GET BANKED OUT DURING THE PEEK PROCESSING.
;
SYS_PEEK:
#IF (INTMODE == 1)
  #IF (CPUFAM == CPU_Z280)
	PUSH	IY
	LD	C,Z280_MSR
	LDCTL	IY,(C)
	PUSH	IY
	HB_DI
  #ELSE
	LD	A,I			; SAVE THE INTERRUPT STATUS
	DI         			;  COPY IFF2 TO P/V FLAG
	PUSH	AF
  #ENDIF
#ENDIF
	CALL	HBX_PEEK		; IMPLEMENTED IN PROXY
#IF (INTMODE == 1)
  #IF (CPUFAM == CPU_Z280)
	LD	C,Z280_MSR
	POP	IY
	LDCTL	(C),IY
	POP	IY
  #ELSE
	POP	AF			; RECALL INITIAL INTERRUPT STATUS
	JP	PO,$+4			; RETURN TO INITIAL STATE
	EI				; *** DO NOT USE HB_EI HERE ***
  #ENDIF
#ENDIF
	XOR	A
	RET
;
;--------------------------------------------------------------------------------------------------
; INTERBANK MEMORY POKE
;--------------------------------------------------------------------------------------------------
;
; WRITE A BYTE OF MEMORY TO SPECIFIED BANK
; ENTRY: D=BANK ID, HL=ADDRESS IN HBIOS BANK, E=BYTE VALUE
;
; IF WE ARE USING INTERRUPT MODE 1, WE NEED TO PREVENT INTERRUPTS
; BECAUSE THE LOW MEMORY BANK CONTAINING THE IM1 VECTOR WILL PROBABLY
; GET BANKED OUT DURING THE POKE PROCESSING.
;
SYS_POKE:
#IF (INTMODE == 1)
  #IF (CPUFAM == CPU_Z280)
	PUSH	IY
	LD	C,Z280_MSR
	LDCTL	IY,(C)
	PUSH	IY
	HB_DI
  #ELSE
	LD	A,I			; SAVE THE INTERRUPT STATUS
	HB_DI      			;  COPY IFF2 TO P/V FLAG
	PUSH	AF
  #ENDIF
#ENDIF
	CALL	HBX_POKE		; IMPLEMENTED IN PROXY
#IF (INTMODE == 1)
  #IF (CPUFAM == CPU_Z280)
	LD	C,Z280_MSR
	POP	IY
	LDCTL	(C),IY
	POP	IY
  #ELSE
	POP	AF			; RECALL INITIAL INTERRUPT STATUS
	JP	PO,$+4			; RETURN TO INITIAL STATE
	EI				; *** DO NOT USE HB_EI HERE ***
  #ENDIF
#ENDIF
	XOR	A
	RET
;
;--------------------------------------------------------------------------------------------------
; INTERRUPT MANAGEMENT FUNCTIONS
;--------------------------------------------------------------------------------------------------
;
; INTERRUPT MANAGEMENT FUNCTIONS
; SUBFUNCTION IN C
;
SYS_INT:
	LD	A,C			; GET REQUESTED SUB-FUNCTION
	CP	BF_SYSINT_INFO
	JR	Z,SYS_INTINFO
	CP	BF_SYSINT_GET
	JR	Z,SYS_INTGET
	CP	BF_SYSINT_SET
	JR	Z,SYS_INTSET
	SYSCHKERR(ERR_NOFUNC)		; SIGNAL ERROR
	RET
;
; GET INTERRUPT SYSTEM INFORMATION
; RETURN D:=INTERRUPT MODE, E:=INT VEC TABLE SIZE
;
SYS_INTINFO:
	LD	D,INTMODE		; D := ACTIVE INTERRUPT MODE
#IF (INTMODE == 0)
	LD	E,0			; 0 ENTRIES IF INTERRUPTS DISABLED
#ENDIF
#IF (INTMODE == 1)
	LD	A,(HB_IM1CNT)		; RETURN IM1 CALL LIST SIZE
	LD	E,A
#ENDIF
#IF ((INTMODE == 2) | (INTMODE == 3))
	LD	E,HBX_IVTCNT		; RETURN INT VEC TABLE SIZE
#ENDIF
	XOR	A			; INDICATE SUCCESS
	RET				; AND DONE
;
; ROUTINE SHARED BY INT GET/SET.  RETURNS ADDRESS OF VECTOR FOR SPECIFIED LIST / TABLE
; POSITION.  ZF SET ON RETURN FOR SUCCESS, ELSE ERROR.
;
SYS_INTVECADR:
#IF (INTMODE == 0)
	SYSCHKERR(ERR_BADCFG)		; SIGNAL ERROR. INVALID FOR INT MODE 0
	RET
#ENDIF
#IF (INTMODE == 1)
	LD	A,(HB_IM1CNT)		; GET CURRENT ENTRY COUNT
	INC	A			; ALLOW FOR EXTRA ENTRY TO APPEND AT END
	LD	C,A			; SAVE IN C FOR COMPARE
#ENDIF
#IF ((INTMODE == 2) | (INTMODE == 3))
	LD	C,HBX_IVTCNT		; GET CURRENT ENTRY COUNT
#ENDIF
	LD	A,E			; INCOMING INDEX POSITION TO A
	CP	C			; COMPARE TO VECTOR COUNT
	JR	C,SYS_INTGET1		; CONTINUE IF POSITION IN RANGE
	SYSCHKERR(ERR_RANGE)		; ELSE ERROR
	RET
SYS_INTGET1:
	OR	A			; CLEAR CARRY
	RLA				; ADJUST FOR TABLE ENTRY
	RLA				; SIZE OF 4 BYTES
	INC	A			; BUMP TO ADR FIELD
	LD	H,0
	LD	L,A
#IF (INTMODE == 1)
	LD	DE,HB_IM1INT		; DE := START OF VECTOR TABLE
#ENDIF
#IF ((INTMODE == 2) | (INTMODE == 3))
	LD	DE,HB_IVT		; DE := START OF VECTOR TABLE
#ENDIF
	ADD	HL,DE			; HL := ADR OF VECTOR
	XOR	A			; INDICATE SUCCESS
	RET
;
; RETURN THE INTERRUPT VECTOR FOR A SPECIFIED POSITION IN THE INT VECTOR LIST / TABLE
; ENTRY: E=LIST/TABLE POSITION
; RETURN: HL=INTERRUPT VECTOR
;
SYS_INTGET:
	CALL	SYS_INTVECADR		; GET VECTOR ADDRESS
	RET	NZ			; BAIL OUT ON ERROR
	LD	A,(HL)			; DEREF HL TO GET VECTOR
	INC	HL
	LD	H,(HL)
	LD	L,A
	XOR	A			; SIGNAL SUCCESS
	RET				; DONE
;
; SET AN INTERRUPT VECTOR FOR A SPECIFIED POSITION IN THE INT VECTOR LIST / TABLE
; ENTRY: E=LIST/TABLE POSITION, HL=NEW INTERRUPT VECTOR
; RETURN: HL=PREVIOUS INTERRUPT VECTOR
;
SYS_INTSET:
	PUSH	HL			; SAVE NEW VECTOR
	CALL	SYS_INTVECADR		; GET VECTOR ADDRESS
	JR	Z,SYS_INTSET1		; CONTINUE IF OK
	POP	HL			; FIX STACK
	RET	NZ			; BAIL OUT ON ERROR
SYS_INTSET1:
	PUSH	HL			; SAVE VECTOR ADDRESS
	LD	A,(HL)			; DEREF HL TO GET PREV VECTOR
	INC	HL
	LD	H,(HL)
	LD	L,A
	EX	(SP),HL			; (SP) := PREV VEC, HL := VEC ADR
	POP	DE			; DE := PREV VEC
	POP	BC			; BC := NEW VEC
	LD	(HL),C			; SAVE LSB
	INC	HL
	LD	(HL),B			; SAVE MSB
	EX	DE,HL			; HL := PREV VEC
	XOR	A			; SIGNAL SUCCESS
	RET				; DONE
;
HB_SYSAPI_END	.EQU	$
;
;==================================================================================================
;   GLOBAL INTERNAL HBIOS FUNCTIONS
;==================================================================================================
;
HB_INTFUNC_BEG	.EQU	$
;
;--------------------------------------------------------------------------------------------------
; PRINT DECIMAL VALUE WITH 3 DIGIT MANTISSA
;--------------------------------------------------------------------------------------------------
;
; PRINT VALUE OF HL AS THOUSANDTHS, IE. 0.000
;
PRTD3M:
	PUSH	BC
	PUSH	DE
	PUSH	HL
	LD	E,'0'
	LD	BC,-10000
	CALL	PRTD3M1
	LD	E,0
	LD	BC,-1000
	CALL	PRTD3M1
	CALL	PC_PERIOD
	LD	BC,-100
	CALL	PRTD3M1
	LD	C,-10
	CALL	PRTD3M1
	LD	C,-1
	CALL	PRTD3M1
	POP	HL
	POP	DE
	POP	BC
	RET
PRTD3M1:
	LD	A,'0' - 1
PRTD3M2:
	INC	A
	ADD	HL,BC
	JR	C,PRTD3M2
	SBC	HL,BC
	CP	E
	JR	Z,PRTD3M3
	LD	E,0
	CALL	COUT
PRTD3M3:
	RET
;
;--------------------------------------------------------------------------------------------------
; INITIALIZATION VECTOR PROCESSING SUPPORT
;--------------------------------------------------------------------------------------------------
;
; CALL A LIST OF ROUTINES POINTED TO BY DE OF LENGTH B.
;
CALLLIST:
	LD	A,(DE)
	LD	L,A
	INC	DE
	LD	A,(DE)
	LD	H,A
	INC	DE
	PUSH	DE
	PUSH	BC
	CALL	JPHL
	POP	BC
	POP	DE
	DJNZ	CALLLIST
CALLDUMMY:
	RET
;
;--------------------------------------------------------------------------------------------------
; GLOBAL IDLE PROCESSING
;--------------------------------------------------------------------------------------------------
;
; GLOBAL HBIOS IDLE PROCESSING IS DONE HERE.  THIS ROUTINE SHOULD
; BE CALLED WHENEVER WAITING FOR USER INPUT.
;
IDLE:
	PUSH	AF
	PUSH	BC
	PUSH	DE
	PUSH	HL
	PUSH	IY
#IF (FDENABLE)
	CALL	FD_IDLE
#ENDIF
	POP	IY
	POP	HL
	POP	DE
	POP	BC
	POP	AF
	RET
;
; COMMON ROUTINE THAT IS CALLED BY CHARACTER IO DRIVERS WHEN
; AN IDLE CONDITION IS DETECTED (WAIT FOR INPUT/OUTPUT)
;
CIO_IDLE:
	PUSH	AF			; PRESERVE AF
	LD	A,(IDLECOUNT)		; GET CURRENT IDLE COUNT
	DEC	A			; DECREMENT
	LD	(IDLECOUNT),A		; SAVE UPDATED VALUE
	CALL	Z,IDLE			; IF ZERO, DO IDLE PROCESSING
	POP	AF			; RECOVER AF
	RET
;
; SET HL TO IY+A, A IS TRASHED
;
LDHLIYA:
	PUSH	IY			; COPY INSTANCE DATA PTR
	POP	HL			; ... TO HL
	;JP	ADDHLA			; APPLY OFFSET TO HL AND RETURN
	ADD	A,L			; ADD OFFSET TO LSB
	LD	L,A			; ... PUT BACK IN L
	RET	NC			; DONE IF CF NOT SET
	INC	H			; IF CF SET, BUMP MSB
	RET				; ... AND RETURN
;
;--------------------------------------------------------------------------------------------------
; CHS TO LBA CONVERSION
;--------------------------------------------------------------------------------------------------
;
; CONVERT AN HBIOS STANDARD HARD DISK CHS ADDRESS TO
; AN LBA ADDRESS.  A STANDARD HBIOS HARD DISK IS ASSUMED
; TO HAVE 16 SECTORS PER TRACK AND 16 HEADS PER CYLINDER.
;
; INPUT:  HL=TRACK, D=HEAD, E=SECTOR
; OUTPUT: DE:HL=32 BIT LBA ADDRESS (D:7 IS NOT SET IN THE RESULT)
;
HB_CHS2LBA:
;
	LD	A,D			; HEAD TO A
	RLCA				; LEFT SHIFT TO HIGH NIBBLE
	RLCA				; ... DEPENDS ON HIGH
	RLCA				; ... NIBBLE BEING 0 SINCE
	RLCA				; ... IT ROTATES INTO LOW NIBBLE
	OR	E			; COMBINE WITH SECTOR (HIGH NIBBLE MUST BE ZERO)
	LD	D,0
	LD	E,H
	LD	H,L
	LD	L,A
	XOR	A
	RET
;
;--------------------------------------------------------------------------------------------------
; SYSTEM CHECK / PANIC
;--------------------------------------------------------------------------------------------------
;
; SYSTEM CHECK: DUMP MACHINE STATE AND CONTINUE?
;
SYSCHKA:
	; CHECK DIAG LEVEL TO SEE IF WE SHOULD DISPLAY
	PUSH	AF			; PRESERVE INCOMING AF VALUE
	LD	A,(CB_DIAGLVL)		; GET DIAGNOSTIC LEVEL
	CP	DL_ERROR		; >= ERROR LEVEL
	JR	C,SYSCHK1		; IF NOT, GO HOME
	POP	AF			; RESTORE INCOMING AF VALUE
;
	; DISPLAY SYSCHK MESSAGE
	PUSH	DE			; PRESERVE DE VALUE
	LD	DE,STR_SYSCHK		; POINT TO PREFIX STRING
	CALL	WRITESTR		; PRINT IT
	POP	DE			; RESTORE DE VALUE
	CALL	XREGDMP			; DUMP REGISTERS

	; DISPLAY ERROR CODE. IT IS AT RETURN ADDRESS+1 .. LD A,XX
	EX	(SP),HL			; GET RETURN ADDRESS
	INC	HL			; POINT TO THE ERROR CODE
	PUSH	AF
	LD	A,(HL)			; DISPLAY
	CALL	PRTHEXBYTE
	POP	AF
	DEC	HL			; RESTORE RETURN ADDRESS
	EX	(SP),HL
;
	JR	CONTINUE		; CHECK W/ USER
;
SYSCHK1:
	; RETURN IF MESSAGING BYPASSED BY DIAG LEVEL
	POP	AF
	RET
;
CONTINUE:
	PUSH	AF
CONTINUE1:
	PUSH	DE
	LD	DE,STR_CONTINUE
	CALL	WRITESTR
	POP	DE
	CALL	CIN
	RES	5,A			; FORCE UPPERCASE (IMPERFECTLY)
	CALL	COUT			; ECHO
	CP	'Y'
	JR	Z,CONTINUE3
	CP	'N'
	JR	Z,SYSHALT
	JR	CONTINUE1
CONTINUE3:
	CALL	NEWLINE
	POP	AF
	RET
;
; PANIC: DUMP MACHINE STATE AND HALT
;
PANIC:
	PUSH	DE
	LD	DE,STR_PANIC
	CALL	WRITESTR
	POP	DE
	CALL	XREGDMP			; DUMP REGISTERS
	JR	SYSHALT			; FULL STOP
;
; ISSUE MESSAGE AND HALT SYSTEM
;
SYSHALT:
	LD	DE,STR_HALT
	CALL	WRITESTR
	DI
	HALT
;
;--------------------------------------------------------------------------------------------------
; INTERRUPT MANAGEMENT SUPPORT
;--------------------------------------------------------------------------------------------------
;
#IF (INTMODE == 1)
;
; ROUTINE BELOW IS USED TO ADD A NEW VECTOR TO THE IM1
; CALL LIST ABOVE.  ENTER WITH HL=VECTOR ADDRESS IN HBIOS
;
HB_ADDIM1:
	EX	DE,HL			; VECTOR ADDRESS TO DE
	LD	HL,(HB_IM1PTR)		; GET PTR FOR NEXT ENTRY
	INC	HL			; BUMP PTR TO ADDRESS FIELD OF CALL OPCODE
	LD	(HL),E			; ADD VECTOR ADDRESS
	INC	HL			; ...
	LD	(HL),D			; ...
	INC	HL			; BUMP PTR
	INC	HL			; BUMP PTR
	LD	(HB_IM1PTR),HL		; SAVE UPDATED POINTER
	LD	HL,HB_IM1CNT		; POINT TO ENTRY COUNT
	INC	(HL)			; INCREMENT
	RET				; DONE
;
#ENDIF
;
;--------------------------------------------------------------------------------------------------
; SYSTEM TIMER INTERRUPT HANDLER
;--------------------------------------------------------------------------------------------------
;
HB_TIMINT:
;
#IF FALSE	; *DEBUG*
	LD	HL,HB_TIMDBGCNT
	INC	(HL)
	LD	A,(HL)
	OUT	(DIAGPORT),A
	JR	HB_TIMDBG1
HB_TIMDBGCNT	.DB	0
HB_TIMDBG1:
#ENDIF	; *DEBUG*
;
; TIMER HANDLER VECTORS
; THESE CAN BE HOOKED AS DESIRED BY DRIVERS
;
VEC_TICK:
	JP	HB_TICK			; TICKS PROCESSING VECTOR
VEC_SECOND:
	JP	HB_SECOND		; SECONDS PROCESSING VECTOR
;
; TIMER HANDLERS
;
HB_TICK:
	; INCREMENT TICK COUNTER (32 BIT)
	LD	HL,HB_TICKS		; POINT TO TICK COUNTER
	CALL	INC32HL
	LD	HL,HB_SECTCK		; POINT TO SECONDS TICK COUNTER
	DEC	(HL)			; COUNTDOWN ONE SECOND OF TICKS
	JR	NZ,HB_TICK1		; NOT DONE, SKIP AHEAD
	LD	A,TICKFREQ		; TICKS PER SECOND
	LD	(HL),A			; RESET COUNTDOWN REGISTER
	CALL	VEC_SECOND		; DO SECONDS PROCESSING VIA VECTOR
;
HB_TICK1:
;
#IF (CPUFAM == CPU_Z180)
	; ACK/RESET Z180 TIMER INTERRUPT
	IN0	A,(Z180_TCR)
	IN0	A,(Z180_TMDR0L)
#ENDIF
;
#IF (WDOGMODE != WDOG_NONE)
	; PULSE WATCHDOG
	OUT	(WDOGIO),A		; VALUE IS IRRELEVANT
#ENDIF
;
#IF MKYENABLE
	CALL	MKY_INT
#ENDIF
;
	OR	$FF			; NZ SET TO INDICATE INT HANDLED
	RET
;
HB_SECOND:
	; INCREMENT SECONDS COUNTER
	LD	HL,HB_SECS		; POINT TO SECONDS COUNTER
	JP	INC32HL			; INCREMENT AND RETURN
;
; BAD INTERRUPT HANDLER
;
HB_BADINT:

#IF FALSE	; *DEBUG*
	LD	HL,HB_BADINTCNT
	INC	(HL)
	LD	A,(HL)
	OUT	(DIAGPORT),A
	OR	$FF
	RET
HB_BADINTCNT	.DB	0
#ENDIF	; *DEBUG*

	CALL	NEWLINE2
	PRTS("+++ BAD INT $")
	LD	A,L
	RRCA
	RRCA
	;CALL PRTHEXBYTE
	;PRTS("H: $")
	CALL	XREGDMP
	CALL	NEWLINE
	;CALL	CONTINUE
	OR	$FF			; SIGNAL INTERRUPT HANDLED
	RET
;
;--------------------------------------------------------------------------------------------------
; API FUNCTION DISPATCH SUPPORT
;--------------------------------------------------------------------------------------------------
;
; ON ENTRY B IS API FUNCTION NUMBER AND C IS UNIT #
; (INDEX INTO XXX_TBL OF UNITS) AND IY POINTS TO START OF UNIT TABLE.
; USE UNIT # IN C TO LOOKUP XXX_TBL ENTRY.  THE XXX_TBL
; ENTRY CONTAINS THE START OF THE DRIVER FUNCTION TABLE AND
; THE DEVICE SPECIFIC INSTANCE DATA (BLOB).  SET IY TO BLOB ADDRESS
; AND CALL THE SPECIFIC FUNCTION REQUESTED IN THE DRIVER.
;
HB_DISPCALL:
	PUSH	HL			; SAVE INCOMING HL VALUE
	CALL	HB_DISPCALC		; IY = BLOB ADR, HL = FN ADR
	JR	NZ,HB_DISPCALL1		; ABORT ON ERROR
	EX	(SP),HL			; RESTORE HL & FN ADR TO TOS
	RET				; JUMP TO FN ADR
HB_DISPCALL1:
	POP	HL			; RECOVER HL
	RET				; AND DONE
;
; ENTRY: BC=FUNC/UNIT, IY=DISPATCH TABLE
; EXIT: HL=FUNC ADR, IY=DATA BLOB ADR
;
HB_DISPCALC:
	; CHECK INCOMING UNIT INDEX IN C FOR VALIDITY
	LD	A,C			; A := INCOMING DISK UNIT INDEX
	CP	(IY-1)			; COMPARE TO COUNT
	JR	NC,HB_UNITERR		; HANDLE INVALID UNIT INDEX

	; CHECK FUNCTION INDEX FOR VALIDITY
	LD	A,B			; A := INCOMING FUNCTION NUMBER
	AND	$0F			; LOW NIBBLE ONLY FOR FUNC INDEX
	CP	(IY-3)			; CHECK FN NUM AGAINST MAX
	JR	NC,HB_FUNCERR		; HANDLE FN NUM OUT OF RANGE ERROR

	; BUMP IY TO ACTUAL XXX_TBL ENTRY FOR INCOMING UNIT INDEX
	PUSH	BC			; SAVE BC
	LD	B,0			; MSB IS ALWAYS ZERO
	RLC	C			; MULTIPLY UNIT INDEX
	RLC	C			; ... BY 4 FOR TABLE ENTRY OFFSET
	ADD	IY,BC			; SET IY TO ENTRY ADDRESS
	POP	BC			; RESTORE BC

	; DERIVE DRIVER FUNC ADR TO CALL
	;PUSH	HL			; SAVE INCOMING HL
	LD	L,(IY+0)		; COPY DRIVER FUNC TABLE
	LD	H,(IY+1)		; ... START TO HL
	RLCA				; CONV UNIT (STILL IN A) TO FN ADR OFFSET
	CALL	ADDHLA			; HL NOW HAS DRIVER FUNC TBL START ADR
	LD	A,(HL)			; DEREFERENCE HL
	INC	HL			; ... TO GET
	LD	H,(HL)			; ... ACTUAL
	LD	L,A			; ... TARGET FUNCTION ADDRESS
	;EX	(SP),HL			; RESTORE HL, FUNC ADR ON STACK

	; GET UNIT INSTANCE DATA BLOB ADDRESS TO IY
	;PUSH	HL			; SAVE INCOMING HL
	PUSH	HL			; SAVE FUNC ADR
	LD	L,(IY+2)		; HL := DATA BLOB ADDRESS
	LD	H,(IY+3)		; ...
	EX	(SP),HL			; RESTORE HL, BLOB ADR ON TOS
	POP	IY			; IY := BLOB ADR

	XOR	A			; SIGNAL SUCCESS
	RET				; JUMP TO DRIVER FUNC ADR ON TOS
;
HB_FUNCERR:
	SYSCHKERR(ERR_NOFUNC)		; SIGNAL ERROR
	RET
;
HB_UNITERR:
	SYSCHKERR(ERR_NOUNIT)		; SIGNAL ERROR
	RET
;
; ADD AN ENTRY TO THE UNIT TABLE AT ADDRESS IN HL
;   BC: DRIVER FUNCTION TABLE
;   DE: ADDRESS OF UNIT INSTANCE DATA
; RETURN
;   A: UNIT NUMBER ASSIGNED
;
HB_ADDENT:
	DEC	HL			; POINT TO ENTRY COUNT
	LD	A,(HL)			; GET ENTRY COUNT
	PUSH	AF			; SAVE VALUE TO RETURN AS ENTRY NUM AT END
	INC	A			; INCREMENT TO ACCOUNT FOR NEW ENTRY
	DEC	HL			; POINT TO ENTRY MAX
	CP	(HL)			; COMPARE MAX TO CURRENT COUNT (COUNT - MAX)
	CALL	NC,PANIC		; OVERFLOW
	INC	HL			; POINT TO COUNT
	LD	(HL),A			; SAVE NEW COUNT
	INC	HL			; POINT TO START OF TABLE
	DEC	A			; CONVERT A FROM ENTRY COUNT TO ENTRY INDEX
	RLCA				; MULTIPLY BY 4
	RLCA				; ... TO GET BYTE OFFSET OF ENTRY
	CALL	ADDHLA			; MAKE HL POINT TO ACTUAL ENTRY ADDRESS
	PUSH	BC			; GET TABLE ENTRY ADDRESS TO BC
	EX	(SP),HL			; ... AND DISPATCH ADDRESS TO HL
	POP	BC			; ... SO THAT DE:HL HAS 32 BIT ENTRY
	CALL	ST32			; LD (BC),DE:HL STORES THE ENTRY
	POP	AF			; RETURN ENTRY INDEX (UNIT NUMBER ASSIGNED)
	RET
;
;--------------------------------------------------------------------------------------------------
; HEAP MEMORY ALLOCATION
;--------------------------------------------------------------------------------------------------
;
; ALLOCATE HL BYTES OF MEMORY ON THE HEAP
; RETURNS POINTER TO ALLOCATED SPACE IN HL
; ON SUCCESS RETURN A == 0, AND Z SET
; ON FAILURE A <> 0 AND NZ SET AND HL TRASHED
; ALL OTHER REGISTERS PRESERVED
;
; A 4 BYTE HEADER IS PLACED IN FRONT OF THE ALLOCATED MEMORY
;  - DWORD: SIZE OF MEMORY ALLOCATED (DOES NOT INCLUDE 4 BYTE HEADER)
;  - DWORD: ADDRESS WHERE ALLOC WAS CALLED (VALUE ON TOP OF STACK AT CALL)
;
HB_ALLOC:
;
#IFDEF MEMDBG
	CALL	PRTSTRD
	.TEXT	"\r\n>>> ALLOC SIZE=0x$")
	CALL	PRTHEXWORDHL
#ENDIF
;
	; SAVE ALLOC SIZE AND REFERENCE ADR FOR SUBSEQUENT HEADER CONSTRUCTION
	LD	(HB_TMPSZ),HL		; SAVE INCOMING SIZE REQUESTED
	; USE EX (SP),HL INSTEAD????
	POP	HL			; GET RETURN ADDRESS
	LD	(HB_TMPREF),HL		; SAVE AS REFERENCE
	; USE EX (SP),HL INSTEAD????
	PUSH	HL			; PUT IT BACK ON STACK
	LD	HL,(HB_TMPSZ)		; RECOVER INCOMING MEM SIZE PARM
;
	; CALC NEW HEAP TOP AND HANDLE OUT-OF-SPACE ERROR
	PUSH	DE			; SAVE INCOMING DE
	LD	DE,4			; SIZE OF HEADER
	ADD	HL,DE			; ADD IT IN
	JR	C,HB_ALLOC1		; ERROR ON OVERFLOW
	LD	DE,(CB_HEAPTOP)		; CURRENT HEAP TOP
	ADD	HL,DE			; ADD IT IN, HL := NEW HEAP TOP
	JR	C,HB_ALLOC1		; ERROR ON OVERFLOW
	BIT	7,H			; TEST PAST END OF BANK (>= 32K)
	JR	NZ,HB_ALLOC1		; ERROR IF PAST END
;
	; SAVE NEW HEAP TOP
	LD	DE,(CB_HEAPTOP)		; GET ORIGINAL HEAP TOP
	LD	(CB_HEAPTOP),HL		; SAVE NEW HEAP TOP
;
#IFDEF MEMDBG
	CALL	PRTSTRD
	.TEXT	" TOP=0x$")
	CALL	PRTHEXWORDHL
#ENDIF
;
	; SET HEADER VALUES
	EX	DE,HL			; HEADER ADR TO HL
	LD	DE,(HB_TMPSZ)		; GET THE ORIG SIZE REQUESTED
	LD	(HL),E			; SAVE SIZE (LSB)
	INC	HL			; BUMP HEADER POINTER
	LD	(HL),D			; SAVE SIZE (MSB)
	INC	HL			; BUMP HEADER POINTER
	LD	DE,(HB_TMPREF)		; GET THE REFERENCE ADR
	LD	(HL),E			; SAVE REF ADR (LSB)
	INC	HL			; BUMP HEADER POINTER
	LD	(HL),D			; SAVE REF ADR (MSB)
	INC	HL			; BUMP HEADER POINTER
;
	; RETURN SUCCESS, HL POINTS TO START OF ALLOCATED MEMORY (PAST HEADER)
	POP	DE			; RESTORE INCOMING DE
	XOR	A			; SIGNAL SUCCESS
	RET				; AND RETURN
;
HB_ALLOC1:
	; ERROR RETURN
	POP	DE			; RESTORE INCOMING DE
	SYSCHKERR(ERR_NOMEM)		; SIGNAL ERROR
	RET
;
HB_TMPSZ	.DW	0
HB_TMPREF	.DW	0
;
;--------------------------------------------------------------------------------------------------
; Z280 SUPPORT ROUTINES
;--------------------------------------------------------------------------------------------------
;
; Z280 SYSTEM TIMER INTERRUPT HANDLER
;
#IF (MEMMGR == MM_Z280)
;
Z280_TIMINT:
	; DISCARD REASON CODE
	INC	SP
	INC	SP
;
	; SAVE INCOMING REGISTERS
	PUSH	AF
	PUSH	BC
	PUSH	DE
	PUSH	HL
;
	; CALL PRIMARY TIMER LOGIC ON EVERY OTHER INT
	LD	A,(Z280_TIMCTR)
	XOR	$FF
	LD	(Z280_TIMCTR),A
	CALL	Z,HB_TIMINT
;
	; SELECT I/O PAGE $FE (SAVING PREVIOUS VALUE)
	LD	C,Z280_IOPR		; REG C POINTS TO I/O PAGE REGISTER
	LDCTL	HL,(C)			; GET CURRENT I/O PAGE
	PUSH	HL			; SAVE IT
	LD	L,$FE			; NEW COUNTER/TIMER I/O PAGE
	LDCTL	(C),HL
;
	; CLEAR END OF COUNT CONDITION TO RESET INTERRUPT
	IN	A,(Z280_CT0_CMDST)	; GET STATUS
	RES	1,A			; CLEAR CC
	OUT	(Z280_CT0_CMDST),A	; SET C/T 0
;
	; RESTORE I/O PAGE
	LD	C,Z280_IOPR		; REG C POINTS TO I/O PAGE REGISTER
	POP	HL			; RECOVER ORIGINAL I/O PAGE
	LDCTL	(C),HL
;
	; RESTORE REGISTERS
	POP	HL
	POP	DE
	POP	BC
	POP	AF
;
	RETIL
;
Z280_TIMCTR	.DB	0		; USED TO DIVIDE TIMER INTS
;
#ENDIF
;
#IF (MEMMGR == MM_Z280)
;
Z280_BADINT:
	; SAVE REASON CODE FOR POSSIBLE RETURN VIA RETIL
	EX	(SP),HL			; GET MSR, SAVE HL
	LD	(HB_RCSAV),HL		; SAVE IT
	POP	HL			; RECOVER HL, POP STACK
	; SAVE MSR FOR POSSIBLE RETURN VIA RETIL
	EX	(SP),HL			; GET MSR, SAVE HL
	LD	(HB_MSRSAV),HL		; SAVE IT
	POP	HL			; RECOVER HL, POP STACK
;
	PUSH	DE
	LD	DE,Z280_BADINTSTR
	CALL	NEWLINE2
	PRTS("+++ $")
	CALL	WRITESTR
	POP	DE
	CALL	XREGDMP
;
	; RECOVER MSR, THEN RETURN VIA RETIL
	PUSH	HL			; SAVE HL
	LD	HL,(HB_RCSAV)		; GET SAVED REASON CODE
	PRTS(" RC=$")
	CALL	PRTHEXWORDHL		; DUMP MSR
	LD	HL,(HB_MSRSAV)		; GET SAVED MSR
	PRTS(" MSR=$")
	CALL	PRTHEXWORDHL		; DUMP MSR
	EX	(SP),HL			; MSR TO STK, RECOVER HL
;
	RETIL				; RETURN FROM INT
;
Z280_SSTEP:
	; SAVE HL AND MSR FOR POSSIBLE RETURN VIA RETIL
	EX	(SP),HL			; GET MSR, SAVE HL
	LD	(HB_MSRSAV),HL		; SAVE IT
	POP	HL			; RECOVER HL, POP STACK
;
	PUSH	DE
	LD	DE,Z280_SSTEPSTR
	JP	Z280_DIAG
;
Z280_BRKHLT:
	; SAVE HL AND MSR FOR POSSIBLE RETURN VIA RETIL
	EX	(SP),HL			; GET MSR, SAVE HL
	LD	(HB_MSRSAV),HL		; SAVE IT
	POP	HL			; RECOVER HL, POP STACK
;
	PUSH	DE
	LD	DE,Z280_BRKHLTSTR
	JP	Z280_DIAG
;
Z280_DIVEXC:
	; SAVE HL AND MSR FOR POSSIBLE RETURN VIA RETIL
	EX	(SP),HL			; GET MSR, SAVE HL
	LD	(HB_MSRSAV),HL		; SAVE IT
	POP	HL			; RECOVER HL, POP STACK
;
	PUSH	DE
	LD	DE,Z280_DIVEXCSTR
	JP	Z280_DIAG
;
Z280_STKOVR:
	; SAVE HL AND MSR FOR POSSIBLE RETURN VIA RETIL
	EX	(SP),HL			; GET MSR, SAVE HL
	LD	(HB_MSRSAV),HL		; SAVE IT
	POP	HL			; RECOVER HL, POP STACK
;
	PUSH	DE
	LD	DE,Z280_STKOVRSTR
	JP	Z280_DIAG
;
Z280_ACCVIO:
	; SAVE HL AND MSR FOR POSSIBLE RETURN VIA RETIL
	EX	(SP),HL			; GET MSR, SAVE HL
	LD	(HB_MSRSAV),HL		; SAVE IT
	POP	HL			; RECOVER HL, POP STACK
;
	PUSH	DE
	LD	DE,Z280_ACCVIOSTR
	JP	Z280_DIAG
;
Z280_DIAG:
	CALL	NEWLINE2
	PRTS("+++ $")
	CALL	WRITESTR
	POP	DE
	CALL	XREGDMP
;
	; RECOVER MSR, THEN RETURN VIA RETIL
	PUSH	HL			; SAVE HL
	LD	HL,(HB_MSRSAV)		; GET SAVED MSR
	PRTS(" MSR=$")
	CALL	PRTHEXWORDHL		; DUMP MSR
	EX	(SP),HL			; MSR TO STK, RECOVER HL
;
	;RETIL
	DI
	HALT
;
Z280_BADINTSTR	.TEXT	"BAD INT $"
Z280_SSTEPSTR	.TEXT	"SINGLE STEP $"
Z280_BRKHLTSTR	.TEXT	"BREAK HALT $"
Z280_DIVEXCSTR	.TEXT	"DIVISION EXCEPTION $"
Z280_STKOVRSTR	.TEXT	"STACK OVERFLOW $"
Z280_ACCVIOSTR	.TEXT	"ACCESS VIOLATION $"
;
#ENDIF
;
; Z280 PRIVILEGED INSTRUCTION HANDLER
;
#IF (MEMMGR == MM_Z280)
;
Z280_PRIVINST:
	; SAVE HL AND MSR FOR POSSIBLE RETURN VIA RETIL
	EX	(SP),HL			; GET MSR, SAVE HL
	LD	(HB_MSRSAV),HL		; SAVE IT
	POP	HL			; RECOVER HL, POP STACK
	EX	(SP),HL			; GET ADR, SAVE HL
;
	PUSH	AF
	PUSH	BC			; NEEDED?
	PUSH	DE			; NEEDED?
;
	LDUP	A,(HL)			; BYTE FROM USER SPACE
;
	; HANDLE USER MODE Z80 DI
	CP	$F3			; DI?
	JR	NZ,Z280_PRIVINST2

	;;;HB_DI			; DO THE DI
	XOR	A			; NO INTERRUPTS
	LD	(HB_MSRSAV),A		; UPDATE SAVED MSR LSB

	INC	HL			; BUMP PAST IT
	JR	Z280_PRIVINSTX
;
Z280_PRIVINST2:
	; HANDLE USER MODE Z80 EI
	CP	$FB			; EI?
	JR	NZ,Z280_PRIVINST3

	;;;HB_EI			; DO THE EI
	LD	A,$0B			; NORMAL INTERRUPTS
	LD	(HB_MSRSAV),A		; UPDATE SAVED MSR LSB

	INC	HL			; BUMP PAST IT
	JR	Z280_PRIVINSTX
;
Z280_PRIVINST3:
	; SOMETHING ELSE, DIAGNOSE & HALT SYSTEM
	LD	DE,Z280_PRIVSTR
	CALL	WRITESTR
	CALL	PRTHEXWORDHL
;
	; DUMP 16 BYTES OF USER ADDRESS SPACE
	CALL	PC_SPACE
	CALL	PC_LBKT
	LD	B,$10
Z280_PRIVINST4:
	LDUP	A,(HL)			; BYTE FROM USER SPACE
	CALL	PRTHEXBYTE
	INC	HL
	DJNZ	Z280_PRIVINST4
	CALL	PC_RBKT
;
	; GO NO FURTHER
	DI
	HALT
;
Z280_PRIVINSTX:
	; RESTORE REGISTERS
	POP	DE			; NEEDED?
	POP	BC			; NEEDED?
	POP	AF
;
	; RECOVER HL AND MSR, THEN RETURN VIA RETIL
	EX	(SP),HL			; RECOVER HL, ADR TO STK
	PUSH	HL			; SAVE HL
	LD	HL,(HB_MSRSAV)		; GET SAVED MSR
	EX	(SP),HL			; MSR TO STK, RECOVER HL
	RETIL				; RETURN FROM INT
;
HB_MSRSAV	.DW	0		; SAVED MSR
HB_RCSAV	.DW	0		; SAVED REASON CODE
;
Z280_PRIVSTR	.TEXT	"\r\n\r\n*** Privileged Instruction @$"
;
#ENDIF
;
; Z280 BANK COPY (CALLED FROM PROXY)
;
; USE Z280 PHYSICAL MEMORY DMA COPY TO PERFORM AN INTERBANK COPY.
; COPY FROM (HB_SRCBNK):(HL) TO (HB_DSTBNK):(DE) FOR BC BYTES.  BOTH
; HB_SRCBNK AND HB_DSTBNK MUST BE INITIALIZED PRIOR TO CALLING THIS
; ROUTINE.
;
; ADDRESSES ARE TRANSLATED FROM LOGICAL (Z80) TO PHYSICAL (Z280) TO
; SETUP THE DMA COPY PARAMETERS.  IF THE SOURCE OR DESTINATION RANGE
; CROSSES OVER THE BANKED/COMMON BOUNDARY AT $8000, THEN SPECIAL STEPS
; MUST BE TAKEN BECAUSE THE BANKED AND COMMON AEAS ARE PROBABLY NOT
; SEQUENTIALLY LOCATED IN PHYSICAL MEMORY.  TWO ENTRY POINTS ARE
; PROVIDED.  Z280_BNKCPY IS MUCH FASTER, BUT DOES NOT ACCOUNT FOR THE
; COPY RANGES CROSSING OVER THE BANKED/COMMON BOUNDARY (WORKS GREAT
; FOR ANY COPY KNOWN TO STAY WITHIN IT'S OWN AREA).  Z280_BNKCPYX
; WILL HANDLE COPIES WHERE THE SOURCE AND/OR DESTINATION RANGES
; CROSS OVER THE BANKED/COMMON MEMORY BOUNDARY.  IT DOES THIS BY
; BREAKING UP THE COPY REQUESTS INTO MULTIPLE REQUESTS THAT ALL FIT
; WITHIN A SINGLE BANKED/COMMON MEMORY SEGMENT AND CALLING Z280_BNKCPY
; ITERATIVELY UNTIL THE COPY IS COMPLETE.
;
; THERE IS ESSENTIALLY NO PROTECTION FOR CALLING THESE ROUTINES WITH
; INVALID PARAMETERS.  FOR EXAMPLE, A REQUEST TO COPY $2000 BYTES
; STARTING AT $F000 EXCEEDS THE SIZE OF THE Z80 MEMORY SPACES AND
; RESULTS IN UNDEFINED BEHAVIOR.
;
; THE COPY IS ALWAYS DONE FROM START TO END.  IF THE SOURCE AND
; DESTINATION RANGES OVERLAP, THEN YOU MUST TAKE THIS INTO ACCOUNT.
;
; THE ROUTINE FUNCTIONS LOGICALLY LIKE THE Z80 LDIR INSTRUCTION.  ON
; RETURN THE SOURCE (HL) AND DESTINATION (DE) REGISTERS WILL BE LEFT
; POINTING TO THE NEXT BYTE THAT WOULD BE COPIED IF THE COPY ROUTINE
; CONTINUED.  BC WILL BE 0.  AF IS UNDEFINED.
;
#IF (MEMMGR == MM_Z280)
;
; ADJUST THE LENGTH OF THE COPY SUCH THAT BOTH THE SOURCE AND
; DESTINATION RANGES DO NOT CROSS OVER THE BANKED/COMMON MEMORY
; BOUNDARY.  CALL Z280_BNKCPY TO DO AS MANY ITERATIONS AS NEEDED TO
; COMPLETE THE COPY.
;
;
Z280_BNKCPYX:
	LD	(Z280_BNKCPY_LEN),BC	; SAVE LENGTH
;
	CALL	Z280_BNKCPY_XOVER	; ADJUST FOR XOVER AS NEEDED
	EX	DE,HL			; SWAP SOURCE/DEST
	CALL	Z280_BNKCPY_XOVER	; ADJUST FOR XOVER AS NEEDED
	EX	DE,HL			; SWAP BACK
;
	; DO THE WORK, SAVE THE LEN OF THIS ITERATION
	PUSH	BC			; SAVE ITER LENGTH
	CALL	Z280_BNKCPY		; DO THE WORK
;
	;;; *DEBUG* SIMULATE CALL TO Z280_BNKCPY
	;;CALL	NEWLINE
	;;CALL	REGDMP			; *DEBUG*
	;;ADD	HL,BC			; INCREMENT SRC ADR BY COUNT
	;;EX	DE,HL			; SWAP
	;;ADD	HL,BC			; INCREMENT DST ADR BY COUNT
	;;EX	DE,HL			; SWAP BACK
	;;LD	BC,0			; COUNT IS NOW ZERO
	;;; END *DEBUG*
;
	POP	BC			; RECOVER ITER LENGTH
;
	; ACCUNT FOR WORK ACTUALLY PERFORMED
	PUSH	HL			; SAVE SOURCE ADR
	LD	HL,(Z280_BNKCPY_LEN)	; GET PENDING LENGTH
	OR	A			; CLEAR CARRY
	SBC	HL,BC			; SUBTRACT WHAT WE DID
	PUSH	HL			; MOVE NEW PENDING LEN
	POP	BC			; TO BC
	POP	HL			; RECOVER SOURCE ADR
;
	; SEE IF WE NEED TO ITERATE
	LD	A,B			; IS LENGTH
	OR	C			; ... NOW ZERO?
	RET	Z			; IF SO, ALL DONE
	JR	Z280_BNKCPYX		; ELSE ITERATE UNTIL DONE
;
Z280_BNKCPY_LEN	.DW	0		; TEMP STORAGE FOR BC
;
Z280_BNKCPY_XOVER:
	; DETECT XOVER IN RANGE AND ADJUST COPY LEN IF SO
	; HL=START, BC=LEN
	; BC IS REDUCED AS NEEDED TO AVOID XOVER
	BIT	7,H			; START ABOVE 32K?
	RET	NZ			; YES, NO XOVER
	PUSH	HL			; SAVE START ADR
	ADD	HL,BC			; ADD COPY LEN
	DEC	HL			; CONVERT TO "LAST" BYTE OF RANGE
	BIT	7,H			; ABOVE 32K?
	POP	HL			; RESTORE HL
	RET	Z			; IF NOT, NO XOVER
;
	; START IS BELOW 32K, END IS OVER 32K, XOVER IN SOURCE!
	; REDUCE LENGTH TO AVOID IT
	; COMPUTE (32K - START) FOR NEW LEN
	PUSH	DE			; SAVE DEST (DE)
	PUSH	HL			; SAVE START (HL)
	LD	DE,$8000
	EX	DE,HL			; DE=START, HL=32K
	OR	A			; CLEAR CARRY
	SBC	HL,DE			; HL = NEW LEN
	PUSH	HL			; MOVE NEW LEN
	POP	BC			; ... TO BC
	POP	HL			; RECOVER START
	POP	DE			; RECOVER DEST
	RET				; RETURN
;
Z280_BNKCPY:
	; Z280 MEMORY TO MEMORY DMA
	; USE FLOW THROUGH MODE
	; SINGLE BYTE TRANSFER
	; TRANSACTION DESCRIPTION REGISTER (TDR)
	; %0000 0000 0000 0000
	; - AUTO INCREMENT MEMORY
	; - FLOWTHROUGH OPERATION
	; - SINGLE TRANSACTION (CAN WE USE CONTINUOUS???)
	; - 1 BYTE XFER SIZE
;
	; SAVE INCOMING REGISTERS
	PUSH	HL
	PUSH	DE
	PUSH	BC
;
	PUSH	BC			; SAVE COUNT
	PUSH	HL			; SAVE SOURCE ADDRESS
;
	; SELECT I/O PAGE $FF
	LD	C,Z280_IOPR		; I/O PAGE REGISTER
	LDCTL	HL,(C)			; GET CURRENT I/O PAGE
	LD	(IOPRSAV),HL		; SAVE IT
	LD	L,$FF			; I/O PAGE $FF
	LDCTL	(C),HL
;
	LD	C,Z280_DMA0_DSTL	; START WITH DEST REG LO
;
	LD	A,(HB_DSTBNK)		; DEST BANK TO ACCUM
	CALL	Z2DMAADR		; SETUP DEST ADR REGS
;
	POP	DE			; SRC ADR TO DE
	LD	A,(HB_SRCBNK)		; DEST BANK TO ACCUM
	CALL	Z2DMAADR		; SETUP SOURCE ADR REGS
;
	POP	HL			; COUNT TO HL
	OUTW	(C),HL
	INC	C			; BUMP TO TDR
;
	LD	HL,$8000		; ENABLE DMA0 TO RUN!
	OUTW	(C),HL
;
	; WAIT FOR XFER TO COMPLETE
Z2DMALOOP:
	INW	HL,(C)			; WORD INPUT
	BIT	7,H			; CHECK EN BIT OF TDR
	JR	NZ,Z2DMALOOP		; LOOP WHILE ACTIVE
;
	; RESTORE I/O PAGE
	LD	C,Z280_IOPR		; I/O PAGE REGISTER
	LD	HL,(IOPRSAV)		; RESTORE I/O PAGE
	LDCTL	(C),HL
;
	; SETUP RETURN VALUES
	POP	BC			; RECOVER ORIGINAL BC
	POP	DE			; RECOVER ORIGINAL DE
	POP	HL			; RECOVER ORIGINAL HL
	ADD	HL,BC			; INCREMENT SRC ADR BY COUNT
	EX	DE,HL			; SWAP
	ADD	HL,BC			; INCREMENT DST ADR BY COUNT
	EX	DE,HL			; SWAP BACK
	LD	BC,0			; COUNT IS NOW ZERO
;
	RET
;
Z2DMAADR:
	; SET ADDRESS REGISTERS, BANK IN A, ADDRESS IN DE
	; C POINTS TO FIRST DMA ADR PORT TO SET
	; A=R000 BBBB, DE=0AAA AAAA AAAA AAAA
	; RC: DMA HI=0000 RBBB BAAA 1111 LO=1111 AAAA AAAA AAAA
	; ZZ: DMA HI=R000 0BBB BAAA 1111 LO=1111 AAAA AAAA AAAA
	BIT	7,D			; HIGH RAM?
	JR	Z,Z2DMAADR1		; NO, SKIP
	LD	A,$8F			; SUBSTITUTE COMMON RAM BANK ID
;
Z2DMAADR1:
	; ADR HI FROM A:DE
	LD	L,D			; L=?AAA AAAA
	LD	H,A			; H=R000 BBBB
	SLA	L			; L=AAAA AAA0 ?
	SRL	H			; H=0R00 0BBB B
	RR	L			; L=BAAA AAAA 0
	LD	A,$0F			; A=0000 1111
	OR	L			; A=BAAA 1111
	LD	L,A			; L=BAAA 1111
;
	; MOVE THE RAM/ROM BIT.
	; RCBUS DMA HI=0000 RBBB BAAA 1111 LO=1111 AAAA AAAA AAAA
	; ZZ80MB DMA HI=R000 0BBB BAAA 1111 LO=1111 AAAA AAAA AAAA
	BIT	6,H			; RAM BIT SET?
	JR	Z,Z2DMAADR2		; IF NOT, ALL DONE
	RES	6,H			; OTHERWISE, REMOVE RAM BIT
	LD	A,RAMBIAS >> 6		; RAM OFFSET (TOP 8 BITS)
	OR	H			; RECOMBINE
	LD	H,A			; AND PUT BACK IN H
;
Z2DMAADR2:
	PUSH	HL			; SAVE IT FOR NOW

	; ADR LO FROM DE:
	LD	L,E			; L=AAAA AAAA
	LD	A,$F0			; A=1111 0000
	OR	D			; A=1111 AAAA
	LD	H,A			; HL=1111 AAAA AAAA AAAA
;
	; SET ADR LO REG
	OUTW	(C),HL
	INC	C			; BUMP TO ADR HI REG
;
	; SET ADR HI REG
	POP	HL			; RECOVER THE HI VAL
	OUTW	(C),HL
	INC	C			; BUMP TO NEXT REG
;
	RET
;
#ENDIF
;
HB_INTFUNC_END	.EQU	$
;
;==================================================================================================
; UTILITY FUNCTIONS
;==================================================================================================
;
HB_UTIL_BEG	.EQU	$
;
#DEFINE USEDELAY
#INCLUDE "util.asm"
#INCLUDE "time.asm"
#INCLUDE "bcd.asm"
#INCLUDE "decode.asm"
#INCLUDE "encode.asm"
;
#IF (WBWDEBUG == USEXIO)
#INCLUDE "xio.asm"
#ENDIF
#IF (WBWDEBUG == USEMIO)
#INCLUDE "mio.asm"
#ENDIF
;
; INCLUDE LZSA2 decompression engine if required.
;
#IF ((CVDUENABLE | GDCENABLE | TMSENABLE | VGAENABLE | VRCENABLE) & USELZSA2)
#INCLUDE "unlzsa2s.asm"
#ENDIF
;
;--------------------------------------------------------------------------------------------------
; CONSOLE CHARACTER I/O HELPER ROUTINES (REGISTERS PRESERVED)
;--------------------------------------------------------------------------------------------------
;
; OUTPUT CHARACTER FROM A
;
COUT:
	; SAVE ALL INCOMING REGISTERS
	PUSH	AF
	PUSH	BC
	PUSH	DE
	PUSH	HL
;
	; GET CURRENT CONSOLE UNIT
	LD	E,A			; TEMPORARILY STASH OUTPUT CHAR IN E
	LD	A,(CB_CONDEV)		; GET CONSOLE UNIT BYTE
	CP	$FF			; TEST FOR $FF (HBIOS NOT READY)
	JR	Z,COUT1			; IF NOT READY, TRY DEBUG OUTPUT
;
	; USE HBIOS
	LD	C,A			; CONSOLE UNIT TO C
	LD	B,BF_CIOOUT		; HBIOS FUNC: OUTPUT CHAR
	CALL	CIO_DISPATCH		; CALL CIO DISPATCHER DIRECTLY
	JR	COUT2			; CONTINUE
;
COUT1:
;
#IF (WBWDEBUG == USEXIO)
	LD	A,E			; GET OUTPUT CHAR BACK TO ACCUM
	CALL	XIO_OUTC		; OUTPUT VIA XIO
#ENDIF
;
#IF (WBWDEBUG == USEMIO)
	LD	A,E
	CALL	MIO_OUTC		; OUTPUT VIA MIO
#ENDIF
;
COUT2:
	; RESTORE ALL REGISTERS
	POP	HL
	POP	DE
	POP	BC
	POP	AF
	RET
;
; INPUT CHARACTER TO A
;
CIN:
	; SAVE INCOMING REGISTERS (AF IS OUTPUT)
	PUSH	BC
	PUSH	DE
	PUSH	HL
;
	LD	A,(CB_CONDEV)		; GET CONSOLE UNIT BYTE
	CP	$FF			; TEST FOR $FF (HBIOS NOT READY)
	JR	Z,CIN1			; IF NOT READY, TRY DEBUG INPUT
;
	; USE HBIOS
	LD	C,A			; CONSOLE UNIT TO C
	LD	B,BF_CIOIN		; HBIOS FUNC: INPUT CHAR
	CALL	CIO_DISPATCH		; CALL CIO DISPATCHER DIRECTLY
	LD	A,E			; RESULTANT CHAR TO A
	JR	CIN2			; CONTINUE
;
CIN1:
;
#IF (WBWDEBUG == USEXIO)
	CALL	XIO_INC			; GET CHAR
#ENDIF
;
#IF (WBWDEBUG == USEMIO)
	CALL	MIO_INC			; GET CHAR
#ENDIF
;
CIN2:
;
	; RESTORE REGISTERS (AF IS OUTPUT)
	POP	HL
	POP	DE
	POP	BC
	RET
;
; RETURN INPUT STATUS IN A (0 = NO CHAR, !=0 CHAR WAITING)
;
CST:
	; SAVE INCOMING REGISTERS (AF IS OUTPUT)
	PUSH	BC
	PUSH	DE
	PUSH	HL
;
	LD	A,(CB_CONDEV)		; GET CONSOLE UNIT BYTE
	CP	$FF			; TEST FOR $FF (HBIOS NOT READY)
	JR	Z,CST1			; IF NOT READY, TRY DEBUG DEBUG STATUS
;
	; USE HBIOS
	LD	C,A			; CONSOLE UNIT TO C
	LD	B,BF_CIOIST		; HBIOS FUNC: INPUT STATUS
	CALL	CIO_DISPATCH		; CALL CIO DISPATCHER DIRECTLY
	JR	CST2			; CONTINUE
;
CST1:
;
#IF (WBWDEBUG == USEXIO)
	CALL	XIO_IST			; GET STATUS
#ENDIF
;
#IF (WBWDEBUG == USEMIO)
	CALL	MIO_IST			; GET STATUS
#ENDIF
;
CST2:
	; RESTORE REGISTERS (AF IS OUTPUT)
	POP	HL
	POP	DE
	POP	BC
	RET
;
;==================================================================================================
;   DYNAMIC RAM SIZE DETECTION ROUTINE
;==================================================================================================
;
; THIS CODE IS COPIED TO $F000 TO PERFORM RAM SIZE DETECTION.
;
#IFDEF SIZERAM
;
RS_IMAGE:
	.ORG	$F000
RS_START:
	LD	A,(HB_CURBNK)		; GET CURRENT BANK
	PUSH	AF			; SAVE IT

	LD	C,0			; RUNNING BANK COUNT
	LD	HL,$7FFF		; BYTE TEST ADDRESS
	LD	IX,RS_ARY		; ORIG BYTE STORAGE ARRAY PTR
RS_LOOP1:
	LD	A,C
	ADD	A,$80			; OFFSET BY START OF RAM BANKS
	CALL	RS_BNKSEL		; SELECT THE BANK

	LD	A,(HL)			; GET ORIGINAL VALUE
	LD	(IX),A			; SAVE IT TO RESTORE LATER
	INC	IX			; BUMP IX

	LD	A,$AA			; TEST LOC WITH $AA
	LD	(HL),A			; AVOID PROBLEMS WITH
	LD	(HL),A			; ... DS1210
	LD	(HL),A
	LD	A,(HL)
	CP	$AA
	JR	NZ,RS_DONE

	LD	A,$55			; TEST LOC WITH $55
	LD	(HL),A
	LD	A,(HL)
	CP	$55
	JR	NZ,RS_DONE

	; STORE A UNIQUE VALUE
	LD	A,C
	LD	(HL),A
	OR	A			; ZERO?
	JR	Z,RS_NEXT		; SKIP STORED VALUE CHECK

	; VERIFY ALL STORED VALUES
	LD	B,C			; INIT LOOP COUNTER
	LD	E,0			; INIT BANK ID
RS_LOOP3:
	LD	A,E
	ADD	A,$80
	CALL	RS_BNKSEL
	LD	A,(HL)
	CP	E			; VERIFY
	JR	NZ,RS_DONE		; ABORT IF MISCOMPARE
	INC	E			; NEXT BANK
	DJNZ	RS_LOOP3
;
RS_NEXT:
	INC	C			; ADD 1 TO RAM BANK COUNT
	JR	RS_LOOP1		; AND LOOP TILL DONE
;
RS_DONE:
	LD	E,C			; FINAL BANK COUNT TO E
	LD	A,C
	OR	A
	JR	Z,RS_LOOPZ
	; RESTORE SAVED VALUES
	LD	IX,RS_ARY
	LD	B,C			; LOOP COUNT
	LD	C,$80			; BANK ID
RS_LOOP2:
	LD	A,C
	CALL	RS_BNKSEL
	INC	C
	LD	A,(IX)			; GET VALUE
	LD	(HL),A			; RESTORE IT
	INC	IX
	DJNZ	RS_LOOP2		; ALL BANKS
RS_LOOPZ:
;
	; RETURN TO ORIGINAL BANK
	POP	AF
	CALL	RS_BNKSEL
	LD	A,E			; RETURN BANK COUNT
	RET
;
; SPECIAL BANK SELECT FOR MEMORY SIZING
;
RS_BNKSEL:
  #IF (MEMMGR == MM_Z280)
	; IF Z280 MEMMGR, THEN WE ARE IN SYSTEM MODE, SO WE NEED TO
	; BANK SELECT THE SYSTEM PDRS INSTEAD OF THE NORMAL USER PDRS.
	PUSH	BC			; SAVE BC
	PUSH	HL			; SAVE HL
	LD	B,$10			; FIRST SYSTEM PDR
	CALL	Z280_BNKSEL		; DO IT
	POP	HL			; RESTORE HL
	POP	BC			; RESTORE BC
	RET				; DONE
  #ELSE
	; NORMAL BANK SELECT
	JP	HBX_BNKSEL
  #ENDIF
;
RS_ARY	.EQU	$
;
RS_LEN	.EQU	$ - RS_START
	.ORG	RS_IMAGE + RS_LEN
;
#ENDIF
;
;==================================================================================================
; FRONT PANEL SUPPORT
;==================================================================================================
;
; FRONT PANEL HARDWARE DETECTION
;
; WE ARE REALLY JUST CHECKING FOR SWITCHES.  NO WAY TO QUERY FOR
; LEDS.  WE CHECK FOR I/O CONFLICT WITH VGARC IF ACTIVE.
;
FP_DETECT:
	; D: LEDS ACTIVE, E: SWITCHES ACTIVE
	LD	D,TRUE			; ASSUME ACTIVE FOR NOW
	LD	E,TRUE			; ASSUME ACTIVE FOR NOW
;
	; IF VGARC IS ENABLED, CHECK IF IT IS ACTIVE.  IF SO AND THE
	; I/O PORTS CONFLICT, DEACTIVATE FRONT PANEL.
;
  #IF (VRCENABLE)
	LD	A,(VRC_ACTIVE)		; GET VGARC ACTIVE STATUS
	OR	A			; SET FLAGS
	JR	Z,FP_DETECT1		; IF NO, CONTINUE
    #IF ((FPLED_IO >= $00) & (FPLED_IO <= $0F))
	; CONFLICT, DEACTIVATE LEDS
	LD	D,FALSE			; FP LEDS NOT ACTIVE
    #ENDIF
    #IF ((FPSW_IO >= $00) & (FPSW_IO <= $0F))
	; CONFLICT, DEACTIVATE SWITCHES
	LD	E,FALSE			; FP SWITCHES NOT ACTIVE
    #ENDIF
  #ENDIF
;
FP_DETECT1:
	; THE SWITCH HARDWARE MAY OR MAY NOT BE INSTALLED.  SO, HERE WE
	; ATTEMPT TO CONFIRM WE HAVE A VALID PORT.  CREDIT TO STEPHEN
	; COUSINS FOR THIS APPROACH.
	LD	C,FPSW_IO		; ADR OF SWITCH PORT
	EZ80_IO
	IN	C,(C)			; READ IT USING IN (C)
	EZ80_IO
	IN	A,(FPSW_IO)		; READ IT USING IN (PORT)
	CP	C			; PORT FLOATING ON MISMATCH
	JR	NZ,FP_DETECT2		; NO H/W, SET FLAG
	CP	$FF			; PORT FLOATING ON $FF
	JR	Z,FP_DETECT2		; NO H/W, SET FLAG
	JR	FP_DETECTZ		; H/W EXISTS, DONE
;
FP_DETECT2:
	LD	E,FALSE			; RECORD NOT PRESENT
;
FP_DETECTZ:
	LD	(FP_ACTIVE),DE		; RECORD RESULTS
	RET				; DONE
;
#IF (FPLED_ENABLE)
;
; SET FRONT PANEL LEDS FROM VALUE IN A
;
FP_SETLEDS:
	PUSH	HL			; SAVE HL
	LD	L,A			; LED VALUE TO L
	LD	A,(FPLED_ACTIVE)	; LEDS ACTIVE?
	OR	A			; SET FLAGS
	LD	A,L			; RESTORE REG A
	JR	Z,FP_SETLEDS1		; BAIL OUT IF NOT ACTIVE
  #IF (FPLED_INV)
	XOR	$FF			; INVERT BITS IF NEEDED
  #ENDIF

	EZ80_IO
	OUT	(FPLED_IO),A		; WRITE
FP_SETLEDS1:
	POP	HL			; RESTORE HL
	RET				; DONE
;
#ENDIF
;
#IF (FPSW_ENABLE)
;
; GET FRONT PANEL SWITCH SETTINGS
;
FP_GETSWITCHES:
	LD	A,(FPSW_ACTIVE)		; SWITCHES ACTIVE?
	OR	A			; SET FLAGS
	RET	Z			; BAIL OUT IF NOT ACTIVE
	EZ80_IO
	IN	A,(FPSW_IO)		; READ SWITCHES
  #IF (FPSW_INV)
	XOR	$FF			; INVERT BITS IF NEEDED
  #ENDIF
	RET				; DONE
;
;
#ENDIF
;
FP_ACTIVE:
FPSW_ACTIVE	.DB	TRUE
FPLED_ACTIVE	.DB	TRUE

#IF (CPUFAM != CPU_EZ80) ; eZ80 WILL RETURNED ITS MEASURED CPUOSC - SO NO NEED FOR DETECTION HERE
;
;==================================================================================================
; CPU SPEED DETECTION USING DS-1302 RTC
;==================================================================================================
;
HB_CPUSPD:
;
#IF (DSRTCENABLE & ((CPUFAM == CPU_Z80) | (CPUFAM == CPU_Z180)))
;
	LD	A,(DSRTC_STAT)		; GET RTC STATUS
	OR	A			; SET FLAGS
	RET	NZ			; NOT ZERO IS ERROR
;
HB_CPUSPD1:
  #IF (CPUFAM == CPU_Z180)
	; USE MEM W/S = 2 AND I/O W/S = 3 FOR TEST
	IN0	A,(Z180_DCNTL)
	PUSH	AF
	LD	A,$B0
	;LD	A,$F0
	OUT0	(Z180_DCNTL),A
  #ENDIF

	; WAIT FOR AN INITIAL TICK TO ALIGN, THEN WAIT
	; FOR A SECOND TICK TO GET A FULL ONE SECOND LOOP COUNT.
	; WAITSEC WILL SET ZF IF AN OVERFLOW OCCURS (MEANING THAT THE
	; CLOCK IS NOT TICKING).  THERE IS NO
	; POINT IN CALLING HB_WAITSEC AGAIN IN THAT CASE, SO WE ONLY
	; CALL HB_WAITSEC AGAIN IF ZF IS NOT SET.
	CALL	DSRTC_START
	CALL	HB_RDSEC		; GET SECONDS
	LD	(HB_CURSEC),A		; AND INIT CURSEC
	CALL	HB_WAITSEC		; WAIT FOR SECONDS TICK
	LD	(HB_CURSEC),A		; SAVE NEW VALUE
	; CALL HB_WAITSEC AGAIN, BUT ONLY IF ZF IS NOT SET
	CALL	NZ,HB_WAITSEC		; WAIT FOR SECONDS TICK
;
  #IF (CPUFAM == CPU_Z180)
	; RESTORE W/S SETTINGS FROM BEFORE TEST
	POP	AF
	OUT0	(Z180_DCNTL),A
  #ENDIF
;
	; MOVE LOOP COUNT TO HL
	PUSH	DE
	POP	HL
;
	; CHECK FOR OVERFLOW (NOT TICKING)
	LD	A,H
	OR	L
	JR	Z,HB_CPUSPD2		; FAILURE, USE DEFAULT CPU SPEED
;
	; TIMES 4 FOR CPU SPEED IN KHZ
;	RES	0,L			; GRANULARITY
	SLA	L
	RL	H
	SLA	L
	RL	H
;
	; RETURN CURRENT CPU SPD (KHZ) IN HL
	XOR	A			; SIGNAL SUCCESS
	RET
;
HB_WAITSEC:
	; WAIT FOR SECONDS TICK
	; RETURN SECS VALUE IN A, LOOP COUNT IN DE
	; ZF IS SET ON OVERFLOW (CLOCK NOT TICKING)
	LD	DE,0			; INIT LOOP COUNTER
HB_WAITSEC1:
;
  #IF (CPUFAM == CPU_Z80)
	; LOOP TARGET IS 4000 T-STATES, SO CPU FREQ IN KHZ = LOOP COUNT * 4
	CALL	DLY32
	CALL	DLY16
	CALL	DLY1			; 27 TSTATES
	SBC	HL,HL			; 15 TSTATES
	SBC	HL,HL			; 15 TSTATES
	INC	HL			; 6 TSTATES
	INC	HL			; 6 TSTATES
  #ENDIF
;
  #IF (CPUFAM == CPU_Z180)
	; LOOP TARGET IS 4000 T-STATES, SO CPU FREQ IN KHZ = LOOP COUNT * 4
	CALL	DLY2
	ADD	IX,BC			; 10 + 4 = 14 TSTATES
	NOP				; 5 TSTATES
	NOP				; 5 TSTATES
	NOP				; 5 TSTATES
	NOP				; 5 TSTATES
  #ENDIF
;
	PUSH	DE			; SAVE COUNTER
	CALL	HB_RDSEC		; GET SECONDS
	POP	DE			; RESTORE COUNTER
	INC	DE			; BUMP COUNTER
	LD	HL,HB_CURSEC		; POINT TO COMP VALUE
	CP	(HL)			; TEST FOR CHANGE
	RET	NZ			; DONE IF TICK OCCURRED
	LD	A,D			; CHECK DE
	OR	E			; ... FOR OVERFLOW
	RET	Z			; TIMEOUT, SOMETHING IS WRONG
	JR	HB_WAITSEC1		; LOOP
;
HB_RDSEC:
	; READ SECONDS BYTE INTO A
	LD	E,$81			; SECONDS REGISTER
	CALL	DSRTC_CMD		; SEND THE COMMAND
	CALL	DSRTC_GET		; READ THE REGISTER
	CALL	DSRTC_END		; FINISH IT
	LD	A,E			; VALUE TO A
	RET
;
#ENDIF
;
HB_CPUSPD2:
	; HANDLE NO RTC OR NOT TICKING
	OR	$FF			; SIGNAL ERROR
	RET				; AND DONE
#ENDIF ; CPUFAM != CPU_EZ80
;
HB_UTIL_END	.EQU	$
;
;==================================================================================================
; DISPLAY SUMMARY OF ATTACHED UNITS/DEVICES
;==================================================================================================
;
HB_PRTSUM_BEG	.EQU	$
;
PRTSUM:
	CALL	NEWLINE2		; SKIP A LINE
	LD	DE,PS_STRHDR		; POINT TO HEADER
	CALL	WRITESTR		; PRINT IT
;
	LD	C,BF_SYSGET_CIOCNT	; CHARACTER DEVICES
	LD	HL,PS_SERIAL
	CALL	PRT_ALLD
;
	LD	C,BF_SYSGET_DIOCNT	; DISK DRIVES
	LD	HL,PS_DISK
	CALL	PRT_ALLD
;
	LD	C,BF_SYSGET_VDACNT	; VIDEO DEVICES
	LD	HL,PS_VIDEO
	CALL	PRT_ALLD
;
	LD	C,BF_SYSGET_SNDCNT	; SOUND DEVICES
	LD	HL,PS_SOUND
	CALL	PRT_ALLD
	RET
;
PRT_ALLD:
	LD	B,BF_SYSGET		; FUNC: SYSTEM INFO GET
	RST	08			; E := UNIT COUNT
	LD	B,E			; MOVE TO B FOR LOOP COUNT
	LD	A,E			; MOVE TO ACCUM
	OR	A			; SET FLAGS
	RET	Z			; IF NONE, JUST RETURN
	LD	C,0			; C WILL BE UNIT INDEX
PRT_ALLD1:
	PUSH	BC			; SAVE LOOP CONTROL
	PUSH	DE
	PUSH	HL
	CALL	JPHL			; CALL THE ROUTINE PASSED IN HL
	POP	HL
	POP	DE
	POP	BC			; RESTORE LOOP CONTROL
	INC	C			; BUMP UNIT INDEX
	DJNZ	PRT_ALLD1		; LOOP THRU ALL DEVICES
	RET
;
; PRINT ONE LINE DISK UNIT/DEVICE INFO, DISK UNIT INDEX IN C
;
PS_DISK:
	PUSH	BC			; SAVE UNIT INDEX FOR LATER
;
	; UNIT COLUMN
	PRTS("Disk $")
	LD	A,C			; MOVE UNIT NUM TO A
	CALL	PRTDECB			; PRINT IT
	CP	10			; CHECK FOR MULTIPLE DIGITS
	CALL	C,PC_SPACE		; EXTRA SPACE IF NEEDED
	PRTS("     $")			; PAD TO NEXT COLUMN
;
	; DEVICE COLUMN
	LD	B,BF_DIODEVICE		; FUNC=GET DEVICE INFO, UNIT NUM STILL IN C
	RST	08			; DE:=DEVTYP/NUM, C:=DISK ATTRIBUTES
	PUSH	BC			; SAVE ATTRIBUTES
	LD	HL,PS_DDMD		; POINT TO DISK DEVICE TYPE NAME TABLE
	CALL	PS_PRTDEV		; PRINT DISK DEVICE NMEMONIC PADDED TO FIELD WIDTH
	POP	DE			; RECOVER ATTRIBUTES TO DE
	PUSH	DE			; SAVE ATTRIBUTES AGAIN
	CALL	PS_PRTDT		; PRINT DISK TYPE
	POP	DE			; RESTORE ATTRIBUTES
	POP	BC			; RESTORE UNIT NUM
	CALL	PS_PRTDC		; PRINT DISK CAPACITY
;
	CALL	NEWLINE
	RET
;
; PRINT DISK TYPE (DISK ATTRIBUTE IN E)
;
PS_PRTDT:
	LD	A,E			; ATTRIBUTES TO A
	BIT	7,A			; FLOPPY BIT SET?
	LD	HL,PS_DTFLOP		; ASSUME FLOPPY
	JP	NZ,PS_PRT18		; IF FLOPPY, JUMP AHEAD
	LD	C,E
	LD	DE,PS_DTHARD
	LD	A,00001111B
	CALL	PRTIDXMSK
	CALL	PS_PAD18		; PAD TO 18 SPACES
	RET
;
; PRINT DISK CAPACITY (UNIT IN C, ATTRIBUTE IN E)
;
PS_PRTDC:
;
	LD	A,E			; ATTRIBUTE TO ACCUM
	BIT	7,A			; TEST FOR FLOPPY
	JR	NZ,PS_PRTDC2		; HANDLE FLOPPY
	AND	$0F			; ISOLATE TYPE BITS
	CP	4			; ROM DISK?
	JR	Z,PS_PRTDC1		; PRINT CAPACITY IN KB
	CP	5			; RAM DISK?
	JR	Z,PS_PRTDC1		; PRINT CAPACITY IN KB
	CP	7			; FLASH DISK?
	JR	Z,PS_PRTDC1		; PRINT CAPACITY IN KB
;
	; PRINT HARD DISK STORAGE SIZE IN MB
	LD	B,BF_DIOCAP		; HBIOS FUNC: GET CAPACTIY
	RST	08			; DE:HL := BLOCKS
	JP	NZ,PS_PRTNUL		; MEDIA PROBLEM
	RES	7,D			; CLEAR LBA BIT
	LD	B,11			; 11 BIT SHIFT TO CONVERT BLOCKS --> MB
	CALL	SRL32			; RIGHT SHIFT
	CALL	PRTDEC32		; PRINT DWORD IN DECIMAL
	PRTS("MB$")			; PRINT SUFFIX
	CALL	PC_COMMA
	PRTS("LBA$")			; FOR NOW, WE ASSUME HARD DISK DOES LBA
	RET				; DONE
;
PS_PRTDC1:
	; PRINT ROM/RAM DISK CAPACITY IN KB
	LD	B,BF_DIOCAP		; HBIOS FUNC: GET CAPACTIY
	RST	08			; DE:HL := BLOCKS
	JP	NZ,PS_PRTNUL		; MEDIA PROBLEM
	RES	7,D			; CLEAR LBA BIT
	LD	B,1			; 11 BIT SHIFT TO CONVERT BLOCKS --> MB
	CALL	SRL32			; RIGHT SHIFT
	CALL	PRTDEC32		; PRINT DWORD IN DECIMAL
	PRTS("KB$")			; PRINT SUFFIX
	CALL	PC_COMMA
	PRTS("LBA$")			; FOR NOW, WE ASSUME HARD DISK DOES LBA
	RET				; DONE
;
PS_PRTDC2:
	LD	C,E			; ATTRIBUTE TO C FOR SAFE KEEPING
;
	LD	A,%00011000		; DISPLAY FORM FACTOR
	LD	DE,PS_FLP_FSTR		; WHICH IS DEFINED IN
	CALL	PRTIDXMSK		; BITS 5 AND 6.
;
	LD	A,%00000100		; DISPLAY SIDES
	LD	DE,PS_FLP_SSTR		; WHICH IS DEFINED
	CALL	PRTIDXMSK		; IN BIT 4
;
	LD	A,%00000011		; DISPLAY DENSITY
	LD	DE,PS_FLP_DSTR		; WHICH IS DEFINED IN
	CALL	PRTIDXMSK		; BITS 2 AND 3.
;
	CALL	PC_COMMA
	PRTS("CHS$")			; FOR NOW, WE ASSUME HARD DISK DOES LBA
;
	RET				; DONE
;
; PRINT ONE LINE SERIAL UNIT/DEVICE INFO, SERIAL UNIT INDEX IN C
;
PS_SERIAL:
	PUSH	BC			; SAVE UNIT INDEX FOR LATER
;
	; UNIT COLUMN
	PRTS("Char $")
	LD	A,C			; MOVE UNIT NUM TO A
	CALL	PRTDECB			; PRINT IT, ASSUME SINGLE DIGIT
	PRTS("      $")			; PAD TO NEXT COLUMN
;
	; DEVICE COLUMN
	LD	B,BF_CIODEVICE		; FUNC=GET DEVICE INFO, UNIT NUM STILL IN C
	RST	08			; DE:=DEVTYP/NUM, C:=DEVICE ATTRIBUTES
	PUSH	BC			; SAVE ATTRIBUTES
	LD	HL,PS_SDUART		; POINT TO SERIAL DEVICE TYPE NAME TABLE
	CALL	PS_PRTDEV		; PRINT SERIAL DEVICE NMEMONIC PADDED TO FIELD WIDTH
	POP	BC			; RECOVER ATTRIBUTES
	PUSH	BC			; SAVE ATTRIBUTES AGAIN
	CALL	PS_PRTST		; PRINT SERIAL TYPE
	POP	BC			; RESTORE ATTRIBUTES
	POP	DE			; RESTORE UNIT NUM TO E
	CALL	PS_PRTSC		; PRINT SERIAL CONFIG
;
	CALL	NEWLINE
	RET
;
; PRINT CHARACTER TYPE (SERIAL ATTRIBUTE IN E)
;
PS_PRTST:
	LD	HL,PS_STPPT
	BIT	6,C
	JP	NZ,PS_PRT18		; PARALLEL TYPE?
	LD	HL,PS_STRS232		; ASSUME RS-232
	BIT	7,C			; 0=RS-232, 1=TERMINAL
	JP	Z,PS_PRT18		; HANDLE TERMINAL TYPE
	LD	HL,PS_STTERM		; TYPE IS TERMINAL
	JP	PS_PRT18
;
; PRINT SERIAL CONFIG (UNIT IN E, ATTRIBUTE IN C)
;
PS_PRTSC:
	BIT	6,C			; PARALLEL TYPE?
	JR	NZ,PSPRTPC0

	BIT	7,C			; 0=RS-232, 1=TERMINAL
	JP	NZ,PS_PRTSC1		; PRINT TERMINAL CONFIG
;
	; PRINT RS-232 CONFIG
	LD	B,BF_CIOQUERY		; HBIOS FUNC: GET CIO CONFIG
	LD	C,E			; SET SERIAL UNIT NUM
	RST	08			; DE:HL := BAUD RATE
	LD	A,D			; TEST FOR $FF
	AND	E
	INC	A			; SET Z IF DE == $FF
	JP	Z,PS_PRTNUL		; $FF == NO CONFIG DEFINED
;
PS_PRTSC0:
	; PRINT BAUD RATE
	PUSH	DE			; PRESERVE DE
	LD	A,D
	AND	$1F			; ISOLATE ENCODED BAUD RATE
	LD	L,A			; PUT IN L
	LD	H,0			; H IS ALWAYS ZERO
	LD	DE,75			; BAUD RATE DECODE CONSTANT
	CALL	DECODE			; DE:HL := BAUD RATE
	LD	BC,HB_BCDTMP		; POINT TO TEMP BCD BUF
	CALL	BIN2BCD			; CONVERT TO BCD
	CALL	PRTBCD			; AND PRINT IN DECIMAL
	POP	DE			; RESTORE DE
;
	; PRINT DATA BITS
	PUSH	DE			; PRESERVE DE
	CALL	PC_COMMA		; FORMATTING
	LD	A,E			; GET CONFIG BYTE
	AND	$03			; ISOLATE DATA BITS VALUE
	ADD	A,'5'			; CONVERT TO CHARACTER
	CALL	COUT			; AND PRINT
	POP	DE			; RESTORE DE
;
	; PRINT PARITY
	PUSH	DE			; PRESERVE DE
	CALL	PC_COMMA		; FORMATTING
	LD	A,E			; GET CONFIG BYTE
	RRCA				; SHIFT RELEVANT BITS
	RRCA				; ...
	RRCA				; ...
	AND	$07			; AND ISOLATE DATA BITS VALUE
	LD	HL,PS_STPARMAP		; CHARACTER LOOKUP TABLE
	CALL	ADDHLA			; APPLY OFFSET
	LD	A,(HL)			; GET CHARACTER
	CALL	COUT			; AND PRINT
	POP	DE			; RESTORE DE
;
	; PRINT STOP BITS
	CALL	PC_COMMA		; FORMATTING
	LD	A,E			; GET CONFIG BYTE
	RRCA				; SHIFT RELEVANT BITS
	RRCA				; ...
	AND	$01			; AND ISOLATE DATA BITS VALUE
	ADD	A,'1'			; MAKE IT A CHARACTER
	CALL	COUT			; AND PRINT
;
	RET
;
PSPRTPC0:
	LD	B,BF_CIOQUERY		; HBIOS FUNC: GET CIO CONFIG
	LD	C,E			; SET PARALLEL UNIT NUM
	RST	08			; DE:HL := I/O SETTING
	LD	A,D			; TEST FOR $FF
	AND	E
	INC	A			; SET Z IF DE == $FF
	JP	Z,PS_PRTNUL		; $FF == NO CONFIG DEFINED
;
PS_PRTPC0:
	LD	C,E			; DISPLAY PIO TYPE
	LD	A,11000000B		; WHICH IS DEFINE BY
	LD	DE,PIO_MODE_STR		; BITS 6 AND 7
	JP	PRTIDXMSK
;	RET				; TRICK RETURN
;
PS_PRTSC1:
	; PRINT TERMINAL CONFIG
	LD	A,C			; GET ATTRIBUTE VALUE
	CP	$BF			; NO ATTACHED VDA
	JR	Z,PS_PRTSC2
	PRTS("Video $")			; FORMATTING
	AND	$0F			; ISOLATE VIDEO UNIT NUM
	CALL	PRTDECB			; PRINT IT
	CALL	PC_COMMA
#IF (VDAEMU == EMUTYP_TTY)
	PRTS("TTY$")
#ENDIF
#IF (VDAEMU == EMUTYP_ANSI)
	PRTS("ANSI$")
#ENDIF
	RET
;
PS_PRTSC2:
	PRTS("Term Module$")
	CALL	PC_COMMA
	PRTS("ANSI$")
	RET
;
; PRINT ONE LINE VIDEO UNIT/DEVICE INFO, VIDEO UNIT INDEX IN C
;
PS_VIDEO:
	PUSH	BC			; SAVE UNIT INDEX FOR LATER
;
	; UNIT COLUMN
	PRTS("Video $")
	LD	A,C			; MOVE UNIT NUM TO A
	CALL	PRTDECB			; PRINT IT, ASSUME SINGLE DIGIT
	PRTS("     $")			; PAD TO NEXT COLUMN
;
	; DEVICE COLUMN
	LD	B,BF_VDADEV		; FUNC=GET DEVICE INFO, UNIT NUM STILL IN C
	RST	08			; DE:=DEVTYP/NUM, H:=DISK ATTRIBUTES
	PUSH	BC			; SAVE ATTRIBUTES
	LD	HL,PS_VDVDU		; POINT TO VIDEO DEVICE TYPE NAME TABLE
	CALL	PS_PRTDEV		; PRINT VIDEO DEVICE NMEMONIC PADDED TO FIELD WIDTH
	POP	DE			; RECOVER ATTRIBUTES
	PUSH	DE			; SAVE ATTRIBUTES AGAIN
	CALL	PS_PRTVT		; PRINT VIDEO TYPE
	POP	DE			; RESTORE ATTRIBUTES
	POP	BC			; RESTORE UNIT NUM
	CALL	PS_PRTVC		; PRINT VIDEO CONFIG
;
	CALL	NEWLINE
	RET
;
; PRINT VIDEO TYPE (VIDEO ATTRIBUTE IN E)
;
PS_PRTVT:
	LD	HL,PS_VTCRT		; ASSUME CRT
	JP	PS_PRT18		; PRINT
;
; PRINT VIDEO CONFIG (UNIT IN C, ATTRIBUTE IN E)
;
PS_PRTVC:
	PRTS("Text$")
	CALL	PC_COMMA
	LD	B,BF_VDAQRY		; FUNC: QUERY FOR VDA CONFIG
	RST	08			; D:=ROWS, E:=COLS
	LD	A,E
	CALL	PRTDECB
	LD	A,'x'
	CALL	COUT
	LD	A,D
	CALL	PRTDECB
	RET
;
; PRINT SOUND CONFIG
;
PS_SOUND:
	PUSH	BC

	; UNIT COLUMN
	PRTS("Sound $")
	LD	A,C			; MOVE UNIT NUM TO A
	CALL	PRTDECB			; PRINT IT
	CP	10			; CHECK FOR MULTIPLE DIGITS
	CALL	C,PC_SPACE		; EXTRA SPACE IF NEEDED
	PRTS("    $")			; PAD TO NEXT COLUMN

	; DEVICE COLUMN

	PUSH	BC
	LD	E,C
	XOR	A
	LD	DE,PS_SDSND		; POINT TO DEVICE TYPE NAME TABLE
	CALL	PRTIDXDEA		; PRINT  DEVICE NMEMONIC PADDED TO FIELD WIDTH
	LD	A,C			; MOVE UNIT NUM TO A
	CALL	PRTDECB			; PRINT IT
	CALL	PC_COLON
	LD	A,(PRTIDXCNT)
	SUB	12-1			; SUBTRACT FIELD WIDTH (LESS THE COLON)
	NEG				; MAKE IT A POSITIVE NUMBER
	CALL	PS_PAD			; PAD AS NEEDED
	POP	BC

	; DEVICE TYPE
;
	LD	B,BF_SNDQUERY		; FUNC=GET DEVICE INFO, UNIT NUM STILL IN C
	LD	E,BF_SNDQ_DEV
	RST	08
	PUSH	BC
	LD	A,B
	LD	DE,PS_SDSN76489
	CALL	PRTIDXDEA
	CALL	PS_PAD18
	POP	BC
;
	; DEVICE CHARACTERISTICS
;
	LD	B,BF_SNDQUERY		; FUNC=GET DEVICE INFO, UNIT NUM STILL IN C
	LD	E,BF_SNDQ_CHCNT
	RST	08
	LD	A,B
	CALL	PRTDECB
	LD	A,'+'
	CALL	COUT
	LD	A,C
	CALL	PRTDECB
	PRTS(" CHANNELS$")
	CALL	NEWLINE
;
	POP	BC
	RET
;
; PRINT DEVICE NMEMONIC, DEVTYP/NUM SPECIFIED IN DE
;
PS_PRTDEV:
	EX	DE,HL
	LD	A,H			; TYPE ID
	CALL	PRTIDXDEA		; PRINT TYPE LABEL
	LD	A,L			; UNIT NUMBER
	CALL	PRTDECB			; PRINT NUM, ASSUME 1 CHAR
	CALL	PC_COLON		; PRINT COLON
	LD 	A,(PRTIDXCNT)
	SUB	12-2+1			; 12 CHAR FIELD - 1 POS FOR UNIT NUM AND 1 POS FOR COLON
	NEG
	CALL	PS_PAD			; PAD N SPACES (SPECIFIED IN A)
	RET
;
; PRINT DEVICE MNEMONIC, DEVTYP/NUM SPECIFIED IN DE
;
PS_PRTNUL:
	LD	HL,PS_STRNUL
	; FALL THRU TO PS_PRT
;
; PRINT STRING AT (HL), $ TERM, RETURN CHARS PRINTED IN C
;
PS_PRT:	LD	C,0			; INIT CHAR COUNT
PS_PRT1:
	LD	A,(HL)			; GET CHAR
	INC	HL			; BUMP INDEX
	CP	'$'			; TERM?
	RET	Z			; IF SO, DONE
	CALL	COUT			; PRINT IT
	INC	C			; BUMP COUNTER
	JR	PS_PRT1			; AND LOOP
;
; PAD 18 CHARACTER FIELD
;
PS_PAD18:
	LD	A,(PRTIDXCNT)
	LD	C,A
	JR	PS_PRT18A
;
; PRINT STRING AT HL IN 18 CHARACTER FIELD
;
PS_PRT18:
	CALL	PS_PRT			; PRINT $ TERM STRING AT (HL), C:=CHARS PRINTED
PS_PRT18A:
	LD	A,18			; 18 CHAR FIELD
	SUB	C
;	CALL	PS_PAD			; PAD N SPACES (SPECIFIED IN A)
;
; PAD N SPACES SPECIFIED IN A
;
PS_PAD:	LD	B,A
	LD	A,' '
PS_PAD1:
	CALL	COUT
	DJNZ	PS_PAD1
	RET
;
HB_CPU_STR:	.TEXT	" Z80$"
		.TEXT	" Z80180$"
		.TEXT	" Z8S180-K$"
		.TEXT	" Z8S180-N$"
		.TEXT	" Z80280$"
		.TEXT   " eZ80$"
;
PS_STRNUL	.TEXT	"--$"		; DISPLAY STRING FOR NUL VALUE
;
; DISK DEVICE STRINGS
;
PS_DDMD		.TEXT	"MD$"
PS_DDFD		.TEXT	"FD$"
PS_DDRF		.TEXT	"RF$"
PS_DDIDE	.TEXT	"IDE$"
PS_DDATAPI	.TEXT	"ATAPI$"
PS_DDPPIDE	.TEXT	"PPIDE$"
PS_DDSD		.TEXT	"SD$"
PS_DDPRPSD	.TEXT	"PRPSD$"
PS_DDPPPSD	.TEXT	"PPPSD$"
PS_DDHDSK	.TEXT	"HDSK$"
PS_DDPPA	.TEXT	"PPA$"
PS_DDIMM	.TEXT	"IMM$"
PS_DDSYQ	.TEXT	"SYQ$"
PS_DDCHUSB	.TEXT	"CHUSB$"
PS_DDCHSD	.TEXT	"CHSD$"
;
; DISK TYPE STRINGS
;
PS_DTFLOP	.TEXT	"Floppy Disk$"
PS_DTHARD	.TEXT	"Hard Disk$"
PS_DTCF		.TEXT	"CompactFlash$"
PS_DTSD		.TEXT	"SD Card$"
PS_DTUSB	.TEXT	"USB Drive$"
PS_DTROM	.TEXT	"ROM Disk$"
PS_DTRAM	.TEXT	"RAM Disk$"
PS_DTRF		.TEXT	"RAM Floppy$"
PS_DTFSH	.TEXT	"Flash Drive$"
PS_DTCD		.TEXT	"CD-ROM$"
PS_DTCRT	.TEXT	"Cartridge$"
PS_DTOTHER	.TEXT	"???$"
;
; FLOPPY ATTRIBUTE STRINGS
;
PS_FLP_FSTR:	.TEXT	"8\",$"		; PS_FLP8
		.TEXT	"5.25\",$"	; PS_FLP5
		.TEXT	"3.5\",$"	; PS_FLP3
		.TEXT	"???\",$"	; PS_FLPN
;
PS_FLP_SSTR:	.TEXT	"SS/$"		; PS_FLPSS
		.TEXT	"DS/$"		; PS_FLPDS
;
PS_FLP_DSTR:	.TEXT	"SD$"		; PS_FLPSD
		.TEXT	"DD$"		; PS_FLPDD
		.TEXT	"HD$"		; PS_FLPHD
		.TEXT	"ED$"		; PS_FLPED
;
; CHARACTER DEVICE STRINGS
;
PS_SDUART	.TEXT	"UART$"
PS_SDASCI	.TEXT	"ASCI$"
PS_SDTERM	.TEXT	"TERM$"
PS_SDPRPCON	.TEXT	"PRPCON$"
PS_SDPPPCON	.TEXT	"PPPCON$"
PS_SDSIO	.TEXT	"SIO$"
PS_SDACIA	.TEXT	"ACIA$"
PS_SDPIO	.TEXT	"PIO$"
PS_SDUF		.TEXT	"UF$"
PS_SDDUART	.TEXT	"DUART$"
PS_SDZ2U	.TEXT	"Z2U$"
PS_SDLPT	.TEXT	"LPT$"
PS_SDESPCON	.TEXT	"ESPCON$"
PS_SDESPSER	.TEXT	"ESPSER$"
PS_SDSCON	.TEXT	"SCON$"
PS_SDEF		.TEXT	"EF$"
PS_SDSSER	.TEXT	"SSER$"
;
; CHARACTER SUB TYPE STRINGS
;
PS_STRS232	.TEXT	"RS-232$"
PS_STTERM	.TEXT	"Terminal$"
PS_STPPT	.TEXT	"Parallel$"
;
PS_STPARMAP	.DB	"NONENMNS"
;
; PARALLEL TYPE STRINGS
;
PIO_MODE_STR:	.TEXT	"Output$"
		.TEXT	"Input$"
		.TEXT	"Bidirectional$"
		.TEXT	"BitCtrl$"
;
; VIDEO DEVICE STRINGS
;
PS_VDVDU	.TEXT	"VDU$"
PS_VDCVDU	.TEXT	"CVDU$"
PS_VDGDC	.TEXT	"GDC$"
PS_VDTMS	.TEXT	"TMS$"
PS_VDVGA	.TEXT	"VGA$"
PS_VDVRC	.TEXT	"VRC$"
PS_VDEF		.TEXT	"EF$"
PS_VDFV		.TEXT	"FV$"
;
; VIDEO TYPE STRINGS
;
PS_VTCRT	.TEXT	"CRT$"
;
; SOUND DEVICE STRINGS
;
PS_SDSND	.TEXT	"SND$"
;
; SOUND TYPE STRINGS
;
PS_SDSN76489	.TEXT	"SN76489$"
PS_SDAY38910	.TEXT	"AY-3-8910$"
PS_SDBITMODE	.TEXT	"I/O PORT$"
PS_SDYM2612	.TEXT	"YM2612$"
;
;			 0	   1	     2	       3	 4	   5	     6	       7
;			 01234567890123456789012345678901234567890123456789012345678901234567890123456789
PS_STRHDR	.TEXT	"Unit        Device      Type              Capacity/Mode\r\n"
		.TEXT	"----------  ----------  ----------------  --------------------\r\n$"
;
HB_PRTSUM_END	.EQU	$
;
;==================================================================================================
;   DEVICE DRIVERS
;==================================================================================================
;
HB_DRIVERS_BEG	.EQU	$
;
;;;#IF (DSKYENABLE)
#IF (ICMENABLE)
ORG_ICM	.EQU	$
  #INCLUDE "icm.asm"
SIZ_ICM	.EQU	$ - ORG_ICM
		MEMECHO	"ICM occupies "
		MEMECHO	SIZ_ICM
		MEMECHO	" bytes.\n"
#ENDIF
;
#IF (PKDENABLE)
ORG_PKD	.EQU	$
  #INCLUDE "pkd.asm"
SIZ_PKD	.EQU	$ - ORG_PKD
		MEMECHO	"PKD occupies "
		MEMECHO	SIZ_PKD
		MEMECHO	" bytes.\n"
#ENDIF
;;;#ENDIF
;
#IF (LCDENABLE)
ORG_LCD	.EQU	$
  #INCLUDE "lcd.asm"
SIZ_LCD	.EQU	$ - ORG_LCD
		MEMECHO	"LCD occupies "
		MEMECHO	SIZ_LCD
		MEMECHO	" bytes.\n"
#ENDIF
;
#IF (GM7303ENABLE)
ORG_GM7303	.EQU	$
  #INCLUDE "gm7303.asm"
SIZ_GM7303	.EQU	$ - ORG_GM7303
		MEMECHO	"GM7303 occupies "
		MEMECHO	SIZ_GM7303
		MEMECHO	" bytes.\n"
#ENDIF
;
#IF (H8PENABLE)
ORG_H8P	.EQU	$
  #INCLUDE "h8p.asm"
SIZ_H8P	.EQU	$ - ORG_H8P
		MEMECHO	"H8P occupies "
		MEMECHO	SIZ_H8P
		MEMECHO	" bytes.\n"
#ENDIF
;
#IF (PLATFORM == PLT_NABU)
ORG_NABU .EQU	$
  #INCLUDE "nabu.asm"
SIZ_NABU .EQU	$ - ORG_NABU
		MEMECHO	"NABU occupies "
		MEMECHO	SIZ_NABU
		MEMECHO	" bytes.\n"
#ENDIF
;
#IF (DSRTCENABLE)
ORG_DSRTC	.EQU	$
  #INCLUDE "dsrtc.asm"
SIZ_DSRTC	.EQU	$ - ORG_DSRTC
		MEMECHO	"DSRTC occupies "
		MEMECHO	SIZ_DSRTC
		MEMECHO	" bytes.\n"
#ENDIF
;
#IF (DS1501RTCENABLE)
ORG_DS1501RTC	.EQU	$
  #INCLUDE "ds1501rtc.asm"
SIZ_DS1501RTC	.EQU	$ - ORG_DS1501RTC
		MEMECHO	"DS1501RTC occupies "
		MEMECHO	SIZ_DS1501RTC
		MEMECHO	" bytes.\n"
#ENDIF
;
#IF (BQRTCENABLE)
ORG_BQRTC	.EQU	$
  #INCLUDE "bqrtc.asm"
SIZ_BQRTC	.EQU	$ - ORG_BQRTC
		MEMECHO	"BQRTC occupies "
		MEMECHO	SIZ_BQRTC
		MEMECHO	" bytes.\n"
#ENDIF
;
#IF (SIMRTCENABLE)
ORG_SIMRTC	.EQU	$
  #INCLUDE "simrtc.asm"
SIZ_SIMRTC	.EQU	$ - ORG_SIMRTC
		MEMECHO	"SIMRTC occupies "
		MEMECHO	SIZ_SIMRTC
		MEMECHO	" bytes.\n"
#ENDIF
;
#IF (PCFENABLE)
ORG_PCF		.EQU	$
  #INCLUDE "pcf.asm"
SIZ_PCF		.EQU	$ - ORG_PCF
		MEMECHO	"PCF occupies "
		MEMECHO	SIZ_PCF
		MEMECHO	" bytes.\n"
#ENDIF
;
#IF (DS5RTCENABLE)
ORG_DS5RTC	.EQU	$
  #INCLUDE "ds5rtc.asm"
SIZ_DS5RTC	.EQU	$ - ORG_DS5RTC
		MEMECHO	"DS5RTC occupies "
		MEMECHO	SIZ_DS5RTC
		MEMECHO	" bytes.\n"
#ENDIF
;
#IF (INTRTCENABLE)
ORG_INTRTC	.EQU	$
  #INCLUDE "intrtc.asm"
SIZ_INTRTC	.EQU	$ - ORG_INTRTC
		MEMECHO	"INTRTC occupies "
		MEMECHO	SIZ_INTRTC
		MEMECHO	" bytes.\n"
#ENDIF
;
#IF (RP5RTCENABLE)
ORG_RP5RTC	.EQU	$
  #INCLUDE "rp5rtc.asm"
SIZ_RP5RTC	.EQU	$ - ORG_RP5RTC
		MEMECHO	"RP5RTC occupies "
		MEMECHO	SIZ_RP5RTC
		MEMECHO	" bytes.\n"
#ENDIF
;
#IF (SSERENABLE)
ORG_SSER	.EQU	$
  #INCLUDE "sser.asm"
SIZ_SSER	.EQU	$ - ORG_SSER
		MEMECHO	"SSER occupies "
		MEMECHO	SIZ_SSER
		MEMECHO	" bytes.\n"
#ENDIF
;
#IF (ASCIENABLE)
ORG_ASCI	.EQU	$
  #INCLUDE "asci.asm"
SIZ_ASCI	.EQU	$ - ORG_ASCI
		MEMECHO	"ASCI occupies "
		MEMECHO	SIZ_ASCI
		MEMECHO	" bytes.\n"
#ENDIF
;
#IF (Z2UENABLE)
ORG_Z2U	.EQU	$
  #INCLUDE "z2u.asm"
SIZ_Z2U	.EQU	$ - ORG_Z2U
		MEMECHO	"Z2U occupies "
		MEMECHO	SIZ_Z2U
		MEMECHO	" bytes.\n"
#ENDIF
;
#IF (UARTENABLE)
ORG_UART	.EQU	$
  #INCLUDE "uart.asm"
SIZ_UART	.EQU	$ - ORG_UART
		MEMECHO	"UART occupies "
		MEMECHO	SIZ_UART
		MEMECHO	" bytes.\n"
#ENDIF
;
#IF (DUARTENABLE)
ORG_DUART	.EQU	$
  #INCLUDE "duart.asm"
SIZ_DUART	.EQU	$ - ORG_DUART
		MEMECHO	"DUART occupies "
		MEMECHO	SIZ_DUART
		MEMECHO	" bytes.\n"
#ENDIF
;
#IF (SIOENABLE)
ORG_SIO		.EQU	$
  #INCLUDE "sio.asm"
SIZ_SIO		.EQU	$ - ORG_SIO
		MEMECHO	"SIO occupies "
		MEMECHO	SIZ_SIO
		MEMECHO	" bytes.\n"
#ENDIF
;
#IF (ACIAENABLE)
ORG_ACIA	.EQU	$
  #INCLUDE "acia.asm"
SIZ_ACIA	.EQU	$ - ORG_ACIA
		MEMECHO	"ACIA occupies "
		MEMECHO	SIZ_ACIA
		MEMECHO	" bytes.\n"
#ENDIF
;
#IF (PIOENABLE)
ORG_PIO		.EQU	$
  #INCLUDE "pio.asm"
SIZ_PIO		.EQU	$ - ORG_PIO
		MEMECHO	"PIO occupies "
		MEMECHO	SIZ_PIO
		MEMECHO	" bytes.\n"
#ENDIF
;
#IF (LPTENABLE)
ORG_LPT		.EQU	$
  #INCLUDE "lpt.asm"
SIZ_LPT		.EQU	$ - ORG_LPT
		MEMECHO	"LPT occupies "
		MEMECHO	SIZ_LPT
		MEMECHO	" bytes.\n"
#ENDIF
;
#IF (PIO_4P | PIO_ZP | PIO_SBC)
ORG_PIO		.EQU	$
  #INCLUDE "pio.asm"
SIZ_PIO		.EQU	$ - ORG_PIO
		MEMECHO	"PIO occupies "
		MEMECHO	SIZ_PIO
		MEMECHO	" bytes.\n"
#ENDIF
;
#IF (UFENABLE)
ORG_UF	.EQU	$
  #INCLUDE "uf.asm"
SIZ_UF	.EQU	$ - ORG_UF
		MEMECHO	"UF occupies "
		MEMECHO	SIZ_UF
		MEMECHO	" bytes.\n"
#ENDIF
;
#IF (VGAENABLE)
ORG_VGA	.EQU	$
  #INCLUDE "vga.asm"
SIZ_VGA	.EQU	$ - ORG_VGA
		MEMECHO	"VGA occupies "
		MEMECHO	SIZ_VGA
		MEMECHO	" bytes.\n"
#ENDIF
;
#IF (CVDUENABLE)
ORG_CVDU	.EQU	$
  #INCLUDE "cvdu.asm"
SIZ_CVDU	.EQU	$ - ORG_CVDU
		MEMECHO	"CVDU occupies "
		MEMECHO	SIZ_CVDU
		MEMECHO	" bytes.\n"
#ENDIF
;
#IF (VDUENABLE)
ORG_VDU		.EQU	$
  #INCLUDE "vdu.asm"
SIZ_VDU		.EQU	$ - ORG_VDU
		MEMECHO	"VDU occupies "
		MEMECHO	SIZ_VDU
		MEMECHO	" bytes.\n"
#ENDIF
;
#IF (TMSENABLE)
ORG_TMS		.EQU	$
  #INCLUDE "tms.asm"
SIZ_TMS	.EQU	$ - ORG_TMS
		MEMECHO	"TMS occupies "
		MEMECHO	SIZ_TMS
		MEMECHO	" bytes.\n"
#ENDIF
;
#IF (EFENABLE)
ORG_EF		.EQU	$
  #INCLUDE "ef.asm"
SIZ_EF		.EQU	$ - ORG_EF
		MEMECHO	"EF occupies "
		MEMECHO	SIZ_EF
		MEMECHO	" bytes.\n"
#ENDIF
;
#IF (GDCENABLE)
ORG_GDC	.EQU	$
  #INCLUDE "gdc.asm"
SIZ_GDC	.EQU	$ - ORG_GDC
		MEMECHO	"GDC occupies "
		MEMECHO	SIZ_GDC
		MEMECHO	" bytes.\n"
#ENDIF
;
#IF (VRCENABLE)
ORG_VRC	.EQU	$
  #INCLUDE "vrc.asm"
SIZ_VRC	.EQU	$ - ORG_VRC
		MEMECHO	"VRC occupies "
		MEMECHO	SIZ_VRC
		MEMECHO	" bytes.\n"
#ENDIF
;
#IF (FVENABLE)
ORG_FV	.EQU	$
  #INCLUDE "fv.asm"
SIZ_FV	.EQU	$ - ORG_FV
		MEMECHO	"FV occupies "
		MEMECHO	SIZ_FV
		MEMECHO	" bytes.\n"
#ENDIF
;
#IF (DMAENABLE)
ORG_DMA	.EQU	$
#INCLUDE "dma.asm"
SIZ_DMA	.EQU	$ - ORG_DMA
		MEMECHO	"DMA occupies "
		MEMECHO	SIZ_DMA
		MEMECHO	" bytes.\n"
#ENDIF
;
#IF (KBDENABLE)
ORG_KBD		.EQU	$
  #INCLUDE "kbd.asm"
SIZ_KBD		.EQU	$ - ORG_KBD
		MEMECHO	"KBD occupies "
		MEMECHO	SIZ_KBD
		MEMECHO	" bytes.\n"
#ENDIF
;
#IF (PPKENABLE)
ORG_PPK		.EQU	$
  #INCLUDE "ppk.asm"
SIZ_PPK		.EQU	$ - ORG_PPK
		MEMECHO	"PPK occupies "
		MEMECHO	SIZ_PPK
		MEMECHO	" bytes.\n"
#ENDIF
;
#IF (MKYENABLE)
ORG_MKY		.EQU	$
  #INCLUDE "mky.asm"
SIZ_MKY		.EQU	$ - ORG_MKY
		MEMECHO	"MKY occupies "
		MEMECHO	SIZ_MKY
		MEMECHO	" bytes.\n"
#ENDIF
;
#IF (NABUKBENABLE)
ORG_NABUKB .EQU	$
  #INCLUDE "nabukb.asm"
SIZ_NABUKB	.EQU	$ - ORG_NABUKB
		MEMECHO	"NABUKB occupies "
		MEMECHO	SIZ_NABUKB
		MEMECHO	" bytes.\n"
#ENDIF
;
#IF (PRPENABLE)
ORG_PRP		.EQU	$
  #INCLUDE "prp.asm"
SIZ_PRP		.EQU	$ - ORG_PRP
		MEMECHO	"PRP occupies "
		MEMECHO	SIZ_PRP
		MEMECHO	" bytes.\n"
#ENDIF
;
#IF (PPPENABLE)
ORG_PPP		.EQU	$
  #INCLUDE "ppp.asm"
SIZ_PPP		.EQU	$ - ORG_PPP
		MEMECHO	"PPP occupies "
		MEMECHO	SIZ_PPP
		MEMECHO	" bytes.\n"
#ENDIF
;
#IF (SCONENABLE)
ORG_SCON		.EQU	$
  #INCLUDE "scon.asm"
SIZ_SCON	.EQU	$ - ORG_SCON
		MEMECHO	"SCON occupies "
		MEMECHO	SIZ_SCON
		MEMECHO	" bytes.\n"
#ENDIF
;
#IF (CHENABLE)
ORG_CH		.EQU	$
  #INCLUDE "ch.asm"
SIZ_CH		.EQU	$ - ORG_CH
		MEMECHO	"CH occupies "
		MEMECHO	SIZ_CH
		MEMECHO	" bytes.\n"
#ENDIF
;
#IF (ESPENABLE)
ORG_ESP		.EQU	$
  #INCLUDE "esp.asm"
SIZ_ESP		.EQU	$ - ORG_ESP
		MEMECHO	"ESP occupies "
		MEMECHO	SIZ_ESP
		MEMECHO	" bytes.\n"
#ENDIF
;
#IF (MDENABLE)
ORG_MD		.EQU	$
  #INCLUDE "md.asm"
SIZ_MD		.EQU	$ - ORG_MD
		MEMECHO	"MD occupies "
		MEMECHO	SIZ_MD
		MEMECHO	" bytes.\n"
#ENDIF
;
#IF (FDENABLE)
ORG_FD		.EQU	$
  #INCLUDE "fd.asm"
SIZ_FD		.EQU	$ - ORG_FD
		MEMECHO	"FD occupies "
		MEMECHO	SIZ_FD
		MEMECHO	" bytes.\n"
#ENDIF
;
#IF (RFENABLE)
ORG_RF	.EQU	$
  #INCLUDE "rf.asm"
SIZ_RF	.EQU	$ - ORG_RF
		MEMECHO	"RF occupies "
		MEMECHO	SIZ_RF
		MEMECHO	" bytes.\n"
#ENDIF
;
#IF (IDEENABLE)
ORG_IDE		.EQU	$
  #INCLUDE "ide.asm"
SIZ_IDE		.EQU	$ - ORG_IDE
		MEMECHO	"IDE occupies "
		MEMECHO	SIZ_IDE
		MEMECHO	" bytes.\n"
#ENDIF
;
#IF (PPIDEENABLE)
ORG_PPIDE	.EQU	$
  #INCLUDE "ppide.asm"
SIZ_PPIDE	.EQU	$ - ORG_PPIDE
		MEMECHO	"PPIDE occupies "
		MEMECHO	SIZ_PPIDE
		MEMECHO	" bytes.\n"
#ENDIF
;
#IF (SDENABLE)
ORG_SD		.EQU	$
  #INCLUDE "sd.asm"
SIZ_SD		.EQU	$ - ORG_SD
		MEMECHO	"SD occupies "
		MEMECHO	SIZ_SD
		MEMECHO	" bytes.\n"
#ENDIF
;
#IF (HDSKENABLE)
ORG_HDSK	.EQU	$
  #INCLUDE "hdsk.asm"
SIZ_HDSK	.EQU	$ - ORG_HDSK
		MEMECHO	"HDSK occupies "
		MEMECHO	SIZ_HDSK
		MEMECHO	" bytes.\n"
#ENDIF
;
#IF (PPAENABLE)
ORG_PPA		.EQU	$
  #INCLUDE "ppa.asm"
SIZ_PPA		.EQU	$ - ORG_PPA
		MEMECHO	"PPA occupies "
		MEMECHO	SIZ_PPA
		MEMECHO	" bytes.\n"
#ENDIF
;
#IF (IMMENABLE)
ORG_IMM		.EQU	$
  #INCLUDE "imm.asm"
SIZ_IMM		.EQU	$ - ORG_IMM
		MEMECHO	"IMM occupies "
		MEMECHO	SIZ_IMM
		MEMECHO	" bytes.\n"
#ENDIF
;
#IF (SYQENABLE)
ORG_SYQ		.EQU	$
  #INCLUDE "syq.asm"
SIZ_SYQ		.EQU	$ - ORG_SYQ
		MEMECHO	"SYQ occupies "
		MEMECHO	SIZ_SYQ
		MEMECHO	" bytes.\n"
#ENDIF
;
; TERM IS ALWAYS INCLUDED
ORG_TERM	.EQU	$
  #INCLUDE "term.asm"
SIZ_TERM	.EQU	$ - ORG_TERM
		MEMECHO	"TERM occupies ")
		MEMECHO	SIZ_TERM
		MEMECHO	" bytes.\n"
;
;#IF (SPKENABLE & DSRTCENABLE
#IF (SPKENABLE)
ORG_SPK	.EQU	$
  #INCLUDE "spk.asm"
SIZ_SPK	.EQU	$ - ORG_SPK
		MEMECHO	"SPK occupies "
		MEMECHO	SIZ_SPK
		MEMECHO	" bytes.\n"
#ENDIF
#IF (KIOENABLE)
ORG_KIO	.EQU	$
  #INCLUDE "kio.asm"
SIZ_KIO	.EQU	$ - ORG_KIO
		MEMECHO	"KIO occupies "
		MEMECHO	SIZ_KIO
		MEMECHO	" bytes.\n"
#ENDIF
#IF (CTCENABLE)
ORG_CTC	.EQU	$
  #INCLUDE "ctc.asm"
SIZ_CTC	.EQU	$ - ORG_CTC
		MEMECHO	"CTC occupies "
		MEMECHO	SIZ_CTC
		MEMECHO	" bytes.\n"
#ENDIF
#IF (SN76489ENABLE)
ORG_SN76489	.EQU	$
  #INCLUDE "sn76489.asm"
SIZ_SN76489	.EQU	$ - ORG_SN76489
		MEMECHO	"SN76489 occupies "
		MEMECHO	SIZ_SN76489
		MEMECHO	" bytes.\n"
#ENDIF
#IF (AY38910ENABLE)
ORG_AY38910	.EQU	$
  #INCLUDE "ay38910.asm"
SIZ_AY38910	.EQU	$ - ORG_AY38910
		MEMECHO	"AY38910 occupies "
		MEMECHO	SIZ_AY38910
		MEMECHO	" bytes.\n"
#ENDIF
#IF (YM2612ENABLE)
ORG_YM2612	.EQU	$
  #INCLUDE "ym2612.asm"
SIZ_YM2612	.EQU	$ - ORG_YM2612
		MEMECHO	"YM2612 occupies "
		MEMECHO	SIZ_YM2612
		MEMECHO	" bytes.\n"
#ENDIF
;
;
#IF (CPUFAM == CPU_EZ80)
	MEMECHO	"EZ80 DRIVERS\n"
ORG_EZ80DRVS	.EQU	$
;
ORG_EZ80CPUDRV	.EQU	$
  #INCLUDE "ez80cpudrv.asm"
SIZ_EZ80CPUDRV	.EQU	$ - ORG_EZ80CPUDRV
		MEMECHO	"  EZ80 CPU DRIVER occupies "
		MEMECHO	SIZ_EZ80CPUDRV
		MEMECHO	" bytes.\n"
;
ORG_EZ80SYSTMR	.EQU	$
  #INCLUDE "ez80systmr.asm"
SIZ_EZ80SYSTMR	.EQU	$ - ORG_EZ80SYSTMR
		MEMECHO	"  EZ80 SYS TIMER occupies "
		MEMECHO	SIZ_EZ80SYSTMR
		MEMECHO	" bytes.\n"
;
#IF (EZ80RTCENABLE)
ORG_EZ80RTC	.EQU	$
  #INCLUDE "ez80rtc.asm"
SIZ_EZ80RTC	.EQU	$ - ORG_EZ80RTC
		MEMECHO	"  EZ80 RTC occupies "
		MEMECHO	SIZ_EZ80RTC
		MEMECHO	" bytes.\n"
#ENDIF
;
#IF (EZ80UARTENABLE)
ORG_EZU	.EQU	$
  #INCLUDE "ez80uart.asm"
SIZ_EZU		.EQU	$ - ORG_EZU
		MEMECHO	"  EZ80 UART occupies "
		MEMECHO	SIZ_EZU
		MEMECHO	" bytes.\n"
#ENDIF

SIZ_EZ80DRVS	.EQU	$ - ORG_EZ80DRVS
		MEMECHO	"  Total "
		MEMECHO	SIZ_EZ80DRVS
		MEMECHO	" bytes.\n"

#ENDIF

		MEMECHO	"RTCDEF="
		MEMECHO	RTCDEF
		MEMECHO	"\n"
;
HB_DRIVERS_END	.EQU	$
;
;==================================================================================================
;   FONTS
;==================================================================================================
;
HB_FONTS_BEG	.EQU	$
;
ORG_FONTS	.EQU	$
;
	MEMECHO	"FONTS"
;
#IFDEF USEFONT8X8
FONT8X8:
;
; FOR NOW, WE NEVER COMPRESS THE 8X8 FONT.  SEE TMS DRIVER.
;
  #IF USELZSA2 & FALSE
    #INCLUDE "font8x8c.asm"
  #ELSE
    #INCLUDE "font8x8u.asm"
  #ENDIF
	MEMECHO	" 8X8"
#ENDIF
;
#IFDEF USEFONT8X11
FONT8X11:
  #IF USELZSA2
    #INCLUDE "font8x11c.asm"
  #ELSE
    #INCLUDE "font8x11u.asm"
  #ENDIF
	MEMECHO	" 8X11"
#ENDIF
;
#IFDEF USEFONT8X16
FONT8X16:
  #IF USELZSA2
    #INCLUDE "font8x16c.asm"
  #ELSE
    #INCLUDE "font8x16u.asm"
  #ENDIF
	MEMECHO	" 8X16"
#ENDIF
;
#IFDEF USEFONTCGA
FONTCGA:
  #IF USELZSA2
    #INCLUDE "fontcgac.asm"
  #ELSE
    #INCLUDE "fontcgau.asm"
  #ENDIF
	MEMECHO	" CGA"
#ENDIF
;
#IFDEF USEFONTVGARC
FONTVGARC:
  #IF USELZSA2
    #INCLUDE "fontvgarcc.asm"
  #ELSE
    #INCLUDE "fontvgarcu.asm"
  #ENDIF
	MEMECHO	" VGARC"
#ENDIF
;
SIZ_FONTS	.EQU	$ - ORG_FONTS
		MEMECHO	" occupy "
		MEMECHO	SIZ_FONTS
		MEMECHO	" bytes.\n"
;
HB_FONTS_END	.EQU	$
;
;==================================================================================================
;   HBIOS GLOBAL DATA
;==================================================================================================
;
HB_DATA_BEG	.EQU	$
;
IDLECOUNT	.DB	0
;
HEAPCURB	.DW	0		; MARK HEAP ADDRESS AFTER INITIALIZATION
;
HB_TICKS	.FILL	4,0		; 32 BIT TICK COUNTER
HB_SECTCK	.DB	TICKFREQ	; TICK COUNTER FOR FRACTIONAL SECONDS
HB_SECS		.FILL	4,0		; 32 BIT SECONDS COUNTER
;
HB_CPUTYPE	.DB	0		; 0=Z80, 1=Z180, 2=Z180-K, 3=Z180-N, 4=Z280
HB_CPUOSC	.DW	CPUOSC		; ACTUAL CPU HARDWARE OSC FREQ IN KHZ
;
HB_BATCOND	.DB	0		; BATTERY CONDITION (0=LOW, 1=OK)
;
RTCDEFVAL	.DB	RTCDEF		; STORAGE FOR RTC DEFAULT VALUE
;
#IF (BT_REC_TYPE != BT_REC_NONE)
HB_BOOT_REC	.DB	0		; BOOT MODE (0=NORMAL, 1=RECOVERY MODE)
#ENDIF
;
STR_BANNER	.DB	"\r\n\r\nRomWBW HBIOS v", BIOSVER, ", ", TIMESTAMP
#IFDEF APPBOOT
		.DB	" (App Boot)"
#ENDIF
		.DB	"$"
STR_PLATFORM	.DB	PLATFORM_NAME, "$"
STR_CONSOLE	.DB	"\r\n\r\n  Console on Unit #$"
STR_BADINT	.DB	"\r\n*** BAD INT ***\r\n$"
STR_LOWBAT	.DB	"\r\n\r\n+++ LOW BATTERY +++$"
;
STR_PANIC	.TEXT	"\r\n>>> PANIC: $"
STR_SYSCHK	.TEXT	"\r\n>>> SYSCHK: $"
STR_CONTINUE	.TEXT	"\r\nContinue (Y/N)? $"
;
HB_CURSEC	.DB	0		; CURRENT SECOND (TEMP)
;
HB_BCDTMP	.FILL	5,0		; BCD NUMBER STORAGE (TEMP)
;
HB_BOOTCON	.DB	0		; INITIAL BOOT CONSOLE SAVE AREA
HB_BOOTCFG	.DW	0		; CONSOLE CONFIG SAVE AREA
HB_NEWCON	.DB	0		; NEW CONSOLE TO SWITCH TO
;
#IF (SUPCTS)
HB_BOOTCONSAV	.DB	0		; INITIAL BOOT CONSOLE SAVE AREA
HB_CONCFGSAV	.DW	0		; CONSOLE CONFIG SAVE AREA
#ENDIF
;
HB_HASFP	.DB	0		; NON-ZERO MEANS FP EXISTS
;
HB_WRKBUF	.EQU	$		; INTERNAL DISK BUFFER
;
; THIS AREA IS USED AS A TEMPORARY DISK BUFFER.  IT IS ALSO USED
; FOR THE APPBOOT STARTUP CODE SINCE THAT CODE CAN BE DISCARDED
; AFTER STARTUP.
;
HB_APPBOOT:
;
#IFDEF APPBOOT
	; APPBOOT IS ONLY SUPPORTED ON A RUNNING ROMWBW SYSTEM.
	; CONFIRM AND DIAGNOSE IF NOT.
	LD	HL,(HB_IDENT)		; HL := ADR OR ROMWBW HBIOS IDENT
	LD	A,(HL)			; GET FIRST BYTE OF ROMWBW MARKER
	CP	'W'			; MATCH?
	JR	NZ,HB_APPBOOTERR	; ABORT WITH INVALID CONFIG BLOCK
	INC	HL			; NEXT BYTE (MARKER BYTE 2)
	LD	A,(HL)			; LOAD IT
	CP	~'W'			; MATCH?
	JR	NZ,HB_APPBOOTERR	; ABORT WITH INVALID CONFIG BLOCK
	JR	HB_APPBOOT1		; WE ARE RUNNING ROMWBW, CONTINUE
;
HB_APPBOOTERR:
	LD	DE,STR_APPBOOTERR	; POINT TO ERROR MESSAGE
	LD	C,9			; BDOS FUNC 9: WRITE STR
	CALL	$0005			; DO IT
	OR	$FF			; SIGNAL ERROR
	RET				; AND RETURN
;
STR_APPBOOTERR	.DB	"\r\n\r\n*** App Boot is only possible on running RomWBW system!\r\n\r\n$"
;
HB_APPBOOT1:
	; APPBOOT REQUIRES THAT THE COMMON BANK IS NOT CHANGED BY
	; THE NEW CONFIG.  TEST FOR THIS AND DIAGNOSE IF SO.
	LD	A,(HCB_BIDCOM)		; RUNNING COMMON BANK ID

	LD	B,BF_SYSGET		; HBIOS SYSGET
	LD	C,BF_SYSGET_BNKINFO	; BANK INFORMATION
	RST	08			; D = BIOS BANK ID
	LD	B,BF_SYSPEEK		; HBIOS FUNC: PEEK
	LD	HL,HCB_LOC + HCB_BIDCOM	; COMMON BANK ID
	RST	08			; E = COMMON BANK ID
	LD	A,E			; PUT IN A
	CP	BID_COM			; COMPARE TO NEW CONFIG
	JR	Z,HB_APPBOOT2		; IF SAME, CONTINUE
;
	; DIAGNOSE COMMON BANK ID MISMATCH
	LD	DE,STR_COMBANKERR	; POINT TO ERROR MESSAGE
	LD	C,9			; BDOS FUNC 9: WRITE STR
	CALL	$0005			; DO IT
	OR	$FF			; SIGNAL ERROR
	RET				; AND RETURN
;
STR_COMBANKERR	.DB	"\r\n\r\n*** Common Bank Mismatch!\r\n\r\n$"
;
HB_APPBOOT2:
	; ANNOUNCE THE APPLICATION BOOT
	LD	DE,STR_APPBOOT		; POINT TO MESSAGE
	LD	C,9			; BDOS FUNC 9: WRITE STR
	CALL	$0005			; DO IT
	CALL	LDELAY			; SERIAL PORT FLUSH TIME
	JR	HB_APPBOOT3		; AND CONTINUE
;
STR_APPBOOT	.DB	"\r\n\r\n*** Launching RomWBW HBIOS v", BIOSVER, ", ", TIMESTAMP, " for"
		.DB	"\r\n\r\n    ", PLATFORM_NAME, "$"
;
HB_APPBOOT3:
;
  #IF (MEMMGR == MM_Z280)
	; WE NEED TO SWITCH FROM USER MODE TO SYSTEM MODE, BUT CONTINUE
	; RUNNING IN THE CURRENT BANK.  THIS IS A LITTLE MESSY.
;
	; FIRST, OVERLAY PROXY CODE WITH FRESH CODE SO WE CAN USE THE
	; PROXY ROUTINES SAFELY.
	LD	A,(HB_CURBNK)		; GET CURBNK
	LD	DE,HBX_LOC		; RUNNING LOCATION
	LD	HL,HBX_IMG		; LOCATION IN IMAGE
	LD	BC,HBX_SIZ		; SIZE
	LDIR				; INSTALL IT
	LD	(HB_CURBNK),A		; RESTORE CURBNK
;
	; NEXT, COPY A BIT OF CODE TO DO THE SYSTEM TRANSITION TO
	; UPPER MEM. WE CAN BORROW THE PROXY BOUNCE BUFFER FOR THIS.
	LD	HL,Z280_GOSYS
	LD	DE,HBX_BUF
	LD	BC,Z280_GOSYS_LEN
	LDIR
;
	; THEN SYSCALL IT.  NOTE THAT THE ROUTINE CALLED DOES NOT
	; (RET)URN, IT JUMPS TO HB_RESTART SO THAT THE SYSCALL DOES
	; NOT RETURN TO USER MODE.
	SC	HBX_BUF			; SYSCALL ROUTINE
;
Z280_GOSYS:
	; THIS BIT OF CODE RUNS IN UPPER MEM.  IT REMAPS THE LOW MEM
	; SYSTEM PAGES TO THE CURRENT BANK.  WE LOSE STACK CONTEXT IN
	; THE PROCESS, SO IN THIS CASE WE NEED TO JUMP BACK TO CONTINUE
	; THE APP BOOT.
	DI				; NO INTERRUPTS
	LD	SP,HBX_LOC		; SETUP INITIAL STACK JUST BELOW HBIOS PROXY
	LD	A,(HB_CURBNK)		; CURRENT BANK
	LD	B,$10			; FIRST SYSTEM PDR
	CALL	Z280_BNKSEL		; DO THE SWITCH
	JP	HB_RESTART		; AND RESUME BOOT
;
Z280_GOSYS_LEN	.EQU	$ - Z280_GOSYS
;
  #ENDIF
;
	XOR	A			; SIGNAL SUCCESS
	RET				; AND RETURN
;
#ENDIF
;
	.FILL	(512 - ($ - HB_WRKBUF))	; PAD REMAINDER OF WORK BUF
;
#IFDEF MG014_MAP
;
	; ALIGN TO 32 BYTE BOUNDARY
	ALIGN($20)
;
MG014_STATMAPLO:
	; LOWER NIBBLE
	.DB	$08	; 00
	.DB	$0C	; 01
	.DB	$00	; 02
	.DB	$04	; 03
	.DB	$0A	; 04
	.DB	$0E	; 05
	.DB	$02	; 06
	.DB	$06	; 07
	.DB	$09	; 08
	.DB	$0D	; 09
	.DB	$01	; 0A
	.DB	$05	; 0B
	.DB	$0B	; 0C
	.DB	$0F	; 0D
	.DB	$03	; 0E
	.DB	$07	; 0F
;
MG014_STATMAPHI:
	; UPPER NIBBLE
	.DB	$80	; 00
	.DB	$C0	; 01
	.DB	$00	; 02
	.DB	$40	; 03
	.DB	$A0	; 04
	.DB	$E0	; 05
	.DB	$20	; 06
	.DB	$60	; 07
	.DB	$90	; 08
	.DB	$D0	; 09
	.DB	$10	; 0A
	.DB	$50	; 0B
	.DB	$B0	; 0C
	.DB	$F0	; 0D
	.DB	$30	; 0E
	.DB	$70	; 0F
;
#ENDIF
;
HB_DATA_END	.EQU	$
;
HB_END		.EQU	$
;
SLACK		.EQU	BNKTOP - $
;
;
;
#IFDEF MEMINFO
	.ECHO "SECTION        \tSTART\tLENGTH\n"
	.ECHO "-------------- \t-------\t-------\n"

	.ECHO "PAGE ZERO      \t" \ .ECHO HB_PGZERO_BEG \ .ECHO "\t" \ .ECHO HB_PGZERO_END - HB_PGZERO_BEG \ .ECHO "\n"
	.ECHO "HCB            \t" \ .ECHO HB_HCB_BEG \ .ECHO "\t" \ .ECHO HB_HCB_END - HB_HCB_BEG \ .ECHO "\n"
	.ECHO "PROXY          \t" \ .ECHO HB_PROXY_BEG \ .ECHO "\t" \ .ECHO HB_PROXY_END - HB_PROXY_BEG \ .ECHO "\n"
	.ECHO "ENTRY          \t" \ .ECHO HB_ENTRY_BEG \ .ECHO "\t" \ .ECHO HB_ENTRY_END - HB_ENTRY_BEG \ .ECHO "\n"
	.ECHO "INTVEC         \t" \ .ECHO HB_INTVEC_BEG \ .ECHO "\t" \ .ECHO HB_INTVEC_END - HB_INTVEC_BEG \ .ECHO "\n"
	.ECHO "SYSINIT        \t" \ .ECHO HB_SYSINIT_BEG \ .ECHO "\t" \ .ECHO HB_SYSINIT_END - HB_SYSINIT_BEG \ .ECHO "\n"
	.ECHO "DISP           \t" \ .ECHO HB_DISP_BEG \ .ECHO "\t" \ .ECHO HB_DISP_END - HB_DISP_BEG \ .ECHO "\n"
	.ECHO "Z280IVT        \t" \ .ECHO HB_Z280IVT_BEG \ .ECHO "\t" \ .ECHO HB_Z280IVT_END - HB_Z280IVT_BEG \ .ECHO "\n"
	.ECHO "EXTAPI         \t" \ .ECHO HB_EXTAPI_BEG \ .ECHO "\t" \ .ECHO HB_EXTAPI_END - HB_EXTAPI_BEG \ .ECHO "\n"
	.ECHO "SYSAPI         \t" \ .ECHO HB_SYSAPI_BEG \ .ECHO "\t" \ .ECHO HB_SYSAPI_END - HB_SYSAPI_BEG \ .ECHO "\n"
	.ECHO "INTFUNC        \t" \ .ECHO HB_INTFUNC_BEG \ .ECHO "\t" \ .ECHO HB_INTFUNC_END - HB_INTFUNC_BEG \ .ECHO "\n"
	.ECHO "UTIL           \t" \ .ECHO HB_UTIL_BEG \ .ECHO "\t" \ .ECHO HB_UTIL_END - HB_UTIL_BEG \ .ECHO "\n"
	.ECHO "PRTSUM         \t" \ .ECHO HB_PRTSUM_BEG \ .ECHO "\t" \ .ECHO HB_PRTSUM_END - HB_PRTSUM_BEG \ .ECHO "\n"
	.ECHO "DRIVERS        \t" \ .ECHO HB_DRIVERS_BEG \ .ECHO "\t" \ .ECHO HB_DRIVERS_END - HB_DRIVERS_BEG \ .ECHO "\n"
	.ECHO "FONTS          \t" \ .ECHO HB_FONTS_BEG \ .ECHO "\t" \ .ECHO HB_FONTS_END - HB_FONTS_BEG \ .ECHO "\n"
	.ECHO "DATA           \t" \ .ECHO HB_DATA_BEG \ .ECHO "\t" \ .ECHO HB_DATA_END - HB_DATA_BEG \ .ECHO "\n"
	.ECHO "SLACK          \t" \ .ECHO HB_END \ .ECHO "\t" \ .ECHO SLACK \ .ECHO "\n"
;
#ENDIF
;
	.ECHO	"HBIOS space remaining: "
	.ECHO	SLACK
	.ECHO	" bytes.\n"
;
; DIAGNOSE HBIOS BANK OVERFLOW
;
#IF (SLACK < 0)
	.ECHO	"*** ERROR: HBIOS too big!!!\n"
	!!!	; FORCE AN ASSEMBLY ERROR
#ENDIF
;
; CHECK TO SEE IF WE HAVE ENOUGH HEAP TO CACHE THE CP/M CCP
; AND ONE DISK SECTOR.  ALTHOUGH SOME OPERATING SYSTEMS OR APPS
; MAY NOT NEED THIS, THE MOST COMMON ONES DO.  CREATING AN HBIOS
; WITHOUT SPACE FOR THIS WILL NOT BE USEFUL.
;
#IF ((CCP_SIZ + 512 + 8) > SLACK)
	.ECHO	"*** ERROR: Insufficient HEAP space!!!\n"
	!!!	; FORCE AN ASSEMBLY ERROR
#ENDIF
;
;;;#IF (SLACK < (1024 * 3))
;;;	.ECHO	"*** ERROR: Low HEAP space!!!\n"
;;;	!!!	; FORCE AN ASSEMBLY ERROR
;;;#ENDIF
;
#IFDEF ROMBOOT
  #IF (ROMSIZE > 0)
	.FILL	SLACK
  #ENDIF
#ENDIF
;
	.END
