NetBSD/sys/dev/mca/ed_mca.c

736 lines
18 KiB
C

/* $NetBSD: ed_mca.c,v 1.46 2009/05/12 13:15:24 cegger Exp $ */
/*
* Copyright (c) 2001 The NetBSD Foundation, Inc.
* All rights reserved.
*
* 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.
*
* THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
* ``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 FOUNDATION OR CONTRIBUTORS
* 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.46 2009/05/12 13:15:24 cegger Exp $");
#include "rnd.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/bufq.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 <sys/intr.h>
#include <sys/bus.h>
#include <dev/mca/mcavar.h>
#include <dev/mca/edcreg.h>
#include <dev/mca/edvar.h>
#include <dev/mca/edcvar.h>
/* #define ATADEBUG */
#ifdef ATADEBUG
#define ATADEBUG_PRINT(args, level) printf args
#else
#define ATADEBUG_PRINT(args, level)
#endif
#define EDLABELDEV(dev) (MAKEDISKDEV(major(dev), DISKUNIT(dev), RAW_PART))
static int ed_mca_probe (struct device *, cfdata_t, void *);
static void ed_mca_attach (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(struct ed_softc *, int *);
static void edgetdisklabel(dev_t, struct ed_softc *);
static void edgetdefaultlabel(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, minphys };
/*
* Just check if it's possible to identify the disk.
*/
static int
ed_mca_probe(struct device *parent, cfdata_t 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;
/*
* 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(struct device *parent, struct device *self, void *aux)
{
struct ed_softc *ed = device_private(self);
struct edc_mca_softc *sc = device_private(parent);
struct ed_attach_args *eda = (struct ed_attach_args *) aux;
char pbuf[8];
int drv_flags;
ed->edc_softc = sc;
ed->sc_devno = eda->edc_drive;
edc_add_disk(sc, ed);
bufq_alloc(&ed->sc_q, "disksort", BUFQ_SORT_RAWBLOCK);
simple_lock_init(&ed->sc_q_lock);
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",
device_xname(&ed->sc_dev), 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.
*/
disk_init(&ed->sc_dk, device_xname(&ed->sc_dev), &eddkdriver);
disk_attach(&ed->sc_dk);
#if NRND > 0
rnd_attach_source(&ed->rnd_source, device_xname(&ed->sc_dev),
RND_TYPE_DISK, 0);
#endif
ed->sc_flags |= EDF_INIT;
/*
* XXX We should try to discovery wedges here, but
* XXX that would mean being able to do I/O. Should
* XXX use config_defer() here.
*/
}
/*
* Read/write routine for a buffer. Validates the arguments and schedules the
* transfer. Does not wait for the transfer to complete.
*/
void
edmcastrategy(struct buf *bp)
{
struct ed_softc *ed;
struct disklabel *lp;
daddr_t blkno;
ed = device_lookup_private(&ed_cd, DISKUNIT(bp->b_dev));
lp = ed->sc_dk.dk_label;
ATADEBUG_PRINT(("edmcastrategy (%s)\n", device_xname(&ed->sc_dev)),
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 done;
}
/* If device invalidated (e.g. media change, door open), error. */
if ((ed->sc_flags & WDF_LOADED) == 0) {
bp->b_error = EIO;
goto done;
}
/* 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;
done:
/* Toss transfer; we're done early. */
bp->b_resid = bp->b_bcount;
biodone(bp);
}
int
edmcaread(dev_t dev, struct uio *uio, int flags)
{
ATADEBUG_PRINT(("edread\n"), DEBUG_XFERS);
return (physio(edmcastrategy, NULL, dev, B_READ, minphys, uio));
}
int
edmcawrite(dev_t dev, struct uio *uio, int flags)
{
ATADEBUG_PRINT(("edwrite\n"), DEBUG_XFERS);
return (physio(edmcastrategy, NULL, dev, B_WRITE, minphys, uio));
}
int
edmcaopen(dev_t dev, int flag, int fmt, struct lwp *l)
{
struct ed_softc *wd;
int part, error;
ATADEBUG_PRINT(("edopen\n"), DEBUG_FUNCS);
wd = device_lookup_private(&ed_cd, DISKUNIT(dev));
if (wd == NULL || (wd->sc_flags & EDF_INIT) == 0)
return (ENXIO);
part = DISKPART(dev);
mutex_enter(&wd->sc_dk.dk_openlock);
/*
* If there are wedges, and this is not RAW_PART, then we
* need to fail.
*/
if (wd->sc_dk.dk_nwedges != 0 && part != RAW_PART) {
error = EBUSY;
goto bad1;
}
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 bad1;
}
} 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);
}
}
/* 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 bad1;
}
/* 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;
error = 0;
bad1:
mutex_exit(&wd->sc_dk.dk_openlock);
return (error);
}
int
edmcaclose(dev_t dev, int flag, int fmt, struct lwp *l)
{
struct ed_softc *wd = device_lookup_private(&ed_cd, DISKUNIT(dev));
int part = DISKPART(dev);
ATADEBUG_PRINT(("edmcaclose\n"), DEBUG_FUNCS);
mutex_enter(&wd->sc_dk.dk_openlock);
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;
}
mutex_exit(&wd->sc_dk.dk_openlock);
return 0;
}
static void
edgetdefaultlabel(struct ed_softc *ed, struct disklabel *lp)
{
ATADEBUG_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_t dev, struct ed_softc *ed)
{
struct disklabel *lp = ed->sc_dk.dk_label;
const char *errstring;
ATADEBUG_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", device_xname(&ed->sc_dev), errstring);
return;
}
}
int
edmcaioctl(dev_t dev, u_long xfer, void *addr, int flag, struct lwp *l)
{
struct ed_softc *ed = device_lookup_private(&ed_cd, DISKUNIT(dev));
int error;
ATADEBUG_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;
mutex_enter(&ed->sc_dk.dk_openlock);
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;
mutex_exit(&ed->sc_dk.dk_openlock);
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_lwp = l;
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
case DIOCAWEDGE:
{
struct dkwedge_info *dkw = (void *) addr;
if ((flag & FWRITE) == 0)
return (EBADF);
/* If the ioctl happens here, the parent is us. */
strlcpy(dkw->dkw_parent, device_xname(&ed->sc_dev),
sizeof(dkw->dkw_parent));
return (dkwedge_add(dkw));
}
case DIOCDWEDGE:
{
struct dkwedge_info *dkw = (void *) addr;
if ((flag & FWRITE) == 0)
return (EBADF);
/* If the ioctl happens here, the parent is us. */
strlcpy(dkw->dkw_parent, device_xname(&ed->sc_dev),
sizeof(dkw->dkw_parent));
return (dkwedge_del(dkw));
}
case DIOCLWEDGES:
{
struct dkwedge_list *dkwl = (void *) addr;
return (dkwedge_list(&ed->sc_dk, dkwl, l));
}
default:
return ENOTTY;
}
#ifdef DIAGNOSTIC
panic("edioctl: impossible");
#endif
}
int
edmcasize(dev_t dev)
{
struct ed_softc *wd;
int part, omask;
int size;
ATADEBUG_PRINT(("edsize\n"), DEBUG_FUNCS);
wd = device_lookup_private(&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_t dev, daddr_t blkno, void *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_private(&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 = (char *)va + min(nblks, eddumpmulti) * lp->d_secsize;
}
eddoingadump = 0;
return (0);
}
static int
ed_get_params(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);
}