title	'Floppy system driver'


;******************************************
;	FLOPPY DISK MODULE	          *
;	Last changed : 2/13/84            *
;				          *
;******************************************

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

F_DELAY		equ	141
P_PRIORITY	equ	145

DELAY_16MS	equ	1
					; FLOPPY MODE BITS
two_sided	equ	01b
d_density	equ	10b

					; READ WRITE CONTROL STRING EQUATES
					; the field offsets into control string
					; refer to 8272 spec(1982 intel data catalog)
					; p. 9-146, or the Disk 1 Controller manual for the details

D		equ	1		; controller-relative drive number
C		equ	2		; 0-relative cylinder number
H		equ	3		; 0-relative head number ( side )
R		equ	4		; 1-relative sector number
N		equ	5		; the N factor ( magic number )
EOT		equ	6		; End Of Track record number
GPL		equ	7		; Inter-record gap Length
DTL		equ	8		; The DTL factor ( magic number )

					; FLOPPY DISK STATUS FIELD EQUATES

stat0		equ	0		; Offsets into Status string returned by FDC
stat1		equ	1		; Every bit means something, so refer
stat2		equ	3		; to the Intel documentation.

					; BIT EQUATES FOR STATUS REGISTER 0

STR0_IC6	equ	40H		; Interrupt Code bit 6
STR0_IC7	equ	80H		; Interrupt Code bit 7
STR0_NR		equ	08H		; not ready
STR0_SE		equ	20H		; seek end
STR0_UNIT_MASK	equ	03H		; unit mask
RLC		equ	0C0H		; Ready Line Change


					; BIT EQUATES FOR STATUS REGISTER 3

STR3_UNIT_SEL0		EQU	1H	; drive the FDC was working with
STR3_UNIT_SEL1		EQU	2H	; used by door open code
STR3_HEAD_ADDR		EQU	4H	; Head FDC was working with
STR3_TRACK_0		EQU	10H	; Head over track 0 
STR3_READY		EQU	20H	; FDD's ready status
STR3_WRT_PROT		EQU	40H	; FDD's write protect status
STR3_FAULT		EQU	80H	; FDD's fault status

					; FLOPPY CONTROLLER PORT ADDRESSES

fd_port		equ	0c0h		; floppy base port
fdcs		equ	fd_port		; status register
fdcd		equ	fd_port + 1	; data register
fdma		equ	fd_port + 2	; dma addr when write
ints		equ	fd_port + 2	; status register when read
ser		equ	fd_port + 3	; serial port

					; FLOPPY CONTROLLER FUNCTION DEFINITIONS

f_s_rtk		equ	02		; single density read track
f_d_rtk		equ	42H		; double density read track
f_spec		equ	03		; specify
f_dsts		equ	04		; drive status
f_rdat		equ	06		; read sector fm
f_drdt		equ	46H		; read sector mfm
f_wrat		equ	05		; write sector fm
f_wrdt		equ	45H		; write sector mfm
f_reca		equ	07		; recalibrate
f_rsts		equ	08		; read status
f_seek		equ	0fh		; seek
f_s_read_id	equ	0ah		; single density read ID
f_d_read_id	equ	04ah		; double density read ID


					; FLOPPY DRIVE CONTROL EQUATES

spt1		equ	16 - 8		; shugart 800s (8 ms)
;		equ	16 - 3		; shugart 850s (3 ms)
;		equ	16 - 3		; remex (3 ms)


					;	DPB	EQUATES

dspt		equ	0		; physical sectors per track
bsh		equ	2		; block shift factor
blm		equ	3		; block mask
exm		equ	4		; extent mask
dsm		equ	5		; disk storage max
drm		equ	7		; directory mask
al0		equ	9		; allocation vector 0
al1		equ	10		; allocation vector 1
cks		equ	11		; check sum vector
off		equ	13		; physical track offset
dos		equ	12		; offset to match DOS DPB and DPB
fatadd		equ	10		; FAT address

					; FOR COMMAND AND STATUS CIRCULAR BUFFER
RET_ADDR	equ	4		; bp and di

FLOPPY_INT_DRIVEN	EQU	TRUE
FLOPPY_POLL_DRIVEN	EQU	not FLOPPY_INT_DRIVEN

					; THESE ARE FOR THE DISK ERRORS
DRV_NOT_READY		EQU	0
DRV_FAULT		EQU	1
DRV_RECAL		EQU	2
ERROR_MESSAGE_LENGTH	EQU	25

MAIN_CONSOLE_INPUT	EQU	0


					; FOR THE FLOPPY DISK CODE READABILITY

SUCCESS			EQU	0H
FIRST_CALL_FLAG		EQU	1
N_MASK			EQU	00001100B
FLPY_CNT_MASK		EQU	03H
TWO_SIDE_BIT		EQU	08H
FDC_INT_CODE_MASK	EQU	0C0H
FDC_SENSE_DRV_STAT_MSK	EQU	07H
STATUS_N_FIELD		EQU	6
N_FIELD_MASK		EQU	03
START_NO_END		EQU	01000000B
EOT_END			EQU	80H

RECAL_MASK		EQU	11111100B
RECAL_TEST		EQU	20H
MAX_FLPY_TRKS		EQU	76
NEW_CYLINDER		EQU	2
DRIVE_MASK		EQU	00FFH
MAX_DRIVES		EQU	2
HEAD_BIT		EQU	4
DRV_HD_OFFSET		EQU	1

SEEK_MASK		EQU	11111100B
VALID_SEEK		EQU	00100000B
SIST_RESULT_LEN		EQU	2
INT_WAIT_POLL_BIT	EQU	80H
FDC_CMD_OUT_TEST	EQU	080H
FDC_CMD_IN_TEST		EQU	0C0H

MSTR_RQM		EQU	80H
MSTR_IO_DIRECTION	EQU	40H
RW_ERROR		EQU	01H
RW_MEDIA_CHANGE		EQU	0FFH
NO_MEDIA_ERROR		EQU	0FFH

SELDSK_ERROR		EQU	0
RETRY			EQU	1
ACCEPT			EQU	2
IGNORE			EQU	3

NO_TRACK		EQU	0FFH
ASCII_UPPER_CASE_MASK	EQU	11011111B

FDC_READ		EQU	1
FDC_WRITE		EQU	2

TRACK_10		EQU	10

ALTOS_DD		equ	02H

					; Interrupt Control Equates
PRECAL		equ	1
PSEEK		equ	2
PRW		equ	3
PRDID		equ	4

					; Interrupt Error Equates
CMD_ERR		equ	0
STAT_ERR	equ	1
SEEK_ERR	equ	2

					; number of times to decrement a counter
					; to use up 12 us.
BURN		equ	4		; 23 clocks for a memory decrement,
					; + 6 clocks to calculate its address,
					; = @ 120 clocks

eject ! include dskcomn.equ

cgroup	group	code, data

eject ! include sysdat.lib


	dseg


extrn	current_reskew:word
extrn	xlts:byte, xltd1:byte, xltd2:byte, xltd3:byte
extrn	dpbs1:byte, dpbs2:byte, dpbd1:byte, dpbd2:byte, dpbd3:byte
extrn	dpbd4:byte, dpbd5:byte, dpbd6:byte, dpbpc1:byte

extrn	re_skew_26_2:byte, re_skew_15_2:byte, re_skew_8_2:byte
extrn	log_phy:byte, phy_log:byte
extrn	disk_modes:byte


extrn	dph_tbl:word

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

type_disk_error	db	0		; error log byte

fdc_op		db	0		; operation byte that read or write
					; will pass to phys_rw

tburn		dw	0		; FDC needs 12 us between each read
					; of the master status port.This is the 
					; the counter we use for it in process 
					; context.

iburn		dw	0		; The counter for use in interrupt
					; context.

int_drv_flpy	db	TRUE		; turns on the interrupt drive for
					; the floppies

	cseg

public	read, write, f_dsk_sel
public	fdc_init, flint

extrn	dispatch:word
extrn	sysdat:word

extrn	doorop:byte
extrn	supif:near
extrn	read_write:near, io_read:near

eject

