;	Figure 7-7.
;
;	Example CP/M Cold Bootstrap Loader
;
;	This program is written out to Track 0, Head 0, Sector 1
;	by the PUTCPMF5 program.
;	It is loaded into memory at location 100H on up by the
;	PROM-based bootstrap mechanism that gets control of the
;	CPU on power-up or system reset.
;
Version		EQU	'01'	;Equates used in the sign on message
Month		EQU	'07'
Day		EQU	'24'
Year		EQU	'82'
;
Debug		EQU	0	;Set Non-zero to debug as normal
				;  transient program
;
;	The actual layout of the diskette is as follows :
;								
; Track 0                     Sector
;         1     2     3     4     5     6     7     8     9	
; Head	+-----+-----+-----+-----+-----+-----+-----+-----+-----+	
;  0	|Boot |<======== CCP ========>|<=======  BDOS ========|	
;	+-----+-----+-----+-----+-----+-----+-----+-----+-----+	
;  1	|====== BDOS ====>|<=============  BIOS  ============>|	
;	+-----+-----+-----+-----+-----+-----+-----+-----+-----+	
;	  10    11    12    13    14    15    16    17    18
;                             Sector
;
;	Equates for defining memory size and the base address and
;	length of the system components.
;
Memory$Size	EQU	64	;Number of Kbytes of RAM
;
;	The BIOS Length must match that declared in the BIOS.
;
BIOS$Length	EQU	0900H
;
CCP$Length	EQU	0800H	;Constant
BDOS$Length	EQU	0E00H	;Constant
;
Length$In$K	EQU	((CCP$Length + BDOS$Length + BIOS$Length) / 1024) + 1
Length$In$Bytes	EQU	CCP$Length + BDOS$Length + BIOS$Length
;
	IF	NOT Debug
CCP$Entry	EQU	(Memory$Size - Length$In$K) * 1024
	ENDIF
	IF	Debug
CCP$Entry	EQU	3980H	;Read into a lower address.
				;This address is chosen to be above
				;  the area into which DDT initially loads
				;  and the 980H makes the addresses similar
				;  to the SYSGEN values so that the memory
				;  image can be checked with DDT.
	ENDIF

BDOS$Entry	EQU	CCP$Entry + CCP$Length + 6	
BIOS$Entry	EQU	CCP$Entry + CCP$Length + BDOS$Length
;
;
;	Disk Characteristics
;
;	These equates describe the physical characteristics of
;	the floppy diskette so that the program can move from
;	one sector to the next, updating the track and resetting
;	the sector when necessary.
;
First$Sector$on$Track	EQU	1
Last$Sector$on$Track	EQU	18
Last$Sector$on$Head$0	EQU	9
Sector$Size		EQU	512
;
;
;	Controller Characteristics
;
;	On this computer system, the floppy disk controller can read
;	multiple sectors in a single command. However, in order to
;	to produce a more general example it is shown only reading one
;	sector at a time.
;
Sectors$Per$Read	EQU	1
;
;
;	Cold Boot Characteristics
;
Start$Track		EQU	0	;Initial values for CP/M image
Start$Sector		EQU	2	;= " =
Sectors$To$Read		EQU	(Length$In$Bytes + Sector$Size - 1) / Sector$Size
;
;
;

	ORG	100H
Cold$Boot$Loader:
	JMP	Main$Code	;Enter main code body
				;For reasons of clarity, the main
				;  data structures are shown before the
				;  executable code.
CR	EQU	0DH		;Carriage Return
LF	EQU	0AH		;Line Feed
;
Signon$Message:
	DB	CR,LF,'CP/M Bootstrap Loader'
	IF	Debug
	DB	' (Debug)'
	ENDIF
	DB	CR,LF
	DB	'Version '
	DW	Version
	DB	' '
	DW	Month
	DB	'/'
	DW	Day
	DB	'/'
	DW	Year
	DB	CR,LF,0

;
;	 Disk Control Tables
;
Disk$Control$5	EQU	45H	;5 1/4" Control Byte
Command$Block$5	EQU	46H	;Control Table Pointer
Disk$Status	EQU	43H	;Completion status
;
;
;	The command table Track and DMA$Address can also be used
;	as working storage and updated as the load process
;	continues. The Sector in the command table cannot be
;	used directly as the disk controller requires it to be
;	the sector number on the specified head (1 - 9) rather
;	than the sector number on track. Hence a separate variable
;	must be used.
;
Sector:		DB	Start$Sector
;
Command$Table:	DB	01H	;Command - Read
Unit:		DB	0	;Unit (Drive) number = 0 or 1
Head:		DB	0	;Head number = 0 or 1 
Track:		DB	Start$Track	;Used as working variable
Sector$on$head:	DB	0	;Converted by low-level driver
Byte$Count:	DW	Sector$Size * Sectors$Per$Read
DMA$Address:	DW	CCP$Entry
Next$Status:	DW	Disk$Status	;Pointer to next Status Block
					;  if commands are chained.
