/* This file contains a driver for a Floppy Disk Controller (FDC) using the * NEC PD765 chip. * * The driver supports the following operations (using message format m2): * * m_type DEVICE PROC_NR COUNT POSITION ADDRESS * ---------------------------------------------------------------- * | DISK_READ | device | proc nr | bytes | offset | buf ptr | * |------------+---------+---------+---------+---------+---------| * | DISK_WRITE | device | proc nr | bytes | offset | buf ptr | * ---------------------------------------------------------------- * |SCATTERED_IO| device | proc nr | requests| | iov ptr | * ---------------------------------------------------------------- * * The file contains one entry point: * * floppy_task: main entry when system is brought up * * Changes: * 27 Oct. 1986 by Jakob Schripsema: fdc_results fixed for 8 MHz * 28 Nov. 1986 by Peter Kay: better resetting for 386 * 06 Jan. 1988 by Al Crew: allow 1.44 MB diskettes * 1989 by Bruce Evans: i/o vector to keep up with 1-1 interleave * 1990 by Bruce Evans: formatting */ #include "kernel.h" #include <minix/callnr.h> #include <minix/com.h> #include <minix/diskparm.h> /* I/O Ports used by floppy disk task. */ #define DOR 0x3F2 /* motor drive control bits */ #define FDC_STATUS 0x3F4 /* floppy disk controller status register */ #define FDC_DATA 0x3F5 /* floppy disk controller data register */ #define FDC_RATE 0x3F7 /* transfer rate register */ #define DMA_ADDR 0x004 /* port for low 16 bits of DMA address */ #define DMA_TOP 0x081 /* port for top 4 bits of 20-bit DMA addr */ #define DMA_COUNT 0x005 /* port for DMA count (count = bytes - 1) */ #define DMA_M2 0x00C /* DMA status port */ #define DMA_M1 0x00B /* DMA status port */ #define DMA_INIT 0x00A /* DMA init port */ #define DMA_RESET_VAL 0x06 /* Status registers returned as result of operation. */ #define ST0 0x00 /* status register 0 */ #define ST1 0x01 /* status register 1 */ #define ST2 0x02 /* status register 2 */ #define ST3 0x00 /* status register 3 (return by DRIVE_SENSE) */ #define ST_CYL 0x03 /* slot where controller reports cylinder */ #define ST_HEAD 0x04 /* slot where controller reports head */ #define ST_SEC 0x05 /* slot where controller reports sector */ #define ST_PCN 0x01 /* slot where controller reports present cyl */ /* Fields within the I/O ports. */ /* Main status register. */ #define CTL_BUSY 0x10 /* bit is set when read or write in progress */ #define DIRECTION 0x40 /* bit is set when reading data reg is valid */ #define MASTER 0x80 /* bit is set when data reg can be accessed */ /* Digital output port (DOR). */ #define MOTOR_MASK 0xF0 /* these bits control the motors in DOR */ #define ENABLE_INT 0x0C /* used for setting DOR port */ /* ST0. */ #define ST0_BITS 0xF8 /* check top 5 bits of seek status */ #define TRANS_ST0 0x00 /* top 5 bits of ST0 for READ/WRITE */ #define SEEK_ST0 0x20 /* top 5 bits of ST0 for SEEK */ /* ST1. */ #define BAD_SECTOR 0x05 /* if these bits are set in ST1, recalibrate */ #define WRITE_PROTECT 0x02 /* bit is set if diskette is write protected */ /* ST2. */ #define BAD_CYL 0x1F /* if any of these bits are set, recalibrate */ /* ST3 (not used). */ #define ST3_FAULT 0x80 /* if this bit is set, drive is sick */ #define ST3_WR_PROTECT 0x40 /* set when diskette is write protected */ #define ST3_READY 0x20 /* set when drive is ready */ /* Floppy disk controller command bytes. */ #define FDC_SEEK 0x0F /* command the drive to seek */ #define FDC_READ 0xE6 /* command the drive to read */ #define FDC_WRITE 0xC5 /* command the drive to write */ #define FDC_SENSE 0x08 /* command the controller to tell its status */ #define FDC_RECALIBRATE 0x07 /* command the drive to go to cyl 0 */ #define FDC_SPECIFY 0x03 /* command the drive to accept params */ #define FDC_READ_ID 0x4A /* command the drive to read sector identity */ #define FDC_FORMAT 0x4D /* command the drive to format a track */ /* DMA channel commands. */ #define DMA_READ 0x46 /* DMA read opcode */ #define DMA_WRITE 0x4A /* DMA write opcode */ /* Parameters for the disk drive. */ #define SECTOR_SIZE 512 /* physical sector size in bytes */ #define HC_SIZE 2880 /* # sectors on largest legal disk (1.44MB) */ #define NR_HEADS 0x02 /* two heads (i.e., two tracks/cylinder) */ #define DTL 0xFF /* determines data length (sector size) */ #define SPEC2 0x02 /* second parameter to SPECIFY */ #define MOTOR_OFF 3*HZ /* how long to wait before stopping motor */ /* Error codes */ #define ERR_SEEK -1 /* bad seek */ #define ERR_TRANSFER -2 /* bad transfer */ #define ERR_STATUS -3 /* something wrong when getting status */ #define ERR_RECALIBRATE -4 /* recalibrate didn't work properly */ #define ERR_WR_PROTECT -5 /* diskette is write protected */ #define ERR_DRIVE -6 /* something wrong with a drive */ #define ERR_READ_ID -7 /* bad read id */ /* Encoding of drive type in minor device number. */ #define DEV_TYPE_BITS 0x7C /* drive type + 1, if nonzero */ #define DEV_TYPE_SHIFT 2 /* right shift to normalize type bits */ #define FORMAT_DEV_BIT 0x80 /* bit in minor to turn write into format */ /* Miscellaneous. */ #define MOTOR_RUNNING 0xFF /* message type for clock interrupt */ #define MAX_ERRORS 3 /* how often to try rd/wt before quitting */ #define MAX_RESULTS 7 /* max number of bytes controller returns */ #define NR_DRIVES 2 /* maximum number of drives */ #define DIVISOR 128 /* used for sector size encoding */ #define MAX_FDC_RETRY 100 /* max # times to try to output to FDC */ #define NT 7 /* number of diskette/drive combinations */ #define AUTOMATIC 0x3F /* bit map allowing both 3.5 and 5.25 disks */ /* except for drive type 6, because that is */ /* hard to distinguish from drive type 1 */ #define THREE_INCH 0x48 /* bit map allowing only 3.5 inch diskettes */ #define FIVE_INCH 0x37 /* bit map allowing only 5.25 inch diskettes */ /* Variables. */ PRIVATE struct floppy { /* main drive struct, one entry per drive */ int fl_opcode; /* FDC_READ, FDC_WRITE or FDC_FORMAT */ int fl_curcyl; /* current cylinder */ int fl_procnr; /* which proc wanted this operation? */ int fl_drive; /* drive number addressed */ int fl_cylinder; /* cylinder number addressed */ int fl_sector; /* sector addressed */ int fl_head; /* head number addressed */ int fl_count; /* byte count */ vir_bytes fl_address; /* user virtual address */ char fl_results[MAX_RESULTS]; /* the controller can give lots of output */ char fl_calibration; /* CALIBRATED or UNCALIBRATED */ char fl_density; /* 0 = 360K/360K; 1 = 360K/1.2M; etc. */ char fl_auto_type; /* nonzero to allow search for working type */ struct disk_parameter_s fl_param; /* parameters for format */ } floppy[NR_DRIVES]; #define UNCALIBRATED 0 /* drive needs to be calibrated at next use */ #define CALIBRATED 1 /* no calibration needed */ PRIVATE int motor_status; /* current motor status is in 4 high bits */ PRIVATE int motor_goal; /* desired motor status is in 4 high bits */ PRIVATE int prev_motor; /* which motor was started last */ PRIVATE int need_reset; /* set to 1 when controller must be reset */ PRIVATE int d; /* diskette/drive combination */ PRIVATE int current_spec1; /* latest spec1 sent to the controller */ PRIVATE message mess; /* message buffer for in and out */ PRIVATE char len[] = {-1,0,1,-1,2,-1,-1,3,-1,-1,-1,-1,-1,-1,-1,4}; PRIVATE char base_sector = 1; /* physical sectors form range starting here */ /* Seven combinations of diskette/drive are supported. * * # Drive diskette Sectors Tracks Rotation Data-rate Comment * 0 360K 360K 9 40 300 RPM 250 kbps Standard PC DSDD * 1 1.2M 1.2M 15 80 360 RPM 500 kbps AT disk in AT drive * 2 720K 360K 9 40 300 RPM 250 kbps Quad density PC * 3 720K 720K 9 80 300 RPM 250 kbps Toshiba, et al. * 4 1.2M 360K 9 40 360 RPM 300 kbps PC disk in AT drive * 5 1.2M 720K 9 80 360 RPM 300 kbps Toshiba in AT drive * 6 1.44M 1.44M 18 80 300 RPM 500 kbps PS/2, et al. * * In addition, 720K diskettes can be read in 1.44MB drives, but that does * not need a different set of parameters. This combination uses * * X 1.44M 720K 9 80 300 RPM 250 kbps PS/2, et al. */ PRIVATE int gap[NT] = {0x2A, 0x1B, 0x2A, 0x2A, 0x23, 0x23, 0x1B}; /* gap size */ PRIVATE int rate[NT] = {0x02, 0x00, 0x02, 0x02, 0x01, 0x01, 0x00}; /* 2=250,1=300,0=500 kbps*/ PRIVATE int nr_sectors[NT] = {9, 15, 9, 9, 9, 9, 18}; /* sectors/track */ PRIVATE int nr_blocks[NT] = {720, 2400, 720, 1440, 720, 1440, 2880}; /* sectors/diskette*/ PRIVATE int steps_per_cyl[NT] = {1, 1, 2, 1, 2, 1, 1}; /* 2 = dbl step */ PRIVATE int mtr_setup[NT] = {1*HZ/4,3*HZ/4,1*HZ/4,4*HZ/4,3*HZ/4,3*HZ/4,4*HZ/4}; /* in ticks */ PRIVATE char spec1[NT] = {0xDF, 0xDF, 0xDF, 0xDF, 0xDF, 0xDF, 0xAF}; /* step rate, etc. */ /* This driver hunts around for the proper density by simply trying them all * until it finds one that works. By defining DEFAULT_CLASS, one can reduce * the searching to only 5.25 inch or only 3.5 inch types. The array * drive_class contains a bit map for each drive, telling which of the NT * combinations defined above should be tried. */ PRIVATE char drive_class[NR_DRIVES] = { #if (DEFAULT_CLASS == 3) THREE_INCH, THREE_INCH /* drive 0 = 3.5 inch, drive 1 also */ #endif #if (DEFAULT_CLASS == 5) FIVE_INCH, FIVE_INCH /* drive 0 = 5.25 inch, drive 1 also */ #endif #if (DEFAULT_CLASS != 3 && DEFAULT_CLASS != 5) AUTOMATIC, AUTOMATIC /* both drives can handle both types */ #endif }; FORWARD void clock_mess(); FORWARD void dma_setup(); FORWARD int do1_rdwt(); FORWARD int do_rdwt(); FORWARD int f_do_vrdwt(); FORWARD void f_reset(); FORWARD void fdc_out(); FORWARD int fdc_results(); FORWARD int read_id(); FORWARD int recalibrate(); FORWARD int seek(); FORWARD void send_mess(); FORWARD void start_motor(); FORWARD void stop_motor(); FORWARD int transfer(); /*===========================================================================* * floppy_task * *===========================================================================*/ PUBLIC void floppy_task() { /* Main program of the floppy disk driver task. */ int r, caller, proc_nr; cim_floppy(); /* ready for floppy interrupts */ /* Here is the main loop of the disk task. It waits for a message, carries * it out, and sends a reply. */ while (TRUE) { /* First wait for a request to read or write a disk block. */ receive(ANY, &mess); /* get a request to do some work */ if (mess.m_source < 0) panic("disk task got message from ", mess.m_source); /* Ignore any alarm to turn motor off, now there is work to do. */ motor_goal = motor_status; caller = mess.m_source; proc_nr = mess.PROC_NR; /* Now carry out the work. */ switch(mess.m_type) { case DISK_READ: case DISK_WRITE: r = do_rdwt(&mess, FALSE); break; case SCATTERED_IO: r = f_do_vrdwt(&mess); break; default: r = EINVAL; break; } /* Start watch_dog timer to turn all motors off in a few seconds. */ motor_goal = ENABLE_INT; clock_mess(MOTOR_OFF, stop_motor); /* Finally, prepare and send the reply message. */ mess.m_type = TASK_REPLY; mess.REP_PROC_NR = proc_nr; mess.REP_STATUS = r; /* # of bytes transferred or error code */ send(caller, &mess); /* send reply to caller */ } } /*===========================================================================* * do_rdwt * *===========================================================================*/ PRIVATE int do_rdwt(m_ptr, dont_retry) message *m_ptr; /* pointer to read or write message */ int dont_retry; /* nonzero to skip retry after error */ { /* Do a single read or write request. */ register struct floppy *fp; int r, sectors, drive, errors; off_t block; unsigned dtype; phys_bytes param_phys; phys_bytes user_param_phys; /* Decode the message parameters. */ drive = m_ptr->DEVICE & ~(DEV_TYPE_BITS | FORMAT_DEV_BIT); if (drive < 0 || drive >= NR_DRIVES) return(EIO); fp = &floppy[drive]; /* 'fp' points to entry for this drive */ fp->fl_drive = drive; /* save drive number explicitly */ fp->fl_opcode = (m_ptr->m_type == DISK_READ ? FDC_READ : FDC_WRITE); if (m_ptr->DEVICE & FORMAT_DEV_BIT) { if (fp->fl_opcode == FDC_READ) return(EIO); fp->fl_opcode = FDC_FORMAT; param_phys = umap(proc_ptr, D, (vir_bytes) &fp->fl_param, (vir_bytes) sizeof fp->fl_param); user_param_phys = numap(m_ptr->PROC_NR, (vir_bytes) (m_ptr->ADDRESS + BLOCK_SIZE / 2), (vir_bytes) sizeof fp->fl_param); phys_copy(user_param_phys, param_phys,(phys_bytes)sizeof fp->fl_param); /* Check that the number of sectors in the data is reasonable, to * avoid division by 0. Leave checking of other data to the FDC. */ if (fp->fl_param.sectors_per_cylinder == 0) return(EIO); } dtype = m_ptr->DEVICE & DEV_TYPE_BITS; if (dtype != 0) { dtype = (dtype >> DEV_TYPE_SHIFT) - 1; if (dtype >= NT) return(EIO); fp->fl_density = dtype; fp->fl_auto_type = FALSE; } else fp->fl_auto_type = TRUE; if (m_ptr->POSITION % BLOCK_SIZE != 0) return(EINVAL); block = m_ptr->POSITION/SECTOR_SIZE; if (block >= HC_SIZE) return(0); /* sector is beyond end of all disks */ d = fp->fl_density; /* diskette/drive combination */ if (fp->fl_auto_type) { /* Check bit map to skip illegal densities. */ while ((drive_class[drive] & (1 << d)) == 0) d = (d + 1) % NT; } /* Store the message parameters in the fp->fl array. */ fp->fl_density=d; sectors = (m_ptr->DEVICE & FORMAT_DEV_BIT ? fp->fl_param.sectors_per_cylinder : nr_sectors[d]); fp->fl_cylinder = (int) (block / (NR_HEADS * sectors)); fp->fl_sector = base_sector + (int) (block % sectors); fp->fl_head = (int) (block % (NR_HEADS * sectors)) / sectors; fp->fl_count = m_ptr->COUNT; fp->fl_address = (vir_bytes) m_ptr->ADDRESS; fp->fl_procnr = m_ptr->PROC_NR; if (fp->fl_count != BLOCK_SIZE) return(EINVAL); errors = 0; /* This loop allows a failed operation to be repeated. It is really a * nested loop allowing MAX_ERRORS errors for each of NT drive types. */ while (errors <= MAX_ERRORS * NT) { if (++errors % MAX_ERRORS == 0) { #if 0 /* this works well except for programs like mkfs which seek to end 1st*/ /* A lot of errors probably means that we are trying the * wrong drive type. Try another one if the cylinder is <= 1. * Otherwise, assume the disk is unchanged and give up. */ if (fp->fl_cylinder > 1) return(block >= nr_blocks[d] ? 0 : EIO); #else /* A lot of errors probably means that we are trying the * wrong drive type. Try another one. */ #endif if (!fp->fl_auto_type) return(EIO); d++; /* Check bit map to skip illegal densities. */ while ((drive_class[drive] & (1 << d)) == 0) d = (d + 1) % NT; fp->fl_density = d; sectors = nr_sectors[d]; fp->fl_cylinder = (int) (block / (NR_HEADS * sectors)); fp->fl_sector = base_sector + (int) (block % sectors); fp->fl_head = (int)(block%(NR_HEADS*sectors)) / sectors; need_reset = 1; } if (block >= nr_blocks[d]) continue; /* First check to see if a reset is needed. */ if (need_reset) f_reset(); /* Set the stepping rate */ if (current_spec1 != spec1[d]) { fdc_out(FDC_SPECIFY); fdc_out(current_spec1=spec1[d]); fdc_out(SPEC2); } /* Set the data rate */ if (pc_at) out_byte(FDC_RATE, rate[d]); /* Now set up the DMA chip. */ dma_setup(fp); /* See if motor is running; if not, turn it on and wait */ start_motor(fp); /* If we are going to a new cylinder, perform a seek. */ r = seek(fp); /* Perform the transfer. */ if (r == OK) r = transfer(fp); if (r == OK) break; /* if successful, exit loop */ if (dont_retry) break; /* retries not wanted */ if (r == ERR_WR_PROTECT) break; /* retries won't help */ } return(r == OK ? fp->fl_count : EIO); } /*===========================================================================* * dma_setup * *===========================================================================*/ PRIVATE void dma_setup(fp) struct floppy *fp; /* pointer to the drive struct */ { /* The IBM PC can perform DMA operations by using the DMA chip. To use it, * the DMA (Direct Memory Access) chip is loaded with the 20-bit memory address * to be read from or written to, the byte count minus 1, and a read or write * opcode. This routine sets up the DMA chip. Note that the chip is not * capable of doing a DMA across a 64K boundary (e.g., you can't read a * 512-byte block starting at physical address 65520). */ int mode, low_addr, high_addr, top_addr, low_ct, high_ct, top_end; vir_bytes vir, ct; phys_bytes user_phys; mode = (fp->fl_opcode == FDC_READ ? DMA_READ : DMA_WRITE); vir = (vir_bytes) fp->fl_address; ct = (vir_bytes) fp->fl_count; user_phys = numap(fp->fl_procnr, vir, ct); low_addr = (int) (user_phys >> 0) & BYTE; high_addr = (int) (user_phys >> 8) & BYTE; top_addr = (int) (user_phys >> 16) & BYTE; low_ct = (int) ( (ct - 1) >> 0) & BYTE; high_ct = (int) ( (ct - 1) >> 8) & BYTE; /* Check to see if the transfer will require the DMA address counter to * go from one 64K segment to another. If so, do not even start it, since * the hardware does not carry from bit 15 to bit 16 of the DMA address. * Also check for bad buffer address. These errors mean FS contains a bug. */ if (user_phys == 0) panic("FS gave floppy disk driver bad addr", (int) vir); top_end = (int) (((user_phys + ct - 1) >> 16) & BYTE); if (top_end != top_addr)panic("Trying to DMA across 64K boundary", top_addr); /* Now set up the DMA registers. */ out_byte(DMA_INIT, DMA_RESET_VAL); /* reset the dma controller */ out_byte(DMA_M2, mode); /* set the DMA mode */ out_byte(DMA_M1, mode); /* set it again */ out_byte(DMA_ADDR, low_addr); /* output low-order 8 bits */ out_byte(DMA_ADDR, high_addr);/* output next 8 bits */ out_byte(DMA_TOP, top_addr); /* output highest 4 bits */ out_byte(DMA_COUNT, low_ct); /* output low 8 bits of count - 1 */ out_byte(DMA_COUNT, high_ct); /* output high 8 bits of count - 1 */ out_byte(DMA_INIT, 2); /* initialize DMA */ } /*===========================================================================* * start_motor * *===========================================================================*/ PRIVATE void start_motor(fp) struct floppy *fp; /* pointer to the drive struct */ { /* Control of the floppy disk motors is a big pain. If a motor is off, you * have to turn it on first, which takes 1/2 second. You can't leave it on * all the time, since that would wear out the diskette. However, if you turn * the motor off after each operation, the system performance will be awful. * The compromise used here is to leave it on for a few seconds after each * operation. If a new operation is started in that interval, it need not be * turned on again. If no new operation is started, a timer goes off and the * motor is turned off. I/O port DOR has bits to control each of 4 drives. * The timer cannot go off while we are changing with the bits, since the * clock task cannot run while another (this) task is active, so there is no * need to lock(). */ int motor_bit, running; motor_bit = 1 << (fp->fl_drive + 4); /* bit mask for this drive */ running = motor_status & motor_bit; /* nonzero if this motor is running */ motor_goal = motor_bit | ENABLE_INT | fp->fl_drive; if (motor_status & prev_motor) motor_goal |= prev_motor; out_byte(DOR, motor_goal); motor_status = motor_goal; prev_motor = motor_bit; /* record motor started for next time */ /* If the motor was already running, we don't have to wait for it. */ if (running) return; /* motor was already running */ clock_mess(mtr_setup[d], send_mess); /* motor was not running */ receive(CLOCK, &mess); /* wait for clock interrupt */ } /*===========================================================================* * stop_motor * *===========================================================================*/ PRIVATE void stop_motor() { /* This routine is called by the clock interrupt after several seconds have * elapsed with no floppy disk activity. It checks to see if any drives are * supposed to be turned off, and if so, turns them off. */ if ( (motor_goal & MOTOR_MASK) != (motor_status & MOTOR_MASK) ) { out_byte(DOR, motor_goal); motor_status = motor_goal; } } /*===========================================================================* * seek * *===========================================================================*/ PRIVATE int seek(fp) struct floppy *fp; /* pointer to the drive struct */ { /* Issue a SEEK command on the indicated drive unless the arm is already * positioned on the correct cylinder. */ int r; /* Are we already on the correct cylinder? */ if (fp->fl_calibration == UNCALIBRATED) if (recalibrate(fp) != OK) return(ERR_SEEK); if (fp->fl_curcyl == fp->fl_cylinder) return(OK); /* No. Wrong cylinder. Issue a SEEK and wait for interrupt. */ fdc_out(FDC_SEEK); /* start issuing the SEEK command */ fdc_out( (fp->fl_head << 2) | fp->fl_drive); fdc_out(fp->fl_cylinder * steps_per_cyl[d]); if (need_reset) return(ERR_SEEK); /* if controller is sick, abort seek */ receive(HARDWARE, &mess); /* Interrupt has been received. Check drive status. */ fdc_out(FDC_SENSE); /* probe FDC to make it return status */ r = fdc_results(fp); /* get controller status bytes */ if ( (fp->fl_results[ST0] & ST0_BITS) != SEEK_ST0) r = ERR_SEEK; if (fp->fl_results[ST1] != fp->fl_cylinder * steps_per_cyl[d]) r = ERR_SEEK; if (r != OK) if (recalibrate(fp) != OK) return(ERR_SEEK); fp->fl_curcyl = (r == OK ? fp->fl_cylinder : -1); if (r == OK && ((d == 6) || (d == 3))) {/* give head time to settle on 3.5 */ clock_mess(2, send_mess); receive(CLOCK, &mess); } return(r); } /*===========================================================================* * transfer * *===========================================================================*/ PRIVATE int transfer(fp) register struct floppy *fp; /* pointer to the drive struct */ { /* The drive is now on the proper cylinder. Read, write or format 1 block. */ int r, s; /* Never attempt a transfer if the drive is uncalibrated or motor is off. */ if (fp->fl_calibration == UNCALIBRATED) return(ERR_TRANSFER); if ( ( (motor_status>>(fp->fl_drive+4)) & 1) == 0) return(ERR_TRANSFER); /* The command is issued by outputting 9 bytes to the controller chip. */ fdc_out(fp->fl_opcode); /* issue the read, write or format command */ fdc_out( (fp->fl_head << 2) | fp->fl_drive); if (fp->fl_opcode == FDC_FORMAT) { fdc_out(fp->fl_param.sector_size_code); fdc_out(fp->fl_param.sectors_per_cylinder); fdc_out(fp->fl_param.gap_length_for_format); fdc_out(fp->fl_param.fill_byte_for_format); } else { fdc_out(fp->fl_cylinder); fdc_out(fp->fl_head); fdc_out(fp->fl_sector); fdc_out( (int) len[SECTOR_SIZE/DIVISOR]); /* sector size code */ fdc_out(nr_sectors[d]); fdc_out(gap[d]); /* sector gap */ fdc_out(DTL); /* data length */ } /* Block, waiting for disk interrupt. */ if (need_reset) return(ERR_TRANSFER); /* if controller is sick, abort op */ receive(HARDWARE, &mess); /* Get controller status and check for errors. */ r = fdc_results(fp); if (r != OK) return(r); if ( (fp->fl_results[ST1] & BAD_SECTOR) || (fp->fl_results[ST2] & BAD_CYL) ) fp->fl_calibration = UNCALIBRATED; if (fp->fl_results[ST1] & WRITE_PROTECT) { printf("Diskette in drive %d is write protected.\n", fp->fl_drive); return(ERR_WR_PROTECT); } if ((fp->fl_results[ST0] & ST0_BITS) != TRANS_ST0) return(ERR_TRANSFER); if (fp->fl_results[ST1] | fp->fl_results[ST2]) return(ERR_TRANSFER); if (fp->fl_opcode == FDC_FORMAT) return(OK); /* Compare actual numbers of sectors transferred with expected number. */ s = (fp->fl_results[ST_CYL] - fp->fl_cylinder) * NR_HEADS * nr_sectors[d]; s += (fp->fl_results[ST_HEAD] - fp->fl_head) * nr_sectors[d]; s += (fp->fl_results[ST_SEC] - fp->fl_sector); if (s * SECTOR_SIZE != fp->fl_count) return(ERR_TRANSFER); return(OK); } /*==========================================================================* * fdc_results * *==========================================================================*/ PRIVATE int fdc_results(fp) struct floppy *fp; /* pointer to the drive struct */ { /* Extract results from the controller after an operation, then allow floppy * interrupts again. */ int result_nr; register int retries; register int status; /* Extract bytes from FDC until it says it has no more. The loop is * really an outer loop on result_nr and an inner loop on status. */ result_nr = 0; retries = MAX_FDC_RETRY; while (TRUE) { /* Reading one byte is almost a mirror of fdc_out() - the DIRECTION * bit must be set instead of clear, but the CTL_BUSY bit destroys * the perfection of the mirror. */ status = in_byte(FDC_STATUS) & (MASTER | DIRECTION | CTL_BUSY); if (status == (MASTER | DIRECTION | CTL_BUSY)) { if (result_nr >= MAX_RESULTS) break; /* too many results */ fp->fl_results[result_nr++] = in_byte(FDC_DATA); retries = MAX_FDC_RETRY; continue; } if (status == MASTER) { /* all read */ cim_floppy(); return(OK); /* only good exit */ } if (--retries == 0) break; /* time out */ } need_reset = TRUE; /* controller chip must be reset */ cim_floppy(); return(ERR_STATUS); } /*===========================================================================* * fdc_out * *===========================================================================*/ PRIVATE void fdc_out(val) register int val; /* write this byte to floppy disk controller */ { /* Output a byte to the controller. This is not entirely trivial, since you * can only write to it when it is listening, and it decides when to listen. * If the controller refuses to listen, the FDC chip is given a hard reset. */ register int retries; if (need_reset) return; /* if controller is not listening, return */ /* It may take several tries to get the FDC to accept a command. */ retries = MAX_FDC_RETRY; while ( (in_byte(FDC_STATUS) & (MASTER | DIRECTION)) != (MASTER | 0) ) if (--retries == 0) { /* Controller is not listening. Hit it over the head with a hammer. */ need_reset = TRUE; return; } out_byte(FDC_DATA, val); } /*===========================================================================* * recalibrate * *===========================================================================*/ PRIVATE int recalibrate(fp) register struct floppy *fp; /* pointer tot he drive struct */ { /* The floppy disk controller has no way of determining its absolute arm * position (cylinder). Instead, it steps the arm a cylinder at a time and * keeps track of where it thinks it is (in software). However, after a * SEEK, the hardware reads information from the diskette telling where the * arm actually is. If the arm is in the wrong place, a recalibration is done, * which forces the arm to cylinder 0. This way the controller can get back * into sync with reality. */ int r; /* Issue the RECALIBRATE command and wait for the interrupt. */ start_motor(fp); /* can't recalibrate with motor off */ fdc_out(FDC_RECALIBRATE); /* tell drive to recalibrate itself */ fdc_out(fp->fl_drive); /* specify drive */ if (need_reset) return(ERR_SEEK); /* don't wait if controller is sick */ receive(HARDWARE, &mess); /* wait for interrupt message */ /* Determine if the recalibration succeeded. */ fdc_out(FDC_SENSE); /* issue SENSE command to request results */ r = fdc_results(fp); /* get results of the FDC_RECALIBRATE command */ fp->fl_curcyl = -1; /* force a SEEK next time */ if (r != OK || /* controller would not respond */ (fp->fl_results[ST0]&ST0_BITS) != SEEK_ST0 || fp->fl_results[ST_PCN] !=0){ /* Recalibration failed. FDC must be reset. */ need_reset = TRUE; fp->fl_calibration = UNCALIBRATED; return(ERR_RECALIBRATE); } else { /* Recalibration succeeded. */ fp->fl_calibration = CALIBRATED; if (ps||((d == 6) || (d == 3))) {/* give head time to settle on 3.5 */ clock_mess(2, send_mess); receive(CLOCK, &mess); } #if RECORD_FLOPPY_SKEW /* This might be used to determine nr_sectors. This is not quite the right * place for it may be called for a format operation. Then an error is * normal, but kills the operation. */ { static char skew[32]; for (r = 0; r < sizeof skew / sizeof skew[0]; ++r) { read_id(fp); skew[r] = fp->fl_results[5]; } } #endif return(OK); } } /*===========================================================================* * reset * *===========================================================================*/ PRIVATE void f_reset() { /* Issue a reset to the controller. This is done after any catastrophe, * like the controller refusing to respond. */ int i; /* Disable interrupts and strobe reset bit low. */ need_reset = FALSE; /* It is not clear why the next lock is needed. Writing 0 to DOR causes * interrupt, while the PC documentation says turning bit 8 off disables * interrupts. Without the lock: * 1) the interrupt handler sets the floppy mask bit in the 8259. * 2) writing ENABLE_INT to DOR causes the FDC to assert the interrupt * line again, but the mask stops the cpu being interrupted. * 3) the sense interrupt clears the interrupt (not clear which one). * and for some reason the reset does not work. */ lock(); motor_status = 0; motor_goal = 0; out_byte(DOR, 0); /* strobe reset bit low */ out_byte(DOR, ENABLE_INT); /* strobe it high again */ unlock(); receive(HARDWARE, &mess); /* collect the RESET interrupt */ fdc_out(FDC_SENSE); /* probe FDC to make it return status */ fdc_results(&floppy[0]); /* flush controller using scratch structure */ for (i=0; i<NR_DRIVES; i++) /* Clear each drive. */ floppy[i].fl_calibration = UNCALIBRATED; /* Tell FDC drive parameters. */ fdc_out(FDC_SPECIFY); /* specify some timing parameters */ fdc_out(current_spec1=spec1[d]); /* step-rate and head-unload-time */ fdc_out(SPEC2); /* head-load-time and non-dma */ } /*===========================================================================* * clock_mess * *===========================================================================*/ PRIVATE void clock_mess(ticks, func) int ticks; /* how many clock ticks to wait */ void (*func)(); /* function to call upon time out */ { /* Send the clock task a message. */ mess.m_type = SET_ALARM; mess.CLOCK_PROC_NR = FLOPPY; mess.DELTA_TICKS = (long) ticks; mess.FUNC_TO_CALL = func; sendrec(CLOCK, &mess); } /*===========================================================================* * send_mess * *===========================================================================*/ PRIVATE void send_mess() { /* This routine is called when the clock task has timed out on motor startup.*/ send(FLOPPY, &mess); } /*==========================================================================* * f_do_vrdwt * *==========================================================================*/ PRIVATE int f_do_vrdwt(m_ptr) message *m_ptr; /* pointer to read or write message */ { /* Carry out a scattered read or write request. */ int base; off_t block; int cylinder; int dist; struct floppy *fp; static struct iorequest_s iovec[NR_BUFS]; phys_bytes iovec_phys; int last_plus1; int limit; int mindist; unsigned nr_requests; int nsector; int request; phys_bytes user_iovec_phys; message vmessage; vmessage = *m_ptr; /* global message will be clobbered */ m_ptr = &vmessage; /* Fetch i/o vector from caller's space. */ nr_requests = m_ptr->COUNT; if (nr_requests > sizeof iovec / sizeof iovec[0]) panic("FS gave floppy driver too big an i/o vector", nr_requests); iovec_phys = umap(proc_ptr, D, (vir_bytes) iovec, (vir_bytes) sizeof iovec); user_iovec_phys = numap(m_ptr->PROC_NR, (vir_bytes) m_ptr->ADDRESS, (vir_bytes) (nr_requests * sizeof iovec[0])); if (user_iovec_phys == 0) panic("FS gave floppy driver bad i/o vector", (int) m_ptr->ADDRESS); phys_copy(user_iovec_phys, iovec_phys, (phys_bytes) nr_requests * sizeof iovec[0]); /* Determine the number of sectors and the last sector. It only hurts * efficiency if these are wrong after a disk change. */ fp = &floppy[(m_ptr->DEVICE & ~(DEV_TYPE_BITS|FORMAT_DEV_BIT)) % NR_DRIVES]; nsector = nr_sectors[fp->fl_density]; read_id(fp); /* should reorganize and seek() before this */ fp->fl_sector = fp->fl_results[5]; /* last sector accessed */ for (base = 0; base < nr_requests; base = limit) { /* Handle all requests on the same cylinder as the base request. */ block = iovec[base].io_position / SECTOR_SIZE; cylinder = (int) (block / (NR_HEADS * nsector)); /* Find the request for the closest sector on the base cylinder. */ for (request = limit = base, mindist = 9999; limit < nr_requests; ++limit) { block = iovec[limit].io_position / SECTOR_SIZE; if (cylinder != (int) (block / (NR_HEADS * nsector))) break; dist = base_sector + (int) (block % nsector) - fp->fl_sector; if (dist < 0) dist += nsector; if (dist > 0 && dist < mindist) { /* Closer. Ignore dist == 0 which is furthest! */ request = limit; mindist = dist; } } /* Do the actual i/o in the good order just found. */ last_plus1 = (request == base) ? limit : request; do { if (request >= limit) request = base; if (do1_rdwt(&iovec[request], m_ptr) != OK) { /* Abort both loops, to avoid reading-ahead of * bad blocks, especially off the end of the disk. */ request = last_plus1 - 1; limit = nr_requests; } } while (++request != last_plus1); /* Advance last sector from the base sector of the last block. * Advance another sector after that to allow time for seek, except * for the last sectors on a cylinder, for which the gap at the end * gives enough time. */ fp->fl_sector += BLOCK_SIZE / SECTOR_SIZE; while (fp->fl_sector >= nsector + base_sector) fp->fl_sector -= nsector; if (fp->fl_sector == base_sector) fp->fl_sector = base_sector + nsector - 1; } /* Return most results in caller's i/o vector. */ phys_copy(iovec_phys, user_iovec_phys, (phys_bytes) nr_requests * sizeof iovec[0]); return(OK); } /*==========================================================================* * do1_rdwt * *==========================================================================*/ PRIVATE int do1_rdwt(iop, m_ptr) register struct iorequest_s *iop; register message *m_ptr; { /* Convert from scattered i/o entry to partially built message and do i/o. * There are too many conversions, so to keep up on slow machines it will * be necessary to do more preparation at a high level, e.g., when looping * over sectors on the same cylinder, do not recompute the cylinder over * and over, and avoid all long arithmetic. */ int result; #if FLOPPY_TIMING #define MAX_TIMED_BLOCK (HC_SIZE / (BLOCK_SIZE / SECTOR_SIZE)) off_t block; static struct { unsigned short start; unsigned short finish; } fl_times[MAX_TIMED_BLOCK]; #endif m_ptr->POSITION = iop->io_position; m_ptr->ADDRESS = iop->io_buf; m_ptr->COUNT = iop->io_nbytes; m_ptr->m_type = iop->io_request & ~OPTIONAL_IO; #if FLOPPY_TIMING block = m_ptr->POSITION/BLOCK_SIZE; if (block < MAX_TIMED_BLOCK) fl_times[block].start = read_counter(); #endif result = do_rdwt(m_ptr, iop->io_request & OPTIONAL_IO); #if FLOPPY_TIMING if (block < MAX_TIMED_BLOCK) fl_times[block].finish = read_counter(); #endif if (result == 0) return(!OK); /* EOF */ if (result < 0) { iop->io_nbytes = result; if (iop->io_request & OPTIONAL_IO) return(!OK); /* abort if optional */ } else iop->io_nbytes -= result; return(OK); } /*==========================================================================* * read_id * *==========================================================================*/ PRIVATE int read_id(fp) register struct floppy *fp; /* pointer to the drive struct */ { /* Determine current cylinder and sector. */ int result; /* Never attempt a read id if the drive is uncalibrated or motor is off. */ if (fp->fl_calibration == UNCALIBRATED) return(ERR_READ_ID); if ( ( (motor_status>>(fp->fl_drive+4)) & 1) == 0) return(ERR_READ_ID); /* The command is issued by outputting 2 bytes to the controller chip. */ fdc_out(FDC_READ_ID); /* issue the read id command */ fdc_out( (fp->fl_head << 2) | fp->fl_drive); /* Block, waiting for disk interrupt. */ if (need_reset) return(ERR_READ_ID); /* if controller is sick, abort op */ receive(HARDWARE, &mess); /* Get controller status and check for errors. */ result = fdc_results(fp); if (result != OK) return(result); if ( (fp->fl_results[ST1] & BAD_SECTOR) || (fp->fl_results[ST2] & BAD_CYL) ) fp->fl_calibration = UNCALIBRATED; if ((fp->fl_results[ST0] & ST0_BITS) != TRANS_ST0) return(ERR_READ_ID); if (fp->fl_results[ST1] | fp->fl_results[ST2]) return(ERR_READ_ID); return(OK); }