
title	'Disk 2 Hard Disk Driver'

;************************************************
;					     	*
;	HARD DISK DRIVER MODULE			*
;	Last changed : 2/13/84			*
;						*
;************************************************

include system.lib
eject ! 
include diskhdr.equ
include exerror.equ

HD_IRL		equ	02H		; hard disk interrupt request line

MASTER_PIC_PORT	EQU	50H
RW_ERROR	EQU	01
SUCCESS		EQU	0
RET_ADDR	EQU	04

					; FOR HARD DISK WITH A DISK 2 CONTROLLER
H_READ_F		EQU	1
H_WRITE_F		EQU	2

					; disk 2 ports
STAT_CONTROL		equ	0C8H
DISK_2_DATA		equ	STAT_CONTROL + 1
DISK_2_SELECT		equ	0F0H

ROUTINE_USE	equ	2

phys_drvs	equ	1		; # physical hard drive(s) supported
track_0		equ	0		; this is track 0
drive_sel_op	equ	0		; disk 2 drive select op code
drive_0		equ	10h		; this is the first and only drive
					; in the system
seek_outer	equ	false
seek_inner	equ	true

home_count	equ	512
					; status equates
ATTN_NOT		equ	80h
TIME_OUT		equ	40h
CRC_ERROR		equ	20h
OVER_RUN		equ	10h
READY_NOT		equ	08h
SEEK_COMPLETE_NOT	equ	04h
WRITE_FAULT_NOT		equ	02h
TRACK0_NOT		equ	01h

					; control equates
RUN			EQU	40H
OP2			EQU	20H
OP1			EQU	10H
OP0			EQU	08H
FAULT_CLR		EQU	04H
USR1			EQU	02H
USR2			EQU	01H

					; command equates
NULL_F		equ	0C0H
CLEAR_INT	equ	080H
READ_DATA_F	equ	0C8H
WRITE_DATA_F	equ	0D0H
WRITE_HEADER_F	equ	091H
READ_HEADER_F	equ	0E0H

					; seek direction, op2(bit 5) of the 
					; command word sets the direction 
					; signal to the disk drive.
INNER		EQU	20H
OUTTER		EQU	00H
					; offsets for read header command buffer
TEST_CYLINDER	EQU	0H
TEST_HEAD	EQU	1H
TEST_SECTOR	EQU	2H

eject

					; For the DISK2 selector card
					; See page 28 of the DISK2 manual 
					; the patterns given are for:
					; d7 = write/read
					; d6 = memory (vs io port operations)
					; d5 = increment the memory address
					; d4 = no wait states 
					; d3 - d0 = complement of the dma dev
					; the selector channel will work with
DISK_2_INT_PRIORITY	equ	10
DISK_2_DMA_PRIORITY	equ	not	DISK_2_INT_PRIORITY
WRITE_TO_DISK	equ	00100000b or ( DISK_2_DMA_PRIORITY and 0fh )
READ_FROM_DISK	equ	10100000b or ( DISK_2_DMA_PRIORITY and 0fh )

					; For the read/write routines
TRACK_OFFSET	EQU	0
HEAD_OFFSET	EQU	2
SECTOR_OFFSET	EQU	4
DMA_OFFSET	equ	6

					; these are the op codes for the DISK2
OP_DRIVE	equ	080H
OP_TRACK	equ	088H
OP_HEAD		equ	090h
OP_SECTOR	equ	098h

eject

H_DISK_FLAG	EQU	8
HD_EOI		EQU	61H

	cseg

public	h_read, h_write, h_dsk_sel
public	h_init, h_disk_int

extrn	intdisp:near
extrn	supif:near, sysdat:word
extrn	read_write:near

	dseg

extrn	dph_tbl:word
extrn	dph1:byte, dph2:byte, dph3:byte

extrn	current_reskew:word

extrn	itrack:word, isector:byte, idmaoff:word, idmaseg:word

hd_stat		db	0		; Interrupt handler will put status
					; from the DISK2 controller here before
					; it resets the PIC.
eject ! include sysdat.lib

eject

	cseg

