NetBSD/sys/dev/qbus/rl.c
jdolecek e0cc03a09b merge kqueue branch into -current
kqueue provides a stateful and efficient event notification framework
currently supported events include socket, file, directory, fifo,
pipe, tty and device changes, and monitoring of processes and signals

kqueue is supported by all writable filesystems in NetBSD tree
(with exception of Coda) and all device drivers supporting poll(2)

based on work done by Jonathan Lemon for FreeBSD
initial NetBSD port done by Luke Mewburn and Jason Thorpe
2002-10-23 09:10:23 +00:00

669 lines
16 KiB
C

/* $NetBSD: rl.c,v 1.17 2002/10/23 09:13:37 jdolecek 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 <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: rl.c,v 1.17 2002/10/23 09:13:37 jdolecek Exp $");
#include <sys/param.h>
#include <sys/device.h>
#include <sys/systm.h>
#include <sys/conf.h>
#include <sys/disk.h>
#include <sys/disklabel.h>
#include <sys/buf.h>
#include <sys/stat.h>
#include <sys/dkio.h>
#include <sys/fcntl.h>
#include <sys/event.h>
#include <ufs/ufs/dinode.h>
#include <ufs/ffs/fs.h>
#include <machine/bus.h>
#include <dev/qbus/ubavar.h>
#include <dev/qbus/rlreg.h>
#include <dev/qbus/rlvar.h>
#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)
printf("RL0%d at %s", ra->type & RLMP_DT ? '2' : '1', name);
printf(" 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 = SBSIZE;
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);
}