;==========
flint:					; Floppy Disk Interrupt Entry
;==========
;
;	ENTRY:	interrupt entry
;
;	EXIT:	leap to the system dispatcher.
;	NOTE:	this interrupt handler expects an 8080 mode IO system


	push ds				; set up a local stack
	mov ds,sysdat

	mov flpy_int_ssreg,ss
	mov flpy_int_spreg,sp

	mov ss,sysdat
	mov sp,offset flpy_int_tos

	push ax				; save the registers we need
	push bx				; in interrupt context

	call flpy_fork_r

	pop bx				; restore the registers used
	pop ax				; by the interrupt handler

	mov ss, flpy_int_ssreg		; restore the pre-interrupt stack 
	mov sp, flpy_int_spreg

	pop ds				; now the pre-interrupt data segment

	jmpf cs:dword ptr dispatch	; go to the system dispatcher

flpy_fork_r:
;-----------
;
; 	This takes care of the FDC interrupt request
; 	It detects a door-open interrupt from the FDC
;
;	ENTRY:	from the flint
;
;	EXIT:	none
;
	dseg

int_ctl		db	0		; Interrupt Control Byte. Routine that
					; initiated the IO must set this .
fsb		db	0		; 1st status byte read from FDC's status
					; stack when handling a R/W or Read ID
					; when processing a read/write
					; interrupt.

fdc_interrupt_stat	db	0, 0	; returned status from an interrupt
;				^  ^
;				|  +---	present cylinder number
;				+------	fdc status byte 0

last_main_status	db	0	; last read from FDC's main status port
					; when FDC had a request for the master
mstr_lc		dw	0		; Master Status Port Loop Counter
					; Used to find out if we have to wait
					; for FDC to become ready for command
					; or Status transfers after it sends us
					; an interrupt.

ffrc0		dw	0		; Incremented if int handler is entered
					; and FDC wants a Sense Int Status Cmd,
					; and Int Code shows this interrupt is
					; not from a change in a FDC ready line.

ffrc1		dw	0		; loop counter for the FDC when
					; FDC gets ready to give us status

ffrc2		dw	0		; loop counter for the FDC when
					; establishing the data direction
					; from the FDC to the CPU.

	cseg

ffr0:

	push cx ! push dx		; Make sure FDC is ready for either a
	push di ! push si		; a Command or Status Transfer
	push bp ! push es
ffr1:
	in al, FDCS 			; Is FDC ready for data transfer ?
	test al, MSTR_RQM 		
	  jnz ffr2			; Yes
	inc mstr_lc			; inc the master port loop count
	  jmps ffr1			; query the port again.

ffr2:					; FDC ready for data transfer
	mov last_main_status, al	; save the main status register image
	test al, MSTR_IO_DIRECTION 	; Does FDC want a command ? 
          jz ffr18			; Yes
	  jmp ffr3			; No, it wants status (or a truffle).

ffr18:					; Send Sense Interrupt Status command.
	  mov al, F_RSTS		; Removes the FDC's pending interrupt.
	  out FDCD, al		
	  call pic_reset		; Reset the FDC's PIC IR line
	  push cx			; Read the FDC's interrupt status
	  mov cx, length fdc_interrupt_stat
	  mov bx, offset fdc_interrupt_stat	

ffr9:					; Wait for FDC to be ready for status rd
	call ibrn
	in al, FDCS
	test al, MSTR_RQM
	  jnz ffr14
	inc ffrc1
	  jmps ffr9
ffr14:
	test al, MSTR_IO_DIRECTION
	  jnz ffr15
	call ibrn
	in al, FDCS
	inc ffrc2
	  jmps ffr14
ffr15:
	  in al, FDCD			; get the data byte
	  mov [bx], al			; save the data byte
	  inc bx			; point to next buffer position
	  loop ffr9			; loop until the entire expected
					; status is read
	  pop cx			; restore counter register
	  cmp int_ctl, PSEEK		; Expect a seek complete interrupt ?
	    jne ffr7			; No
	    jmps ffr11
ffr7:					; Check for Recal complete interrrupt
	cmp int_ctl, PRECAL
	  jne ffr8			; Don't expect one.
ffr11:			        	; check Interrupt Code bits (6 & 7) of 
					; STR0 for change of ready line status
	mov al, fdc_interrupt_stat
	and al, STR0_IC6 or STR0_IC7 or STR0_SE
	cmp al, RLC 
          jne ffr10			; Not due to change of status.
	call d_open			; Change of status, post door-open flag
	  jmps ffre
ffr10:					; Interrupt not due to change of ready
					; line status and we expect a seek or
					; or recal complete interrupt.
	mov int_ctl,0			; This implies the seek or recal has
       	call iset			; completed. Reset int_ctl & do flagset
	  jmps ffre
ffr8:					; Didn't expect a Seek or Recal complete
					; interrupt. Check Int bits of STR0
	mov al, fdc_interrupt_stat	; for change of ready line status.
	and al, STR0_IC6 or STR0_IC7 or STR0_SE
	cmp al, RLC 
         jne ffr13			; Not due to change of status.
	call d_open			; Change of status, post door-open flag
	  jmps ffre
ffr13:					; No change in ready line status & we're
					; not expecting a seek or recal complete
					; interrupt. Record event and leave.
	inc ffrc0
	  jmps ffre

ffr3:					; FDC wants the CPU to read its status 
					; Expect to handle a Read ID or R/W int
	cmp int_ctl, PRW		; Are we expecting a R/W complete int ?
	  jne ffr4			; No
	  jmps ffr12

ffr4:
	cmp int_ctl, PRDID		; Expecting a Read ID interrupt ?
	  jne ffr5			; No

ffr12:
	mov int_ctl, 0			; reset int_ctl
	in al, FDCD			; get the status byte
	mov fsb, al			; save it for the process
	call pic_reset			; Reset the FDC's PIC IR line
	call iset			; wake up the process
	  jmps ffre	  

ffr5:			        	; We should never get here.
					; FDC wants to send us Status, but we
					; never issued a command that should leave
					; the FDC in a state where it wants to send
					; us status.
	call stat_errf

ffre:				
	pop es ! pop bp			; restore the environment
	pop si ! pop di
	pop dx ! pop cx

	ret


iset:
;----
; INTERRUPT (flag) SET
;
	mov dl, fdc_flag		; set the systems floppy flag
	mov cl, f_flagset
	call supif
	ret

pic_reset:				; Reset the floppy's PIC IR line
;---------

	pushf
	cli
	mov al, FLPY_EOI
	out MASTER_PIC_PORT, al
	popf
	ret



d_open:					; Door Open
;------
;
;	Set the global door open flag in the XIOS header.
;	Set the media flag in the DPH of the drive that 
;	issued the door open interrupt.
;	Use the unit select code from status register 0 of the FDC 
;	to figure out which drive issued the door open interrupt.
;
;	Also use the unit select code from STR0 to index into the DPH 
;	table for the DPH that needs it's media flag posted.

	mov doorop, TRUE		; global door open interrupt flag
					; post media flag in the DPH 
	xor bx, bx			; clear the index register
	mov bl, fdc_interrupt_stat	; get the physical floppy code
	and bl, STR0_UNIT_MASK		; mask off the irrelevant bits
	mov bl, phy_log[bx]		; bias the index off of the first
					; floppy in the system.
					; this translates from the 
					; controller's drive number to the
					; system drive number
	shl bx, 1			; it's a word value table
	mov bx, dph_tbl[bx]		; bx = DPH of the drive that needs
					; it's media flag set
	mov MF[bx], TRUE		; set the MF 

	ret



stat_errf:				; Status Error Handler
;---------
;	By the time we get here the FDC is expecting to have
;	it's status read, but we never issued a command that
;	should put the FDC is this state.
;
;	The FDC has an outstanding interrupt pending to the PIC.
;	We have to clear the pending interrupt from the FDC
;	so we can reset the PIC.
;
;	There is no code ready to handle this interrupt so
;	just iret when we get back.

	dseg

stat_errc	dw	0		; status error counter
stat_errb	rb	10H		; status error handler buffer
bytes_read	dw	0		; the number of status bytes read

sefs		db	0		; status error first status byte
					; read flag
	cseg

	inc stat_errc			; indicate we have been here
	mov sefs, TRUE			; initialize the first status 
					; byte flag
	mov bytes_read, 0		; clear the number of bytes read
	mov bx, 0			; index into the buffer

se0:
	call ibrn
	in al, FDCS			; wait for the FDC to become
	test al, MSTR_RQM		; ready to send us its status
	  jz se0
	test al, MSTR_IO_DIRECTION 	; Check data transfer direction
          jz se1
	in al, FDCD			; FDC still wants to send us status.
	mov stat_errb[bx], al		; put it in the circular buffer
	inc bx			        ; inc  the buffer pointer
	and bx, length ( stat_errb - 1 )
	inc bytes_read			; inc  the number of bytes we read
	cmp sefs, TRUE 			; Is this 1st status byte read ?
          jne se2			; No
	call pic_reset
	mov sefs, FALSE
