/* $NetBSD: rl.c,v 1.51 2017/05/22 17:22:29 ragge 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. * * 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.51 2017/05/22 17:22:29 ragge Exp $"); #include #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(device_t, cfdata_t, void *); static void rlcattach(device_t, device_t, void *); static int rlcprint(void *, const char *); static void rlcintr(void *); static int rlmatch(device_t, cfdata_t, void *); static void rlattach(device_t, device_t, void *); static void rlcstart(struct rlc_softc *, struct buf *); static void waitcrdy(struct rlc_softc *); static void rlcreset(device_t); CFATTACH_DECL_NEW(rlc, sizeof(struct rlc_softc), rlcmatch, rlcattach, NULL, NULL); CFATTACH_DECL_NEW(rl, sizeof(struct rl_softc), rlmatch, rlattach, NULL, NULL); static dev_type_open(rlopen); static dev_type_close(rlclose); static dev_type_read(rlread); static dev_type_write(rlwrite); static dev_type_ioctl(rlioctl); static dev_type_strategy(rlstrategy); static dev_type_dump(rldump); static dev_type_size(rlpsize); const struct bdevsw rl_bdevsw = { .d_open = rlopen, .d_close = rlclose, .d_strategy = rlstrategy, .d_ioctl = rlioctl, .d_dump = rldump, .d_psize = rlpsize, .d_discard = nodiscard, .d_flag = D_DISK }; const struct cdevsw rl_cdevsw = { .d_open = rlopen, .d_close = rlclose, .d_read = rlread, .d_write = rlwrite, .d_ioctl = rlioctl, .d_stop = nostop, .d_tty = notty, .d_poll = nopoll, .d_mmap = nommap, .d_kqfilter = nokqfilter, .d_discard = nodiscard, .d_flag = 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 const char * const rlstates[] = { "drive not loaded", "drive spinning up", "drive brushes out", "drive loading heads", "drive seeking", "drive ready", "drive unloading heads", "drive spun down", }; static const struct dkdriver rldkdriver = { .d_strategy = rlstrategy, .d_minphys = minphys }; static const 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; } aprint_error_dev(sc->sc_dev, "never got ready\n"); /* ?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(device_t parent, cfdata_t 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(device_t parent, device_t self, void *aux) { struct rlc_softc *sc = device_private(self); struct uba_attach_args *ua = aux; struct rlc_attach_args ra; int i, error; sc->sc_dev = self; sc->sc_uh = device_private(parent); 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, device_xname(sc->sc_dev), "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) { aprint_error(": Failed to allocate DMA map, error %d\n", error); return; } bufq_alloc(&sc->sc_q, "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(device_t parent, cfdata_t 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(device_t parent, device_t self, void *aux) { struct rl_softc *rc = device_private(self); struct rlc_attach_args *ra = aux; struct disklabel *dl; rc->rc_dev = self; rc->rc_rlc = device_private(parent); rc->rc_hwid = ra->hwid; disk_init(&rc->rc_disk, device_xname(rc->rc_dev), &rldkdriver); 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 = DKTYPE_DEC; printf(": %s, %s\n", dl->d_typename, rlstate(rc->rc_rlc, ra->hwid)); /* * XXX We should try to discovery wedges here, but * XXX that would mean loading up the pack and being * XXX able to do I/O. Should use config_defer() here. */ } int rlopen(dev_t dev, int flag, int fmt, struct lwp *l) { struct rl_softc * const rc = device_lookup_private(&rl_cd, DISKUNIT(dev)); struct rlc_softc *sc; int error, part, mask; struct disklabel *dl; const char *msg; /* * Make sure this is a reasonable open request. */ if (rc == NULL) return ENXIO; sc = rc->rc_rlc; part = DISKPART(dev); mutex_enter(&rc->rc_disk.dk_openlock); /* * If there are wedges, and this is not RAW_PART, then we * need to fail. */ if (rc->rc_disk.dk_nwedges != 0 && part != RAW_PART) { error = EBUSY; goto bad1; } /* Check that the disk actually is useable */ msg = rlstate(sc, rc->rc_hwid); if (msg == NULL || msg == rlstates[RLMP_UNLOAD] || msg == rlstates[RLMP_SPUNDOWN]) { error = ENXIO; goto bad1; } /* * 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 */ maj = cdevsw_lookup_major(&rl_cdevsw); if ((msg = readdisklabel(MAKEDISKDEV(maj, device_unit(rc->rc_dev), RAW_PART), rlstrategy, dl, NULL))) aprint_normal_dev(rc->rc_dev, "%s", msg); aprint_normal_dev(rc->rc_dev, "size %d sectors\n", dl->d_secperunit); } if (part >= dl->d_npartitions) { error = ENXIO; goto bad1; } 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; error = 0; bad1: mutex_exit(&rc->rc_disk.dk_openlock); return (error); } int rlclose(dev_t dev, int flag, int fmt, struct lwp *l) { int unit = DISKUNIT(dev); struct rl_softc *rc = device_lookup_private(&rl_cd, unit); int mask = (1 << DISKPART(dev)); mutex_enter(&rc->rc_disk.dk_openlock); 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 */ mutex_exit(&rc->rc_disk.dk_openlock); return 0; } void rlstrategy(struct buf *bp) { struct rl_softc * const rc = device_lookup_private(&rl_cd, DISKUNIT(bp->b_dev)); struct disklabel *lp; int s; if (rc == NULL || rc->rc_state != DK_OPEN) /* How did we end up here at all? */ panic("rlstrategy: state impossible"); lp = rc->rc_disk.dk_label; if (bounds_check_with_label(&rc->rc_disk, bp, 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; s = splbio(); bufq_put(rc->rc_rlc->sc_q, bp); rlcstart(rc->rc_rlc, 0); splx(s); return; done: biodone(bp); } int rlioctl(dev_t dev, u_long cmd, void *addr, int flag, struct lwp *l) { struct rl_softc *rc = device_lookup_private(&rl_cd, DISKUNIT(dev)); struct disklabel *lp = rc->rc_disk.dk_label; int error; #ifdef __HAVE_OLD_DISKLABEL struct disklabel newlabel; #endif error = disk_ioctl(&rc->rc_disk, dev, cmd, addr, flag, l); if (error != EPASSTHROUGH) return error; else error = 0; switch (cmd) { 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) error = EBADF; else { mutex_enter(&rc->rc_disk.dk_openlock); error = (( #ifdef __HAVE_OLD_DISKLABEL cmd == ODIOCSDINFO || #endif cmd == DIOCSDINFO) ? setdisklabel(lp, tp, 0, 0) : writedisklabel(dev, rlstrategy, lp, 0)); mutex_exit(&rc->rc_disk.dk_openlock); } break; } case DIOCWLABEL: if ((flag & FWRITE) == 0) error = EBADF; break; default: error = ENOTTY; break; } return error; } int rlpsize(dev_t dev) { struct rl_softc * const rc = device_lookup_private(&rl_cd, DISKUNIT(dev)); struct disklabel *dl; int size; if (rc == NULL) 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, void *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 const char * const 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) { aprint_error_dev(sc->sc_dev, "strange interrupt\n"); 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; aprint_error_dev(sc->sc_dev, "%s\n", rlerr[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 = device_lookup_private(&rl_cd, 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", device_xname(sc->sc_dev), err); RL_WREG(RL_BA, (sc->sc_dmam->dm_segs[0].ds_addr & 0xffff)); /* Count up vars */ sc->sc_bufaddr = (char *)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(device_t dev) { struct rlc_softc *sc = device_private(dev); struct rl_softc *rc; int i; u_int16_t mp; for (i = 0; i < rl_cd.cd_ndevs; i++) { if ((rc = device_lookup_private(&rl_cd, i)) == NULL) continue; if (rc->rc_state != DK_OPEN) continue; if (rc->rc_rlc != sc) continue; 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); }