title 'Disk 2 Hard Disk Driver' ;************************************************ ; * ; HARD DISK DRIVER MODULE * ; Last changed : 2/13/84 * ; * ;************************************************ include system.lib eject ! include diskhdr.equ include exerror.equ HD_IRL equ 02H ; hard disk interrupt request line MASTER_PIC_PORT EQU 50H RW_ERROR EQU 01 SUCCESS EQU 0 RET_ADDR EQU 04 ; FOR HARD DISK WITH A DISK 2 CONTROLLER H_READ_F EQU 1 H_WRITE_F EQU 2 ; disk 2 ports STAT_CONTROL equ 0C8H DISK_2_DATA equ STAT_CONTROL + 1 DISK_2_SELECT equ 0F0H ROUTINE_USE equ 2 phys_drvs equ 1 ; # physical hard drive(s) supported track_0 equ 0 ; this is track 0 drive_sel_op equ 0 ; disk 2 drive select op code drive_0 equ 10h ; this is the first and only drive ; in the system seek_outer equ false seek_inner equ true home_count equ 512 ; status equates ATTN_NOT equ 80h TIME_OUT equ 40h CRC_ERROR equ 20h OVER_RUN equ 10h READY_NOT equ 08h SEEK_COMPLETE_NOT equ 04h WRITE_FAULT_NOT equ 02h TRACK0_NOT equ 01h ; control equates RUN EQU 40H OP2 EQU 20H OP1 EQU 10H OP0 EQU 08H FAULT_CLR EQU 04H USR1 EQU 02H USR2 EQU 01H ; command equates NULL_F equ 0C0H CLEAR_INT equ 080H READ_DATA_F equ 0C8H WRITE_DATA_F equ 0D0H WRITE_HEADER_F equ 091H READ_HEADER_F equ 0E0H ; seek direction, op2(bit 5) of the ; command word sets the direction ; signal to the disk drive. INNER EQU 20H OUTTER EQU 00H ; offsets for read header command buffer TEST_CYLINDER EQU 0H TEST_HEAD EQU 1H TEST_SECTOR EQU 2H eject ; For the DISK2 selector card ; See page 28 of the DISK2 manual ; the patterns given are for: ; d7 = write/read ; d6 = memory (vs io port operations) ; d5 = increment the memory address ; d4 = no wait states ; d3 - d0 = complement of the dma dev ; the selector channel will work with DISK_2_INT_PRIORITY equ 10 DISK_2_DMA_PRIORITY equ not DISK_2_INT_PRIORITY WRITE_TO_DISK equ 00100000b or ( DISK_2_DMA_PRIORITY and 0fh ) READ_FROM_DISK equ 10100000b or ( DISK_2_DMA_PRIORITY and 0fh ) ; For the read/write routines TRACK_OFFSET EQU 0 HEAD_OFFSET EQU 2 SECTOR_OFFSET EQU 4 DMA_OFFSET equ 6 ; these are the op codes for the DISK2 OP_DRIVE equ 080H OP_TRACK equ 088H OP_HEAD equ 090h OP_SECTOR equ 098h eject H_DISK_FLAG EQU 8 HD_EOI EQU 61H cseg public h_read, h_write, h_dsk_sel public h_init, h_disk_int extrn intdisp:near extrn supif:near, sysdat:word extrn read_write:near dseg extrn dph_tbl:word extrn dph1:byte, dph2:byte, dph3:byte extrn current_reskew:word extrn itrack:word, isector:byte, idmaoff:word, idmaseg:word hd_stat db 0 ; Interrupt handler will put status ; from the DISK2 controller here before ; it resets the PIC. eject ! include sysdat.lib eject cseg ;========== h_disk_int: ; hard disk interrupt handler ;========== ; push ds mov ds,sysdat mov h_disk_ssreg,ss ; may need to check for mov h_disk_spreg,sp ; re-entrant interrupt since mov ss,sysdat ; nmi's can't be disabled mov sp,offset h_disk_tos push ax ! push bx push cx ! push dx push di ! push si push bp ! push es in al, ( MASTER_PIC_PORT + 1 ) ; get the PIC's mask or al, HD_IRL ; mask out the Disk 2 out ( MASTER_PIC_PORT + 1 ), al ; write the mask back to the PIC mov dx, H_DISK_FLAG mov cl,F_FLAGSET call supif mov al, HD_EOI ; just reset the PIC's out MASTER_PIC_PORT,al pop es ! pop bp pop si ! pop di pop dx ! pop cx pop bx ! pop ax mov ss, h_disk_ssreg mov sp, h_disk_spreg pop ds jmp intdisp ; iret from dispatcher eject ;========= h_dsk_sel: ;========= ; ; Translates cpm logical drive to a controller specific drive ; ; ENTRY: cl = cpm logical drive ; bx = offset into a dph table ; dl[bit 0] = select code ; ; EXIT: ax = bx = &dph if every thing went ok ; ax = bx = 0 if there is no DPH for this drive ; ; NOTE: this routine just indexes into the dph table ; and returns whatever is there. There is no protection. mov ax, dph_tbl[bx] mov bx, ax mov current_reskew, 0 ret ;==== h_init: ;==== ; Initialzation of the hard disk sub-system. ; Initializes the memory structures, homes the head, ; and puts the disk2 and its selector in a safe state. ; See microdisk drives C/E Manual (lsi version) by FUJITSU ; and page 6 of the disk 2 manual ; init the last physical sector to 0 mov last_cyl,track_0 ; do the initial read of the selector channel ; pages 27 and 28 of the disk 2 guide in al, DISK_2_SELECT ; init the header buffer d word mov ( header_buffer_addr + 2 ), ds ; we only are supporting one drive ; wait for the drive to be come ready mov al, OP_DRIVE ; select drive command call send_command mov al, DRIVE_0 ; for this drive call send_data init_drv_rdy_lp: ; wait for the drive mov al, NULL_F call send_command call stat_return test al, READY_NOT jnz init_drv_rdy_lp call h_home jz init_exit or al, ERROR ; extended from h_home init_exit: ret eject ;====== h_read: ;====== ; this routine is called by the bdos ; ; ENTRY: bp = &(IOPB on the stack) ; ; EXIT: OK if the read happened ok ; RW_ERROR if there was a read error ; mov hard_op, H_READ_F mov si, offset h_rd_wt jmp read_write ;====== h_write: ;====== ; this routine is called by the bdos ; ; ENTRY: bp = &(IOPB on the stack) ; ; EXIT: OK if the read happened ok ; RW_ERROR if there was a read error ; mov hard_op, H_WRITE_F mov si, offset h_rd_wt jmp read_write ;======= h_rd_wt: ;======= ; ; This is the main sector read routine for concurrent ; It does everything possible to make the read. ; The drive is assumed to be 0 and selected befor this operation. ; ; ENTRY: hd_op = READ if we want a read, WRITE if we want a write ; itrack = the track we want to read ; isector = the sector we want to read ; idmaoff = where to get or put the data ; idmaseg = where to get or put the data ; ; EXIT: al = OK if every thing went OK ; al = RW_ERROR (01) if there was a fatal error ; mov bx, offset rw_track mov h_rd_wt_param_add, bx ; save the param pointer ; translate the track from concurrent to the track and ; sector for the hard disk ; this assumes 8 heads [ 0 - 7 ] mov ax, itrack and ax, 07 mov HEAD_OFFSET[bx], al mov ax, itrack mov cl, 3 shr ax, cl mov TRACK_OFFSET[bx], ax ; move the remaining params passed in from read_write to ; our local copies xor ax, ax mov al, isector mov SECTOR_OFFSET[bx], ax mov ax, idmaoff mov DMA_OFFSET[bx], ax mov ax, idmaseg mov DMA_OFFSET + 2 [bx], ax ; select the drive and ; check for the drive being ready. mov al, OP_DRIVE call send_command mov al, DRIVE_0 call send_data call stat_return and al, READY_NOT! jz rd_drv_rdy mov ah,ATTA_FAILED ; drive was not ready or al, RW_ERROR ret rd_drv_rdy: mov re_homes, RETRY_COUNT / 2 re_home_lp: mov h_rd_wt_retries, RETRY_COUNT h_rd_wt_retry_lp: mov bx, h_rd_wt_param_add ; set up the parameter pointer ; seek to the proper track mov ax, TRACK_OFFSET[bx] mov cyl,ax call seek mov bx, h_rd_wt_param_add ; set up the parameter pointer ; set up the drive register mov al, OP_DRIVE ; select the drive/head register for call send_command ; for the subsequent out instruction xor ax,ax ; only drive 0 is valid mov ax, HEAD_OFFSET[bx] and ax, 0fH ; THE lower nibble in al is head or al, DRIVE_0 ; the upper nibble is the drive call send_data ; set up the track register mov al, OP_TRACK call send_command mov ax, TRACK_OFFSET[bx] ; select the track call send_data ; set up the head register mov al, OP_HEAD call send_command mov ax, HEAD_OFFSET[bx] ; select the head and ax,0fh call send_data ; set up the sector register mov al, OP_SECTOR call send_command mov ax, SECTOR_OFFSET[bx] ;select the sector and al, 0ffh call send_data ; set up the dma base and direction lea bx, DMA_OFFSET[bx] cmp hard_op, H_READ_F jne h_wrt_now mov al, READ_FROM_DISK jmps set_h_dma h_wrt_now: mov al, WRITE_TO_DISK set_h_dma: call set_disk_2_dma ; do the read or write cmp hard_op, H_READ_F jne h_wrt_now_1 mov al, READ_DATA_F jmps h_do_rd_wt h_wrt_now_1: mov al, WRITE_DATA_F h_do_rd_wt: call send_command ; get the status byte and deal with any errors call h_int_wait call stat_return test al, TIME_OUT or CRC_ERROR or OVER_RUN jnz h_rd_wt_err_1 and al, SUCCESS jmp h_rd_wt_exit h_rd_wt_err_1: ; stash the last return error code from the controller mov h_rd_wt_error_type, al ; check the number of retries dec h_rd_wt_retries ! jnz rd_wt_retry_1 ; re-home the head after 5 retries dec re_homes ! jz re_home_1 call h_home jmp re_home_lp re_home_1: mov ah,ATTA_FAILED mov al, RW_ERROR jmp h_rd_wt_exit rd_wt_retry_1: ; if it's a crc_error or a data_over_run error just retry it test al, TIME_OUT! jnz rd_wt_retry_2 jmp h_rd_wt_retry_lp rd_wt_retry_2: ; must be a time out error, so check for the proper track mov bx, offset header_buffer_addr call read_header cmp al, SUCCESS! je check_track or al, RW_ERROR ; extented error set in read_header jmps h_rd_wt_exit check_track: ; we h_rd_wt the header so check the track mov bx, h_rd_wt_param_add mov ax, TRACK_OFFSET[bx] mov bx, offset header_buffer ; if the head is over the proper cyinder/track just retry cmp ax, TEST_CYLINDER[bx]! jne wrong_track jmp h_rd_wt_retry_lp wrong_track: ; must have been over a wrong track. ; so update last track and re-seek mov ax, TEST_CYLINDER[bx] mov last_cyl,ax mov bx, h_rd_wt_param_add mov ax, TRACK_OFFSET[bx] mov cyl,ax call seek jmp h_rd_wt_retry_lp h_rd_wt_exit: ret eject ;===== h_home: ;===== ; ; this routine will home the drive. ; If the track 0 not signal is true we do nothing. ; If it's false we try to home the heads. ; ; ENTRY: none ; ; EXIT: al = 0ffh on error ; al = 000h if things went OK. ; the zero flag refects al ; if were on track 0 we don't have to home mov al, OP_DRIVE call send_command mov al, DRIVE_0 call send_data call stat_return test al, TRACK0_NOT jz home_exit ; we have do the home mov home_retries,RETRY_COUNT home_retry_lp: ; first find out what track we're over mov bx, offset header_buffer_addr call read_header cmp al, SUCCESS ! je h_home_1 dec home_retries jnz home_retry_lp or al, ERROR ; extended error set in read_header jmp home_exit h_home_1: ; get the current cylinder xor ax,ax mov bx, offset header_buffer mov al, TEST_CYLINDER[bx] mov last_cyl, ax ; seek to cylinder 0 mov cyl, 0 call seek call stat_return test al, TRACK0_NOT ! jz end_home_lp ; try to issue the home command pushf! cli mov al, SEEK_OUTER ; set direction call send_command mov cx, HOME_COUNT ; and bang the port home_lp: in al, DISK_2_DATA ; over 256 reads from the data port loop home_lp ; at a rate between 3K and 3 Meg popf mov dl, ( 15 * DELAY_16_MS ) ; wait for the hard disk to recover mov cl, DELAY_F ; about 250 ms call supif call stat_return test al,TRACK0_NOT ! jz end_home_lp ; if we're still not over track 0 ; get away from the stop and try again mov cyl, 30 call seek dec home_retries jnz home_retry_lp mov ah,SEEK_FAILED or al, ERROR end_home_lp: mov last_cyl, TRACK_0 home_exit: ret eject read_header: ;----------- ; ; This routine reads the three header bytes from the first sector ; encountered on the track the head is over. In essence it just ; drops the head and tells you the current cylinder, head, and sector. ; ; ENTRY: bx = &dma_add (dword) ; ; EXIT: ; al = ERROR if the drive was not ready. ; or if the header couldn't be ready with in ; RETRY_COUNT attempts. ; al = OK if every thing worked OK, ; the address pointed to by bx on the way in ; will contain the cylinder, head, and sector ; for the currently selected drive. mov rd_hdr_retries, RETRY_COUNT rd_hdr_retry_lp: ; set up the dma base and direction mov al, READ_FROM_DISK call set_disk_2_dma rd_hdr_drv_rdy: mov al, READ_HEADER_F call send_command call h_int_wait call stat_return ; check for no errors and al, TIME_OUT or CRC_ERROR or OVER_RUN test al,SUCCESS! jz exit_read_header ; must have some sort of error dec rd_hdr_retries! jnz rd_hdr_retry_lp mov ah,al or al, ERROR test ah,TIME_OUT jz not_time_out mov ah,ATTA_FAILED jmps exit_read_header not_time_out: shr ah,1 ; convert to extended error exit_read_header: ret eject seek: ;---- ; ; This routine seeks to the track passed in. ; It does no error recovery. It expects the READ or Write routine ; to take care of errors. ; ; ENTRY: cyl the cylinder we want to seek to. ; last_cyl the last cylinder we were on. ; The one the head should be over when we ; enter this routine. ; EXIT: there is no error code returned. ; if there is an error in the seek operation, the corresponing ; read or write will fail. When the corresponding read ; or write tries to recover it will do the seeks again. ; Is the current cylinder the one we want. mov ax, cyl cmp ax, last_cyl! je exit_seek jg requested_cyl_greater sub last_cyl,ax ; the last cylinder was greater mov al, OUTTER ; set the direction twords the edge call send_command jmps seek_1 requested_cyl_greater: xchg ax, last_cyl ; the cylinder we want is greater sub last_cyl,ax mov al, inner ; set the direction twords the spindle call send_command seek_1: mov cx, last_cyl ; last_cyl = abs( cyl - last_cyl ) pushf! cli ; critical region seek_2: in al, DISK_2_DATA ; bang the step port loop seek_2 popf ; set the null command and do a flag wait mov al, NULL_F call send_command call h_int_wait call stat_return mov ax,cyl ; update the cylinder register mov last_cyl,ax ; just to get back to here the seek must have happened. ; the only action I can take at this time would be a sample ; of the track field on the disk. This has to be done ; by the read or write any way. So return. exit_seek: ret eject set_disk_2_dma: ;-------------- ; ; This routine sets the 24 bit dma from the dword ; pointed to by Bx. ; ; ENTRY: al = mode byte for the selector channel ; bx = &dma_add (dword) ; ; EXIT: the selector channel will have it's dma ; set to the value passed in. push ax ; just do a read of the selector channel to make it ; ready for the subsequent writes in al, DISK_2_SELECT ; calculate the segment and offset into a 20 bit dma value mov dx, 2[bx]! mov ax,dx mov cl,4! shl ax,cl shr dh,cl add ax,[bx] adc dh,0 ; send this three (3) byte dma value to the disk 2 selector channel mov dl, al in al, DISK_2_SELECT mov al,dh! out DISK_2_SELECT,al mov al,ah! out DISK_2_SELECT,al mov al,dl! out DISK_2_SELECT,al ; send the mode control byte to the selector channel pop ax out DISK_2_SELECT,al ret eject h_int_wait: ;---------- ; ; This routine waits for the interrupt ; ; ENTRY: none ; ; EXIT: none jmps real_h_int int_wait_lp: call stat_return test al, ATTN_NOT jnz int_wait_lp jmp h_int_wait_exit real_h_int: push ax! push bx! push cx! push dx push di! push si! push bp push es! push ds! ; turn on the disk 2's interrupt line in al, ( MASTER_PIC_PORT + 1 ) ; read the PIC's int mask and al, not HD_IRL ; turn on the disk 2 out ( MASTER_PIC_PORT + 1 ), al ; write the mask back out mov dl, H_DISK_FLAG mov cl, F_FLAGWAIT call supif pop ds! pop es pop bp! pop si! pop di pop dx! pop cx! pop bx! pop ax h_int_wait_exit: ret eject send_command: ;------------ ; ; this routine sends a command to the DISK2 controller ; ; ENTRY: al = command ; ; EXIT: none ; ; bx must be preserved out STAT_CONTROL, al ret eject send_data: ;--------- ; ; this routine sends a data byte to the DISK2 controller ; ; ENTRY: al = data byte ; ; EXIT: none ; ; NOTE: bx must be preserved out DISK_2_DATA, al ret eject stat_return: ;----------- ; ; this routine just returns the status from the disk controler ; ; ENTRY: none ; ; EXIT: al = current status from the disk controller in al, STAT_CONTROL ; get the controler status ret rw 48 h_disk_tos rw 0 h_disk_ssreg rw 1 h_disk_spreg rw 1 eject dseg h_rd_wt_error_type db 0 header_buffer_addr dw offset header_buffer dw 0 header_buffer db 0,0,0,0,0 ; should only need three eject ; ; params for h_rd_wt hard_op db 0 rw_track dw 0 rw_head dw 0 rw_sector dw 0 rw_dma_off dw 0 rw_dma_seg dw 0 ; ; these are the current retry counters h_rd_wt_retries db 0 re_homes db 0 write_retries db 0 rd_hdr_retries db 0 home_retries db 0 ; ; The disk 2 controler wants a control word and delivers a status ; word for each operation disk2_control db 0 disk2_status db 0 ; BIT CTL STATUS ; (write) (read) ;-------------------------------------------------------- ; 7 attn_not attn_not ; 6 run time out ; 5 op2 crc error ; 4 op1 over run ; 3 op0 ready_not ; 2 fault clr seek complete_not ; 1 usr1 write fault_not ; 0 usr0 track0_not ; ; where: _not indicates an active low signal ; ; used by init and seek last_cyl dw 0 ; the last cylinder we were on cyl dw 0 ; the current cylinder we want h_rd_wt_param_add dw 0 end