se2:
	jmps se0
se1:

	ret

ibrn:
;----
;	Time Use routine used by the interrupt code to wait for the FDC.

	mov iburn, BURN
ibrn0:
	dec iburn
	  jnz ibrn0
	ret


eject

fdc_init:				; FDC initialization
;--------
;	Sets up the values for FDC's 3 internal timers :
;	  HUT (head unload time)	= (0fh * 16ms) [240ms]
;	  SRT (step rate)		= (0fh - 0dh * 1ms increments) [3ms] 
;	  HLT (head load time) 		= (31H * 2ms) [98ms]
;
;	Set up FDC's DMA mode:
;	  ND ( non-dma bit )		= DMA mode [0]
;	(See pgs.41,38 of DISK1 manual.
;
;	This system uses a byte per floppy as a bit vector for floppy type.
;	It also changes the DRM field of DOS DBPs.
;
;	ENTRY:	none
;
;	EXIT:	The FDC is initialized as specified above

	dseg

spec_str	db	f_spec, 0dfh, 70	

	cseg

	mov bx, offset spec_str		; intialize the FDC
	mov cx,length spec_str
	call send_cmd
	sti				; Now init FMT to zeros.

	push es				; save the extra segment
	mov ax,ds			; set up for store string operation
	mov es,ax
	xor ax,ax
	mov cx, length fmt
	mov di, offset fmt
	rep stosb			; clear the table to 0
	pop es				; restore the extra segment

	mov	bx, offset dpbpc1	; Run time fix of DRM,CKS and DSM
	mov	word ptr DRM+DOS[bx],67 ; field of PCMODE DPB
	mov	word ptr CKS+DOS[bx],801aH
	mov	word ptr DSM+DOS[bx],495

	ret

;====
read:
;====
;
;	ENTRY:	bp = addr of IOPB
;
;	EXIT:	if track = 0	disks are treated as SS SD 
;		if track > 0	go to the read write deskewing routine 

	cmp TRACK, 0 
         jne rd1
	mov fdc_op, FDC_READ
	call trk0_rw
	mov si, offset read1
	call read_write
	push ax
	call trk0_rw1
	pop ax
	  jmps rd2
rd1:					; Not track 0
	mov si, offset read1
	  jmp read_write
rd2:
	ret
	
read1:
;-----
;

	mov fdc_op, FDC_READ
	call phys_rw

	ret



;=====
write:
;=====
;
;	ENTRY:	bp = addr of IOPB
;
;	EXIT:	if track = 0	disks are treated as SS SD 
;		if track > 0	go to the read write deskewing routine 


	cmp TRACK, 0 
         jne wrt0
	mov fdc_op, FDC_WRITE
	call trk0_rw
	mov si, offset write1
	call read_write
	push ax
	call trk0_rw1
	pop ax
	  jmps wrt1
wrt0:					; Not track 0
	mov si, offset write1
	  jmp read_write
wrt1:
	ret


write1:
;------
;

	mov fdc_op, FDC_WRITE		; copy current FMB
	call phys_rw

	ret


trk0_rw:
;-------
;	This is the setup for track 0 
;
;	ENTRY:	bp = addr of IOPB
;

	dseg

save_fmb	db	0		; temp store for current fmb
saved_reskew	dw	0		; temp store for the current reskew
rw_save		rb	9		; temp store for the current rw_cs

	cseg


	mov	f_call,FALSE		; used in f_dsk_sel
	mov al, c_fmb			; save the current FMB
	mov save_fmb, al

	mov ax, current_reskew		; save the current reskew index
	mov saved_reskew, ax
					; save read write control string
	pushf				; set up the flags and es
	push es
	mov ax, ds
	mov es,ax
	cld

	mov cx, 9			; set up the length, source, and dest
	mov si, offset rw_cs
	mov di, offset rw_save
	rep movsb			; do the move

	pop es				; restore the environment
	popf

	xor al,al			; set the floppy mode byte
	mov c_fmb, al			; fmb code for a S.S. S.D. floppy

	mov ax, offset re_skew_26_2	; set the current reskew factor
	mov current_reskew, ax

	call ds0			; entry point in disk select that
					; has the c_fmb established. This
					; call sets up the select
					; specific bytes in the rw_cs
	ret

trk0_rw1:
;-------
;	This is the reset from track 0 
;
;	ENTRY:	bp = addr of IOPB
;

	mov al, save_fmb		; restore the current FMB
	mov c_fmb, al

	mov ax, saved_reskew		; restore the reskew factor
	mov current_reskew, ax
					; restore the pre-track 0 operation 
					; read write control string
	pushf				; set up the flags and es
	push es
	mov ax, ds
	mov es,ax
	cld

	mov cx, 9			; set up the length, source, and dest
	mov di, offset rw_cs
	mov si, offset rw_save
	rep movsb			; do the move

	pop es				; restore the environment
	popf

	ret

phys_rw:				; Physical read/write
;------
; 	Does the actual read or write from the floppy.
;
;	ENTRY:	fdc_op	= FDC_READ if a read / FDC_WRITE if a write
;		idrive	= the drive to read or write
;		itrack	= the track to read or write from
;		isector	= the sector to read or write from
;		idmaoff	= the dma offset to read or write to or from
;		idmaseg	= the dma segment to read or write to or from
;
;		N, GTL, and DTL in rw_cs are to be set up by io_seldsk
;
;	EXIT:	al = SUCCESS if everthing went OK
;		al = RW_MEDIA_CHANGE if the media was changed
;		al = RW_ERROR if an error other than a detectable
;		     media change occured.
;

	dseg

;	           D  C  H  R  N  EOT  GPL   DTL
rw_cs	db	0, 0, 0, 0, 0, 0,  0,   0,    0	; read_write_control_string

;		S  S  S  			; r/w status return string
;		T  T  T
;		0  1  2  C  H  R  N
rw_ss	db	0, 0, 0, 0, 0, 0, 0 


last_fmb	db	0

rw_rc		db	0		; read write recal counter
rw_rt		db	0		; read write retry counter, used
					; by the sense drive status code
rw_rt1		db	0		; read write retry counter, used
					; by the main read/write loop

	cseg

	mov rw_rc, RW_RECAL_COUNT	; Pre-load retry counters.
	mov rw_rt, RETRY_COUNT
	mov rw_rt1, RETRY_COUNT
rw1:					; Is drive on line ?
	mov 	al,phy_drive		
	call sense_drvst
	cmp al, SUCCESS 
          je rw3
	dec rw_rt			; Drive wasn't ready.
	  jz rw2
	    				; We still have have some more retries.
	    				; Wait a while and check the ready status
	    				; After the second try, wait 16MS longer
	    				; than the previous attempt.
	 mov dx, RETRY_COUNT		; if retries > 0 then
	 sub dl, rw_rt			;  delay = Max retries - retries left
	 cmp dx, 0 
           jne rw12	
	 mov dx, DELAY_16MS		;  else delay = 1
rw12:
	 mov cl, F_DELAY
	 call supif
	    jmps rw1			; check the ready status again
rw2:					; Ran out of retries while checking for
					; the drive being ready. So, return 
	mov 	ah,ATTA_FAILED		; a hard error.
	mov al, RW_ERROR
	  jmp rw_e
	
rw3:					; The drive is ready and on line. Set
					; up the read write control string
	call rw_preamble		; and do the seek.
	cmp al, SUCCESS 
          je rw4
        mov ah,SEEK_FAILED		; rw_preamble had trouble with the seek
	mov al, RW_ERROR		; Return an error.
	  jmp rw_e
rw4:
	mov rw_rt1, RETRY_COUNT		; reset the retry count
rw7:					; Top of the inner retry loop
	mov bx, offset rw_cs		; send out the command string
	mov cx, length rw_cs
	call send_cmd
	mov int_ctl, PRW		; set the interrupt action control
	sti

	call int_wait			; wait for the read or write

	mov bx, offset rw_ss		; get the results of the operation
	mov cx, length rw_ss
	call stat_ret
	mov al, rw_ss			; get the FDC's return code
	and al, FDC_INT_CODE_MASK	; check for start but no end due to EOT
	cmp al, START_NO_END 
          je rw5	
          jmps rw6			; Error other than End of Track
rw5:					; error may have been due to EOT, check
					; with Status register 1
	cmp byte ptr (rw_ss + 1), EOT_END
	  jne rw6
	mov al, SUCCESS			; Return code was EOT
	  jmps rw_e
rw6:					; error was not an EOT, so retry.
	dec rw_rt1
	  jnz rw7			; If we haven't exhausted our retry
					; attempts go to top of the inner loop
	  				; Else had 10 failures to read the disk
	  				; If no recal attempts were made,check 
					; for a media change.
	cmp rw_rc, RW_RECAL_COUNT
	  jne rw8
	mov al, ( rw_cs + D )		; sample the drive
	call build_fmb
	cmp al, c_fmb			; Compare it to what we thought we were
					; reading.If not the same, it's a media
	  jne rw11			; change.
rw8:					; At this point we've made 10 retries
					; on this sector, and media is the same
					; as the one expected. Do recal.
	dec rw_rc			; decrement the recal counter
	  jz rw9
	mov al, ( rw_cs + D )		; get the drive
	call recal			; do a recal
	  jmp rw3			; goto the top of the recal loop
rw9:					; We've tried 5 recals and 50 retries
					; give up on the disk and return
	mov	ah,ATTA_FAILED		; hard error to the BDOS.
	mov al, RW_ERROR
	  jmps rw_e
rw11:					; Media was changed.
	mov ah, NO_EXTD_ERR		; Return media change to the BDOS.
	mov al, RW_MEDIA_CHANGE
rw_e:					; read write exit point
	or al,al			; set the flags on the return code

	ret


eject

rw_preamble:
;-----------
;
; Read/write pre-amble does the following funtions
;
;   1] sets up D, C, H, R, and EOT fields of rw_cs
;   2] sets up the 24 bit DMA address for the Disk1
;   3] positions the head over the cylinder the track is on
;      that we want to read or write.
;
;	ENTRY:	fdc_op	= read or write opertion
;		idrive	= drive to prepare for the read or write
;		itrack	= cylinder to seek to
;		isector	= sector to read/write
;		idmaoff	= where to get or put it
;		idmaseg	=   "   "   "  "   "   "
;		c_fmb   = the current floppy mode byte for this drive
;			  it is set up by a previous BDOS call to io_seldsk
;
;	EXIT:	al = ERROR if there was a seek error
;		al = SUCCESS if the seek happened OK
;
;	If the return code is SUCCESS the system exists in the
;	following state:
;
;	  1] the command byte, D, C, H, R, and EOT fields of 
;	  2] the R/W control string are set up
;	  3] the dma address ( segment and offset ) is set up on the
;	     DISK 1 controler.
;	  4] The Head is positioned over the proper cylinder.
;
;	The following image of the read write control is here for
;	reference, so we can see what bytes in the control string 
;	are set up.
;
;		   command byte
;			|
;		        V  D  C  H  R  N  EOT  GPL   DTL
;	rw_cs	db	0, 0, 0, 0, 0, 0,  0,   0,    0	
;
;
	dseg

