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