;	CPM on-disk boot loader for my 8080 microcomputer
;	JWD - v1.0   06/11/2024   
;
	CPU 8080        ; Specify architecture
	PAGE 0
;
;
; Initial stack
;
STACK	equ		0FFFFh	;Initial stack at top of RAM
;
; BIOS parameters
;
MEMSTART equ 0de00h  ;Destination of CP/M image
SECTORS  equ 36h     ;Number of sectors to load
SECSIZE  equ 80h     ;CP/M sector size
MONITOR  equ 100h    ;System monitor start address
CPMCOLD  equ 0f400h  ;Address of CP/M Cold Start
;
; Equates for I/O mapped 8250 or 16450 serial port
;
S16450  equ     0       ;base of 16450 UART
RXR     equ     0       ;Receiver buffer register
TXR     equ     0       ;Transmitter buffer register
IER     equ     1       ;Interrupt enable register
LCR     equ     3       ;Line control register
MCR     equ     4       ;Modem control register
LSR     equ     5       ;Line status register
;
; Define monitor serial port
;
SER_STATUS equ S16450+LSR
SER_DATA   equ S16450+RXR
SER_RXRDY  equ 1    ;Mask for rx buffer full
SER_TXRDY  equ 32   ;Mask for tx buffer empty
;
; ATA Drive Definitions
;
ATA_CS1			equ 40h			;ATA Drive CS1 address
ATA_CS2			equ	80h			;ATA Drive CS2 address
ATA_DATA		equ	ATA_CS1		;ATA Drive Data Register
ATA_ERROR		equ	ATA_CS1+1	;ATA Drive Error Register (Read Only)
ATA_FEATURES	equ	ATA_CS1+1	;ATA Drive Feature Register (Write Only)
ATA_SEC_COUNT	equ	ATA_CS1+2	;ATA Drive Sector Count Register
ATA_SEC			equ	ATA_CS1+3	;ATA Drive Sector Number Register
ATA_CYL_LOW		equ	ATA_CS1+4	;ATA Drive Cylinder LSB Register
ATA_CYL_HI		equ	ATA_CS1+5	;ATA Drive Cylinder MSB Register
ATA_DRV_HEAD	equ	ATA_CS1+6	;ATA Drive Device/Head Register
ATA_STATUS		equ	ATA_CS1+7	;ATA Drive Status Register (Read Only)
ATA_COMMAND		equ	ATA_CS1+7	;ATA Drive Command Register (Write Only)
;
ATA_LBA_BYTE1	equ	ATA_CS1+3	;ATA Bytes 0 through 7 in LBA mode
ATA_LBA_BYTE2	equ	ATA_CS1+4	;ATA Bytes 8 through 15 in LBA mode
ATA_LBA_BYTE3	equ	ATA_CS1+5	;ATA Bytes 16 through 23 in LBA mode
ATA_LBA_BYTE4	equ	ATA_CS1+6	;ATA Bytes 24 through 27 in LBA mode
;
ATA_ALT_STATUS	equ	ATA_CS2+6	;ATA Drive Alternate Status Register
ATA_DEV_CTRL	equ	ATA_CS2+6	;ATA Drive Control Register
ATA_DEV_ADDRESS	equ	ATA_CS2+7	;ATA Drive Address Register
;
ATA_LBA0_7		equ	ATA_CS1+3	;ATA Drive LBA Bits 0-7
ATA_LBA8_15		equ	ATA_CS1+4	;ATA Drive LBA Bits 8-15
ATA_LBA16_23	equ	ATA_CS1+5	;ATA Drive LBA Bits 16-23
ATA_LBA24_27	equ	ATA_CS1+6	;ATA Drive LBA Bits 24-27
;
; ATA Command Options
;
CMD_RETRY		equ	0
CMD_NO_RETRY	equ	1
;
; ATA Read/Write Commands
;
CMD_READ_SECTOR_RETRY		equ	20h  ;Read sectors in sector count register - retry enabled
CMD_READ_SECTOR_NORETRY		equ	21h  ;Read sectors in sector count register - retry disabled
CMD_READ_LONG_RETRY			equ	22h  ;Reads one sector with ECC bytes, retry enabled
CMD_READ_LONG_NORETRY		equ	23h  ;Reads one sector with ECC bytes, retry disabled
CMD_WRITE_SECTOR_RETRY		equ	30h  ;Write sectors in sector count register - retry enabled
CMD_WRITE_SECTOR_NORETRY	equ	31h  ;Write sectors in sector count register - retry disabled
CMD_WRITE_LONG_RETRY		equ	32h  ;Writes one sector with ECC bytes, retry enabled
CMD_WRITE_LONG_NORETRY		equ	33h  ;Writes one sector with ECC bytes, retry disabled
CMD_WRITE_VERIFY			equ	3Ch  ;Write sectors in sector count register - verify after writing
CMD_VERIFY_SECTOR_RETRY		equ	40h  ;Verify sectors in sector count register - retry enabled, no data 
CMD_VERIFY_SECTOR_NORETRY	equ	41h  ;Verify sectors in sector count register - retry disabled, no data
CMD_READ_MULTIPLE           equ 0C4h ;Read multiple sectors - interrupt when finished 
CMD_WRITE_MULTIPLE          equ 0C5h ;Write multiple sectors - interrupt when finished 
CMD_READ_BUFFER				equ	0E4h ;Read contents of sector buffer
CMD_WRITE_BUFFER			equ	0E8h ;Write to sector buffer
;
;ATA Control Commands
;
CMD_REQUEST_SENSE		equ	3    ;Gets extended error for previous command
CMD_RECALIBRATE         equ 10h  ;Reinitialize ATA controller (does nothing with CF)
CMD_SEEK                equ 70h  ;Moves head to track specified (does nothing with CF)
CMD_DIAGNOSTIC			equ	90h  ;Check power mode of the card
CMD_INITIALIZE			equ	91h  ;Allows change to sectors per track and heads per cylinder
CMD_STANDBY_IMMEDIATE	equ	94h  ;Puts card in standby mode without idle timer
CMD_STANDBY				equ	96h  ;Puts card in standby mode after idle until timer expired
CMD_CHECK_POWER			equ	98h  ;Returns power status of the card
CMD_SLEEP				equ	99h  ;Puts card in sleep mode until hardware/software reset
CMD_MULTIPLE            equ 0C6h ;Enable read/write multiple operations
CMD_IDENTIFY			equ	0ECh ;Performs self diagnostics on the card
CMD_SET_FEATURES		equ	0EFh ;Set card features (aka 8-bit mode)
;
;ATA Control Settings
;
DEV_CONTROL_SRST	equ	04h	;Device Control Register Software Reset 
DEV_CONTROL_NIEN	equ	02h	;Device Control Register Interrupt Enable
;
DHR_SLAVE_SELECT	equ	10h	;DHR Register select slave (default is master)
DHR_LBA_MODE		equ	40h	;DHR Register enable LBA mode (default is CHS)
;
;ATA Error Register Flags
;
ERROR_BBK		equ	80h	;Bad Block Mark (Always 0, not supported on CF)
ERROR_UNC		equ	40h	;Uncorrectable Data Error
ERROR_MC		equ	20h	;Media Changed (Always 0)
ERROR_IDNF		equ	10h	;Sector ID Field Not Found
ERROR_MCR		equ	08h	;Media Change Requested (Always 0)
ERROR_ABRT		equ	04h	;Status error or Invalid Command Aborted
ERROR_TKN0NF	equ	02h	;Track 0 Not Found (Not supported on CF)
ERROR_AMNF		equ	01h	;Address Mark Not Found (Not supported on CF)
;
;ATA Status Register Flage
;
STATUS_BSY		equ	80h	;Device Busy
STATUS_DRDY		equ	40h	;Device Ready
STATUS_DWF		equ	20h	;Device Write Fault
STATUS_DSC		equ	10h	;Device Seek Complete
STATUS_DRQ		equ	08h	;Data Request
STATUS_CORR		equ	04h	;Corrected Data
STATUS_IDX		equ	02h	;Index (Not used on CF, Always 0)
STATUS_ERR		equ	01h	;Command Error
;
;ATA Feature Codes
;
FEATURE_8BIT_ON			equ	01h  ;Enable 8-bit transfers in LBA mode
FEATURE_LOOKAHEAD_OFF	equ	55h  ;Disable read lookahead
FEATURE_DEFAULTS_OFF	equ	66h  ;Disable power-on defaults
FEATURE_8BIT_OFF		equ	81h  ;Disable 8-bit transfers in LBA mode
FEATURE_ECC_ON			equ	0BBh ;Enable 4 bytes ECC on read/write long
FEATURE_DEFAULTS_ON		equ	0CCh ;Enable power-on defaults
;
msize	equ	63		;cp/m version memory size in kilobytes
;
;*****************************************************
;*                                                   *
;*         CP/M to host disk constants               *
;*                                                   *
;*****************************************************
blksiz	equ	4096	        ;CP/M allocation size
hstsiz	equ	512		        ;host disk sector size
hstspt	equ	64		        ;host disk sectors/trk
hstblk	equ	hstsiz/SECSIZE	;CP/M sects/host buff
cpmspt	equ	hstblk * hstspt	;CP/M sectors/track
secmsk	equ	hstblk-1	    ;sector mask
secshf	equ	2
;
;
	org	1000h		;origin of this program