rwp_rt		db	0		; retry counter, used by seek 
					; code
rwp_cyl		db	0		; rw_preambles local copy of the
					; cylinder to seek to.
	cseg

	; Set up the Read Write control string Record and End Of Track
	; fields.  The R and EOT fields are set to the same value
	; so the FDC will error out at the end of a track.  This is
	; because the Terminal count pin on the FDC is tied to ground.

	; The Bdos uses 0 relative sectors, but the FDC uses 1 relative
	; sectors.

	mov al, isector 	; get the sector the de-skewing/re-skewing
				; wants returned.
	inc al			; translate from the BDOS's 0 relative
				; sector reference to the FDC's 1 relative
				; sector reference
	mov ( rw_cs + R ),al	; stuff the read write control string's
				; record field
	mov ( rw_cs + EOT ),al	; and the read write control string's EOT
				; field

					; set up the read or write control byte
					; use the fdc_op code to see if it's a
					; read or write.Use density bit of FMB 
					; to see if it's single or dbl
	cmp fdc_op, FDC_READ		; Is it a read ?
	  jne rwp2			; No
	test c_fmb, D_DENSITY 		; Is it double ?
          jz rwp1			; No
	mov rw_cs, F_DRDT
	  jmps rwp4
rwp1:					; it's a single density read
	mov rw_cs, F_RDAT
	  jmps rwp4
rwp2:					; it's a write operation
	test c_fmb, D_DENSITY 
          jz rwp3
	mov rw_cs, F_WRDT
	  jmps rwp4
rwp3:					; it's a single density write
	mov rw_cs, F_WRAT
rwp4:					; FDC op code is now set
	lea bx, idmaoff			; set up the dma addr for DISK1
	mov dx,2[bx]! mov ax,dx		; get the segment
	mov cl,4! shl ax,cl		; (para to bytes) shift add to offset
	mov cl,4! shr dh,cl		; save ms nibble
	add ax,[bx]			; add the bytes
	adc dh,0			; carry into msn

	mov dl,al			; send 24 bits to controller
	mov al,dh! out F_DMA,al		; msb - lsb
	mov al,ah! out F_DMA,al
	mov al,dl! out F_DMA,al

	; Now we have to seek to the proper cylinder
	; for single sided disks the track requested is the
	; cylinder we need to put the head over.
	; For a single sided disk we always used head 0.
	; for double sided disk we have to divide the track by two
	; to derive the cylinder.  The head is selected depending on the
	; track, if were seeking to an ODD track it's head 1,
	; if it's an even track we select head 0.

	mov rwp_rt, RETRY_COUNT		; pre-load retry count
	mov ax, itrack			; get CCP/M's logical track
	test c_fmb,TWO_SIDED		; is it a two sided disk ?
	  jz rwp5			; No
        shr ax,1			; Yes, div by 2 to get cylinder
rwp5:					; al-> cylinder we want to seek to
	mov rwp_cyl, al
	mov ( rw_cs + C ), al		; Set cylinder # for R/W control string
	mov	al,phy_drive		;Set up head/drive field of R/W ctl str
	test c_fmb, TWO_SIDED		; is it a double sided disk
	  jz rwp6			; No
	test itrack, 1			; Check lsb for odd track.
	  jz rwp6			; Even track.
	or al,HEAD_BIT			; Set head bit in drive field of R/W
					; ctl string for head 1
	mov byte ptr ( rw_cs + H ), 1	; Now set head field for head 1
	  jmps rwp7
rwp6:					; it is head 0
	mov byte ptr ( rw_cs + H ), 0
rwp7:					; AL -> drive code with head bit OR'd in
	mov ( rw_cs + D ), al		; Copy into R/W control string.
rwp8:
	mov ah, ( rw_cs + D )		; set up the drive/head parameter
	mov al, rwp_cyl			; set up the cylinder parameter
	call p_seek			; do the seek

	cmp al, SUCCESS 
          je rwp9 
	dec rwp_rt			; Seek failed, do the retry.
	  jz rwp10
	mov dx, DELAY_16MS		; Wait, and try again.
	mov cl, F_DELAY
	call supif
	  jmps rwp8
rwp10:					; Out of retries, return hard error
	mov	ah,SEEK_FAILED
	mov al, ERROR
rwp9:
	or al,al
	
	ret

;=========
f_dsk_sel:				; Disk Select Entry Point
;=========
;
;	The floppy system supports 10 different disk formats
;	They are:
;
;	  1]	SD	128 bytes/sector	26 sectors/track
;	  2]	DD	128 bytes/sector	26 sectors/track
;	  3]	DD	256 bytes/sector	26 sectors/track
;	  4]	DD	512 bytes/sector	26 sectors/track
;	  5]	DD	1024 bytes/sector	26 sectors/track
;	  6-10]	same as 1 - 5 with double sided disks.
;
;	On a first time select as indicated by dl(0) = 0 , this :
;	
;	  1]	determines what kind of floppy is in the drive
;	  2]	does a recal, homes the head
;	  3]	sets up the floppy mode table byte for this disk.
;	  4]	sets up the dph for this disk
;
;	on all calls it
;
;	  1]	sets up the current floppy mode byte.
;	  2]	sets up the select specific bytes in the r/w control string
;		N, GPL, and DTL
;	  3]	returns the addr of the DPH
;
;	The following image of the read write control is here for
;	reference, so we can see what bytes in the control string 
;	are set up when the disk select call is made.
;
;		   command byte
;			|
;		        V  D  C  H  R  N  EOT  GPL   DTL
;	rw_cs	db	0, 0, 0, 0, 0, 0,  0,   0,    0	
;
;
;	ENTRY:	cl = concurrents logical drive
;		dl(0) = first select request
;			if dl(0) = 1 this is not a first call request
;			else if dl(0) = 0 this is a first call request
;
;	EXIT:	Ax, Bx  = addr of the DPH if everything went OK
;			= 0 if there was an error or if a non-supported
;			  drive was requested.
;	

	dseg