Next$Control:	DW	Disk$Control$5	;Pointer to next Control Byte
					;  if commands are chained.

Main$Code:
	LXI	SP,Cold$Boot$Loader	;Stack grows down below code

	LXI	H,Signon$Message	;Sign on
	CALL	Display$Message

	LXI	H,Command$Table		;Point the disk controller at
	SHLD	Command$Block$5		;  the command block

	MVI	C,Sectors$To$Read	;Set sector count
Load$Loop:
	CALL	Cold$Boot$Read		;Read data into memory
	DCR	C			;Downdate sector count

	IF	NOT Debug
	JZ	BIOS$Entry		;Enter BIOS when load done
	ENDIF
	IF	Debug
	JZ	0			;Warm Boot
	ENDIF

	LXI	H,Sector		;Update sector number
	MVI	A,Sectors$Per$Read	;  by adding on number of sectors
	ADD	M			;  by controller
	MOV	M,A			;Save result
	MVI	A,Last$Sector$On$Track + 1	;Check if at end of track
	CMP	M
	JNZ	Not$End$Track

	MVI	M,First$Sector$On$Track	;Yes, reset to beginning
	LHLD	Track			;Update Track number
	INX	H
	SHLD	Track

Not$End$Track:
	LHLD	DMA$Address		;Update DMA Address
	LXI	D,Sector$Size * Sectors$Per$Read
	DAD	D
	SHLD	DMA$Address
	JMP	Load$Loop		;Read next block
;
Cold$Boot$Read:			;At this point, the description of the
				;  operation required is in the variables
				;  contained in the Command Table, along
				;  with the Sector variable.

	PUSH	B			;Save sector count in C

;------ Change this routine to match the disk controller in use ------

	MVI	B,0			;Assume head 0
	LDA	Sector			;Get requested sector
	MOV	C,A			;Take a copy of it
	CPI	Last$Sector$on$Head$0+1	;Check if on head 1
	JC	Head$0			;No
	SUI	Last$Sector$on$Head$0	;Bias down for head 1
	MOV	C,A			;Save copy
	INR	B			;Set head 1
Head$0:
	MOV	A,B			;Get head
	STA	Head
	MOV	A,C			;Get sector
	STA	Sector$On$Head

	LXI	H,Disk$Control$5	;Activate controller
	MVI	M,80H

Wait$For$Boot$Complete:
	MOV	A,M			;Get status byte		
	ORA	A			;Check if complete
	JNZ	Wait$For$Boot$Complete	;No
					;Yes, check for errors
	LDA	Disk$Status
	CPI	80H		
	JC 	Cold$Boot$Error		;Yes, an error occurred

;------ End of Physical Read routine ------

	POP	B			;Recover sector count in C
	RET
;
Cold$Boot$Error:
	LXI	H,Cold$Boot$Error$Message
	CALL	Display$Message		;Output error message
	JMP	Main$Code		;Restart the loader
;

Cold$Boot$Error$Message:
	DB	CR,LF,'Bootstrap Loader Error - retrying...',CR,LF,0
;
;	Equates for Terminal Output
;
Terminal$Status$Port	EQU	01H
Terminal$Data$Port	EQU	02H
;
Terminal$Output$Ready	EQU	0000$0001B
;
;
Display$Message:	;Displays the specified message on the console.
			;On entry, HL points to a stream of bytes to be
			;output. A 00H-byte terminates the message.
	MOV	A,M		;Get next message byte
	ORA	A		;Check if terminator
	RZ			;Yes, return to caller
	MOV	C,A		;Prepare for output

Output$Not$Ready:
	IN	Terminal$Status$Port	;Check if ready for output
	ANI	Terminal$Output$Ready
	JZ	Output$Not$Ready	;No, wait
	MOV	A,C			;Get Data character
	OUT	Terminal$Data$Port	;Output to Screen

	INX	H		;Move to next byte of message
	JMP	Display$Message	;Loop until complete message output

				;The PROM-based bootstrap loader checks
				;  to see that the characters "CP/M"
				;  are on the diskette bootstrap sector
				;  before it transfers control to it.
	ORG	2E0H
	DB	'CP/M'
	END	Cold$Boot$Loader