;
;	individual subroutines to perform each function
;
	lxi sp, STACK	;set stack pointer
	xra	a			;zero in the accum
	sta	hstact		;host buffer inactive
	sta	unacnt		;clear unalloc count
    sta disk00      ;default physical to logigical disk map
;
;  print bios boot message
;
    lxi d, bios_boot  ;point to boot message
	call bios_print   ;print it
;
;  Initialize ATA Controller
;
;  Software reset the drive, disable interrupts
;
init:
	call ataready	 ;Wait for drive ready in case of power reset
	in ATA_DEV_CTRL  ;Get the ATA device control bits
	ani DEV_CONTROL_SRST+DEV_CONTROL_NIEN  ;Enable software reset, disable interrupts
	out ATA_DEV_CTRL ;Store in device control register
	in ATA_DEV_CTRL  ;Get the ATA device control bits
	ani DEV_CONTROL_NIEN ;Turn off software reset
	out ATA_DEV_CTRL ;Store in device control register
	call ataready	 ;Wait for drive to be ready
;
;  Set drive for LBA mode
;
	in ATA_DRV_HEAD	 ;Get current DHR bits
	ori	DHR_LBA_MODE ;Set the LBA mode bit
	out	ATA_DRV_HEAD ;Write bits to the DHR register
;
;  Enable 8-bit data transfers
;
	mvi a, FEATURE_8BIT_ON	;Get feature code for 8-bit mode
	out ATA_FEATURES		;Write 8-bit feature code to features register
	mvi a, CMD_SET_FEATURES	;Get ready for Features command
	out ATA_COMMAND	        ;Request Set Features command
	call atabusy	        ;Wait for command completion
