NetBSD/sys/dev/mca/ed_mca.c
2003-06-29 22:28:00 +00:00

777 lines
18 KiB
C

/* $NetBSD: ed_mca.c,v 1.22 2003/06/29 22:30:22 fvdl Exp $ */
/*
* Copyright (c) 2001 The NetBSD Foundation, Inc.
*
* This code is derived from software contributed to The NetBSD Foundation
* by Jaromir Dolecek.
*
* 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 by the NetBSD
* Foundation, Inc. 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.
*/
/*
* Disk drive goo for MCA ESDI controller driver.
*/
#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: ed_mca.c,v 1.22 2003/06/29 22:30:22 fvdl Exp $");
#include "rnd.h"
#include "locators.h"
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/kernel.h>
#include <sys/conf.h>
#include <sys/file.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <sys/buf.h>
#include <sys/uio.h>
#include <sys/malloc.h>
#include <sys/device.h>
#include <sys/disklabel.h>
#include <sys/disk.h>
#include <sys/syslog.h>
#include <sys/proc.h>
#include <sys/vnode.h>
#if NRND > 0
#include <sys/rnd.h>
#endif
#include <machine/intr.h>
#include <machine/bus.h>
#include <dev/mca/mcavar.h>
#include <dev/mca/edcreg.h>
#include <dev/mca/edvar.h>
#include <dev/mca/edcvar.h>
/* #define WDCDEBUG */
#ifdef WDCDEBUG
#define WDCDEBUG_PRINT(args, level) printf args
#else
#define WDCDEBUG_PRINT(args, level)
#endif
#define EDLABELDEV(dev) (MAKEDISKDEV(major(dev), DISKUNIT(dev), RAW_PART))
static int ed_mca_probe __P((struct device *, struct cfdata *, void *));
static void ed_mca_attach __P((struct device *, struct device *, void *));
CFATTACH_DECL(ed_mca, sizeof(struct ed_softc),
ed_mca_probe, ed_mca_attach, NULL, NULL);
extern struct cfdriver ed_cd;
static int ed_get_params __P((struct ed_softc *, int *));
static int ed_lock __P((struct ed_softc *));
static void ed_unlock __P((struct ed_softc *));
static void edgetdisklabel __P((dev_t, struct ed_softc *));
static void edgetdefaultlabel __P((struct ed_softc *, struct disklabel *));
dev_type_open(edmcaopen);
dev_type_close(edmcaclose);
dev_type_read(edmcaread);
dev_type_write(edmcawrite);
dev_type_ioctl(edmcaioctl);
dev_type_strategy(edmcastrategy);
dev_type_dump(edmcadump);
dev_type_size(edmcasize);
const struct bdevsw ed_bdevsw = {
edmcaopen, edmcaclose, edmcastrategy, edmcaioctl,
edmcadump, edmcasize, D_DISK
};
const struct cdevsw ed_cdevsw = {
edmcaopen, edmcaclose, edmcaread, edmcawrite, edmcaioctl,
nostop, notty, nopoll, nommap, nokqfilter, D_DISK
};
static struct dkdriver eddkdriver = { edmcastrategy };
/*
* Just check if it's possible to identify the disk.
*/
static int
ed_mca_probe(parent, cf, aux)
struct device *parent;
struct cfdata *cf;
void *aux;
{
u_int16_t cmd_args[2];
struct edc_mca_softc *sc = (void *) parent;
struct ed_attach_args *eda = (struct ed_attach_args *) aux;
int found = 1;
/*
* Check we match hardwired config.
*/
if (cf->edccf_unit != EDCCF_DRIVE_DEFAULT &&
cf->edccf_unit != eda->edc_drive)
return (0);
/*
* Get Device Configuration (09).
*/
cmd_args[0] = 14; /* Options: 00s110, s: 0=Physical 1=Pseudo */
cmd_args[1] = 0;
if (edc_run_cmd(sc, CMD_GET_DEV_CONF, eda->edc_drive, cmd_args, 2, 1))
found = 0;
return (found);
}
static void
ed_mca_attach(parent, self, aux)
struct device *parent, *self;
void *aux;
{
struct ed_softc *ed = (void *) self;
struct edc_mca_softc *sc = (void *) parent;
struct ed_attach_args *eda = (struct ed_attach_args *) aux;
char pbuf[8], lckname[10];
int drv_flags;
ed->edc_softc = sc;
ed->sc_devno = eda->edc_drive;
edc_add_disk(sc, ed);
bufq_alloc(&ed->sc_q, BUFQ_DISKSORT|BUFQ_SORT_RAWBLOCK);
simple_lock_init(&ed->sc_q_lock);
snprintf(lckname, sizeof(lckname), "%slck", ed->sc_dev.dv_xname);
lockinit(&ed->sc_lock, PRIBIO | PCATCH, lckname, 0, 0);
if (ed_get_params(ed, &drv_flags)) {
printf(": IDENTIFY failed, no disk found\n");
return;
}
format_bytes(pbuf, sizeof(pbuf),
(u_int64_t) ed->sc_capacity * DEV_BSIZE);
printf(": %s, %u cyl, %u head, %u sec, 512 bytes/sect x %u sectors\n",
pbuf,
ed->cyl, ed->heads, ed->sectors,
ed->sc_capacity);
printf("%s: %u spares/cyl, %s, %s, %s, %s, %s\n",
ed->sc_dev.dv_xname, ed->spares,
(drv_flags & (1 << 0)) ? "NoRetries" : "Retries",
(drv_flags & (1 << 1)) ? "Removable" : "Fixed",
(drv_flags & (1 << 2)) ? "SkewedFormat" : "NoSkew",
(drv_flags & (1 << 3)) ? "ZeroDefect" : "Defects",
(drv_flags & (1 << 4)) ? "InvalidSecondary" : "SecondaryOK"
);
/*
* Initialize and attach the disk structure.
*/
ed->sc_dk.dk_driver = &eddkdriver;
ed->sc_dk.dk_name = ed->sc_dev.dv_xname;
disk_attach(&ed->sc_dk);
#if NRND > 0
rnd_attach_source(&ed->rnd_source, ed->sc_dev.dv_xname,
RND_TYPE_DISK, 0);
#endif
ed->sc_flags |= EDF_INIT;
}
/*
* Read/write routine for a buffer. Validates the arguments and schedules the
* transfer. Does not wait for the transfer to complete.
*/
void
edmcastrategy(bp)
struct buf *bp;
{
struct ed_softc *ed = device_lookup(&ed_cd, DISKUNIT(bp->b_dev));
struct disklabel *lp = ed->sc_dk.dk_label;
daddr_t blkno;
WDCDEBUG_PRINT(("edmcastrategy (%s)\n", ed->sc_dev.dv_xname),
DEBUG_XFERS);
/* Valid request? */
if (bp->b_blkno < 0 ||
(bp->b_bcount % lp->d_secsize) != 0 ||
(bp->b_bcount / lp->d_secsize) >= (1 << NBBY)) {
bp->b_error = EINVAL;
goto bad;
}
/* If device invalidated (e.g. media change, door open), error. */
if ((ed->sc_flags & WDF_LOADED) == 0) {
bp->b_error = EIO;
goto bad;
}
/* If it's a null transfer, return immediately. */
if (bp->b_bcount == 0)
goto done;
/*
* Do bounds checking, adjust transfer. if error, process.
* If end of partition, just return.
*/
if (DISKPART(bp->b_dev) != RAW_PART &&
bounds_check_with_label(&ed->sc_dk, bp,
(ed->sc_flags & (WDF_WLABEL|WDF_LABELLING)) != 0) <= 0)
goto done;
/*
* Now convert the block number to absolute and put it in
* terms of the device's logical block size.
*/
if (lp->d_secsize >= DEV_BSIZE)
blkno = bp->b_blkno / (lp->d_secsize / DEV_BSIZE);
else
blkno = bp->b_blkno * (DEV_BSIZE / lp->d_secsize);
if (DISKPART(bp->b_dev) != RAW_PART)
blkno += lp->d_partitions[DISKPART(bp->b_dev)].p_offset;
bp->b_rawblkno = blkno;
/* Queue transfer on drive, activate drive and controller if idle. */
simple_lock(&ed->sc_q_lock);
BUFQ_PUT(&ed->sc_q, bp);
simple_unlock(&ed->sc_q_lock);
/* Ring the worker thread */
wakeup_one(ed->edc_softc);
return;
bad:
bp->b_flags |= B_ERROR;
done:
/* Toss transfer; we're done early. */
bp->b_resid = bp->b_bcount;
biodone(bp);
}
int
edmcaread(dev, uio, flags)
dev_t dev;
struct uio *uio;
int flags;
{
WDCDEBUG_PRINT(("edread\n"), DEBUG_XFERS);
return (physio(edmcastrategy, NULL, dev, B_READ, minphys, uio));
}
int
edmcawrite(dev, uio, flags)
dev_t dev;
struct uio *uio;
int flags;
{
WDCDEBUG_PRINT(("edwrite\n"), DEBUG_XFERS);
return (physio(edmcastrategy, NULL, dev, B_WRITE, minphys, uio));
}
/*
* Wait interruptibly for an exclusive lock.
*/
static int
ed_lock(ed)
struct ed_softc *ed;
{
int error;
int s;
WDCDEBUG_PRINT(("ed_lock\n"), DEBUG_FUNCS);
s = splbio();
error = lockmgr(&ed->sc_lock, LK_EXCLUSIVE, NULL);
splx(s);
return (error);
}
/*
* Unlock and wake up any waiters.
*/
static void
ed_unlock(ed)
struct ed_softc *ed;
{
WDCDEBUG_PRINT(("ed_unlock\n"), DEBUG_FUNCS);
(void) lockmgr(&ed->sc_lock, LK_RELEASE, NULL);
}
int
edmcaopen(dev, flag, fmt, p)
dev_t dev;
int flag, fmt;
struct proc *p;
{
struct ed_softc *wd;
int part, error;
WDCDEBUG_PRINT(("edopen\n"), DEBUG_FUNCS);
wd = device_lookup(&ed_cd, DISKUNIT(dev));
if (wd == NULL || (wd->sc_flags & EDF_INIT) == 0)
return (ENXIO);
if ((error = ed_lock(wd)) != 0)
goto bad4;
if (wd->sc_dk.dk_openmask != 0) {
/*
* If any partition is open, but the disk has been invalidated,
* disallow further opens.
*/
if ((wd->sc_flags & WDF_LOADED) == 0) {
error = EIO;
goto bad3;
}
} else {
if ((wd->sc_flags & WDF_LOADED) == 0) {
int s;
wd->sc_flags |= WDF_LOADED;
/* Load the physical device parameters. */
s = splbio();
ed_get_params(wd, NULL);
splx(s);
/* Load the partition info if not already loaded. */
edgetdisklabel(dev, wd);
}
}
part = DISKPART(dev);
/* Check that the partition exists. */
if (part != RAW_PART &&
(part >= wd->sc_dk.dk_label->d_npartitions ||
wd->sc_dk.dk_label->d_partitions[part].p_fstype == FS_UNUSED)) {
error = ENXIO;
goto bad;
}
/* Insure only one open at a time. */
switch (fmt) {
case S_IFCHR:
wd->sc_dk.dk_copenmask |= (1 << part);
break;
case S_IFBLK:
wd->sc_dk.dk_bopenmask |= (1 << part);
break;
}
wd->sc_dk.dk_openmask =
wd->sc_dk.dk_copenmask | wd->sc_dk.dk_bopenmask;
ed_unlock(wd);
return 0;
bad:
if (wd->sc_dk.dk_openmask == 0) {
}
bad3:
ed_unlock(wd);
bad4:
return (error);
}
int
edmcaclose(dev, flag, fmt, p)
dev_t dev;
int flag, fmt;
struct proc *p;
{
struct ed_softc *wd = device_lookup(&ed_cd, DISKUNIT(dev));
int part = DISKPART(dev);
int error;
WDCDEBUG_PRINT(("edmcaclose\n"), DEBUG_FUNCS);
if ((error = ed_lock(wd)) != 0)
return error;
switch (fmt) {
case S_IFCHR:
wd->sc_dk.dk_copenmask &= ~(1 << part);
break;
case S_IFBLK:
wd->sc_dk.dk_bopenmask &= ~(1 << part);
break;
}
wd->sc_dk.dk_openmask =
wd->sc_dk.dk_copenmask | wd->sc_dk.dk_bopenmask;
if (wd->sc_dk.dk_openmask == 0) {
#if 0
wd_flushcache(wd, AT_WAIT);
#endif
/* XXXX Must wait for I/O to complete! */
if (! (wd->sc_flags & WDF_KLABEL))
wd->sc_flags &= ~WDF_LOADED;
}
ed_unlock(wd);
return 0;
}
static void
edgetdefaultlabel(ed, lp)
struct ed_softc *ed;
struct disklabel *lp;
{
WDCDEBUG_PRINT(("edgetdefaultlabel\n"), DEBUG_FUNCS);
memset(lp, 0, sizeof(struct disklabel));
lp->d_secsize = DEV_BSIZE;
lp->d_ntracks = ed->heads;
lp->d_nsectors = ed->sectors;
lp->d_ncylinders = ed->cyl;
lp->d_secpercyl = lp->d_ntracks * lp->d_nsectors;
lp->d_type = DTYPE_ESDI;
strncpy(lp->d_typename, "ESDI", 16);
strncpy(lp->d_packname, "fictitious", 16);
lp->d_secperunit = ed->sc_capacity;
lp->d_rpm = 3600;
lp->d_interleave = 1;
lp->d_flags = 0;
lp->d_partitions[RAW_PART].p_offset = 0;
lp->d_partitions[RAW_PART].p_size =
lp->d_secperunit * (lp->d_secsize / DEV_BSIZE);
lp->d_partitions[RAW_PART].p_fstype = FS_UNUSED;
lp->d_npartitions = RAW_PART + 1;
lp->d_magic = DISKMAGIC;
lp->d_magic2 = DISKMAGIC;
lp->d_checksum = dkcksum(lp);
}
/*
* Fabricate a default disk label, and try to read the correct one.
*/
static void
edgetdisklabel(dev, ed)
dev_t dev;
struct ed_softc *ed;
{
struct disklabel *lp = ed->sc_dk.dk_label;
const char *errstring;
WDCDEBUG_PRINT(("edgetdisklabel\n"), DEBUG_FUNCS);
memset(ed->sc_dk.dk_cpulabel, 0, sizeof(struct cpu_disklabel));
edgetdefaultlabel(ed, lp);
errstring = readdisklabel(
EDLABELDEV(dev), edmcastrategy, lp, ed->sc_dk.dk_cpulabel);
if (errstring) {
/*
* This probably happened because the drive's default
* geometry doesn't match the DOS geometry. We
* assume the DOS geometry is now in the label and try
* again. XXX This is a kluge.
*/
#if 0
if (wd->drvp->state > RECAL)
wd->drvp->drive_flags |= DRIVE_RESET;
#endif
errstring = readdisklabel(EDLABELDEV(dev),
edmcastrategy, lp, ed->sc_dk.dk_cpulabel);
}
if (errstring) {
printf("%s: %s\n", ed->sc_dev.dv_xname, errstring);
return;
}
}
int
edmcaioctl(dev, xfer, addr, flag, p)
dev_t dev;
u_long xfer;
caddr_t addr;
int flag;
struct proc *p;
{
struct ed_softc *ed = device_lookup(&ed_cd, DISKUNIT(dev));
int error;
WDCDEBUG_PRINT(("edioctl\n"), DEBUG_FUNCS);
if ((ed->sc_flags & WDF_LOADED) == 0)
return EIO;
switch (xfer) {
case DIOCGDINFO:
*(struct disklabel *)addr = *(ed->sc_dk.dk_label);
return 0;
case DIOCGPART:
((struct partinfo *)addr)->disklab = ed->sc_dk.dk_label;
((struct partinfo *)addr)->part =
&ed->sc_dk.dk_label->d_partitions[DISKPART(dev)];
return 0;
case DIOCWDINFO:
case DIOCSDINFO:
{
struct disklabel *lp;
lp = (struct disklabel *)addr;
if ((flag & FWRITE) == 0)
return EBADF;
if ((error = ed_lock(ed)) != 0)
return error;
ed->sc_flags |= WDF_LABELLING;
error = setdisklabel(ed->sc_dk.dk_label,
lp, /*wd->sc_dk.dk_openmask : */0,
ed->sc_dk.dk_cpulabel);
if (error == 0) {
#if 0
if (wd->drvp->state > RECAL)
wd->drvp->drive_flags |= DRIVE_RESET;
#endif
if (xfer == DIOCWDINFO)
error = writedisklabel(EDLABELDEV(dev),
edmcastrategy, ed->sc_dk.dk_label,
ed->sc_dk.dk_cpulabel);
}
ed->sc_flags &= ~WDF_LABELLING;
ed_unlock(ed);
return (error);
}
case DIOCKLABEL:
if (*(int *)addr)
ed->sc_flags |= WDF_KLABEL;
else
ed->sc_flags &= ~WDF_KLABEL;
return 0;
case DIOCWLABEL:
if ((flag & FWRITE) == 0)
return EBADF;
if (*(int *)addr)
ed->sc_flags |= WDF_WLABEL;
else
ed->sc_flags &= ~WDF_WLABEL;
return 0;
case DIOCGDEFLABEL:
edgetdefaultlabel(ed, (struct disklabel *)addr);
return 0;
#if 0
case DIOCWFORMAT:
if ((flag & FWRITE) == 0)
return EBADF;
{
register struct format_op *fop;
struct iovec aiov;
struct uio auio;
fop = (struct format_op *)addr;
aiov.iov_base = fop->df_buf;
aiov.iov_len = fop->df_count;
auio.uio_iov = &aiov;
auio.uio_iovcnt = 1;
auio.uio_resid = fop->df_count;
auio.uio_segflg = 0;
auio.uio_offset =
fop->df_startblk * wd->sc_dk.dk_label->d_secsize;
auio.uio_procp = p;
error = physio(wdformat, NULL, dev, B_WRITE, minphys,
&auio);
fop->df_count -= auio.uio_resid;
fop->df_reg[0] = wdc->sc_status;
fop->df_reg[1] = wdc->sc_error;
return error;
}
#endif
default:
return ENOTTY;
}
#ifdef DIAGNOSTIC
panic("edioctl: impossible");
#endif
}
int
edmcasize(dev)
dev_t dev;
{
struct ed_softc *wd;
int part, omask;
int size;
WDCDEBUG_PRINT(("edsize\n"), DEBUG_FUNCS);
wd = device_lookup(&ed_cd, DISKUNIT(dev));
if (wd == NULL)
return (-1);
part = DISKPART(dev);
omask = wd->sc_dk.dk_openmask & (1 << part);
if (omask == 0 && edmcaopen(dev, 0, S_IFBLK, NULL) != 0)
return (-1);
if (wd->sc_dk.dk_label->d_partitions[part].p_fstype != FS_SWAP)
size = -1;
else
size = wd->sc_dk.dk_label->d_partitions[part].p_size *
(wd->sc_dk.dk_label->d_secsize / DEV_BSIZE);
if (omask == 0 && edmcaclose(dev, 0, S_IFBLK, NULL) != 0)
return (-1);
return (size);
}
/* #define WD_DUMP_NOT_TRUSTED if you just want to watch */
static int eddoingadump = 0;
static int eddumprecalibrated = 0;
static int eddumpmulti = 1;
/*
* Dump core after a system crash.
*/
int
edmcadump(dev, blkno, va, size)
dev_t dev;
daddr_t blkno;
caddr_t va;
size_t size;
{
struct ed_softc *ed; /* disk unit to do the I/O */
struct disklabel *lp; /* disk's disklabel */
int part;
int nblks; /* total number of sectors left to write */
int error;
/* Check if recursive dump; if so, punt. */
if (eddoingadump)
return EFAULT;
eddoingadump = 1;
ed = device_lookup(&ed_cd, DISKUNIT(dev));
if (ed == NULL)
return (ENXIO);
part = DISKPART(dev);
/* Make sure it was initialized. */
if ((ed->sc_flags & EDF_INIT) == 0)
return ENXIO;
/* Convert to disk sectors. Request must be a multiple of size. */
lp = ed->sc_dk.dk_label;
if ((size % lp->d_secsize) != 0)
return EFAULT;
nblks = size / lp->d_secsize;
blkno = blkno / (lp->d_secsize / DEV_BSIZE);
/* Check transfer bounds against partition size. */
if ((blkno < 0) || ((blkno + nblks) > lp->d_partitions[part].p_size))
return EINVAL;
/* Offset block number to start of partition. */
blkno += lp->d_partitions[part].p_offset;
/* Recalibrate, if first dump transfer. */
if (eddumprecalibrated == 0) {
eddumprecalibrated = 1;
eddumpmulti = 8;
#if 0
wd->drvp->state = RESET;
#endif
}
while (nblks > 0) {
error = edc_bio(ed->edc_softc, ed, va, blkno,
min(nblks, eddumpmulti) * lp->d_secsize, 0, 1);
if (error)
return (error);
/* update block count */
nblks -= min(nblks, eddumpmulti);
blkno += min(nblks, eddumpmulti);
va += min(nblks, eddumpmulti) * lp->d_secsize;
}
eddoingadump = 0;
return (0);
}
static int
ed_get_params(ed, drv_flags)
struct ed_softc *ed;
int *drv_flags;
{
u_int16_t cmd_args[2];
/*
* Get Device Configuration (09).
*/
cmd_args[0] = 14; /* Options: 00s110, s: 0=Physical 1=Pseudo */
cmd_args[1] = 0;
if (edc_run_cmd(ed->edc_softc, CMD_GET_DEV_CONF, ed->sc_devno,
cmd_args, 2, 1))
return (1);
ed->spares = ed->sense_data[1] >> 8;
if (drv_flags)
*drv_flags = ed->sense_data[1] & 0x1f;
ed->rba = ed->sense_data[2] | (ed->sense_data[3] << 16);
/* Instead of using:
ed->cyl = ed->sense_data[4];
ed->heads = ed->sense_data[5] & 0xff;
ed->sectors = ed->sense_data[5] >> 8;
* we fabricate the numbers from RBA count, so that
* number of sectors is 32 and heads 64. This seems
* to be necessary for integrated ESDI controller.
*/
ed->sectors = 32;
ed->heads = 64;
ed->cyl = ed->rba / (ed->heads * ed->sectors);
ed->sc_capacity = ed->rba;
return (0);
}