phy_drive	db	0		; current drive we're selecting
c_fmb		db	0		; the current floppy mode byte
save_xlt	rw	1
f_call		rw	1		; first call select flag
edpb		rw	1		; correct DPB if a PC_MODE disk

fat_id		rw	1		; FAT id if PC_MODE disk
		rb	126		; make room for a 128 byte sector

					; FLOPPY MODE BYTE AND TABLE
;
;	7 6 5 4 3 2 1 0
;	        \/    +	-------	set = two sided, reset = single sided
;		 |  +----------	set = double density, reset = single density
;	         +-------------	0 = 128 byte sectors
;				1 = 256 byte sectors
;				2 = 512 byte sectors
;				3 = 1024 byte sectors

fmt		rb	16		; floppy mode table

					; Gap length table. Indexed by the N
					; field read from disk sector header,
					; Values correspond to those the FDC
					; expects in the GPL byte of R/W ctl st
gpl_tbl	db	07H			; SD 128 byte sectors
	db	0EH			; DD 256 byte sectors
	db	1BH			; DD 512 byte sectors
	db	35H			; DD 1024 byte sectors

					; the address of the re skewing tables
; It is indexed by the value in the N field of the Read Write control string

re_skew_tbl	dw	offset re_skew_26_2	; S.D. 128 bytes/sec
		dw	offset re_skew_26_2	; D.D. 256 bytes/sec
		dw	offset re_skew_15_2	; D.D. 512 bytes/sec
		dw	offset re_skew_8_2	; D.D. 1024 bytes/sec

	cseg

	xor	bx,bx
	mov	bl,cl		; logical index
	mov 	al,log_phy[bx]	; convert from concurrents logical
				; drive to our controller drive reference
	mov 	phy_drive,al	; stash the physical drive we are selecting
;***	mov	bl,disk_modes[bx]	; get mode of drive for easy ref.
;***	mov	c_mode,bl
	mov	bl,al		; put logical drive into physical floppy table
	mov	phy_log[bx],cl

	; check for this being the first call for this floppy
	test dl, FIRST_CALL_FLAG
	jnz nfc
	  ; this is the first call
	  ; so, take care of it.	al has physical floppy number
	  mov	f_call,TRUE
	  call first_call
	  cmp al, ERROR ! jne ds4
	    ; there was an error when we made the first call 
	    ; so, set up the error code for CCP/M and return the error
	    mov ax, SELDSK_ERROR
	    mov bx, ax
	    jmp ds3			; leap to the exit point
ds4:
	; we made it through the first call for this disk
	; al contains the current FMB ( floppy mode byte )
	; bx contains Extended DPB (used if this is a PC_MODE disk)

	mov	bx,pc_dpb_tbl[bx]	; store EDPB
	mov	edpb,bx
	mov c_fmb, al			; set the current FMB

	; put the current FMB in the FMT ( floppy mode table )

	xor bx, bx			; clear the memory pointer register
	mov bl, phy_drive			; set the index into the FMT
	mov fmt[bx], al			; put the FMB into the FMT indexed 
					; by the drive we're working with.
	jmps ds0			; leap around getting the FMB 
					; from the FMT
nfc:
	; we land here when this is not a first time select for
	; this disk. 

	mov	f_call,FALSE
	xor bx, bx			; clear the memory pointer
	mov bl, phy_drive		; index into the FMT as per the drive
	mov al, fmt[bx]			; get the drive's FMB
	mov c_fmb, al			; set our current FMB to 
					; this drives FMB

ds0:	;*** ENTRY POINT FOR THE TRK0_RW ROUTNE ***
	;
	; at this point the current FMB is set up for the disk
	; that has been selected, and al contains an image of the current
	; FMB.

	; Set up the N field of the Read Write control string.
	; at this point al contains an image of the current FMB

	and al, N_MASK		; mask out the N field from the FMB
	shr al,1 ! shr al,1	;   move the N field into position
	mov ( rw_cs + N ), al	; for the read write control string

	; Set up the DTL field of the Read Write control string.
	; The DTL field is only used when the N field is zero.
	; The DTL field must be set to 0FFH if it's not used.
	; When the N field is 0 the DTL field value is the number of
	; bytes to transfer for each sector.  The FDC still reads the
	; entire sector to calculate the check sum.
	; The N field is only zero on a single density floppy.
	; At this point al contains an image of the Read Write 
	; control string N field.

	cmp al, 0 
	jne ds1
	  ; this must be a single density floppy
	  mov byte ptr ( rw_cs + DTL), 128
	  jmps ds2
ds1:
	; this must be double density floppy
	mov byte ptr ( rw_cs + DTL ), 0ffh 
ds2:

	; Set up the GPL ( inter-record gap length ) field of the 
	; read write control string.  There is a different inter-record
	; depending on the size and density of the sector.  This can
	; be determined by the value of the N field.
	; At this point al still contains the value of the N field.

	xor bx, bx			; clear the memory pointer
	mov bl, al			; get the table index
	mov al, gpl_tbl[bx]		; get the GPL value
	mov ( rw_cs + GPL ), al		; initialize the control string
					; GPL field

	; Set up the re_skew vector
	; In order to minimize rotation latency of any of the floppy
	; disk formats we have to re skew the order in which the sectors
	; are read from the disk.
	; There is a table of address of the re skewing tables that
	; is indexed by the N field of the FMB.

	xor bx, bx			; clear the memory pointer
	mov bl, c_fmb			; get the FMB
	shr bx, 1			; shift the N field into place
	and bx, 0FEH			; to be used as an word index 
					; ( multiplied by two )
	mov ax, re_skew_tbl[bx]		; get the reskewing table address
	mov current_reskew, ax		; into the current reskewing pointer

;***	; return the DPH for concurrents logical disk
;***
;***	xor bx, bx			; clear the memory pointer
;***	mov bl, idrive			; get CCP/M's logical drive
;***	shl bx, 1			; times 2 for word values
;***	mov ax, dph_tbl[bx]		; get the DPH into ax
;***	mov bx, ax			; and put it into BX also
;***
;***
	; For PC_MODE disk support we need to check disk type
	;  and return the Extend DPB for PC_MODE disk
	;
	; On first time calls we will get the fat_id and if it
	;  matches PC_MODE disk types we will modify the DPH
	;  to reference the EDPB and set the XLT field to 0.

	mov	ax,f_call
	cmp	ax,FALSE
	je	not_a_fat		; not first time, so DPH is already set up
	mov	bx,edpb			; If Extended DPB is 0 then disk in
	cmp	bx,0			;  drive is not supported as a PC_MODE
	jz	cpm_disk		;  disk, so assume CCP/M

	; read the first FAT sector

	mov	al,idrive
	mov	ah,1
	push	ax			; drive | m_cnt
	xor	dx,dx
	mov	ax,FATADD[bx]		; bx has edpb in it
	div	word ptr DOS+SPT[bx]
	push	ax			;     track
	push	dx			;     sector
	push	ds			;    dma seg
	mov	ax,offset fat_id
	push	ax			;    dma off
	sub	sp,4			;   far return
	call	io_read

	add	sp,14			; clear stack
	cmp	al,0
	jz	ok_read
	mov	ax,SELDSK_ERROR
	mov	bx,ax
	ret				; report error

ok_read:
	mov	ax,fat_id
	cmp	ax,0fffcH
	jb	cpm_disk		; This is a CP/M disk

	; change DPH to indicate PC_MODE disk

	xor	bx,bx
	mov	bl,idrive
	mov	disk_modes[bx],1	; indicate PC_MODE disk
	shl	bx,1
	mov	bx,dph_tbl[bx]		; get present DPH
;***	mov	XLT[bx],0
	mov	ax,edpb
	mov	DPB[bx],ax
	jmps	not_a_fat

cpm_disk:
	xor	bx,bx
	mov	bl,idrive
	shl	bx,1
	mov	bx,dph_tbl[bx]
	mov	ax,save_xlt
	mov	XLT[bx],ax

not_a_fat:

	; at this point the present DPH is correct for the current disk
	
	xor	bx,bx
	mov	bl,idrive
	shl	bx,1
	mov	ax,dph_tbl[bx]		; get DPH
	mov	bx,ax

ds3:
	ret

eject


