	title 'Tarbell / Morrow winchester disk handler'

;    CP/M-80 Version 3     --  Modular BIOS

;	Disk I/O Module for CMI 5619

	;	Initial version 1.1 , 6-29-83
	;	Copyright (c) 1983 , Tarbell Electronics

	dseg

    ; Disk drive dispatching tables for linked BIOS

	public	win0,win1,win2,win3

    ; local buffer is external in floppy module

	extrn	@dskbf

    ; Variables containing parameters passed by BDOS

	extrn	@adrv,@rdrv
	extrn	@dma,@trk,@sect
	extrn	@dbnk

    ; System Control Block variables

	extrn	@ermde		; BDOS error mode

    ; Utility routines in standard BIOS

	extrn	?wboot	; warm boot vector
	extrn	?pmsg	; print message @<HL> up to 00, saves <BC> & <DE>
	extrn	?pdec	; print binary number in <A> from 0 to 99.
	extrn	?pderr	; print BIOS disk error header
	extrn	?conin,?cono	; con in and out
	extrn	?const		; get console status

    ; Utility routines in MOVE.ASM

	extrn	?gndma,?ax16,?rar4
	extrn	?xmove,?move

    ; CP/M 3 Disk definition macros

	maclib cpm3

    ; Z80 macro library instruction definitions

	maclib z80

    ; common control characters

cr	equ 13
lf	equ 10
bell	equ 7

;drive specification table for the hard disk controller

cyl	equ	306		;number of cylinders
heads	equ	6		;number of heads per cylinder
precomp	equ	128		;cylinder to start write precomensation
lowcurr	equ	0		;low current not used
stepdly	equ	2		;step delay (0-12.7 milliseconds)
steprcl	equ	30		;recalibrate step delay
headdly	equ	0		;settle delay (0-25.5 milliseconds)
sectsiz	equ	3		;code for 512 byte sectors
badsiz	equ	32		;#of bad sector entries
hrdspt	equ	17		;physical sectors/track
cpmhrd	equ	hrdspt*heads*4	;cpm sectors/track

;define controller commands

dmaread	equ	0		;read sector
dmawrit	equ	1		;write sector
dmarhed	equ	2		;find a sector
dmawhed	equ	3		;write headers (format a track)
dmalcon	equ	4		;load disk parameters
dmassta	equ	5		;sense disk drive status
dmanoop	equ	6		;null controller operation

reset	equ	54h		;reset controller
attn	equ	55h		;send a controller attention

chan	equ	50h		;default channel address
stepout	equ	10h		;step direction out
stepin	equ	0		;step direction in
band1	equ	40h		;no precomp, high current
band2	equ	0c0h		;precomp, high current
band3	equ	80h		;precomp, low current
track0	equ	1		;track zero status
wflt	equ	2		;write fault from drive
dready	equ	4		;drive ready
sekcmp	equ	8		;seek complete


    ; Extended Disk Parameter Headers (XPDHs)

	dw	mw$write
	dw	mw$read
	dw	mw$login
	dw	mw$init
	db	0,0		; relative drive zero
win0	dw	0		; no sector translation
	db	0,0,0,0,0,0,0,0,0
	db	0		; media flag
	dw	mwdpb		; disk parameter block
	dw	0		; permanent drive
	dw	0fffeh		; alv set by gencpm
	dw	0fffeh		; dirbcb set by gencpm
	dw	0fffeh		; dtabcb set by gencpm
	dw	0fffeh		; hash set by gencpm
	db	0		; hash bank = 0


	dw	mw$write
	dw	mw$read
	dw	mw$login
	dw	mw$init
	db	1,0		; relative drive one
win1	dw	0		; no sector translation
	db	0,0,0,0,0,0,0,0,0
	db	0		; media flag
	dw	mwdpb		; disk parameter block
	dw	0		; permanent drive
	dw	0fffeh		; alv set by gencpm
	dw	0fffeh		; dirbcb set by gencpm
	dw	0fffeh		; dtabcb set by gencpm
	dw	0fffeh		; hash set by gencpm
	db	0		; hash bank = 0


	dw	mw$write
	dw	mw$read
	dw	mw$login
	dw	mw$init
	db	2,0		; relative drive two
win2	dw	0		; no sector translation
	db	0,0,0,0,0,0,0,0,0
	db	0		; media flag
	dw	mwdpb		; disk parameter block
	dw	0		; permanent drive
	dw	0fffeh		; alv set by gencpm
	dw	0fffeh		; dirbcb set by gencpm
	dw	0fffeh		; dtabcb set by gencpm
	dw	0fffeh		; hash set by gencpm
	db	0		; hash bank = 0


	dw	mw$write
	dw	mw$read
	dw	mw$login
	dw	mw$init
	db	3,0		; relative drive three