;
initok:
	lxi d, bios_msg ;Point to boot start message
	call bios_print ;Print it
;
load:
	mvi a, 0        ;Select drive 0
	sta sekdsk      ;Save it in drive number
    sta sektrk      ;Save it in track number
	mvi a, 4        ;Select logical sector 4
	sta seksec      ;Save it in sector number
	lxi h, MEMSTART ;Get dma position
	shld dmaadr     ;Save dma position
	mvi a, SECTORS  ;Sector counter
	sta seccnt      ;Store it in memory
load01:
	call read       ;read the first logical sector
    lda seksec      ;Get current sector
	inr a           ;Point to next sector
	sta seksec      ;Save it in sector number
;
	lhld dmaadr     ;Get DMA address
	lxi b, SECSIZE  ;Get sector size
	dad b           ;Add byte increment
    shld dmaadr     ;Save DMA address
;    
    lda seccnt      ;Get sector counter
	dcr a           ;Decrement the sector count
	sta seccnt      ;Save sector counter
	jnz load01      ;If not zero, do next sector
;
	lxi d, bios_end ;Point to end message
	call bios_print ;Print it
	jmp CPMCOLD     ;Exit to CPM cold start;
;
;   BIOS messages
;
bios_boot:	db 'JWD 8080 CP/M Disk Loader v1.0'
    dw 0d0ah
	db 0
bios_msg:	db 'Loading CP/M from ATA drive 0'
    dw 0d0ah
	db 0
bios_end:   db 'CP/M load completed'
    dw 0d0ah
	db 0;
;
;
conout:	;console character output from register c
	in SER_STATUS  ;read device status
	ani SER_TXRDY  ;tx ready?
	jz conout      ;repeat if no
	mov	a, c       ;get character to accumulator
	out SER_DATA   ;send it
	ret