first_call:
;----------
;
;	This routine does the following
;	  1] builds the FMB for the drive passed in al
;	  2] puts this FMB in the FMT ( floppy mode byte table )
;	     for the drive specified.
;	  3] sets up the DPH ( disk parameter header )
;	     3.1] Puts in the address of the proper skew table
;	     3.2] Puts in the address of the proper DPB ( disk parameter 
;		  block )
;
;	ENTRY:	al = drive to perform the first call dance on
;		     the drives are 0 relative.
;
;	EXIT:	al = the FMB ( floppy mode byte ) that was built 
;		     for the disk specified in al.
;		al = ERROR if there was an error


	dseg

fc_drv		db	0		; local drive code for first call

fc_fmb		db	0		; local version of the FMB

; This table contains the addresses of the skewing tables.
; The skewing table will change for each floppy.
; The value in the N field is used to select the proper skew table
; to plug into the skew field of the DPH for a given floppy.

xtable	DW	(Offset xlts)		;single 128
	DW	(Offset xltd1)		;double 256
	DW	(Offset xltd2)		;double 512
	DW	(Offset xltd3)		;double 1024

; This table is indexed by the FMB ( floppy mode byte ).
; Each entry corresponds to a uniquie bit combination of the
; FMB.  At each entry is the address of the DPB that concurrent
; expects to use for the floppy with the FMB that indexed into the
; table.  This DPB address is in turn put into the DPH that
; corresponds to the floppy selected.

dpb_tbl	dw	dpbs1		; ss,	sd,	128
	dw	dpbs2		; ds,	sd,	128
	dw	0
	dw	0
	dw	0
	dw	0
	dw	dpbd1		; ss,	dd,	256
	dw	dpbd2		; ds,	dd,	256
	dw	0
	dw	0
	dw	dpbd3		; ss,	dd,	512
	dw	dpbd4		; ds,	dd,	512
	dw	0
	dw	0
	dw	dpbd5		; ss,	dd,	1024
	dw	dpbd6		; ds,	dd,	1024

; same as above but for PC_MODE disks

pc_dpb_tbl	dw	dpbpc1		; ss,	sd,	128
		dw	0		; ds,	sd,	128
		dw	0
		dw	0
		dw	0
		dw	0
		dw	0		; ss,	dd,	256
		dw	0		; ds,	dd,	256
		dw	0
		dw	0
		dw	0		; ss,	dd,	512
		dw	0		; ds,	dd,	512
		dw	0
		dw	0
		dw	0		; ss,	dd,	1024
		dw	0		; ds,	dd,	1024


	cseg

	; figure out what type of floppy is in the drive.

	mov fc_drv, al
	call build_fmb
	cmp al, ERROR ! jne fc1
	  ; there must have been an error 
	  ; when we attempted to build the FMB
;***	  mov al, ERROR
	  jmps fc_exit
fc1:
	; At this point al contains the FMB
	; stash the FMB for use later in this routine

	mov fc_fmb, al

	; check for the disk being an ALTOS Double Density disk
	; WE are not supporting this format
	; so, we will return an ERROR at this time

	cmp al, ALTOS_DD ! jne fc3
	  ; the disk was an Altos DD 
	  ; so, return an error
	  mov	ah,NO_EXTD_ERR
	  mov al, ERROR
	  jmps fc_exit
fc3:
	; the disk was not an Altos DD so finish the First call dance

	; set up the DPH ( disk parameter header )
	; Scince we are supporting 10 different types of floppies
	; we have to dynamicly change the DPH's to reflect the type of
	; floppy in the drive at first select time.
	; The fields of the DPH that have to be set up for each floppy
	; are the Skew table address, and the DPB address.
	; The DPH is determined by concurrents logical disk number


	; Get the address of the DPH for the drive passed in
	; into di

	xor bx, bx			; clear the table index register
	mov 	bl,idrive		; get the logical drive
	mov	disk_modes[bx],0	; assume CCP/M until proven PC_MODE
	shl bx, 1
	mov di, dph_tbl[bx]		; get the dph's address

	; AT this point di contains the addr of the DPH we're
	; building, and al contains a copy of the FMB

	; The FMB's bit(2) and bit(3) ( bits being 0 relative )
	; contain the bytes per sector code.  This code is used
	; to index into the table of translate tables.

	xor bh, bh			; clear the xlate table index
	mov bl, al			; current FMB into index register
	shr bx, 1			; shift the bytes/sector field
					; into position to be used as an
					; index into xlate table
	and bx, 0FFFEH			; mask out the bit that was used
					; to hold the density attribute
	mov ax, xtable[bx]		; get the translate table address
	mov	save_xlt,ax
	mov 	XLT[di],0		; No XLT needed for PC_MODE test

	; The DPB table contains an entry for each possible
	; combination of bits from the floppy mode byte
	; This code uses the current FMB to index into the DPB table
	; to select the proper DPB for the disk we found in the drive

	xor bh, bh			; clear index into DPB table
	mov bl, fc_fmb			; get the FMB
	shl bx, 1			; times 2 for a word table index
	mov ax, dpb_tbl[bx]		; get the DPB's address (CCP/M)
;***	cmp	c_mode,0		; Is it a CCP/M disk?
;***	jz	yes3
;***	mov	ax,pc_dpb_tbl[bx]	; get the DOS DPB's address (extended)
;***	cmp	ax,0			; Is this disk type supported?
;***	jnz	yes3
;***	mov	ax,ERROR
;****	jmp	fc_exit
;***yes3:
	mov DPB[di], ax			; put the DPB's address into the
					; DPH we're building
	mov al, fc_fmb			; set this for our return code
fc_exit:

	ret


build_fmb:
;---------
;
;	This routine builds the floppy mode byte from the disk.
;
;	ENTRY:	al = disk the FMB should be built for ( 0 relative )
;
;	EXIT:	al = FMB if every thing went OK
;		al = ERROR if something went wrong while we
;		     were building the FMB.

	dseg

fmb		db	0	; this is the current floppy mode byte

; 		FLOPPY MODE BYTE BIT DEFINITIONS
;
;	7 6 5 4 3 2 1 0
;	        \/    +	-------	set = two sided, reset = single sided
;		 |  +----------	set = double density, reset = single density
;	         +-------------	0 = 128 byte sectors
;				1 = 256 byte sectors
;				2 = 512 byte sectors
;				3 = 1024 byte sectors

bfmb_rt		db	0	; retry counter, this is used when we're
				; attempting to seek to track 10.  To
				; sample the media.
bfmb_rt1	db	0	; retry counter 1, this is used when we're
				; sampling the media with the read ID
				; routine.

bfmb_dsk	db	0	; this is the disk we're building
				; the FMB for. It is initialized upon
				; entry to this routine.

; Density table.
; This table is indexed by the N factor from the read ID status
; string.

den_tbl	db	0000b		; 128 bytes/sector
	db	0100b		; 256 bytes/sector
	db	1000b		; 512 bytes/sector
	db	1100b		; 1024 bytes/sector

	cseg


	; pre-load both retry counters used in making the query to the disk 

	mov bfmb_rt, RETRY_COUNT
	mov bfmb_rt1, RETRY_COUNT

	mov bfmb_dsk, al		; save off the disk we're working
					; for use later in this routine
bfmb0:
	; recalibrate the drive, and FDC for this drive.
	; This puts the head over track 0.
	; The recal routine takes care of it's own retrying
	; and checking for drive status functions.

	call recal
	cmp al, SUCCESS ! je bfmb1
	  ; an error was returned when 
	  ; we tried to recal the drive
	  mov al, ERROR
	  jmp bfmb_e
bfmb1:
	; The drive has been recalibrated.
	; Put the head over track 10, to get past the system
	; track(s).

	mov ah, bfmb_dsk		; set the drive to seek on
	mov al, TRACK_10		; set the track to seek to
	call p_seek			; do the seek
	cmp al, SUCCESS ! je bfmb3
	  ; an error was returned from the seek
	  ; so, do the retrying movie
	  dec bfmb_rt			; decrement the retry count
	  jnz bfmb1			; if it's not 0 try again
	    ; the retry counter hit 0
	    ; so, return a hard error
	    mov	ah,SEEK_FAILED
	    mov al, ERROR		; set the error code
	    jmp bfmb_e			; leap to the routine exit point.
