/* * Copyright (c) 1994 Shantanu Goel * All Rights Reserved. * * Permission to use, copy, modify and distribute this software and its * documentation is hereby granted, provided that both the copyright * notice and this permission notice appear in all copies of the * software, derivative works or modified versions, and any portions * thereof, and that both notices appear in supporting documentation. * * THE AUTHOR ALLOWS FREE USE OF THIS SOFTWARE IN ITS "AS IS" * CONDITION. THE AUTHOR DISCLAIMS ANY LIABILITY OF ANY KIND FOR * ANY DAMAGES WHATSOEVER RESULTING FROM THE USE OF THIS SOFTWARE. */ #include #if NFD > 0 /* * Floppy disk driver. * * Supports: * 1 controller and 2 drives. * Media change and automatic media detection. * Arbitrarily sized read/write requests. * Misaligned requests * DMA above 16 Meg * * TODO: * 1) Real probe routines for controller and drives. * 2) Support for multiple controllers. The driver does * not assume a single controller since all functions * take the controller and/or device structure as an * argument, however the probe routines limit the * number of controllers and drives to 1 and 2 respectively. * 3) V_VERIFY ioctl. * 4) User defined diskette parameters. * 5) Detect Intel 82077 or compatible and use its FIFO mode. * * Shantanu Goel (goel@cs.columbia.edu) */ #include #include #include "vm_param.h" #include #include #include #include #include #include #include #include #include #include #include /* * Number of drives supported by an FDC. * The controller is actually capable of * supporting 4 drives, however, most (all?) * board implementations only support 2. */ #define NDRIVES_PER_FDC 2 #define NFDC ((NFD + NDRIVES_PER_FDC - 1) / NDRIVES_PER_FDC) #define fdunit(dev) (((int)(dev) >> 6) & 3) #define fdmedia(dev) ((int)(dev) & 3) #define b_cylin b_resid #define B_FORMAT B_MD1 #define SECSIZE 512 #define DMABSIZE (18*1024) /* size of DMA bounce buffer */ #define OP_TIMEOUT 5 /* time to wait (secs) for an operation before giving up */ #define MOTOR_TIMEOUT 5 /* time to wait (secs) before turning off an idle drive motor */ #define MAX_RETRIES 48 /* number of times to try an I/O operation */ #define SRTHUT 0xdf /* step rate/head unload time */ #define HLTND 0x02 /* head load time/dma mode */ /* * DMA controller. * * XXX: There should be a generic file. */ /* * Ports */ #define DMA2_PAGE 0x81 /* channel 2, page register */ #define DMA2_ADDR 0x04 /* channel 2, addr register */ #define DMA2_COUNT 0x05 /* channel 2, count register */ #define DMA_STATUS 0x08 /* status register */ #define DMA_COMMAND 0x08 /* command register */ #define DMA_WREQ 0x09 /* request register */ #define DMA_SINGLEMSK 0x0a /* single mask register */ #define DMA_MODE 0x0b /* mode register */ #define DMA_FLIPFLOP 0x0c /* pointer flip/flop */ #define DMA_TEMP 0x0d /* temporary register */ #define DMA_MASTERCLR 0x0d /* master clear */ #define DMA_CLRMASK 0x0e /* clear mask register */ #define DMA_ALLMASK 0x0f /* all mask register */ /* * Commands */ #define DMA_WRITE 0x46 /* write on channel 2 */ #define DMA_READ 0x4a /* read on channel 2 */ /* * Autoconfiguration stuff. */ struct bus_ctlr *fdminfo[NFDC]; struct bus_device *fddinfo[NFD]; int fdstd[] = { 0 }; int fdprobe(), fdslave(), fdintr(); void fdattach(); struct bus_driver fddriver = { fdprobe, fdslave, fdattach, 0, fdstd, "fd", fddinfo, "fdc", fdminfo }; /* * Per-controller state. */ struct fdcsoftc { int sc_flags; #define FDF_WANT 0x01 /* someone needs direct controller access */ #define FDF_RESET 0x02 /* controller needs reset */ #define FDF_LIMIT 0x04 /* limit transfer to a single sector */ #define FDF_BOUNCE 0x08 /* using bounce buffer */ int sc_state; /* transfer fsm */ caddr_t sc_addr; /* buffer address */ int sc_resid; /* amount left to transfer */ int sc_amt; /* amount currently being transferred */ int sc_op; /* operation being performed */ int sc_mode; /* DMA mode */ int sc_sn; /* sector number */ int sc_tn; /* track number */ int sc_cn; /* cylinder number */ int sc_recalerr; /* # recalibration errors */ int sc_seekerr; /* # seek errors */ int sc_ioerr; /* # i/o errors */ int sc_dor; /* copy of digital output register */ int sc_rate; /* copy of transfer rate register */ int sc_wticks; /* watchdog */ u_int sc_buf; /* buffer for transfers > 16 Meg */ u_char sc_cmd[9]; /* command buffer */ u_char sc_results[7]; /* operation results */ } fdcsoftc[NFDC]; #define sc_st0 sc_results[0] #define sc_st3 sc_results[0] #define sc_st1 sc_results[1] #define sc_pcn sc_results[1] #define sc_st2 sc_results[2] #define sc_c sc_results[3] #define sc_h sc_results[4] #define sc_r sc_results[5] #define sc_n sc_results[6] /* * Transfer states. */ #define IDLE 0 /* controller is idle */ #define RESET 1 /* reset controller */ #define RESETDONE 2 /* reset completion interrupt */ #define RECAL 3 /* recalibrate drive */ #define RECALDONE 4 /* recalibration complete interrupt */ #define SEEK 5 /* perform seek on drive */ #define SEEKDONE 6 /* seek completion interrupt */ #define TRANSFER 7 /* perform transfer on drive */ #define TRANSFERDONE 8 /* transfer completion interrupt */ /* * Per-drive state. */ struct fdsoftc { int sc_flags; #define FDF_RECAL 0x02 /* drive needs recalibration */ #define FDF_SEEK 0x04 /* force seek during auto-detection */ #define FDF_AUTO 0x08 /* performing auto-density */ #define FDF_AUTOFORCE 0x10 /* force auto-density */ #define FDF_INIT 0x20 /* drive is being initialized */ int sc_type; /* drive type */ struct fddk *sc_dk; /* diskette type */ int sc_cyl; /* current head position */ int sc_mticks; /* motor timeout */ } fdsoftc[NFD]; struct buf fdtab[NFDC]; /* controller queues */ struct buf fdutab[NFD]; /* drive queues */ /* * Floppy drive type names. */ char *fdnames[] = { "360K", "1.2 Meg", "720K", "1.44 Meg" }; #define NTYPES (sizeof(fdnames) / sizeof(fdnames[0])) /* * Floppy diskette parameters. */ struct fddk { int dk_nspu; /* sectors/unit */ int dk_nspc; /* sectors/cylinder */ int dk_ncyl; /* cylinders/unit */ int dk_nspt; /* sectors/track */ int dk_step; /* !=0 means double track steps */ int dk_gap; /* read/write gap length */ int dk_fgap; /* format gap length */ int dk_rate; /* transfer rate */ int dk_drives; /* bit mask of drives that accept diskette */ char *dk_name; /* type name */ } fddk[] = { /* * NOTE: largest density for each drive type must be first so * fdauto() tries it before any lower ones. */ { 2880, 36, 80, 18, 0, 0x1b, 0x6c, 0x00, 0x08, "1.44 Meg" }, { 2400, 30, 80, 15, 0, 0x1b, 0x54, 0x00, 0x02, "1.2 Meg" }, { 1440, 18, 80, 9, 0, 0x2a, 0x50, 0x02, 0x0c, "720K" }, { 720, 18, 40, 9, 1, 0x23, 0x50, 0x01, 0x02, "360K" }, { 720, 18, 40, 9, 0, 0x2a, 0x50, 0x02, 0x01, "360K PC" } }; #define NDKTYPES (sizeof(fddk) / sizeof(fddk[0])) /* * For compatibility with old driver. * This array is indexed by the old floppy type codes * and points to the corresponding entry for that * type in fddk[] above. */ struct fddk *fdcompat[NDKTYPES]; int fdwstart = 0; int fdstrategy(), fdformat(); char *fderrmsg(); void fdwatch(), fdminphys(), fdspinup(), wakeup(); #define FDDEBUG #ifdef FDDEBUG int fddebug = 0; #define DEBUGF(n, stmt) { if (fddebug >= (n)) stmt; } #else #define DEBUGF(n, stmt) #endif /* * Probe for a controller. */ int fdprobe(xxx, um) int xxx; struct bus_ctlr *um; { struct fdcsoftc *fdc; if (um->unit >= NFDC) { printf("fdc%d: not configured\n", um->unit); return (0); } if (um->unit > 0) /* XXX: only 1 controller */ return (0); /* * XXX: need real probe */ take_ctlr_irq(um); printf("%s%d: port 0x%x, spl %d, pic %d.\n", um->name, um->unit, um->address, um->sysdep, um->sysdep1); /* * Set up compatibility array. */ fdcompat[0] = &fddk[2]; fdcompat[1] = &fddk[0]; fdcompat[2] = &fddk[3]; fdcompat[3] = &fddk[1]; fdc = &fdcsoftc[um->unit]; fdc->sc_rate = -1; if (!fdc->sc_buf) { fdc->sc_buf = alloc_dma_mem(DMABSIZE, 64*1024); if (fdc->sc_buf == 0) panic("fd: alloc_dma_mem() failed"); } fdc->sc_dor = DOR_RSTCLR | DOR_IENABLE; outb(FD_DOR(um->address), fdc->sc_dor); return (1); } /* * Probe for a drive. */ int fdslave(ui) struct bus_device *ui; { struct fdsoftc *sc; if (ui->unit >= NFD) { printf("fd%d: not configured\n", ui->unit); return (0); } if (ui->unit > 1) /* XXX: only 2 drives */ return (0); /* * Find out from CMOS if drive exists. */ sc = &fdsoftc[ui->unit]; outb(CMOS_ADDR, 0x10); sc->sc_type = inb(CMOS_DATA); if (ui->unit == 0) sc->sc_type >>= 4; sc->sc_type &= 0x0f; return (sc->sc_type); } /* * Attach a drive to the system. */ void fdattach(ui) struct bus_device *ui; { struct fdsoftc *sc; sc = &fdsoftc[ui->unit]; if (--sc->sc_type >= NTYPES) { printf(": unknown drive type %d", sc->sc_type); ui->alive = 0; return; } printf(": %s", fdnames[sc->sc_type]); sc->sc_flags = FDF_RECAL | FDF_SEEK | FDF_AUTOFORCE; } int fdopen(dev, mode) dev_t dev; int mode; { int unit = fdunit(dev), error; struct bus_device *ui; struct fdsoftc *sc; if (unit >= NFD || (ui = fddinfo[unit]) == 0 || ui->alive == 0) return (ENXIO); /* * Start watchdog. */ if (!fdwstart) { fdwstart++; timeout(fdwatch, 0, hz); } /* * Do media detection if drive is being opened for the * first time or diskette has been changed since the last open. */ sc = &fdsoftc[unit]; if ((sc->sc_flags & FDF_AUTOFORCE) || fddskchg(ui)) { if (error = fdauto(dev)) return (error); sc->sc_flags &= ~FDF_AUTOFORCE; } return (0); } int fdclose(dev) dev_t dev; { int s, unit = fdunit(dev); struct fdsoftc *sc = &fdsoftc[unit]; /* * Wait for pending operations to complete. */ s = splbio(); while (fdutab[unit].b_active) { sc->sc_flags |= FDF_WANT; assert_wait((event_t)sc, FALSE); thread_block((void (*)())0); } splx(s); return (0); } int fdread(dev, ior) dev_t dev; io_req_t ior; { return (block_io(fdstrategy, fdminphys, ior)); } int fdwrite(dev, ior) dev_t dev; io_req_t ior; { return (block_io(fdstrategy, fdminphys, ior)); } int fdgetstat(dev, flavor, status, status_count) dev_t dev; dev_flavor_t flavor; dev_status_t status; mach_msg_type_number_t *status_count; { switch (flavor) { case DEV_GET_SIZE: { int *info; io_return_t error; struct disk_parms dp; if (error = fdgetparms(dev, &dp)) return (error); info = (int *)status; info[DEV_GET_SIZE_DEVICE_SIZE] = dp.dp_pnumsec * SECSIZE; info[DEV_GET_SIZE_RECORD_SIZE] = SECSIZE; *status_count = DEV_GET_SIZE_COUNT; return (D_SUCCESS); } case V_GETPARMS: if (*status_count < (sizeof(struct disk_parms) / sizeof(int))) return (D_INVALID_OPERATION); *status_count = sizeof(struct disk_parms) / sizeof(int); return (fdgetparms(dev, (struct disk_parms *)status)); default: return (D_INVALID_OPERATION); } } int fdsetstat(dev, flavor, status, status_count) dev_t dev; dev_flavor_t flavor; dev_status_t status; mach_msg_type_number_t status_count; { switch (flavor) { case V_SETPARMS: return (fdsetparms(dev, *(int *)status)); case V_FORMAT: return (fdformat(dev, (union io_arg *)status)); case V_VERIFY: /* * XXX: needs to be implemented */ return (D_SUCCESS); default: return (D_INVALID_OPERATION); } } int fddevinfo(dev, flavor, info) dev_t dev; int flavor; char *info; { switch (flavor) { case D_INFO_BLOCK_SIZE: *(int *)info = SECSIZE; return (D_SUCCESS); default: return (D_INVALID_OPERATION); } } /* * Allow arbitrary transfers. Standard minphys restricts * transfers to a maximum of 256K preventing us from reading * an entire diskette in a single system call. */ void fdminphys(ior) io_req_t ior; { } /* * Return current media parameters. */ int fdgetparms(dev, dp) dev_t dev; struct disk_parms *dp; { struct fddk *dk = fdsoftc[fdunit(dev)].sc_dk; dp->dp_type = DPT_FLOPPY; dp->dp_heads = 2; dp->dp_sectors = dk->dk_nspt; dp->dp_pstartsec = 0; dp->dp_cyls = dk->dk_ncyl; dp->dp_pnumsec = dk->dk_nspu; return (0); } /* * Set media parameters. */ int fdsetparms(dev, type) dev_t dev; int type; { struct fdsoftc *sc; struct fddk *dk; if (type < 0 || type >= NDKTYPES) return (EINVAL); dk = fdcompat[type]; sc = &fdsoftc[fdunit(dev)]; if ((dk->dk_drives & (1 << sc->sc_type)) == 0) return (EINVAL); sc->sc_dk = dk; return (D_SUCCESS); } /* * Format a floppy. */ int fdformat(dev, arg) dev_t dev; union io_arg *arg; { int i, j, sect, error = 0; unsigned track, num_trks; struct buf *bp; struct fddk *dk; struct format_info *fmt; dk = fdsoftc[fdunit(dev)].sc_dk; num_trks = arg->ia_fmt.num_trks; track = arg->ia_fmt.start_trk; if (num_trks == 0 || track + num_trks > (dk->dk_ncyl << 1) || arg->ia_fmt.intlv >= dk->dk_nspt) return (EINVAL); bp = (struct buf *)geteblk(SECSIZE); bp->b_dev = dev; bp->b_bcount = dk->dk_nspt * sizeof(struct format_info); bp->b_blkno = track * dk->dk_nspt; while (num_trks-- > 0) { /* * Set up format information. */ fmt = (struct format_info *)bp->b_un.b_addr; for (i = 0; i < dk->dk_nspt; i++) fmt[i].sector = 0; for (i = 0, j = 0, sect = 1; i < dk->dk_nspt; i++) { fmt[j].cyl = track >> 1; fmt[j].head = track & 1; fmt[j].sector = sect++; fmt[j].secsize = 2; if ((j += arg->ia_fmt.intlv) < dk->dk_nspt) continue; for (j -= dk->dk_nspt; j < dk->dk_nspt; j++) if (fmt[j].sector == 0) break; } bp->b_flags = B_FORMAT; fdstrategy(bp); biowait(bp); if (bp->b_flags & B_ERROR) { error = bp->b_error; break; } bp->b_blkno += dk->dk_nspt; track++; } bp->b_flags &= ~B_FORMAT; brelse(bp); return (error); } /* * Strategy routine. * Enqueue a request on drive queue. */ int fdstrategy(bp) struct buf *bp; { int unit = fdunit(bp->b_dev), s; int bn, sz, maxsz; struct buf *dp; struct bus_device *ui = fddinfo[unit]; struct fddk *dk = fdsoftc[unit].sc_dk; bn = bp->b_blkno; sz = (bp->b_bcount + SECSIZE - 1) / SECSIZE; maxsz = dk->dk_nspu; if (bn < 0 || bn + sz > maxsz) { if (bn == maxsz) { bp->b_resid = bp->b_bcount; goto done; } sz = maxsz - bn; if (sz <= 0) { bp->b_error = EINVAL; bp->b_flags |= B_ERROR; goto done; } bp->b_bcount = sz * SECSIZE; } bp->b_cylin = bn / dk->dk_nspc; dp = &fdutab[unit]; s = splbio(); disksort(dp, bp); if (!dp->b_active) { fdustart(ui); if (!fdtab[ui->mi->unit].b_active) fdstart(ui->mi); } splx(s); return; done: biodone(bp); return; } /* * Unit start routine. * Move request from drive to controller queue. */ int fdustart(ui) struct bus_device *ui; { struct buf *bp; struct buf *dp; bp = &fdutab[ui->unit]; if (bp->b_actf == 0) return; dp = &fdtab[ui->mi->unit]; if (dp->b_actf == 0) dp->b_actf = bp; else dp->b_actl->b_forw = bp; bp->b_forw = 0; dp->b_actl = bp; bp->b_active++; } /* * Start output on controller. */ int fdstart(um) struct bus_ctlr *um; { struct buf *bp; struct buf *dp; struct fdsoftc *sc; struct fdcsoftc *fdc; struct bus_device *ui; struct fddk *dk; /* * Pull a request from the controller queue. */ dp = &fdtab[um->unit]; if ((bp = dp->b_actf) == 0) return; bp = bp->b_actf; fdc = &fdcsoftc[um->unit]; ui = fddinfo[fdunit(bp->b_dev)]; sc = &fdsoftc[ui->unit]; dk = sc->sc_dk; /* * Mark controller busy. */ dp->b_active++; /* * Figure out where this request is going. */ fdc->sc_cn = bp->b_cylin; fdc->sc_sn = bp->b_blkno % dk->dk_nspc; fdc->sc_tn = fdc->sc_sn / dk->dk_nspt; fdc->sc_sn %= dk->dk_nspt; /* * Set up for multi-sector transfer. */ fdc->sc_op = ((bp->b_flags & B_FORMAT) ? CMD_FORMAT : ((bp->b_flags & B_READ) ? CMD_READ : CMD_WRITE)); fdc->sc_mode = (bp->b_flags & B_READ) ? DMA_WRITE : DMA_READ; fdc->sc_addr = bp->b_un.b_addr; fdc->sc_resid = bp->b_bcount; fdc->sc_wticks = 0; fdc->sc_recalerr = 0; fdc->sc_seekerr = 0; fdc->sc_ioerr = 0; /* * Set initial transfer state. */ if (fdc->sc_flags & FDF_RESET) fdc->sc_state = RESET; else if (sc->sc_flags & FDF_RECAL) fdc->sc_state = RECAL; else if (sc->sc_cyl != fdc->sc_cn) fdc->sc_state = SEEK; else fdc->sc_state = TRANSFER; /* * Set transfer rate. */ if (fdc->sc_rate != dk->dk_rate) { fdc->sc_rate = dk->dk_rate; outb(FD_RATE(um->address), fdc->sc_rate); } /* * Turn on drive motor. * Don't start I/O if drive is spinning up. */ if (fdmotoron(ui)) { timeout(fdspinup, (void *)um, hz / 2); return; } /* * Call transfer state routine to do the actual I/O. */ fdstate(um); } /* * Interrupt routine. */ int fdintr(ctlr) int ctlr; { int timedout; u_char results[7]; struct buf *bp; struct bus_device *ui; struct fdsoftc *sc; struct buf *dp = &fdtab[ctlr]; struct fdcsoftc *fdc = &fdcsoftc[ctlr]; struct bus_ctlr *um = fdminfo[ctlr]; if (!dp->b_active) { printf("fdc%d: stray interrupt\n", ctlr); return; } timedout = fdc->sc_wticks >= OP_TIMEOUT; fdc->sc_wticks = 0; bp = dp->b_actf->b_actf; ui = fddinfo[fdunit(bp->b_dev)]; sc = &fdsoftc[ui->unit]; /* * Operation timed out, terminate request. */ if (timedout) { fderror("timed out", ui); fdmotoroff(ui); sc->sc_flags |= FDF_RECAL; bp->b_flags |= B_ERROR; bp->b_error = ENXIO; fddone(ui, bp); return; } /* * Read results from FDC. * For transfer completion they can be read immediately. * For anything else, we must issue a Sense Interrupt * Status Command. We keep issuing this command till * FDC returns invalid command status. The Controller Busy * bit in the status register indicates completion of a * read/write/format operation. */ if (inb(FD_STATUS(um->address)) & ST_CB) { if (!fdresults(um, fdc->sc_results)) return; } else { while (1) { fdc->sc_cmd[0] = CMD_SENSEI; if (!fdcmd(um, 1)) { DEBUGF(2, printf(2, "fd%d: SENSEI failed\n")); return; } if (!fdresults(um, results)) return; if ((results[0] & ST0_IC) == 0x80) break; if ((results[0] & ST0_US) == ui->slave) { fdc->sc_results[0] = results[0]; fdc->sc_results[1] = results[1]; } } } /* * Let transfer state routine handle the rest. */ fdstate(um); } /* * Transfer finite state machine driver. */ int fdstate(um) struct bus_ctlr *um; { int unit, max, pa, s; struct buf *bp; struct fdsoftc *sc; struct bus_device *ui; struct fddk *dk; struct fdcsoftc *fdc = &fdcsoftc[um->unit]; bp = fdtab[um->unit].b_actf->b_actf; ui = fddinfo[fdunit(bp->b_dev)]; sc = &fdsoftc[ui->unit]; dk = sc->sc_dk; while (1) switch (fdc->sc_state) { case RESET: /* * Reset the controller. */ fdreset(um); return; case RESETDONE: /* * Reset complete. * Mark all drives as needing recalibration * and issue specify command. */ for (unit = 0; unit < NFD; unit++) if (fddinfo[unit] && fddinfo[unit]->alive && fddinfo[unit]->mi == um) fdsoftc[unit].sc_flags |= FDF_RECAL; fdc->sc_cmd[0] = CMD_SPECIFY; fdc->sc_cmd[1] = SRTHUT; fdc->sc_cmd[2] = HLTND; if (!fdcmd(um, 3)) return; fdc->sc_flags &= ~FDF_RESET; fdc->sc_state = RECAL; break; case RECAL: /* * Recalibrate drive. */ fdc->sc_state = RECALDONE; fdc->sc_cmd[0] = CMD_RECAL; fdc->sc_cmd[1] = ui->slave; fdcmd(um, 2); return; case RECALDONE: /* * Recalibration complete. */ if ((fdc->sc_st0 & ST0_IC) || (fdc->sc_st0 & ST0_EC)) { if (++fdc->sc_recalerr == 2) { fderror("recalibrate failed", ui); goto bad; } fdc->sc_state = RESET; break; } sc->sc_flags &= ~FDF_RECAL; fdc->sc_recalerr = 0; sc->sc_cyl = -1; fdc->sc_state = SEEK; break; case SEEK: /* * Perform seek operation. */ fdc->sc_state = SEEKDONE; fdc->sc_cmd[0] = CMD_SEEK; fdc->sc_cmd[1] = (fdc->sc_tn << 2) | ui->slave; fdc->sc_cmd[2] = fdc->sc_cn; if (dk->dk_step) fdc->sc_cmd[2] <<= 1; fdcmd(um, 3); return; case SEEKDONE: /* * Seek complete. */ if (dk->dk_step) fdc->sc_pcn >>= 1; if ((fdc->sc_st0 & ST0_IC) || (fdc->sc_st0 & ST0_SE) == 0 || fdc->sc_pcn != fdc->sc_cn) { if (++fdc->sc_seekerr == 2) { fderror("seek failed", ui); goto bad; } fdc->sc_state = RESET; break; } fdc->sc_seekerr = 0; sc->sc_cyl = fdc->sc_pcn; fdc->sc_state = TRANSFER; break; case TRANSFER: /* * Perform I/O transfer. */ fdc->sc_flags &= ~FDF_BOUNCE; pa = pmap_extract(kernel_pmap, fdc->sc_addr); if (fdc->sc_op == CMD_FORMAT) { max = sizeof(struct format_info) * dk->dk_nspt; } else if (fdc->sc_flags & FDF_LIMIT) { fdc->sc_flags &= ~FDF_LIMIT; max = SECSIZE; } else { max = (dk->dk_nspc - dk->dk_nspt * fdc->sc_tn - fdc->sc_sn) * SECSIZE; } if (max > fdc->sc_resid) max = fdc->sc_resid; if (pa >= 16*1024*1024) { fdc->sc_flags |= FDF_BOUNCE; pa = fdc->sc_buf; if (max < DMABSIZE) fdc->sc_amt = max; else fdc->sc_amt = DMABSIZE; } else { int prevpa, curpa, omax; vm_offset_t va; omax = max; if (max > 65536 - (pa & 0xffff)) max = 65536 - (pa & 0xffff); fdc->sc_amt = I386_PGBYTES - (pa & (I386_PGBYTES - 1)); va = (vm_offset_t)fdc->sc_addr + fdc->sc_amt; prevpa = pa & ~(I386_PGBYTES - 1); while (fdc->sc_amt < max) { curpa = pmap_extract(kernel_pmap, va); if (curpa >= 16*1024*1024 || curpa != prevpa + I386_PGBYTES) break; fdc->sc_amt += I386_PGBYTES; va += I386_PGBYTES; prevpa = curpa; } if (fdc->sc_amt > max) fdc->sc_amt = max; if (fdc->sc_op == CMD_FORMAT) { if (fdc->sc_amt != omax) { fdc->sc_flags |= FDF_BOUNCE; pa = fdc->sc_buf; fdc->sc_amt = omax; } } else if (fdc->sc_amt != fdc->sc_resid) { if (fdc->sc_amt < SECSIZE) { fdc->sc_flags |= FDF_BOUNCE; pa = fdc->sc_buf; if (omax > DMABSIZE) fdc->sc_amt = DMABSIZE; else fdc->sc_amt = omax; } else fdc->sc_amt &= ~(SECSIZE - 1); } } DEBUGF(2, printf("fd%d: TRANSFER: amt %d cn %d tn %d sn %d\n", ui->unit, fdc->sc_amt, fdc->sc_cn, fdc->sc_tn, fdc->sc_sn + 1)); if ((fdc->sc_flags & FDF_BOUNCE) && fdc->sc_op != CMD_READ) { fdc->sc_flags &= ~FDF_BOUNCE; bcopy(fdc->sc_addr, (caddr_t)phystokv(fdc->sc_buf), fdc->sc_amt); } /* * Set up DMA. */ s = sploff(); outb(DMA_SINGLEMSK, 0x04 | 0x02); outb(DMA_FLIPFLOP, 0); outb(DMA_MODE, fdc->sc_mode); outb(DMA2_ADDR, pa); outb(DMA2_ADDR, pa >> 8); outb(DMA2_PAGE, pa >> 16); outb(DMA2_COUNT, fdc->sc_amt - 1); outb(DMA2_COUNT, (fdc->sc_amt - 1) >> 8); outb(DMA_SINGLEMSK, 0x02); splon(s); /* * Issue command to FDC. */ fdc->sc_state = TRANSFERDONE; fdc->sc_cmd[0] = fdc->sc_op; fdc->sc_cmd[1] = (fdc->sc_tn << 2) | ui->slave; if (fdc->sc_op == CMD_FORMAT) { fdc->sc_cmd[2] = 0x02; fdc->sc_cmd[3] = dk->dk_nspt; fdc->sc_cmd[4] = dk->dk_fgap; fdc->sc_cmd[5] = 0xda; fdcmd(um, 6); } else { fdc->sc_cmd[2] = fdc->sc_cn; fdc->sc_cmd[3] = fdc->sc_tn; fdc->sc_cmd[4] = fdc->sc_sn + 1; fdc->sc_cmd[5] = 0x02; fdc->sc_cmd[6] = dk->dk_nspt; fdc->sc_cmd[7] = dk->dk_gap; fdc->sc_cmd[8] = 0xff; fdcmd(um, 9); } return; case TRANSFERDONE: /* * Transfer complete. */ if (fdc->sc_st0 & ST0_IC) { fdc->sc_ioerr++; if (sc->sc_flags & FDF_AUTO) { /* * Give up on second try if * media detection is in progress. */ if (fdc->sc_ioerr == 2) goto bad; fdc->sc_state = RECAL; break; } if (fdc->sc_ioerr == MAX_RETRIES) { fderror(fderrmsg(ui), ui); goto bad; } /* * Give up immediately on write-protected diskettes. */ if (fdc->sc_st1 & ST1_NW) { fderror("write-protected diskette", ui); goto bad; } /* * Limit transfer to a single sector. */ fdc->sc_flags |= FDF_LIMIT; /* * Every fourth attempt recalibrate the drive. * Every eight attempt reset the controller. * Also, every eighth attempt inform user * about the error. */ if (fdc->sc_ioerr & 3) fdc->sc_state = TRANSFER; else if (fdc->sc_ioerr & 7) fdc->sc_state = RECAL; else { fdc->sc_state = RESET; fderror(fderrmsg(ui), ui); } break; } /* * Transfer completed successfully. * Advance counters/pointers, and if more * is left, initiate I/O. */ if (fdc->sc_flags & FDF_BOUNCE) { fdc->sc_flags &= ~FDF_BOUNCE; bcopy((caddr_t)phystokv(fdc->sc_buf), fdc->sc_addr, fdc->sc_amt); } if ((fdc->sc_resid -= fdc->sc_amt) == 0) { bp->b_resid = 0; fddone(ui, bp); return; } fdc->sc_state = TRANSFER; fdc->sc_ioerr = 0; fdc->sc_addr += fdc->sc_amt; if (fdc->sc_op == CMD_FORMAT) { fdc->sc_sn = 0; if (fdc->sc_tn == 1) { fdc->sc_tn = 0; fdc->sc_cn++; fdc->sc_state = SEEK; } else fdc->sc_tn = 1; } else { fdc->sc_sn += fdc->sc_amt / SECSIZE; while (fdc->sc_sn >= dk->dk_nspt) { fdc->sc_sn -= dk->dk_nspt; if (fdc->sc_tn == 1) { fdc->sc_tn = 0; fdc->sc_cn++; fdc->sc_state = SEEK; } else fdc->sc_tn = 1; } } break; default: printf("fd%d: invalid state\n", ui->unit); panic("fdstate"); /*NOTREACHED*/ } bad: bp->b_flags |= B_ERROR; bp->b_error = EIO; sc->sc_flags |= FDF_RECAL; fddone(ui, bp); } /* * Terminate current request and start * any others that are queued. */ int fddone(ui, bp) struct bus_device *ui; struct buf *bp; { struct bus_ctlr *um = ui->mi; struct fdsoftc *sc = &fdsoftc[ui->unit]; struct fdcsoftc *fdc = &fdcsoftc[um->unit]; struct buf *dp = &fdtab[um->unit]; DEBUGF(1, printf("fd%d: fddone()\n", ui->unit)); /* * Remove this request from queue. */ if (bp) { fdutab[ui->unit].b_actf = bp->b_actf; biodone(bp); bp = &fdutab[ui->unit]; dp->b_actf = bp->b_forw; } else bp = &fdutab[ui->unit]; /* * Mark controller and drive idle. */ dp->b_active = 0; bp->b_active = 0; fdc->sc_state = IDLE; sc->sc_mticks = 0; fdc->sc_flags &= ~(FDF_LIMIT|FDF_BOUNCE); /* * Start up other requests. */ fdustart(ui); fdstart(um); /* * Wakeup anyone waiting for drive or controller. */ if (sc->sc_flags & FDF_WANT) { sc->sc_flags &= ~FDF_WANT; wakeup((void *)sc); } if (fdc->sc_flags & FDF_WANT) { fdc->sc_flags &= ~FDF_WANT; wakeup((void *)fdc); } } /* * Check if diskette change has occured since the last open. */ int fddskchg(ui) struct bus_device *ui; { int s, dir; struct fdsoftc *sc = &fdsoftc[ui->unit]; struct bus_ctlr *um = ui->mi; struct fdcsoftc *fdc = &fdcsoftc[um->unit]; /* * Get access to controller. */ s = splbio(); while (fdtab[um->unit].b_active) { fdc->sc_flags |= FDF_WANT; assert_wait((event_t)fdc, FALSE); thread_block((void (*)())0); } fdtab[um->unit].b_active = 1; fdutab[ui->unit].b_active = 1; /* * Turn on drive motor and read digital input register. */ if (fdmotoron(ui)) { timeout(wakeup, (void *)fdc, hz / 2); assert_wait((event_t)fdc, FALSE); thread_block((void (*)())0); } dir = inb(FD_DIR(um->address)); fddone(ui, NULL); splx(s); if (dir & DIR_DSKCHG) { printf("fd%d: diskette change detected\n", ui->unit); sc->sc_flags |= FDF_SEEK; return (1); } return (0); } /* * Do media detection. */ int fdauto(dev) dev_t dev; { int i, error = 0; struct buf *bp; struct bus_device *ui = fddinfo[fdunit(dev)]; struct fdsoftc *sc = &fdsoftc[ui->unit]; struct fddk *dk, *def = 0; sc->sc_flags |= FDF_AUTO; bp = (struct buf *)geteblk(SECSIZE); for (i = 0, dk = fddk; i < NDKTYPES; i++, dk++) { if ((dk->dk_drives & (1 << sc->sc_type)) == 0) continue; if (def == 0) def = dk; sc->sc_dk = dk; bp->b_flags = B_READ; bp->b_dev = dev; bp->b_bcount = SECSIZE; if (sc->sc_flags & FDF_SEEK) { sc->sc_flags &= ~FDF_SEEK; bp->b_blkno = 100; } else bp->b_blkno = 0; fdstrategy(bp); biowait(bp); if ((bp->b_flags & B_ERROR) == 0 || bp->b_error == ENXIO) break; } if (i == NDKTYPES) { printf("fd%d: couldn't detect type, using %s\n", ui->unit, def->dk_name); sc->sc_dk = def; } else if ((bp->b_flags & B_ERROR) == 0) printf("fd%d: detected %s\n", ui->unit, sc->sc_dk->dk_name); else error = ENXIO; sc->sc_flags &= ~FDF_AUTO; brelse(bp); return (error); } /* * Turn on drive motor and select drive. */ int fdmotoron(ui) struct bus_device *ui; { int bit; struct bus_ctlr *um = ui->mi; struct fdcsoftc *fdc = &fdcsoftc[um->unit]; bit = 1 << (ui->slave + 4); if ((fdc->sc_dor & bit) == 0) { fdc->sc_dor &= ~3; fdc->sc_dor |= bit | ui->slave; outb(FD_DOR(um->address), fdc->sc_dor); return (1); } if ((fdc->sc_dor & 3) != ui->slave) { fdc->sc_dor &= ~3; fdc->sc_dor |= ui->slave; outb(FD_DOR(um->address), fdc->sc_dor); } return (0); } /* * Turn off drive motor. */ int fdmotoroff(ui) struct bus_device *ui; { struct bus_ctlr *um = ui->mi; struct fdcsoftc *fdc = &fdcsoftc[um->unit]; fdc->sc_dor &= ~(1 << (ui->slave + 4)); outb(FD_DOR(um->address), fdc->sc_dor); } /* * This routine is invoked via timeout() by fdstart() * to call fdstate() at splbio. */ void fdspinup(um) struct bus_ctlr *um; { int s; s = splbio(); fdstate(um); splx(s); } /* * Watchdog routine. * Check for hung operations. * Turn off motor of idle drives. */ void fdwatch() { int unit, s; struct bus_device *ui; timeout(fdwatch, 0, hz); s = splbio(); for (unit = 0; unit < NFDC; unit++) if (fdtab[unit].b_active && ++fdcsoftc[unit].sc_wticks == OP_TIMEOUT) fdintr(unit); for (unit = 0; unit < NFD; unit++) { if ((ui = fddinfo[unit]) == 0 || ui->alive == 0) continue; if (fdutab[unit].b_active == 0 && (fdcsoftc[ui->mi->unit].sc_dor & (1 << (ui->slave + 4))) && ++fdsoftc[unit].sc_mticks == MOTOR_TIMEOUT) fdmotoroff(ui); } splx(s); } /* * Print an error message. */ int fderror(msg, ui) char *msg; struct bus_device *ui; { struct fdcsoftc *fdc = &fdcsoftc[ui->mi->unit]; printf("fd%d: %s, %sing cn %d tn %d sn %d\n", ui->unit, msg, (fdc->sc_op == CMD_READ ? "read" : (fdc->sc_op == CMD_WRITE ? "writ" : "formatt")), fdc->sc_cn, fdc->sc_tn, fdc->sc_sn + 1); } /* * Return an error message for an I/O error. */ char * fderrmsg(ui) struct bus_device *ui; { struct fdcsoftc *fdc = &fdcsoftc[ui->mi->unit]; if (fdc->sc_st1 & ST1_EC) return ("invalid sector"); if (fdc->sc_st1 & ST1_DE) return ("CRC error"); if (fdc->sc_st1 & ST1_OR) return ("DMA overrun"); if (fdc->sc_st1 & ST1_ND) return ("sector not found"); if (fdc->sc_st1 & ST1_NW) return ("write-protected diskette"); if (fdc->sc_st1 & ST1_MA) return ("missing address mark"); return ("hard error"); } /* * Output a command to FDC. */ int fdcmd(um, n) struct bus_ctlr *um; int n; { int i, j; struct fdcsoftc *fdc = &fdcsoftc[um->unit]; for (i = j = 0; i < 200; i++) { if ((inb(FD_STATUS(um->address)) & (ST_RQM|ST_DIO)) != ST_RQM) continue; outb(FD_DATA(um->address), fdc->sc_cmd[j++]); if (--n == 0) return (1); } /* * Controller is not responding, reset it. */ DEBUGF(1, printf("fdc%d: fdcmd() failed\n", um->unit)); fdreset(um); return (0); } /* * Read results from FDC. */ int fdresults(um, rp) struct bus_ctlr *um; u_char *rp; { int i, j, status; for (i = j = 0; i < 200; i++) { status = inb(FD_STATUS(um->address)); if ((status & ST_RQM) == 0) continue; if ((status & ST_DIO) == 0) return (j); if (j == 7) break; *rp++ = inb(FD_DATA(um->address)); j++; } /* * Controller is not responding, reset it. */ DEBUGF(1, printf("fdc%d: fdresults() failed\n", um->unit)); fdreset(um); return (0); } /* * Reset controller. */ int fdreset(um) struct bus_ctlr *um; { struct fdcsoftc *fdc = &fdcsoftc[um->unit]; outb(FD_DOR(um->address), fdc->sc_dor & ~(DOR_RSTCLR|DOR_IENABLE)); fdc->sc_state = RESETDONE; fdc->sc_flags |= FDF_RESET; outb(FD_DOR(um->address), fdc->sc_dor); } #endif /* NFD > 0 */