;
;	Disk I/O Operations
;
;*****************************************************
;*                                                   *
;*	The READ entry point takes the place of          *
;*	the previous BIOS defintion for READ.            *
;*                                                   *
;*****************************************************
read:
	;read the selected CP/M sector
	xra	a
	sta	unacnt
	mvi	a,1
	sta	readop		;read operation
	sta	rsflag		;must read data
	jmp	rwoper		;to perform the read
;
;*****************************************************
;*                                                   *
;*	Common code for READ and WRITE follows           *
;*                                                   *
;*****************************************************
rwoper:
	;enter here to perform the read/write
	xra	a		;zero to accum
	sta	erflag		;no errors (yet)
	lda	seksec		;compute host sector
	rept	secshf
	ora	a		;carry = 0
	rar			;shift right
	endm
	sta	sekhst		;host sector to seek
;
;	active host sector?
	lxi	h,hstact	;host active flag
	mov	a,m
	mvi	m,1		;always becomes 1
	ora	a		;was it already?
	jz	filhst		;fill host if not
;
;	host buffer active, same as seek buffer?
	lda	sekdsk
	lxi	h,hstdsk	;same disk?
	cmp	m		;sekdsk = hstdsk?
	jnz	filhst
;
;	same disk, same track?
	lxi	h,hsttrk
	call	sektrkcmp	;sektrk = hsttrk?
	jnz	filhst
;
;	same disk, same track, same buffer?
	lda	sekhst
	lxi	h,hstsec	;sekhst = hstsec?
	cmp	m
	jz	match		;skip if match
;
filhst:
	;may have to fill the host buffer
	lda	sekdsk
	sta	hstdsk
	lhld	sektrk
	shld	hsttrk
	lda	sekhst
	sta	hstsec
	lda	rsflag		;need to read?
	ora	a
	cnz	readhst		;yes, if 1
	xra	a		;0 to accum
	sta	hstwrt		;no pending write
;
match:
	;copy data to or from buffer
	lda	seksec		;mask buffer number
	ani	secmsk		;least signif bits
	mov	l,a		;ready to shift
	mvi	h,0		;double count
	rept	7		;shift left 7
	dad	h
	endm
	lxi	d,hstbuf
	dad	d		;hl = host address
	xchg			;now in DE
	lhld	dmaadr	;get/put CP/M data
	mvi	c,128		;length of move
;
rwmove:
	;C initially 128, DE is source, HL is dest
	ldax	d		;source character
	inx	d
	mov	m,a		;to dest
	inx	h
	dcr	c		;loop 128 times
	jnz	rwmove
	lda	erflag		;in case of errors
	ret			;no further processing
;
;
;*****************************************************
;*                                                   *
;*	Utility subroutine for 16-bit compare            *
;*                                                   *
;*****************************************************
sektrkcmp:
	xchg
	lxi	h,sektrk
	ldax	d		;low byte compare
	cmp	m		;same?
	rnz			;return if not
;	low bytes equal, test high 1s
	inx	d
	inx	h
	ldax	d
	cmp	m	;sets flags
	ret
;
;*****************************************************
;*                                                   *
;*	READHST reads the physical                       *
;*	disk.					                         *
;*                                                   *
;*****************************************************
;
;
;hstdsk = host disk #, hsttrk = host track #,
;hstsec = host sect #. read "hstsiz" bytes
;into hstbuf and return error flag in erflag.
;
readhst:	;perform read operation
;
;Do DTS conversion to LBA here
;
	mvi a, CMD_READ_SECTOR_RETRY  ;Set up for sector read
    call doatacmd    ;Execute the read command
	jz rdhst01       ;Signal read error
	mvi	a, 1		 ;error condition
	sta erflag       ;flag an error
	ret
;
rdhst01:
	lxi b, hstbuf    ;Point to sector buffer
rdhst02:
	in ATA_DATA      ;Get data byte
	stax b           ;Store data in sector buffer
	inx b            ;Point to next buffer byte
	in ATA_STATUS    ;Check card status
	ani STATUS_DRQ   ;Get data ready status bit
	jnz rdhst02      ;Get another byte from the card
	xra a            ;Signal no error
	sta erflag       ;save it
    ret
;
;
doatacmd: ;Execute ATA READ/Write Command
    mov b, a          ;Save A temporarially