bfmb3:
	; At this point the head is over track 10
	; so we have to build the FMB

	mov fmb, 0			; initialize the FMB to 0 
					; this is equivilent to single sided,
					; single density disk

	; The previous calls for recalibrating the drive 
	; and seeking to track 10 both made queries to the FDC
	; for the drive status.  The most current drive status
	; is now in the status register 3 table of the sense drive
	; status subroutine.  This value is used to determine
	; the number of sides for the floppy in the current drive
	; we're working with.

	xor bx, bx			; index into the drive status table
	mov bl, bfmb_dsk		; as per the disk we're working with
	test stat_reg_3_tbl[bx], TWO_SIDE_BIT ; test for the number of sides
	jz bfmb4
	  ; it must be a double sided disk
	  ; so, or this atribute into the FMB
	  or fmb, TWO_SIDED
bfmb4:
	; The number of sides has been resolved
	; so, resolve the density ( either SD or DD ).
	; The way we resolve the density is by attempting
	; to read the ID field of the first sector that goes by
	; the head as a single density disk.  If this read ID operation
	; fails we try to read the first sector going by the head
	; as a double density disk.
	; If we get an error from the double density read ID we go into
	; a retry movie.
	; If the disk happens to be a double density disk we read the
	; N field from the read ID status string to figure out how many
	; bytes per sector this double density disk has.
	; The density and bytes per sector information is put into
	; the FMB.

	mov ah, bfmb_dsk		; set up the disk to query
	mov al, FS_READ_ID		; query for single density
	call read_id			; query the disk

	mov al, STAT0[bx]		; get the results
	and al, FDC_INT_CODE_MASK	; mask for the interrupt code
	cmp al, SUCCESS ! jne bfmb5	; was the read ID a success
	  ; yes, this completes building 
	  ; the FMB for a single density floppy.
	  ; fmb contains the code for a single or
	  ; double sided, single density disk.
	  ; so leap to the exit point
	  jmps bfmb6
bfmb5:
	; the single density read ID failed.
	; so try to read the disk as a double density disk.

	mov ah, bfmb_dsk		; set up disk to query
	mov al, FD_READ_ID		; query for double density
	call read_id			; query the disk

	mov al, STAT0[bx]		; get the results
	and al, FDC_INT_CODE_MASK	; mask for the interrupt code
	cmp al, SUCCESS ! je bfmb7	; was the read ID a success
	  ; no, an error was returnd from the
	  ; double density read ID attempt.
	  ; so, do the retry movie.
	  dec bfmb_rt1			; decrement the retry count
	  jnz bfmb4			; and try again
	    ; we have exhausted our retry
	    ; limit so return a hard error
	    mov	ah,ATTA_FAILED
	    mov al, ERROR
	    jmps bfmb6
bfmb7:
	; At this point we know the disk is a double density disk
	; so, or the double density attribute into the FMB

	or fmb, D_DENSITY

	; get the disk's N factor from the read ID status string.
	; then use this N factor to index into the density table
	; the density table contains the FMB's density code
	; ( bytes per sector, assumed double density at this point )

	mov bl, STATUS_N_FIELD[bx]	; get the N factor
	xor bh,bh			; clear the indexes high byte
	mov al, den_tbl[bx]		; get the density mask
	or fmb, al			; or the density mask into the 
					; FMB.
bfmb6:
	mov al, fmb			; make sure the FMB is in al
bfmb_e:

	ret


	
read_id:
;-------
;
; read ID expects the control byte and the drive/head byte
; set up before it is called.
; It returns with bx pointing to the returned status.
;
;	ENTRY:	ah = drive and head code
;		al = FDC's command code for the density to read
;		      the ID of.
;
;	EXIT:	bx = addr(rdid_ss)  ( read ID status string )
;		al = error return code
;			SUCCESS if the read ID status string contains
;				the results of an attempted read ID
;				operation
;			ERROR if the drive was not ready.

	dseg

;	READ ID CONTROL STRING
;
rdid_cs	db	0, 0
;		^  ^
;		|  +---	head and drive
;	  	+------	control byte

;	READ ID STATUS STRING
;
;		S  S  S
;		T  T  T
;		0  1  2  C  H  R  N
rdid_ss	db	0, 0, 0, 0, 0, 0, 0 
	
	cseg

	; set up the read ID control string

	mov rdid_cs, al			; set up the control byte
	mov ( rdid_cs + D ), ah		; set up the drive and head field

	mov al, ah			; sense drive status requires the
					; drive in al
	and al, FLPY_CNT_MASK		; mask out the head bit
	call sense_drvst		; is the drive ready

	cmp al, SUCCESS ! je rdid1
	  ; the drive was not ready
	  ; so, return a hard error
	  mov al, ERROR
	  jmps rdid2
rdid1:
	; the drive is on line and ready
	; so, attempt to read the first header cruising by the head

	mov bx, offset rdid_cs		; send the read id command
	mov cx, length rdid_cs
	call send_cmd
	mov int_ctl, PRDID		; set the interrupt control 
	sti

	call int_wait			; wait for the read

	mov bx, offset rdid_ss 		; get the status
	mov cx, length rdid_ss
	call stat_ret

	mov al, SUCCESS			; we tried to read the ID
					; so, set a kosher return code
 	mov bx, offset rdid_ss		; return a pointer to the status
					; string

rdid2:

	ret


p_seek:
;------
;
; Seek primitive.  This builds the seek command string from the 
; parameters passed in.  It checks the drive status and tries to
; seek.  After the seek it checks the return code from the FDC to see
; if the FDC and the program agree on the track the head is over.
;
; If the drive is not ready, off line, or if the track the FDC thinks the
; head is over is different from the track we wanted an error is returned.
; This routine does no retries.  It is the responsibility of the caller
; for all error recovery.
;
;	ENTRY:	ah = drive to seek on ( the head bit must be set up )
;		     controler relative
;		al = track to seek to
;
;	EXIT:	al = SUCCESS if everthing went OK
;		al = ERROR if it could not find the right track
;		     or the drive was off line

	dseg

seek_f_s	db	f_seek, 0, 0
;				^  ^
;				|  +---	new cylinder number
;				+------	head and drive
;
; Refer to the fdc_interrupt_stat data structure at the beginning
; of this program for a picture of the status returned by the FDC
; from a seek operation.

	cseg

	; set up the control string for a seek from the drive and
	; track passed in.

	mov ( seek_f_s + D ), ah	; set up the drive to seek on
	mov ( seek_f_s + C ), al	; set up the track to seek to

	mov al, ah			; parameter setup for 
					; sense drive status
	call sense_drvst		; now sense the drive's ready status
	cmp al, SUCCESS! je ps0
	  ; the drive is not ready
	  ; so, return a hard error
	  jmps ps_e
ps0:
	; the drive must be on line and ready

	mov bx, offset seek_f_s		; send the seek command
	mov cx, length seek_f_s
	cli
	call send_cmd
	mov int_ctl, PSEEK		; set the interrupt code
	sti

	call int_wait			; wait for the seek

	mov al, fdc_interrupt_stat	; get status word 0
	and al, SEEK_MASK		; mask for the seek complete bit
	cmp al, VALID_SEEK ! je ps1
	  ; the seek did not complete so return an error
	  mov	ah,SEEK_FAILED
	  mov al, ERROR
	  jmps ps_e
ps1:
	; the seek completed with no error, so far
	; check the FDC's idea of the track the head is over with the
	; callers idea of the track the head is over.

	mov al, ( seek_f_s + C )	  ; compare the cylinder we're over
	cmp al, ( fdc_interrupt_stat + 1) ; to the one we wanted
	mov al, SUCCESS
	je ps_e
	  ; the track the FDC thinks we are over 
	  ; is different from the track the caller requested.
	  ; so return an error.
	  mov	ah,ADR_NOT_FND
	  mov al, ERROR 
ps_e:
	or al,al			; set the flags for a return code

	ret

recal:
;-----
;
; this issues the recalibrate command to a drive
;
;	ENTRY:	al = drive to recal. 
;
;	EXIT:	al = ERROR if there was an error
;		al = SUCCESS if everything went OK

	dseg

rccs		db	f_reca, 0	; re-cal control string

rcl_drv		db	0		; the drive recal is working with

rcl_rt		db	0		; recal error count, used when
					; sensing a drive's status.
rcl_rt1		db	0		; recal error count 1, used when
					; exacuting the recalibrate command

	cseg

	mov rcl_rt, RETRY_COUNT		; pre-load retry counter 0, used
					; by the code checks for the drive
					; being ready and on line.

	mov rcl_rt1, RETRY_COUNT	; pre-load retry counter 1, used
					; by code that sends the recal 
					; command to the FDC

	mov rcl_drv, al			; stash the drive