win3	dw	0		; no sector translation
	db	0,0,0,0,0,0,0,0,0
	db	0		; media flag
	dw	mwdpb		; disk parameter block
	dw	0		; permanent drive
	dw	0fffeh		; alv set by gencpm
	dw	0fffeh		; dirbcb set by gencpm
	dw	0fffeh		; dtabcb set by gencpm
	dw	0fffeh		; hash set by gencpm
	db	0		; hash bank = 0

	cseg	; DPB must be resident

mwdpb	dw	cpmhrd		; # of cpm sectors per track
	db	5		; bsh - 4k block size
	db	31		; blm
	db	1		; exm - extent mask
	dw	3887		; dsm - disk size -1
	dw	2047		; drm - directory size -1
	db	0ffh		; al0 - directory allocation
	db	0ffh		; al1 - directory allocation
	dw	8000h		; cks - permanent disk
	dw	1		; off - 1 reserved track
	db	2		; psh - 512 byte physical sector
	db	3		; phm - 512 byte physical sector

	dseg	; rest is banked

    ; Disk I/O routines for standardized BIOS interface

; Initialization entry point.

;		called for first time initialization.


mw$init:
	ret

mw$login:
		; This entry is called when a
		; drive is about to be logged on.

	call	mwhome		;do a restore
	lxi	b,0		;track 0
	call	mwseek		;do the seek
	mvi	a,dmaread	;setup for read
	sta	dmaop
	lxi	h,@dskbf
	shld	dmadma		;use local buffer
	xra	a
	sta	extdma		;bank 0
	sta	mwsectr		;sector 0 for badmap
	mvi	a,2		;head 2
	sta	mwhead
	call	rwsect		;go read the sector
	ora	a		;test status of read
	jrnz	overflo
	lhld	badptr		;pick up bad map pointer
	lxi	d,@dskbf	;start at beginning of buffer
badl:	ldax	d		;pick up an entry from the buffer
	ora	a
	jz	bade		;all done
	mov	a,m		;pick up entry from bad map table
	inr	a
	jz	overflo		;bad map overflow
	lda	@rdrv		;put drive in table
	mov	m,a
	inx	h
	lxi	b,8
	xchg
	ldir			;move the rest of information into the table
	xchg
	jmp	badl 

bade:	shld	badptr		;restore new bad map pointer
	ret				
		
overflo:
	lda	@adrv		;get absolute drive
	adi	'A'		;make ascii
	sta	map$message$drive
	lxi	h,map$message
	call	?pmsg
	pop	d		;pop return address
	pop	h		;pop dph pointer
	lxi	h,0		;indicate select error
	push	h
	push	d		;restore return address
	ret

map$message
	db	cr,lf,'Badmap read error or overflow on '
map$message$drive
	db	0,':',0

badptr	dw	badmap		;pointer to next available bad map entry

;
; disk READ and WRITE entry points.
;

		; these entries are called with the following arguments:

			; relative drive number in @rdrv (8 bits)
			; absolute drive number in @adrv (8 bits)
			; disk transfer address in @dma (16 bits)
			; disk transfer bank	in @dbnk (8 bits)
			; disk track address	in @trk (16 bits)
			; disk sector address	in @sect (16 bits)
			; pointer to XDPH in <DE>

		; they transfer the appropriate data, perform retries
		; if necessary, then return an error code in <A>

mw$read:
	mvi	a,dmaread
	sta	mwop
	jr	findalt
mw$write:
	mvi	a,dmawrit
	sta	mwop
findalt:		; now check for a replacement sector
	lda	@rdrv		;get drive #
	mov	c,a		;put in c for fast search
	lxi	h,badmap	;get badmap address
all:	lded	badptr
	ora	a		;clear carry
	xchg
	dsbc	d		;check for end of badmap
	jrz	alt2		;if the end
	mov	a,c		;get drive #
	cmp	m		;check it
	jrnz	altmis		;nope
	inx	h		;bump to track
	lda	@trk		;get lsb track
	cmp	m		;check it
	jrnz	altmis		;nope
	inx	h		;bump to msb
	lda	@trk+1		;get msb track
	cmp	m		;check it
	jrnz	altmis
	inx	h
	mov	a,m		;get map sector
	sbi	1		;make real sector number
	mov	b,a		;put in b
	lda	@sect		;get sector
	cmp	b		;check it
	jrnz	altmis
	inx	h		;alternate found
	inx	h		;bump to alternate
	mov	c,m		;get lsb alternate track
	inx	h
	mov	b,m		;get msb alternate track
	push	h		;save hl
	call	mwseek		;do the seek
	pop	h		;restore hl
	inx	h
	lda	mwop		;get the operation
	sta	dmaop		;tell the controller
	mov	a,m		;get the alternate sector
	sbi	1		;make real sector number
	pop	h		;make stack right
	jmp	altfnd		;calculate head and do it!
