/* $NetBSD: rl.c,v 1.19 2003/04/02 20:38:28 he Exp $ */ /* * Copyright (c) 2000 Ludd, University of Lule}, Sweden. 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. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed at Ludd, University of * Lule}, Sweden and its contributors. * 4. 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. */ /* * RL11/RLV11/RLV12 disk controller driver and * RL01/RL02 disk device driver. * * TODO: * Handle disk errors more gracefully * Do overlapping seeks on multiple drives * * Implementation comments: * */ #include __KERNEL_RCSID(0, "$NetBSD: rl.c,v 1.19 2003/04/02 20:38:28 he Exp $"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "ioconf.h" #include "locators.h" static int rlcmatch(struct device *, struct cfdata *, void *); static void rlcattach(struct device *, struct device *, void *); static int rlcprint(void *, const char *); static void rlcintr(void *); static int rlmatch(struct device *, struct cfdata *, void *); static void rlattach(struct device *, struct device *, void *); static void rlcstart(struct rlc_softc *, struct buf *); static void waitcrdy(struct rlc_softc *); static void rlcreset(struct device *); CFATTACH_DECL(rlc, sizeof(struct rlc_softc), rlcmatch, rlcattach, NULL, NULL); CFATTACH_DECL(rl, sizeof(struct rl_softc), rlmatch, rlattach, NULL, NULL); dev_type_open(rlopen); dev_type_close(rlclose); dev_type_read(rlread); dev_type_write(rlwrite); dev_type_ioctl(rlioctl); dev_type_strategy(rlstrategy); dev_type_dump(rldump); dev_type_size(rlsize); const struct bdevsw rl_bdevsw = { rlopen, rlclose, rlstrategy, rlioctl, rldump, rlsize, D_DISK }; const struct cdevsw rl_cdevsw = { rlopen, rlclose, rlread, rlwrite, rlioctl, nostop, notty, nopoll, nommap, nokqfilter, D_DISK }; #define MAXRLXFER (RL_BPS * RL_SPT) #define RL_WREG(reg, val) \ bus_space_write_2(sc->sc_iot, sc->sc_ioh, (reg), (val)) #define RL_RREG(reg) \ bus_space_read_2(sc->sc_iot, sc->sc_ioh, (reg)) static char *rlstates[] = { "drive not loaded", "drive spinning up", "drive brushes out", "drive loading heads", "drive seeking", "drive ready", "drive unloading heads", "drive spun down", }; static char * rlstate(struct rlc_softc *sc, int unit) { int i = 0; do { RL_WREG(RL_DA, RLDA_GS); RL_WREG(RL_CS, RLCS_GS|(unit << RLCS_USHFT)); waitcrdy(sc); } while (((RL_RREG(RL_CS) & RLCS_ERR) != 0) && i++ < 10); if (i == 10) return NULL; i = RL_RREG(RL_MP) & RLMP_STATUS; return rlstates[i]; } void waitcrdy(struct rlc_softc *sc) { int i; for (i = 0; i < 1000; i++) { DELAY(10000); if (RL_RREG(RL_CS) & RLCS_CRDY) return; } printf("%s: never got ready\n", sc->sc_dev.dv_xname); /* ?panic? */ } int rlcprint(void *aux, const char *name) { struct rlc_attach_args *ra = aux; if (name) aprint_normal("RL0%d at %s", ra->type & RLMP_DT ? '2' : '1', name); aprint_normal(" drive %d", ra->hwid); return UNCONF; } /* * Force the controller to interrupt. */ int rlcmatch(struct device *parent, struct cfdata *cf, void *aux) { struct uba_attach_args *ua = aux; struct rlc_softc ssc, *sc = &ssc; int i; sc->sc_iot = ua->ua_iot; sc->sc_ioh = ua->ua_ioh; /* Force interrupt by issuing a "Get Status" command */ RL_WREG(RL_DA, RLDA_GS); RL_WREG(RL_CS, RLCS_GS|RLCS_IE); for (i = 0; i < 100; i++) { DELAY(100000); if (RL_RREG(RL_CS) & RLCS_CRDY) return 1; } return 0; } void rlcattach(struct device *parent, struct device *self, void *aux) { struct rlc_softc *sc = (struct rlc_softc *)self; struct uba_attach_args *ua = aux; struct rlc_attach_args ra; int i, error; sc->sc_iot = ua->ua_iot; sc->sc_ioh = ua->ua_ioh; sc->sc_dmat = ua->ua_dmat; uba_intr_establish(ua->ua_icookie, ua->ua_cvec, rlcintr, sc, &sc->sc_intrcnt); evcnt_attach_dynamic(&sc->sc_intrcnt, EVCNT_TYPE_INTR, ua->ua_evcnt, sc->sc_dev.dv_xname, "intr"); uba_reset_establish(rlcreset, self); printf("\n"); /* * The RL11 can only have one transfer going at a time, * and max transfer size is one track, so only one dmamap * is needed. */ error = bus_dmamap_create(sc->sc_dmat, MAXRLXFER, 1, MAXRLXFER, 0, BUS_DMA_ALLOCNOW, &sc->sc_dmam); if (error) { printf(": Failed to allocate DMA map, error %d\n", error); return; } bufq_alloc(&sc->sc_q, BUFQ_DISKSORT|BUFQ_SORT_CYLINDER); for (i = 0; i < RL_MAXDPC; i++) { waitcrdy(sc); RL_WREG(RL_DA, RLDA_GS|RLDA_RST); RL_WREG(RL_CS, RLCS_GS|(i << RLCS_USHFT)); waitcrdy(sc); ra.type = RL_RREG(RL_MP); ra.hwid = i; if ((RL_RREG(RL_CS) & RLCS_ERR) == 0) config_found(&sc->sc_dev, &ra, rlcprint); } } int rlmatch(struct device *parent, struct cfdata *cf, void *aux) { struct rlc_attach_args *ra = aux; if (cf->cf_loc[RLCCF_DRIVE] != RLCCF_DRIVE_DEFAULT && cf->cf_loc[RLCCF_DRIVE] != ra->hwid) return 0; return 1; } void rlattach(struct device *parent, struct device *self, void *aux) { struct rl_softc *rc = (struct rl_softc *)self; struct rlc_attach_args *ra = aux; struct disklabel *dl; rc->rc_hwid = ra->hwid; rc->rc_disk.dk_name = rc->rc_dev.dv_xname; disk_attach(&rc->rc_disk); dl = rc->rc_disk.dk_label; dl->d_npartitions = 3; strcpy(dl->d_typename, "RL01"); if (ra->type & RLMP_DT) dl->d_typename[3] = '2'; dl->d_secsize = DEV_BSIZE; /* XXX - wrong, but OK for now */ dl->d_nsectors = RL_SPT/2; dl->d_ntracks = RL_SPD; dl->d_ncylinders = ra->type & RLMP_DT ? RL_TPS02 : RL_TPS01; dl->d_secpercyl = dl->d_nsectors * dl->d_ntracks; dl->d_secperunit = dl->d_ncylinders * dl->d_secpercyl; dl->d_partitions[0].p_size = dl->d_partitions[2].p_size = dl->d_secperunit; dl->d_partitions[0].p_offset = dl->d_partitions[2].p_offset = 0; dl->d_interleave = dl->d_headswitch = 1; dl->d_bbsize = BBSIZE; dl->d_sbsize = SBLOCKSIZE; dl->d_rpm = 2400; dl->d_type = DTYPE_DEC; printf(": %s, %s\n", dl->d_typename, rlstate((struct rlc_softc *)parent, ra->hwid)); } int rlopen(dev_t dev, int flag, int fmt, struct proc *p) { int part, unit, mask; struct disklabel *dl; struct rlc_softc *sc; struct rl_softc *rc; char *msg; /* * Make sure this is a reasonable open request. */ unit = DISKUNIT(dev); if (unit >= rl_cd.cd_ndevs) return ENXIO; rc = rl_cd.cd_devs[unit]; if (rc == 0) return ENXIO; sc = (struct rlc_softc *)rc->rc_dev.dv_parent; /* Check that the disk actually is useable */ msg = rlstate(sc, rc->rc_hwid); if (msg == NULL || msg == rlstates[RLMP_UNLOAD] || msg == rlstates[RLMP_SPUNDOWN]) return ENXIO; /* * If this is the first open; read in where on the disk we are. */ dl = rc->rc_disk.dk_label; if (rc->rc_state == DK_CLOSED) { u_int16_t mp; int maj; RL_WREG(RL_CS, RLCS_RHDR|(rc->rc_hwid << RLCS_USHFT)); waitcrdy(sc); mp = RL_RREG(RL_MP); rc->rc_head = ((mp & RLMP_HS) == RLMP_HS); rc->rc_cyl = (mp >> 7) & 0777; rc->rc_state = DK_OPEN; /* Get disk label */ printf("%s: ", rc->rc_dev.dv_xname); maj = cdevsw_lookup_major(&rl_cdevsw); if ((msg = readdisklabel(MAKEDISKDEV(maj, rc->rc_dev.dv_unit, RAW_PART), rlstrategy, dl, NULL))) printf("%s: ", msg); printf("size %d sectors\n", dl->d_secperunit); } part = DISKPART(dev); if (part >= dl->d_npartitions) return ENXIO; mask = 1 << part; switch (fmt) { case S_IFCHR: rc->rc_disk.dk_copenmask |= mask; break; case S_IFBLK: rc->rc_disk.dk_bopenmask |= mask; break; } rc->rc_disk.dk_openmask |= mask; return 0; } int rlclose(dev_t dev, int flag, int fmt, struct proc *p) { int unit = DISKUNIT(dev); struct rl_softc *rc = rl_cd.cd_devs[unit]; int mask = (1 << DISKPART(dev)); switch (fmt) { case S_IFCHR: rc->rc_disk.dk_copenmask &= ~mask; break; case S_IFBLK: rc->rc_disk.dk_bopenmask &= ~mask; break; } rc->rc_disk.dk_openmask = rc->rc_disk.dk_copenmask | rc->rc_disk.dk_bopenmask; if (rc->rc_disk.dk_openmask == 0) rc->rc_state = DK_CLOSED; /* May change pack */ return 0; } void rlstrategy(struct buf *bp) { struct disklabel *lp; struct rlc_softc *sc; struct rl_softc *rc; int unit, s, err; /* * Make sure this is a reasonable drive to use. */ unit = DISKUNIT(bp->b_dev); if (unit > rl_cd.cd_ndevs || (rc = rl_cd.cd_devs[unit]) == NULL) { bp->b_error = ENXIO; bp->b_flags |= B_ERROR; goto done; } if (rc->rc_state != DK_OPEN) /* How did we end up here at all? */ panic("rlstrategy: state impossible"); lp = rc->rc_disk.dk_label; if ((err = bounds_check_with_label(bp, lp, 1)) <= 0) goto done; if (bp->b_bcount == 0) goto done; bp->b_rawblkno = bp->b_blkno + lp->d_partitions[DISKPART(bp->b_dev)].p_offset; bp->b_cylinder = bp->b_rawblkno / lp->d_secpercyl; sc = (struct rlc_softc *)rc->rc_dev.dv_parent; s = splbio(); BUFQ_PUT(&sc->sc_q, bp); rlcstart(sc, 0); splx(s); return; done: biodone(bp); } int rlioctl(dev_t dev, u_long cmd, caddr_t addr, int flag, struct proc *p) { struct rl_softc *rc = rl_cd.cd_devs[DISKUNIT(dev)]; struct disklabel *lp = rc->rc_disk.dk_label; int err = 0; #ifdef __HAVE_OLD_DISKLABEL struct disklabel newlabel; #endif switch (cmd) { case DIOCGDINFO: bcopy(lp, addr, sizeof (struct disklabel)); break; #ifdef __HAVE_OLD_DISKLABEL case ODIOCGDINFO: newlabel = *lp; if (newlabel.d_npartitions > OLDMAXPARTITIONS) return ENOTTY; bcopy(&newlabel, addr, sizeof (struct olddisklabel)); break; #endif case DIOCGPART: ((struct partinfo *)addr)->disklab = lp; ((struct partinfo *)addr)->part = &lp->d_partitions[DISKPART(dev)]; break; case DIOCSDINFO: case DIOCWDINFO: #ifdef __HAVE_OLD_DISKLABEL case ODIOCWDINFO: case ODIOCSDINFO: #endif { struct disklabel *tp; #ifdef __HAVE_OLD_DISKLABEL if (cmd == ODIOCSDINFO || cmd == ODIOCWDINFO) { memset(&newlabel, 0, sizeof newlabel); memcpy(&newlabel, addr, sizeof (struct olddisklabel)); tp = &newlabel; } else #endif tp = (struct disklabel *)addr; if ((flag & FWRITE) == 0) err = EBADF; else err = (( #ifdef __HAVE_OLD_DISKLABEL cmd == ODIOCSDINFO || #endif cmd == DIOCSDINFO) ? setdisklabel(lp, tp, 0, 0) : writedisklabel(dev, rlstrategy, lp, 0)); break; } case DIOCWLABEL: if ((flag & FWRITE) == 0) err = EBADF; break; default: err = ENOTTY; } return err; } int rlsize(dev_t dev) { struct disklabel *dl; struct rl_softc *rc; int size, unit = DISKUNIT(dev); if ((unit >= rl_cd.cd_ndevs) || ((rc = rl_cd.cd_devs[unit]) == 0)) return -1; dl = rc->rc_disk.dk_label; size = dl->d_partitions[DISKPART(dev)].p_size * (dl->d_secsize / DEV_BSIZE); return size; } int rldump(dev_t dev, daddr_t blkno, caddr_t va, size_t size) { /* Not likely... */ return 0; } int rlread(dev_t dev, struct uio *uio, int ioflag) { return (physio(rlstrategy, NULL, dev, B_READ, minphys, uio)); } int rlwrite(dev_t dev, struct uio *uio, int ioflag) { return (physio(rlstrategy, NULL, dev, B_WRITE, minphys, uio)); } static char *rlerr[] = { "no", "operation incomplete", "read data CRC", "header CRC", "data late", "header not found", "", "", "non-existent memory", "memory parity error", "", "", "", "", "", "", }; void rlcintr(void *arg) { struct rlc_softc *sc = arg; struct buf *bp; u_int16_t cs; bp = sc->sc_active; if (bp == 0) { printf("%s: strange interrupt\n", sc->sc_dev.dv_xname); return; } bus_dmamap_unload(sc->sc_dmat, sc->sc_dmam); sc->sc_active = 0; cs = RL_RREG(RL_CS); if (cs & RLCS_ERR) { int error = (cs & RLCS_ERRMSK) >> 10; printf("%s: %s\n", sc->sc_dev.dv_xname, rlerr[error]); bp->b_flags |= B_ERROR; bp->b_error = EIO; bp->b_resid = bp->b_bcount; sc->sc_bytecnt = 0; } if (sc->sc_bytecnt == 0) /* Finished transfer */ biodone(bp); rlcstart(sc, sc->sc_bytecnt ? bp : 0); } /* * Start routine. First position the disk to the given position, * then start reading/writing. An optimization would be to be able * to handle overlapping seeks between disks. */ void rlcstart(struct rlc_softc *sc, struct buf *ob) { struct disklabel *lp; struct rl_softc *rc; struct buf *bp; int bn, cn, sn, tn, blks, err; if (sc->sc_active) return; /* Already doing something */ if (ob == 0) { bp = BUFQ_GET(&sc->sc_q); if (bp == NULL) return; /* Nothing to do */ sc->sc_bufaddr = bp->b_data; sc->sc_diskblk = bp->b_rawblkno; sc->sc_bytecnt = bp->b_bcount; bp->b_resid = 0; } else bp = ob; sc->sc_active = bp; rc = rl_cd.cd_devs[DISKUNIT(bp->b_dev)]; bn = sc->sc_diskblk; lp = rc->rc_disk.dk_label; if (bn) { cn = bn / lp->d_secpercyl; sn = bn % lp->d_secpercyl; tn = sn / lp->d_nsectors; sn = sn % lp->d_nsectors; } else cn = sn = tn = 0; /* * Check if we have to position disk first. */ if (rc->rc_cyl != cn || rc->rc_head != tn) { u_int16_t da = RLDA_SEEK; if (cn > rc->rc_cyl) da |= ((cn - rc->rc_cyl) << RLDA_CYLSHFT) | RLDA_DIR; else da |= ((rc->rc_cyl - cn) << RLDA_CYLSHFT); if (tn) da |= RLDA_HSSEEK; waitcrdy(sc); RL_WREG(RL_DA, da); RL_WREG(RL_CS, RLCS_SEEK | (rc->rc_hwid << RLCS_USHFT)); waitcrdy(sc); rc->rc_cyl = cn; rc->rc_head = tn; } RL_WREG(RL_DA, (cn << RLDA_CYLSHFT) | (tn ? RLDA_HSRW : 0) | (sn << 1)); blks = sc->sc_bytecnt/DEV_BSIZE; if (sn + blks > RL_SPT/2) blks = RL_SPT/2 - sn; RL_WREG(RL_MP, -(blks*DEV_BSIZE)/2); err = bus_dmamap_load(sc->sc_dmat, sc->sc_dmam, sc->sc_bufaddr, (blks*DEV_BSIZE), (bp->b_flags & B_PHYS ? bp->b_proc : 0), BUS_DMA_NOWAIT); if (err) panic("%s: bus_dmamap_load failed: %d", sc->sc_dev.dv_xname, err); RL_WREG(RL_BA, (sc->sc_dmam->dm_segs[0].ds_addr & 0xffff)); /* Count up vars */ sc->sc_bufaddr += (blks*DEV_BSIZE); sc->sc_diskblk += blks; sc->sc_bytecnt -= (blks*DEV_BSIZE); if (bp->b_flags & B_READ) RL_WREG(RL_CS, RLCS_IE|RLCS_RD|(rc->rc_hwid << RLCS_USHFT)); else RL_WREG(RL_CS, RLCS_IE|RLCS_WD|(rc->rc_hwid << RLCS_USHFT)); } /* * Called once per controller when an ubareset occurs. * Retracts all disks and restarts active transfers. */ void rlcreset(struct device *dev) { struct rlc_softc *sc = (struct rlc_softc *)dev; struct rl_softc *rc; int i; u_int16_t mp; for (i = 0; i < rl_cd.cd_ndevs; i++) { if ((rc = rl_cd.cd_devs[i]) == NULL) continue; if (rc->rc_state != DK_OPEN) continue; printf(" %s", rc->rc_dev.dv_xname); RL_WREG(RL_CS, RLCS_RHDR|(rc->rc_hwid << RLCS_USHFT)); waitcrdy(sc); mp = RL_RREG(RL_MP); rc->rc_head = ((mp & RLMP_HS) == RLMP_HS); rc->rc_cyl = (mp >> 7) & 0777; } if (sc->sc_active == 0) return; BUFQ_PUT(&sc->sc_q, sc->sc_active); sc->sc_active = 0; rlcstart(sc, 0); }