diff options
Diffstat (limited to 'i386/i386at/nfd.c')
-rw-r--r-- | i386/i386at/nfd.c | 1484 |
1 files changed, 1484 insertions, 0 deletions
diff --git a/i386/i386at/nfd.c b/i386/i386at/nfd.c new file mode 100644 index 00000000..950f8964 --- /dev/null +++ b/i386/i386at/nfd.c @@ -0,0 +1,1484 @@ +/* + * 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 <fd.h> +#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 <sys/types.h> +#include <sys/ioctl.h> +#include "vm_param.h" +#include <kern/time_out.h> +#include <vm/pmap.h> +#include <device/param.h> +#include <device/buf.h> +#include <device/errno.h> +#include <chips/busses.h> +#include <i386/machspl.h> +#include <i386/pio.h> +#include <i386at/cram.h> +#include <i386at/disk.h> +#include <i386at/nfdreg.h> + +/* + * 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 <i386/dma.h> 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 */ |