altmis:	pop	h		;restore hl
	lxi	d,9		;offset to next badmap entry
	dad	d
	jmp	all		;look some more

; jump here if no alternate found

alt2:	lbcd	@trk		;get requested track
	call	mwseek		;do the seek
	lda	mwop		;get the operation
	sta	dmaop		;tell the controller
	lda	@sect		;get the requested sector

; jump here if an alternate is found

altfnd:	mov	c,a		;put in c
	mvi	d,0		;clear head counter
seclp:	sui	hrdspt		;subtract out a heads worth
	jrc	secfnd		;jump if found
	inr	d		;else bump to next head
	mov	c,a		;update sector count
	jr	seclp		;and do it again
secfnd:	mov	a,d		;get head
	sta	mwhead		;save it
	mov	a,c		;get sector
	sta	mwsectr		;save it also
; generate and check the real dma address
; for a proper transfer
	call	?gndma		;generate real dma address
	sta	extdma		;save extended address
	sded	dmadma		;save real dma address
	inx	h		;point to next manager byte
	push	h		;save pointer
	lxi	h,01ffh		;byte count-1
	dad	d		;add in address
	push	psw		;save flags
	mov	a,h		;get h reg
	call	?rar4		;move to lower nib
	mov	h,a		;back to h
	pop	psw		;get ext adr & flags back
	jrnc	samebk
	inr	a		;bump extended address
samebk:	call	?ax16		;mov to upper nibble
	ora	h		;or in h
	pop	h		;get manager pointer
	cmp	m		;check for continuity
	jrz	rwsect		;ok for direct transfer

;if memory is not continuous past a 4k boundary a
;local buffer must be used.

	lda	mwop		;get operation
	cpi	dmaread		;test for read
	jrnz	notread		;jump if not a read

;if a read operation - read the sector to the local buffer 
;then use xmove to transfer to the requested address.

	xra	a
	sta	extdma		;use bank 0 for read
	lxi	h,@dskbf
	shld	dmadma		;use local buffer for read
	call	rwsect		;do the read
	push	psw		;save the results
	mvi	c,0		;bank 0 is the source
	lda	@dbnk		;get dma bank
	mov	b,a		;use for destination
	di			;disable interrupts
	call	?xmove		;setup for extended move
	lxi	d,@dskbf	;use local buffer as source
	lhld	@dma		;dma address is destination
	lxi	b,512		;move 512 bytes
	call	?move		;do the move
	pop	psw		;restore results of the read
	ret			;return to cp/m

;if a write operation - transfer the requested data to the
;local buffer with xmove then write it to the disk.

notread:
	lda	@dbnk		;get dma bank
	mov	c,a		;use as source
	mvi	b,0		;use bank 0 as destination
	di			;disable interrupts
	call	?xmove		;setup for extended move
	lxi	h,@dskbf	;use local buffer as destination
	shld	dmadma		;also use for write
	lded	@dma		;use dma address as source
	lxi	b,512		;move 512 bytes
	call	?move		;move data to local buffer
	xra	a
	sta	extdma		;use bank 0

; read or write operation with retrys
; dma address is already setup.

rwsect:
	mvi	a,10		;10 retrys
	sta	rtcnt
retry:	call	mwrdwr		;go to it !
	mvi	a,0
	rnc			;return if no error
	lda	rtcnt		;get retry count
	dcr	a
	sta	rtcnt		;update count
	jnz	retry
	inr	a		;set error code
	ret

; these are the low level drivers for the morrow controller

; reset the controller and do a home on @rdrv

mwhome	call	mwreset		;reset controller
	lxi	h,dmarg1	;load arguments
	mvi	m,steprcl	;load step delay (slow)
	inx	h
	mvi	m,headdly	;head settle delay
	call	mwissue		;do load constants again
	call	mwptr		;get pointer to current cylinder number
	mvi	m,0		;setup fake at cyl 0
	inx	h
	mvi	m,0
	lxi	b,2		;seek in 2 cylinders
	call	mwseek
	call	mwptr		;get pointer again
	mvi	m,0ffh		;fake at cyl 255 for max head travel
	inx	h
	mvi	m,0ffh
	lxi	b,0		;seek to cylinder 0
	call	mwseek		;recal slowly
	jmp	mwreset		;back to fast stepping mode

; seek @rdrv to the track in bc