;==========
h_disk_int:				; hard disk interrupt handler
;==========
;

	push ds
	mov ds,sysdat

	mov h_disk_ssreg,ss		; may need to check for
	mov h_disk_spreg,sp		; re-entrant interrupt since
	mov ss,sysdat			; nmi's can't be disabled
	mov sp,offset h_disk_tos

	push ax ! push bx
	push cx ! push dx
	push di ! push si
	push bp ! push es
	
	in al, ( MASTER_PIC_PORT + 1 )	; get the PIC's mask
	or al, HD_IRL			; mask out the Disk 2
	out ( MASTER_PIC_PORT + 1 ), al	; write the mask back to the PIC
	
 	mov dx, H_DISK_FLAG
 	mov cl,F_FLAGSET 
	call supif

	mov al, HD_EOI			; just reset the PIC's
	out MASTER_PIC_PORT,al

	pop es ! pop bp
	pop si ! pop di
	pop dx ! pop cx
	pop bx ! pop ax

	mov ss, h_disk_ssreg
	mov sp, h_disk_spreg

	pop ds

	jmp intdisp			; iret from dispatcher


eject

;=========
h_dsk_sel:
;=========
;
; 	Translates cpm logical drive to a controller specific drive
;
;	ENTRY:	cl = cpm logical drive
;		bx = offset into a dph table
;		dl[bit 0] = select code
;
;	EXIT:	ax = bx = &dph if every thing went ok
;		ax = bx = 0 if there is no DPH for this drive
;
;	NOTE:	this routine just indexes into the dph table
;		and returns whatever is there. There is no protection.

	mov ax, dph_tbl[bx]
	mov bx, ax
	mov current_reskew, 0
	ret


;====
h_init:
;====
; 	Initialzation of the hard disk sub-system.
; 	Initializes the memory structures, homes the head,
; 	and puts the disk2 and its selector in a safe state.
;       See microdisk drives C/E Manual (lsi version) by FUJITSU
; 	and page 6 of the disk 2 manual

	; init the last physical sector to 0
	mov last_cyl,track_0

	; do the initial read of the selector channel
	; pages 27 and 28 of the disk 2 guide
	in al, DISK_2_SELECT

	; init the header buffer d word
	mov ( header_buffer_addr + 2 ), ds

	; we only are supporting one drive
	; wait for the drive to be come ready
	mov al, OP_DRIVE		; select drive command
	call send_command
	mov al, DRIVE_0			;   for this drive
	call send_data

init_drv_rdy_lp:			; wait for the drive
	mov al, NULL_F
	call send_command
	call stat_return
	test al, READY_NOT
	jnz init_drv_rdy_lp

	call h_home
	jz init_exit
	  or al, ERROR			; extended from h_home
init_exit:

	ret

eject

;======
h_read:
;======
;	this routine is called by the bdos
;
;	ENTRY:	bp = &(IOPB on the stack)
;
;	EXIT:	OK if the read happened ok
;		RW_ERROR if there was a read error
;

	mov hard_op, H_READ_F
	mov si, offset h_rd_wt
	jmp read_write



;======
h_write:
;======
;	this routine is called by the bdos
;
;	ENTRY:	bp = &(IOPB on the stack)
;
;	EXIT:	OK if the read happened ok
;		RW_ERROR if there was a read error
;

	mov hard_op, H_WRITE_F
	mov si, offset h_rd_wt
	jmp read_write



;=======
h_rd_wt:
;=======
;
; This is the main sector read routine for concurrent
; It does everything possible to make the read.
; The drive is assumed to be 0 and selected befor this operation.
;
;	ENTRY:	hd_op	= READ if we want a read, WRITE if we want a write
;		itrack	= the track we want to read
;		isector	= the sector we want to read
;		idmaoff	= where to get or put the data
;		idmaseg	= where to get or put the data
;
;	EXIT:	al	= OK if every thing went OK
;		al	= RW_ERROR (01)  if there was a fatal error
;

	mov bx, offset rw_track
	mov h_rd_wt_param_add, bx		; save the param pointer

	; translate the track from concurrent to the track and
	; sector for the hard disk
	; this assumes 8 heads [ 0 - 7 ]

	mov ax, itrack
	and ax, 07
	mov HEAD_OFFSET[bx], al
	mov ax, itrack
	mov cl, 3
	shr ax, cl
	mov TRACK_OFFSET[bx], ax

	; move the remaining params passed in from read_write to
	; our local copies
	xor ax, ax
	mov al, isector
	mov SECTOR_OFFSET[bx], ax
	mov ax, idmaoff
	mov DMA_OFFSET[bx], ax
	mov ax, idmaseg
	mov DMA_OFFSET + 2 [bx], ax
	
	; select the drive and
	; check for the drive being ready.
	mov al, OP_DRIVE
	call send_command
	mov al, DRIVE_0
	call send_data
	call stat_return
	and al, READY_NOT! jz rd_drv_rdy
	  mov	ah,ATTA_FAILED		; drive was not ready
	  or al, RW_ERROR
	  ret
