/* * fdc.c - Floppy controller handler functions. * * Copyright (C) 1998 Fabian Nunez * (C) 2002 Jari Tuominen * Ported to JTMOS & performance modifications by Jari Tuominen * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * * The author can be reached by email at: fabian@cs.uct.ac.za * * or by airmail at: Fabian Nunez * 10 Eastbrooke * Highstead Road * Rondebosch 7700 * South Africa */ #include "basdef.h" #include "kernel32.h" #include "threads.h" #include "fdc.h" #include "devapi.h" #include "devsys.h" #include "syssh.h" #include "dma.h" // Currently selected drive. // 0=A:, 1=B:, ... (0-3) int fdc_drive=0; /* globals */ static volatile BOOL done = FALSE; static BOOL dchange = FALSE; static BOOL motor = FALSE; static int mtick = 0; static int tmout = 0; static BYTE status[7] = { 0 }; static BYTE statsz = 0; static BYTE sr0 = 0; static BYTE fdc_track = 0xff; static DrvGeom geometry = { DG144_HEADS,DG144_TRACKS,DG144_SPT }; extern void fdc_asmirqhandler6(void); /* physical address of track buffer located below 1M */ // Define a location above the kernel. // NOTE: also used by sound blaster. static long tbaddr = 0x90000; /* prototypes */ void fd_sendbyte(int byte); int fd_getbyte(); void fd_irq6(void); void fd_int1c(void); BOOL fd_waitfdc(BOOL sensei); BOOL fd_fdc_rw(int block,BYTE *blockbuff,BOOL read); // void fd_selectdrive(int which) { if(which<=3) { fdc_drive=which&3; } } // void fd_sleep(int waitAmount_ms) { // int i,i2,i3,i4; // i4=(291*waitAmount_ms)/1000; // enable(); // for(i=glvar.timer[10]; glvar.timer[10]<(i+i4); ) { idle_moment(); } } // void fdc_init(void) { fd_init(); floppy_register_device(); floppy2_register_device(); } /* helper functions */ /* init driver */ void fd_init(void) { int i; // printk("\rfdc.c: Floppy driver initialising ... \r"); /* install IRQ6 handler */ setint(M_VEC+6, fdc_asmirqhandler6, GIMMIE_CS(), D_PRESENT + D_INT + D_DPL3); enable_irq(6); fd_reset(); printk("\rfdc.c: Determining HW version \r"); /* get floppy controller version */ fd_sendbyte(CMD_VERSION); i = fd_getbyte(); if (i == 0x80) printk("fdc.c: NEC765 controller found. \n"); else printk("fdc.c: Enhanced controller found(0x%x). \n", i); } /* deinit driver */ void fd_deinit(void) { /* stop motor forcefully */ outportb(FDC_DOR,0x0c|fdc_drive); } /* fd_sendbyte() routine from intel manual */ void fd_sendbyte(int byte) { volatile int msr; int tmo; for (tmo=0; tmo 0) --mtick; else if (!mtick && motor) { outportb(FDC_DOR,0x0c|fdc_drive); /* turn off floppy motor */ motor = FALSE; } } /* * converts linear block address to head/track/sector * * blocks are numbered 0..heads*tracks*spt-1 * blocks 0..spt-1 are serviced by head #0 * blocks spt..spt*2-1 are serviced by head 1 * * WARNING: garbage in == garbage out */ void fd_block2hts(int block,int *head,int *track,int *sector) { *head = (block % (geometry.spt * geometry.heads)) / (geometry.spt); *track = block / (geometry.spt * geometry.heads); *sector = block % geometry.spt + 1; } /**** disk operations ****/ /* this gets the FDC to a known state */ void fd_reset(void) { /* stop the motor and disable IRQ/DMA */ outportb(FDC_DOR,0|fdc_drive); mtick = 0; motor = FALSE; /* program data rate (500K/s) */ outportb(FDC_DRS,0); /* re-enable interrupts */ outportb(FDC_DOR,0x0c|fdc_drive); /* resetting triggered an interrupt - handle it */ done = TRUE; fd_waitfdc(TRUE); /* specify drive timings (got these off the BIOS) */ fd_sendbyte(CMD_SPECIFY); fd_sendbyte(0xdf); /* SRT = 3ms, HUT = 240ms */ fd_sendbyte(0x02); /* HLT = 16ms, ND = 0 */ /* clear "disk change" status */ fd_seek(1); fd_recalibrate(); dchange = FALSE; } /* this returns whether there was a disk change */ BOOL fd_diskchange(void) { return dchange; } /* this turns the motor on */ void fd_motoron(void) { if (!motor) { mtick = -1; /* stop motor kill countdown */ outportb(FDC_DOR,0x1c|fdc_drive); fd_sleep(V_DELAY_MOTORON); /* delay 500ms for motor to spin up */ motor = TRUE; } } /* this turns the motor off */ void fd_motoroff(void) { if (motor) { mtick = 36*X_TIMER; /* start motor kill countdown: 36 ticks ~ 2s */ } } /* recalibrate the drive */ void fd_recalibrate(void) { /* turn the motor on */ fd_motoron(); /* send actual command bytes */ fd_sendbyte(CMD_RECAL); fd_sendbyte(0); /* wait until seek finished */ fd_waitfdc(TRUE); /* turn the motor off */ fd_motoroff(); } /* seek to track */ BOOL fd_seek(int track) { if (fdc_track == track) /* already there? */ return TRUE; fd_motoron(); /* send actual command bytes */ fd_sendbyte(CMD_SEEK); fd_sendbyte(0); fd_sendbyte(track); /* wait until seek finished */ if (!fd_waitfdc(TRUE)) return FALSE; /* timeout! */ /* now let head settle for 15ms */ fd_sleep(V_DELAY_SEEK); fd_motoroff(); /* check that seek worked */ if ((sr0 != 0x20) || (fdc_track != track)) return FALSE; else return TRUE; } /* checks drive geometry - call this after any disk change */ BOOL log_disk(DrvGeom *g) { /* get drive in a known status before we do anything */ fd_reset(); /* assume disk is 1.68M and try and read block #21 on first track */ geometry.heads = DG168_HEADS; geometry.tracks = DG168_TRACKS; geometry.spt = DG168_SPT; if(fd_read_block(20,NULL)) { /* disk is a 1.68M disk */ if(g) { g->heads = geometry.heads; g->tracks = geometry.tracks; g->spt = geometry.spt; } return TRUE; } /* OK, not 1.68M - try again for 1.44M reading block #18 on first track */ geometry.heads = DG144_HEADS; geometry.tracks = DG144_TRACKS; geometry.spt = DG144_SPT; if(fd_read_block(17,NULL)) { /* disk is a 1.44M disk */ if(g) { g->heads = geometry.heads; g->tracks = geometry.tracks; g->spt = geometry.spt; } return TRUE; } /* it's not 1.44M or 1.68M - we don't support it */ return FALSE; } /* read block (blockbuff is 512 byte buffer) */ BOOL fd_read_block(int block,BYTE *blockbuff) { int rv; int tries; tries=0; retry: rv = fdc_rw(block,blockbuff,TRUE); if( rv==FALSE) { if(tries==3) { fd_reset(); } tries++; printk("fdc.c: trying to read block %d, try=%d\n", block, tries); if(tries<12)goto retry; printk("fdc.c: fd_read_block read failure\n"); } return rv; } /* write block (blockbuff is 512 byte buffer) */ BOOL fd_write_block(int block,BYTE *blockbuff) { return fdc_rw(block,blockbuff,FALSE); } /* * since reads and writes differ only by a few lines, this handles both. This * function is called by fd_read_block() and write_block() */ BOOL fdc_rw(int block,BYTE *blockbuff,BOOL read) { int head,track,sector,tries,i; /* convert logical address into physical address */ fd_block2hts(block,&head,&track,§or); // printk("block %d = %d:%02d:%02d\n",block,head,track,sector); /* spin up the disk */ fd_motoron(); if (!read && blockbuff) { /* copy data from data buffer into track buffer */ memcpy(tbaddr, blockbuff, 512); } for (tries = 0;tries < FD_MAX_TRIES;tries++) { /* check for diskchange */ if (inportb(FDC_DIR) & 0x80) { dchange = TRUE; fd_seek(1); /* clear "disk change" status */ fd_recalibrate(); fd_motoroff(); return FALSE; } /* move head to right track */ if (!fd_seek(track)) { fd_motoroff(); return FALSE; } /* program data rate (500K/s) */ outportb(FDC_CCR,0); /* send command */ if (read) { dma_xfer(2,tbaddr,512,FALSE); fd_sendbyte(CMD_READ); } else { dma_xfer(2,tbaddr,512,TRUE); fd_sendbyte(CMD_WRITE); } fd_sendbyte(head << 2); fd_sendbyte(track); fd_sendbyte(head); fd_sendbyte(sector); fd_sendbyte(2); /* 512 bytes/sector */ fd_sendbyte(geometry.spt); if (geometry.spt == DG144_SPT) fd_sendbyte(DG144_GAP3RW); /* gap 3 size for 1.44M read/write */ else fd_sendbyte(DG168_GAP3RW); /* gap 3 size for 1.68M read/write */ fd_sendbyte(0xff); /* DTL = unused */ /* wait for command completion */ /* read/write don't need "sense interrupt status" */ if (!fd_waitfdc(FALSE)) return FALSE; /* timed out! */ if ((status[0] & 0xc0) == 0) break; /* worked! outta here! */ fd_recalibrate(); /* oops, try again... */ } /* stop the motor */ // FCE2E37B fd_motoroff(); if (read && blockbuff) { /* copy data from track buffer into data buffer */ memcpy(blockbuff, tbaddr, 512); // movedata(_dos_ds,tbaddr,_my_ds(),(long)blockbuff,512); } /* printk("status bytes: "); for (i = 0;i < statsz;i++) printk("%02x ",status[i]); printk("\n");*/ return (tries != FD_MAX_TRIES); } /* this formats a track, given a certain geometry */ BOOL format_track(BYTE track,DrvGeom *g) { int i,h,r,r_id,split; BYTE tmpbuff[256]; /* check geometry */ if (g->spt != DG144_SPT && g->spt != DG168_SPT) return FALSE; /* spin up the disk */ fd_motoron(); /* program data rate (500K/s) */ outportb(FDC_CCR,0); fd_seek(track); /* seek to track */ /* precalc some constants for interleave calculation */ split = g->spt / 2; if (g->spt & 1) split++; for (h = 0;h < g->heads;h++) { /* for each head... */ /* check for diskchange */ if (inportb(FDC_DIR) & 0x80) { dchange = TRUE; fd_seek(1); /* clear "disk change" status */ fd_recalibrate(); fd_motoroff(); return FALSE; } i = 0; /* reset buffer index */ for (r = 0;r < g->spt;r++) { /* for each sector... */ /* calculate 1:2 interleave (seems optimal in my system) */ r_id = r / 2 + 1; if (r & 1) r_id += split; /* add some head skew (2 sectors should be enough) */ if (h & 1) { r_id -= 2; if (r_id < 1) r_id += g->spt; } /* add some track skew (1/2 a revolution) */ if (track & 1) { r_id -= g->spt / 2; if (r_id < 1) r_id += g->spt; } /**** interleave now calculated - sector ID is stored in r_id ****/ /* fill in sector ID's */ tmpbuff[i++] = track; tmpbuff[i++] = h; tmpbuff[i++] = r_id; tmpbuff[i++] = 2; } /* copy sector ID's to track buffer */ memcpy(tmpbuff, tbaddr, i); // movedata(_my_ds(),(long)tmpbuff,_dos_ds,tbaddr,i); /* start dma xfer */ dma_xfer(2,tbaddr,i,TRUE); /* prepare "format track" command */ fd_sendbyte(CMD_FORMAT); fd_sendbyte(h << 2); fd_sendbyte(2); fd_sendbyte(g->spt); if (g->spt == DG144_SPT) fd_sendbyte(DG144_GAP3FMT); /* gap3 size for 1.44M format */ else fd_sendbyte(DG168_GAP3FMT); /* gap3 size for 1.68M format */ fd_sendbyte(0); /* filler byte */ /* wait for command to finish */ if(!fd_waitfdc(FALSE)) return FALSE; if(status[0] & 0xc0) { fd_motoroff(); return FALSE; } } fd_motoroff(); return TRUE; }