/* $Id: iwm_fd.c,v 1.1 1999/02/18 07:38:26 scottr Exp $ */ /* * Copyright (c) 1997, 1998 Hauke Fath. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. The name of the author may not be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /* * fd.c -- Sony (floppy disk) driver for Macintosh m68k * * The present implementation supports the 400/800K GCR format on * non-DMA machines. */ #include #include #include #include #include #include #include #define FSTYPENAMES #include #include #include #include #include #include #include #include #include #include #include #include /** ** Private functions **/ static int map_iwm_base(vm_offset_t base); /* Autoconfig */ int iwm_match __P((struct device *, struct cfdata *, void *)); void iwm_attach __P((struct device *, struct device *, void *)); int iwm_print __P((void *, const char *)); int fd_match __P((struct device *, struct cfdata *, void *)); void fd_attach __P((struct device *, struct device *, void *)); int fd_print __P((void *, const char *)); /* Disklabel stuff */ static void fdGetDiskLabel(fd_softc_t *fd, dev_t dev); static void fdPrintDiskLabel(struct disklabel *lp); static fdInfo_t *getFDType(short unit); static fdInfo_t *fdDeviceToType(fd_softc_t *fd, dev_t dev); static void fdstart(fd_softc_t *fd); static void remap_geometry(daddr_t block, int heads, diskPosition_t *loc); static void motor_off(void *param); static int seek(diskPosition_t *loc, int style); static int checkTrack(diskPosition_t loc, int debugFlag); #ifdef _LKM static int probe_fd(void); #endif /** ** Driver debugging **/ static void hexDump(u_char *buf, int len); /* * Stuff taken from Egan/Teixeira ch 8: 'if()' debug output statements * don't break indentation, and when DEBUG is not defined, the compiler * drops them as dead code. */ #ifdef DEBUG #define M_TRACE_CONFIG 0x0001 #define M_TRACE_OPEN 0x0002 #define M_TRACE_CLOSE 0x0004 #define M_TRACE_READ 0x0008 #define M_TRACE_WRITE 0x0010 #define M_TRACE_STRAT (M_TRACE_READ | M_TRACE_WRITE) #define M_TRACE_IOCTL 0x0020 #define M_TRACE_STEP 0x0040 #define M_TRACE_ALL 0xFFFF #define TRACE_CONFIG (iwmDebugging & M_TRACE_CONFIG) #define TRACE_OPEN (iwmDebugging & M_TRACE_OPEN) #define TRACE_CLOSE (iwmDebugging & M_TRACE_CLOSE) #define TRACE_READ (iwmDebugging & M_TRACE_READ) #define TRACE_WRITE (iwmDebugging & M_TRACE_WRITE) #define TRACE_STRAT (iwmDebugging & M_TRACE_STRAT) #define TRACE_IOCTL (iwmDebugging & M_TRACE_IOCTL) #define TRACE_STEP (iwmDebugging & M_TRACE_STEP) #define TRACE_ALL (iwmDebugging & M_TRACE_ALL) /* -1 = all active */ int iwmDebugging = 0 /* | M_TRACE_CONFIG */ ; #else #define TRACE_CONFIG 0 #define TRACE_OPEN 0 #define TRACE_CLOSE 0 #define TRACE_READ 0 #define TRACE_WRITE 0 #define TRACE_STRAT 0 #define TRACE_IOCTL 0 #define TRACE_STEP 0 #define TRACE_ALL 0 #endif #define DISABLED 0 /** ** Module-global Variables **/ /* The IWM base address */ u_long IWMBase; /* * Table of supported disk types. * The table order seems to be pretty standardized across NetBSD ports, but * then, they are all MFM... So we roll our own for now. */ static fdInfo_t fdTypes[] = { {1, 80, 512, 10, 10, 800, 12, 2, IWM_GCR, "400K Sony"}, {2, 80, 512, 10, 20, 1600, 12, 2, IWM_GCR, "800K Sony"} }; /* Table of GCR disk zones for one side (see IM II-211, The Disk Driver) */ static diskZone_t diskZones[] = { {16, 12, 0, 191}, {16, 11, 192, 367}, {16, 10, 368, 527}, {16, 9, 528, 671}, {16, 8, 672, 799} }; /* disk(9) framework device switch */ struct dkdriver fd_dkDriver = { fdstrategy }; /* Drive format codes/indexes */ enum { k400K_Sony = 0, k800K_Sony = 1, k720K_SuperDrive = 2, k1440K_SuperDrive = 3 }; /** ** Autoconfiguration code **/ /* * Autoconfig data structures * * These data structures (see ) are referenced in * compile/$KERNEL/ioconf.c, which is generated by config(8). * Their names are formed like {device}_{ca,cd}. * * {device}_ca * is used for dynamically allocating driver data, probing and * attaching a device; * * {device}_cd * references all found devices of a type. */ #ifdef _LKM struct cfdriver iwm_cd = { NULL, /* Ptr to array of devices found */ "iwm", /* Device name string */ DV_DULL, /* Device classification */ 0 /* Number of devices found */ }; struct cfdriver fd_cd = { NULL, "fd", DV_DISK, 0 }; #else /* defined _LKM */ extern struct cfdriver iwm_cd; extern struct cfdriver fd_cd; #endif /* defined _LKM */ /* IWM floppy disk controller */ struct cfattach iwm_ca = { sizeof(iwm_softc_t), /* Size of device data for malloc() */ iwm_match, /* Probe device and return match level */ iwm_attach /* Initialize and attach device */ }; /* Attached floppy disk drives */ struct cfattach fd_ca = { sizeof(fd_softc_t), fd_match, fd_attach }; /*** Configure the IWM controller ***/ /* * iwm_match * * Is the IWM chip present? (Can't pull it out...) * Here, *aux is a ptr to struct confargs (see ), * which does not hold any information to match against. After all, * that's what the obio concept is about: Onboard components that are * present depending (only) on machine type. */ int iwm_match(parent, match, auxp) struct device *parent; struct cfdata *match; void *auxp; { int matched; #ifdef _LKM int iwmErr; #endif extern u_long IOBase; /* from mac68k/machdep.c */ extern u_long IWMBase; if (0 == map_iwm_base(IOBase)) { /* * Unknown machine HW: * The SWIM II/III chips that are present in post-Q700 * '040 Macs have dropped the IWM register structure. * We know next to nothing about the SWIM. */ matched = 0; printf("IWM or SWIM not found: Unknown location (SWIM II/III?).\n"); } else { matched = 1; if (TRACE_CONFIG) { printf("iwm: IWMBase mapped to 0x%lx in VM.\n", IWMBase); } #ifdef _LKM iwmErr = iwmInit(); if (TRACE_CONFIG) printf("initIWM() says %d.\n", iwmErr); matched = (iwmErr == 0) ? 1 : 0; #endif } return matched; } /* * iwm_attach * * The IWM is present, initialize it. Then look up the connected drives * and attach them. */ void iwm_attach(parent, self, auxp) struct device *parent; struct device *self; void *auxp; { int iwmErr; iwm_softc_t *iwm; iwmAttachArgs_t ia; printf(": Apple GCR floppy disk controller\n"); iwm = (iwm_softc_t *)self; iwmErr = iwmInit(); if (TRACE_CONFIG) printf("initIWM() says %d.\n", iwmErr); if (0 == iwmErr) { /* Set up the IWM softc */ iwm->maxRetries = 10; /* Look for attached drives */ for (ia.unit = 0; ia.unit < IWM_MAX_DRIVE; ia.unit++) { iwm->fd[ia.unit] = NULL; ia.driveType = getFDType(ia.unit); if (NULL != ia.driveType) config_found_sm(self, (void *)&ia, fd_print, NULL); } if (TRACE_CONFIG) printf("iwm: Initialization completed.\n"); } else { printf("iwm: Initialization FAILED (%d)\n", iwmErr); } } /* * iwm_print -- print device configuration. * * If the device is not configured 'controller' refers to a name string * we print here. * Else it is NULL and we print a message in the *Attach routine; the * return value of *Print() is ignored. */ int iwm_print(auxp, controller) void *auxp; const char *controller; { return UNCONF; } /* * map_iwm_base * * Map physical IO address of IWM to VM address */ static int map_iwm_base(vm_offset_t base) { int known; extern u_long IWMBase; switch (current_mac_model->class) { case MACH_CLASSQ: case MACH_CLASSQ2: case MACH_CLASSP580: IWMBase = base + 0x1E000; known = 1; break; case MACH_CLASSII: case MACH_CLASSPB: case MACH_CLASSDUO: case MACH_CLASSIIci: case MACH_CLASSIIsi: case MACH_CLASSIIvx: case MACH_CLASSLC: IWMBase = base + 0x16000; known = 1; break; case MACH_CLASSIIfx: case MACH_CLASSAV: default: /* Sorry, no luck, wrong hardware... */ printf("No IWM here, blech...\n"); IWMBase = 0L; known = 0; break; } return known; } /*** Configure Sony disk drive(s) ***/ /* * fd_match */ int fd_match(parent, match, auxp) struct device *parent; struct cfdata *match; void *auxp; { int matched, cfUnit; struct cfdata *cfp; iwmAttachArgs_t *fdParams; cfp = match; fdParams = (iwmAttachArgs_t *)auxp; cfUnit = cfp->cf_loc[0]; matched = (cfUnit == fdParams->unit || cfUnit == -1) ? 1 : 0; if (TRACE_CONFIG) { printf("fdMatch() drive %d ? cfUnit = %d\n", fdParams->unit, cfp->cf_loc[0]); } return matched; } /* * fd_attach * * We have checked that the IWM is fine and the drive is present, * so we can attach it. */ void fd_attach(parent, self, auxp) struct device *parent; struct device *self; void *auxp; { iwm_softc_t *iwm; fd_softc_t *fd; iwmAttachArgs_t *ia; int driveInfo; iwm = (iwm_softc_t *)parent; fd = (fd_softc_t *)self; ia = (iwmAttachArgs_t *)auxp; driveInfo = iwmCheckDrive(ia->unit); fd->currentType = ia->driveType; fd->unit = ia->unit; fd->defaultType = &fdTypes[k800K_Sony]; fd->trackBuf = NULL; fd->stepDirection = 0; iwm->fd[ia->unit] = fd; /* iwm has ptr to this drive */ iwm->drives++; printf(" drive %d: ", fd->unit); if (IWM_NO_DISK & driveInfo) { printf("(drive empty)\n"); } else if (!(IWM_DD_DISK & driveInfo)) { printf("(HD disk -- not supported)\n"); iwmDiskEject(fd->unit); /* XXX */ } else { printf("%s %d cyl, %d head(s)\n", fd->currentType->description, fd->currentType->tracks, fd->currentType->heads); } if (TRACE_CONFIG) { int reg, flags, spl; /* List contents of drive status registers */ spl = splhigh(); for (reg = 0; reg < 0x10; reg++) { flags = iwmQueryDrvFlag(fd->unit, reg); printf("iwm: Drive register 0x%x = 0x%x\n", reg, flags); } splx(spl); } fd->diskInfo.dk_name = fd->devInfo.dv_xname; fd->diskInfo.dk_driver = &fd_dkDriver; disk_attach(&fd->diskInfo); } /* * fdPrint -- print device configuration. * * If the device is not configured 'controller' refers to a name string * we print here. * Else it is NULL and we print a message in the *Attach routine; the * return value of *Print() is ignored. */ int fd_print(auxp, controller) void *auxp; const char *controller; { iwmAttachArgs_t *ia; ia = (iwmAttachArgs_t *)auxp; if (NULL != controller) printf("fd%d at %s", ia->unit, controller); return UNCONF; } #ifdef _LKM static iwm_softc_t *iwm; /* * fd_mod_init * * Any initializations necessary after loading the module happen here. */ int fd_mod_init(void) { int err; iwm = (iwm_softc_t *)malloc(sizeof(iwm_softc_t), M_DEVBUF, M_WAITOK); err = (1 == iwm_match(NULL, NULL, NULL)) ? 0 : EIO; if (!err) { bzero(iwm, sizeof(iwm_softc_t)); iwm->maxRetries = 10; err = (0 == probe_fd()) ? 0 : EIO; } return err; } /* * fd_mod_free * * Necessary clean-up before unloading the module. */ void fd_mod_free(void) { int unit, spl; spl = splbio(); /* Release any allocated memory */ for (unit = 0; unit < IWM_MAX_DRIVE; unit++) if (iwm->fd[unit] != NULL) { /* * Let's hope there is only one task per drive, * see timeout(9). */ untimeout(motor_off, iwm->fd[unit]); disk_detach(&iwm->fd[unit]->diskInfo); free(iwm->fd[unit], M_DEVBUF); iwm->fd[unit] = NULL; } free(iwm, M_DEVBUF); splx(spl); } /* * probe_fd * * See if there are any drives out there and configure them. * If we find a drive we allocate a softc structure for it and * insert its address into the iwm_softc. * * XXX Merge the remainder of probeFD() with the autoconfig framework. */ static int probe_fd(void) { fd_softc_t *fd; iwmAttachArgs_t ia; int err, unit; err = 0; for (ia.unit = 0; ia.unit < IWM_MAX_DRIVE; ia.unit++) { ia.driveType = getFDType(ia.unit); if (NULL == ia.driveType) { iwm->fd[ia.unit] = NULL; continue; } fd = (fd_softc_t *)malloc(sizeof(fd_softc_t), M_DEVBUF, M_WAITOK); if (fd == NULL) { err = ENOMEM; break; } else { bzero(fd, sizeof(fd_softc_t)); /* This is usually set by the autoconfig framework */ sprintf(fd->devInfo.dv_xname, "fd%d%c", ia.unit, 'a'); fd_attach((struct device *)iwm, (struct device *)fd, &ia); } } if (err) { /* Release any allocated memory */ for (unit = 0; unit < IWM_MAX_DRIVE; unit++) if (iwm->fd[unit] != NULL) { free(iwm->fd[unit], M_DEVBUF); iwm->fd[unit] = NULL; } } return err; } #endif /* defined _LKM */ /** ** Implementation section of driver interface ** ** The prototypes for these functions are set up automagically ** by macros in mac68k/conf.c. Their names are generated from {fd} ** and {open,close,strategy,dump,size,read,write}. The driver entry ** points are then plugged into bdevsw[] and cdevsw[]. **/ /* * fdopen * * Open a floppy disk device. */ int fdopen(dev, flags, devType, proc) dev_t dev; int flags; int devType; struct proc *proc; { fd_softc_t *fd; fdInfo_t *info; int partitionMask; int fdType, fdUnit; int ierr, err; #ifndef _LKM iwm_softc_t *iwm = iwm_cd.cd_devs[0]; #endif info = NULL; /* XXX shut up egcs */ /* * See for struct cfdriver, for * DISKUNIT() and arch/atari/atari/device.h for getsoftc(). */ fdType = minor(dev) % MAXPARTITIONS; fdUnit = minor(dev) / MAXPARTITIONS; if (TRACE_OPEN) printf("iwm: Open drive %d", fdUnit); /* Check if device # is valid */ err = (iwm->drives < fdUnit) ? ENXIO : 0; if (!err) { (void)iwmSelectDrive(fdUnit); if (TRACE_OPEN) printf(".\n Get softc"); /* Get fd state */ fd = iwm->fd[fdUnit]; err = (NULL == fd) ? ENXIO : 0; } if (!err) { if (fd->state & IWM_FD_IS_OPEN) { /* * Allow multiple open calls only if for identical * floppy format. */ if (TRACE_OPEN) printf(".\n Drive already opened!\n"); err = (fd->partition == fdType) ? 0 : ENXIO; } else { if (TRACE_OPEN) printf(".\n Get format info"); /* Get format type */ info = fdDeviceToType(fd, dev); if (NULL == info) { err = ENXIO; if (TRACE_OPEN) printf(".\n No such drive.\n"); } } } if (!err && !(fd->state & IWM_FD_IS_OPEN)) { if (TRACE_OPEN) printf(".\n Set diskInfo flags.\n"); fd->writeLabel = 0; fd->partition = fdType; fd->currentType = info; fd->drvFlags = iwmCheckDrive(fd->unit); if (fd->drvFlags & IWM_NO_DISK) { err = EIO; #ifdef DIAGNOSTIC printf(" Drive %d is empty.\n", fd->unit); #endif } else if (!(fd->drvFlags & IWM_WRITEABLE) && (flags & FWRITE)) { err = EPERM; #ifdef DIAGNOSTIC printf(" Disk is write protected.\n"); #endif } else if (!(fd->drvFlags & IWM_DD_DISK)) { err = ENXIO; #ifdef DIAGNOSTIC printf(" HD format not supported.\n"); #endif (void)iwmDiskEject(fd->unit); } else /* We're open now! */ fd->state |= IWM_FD_IS_OPEN; } if (!err) { /* * Later, we might not want to recalibrate the drive when it * is already open. For now, it doesn't hurt. */ if (TRACE_OPEN) printf(" Seek track 00 says"); bzero(&fd->pos, sizeof(diskPosition_t)); ierr = seek(&fd->pos, IWM_SEEK_RECAL); if (TRACE_OPEN) printf(" %d.\n", ierr); if (0 == ierr) fd->pos.track = fd->pos.oldTrack = 0; else err = EIO; } if (!err) { /* * Update disklabel if we are not yet open. * (We shouldn't be: We are synchronous.) */ if (fd->diskInfo.dk_openmask == 0) fdGetDiskLabel(fd, dev); partitionMask = (1 << fdType); switch (devType) { case S_IFCHR: fd->diskInfo.dk_copenmask |= partitionMask; break; case S_IFBLK: fd->diskInfo.dk_bopenmask |= partitionMask; break; } fd->diskInfo.dk_openmask = fd->diskInfo.dk_copenmask | fd->diskInfo.dk_bopenmask; } if (TRACE_OPEN) printf("iwm: fdopen() says %d.\n", err); return err; } /* * fdclose */ int fdclose(dev, flags, devType, proc) dev_t dev; int flags; int devType; struct proc *proc; { fd_softc_t *fd; int partitionMask, fdUnit, fdType; #ifndef _LKM iwm_softc_t *iwm = iwm_cd.cd_devs[0]; #endif if (TRACE_CLOSE) printf("iwm: Closing driver."); fdUnit = minor(dev) / MAXPARTITIONS; fdType = minor(dev) % MAXPARTITIONS; fd = iwm->fd[fdUnit]; partitionMask = (1 << fdType); /* Set state flag. */ fd->state &= ~IWM_FD_IS_OPEN; switch (devType) { case S_IFCHR: fd->diskInfo.dk_copenmask &= ~partitionMask; break; case S_IFBLK: fd->diskInfo.dk_bopenmask &= ~partitionMask; break; } fd->diskInfo.dk_openmask = fd->diskInfo.dk_copenmask | fd->diskInfo.dk_bopenmask; return 0; } /* * fdstrategy * * Entry point for read and write requests. The strategy routine usually * queues io requests and kicks off the next transfer if the device is idle; * but we get no interrupts from the IWM and have to do synchronous * transfers - no queue. */ void fdstrategy(bp) struct buf *bp; { int fdUnit, err, done, spl; int sectSize, transferSize; diskPosition_t physDiskLoc; fd_softc_t *fd; #ifndef _LKM iwm_softc_t *iwm = iwm_cd.cd_devs[0]; #endif err = 0; done = 0; fdUnit = minor(bp->b_dev) / MAXPARTITIONS; if (TRACE_STRAT) { printf("iwm: fdstrategy()...\n"); printf(" struct buf is at %p\n", bp); printf(" Allocated buffer size (b_bufsize): 0x0%lx\n", bp->b_bufsize); printf(" Base address of buffer (b_un.b_addr): %p\n", bp->b_un.b_addr); printf(" Bytes to be transferred (b_bcount): 0x0%lx\n", bp->b_bcount); printf(" Remaining I/O (b_resid): 0x0%lx\n", bp->b_resid); } /* Check for valid fd unit, controller and io request */ if (fdUnit >= iwm->drives) { if (TRACE_STRAT) printf(" No such unit (%d)\n", fdUnit); err = EINVAL; } if (!err) { fd = iwm->fd[fdUnit]; err = (NULL == fd) ? EINVAL : 0; } if (!err) { sectSize = fd->currentType->sectorSize; if (bp->b_blkno < 0 || (bp->b_bcount % sectSize) != 0) { if (TRACE_STRAT) printf(" Illegal transfer size: " "block %d, %ld bytes\n", bp->b_blkno, bp->b_bcount); err = EINVAL; } } if (!err) { /* Null transfer: Return, nothing to do. */ if (0 == bp->b_bcount) { if (TRACE_STRAT) printf(" Zero transfer length.\n"); done = 1; } } if (!err && !done) { /* What to do if we touch the boundaries of the disk? */ transferSize = (bp->b_bcount + (sectSize - 1)) / sectSize; if (bp->b_blkno + transferSize > fd->currentType->secPerDisk) { if (TRACE_STRAT) { printf("iwm: Transfer beyond end of disk!\n" \ " (Starting block %d, # of blocks %d," \ " last disk block %d).\n", bp->b_blkno, transferSize, fd->currentType->secPerDisk); } /* * Return EOF if we are exactly at the end of the * disk, EINVAL if we try to reach past the end; else * truncate the request. */ transferSize = fd->currentType->secPerDisk - bp->b_blkno; if (0 == transferSize) { bp->b_resid = bp->b_bcount; done = 1; } else if (0 > transferSize) err = EINVAL; else bp->b_bcount = transferSize << DEV_BSHIFT; } } if (!err && !done) { /* * Calculate cylinder # for disksort(). * * XXX Shouldn't we use the (fake) logical cyl no here? */ remap_geometry(bp->b_blkno, fd->currentType->heads, &physDiskLoc); bp->b_cylinder = physDiskLoc.track; if (TRACE_STRAT) { printf(" This job starts at b_blkno %d; ", bp->b_blkno); printf("it gets sorted for cylinder # %ld.\n", bp->b_cylinder); } spl = splbio(); untimeout(motor_off, fd); disksort(&fd->bufQueue, bp); if (fd->bufQueue.b_active == 0) fdstart(fd); splx(spl); } /* Clean up, if necessary */ else { if (TRACE_STRAT) printf(" fdstrategy() finished early, err = %d.\n", err); if (err) { bp->b_error = err; bp->b_flags |= B_ERROR; } bp->b_resid = bp->b_bcount; biodone(bp); } /* Comment on results */ if (TRACE_STRAT) { printf("iwm: fdstrategy() done.\n"); printf(" We have b_resid = %ld bytes left, " \ "b_error is %d;\n", bp->b_resid, bp->b_error); printf(" b_flags are 0x0%lx.\n", bp->b_flags); } } /* * fdioctl * * We deal with all the disk-specific ioctls in here even if * we do not support them. */ int fdioctl(dev, cmd, data, flags, proc) dev_t dev; u_long cmd; caddr_t data; int flags; struct proc *proc; { int result, fdUnit, fdType; fd_softc_t *fd; #ifndef _LKM iwm_softc_t *iwm = iwm_cd.cd_devs[0]; #endif if (TRACE_IOCTL) printf("iwm: Execute ioctl... "); /* Check if device # is valid and get its softc */ fdUnit = minor(dev) / MAXPARTITIONS; fdType = minor(dev) % MAXPARTITIONS; if (fdUnit >= iwm->drives) { if (TRACE_IOCTL) { printf("iwm: Wanted device no (%d) is >= %d.\n", fdUnit, iwm->drives); } return ENXIO; } fd = iwm->fd[fdUnit]; result = 0; switch (cmd) { case DIOCGDINFO: if (TRACE_IOCTL) printf(" DIOCGDINFO: Get in-core disklabel.\n"); *(struct disklabel *) data = *(fd->diskInfo.dk_label); result = 0; break; case DIOCSDINFO: if (TRACE_IOCTL) printf(" DIOCSDINFO: Set in-core disklabel.\n"); result = ((flags & FWRITE) == 0) ? EBADF : 0; if (result == 0) result = setdisklabel(fd->diskInfo.dk_label, (struct disklabel *)data, 0, fd->diskInfo.dk_cpulabel); break; case DIOCWDINFO: if (TRACE_IOCTL) printf(" DIOCWDINFO: Set in-core disklabel " "& update disk.\n"); result = ((flags & FWRITE) == 0) ? EBADF : 0; if (result == 0) result = setdisklabel(fd->diskInfo.dk_label, (struct disklabel *)data, 0, fd->diskInfo.dk_cpulabel); if (result == 0) result = writedisklabel(dev, fdstrategy, fd->diskInfo.dk_label, fd->diskInfo.dk_cpulabel); break; case DIOCGPART: if (TRACE_IOCTL) printf(" DIOCGPART: Get disklabel & partition table.\n"); ((struct partinfo *)data)->disklab = fd->diskInfo.dk_label; ((struct partinfo *)data)->part = &fd->diskInfo.dk_label->d_partitions[fdType]; result = 0; break; case DIOCRFORMAT: case DIOCWFORMAT: if (TRACE_IOCTL) printf(" DIOC{R,W}FORMAT: No formatter support (yet?).\n"); result = EINVAL; break; case DIOCSSTEP: if (TRACE_IOCTL) printf(" DIOCSSTEP: IWM does step handshake.\n"); result = EINVAL; break; case DIOCSRETRIES: if (TRACE_IOCTL) printf(" DIOCSRETRIES: Set max. # of retries.\n"); if (*(int *)data < 0) result = EINVAL; else { iwm->maxRetries = *(int *)data; result = 0; } break; case DIOCWLABEL: if (TRACE_IOCTL) printf(" DIOCWLABEL: Set write access to disklabel.\n"); result = ((flags & FWRITE) == 0) ? EBADF : 0; if (result == 0) fd->writeLabel = *(int *)data; break; case DIOCSBAD: if (TRACE_IOCTL) printf(" DIOCSBAD: No bad144-style handling.\n"); result = EINVAL; break; case DIOCEJECT: /* XXX Eject disk only when unlocked */ if (TRACE_IOCTL) printf(" DIOCEJECT: Eject disk from unit %d.\n", fd->unit); result = iwmDiskEject(fd->unit); break; case DIOCLOCK: /* XXX Use lock to prevent ejectimg a mounted disk */ if (TRACE_IOCTL) printf(" DIOCLOCK: No need to (un)lock Sony drive.\n"); result = 0; break; default: if (TRACE_IOCTL) printf(" Not a disk related ioctl!\n"); result = ENOTTY; break; } return result; } /* * fddump -- We don't dump to a floppy disk. */ int fddump(dev, blkno, va, size) dev_t dev; daddr_t blkno; caddr_t va; size_t size; { return ENXIO; } /* * fdsize -- We don't dump to a floppy disk. */ int fdsize(dev) dev_t dev; { return -1; } /* * fdread */ int fdread(dev, uio, flags) dev_t dev; struct uio *uio; int flags; { int err; err = physio(fdstrategy, NULL, dev, B_READ, minphys, uio); if (DISABLED && TRACE_READ) printf(" Raw read: physio() says %d.\n", err); return err; } /* * fdwrite */ int fdwrite(dev, uio, flags) dev_t dev; struct uio *uio; int flags; { int err; err = physio(fdstrategy, NULL, dev, B_WRITE, minphys, uio); if (DISABLED && TRACE_WRITE) printf(" Raw write: physio() says %d.\n", err); return err; } /* ======================================================================== */ /* * fdstart * * we are called from the strategy() routine to perform a data transfer. * * The disk(9) framework demands we run at splbio(); our caller * takes care of that. */ static void fdstart(fd) fd_softc_t *fd; { int iwmErr; /* Holds the low level err code */ int state; int ioRetries, seekRetries, sectRetries; int readFlag, done; char taskDesc[8]; caddr_t buffer; struct buf *bp; sectorHdr_t sHdr; #ifndef _LKM iwm_softc_t *iwm = iwm_cd.cd_devs[0]; #endif char *stateDesc[] = { "Init", "Seek", "DoIO", "IOFinish", "IOErr", "Fault", "Exit" }; enum { state_Init = 0, state_Seek, state_DoIO, state_IOFinish, state_IOErr, state_Fault, state_Exit }; /* XXX Shut up egcs */ iwmErr = ioRetries = seekRetries = done = 0; buffer = 0; /* * Get the first entry from the queue. This is the buf we gave to * fdstrategy(); disksort() put it into our softc. */ bp = fd->bufQueue.b_actf; if (NULL == bp) { if (TRACE_STRAT) printf("Queue empty: Nothing to do"); return; } readFlag = bp->b_flags & B_READ; strncpy(taskDesc, readFlag ? "Read" : "Write", sizeof(taskDesc)); state = state_Init; do { if (TRACE_STRAT) printf(" fdstart state %d [%s] ", state, stateDesc[state]); switch (state) { case state_Init: /* Set up things */ disk_busy(&fd->diskInfo); if (!(fd->state & IWM_FD_MOTOR_ON)) { iwmMotor(fd->unit, 1); fd->state |= IWM_FD_MOTOR_ON; } buffer = bp->b_un.b_addr; /* XXX - assumes blocks of 512 bytes */ fd->startBlk = bp->b_blkno; iwmErr = done = 0; fd->bytesDone = 0; fd->bytesLeft = bp->b_bcount; state = state_Seek; break; case state_Seek: /* Calculate side/track/sector our block is at. */ if (TRACE_STRAT) printf(" Remap block %d ", fd->startBlk); remap_geometry(fd->startBlk, fd->currentType->heads, &fd->pos); if (TRACE_STRAT) printf("to c%d_h%d_s%d ", fd->pos.track, fd->pos.side, fd->pos.sector); /* * If necessary, seek to wanted track. Note that * seek() performs any necessary retries. */ if (fd->pos.track != fd->pos.oldTrack && 0 != (iwmErr = seek(&fd->pos, IWM_SEEK_VANILLA))) { state = state_Fault; } else { state = state_DoIO; } break; case state_DoIO: if (TRACE_STRAT) printf("<%s c%d_h%d_s%d> ", taskDesc, fd->pos.track, fd->pos.side, fd->pos.sector); ioRetries = seekRetries = sectRetries = 0; /* * Transfer a sector from/to disk. On this level, the * task does not change much with the direction of * data flow. * * XXX We want a track buffering scheme here. */ for (;;) { sHdr.side = fd->pos.side; sHdr.sector = fd->pos.sector; sHdr.track = fd->pos.track; (void)iwmSelectSide(fd->pos.side); iwmErr = readFlag ? iwmReadSector(buffer, &sHdr) : iwmWriteSector(buffer, &sHdr); /* Relies on lazy evaluation */ if (sHdr.sector == fd->pos.sector || iwmErr != 0 || IWM_MAX_FLOPPY_SECT < sectRetries++) break; } /* Check possible error conditions */ if ( /* DISABLED && */ TRACE_STRAT) printf("c%d_h%d_s%d_err(%d)_sr%d ", sHdr.track, sHdr.side >> 3, sHdr.sector, iwmErr, sectRetries); /* IWM IO error? */ if (iwmErr != 0) { state = state_IOErr; break; } /* Bad seek? Retry */ if (sHdr.track != fd->pos.track) { if (TRACE_STRAT) { printf("Wanted track %d, got %d, " \ "%d seek retries.\n", fd->pos.track, sHdr.track, seekRetries); } if (iwm->maxRetries > seekRetries++) { iwmErr = seek(&fd->pos, IWM_SEEK_RECAL); if (TRACE_STRAT) { printf("[%d]", seekRetries); (void)checkTrack(fd->pos, 1); } } else iwmErr = seekErr; state = (0 == iwmErr) ? state_DoIO : state_Fault; break; } /* Sector not found? */ if (sHdr.sector != fd->pos.sector) { if (TRACE_STRAT) printf("c%d_h%d_s%d " \ "sect not found, %d retries ", sHdr.track, sHdr.side >> 3, sHdr.sector, sectRetries); iwmErr = noAdrMkErr; state = state_Fault; break; } /* Success */ state = state_IOFinish; break; case state_IOFinish: /* Prepare for next block, if any */ if (TRACE_STRAT) printf("%s c%d_h%d_s%d ok ", taskDesc, sHdr.track, sHdr.side >> 3, sHdr.sector); fd->bytesDone += fd->currentType->sectorSize; fd->bytesLeft -= fd->currentType->sectorSize; buffer += fd->currentType->sectorSize; /* * Instead of recalculating the chs mapping for * each and every sector, check for * 'current sector# <= max sector#' and recalculate * after overflow. */ fd->startBlk++; if (fd->bytesLeft > 0) state = (++fd->pos.sector < fd->pos.maxSect) ? state_DoIO : state_Seek; else state = state_Exit; break; case state_IOErr: /* Bad IO, repeat */ #ifdef DIAGNOSTIC printf("iwm%sSector() err = %d, " \ "%d retries, on c%d_h%d_s%d.\n", taskDesc, iwmErr, ioRetries, fd->pos.track, fd->pos.side, fd->pos.sector); #endif /* XXX Do statistics */ state = ++ioRetries < iwm->maxRetries ? state_DoIO : state_Fault; break; case state_Fault: /* A non-recoverable error */ if (TRACE_STRAT) { printf("Seek retries %d, IO retries %d, " \ "only found c%d_h%d_s%d \n", seekRetries, ioRetries, sHdr.track, sHdr.side >> 3, sHdr.sector); printf("A non-recoverable error: %d ", iwmErr); } state = state_Exit; break; case state_Exit: /* We're done, for good or bad */ bp->b_resid = fd->bytesLeft; bp->b_error = (0 == iwmErr) ? 0 : EIO; if (iwmErr) bp->b_flags |= B_ERROR; if (TRACE_STRAT) { printf(" fdstart() finished job; " \ "iwmErr = %d, b_error = %d", iwmErr, bp->b_error); if (DISABLED) hexDump(bp->b_un.b_addr, bp->b_bcount); } /* * Remove requested buf from beginning of queue * and release it. * * XXX What happens here if we couldn't read/write * the buffer successfully? */ fd->bufQueue.b_actf = bp->b_actf; if (DISABLED && TRACE_STRAT) printf(" Next buf (bufQueue.b_actf) at %p\n", fd->bufQueue.b_actf); disk_unbusy(&fd->diskInfo, bp->b_bcount - bp->b_resid); biodone(bp); /* * Stop motor after 10s * * XXX Unloading the module while the timeout is still * running WILL crash the machine. */ timeout(motor_off, fd, 10 * hz); done = 1; break; } /* switch */ if (TRACE_STRAT) printf(".\n"); } while (!done); } /* * remap_geometry() * Remap the rigid UN*X view of a disk's cylinder/sector geometry * to our zone recorded real Sony drive by splitting the disk * into zones. * * Loop { * Look if logical block number is in current zone * NO: Add # of tracks for current zone to track counter * Process next zone * * YES: Subtract (number of first sector of current zone times heads) * from logical block number, then break up the difference * in tracks/side/sectors (spt is constant within a zone). * Done * } */ static void remap_geometry(block, heads, loc) daddr_t block; int heads; diskPosition_t *loc; { int zone, spt; extern diskZone_t diskZones[]; spt = 0; /* XXX Shut up egcs warning */ loc->oldTrack = loc->track; loc->track = 0; for (zone = 0; zone < IWM_GCR_DISK_ZONES; zone++) { if (block >= heads * (diskZones[zone].lastBlock + 1)) { /* Process full zones */ loc->track += diskZones[zone].tracks; } else { /* Process partial zone */ spt = diskZones[zone].sectPerTrack; block -= heads * diskZones[zone].firstBlock; loc->track += block / (spt * heads); loc->sector = (block % spt); loc->side = (block % (spt * heads)) / spt; break; } } loc->maxSect = spt; } /* * motor_off * * Callback for timeout() */ static void motor_off(param) void *param; { int spl; fd_softc_t *fd; fd = (fd_softc_t *)param; if (TRACE_STRAT) printf("iwm: Switching motor OFF (timeout).\n"); spl = splhigh(); (void)iwmMotor(fd->unit, 0); fd->state &= ~IWM_FD_MOTOR_ON; splx(spl); } /* * fdGetDiskLabel * * Set up disk label with parameters from current disk type. * Then call the generic disklabel read routine which tries to * read a label from disk and insert it. If it doesn't exist use * our defaults. */ static void fdGetDiskLabel(fd, dev) fd_softc_t *fd; dev_t dev; { char *msg; int fdType; struct disklabel *lp; struct cpu_disklabel *clp; if (TRACE_IOCTL) printf("iwm: fdGetDiskLabel() for disk %d.\n", minor(dev) / MAXPARTITIONS); fdType = minor(dev) % MAXPARTITIONS; lp = fd->diskInfo.dk_label; clp = fd->diskInfo.dk_cpulabel; bzero(lp, sizeof(struct disklabel)); bzero(clp, sizeof(struct cpu_disklabel)); /* * How to describe a drive with a variable # of sectors per * track (8..12) and variable rpm (300..550)? Apple came up * with ZBR in 1983! Un*x drive management sucks. */ lp->d_type = DTYPE_FLOPPY; lp->d_rpm = 300; lp->d_secsize = fd->currentType->sectorSize; lp->d_ntracks = fd->currentType->heads; lp->d_ncylinders = fd->currentType->tracks; lp->d_nsectors = fd->currentType->secPerTrack; lp->d_secpercyl = fd->currentType->secPerCyl; lp->d_secperunit = fd->currentType->secPerDisk; lp->d_interleave = fd->currentType->interleave; lp->d_trkseek = fd->currentType->stepRate * 1000; /* XXX usec */ strncpy(lp->d_typename, "floppy", sizeof(lp->d_typename)); strncpy(lp->d_packname, "fictitious", sizeof(lp->d_packname)); lp->d_npartitions = fdType + 1; lp->d_partitions[fdType].p_offset = 0; lp->d_partitions[fdType].p_size = lp->d_secperunit; lp->d_partitions[fdType].p_fstype = FS_BSDFFS; lp->d_partitions[fdType].p_fsize = 512; lp->d_partitions[fdType].p_frag = 8; lp->d_magic = DISKMAGIC; lp->d_magic2 = DISKMAGIC; lp->d_checksum = dkcksum(lp); /* * Call the generic disklabel extraction routine. If we don't * find a label on disk, keep our faked one. */ if (TRACE_OPEN) printf(" now calling readdisklabel()...\n"); msg = readdisklabel(dev, fdstrategy, lp, clp); if (msg == NULL) { strncpy(lp->d_packname, "default label", sizeof(lp->d_packname)); /* XXX - ?? */ } #ifdef DEBUG else printf("iwm: %s.\n", msg); #endif if (TRACE_OPEN) fdPrintDiskLabel(lp); } /* * getFDType * * return pointer to disk format description */ static fdInfo_t * getFDType(unit) short unit; { int driveFlags; fdInfo_t *thisType; extern fdInfo_t fdTypes[]; driveFlags = iwmCheckDrive(unit); /* * Drive flags are: Bit 0 - 1 = Drive is double sided * 1 - 1 = No disk inserted * 2 - 1 = Motor is off * 3 - 1 = Disk is writeable * 4 - 1 = Disk is DD (800/720K) * 31 - 1 = No drive / invalid drive # */ if (TRACE_CONFIG) { printf("iwm: Drive %d says 0x0%x (%d)\n", unit, driveFlags, driveFlags); } if (driveFlags < 0) thisType = NULL;/* no such drive */ else if (driveFlags & 0x01) thisType = &fdTypes[1]; /* double sided */ else thisType = &fdTypes[0]; /* single sided */ return thisType; } /* * fdDeviceToType * * maps the minor device number (elsewhere: partition type) to * a corresponding disk format. * This is currently: * fdXa default (800K GCR) * fdXb 400K GCR * fdXc 800K GCR */ static fdInfo_t * fdDeviceToType(fd, dev) fd_softc_t *fd; dev_t dev; { int type; fdInfo_t *thisInfo; /* XXX This broke with egcs 1.0.2 */ /* extern fdInfo_t fdTypes[]; */ type = minor(dev) % MAXPARTITIONS; /* 1,2,... */ if (type > sizeof(fdTypes) / sizeof(fdTypes[0])) thisInfo = NULL; else thisInfo = (type == 0) ? fd->defaultType : &fdTypes[type - 1]; return thisInfo; } /* * seek * * Step to given track; optionally restore to track zero before * and/or verify correct track. * Note that any necessary retries are done here. * We keep the current position on disk in a 'struct diskPosition'. */ static int seek(loc, style) diskPosition_t *loc; int style; { int state, done; int err, ierr; int seekRetries, verifyRetries; int steps; int spl; sectorHdr_t hdr; char action[32]; #ifndef _LKM iwm_softc_t *iwm = iwm_cd.cd_devs[0]; #endif char *stateDesc[] = { "Init", "Seek", "Recalibrate", "Verify", "Exit" }; enum { state_Init = 0, state_Seek, state_Recalibrate, state_Verify, state_Exit }; done = err = ierr = seekRetries = verifyRetries = 0; /* XXX egcs */ state = state_Init; do { if (TRACE_STEP) printf(" seek state %d [%s].\n", state, stateDesc[state]); switch (state) { case state_Init: if (TRACE_STEP) printf("Current track is %d, new track %d.\n", loc->oldTrack, loc->track); bzero(&hdr, sizeof(hdr)); err = ierr = 0; seekRetries = verifyRetries = 0; state = (style == IWM_SEEK_RECAL) ? state_Recalibrate : state_Seek; done = 0; break; case state_Recalibrate: ierr = iwmTrack00(); if (ierr == 0) { loc->oldTrack = 0; state = state_Seek; } else { strncpy(action, "Recalibrate (track 0)", sizeof(action)); state = state_Exit; } break; case state_Seek: ierr = 0; steps = loc->track - loc->oldTrack; if (steps != 0) ierr = iwmSeek(steps); if (ierr == 0) { /* No error or nothing to do */ state = (style == IWM_SEEK_VERIFY) ? state_Verify : state_Exit; } else { if (++seekRetries < iwm->maxRetries) state = state_Recalibrate; else { strncpy(action, "Seek retries", sizeof(action)); state = state_Exit; } } break; case state_Verify: spl = splhigh(); iwmSelectSide(loc->side); ierr = iwmReadSectHdr(&hdr); splx(spl); if (ierr == 0 && loc->track == hdr.track) state = state_Exit; else { if (++verifyRetries < iwm->maxRetries) state = state_Recalibrate; else { strncpy(action, "Verify retries", sizeof(action)); state = state_Exit; } } break; case state_Exit: if (ierr == 0) { loc->oldTrack = loc->track; err = 0; /* Give the head some time to settle down */ delay(3000); } else { #ifdef DIAGNOSTIC printf(" seek() action \"%s\", err = %d.\n", action, ierr); #endif err = EIO; } done = 1; break; } } while (!done); return err; } /* * checkTrack * * After positioning, get a sector header for validation */ static int checkTrack(loc, debugFlag) diskPosition_t loc; int debugFlag; { int spl; int iwmErr; sectorHdr_t hdr; spl = splhigh(); iwmSelectSide(loc.side); iwmErr = iwmReadSectHdr(&hdr); splx(spl); if (debugFlag) { printf("Seeked for %d, got at %d, Hdr read err %d.\n", loc.track, hdr.track, iwmErr); } return iwmErr; } /* Debugging stuff */ static void hexDump(buf, len) u_char *buf; int len; { int i, j; u_char ch; printf("\nDump %d from %p:\n", len, buf); i = j = 0; if (NULL != buf) do { printf("%04x: ", i); for (j = 0; j < 8; j++) printf("%02x ", buf[i + j]); printf(" "); for (j = 8; j < 16; j++) printf("%02x ", buf[i + j]); printf(" "); for (j = 0; j < 16; j++) { ch = buf[i + j]; if (ch > 31 && ch < 127) printf("%c", ch); else printf("."); } printf("\n"); i += 16; } while (len > i); } static void fdPrintDiskLabel(lp) struct disklabel *lp; { int i; printf("iwm: Disklabel entries of current floppy.\n"); printf("\t d_type:\t%d (%s)\n", lp->d_type, fstypenames[lp->d_type]); printf("\t d_typename:\t%s\n", lp->d_typename); printf("\t d_packname:\t%s\n", lp->d_packname); printf("\t d_secsize:\t%d\n", lp->d_secsize); printf("\t d_nsectors:\t%d\n", lp->d_nsectors); printf("\t d_ntracks:\t%d\n", lp->d_ntracks); printf("\t d_ncylinders:\t%d\n", lp->d_ncylinders); printf("\t d_secpercyl:\t%d\n", lp->d_secpercyl); printf("\t d_secperunit:\t%d\n", lp->d_secperunit); printf("\t d_rpm: \t%d\n", lp->d_rpm); printf("\t d_interleave:\t%d\n", lp->d_interleave); printf("\t d_trkseek:\t%d [us]\n", lp->d_trkseek); printf(" d_npartitions:\t%d\n", lp->d_npartitions); for (i = 0; i < lp->d_npartitions; i++) { printf("\t d_partitions[%d].p_offset:\t%d\n", i, lp->d_partitions[i].p_offset); printf("\t d_partitions[%d].p_size:\t%d\n", i, lp->d_partitions[i].p_size); printf("\t d_partitions[%d].p_fstype:\t%d (%s)\n", i, lp->d_partitions[i].p_fstype, fstypenames[lp->d_partitions[i].p_fstype]); printf("\t d_partitions[%d].p_frag:\t%d\n", i, lp->d_partitions[i].p_frag); printf("\n"); } }