rd_drv_rdy:

	mov re_homes, RETRY_COUNT / 2
re_home_lp:
	mov h_rd_wt_retries, RETRY_COUNT 
h_rd_wt_retry_lp:

	mov bx, h_rd_wt_param_add	; set up the parameter pointer

	; seek to the proper track
	mov ax, TRACK_OFFSET[bx]
	mov cyl,ax
	call seek
	
	mov bx, h_rd_wt_param_add	; set up the parameter pointer

	; set up the drive register
	mov al, OP_DRIVE		; select the drive/head register for
	call send_command		; for the subsequent out instruction
	xor ax,ax			; only drive 0 is valid
	mov ax, HEAD_OFFSET[bx]
	and ax, 0fH			; THE lower nibble in al is head
	or al, DRIVE_0			; the upper nibble is the drive
	call send_data

	; set up the track register
	mov al, OP_TRACK
	call send_command
	mov ax, TRACK_OFFSET[bx]	; select the track
	call send_data

	; set up the head register
	mov al, OP_HEAD
	call send_command
	mov ax, HEAD_OFFSET[bx]		; select the head
	and ax,0fh
	call send_data

	; set up the sector register
	mov al, OP_SECTOR
	call send_command
	mov ax, SECTOR_OFFSET[bx]	;select the sector
	and al, 0ffh
	call send_data

	; set up the dma base and direction
	lea bx, DMA_OFFSET[bx]
	cmp hard_op, H_READ_F
	jne h_wrt_now
	  mov al, READ_FROM_DISK
	  jmps set_h_dma
h_wrt_now:
	mov al, WRITE_TO_DISK
set_h_dma:
	call set_disk_2_dma

	; do the read or write
	cmp hard_op, H_READ_F
	jne h_wrt_now_1
	  mov al, READ_DATA_F
	  jmps h_do_rd_wt
h_wrt_now_1:
	mov al, WRITE_DATA_F
h_do_rd_wt:
	call send_command

	; get the status byte and deal with any errors

	call h_int_wait
	call stat_return
	test al, TIME_OUT or CRC_ERROR or OVER_RUN
	jnz h_rd_wt_err_1
	  and al, SUCCESS
	  jmp h_rd_wt_exit
h_rd_wt_err_1:
	; stash the last return error code from the controller
	mov h_rd_wt_error_type, al
	; check the number of retries
	dec h_rd_wt_retries ! jnz rd_wt_retry_1
	  ; re-home the head after 5 retries
	  dec re_homes ! jz re_home_1
	    call h_home
	    jmp re_home_lp
re_home_1:
	mov	ah,ATTA_FAILED
    	mov al, RW_ERROR
	jmp h_rd_wt_exit
rd_wt_retry_1: 
	; if it's a crc_error or a data_over_run error just retry it
	test al, TIME_OUT! jnz rd_wt_retry_2
	  jmp h_rd_wt_retry_lp
rd_wt_retry_2:
	  ; must be a time out error, so check for the proper track
	  mov bx, offset header_buffer_addr
	  call read_header
	  cmp al, SUCCESS! je check_track
	    or al, RW_ERROR		; extented error set in read_header
	    jmps h_rd_wt_exit
check_track:
	; we h_rd_wt the header so check the track
	mov bx, h_rd_wt_param_add
	mov ax, TRACK_OFFSET[bx]
	mov bx, offset header_buffer
	; if the head is over the proper cyinder/track just retry
	cmp ax, TEST_CYLINDER[bx]! jne wrong_track
	  jmp h_rd_wt_retry_lp
wrong_track:
	  ; must have been over a wrong track. 
	  ; so update last track and re-seek
	  mov ax, TEST_CYLINDER[bx]
	  mov last_cyl,ax
	  mov bx, h_rd_wt_param_add
	  mov ax, TRACK_OFFSET[bx]
	  mov cyl,ax
	  call seek
	  jmp h_rd_wt_retry_lp
h_rd_wt_exit:

	ret


eject



;=====
h_home:
;=====
;
; this routine will home the drive.
; If the track 0 not signal is true we do nothing.
; If it's false we try to home the heads.
;
;	ENTRY:	none
;
;	EXIT:	al = 0ffh	on error
;		al = 000h	if things went OK.
;		the zero flag refects al


	; if were on track 0 we don't have to home
	mov al, OP_DRIVE
	call send_command
	mov al, DRIVE_0
	call send_data
	call stat_return
	test al, TRACK0_NOT
	jz home_exit
	  ; we have do the home
	  mov home_retries,RETRY_COUNT