mwseek	calì	mwptr		;geô tracë pointer
	mov	e,m		;get old track number
	inx	h
	mov	d,m
	dcx	h
	mov	m,c		;store new track number
	inx	h
	mov	m,b
	mov	l,c		;build cylinder word
	mov	h,b
	shld	dmarg0		;set command channel cylinder number
	mov	a,d
	inr	a
	lxi	h,0ffffh
	jnz	mwskip0
	mvi	c,stepout
	jmp	mwskip

mwskip0:
	mov	h,b		;(hl) = new track, (de) = old track
	mov	l,c
	call	mwhlmde
	mvi	c,stepout
	mov	a,h
	ani	80h		;check hit bit for negitive direction
	jnz	mwsout		;step in
	mvi	c,0
	jmp	mwskip
mwsout:	call	mwneghl
mwskip:	shld	dmastep
	lda	@rdrv
	ora	c
	sta	dmasel0

	mvé	a,dmanooð	;no-operatioî commanä foò thå channel
	call	mwprep		;step to proper track
	lxi	h,0		;clear step counter
	shld	dmastep
	ret

; reset the controller and setup the default parameters

mwreset	out	reset		;send reset pulse to controller
	lxi	h,dmachan	;address of command channel
	shld	chan		;default channel address
	xra	a
	sta	chan+2		;clear extended address byte
	lxi	h,dmarg1	;load arguments
	mvi	m,stepdly	;load step delay
	inx	h
	mvi	m,headdly	;head settle delay
	inx	h
	mvi	m,sectsiz	;sector size code
	inx	h
	mvi	m,dmalcon	;load constants command
	jmp	mwissue		;do load constants

; do the operation spec by a reg.

mwprep	sta	dmaop		;save command channel op code

; do the operation spec by dmaop.

mwrdwr:	mvi	c,band1
	lhld	dmarg0
	lxi	d,precomp
	call	mwhlcde
	jc	mwpreps

	mvi	c,band2
	lxi	d,lowcurr
	call	mwhlcde
	jc	mwpreps

	mvi	c,band3		;cylinder > low_current
mwpreps	lda	mwhead		;load head address
	sta	dmarg2
	cma			;negative logic for the controller
	ani	7		;3 bits of head select
	rlc			;shove over to bits 2 - 4
	rlc
	ora	c		;add on low current and precomp bits
	mov	c,a
	lda	@rdrv		;load drive address
	ora	c		;slap in drive bits
	sta	dmasel1		;save in command channel head select
	lda	mwsectr		;load sector address
	sta	dmarg3

; start the controller running and wait for completion.

mwissue	lxi	h,dmastat	;status byte
	mvi	m,0
	out	attn		;start controller
	lxi	d,0		;time out counter
	mov	b,e		;controller busy status
mwiloop	mov	a,m		;get status
	cpi	0ffh		;test for successful operation
	rz			;return no error (carry reset)
	ora	a		;test for error
	stc
	rnz			;return error status
;controller busy wait for it
	push	b
	mvi	b,4		;total 75 t-states
mwilop:	djnz	mwilop		; 36+((b-1)*13)
	pop	b
	dcx	d		;bump timeout counter
	mov	a,d
	ora	e
	jnz	mwiloop		;loop if still busy
	mov	a,m		;get error status
	stc			;set error flag
	ret

; return with hl pointing to the track word for @rdrv.

mwptr:	lda	@rdrv		;get currently select drives track address
	rlc
	mov	e,a
	mvi	d,0
	lxi	h,mwtab
	dad	d		;offset into track table
	ret

mwneghl:
	mov	a,h
	cma
	mov	h,a
	mov	a,l
	cma
	mov	l,a
	inx	h
	ret

mwhlmde:
	xchg
	call	mwneghl
	xchg
	dad	d
	ret

mwhlcde:
	mov	a,h
	cmp	d
	rnz
	mov	a,l
	cmp	e
	ret

mwtab	;collection of track addresses
	dw	0ffffh		;initialize to 
	dw	0ffffh		; (way out on the end of the disk)
	dw	0ffffh
	dw	0ffffh

mwop	db	0
mwhead	db	0		;currently selected head
mwsectr
	db	0		;currently selected sector
rtcnt	db	0		;retry count

dmachan	equ	$		;command channel area
dmasel0	db	0		;drive select
dmastep	dw	0		;relative step counter
dmasel1	db	0		;head select
dmadma	dw	0		;dma address
extdma	db	0		;extended address
dmarg0	db	0		;first argument
dmarg1	db	0		;second argument
dmarg2	db	0		;third argument
dmarg3	db	0		;fourth argument
dmaop	db	0		;operation code
dmastat	db	0		;controller status byte
dmalnk	dw	dmachan		;link address to next command channel
	db	0		;extended address of link

badmap	ds	badsiz*9	;bad sector map
	db	0ffh		;end marker

	end