;Set disk number MSB in LBA
    lda hstdsk        ;Get current host disk number
    ora a             ;Clear carry
    rar               ;Remove lsb of disk number
	ora a             ;Clear carry
	rar               ;Remove lsb of disk number
    out ATA_LBA16_23  ;Save it in ATA_LBA16-23
	lda hstdsk        ;Get current host disk number
    ani 3             ;Get two lsb of disk number
	rrc               ;Put them in msb
	rrc
	mov c, a          ;Save it in C
;Set disk number LSB and track MSB in LBA
    lda hsttrk        ;Get current host track number
    ani 0fch          ;Remove two lsb bits
	rrc               ;Move over two bits
	rrc
    ora c             ;Add in drive lsb bits
    out ATA_LBA8_15   ;Set it in ATA_LBA8_15
    lda hsttrk        ;Get current host track number
	ani 3             ;Get two lsb bits of track
	rrc               ;Put them in msb
	rrc
;Set track number LSB and sector in LBA
	mov c, a          ;Save it in C
	lda hstsec        ;Get current host sector number
	ani 3fh           ;Remove msb bits just in case
	ora c             ;Add in sector bits
	out ATA_LBA0_7    ;Set it in ATA_LBA0_7
;Set sector count to 1
	mvi a, 01         ;Read/Write one sector
	out ATA_SEC_COUNT ;Set it in ATA drive
	in ATA_LBA24_27   ;Make sure all other LBA bits are zero
	ani 0f0h
	out	ATA_LBA24_27  ;set it in ATA_LBA24_27
;Set Read/Write command code
    mov a, b          ;restore A
	out ATA_COMMAND   ;Read sector with retry
	call atabusy      ;Wait for command completion
	call ataready     ;Wait for data buffer ready
	call ataerror     ;Check for error condition
    ret

atabusy: ;Wait for the ATA adapter to complete a command
	in ATA_STATUS    ;Get ATA status
	ani STATUS_BSY   ;Mask on busy bit
	jnz atabusy      ;If busy check again
	ret              ;And exit
;
;
ataready: ;Wait for ATA to become ready to execute commands
	in ATA_STATUS    ;Get ATA status
	ani	STATUS_DRDY  ;Mask on data ready bit
	jz ataready      ;Check again if not ready
	ret              ;And exit
;
;
ataerror: ;Check for ATA error status (not zero on error)
	in ATA_STATUS    ;Get ATA status
	ani STATUS_ERR   ;Mask on command error bit (sets flags)
	ret              ;And exit
;
;
; JWD - Miscellaneous support routines
;
;
bios_print: ;Print a message on the console
	ldax d           ;Get char pointed to by D into A
	cpi 0            ;End of string?
	jz bios_pend     ;Exit if end of string
	mov c, a         ;Move shar into C for console out
	call conout      ;Print it
	inx d            ;Point to next character
	jmp bios_print   ;Repeat
bios_pend:
	ret              ;And exit
;
;
;*****************************************************
;*                                                   *
;*	Unitialized RAM data areas		                 *
;*                                                   *
;*****************************************************
;
begdat	equ	$	 	;beginning of data area
;
sekdsk:	ds	1		;seek disk number
sektrk:	ds	2		;seek track number
seksec:	ds	1		;seek sector number
;
hstdsk:	ds	1		;host disk number
hsttrk:	ds	2		;host track number
hstsec:	ds	1		;host sector number
;
sekhst:	ds	1		;seek shr secshf
hstact:	ds	1		;host active flag
hstwrt:	ds	1		;host written flag
;
unacnt:	ds	1		;unalloc rec cnt
unadsk:	ds	1		;last unalloc disk
unatrk:	ds	2		;last unalloc track
unasec:	ds	1		;last unalloc sector
;
erflag:	ds	1		;error reporting
rsflag:	ds	1		;read sector flag
readop:	ds	1		;1 if read operation
wrtype:	ds	1		;write operation type
dmaadr:	ds	2		;last dma address
;
seccnt: ds  1       ;sector counter for CP/M loader
disk00: ds  1       ;physical disk to virtual disk translation
;
;   Sector buffer
;
hstbuf:	ds	hstsiz	;host buffer
;
;
enddat	equ	$	 	;end of data area
datsiz	equ	$-begdat;	;size of data area
;
;
	end