rcl0:	
	and al, FLPY_CNT_MASK		; mask out the head bit (drive only)
	mov ( rccs + D ), al		; stuff the control string

	call sense_drvst		; is the drive ready ?
	cmp al, SUCCESS ! je rcl1
	  ; no, so do the retry movie
	  dec rcl_rt			; decrement the retry count
	  jz rcl5			
	    ; there are some more retries left
	    ; so wait for a little while
	    mov dx, DELAY_16MS
	    mov cl, F_DELAY
	    call supif
	    mov al, rcl_drv		; restore the drive were waiting for
	    jmps rcl0			; goto the top of the disk error loop
rcl5:
	; we ran out of retries while we were waiting
	; for the drive to become ready
	; so, return a hard error
	mov	ah,ATTA_FAILED
	mov al, ERROR
	jmps rcl2
rcl1:
	; At this point the drive must 
	; be ready and on line 

	mov bx, offset rccs		; send the recal command
	mov cx, length rccs
	cli
	call send_cmd
	mov int_ctl, PRECAL		; set the interrupt code
	sti

	call int_wait			; wait for the recal to happen

	; the floppy interrupt routine reads
	; the FDC's interrupt sense.
	; fdc_interrupt_stat is the value read.

	mov al, fdc_interrupt_stat	; check for a kosher recal
	and al, RECAL_MASK
	cmp al, RECAL_TEST ! je rcl3 	; exit if recal ok
	  ; something went wrong with 
	  ; the recal, do the retry movie
	  dec rcl_rt1 
	  jz rcl4
	    mov dx, DELAY_16MS		; wait for a system tick
	    mov cl, F_DELAY
	    call supif
	    mov al, rcl_drv		; restore the drive we're recaling
	    jmps rcl1	    		; goto the top of the error loop
rcl4:
	; we ran out of retries
	; so, return a hard error
	mov	ah,ATTA_FAILED
	mov al, ERROR
	jmps rcl2	
rcl3:
	mov al, SUCCESS
rcl2:
	or al,al
	
	ret


sense_drvst:
;-----------
;
; Sense drive status
;
; This routine issues the sense drive status to the FDC for
; the drive passed in al.  It also updates the status register
; three table for this drive.  This is the closest point to the
; drive status query in the program.  So by updating the drive status
; table in this routine the drive status table is as current as possible.
;
;	ENTRY:	al = the drive we want to query.
;
;	EXIT:	al = SUCCESS if the drive is on line
;		     ERROR if the drive is not on line
;		stat_reg_3_tbl[drive] = status register 3
;
	dseg


sdscs	db	f_dsts, 0	; sense drive status control string
sdsst	db	0		; sense drive status returned status

; STATUS REGISTER 3 TABLE, these reflect the most current
; status of the floppy drives.

stat_reg_3_tbl	db	0	; floppy 0, ( A )
		db	0	; floppy 1, ( B )
		db	0	; 
		db	0	; 
		rb	12	; for more floppies latter...

; loop counters to keep track of this routine

sdsc0	dw	0
sdsc1	dw	0
sdsc2	dw	0
sdsc3	dw	0

	cseg

	; query the FDC for the FDD's current status

	and al, FLPY_CNT_MASK		; mask out any head info
	mov ( sdscs + D ), al		; set up the sense disk stat string
	mov bx, offset sdscs		; use the send command routine
	mov cx, length sdscs		; for history it will provide

sds3:
	call tbrn
	in al, FDCS			; wait for the FDC to become 
	test al, MSTR_RQM		; ready for data xfer
	jnz sds4
	  inc sdsc0
	  jmps sds3
sds4:
	test al, MSTR_IO_DIRECTION	; wait for the FDC to become
	jz sds5				; ready to receive a command
	  call tbrn
	  in al, FDCS
	  inc sdsc1
	  jmps sds4
sds5:
	mov al, [bx]			; get the command byte
	out FDCD, al			; send it to the FDC
	inc bx				; point to next byte
	loop sds3			; loop until the complete command is 
					; sent.

	; wait for the FDc to become ready
	; to send us it's status
sds0:
	call tbrn
	in al, FDCS
	test al, MSTR_RQM
	jnz sds6
	  inc sdsc2
	  jmps sds0
sds6:
	test al, MSTR_IO_DIRECTION
	jnz sds7
	  call tbrn
	  in al, FDCS
	  inc sdsc3
	  jmps sds6
sds7:

	; it's ready, so read the status

	in al, FDCD
	mov sdsst, al

	; stash the byte in the status register 3 table

	xor bh,bh			; clear the upper byte
	mov bl, ( sdscs + D )		; get the drive
	mov al, sdsst			; get status register 3
	mov stat_reg_3_tbl[bx], al	; put the status register 3 just
					; read from the floppy into the
					; table entry for this floppy

	; set the return code for the drive
	; being on line and ready or not.

	test al, STR3_READY ! jnz sds1
	  ; the drive is not ready
	  mov	ah,ATTA_FAILED
	  mov al, ERROR
	  jmps sds2
sds1:
	; the drive is on line and ready
	mov al, SUCCESS
sds2:	

	ret


eject

send_cmd:
;--------
; Send Command
;
;	ENTRY:	bx = addr ( command string )
;		cx = length ( command string )
;
;	EXIT:	the command is sent
;	
;	NOTE:	just before the last byte of a command is sent
;		to the FDC interrupts are turned off.  This is so
;		the caller can post the proper action for the interrupt
;		handler before an interrupt can get in.  The caller
;		is responsible to enabling interrupts as soon as the
;		handler has it's control byte set.
;
;		The FDC requires 12 us between each query of the main
;		status port.  The time burn loops do this

	dseg

scc0		dw	0		; waiting for FDC ready
scc1		dw	0		; waiting for FDC direction

	cseg

	; poll on the fdc to be ready
sc1:
	call tbrn
	in al, FDCS
	test al, MSTR_RQM
	jnz sc2
	  inc scc0
	  jmps sc1
sc2:
	; wait for the FDC's direction

	test al, MSTR_IO_DIRECTION 
	jz sc3
	  call tbrn
	  in al, FDCS
	  inc scc1
	  jmps sc2
sc3:

	; send the command byte

	mov al, [bx]
	out fdcd, al
	inc bx

	; when cx = 1 it means the next byte through
	; will be the last byte of the command being sent
	; to the FDC.

	cmp cx, 1 ! jne scs2		; is the next byte the last ?
	  ; yes, the next 
	  ; byte is the last
	  cli
scs2:
	loop sc1

	ret



stat_ret:
;--------
; STATUS RETURN
;
;	input	cx	number of bytes to expect from the fdc
;		bx	addr of where to put the results
;
;	output		the location starting where bx was pointing
;			for a length of cx will have the results from the 
;			fdc result phase
;
;	NOTE:		The FDC requires 12 us between each query of the main
;			status port.  The time burn loops do this

	dseg

strc0		dw	0		; waiting for FDC ready counter
strc1		dw	0		; waiting for FDC direction counter

	cseg

	mov al, fsb			; get the first status byte, that
	mov [bx], al			; was read by the interrupt handler

	inc bx				; inc  the status buffer pointer
	dec cx				; inc  the bytes left count

	jcxz sre			; if it's a one byte worth of status
					; skip the loop

str1:
	; wait for the FDC to become ready to send it's status
	call tbrn
	in al, FDCS
	test al, MSTR_RQM
	jnz str2
	  inc strc0
	  jmps str1
str2:
	test al, MSTR_IO_DIRECTION
	jnz str3
	  call tbrn
	  in al, FDCS
	  inc strc1
	  jmps str2
str3:

	in al, FDCD			; get the FDC's status byte

 	mov [bx], al			; stash the status byte
	inc bx				; point to the next buffer position
	loop str1			; loop until all the status bytes are
					; read.
sre:
	ret

eject


tbrn:
;----
;
;	Time burn routine used by the interrupt code to wait for
;	the FDC

	mov tburn, BURN
tbrn0:
	dec tburn
	jnz tbrn0
	ret

int_wait:
;
;	this routine does a flag wait on the fdc operation.

	cmp int_drv_flpy, TRUE! je real_int
iw1:
	in al, ints			; poll the interrupt status
	test al, INT_WAIT_POLL_BIT
	jz iw1

	ret

real_int:

	mov dl, fdc_flag
	mov cl, f_flagwait
	call supif

	ret


		rw	48
flpy_int_tos	rw	0
flpy_int_ssreg	rw	1
flpy_int_spreg	rw	1

	end