home_retry_lp:
	; first find out what track we're over
	mov bx, offset header_buffer_addr
	call read_header
	cmp al, SUCCESS ! je h_home_1
	  dec home_retries
	  jnz home_retry_lp
	    or al, ERROR		; extended error set in read_header
	    jmp home_exit
h_home_1:
	; get the current cylinder
	xor ax,ax
	mov bx, offset header_buffer
	mov al, TEST_CYLINDER[bx]
	mov last_cyl, ax

	; seek to cylinder 0
	mov cyl, 0
	call seek

	call stat_return
	test al, TRACK0_NOT ! jz end_home_lp
	  ; try to issue the home command
	  pushf! cli
	  mov al, SEEK_OUTER	; set direction
	  call send_command
	  mov cx, HOME_COUNT	; and bang the port
home_lp:
	  in al, DISK_2_DATA	; over 256 reads from the data port
	  loop home_lp		; at a rate between 3K and 3 Meg
	    popf

	mov dl, ( 15 * DELAY_16_MS )	; wait for the hard disk to recover
	mov cl, DELAY_F			; about 250 ms
	call supif

	call stat_return

	test al,TRACK0_NOT ! jz end_home_lp
	  ; if we're still not over track 0
	  ; get away from the stop and try again
	  mov cyl, 30
	  call seek
	  dec home_retries
	  jnz home_retry_lp
	    mov	ah,SEEK_FAILED
	    or al, ERROR
end_home_lp:
	mov last_cyl, TRACK_0
home_exit:

	ret
eject

read_header:
;-----------
;
; This routine reads the three header bytes from the first sector
; encountered on the track the head is over.  In essence it just
; drops the head and tells you the current cylinder, head, and sector.
;
;	ENTRY:	bx	= &dma_add (dword)
;
;	EXIT:
;		al	= ERROR if the drive was not ready.
;			  or if the header couldn't be ready with in 
;			  RETRY_COUNT attempts.
;		al	= OK if every thing worked OK, 
;			  the address pointed to by bx on the way in
;			  will contain the cylinder, head, and sector
;			  for the currently selected drive.

	mov rd_hdr_retries, RETRY_COUNT
rd_hdr_retry_lp:

	; set up the dma base and direction
	mov al, READ_FROM_DISK
	call set_disk_2_dma

rd_hdr_drv_rdy:
	mov al, READ_HEADER_F
	call send_command
	
	call h_int_wait
	call stat_return

	; check for no errors
	and al, TIME_OUT or CRC_ERROR or OVER_RUN
	test al,SUCCESS! jz exit_read_header
	  ; must have some sort of error
	  dec rd_hdr_retries! jnz rd_hdr_retry_lp
	    mov		ah,al
	    or al, ERROR
	    test	ah,TIME_OUT
	    jz		not_time_out
	    mov		ah,ATTA_FAILED
	    jmps	exit_read_header
not_time_out:
	    shr		ah,1		; convert to extended error
exit_read_header:

	ret


eject


seek:
;----
;
; This routine seeks to the track passed in.
; It does no error recovery.  It expects the READ or Write routine
; to take care of errors.
;
;	ENTRY:	cyl		the cylinder we want to seek to.
;		last_cyl	the last cylinder we were on.
;				The one the head should be over when we
;				enter this routine.
;	EXIT:	there is no error code returned.
;		if there is an error in the seek operation, the corresponing
;		read or write will fail.  When the corresponding read 
;		or write tries to recover it will do the seeks again.

	; Is the current cylinder the one we want.
	mov ax, cyl
	cmp ax, last_cyl! je exit_seek
	  jg requested_cyl_greater
	    sub last_cyl,ax		; the last cylinder was greater 
	    mov al, OUTTER		; set the direction twords the edge
	    call send_command
	    jmps seek_1
requested_cyl_greater:
	xchg ax, last_cyl		; the cylinder we want is greater
	sub last_cyl,ax
	mov al, inner			; set the direction twords the spindle
	call send_command
seek_1:
	mov cx, last_cyl		; last_cyl = abs( cyl - last_cyl )
	pushf! cli			; critical region
seek_2:
	in al, DISK_2_DATA		; bang the step port
	loop seek_2
	popf
	
	; set the null command and do a flag wait
	mov al, NULL_F
	call send_command

	call h_int_wait
	call stat_return

	mov ax,cyl		; update the cylinder register
	mov last_cyl,ax

	; just to get back to here the seek must have happened.
	; the only action I can take at this time would be a sample
	; of the track field on the disk.  This has to be done
	; by the read or write any way. So return.

exit_seek:

	ret


eject	


set_disk_2_dma:
;--------------
;
; This routine sets the 24 bit dma from the dword
; pointed to by Bx.
;
;	ENTRY:	al	= mode byte for the selector channel
;		bx	= &dma_add (dword)
;
;	EXIT:		the selector channel will have it's dma
;			set to the value passed in.

	push ax

	; just do a read of the selector channel to make it
	; ready for the subsequent writes
	in al, DISK_2_SELECT

	; calculate the segment and offset into a 20 bit dma value
	mov dx, 2[bx]! mov ax,dx
	mov cl,4! shl ax,cl
	shr dh,cl
	add ax,[bx]
	adc dh,0

	; send this three (3) byte dma value to the disk 2 selector channel
	mov dl, al
	in al, DISK_2_SELECT
	mov al,dh! out DISK_2_SELECT,al
	mov al,ah! out DISK_2_SELECT,al
	mov al,dl! out DISK_2_SELECT,al

	; send the mode control byte to the selector channel
	pop ax
	out DISK_2_SELECT,al	

	ret


eject


h_int_wait:
;----------
;
;	This routine waits for the interrupt
;
;	ENTRY:	none
;
;	EXIT:	none

	jmps real_h_int

int_wait_lp:
	call stat_return
	test al, ATTN_NOT
	jnz int_wait_lp

	jmp h_int_wait_exit

real_h_int:
	push ax! push bx! push cx! push dx
	push di! push si! push bp
	push es! push ds!

	; turn on the disk 2's interrupt line

	in al, ( MASTER_PIC_PORT + 1 )	; read the PIC's int mask
	and al, not HD_IRL		; turn on the disk 2
	out ( MASTER_PIC_PORT + 1 ), al	; write the mask back out

	mov dl, H_DISK_FLAG
	mov cl, F_FLAGWAIT
	call supif

	pop ds! pop es
	pop bp! pop si! pop di
	pop dx! pop cx! pop bx! pop ax

h_int_wait_exit:
	ret

eject

send_command:
;------------
;
; this routine sends a command to the DISK2 controller
;
;	ENTRY:	al = command
;
;	EXIT:	none
;
;	bx must be preserved

	out STAT_CONTROL, al

	ret


eject

send_data:
;---------
;
; this routine sends a data byte to the DISK2 controller
;
;	ENTRY:	al = data byte
;
;	EXIT:	none
;
;	NOTE:	bx must be preserved

	out DISK_2_DATA, al

	ret

eject

stat_return:
;-----------
;
; this routine just returns the status from the disk controler
;
;	ENTRY:	none
;
;	EXIT:	al = current status from the disk controller

	in al, STAT_CONTROL		; get the controler status

	ret

		rw	48
h_disk_tos	rw	0
h_disk_ssreg	rw	1
h_disk_spreg	rw	1


eject

	dseg

h_rd_wt_error_type		db	0

header_buffer_addr	dw	offset header_buffer
			dw	0

header_buffer		db	0,0,0,0,0	; should only need three


eject

;
; params for h_rd_wt

hard_op		db	0

rw_track	dw	0
rw_head		dw	0
rw_sector	dw	0
rw_dma_off	dw	0
rw_dma_seg	dw	0

;
; these are the current retry counters

h_rd_wt_retries		db	0
re_homes		db	0
write_retries		db	0
rd_hdr_retries		db	0	
home_retries		db	0

;
; The disk 2 controler wants a control word and delivers a status
; word for each operation

disk2_control	db	0
disk2_status	db	0

;      BIT	CTL		        STATUS
;     	       (write)		        (read)
;--------------------------------------------------------
;	7	attn_not		attn_not
;	6	run			time out
;	5	op2			crc error
;	4	op1			over run
;	3	op0			ready_not
;	2	fault clr		seek complete_not
;	1	usr1			write fault_not
;	0	usr0			track0_not
;
; where: 	_not   indicates an active low signal

;
; used by init and seek

last_cyl	dw	0	; the last cylinder we were on
cyl		dw	0	; the current cylinder we want 

h_rd_wt_param_add	dw	0


	end

