Source to i386/isa/wd.c
/*-
* Copyright (c) 1990, 1993
* The Regents of the University of California. All rights reserved.
*
* This code is derived from software contributed to Berkeley by
* William Jolitz.
*
* 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 University of
* California, Berkeley and its contributors.
* 4. Neither the name of the University nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE REGENTS 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 REGENTS 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.
*
* @(#)wd.c 8.1 (Berkeley) 6/11/93
*/
/* TODO:peel out buffer at low ipl,
speed improvement, rewrite to clean code from garbage artifacts */
#include "wd.h"
#if NWD > 0
#include <sys/param.h>
#include <sys/dkbad.h>
#include <sys/systm.h>
#include <sys/conf.h>
#include <sys/file.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <sys/disklabel.h>
#include <sys/buf.h>
#include <sys/uio.h>
#include <sys/syslog.h>
#include <i386/isa/isa_device.h>
#include <i386/isa/icu.h>
#include <i386/isa/wdreg.h>
#include <vm/vm.h>
#define RETRIES 5 /* number of retries before giving up */
#define MAXTRANSFER 32 /* max size of transfer in page clusters */
#define wdctlr(dev) ((minor(dev) & 0x80) >> 7)
#define wdunit(dev) ((minor(dev) & 0x60) >> 5)
#define wdpart(dev) ((minor(dev) & 0x1f))
#define b_cylin b_resid /* cylinder number for doing IO to */
/* shares an entry in the buf struct */
/*
* Drive states. Used for open and format operations.
* States < OPEN (> 0) are transient, during an open operation.
* OPENRAW is used for unlabeled disks, and for floppies, to inhibit
* bad-sector forwarding.
*/
#define RAWDISK 8 /* raw disk operation, no translation*/
#define ISRAWSTATE(s) (RAWDISK&(s)) /* are we in a raw state? */
#define DISKSTATE(s) (~RAWDISK&(s)) /* are we in a given state regardless
of raw or cooked mode? */
#define CLOSED 0 /* disk is closed. */
/* "cooked" disk states */
#define WANTOPEN 1 /* open requested, not started */
#define RECAL 2 /* doing restore */
#define RDLABEL 3 /* reading pack label */
#define RDBADTBL 4 /* reading bad-sector table */
#define OPEN 5 /* done with open */
#define WANTOPENRAW (WANTOPEN|RAWDISK) /* raw WANTOPEN */
#define RECALRAW (RECAL|RAWDISK) /* raw open, doing restore */
#define OPENRAW (OPEN|RAWDISK) /* open, but unlabeled disk or floppy */
/*
* The structure of a disk drive.
*/
struct disk {
struct disklabel dk_dd; /* device configuration data */
long dk_bc; /* byte count left */
short dk_skip; /* blocks already transferred */
char dk_unit; /* physical unit number */
char dk_state; /* control state */
u_char dk_status; /* copy of status reg. */
u_char dk_error; /* copy of error reg. */
short dk_open; /* open/closed refcnt */
u_long dk_copenpart; /* character units open on this drive */
u_long dk_bopenpart; /* block units open on this drive */
u_long dk_openpart; /* all units open on this drive */
short dk_wlabel; /* label writable? */
};
/*
* This label is used as a default when initializing a new or raw disk.
* It really only lets us access the first track until we know more.
*/
struct disklabel dflt_sizes = {
DISKMAGIC, DTYPE_ST506, 0, "default", "",
512, /* sector size */
17, /* # of sectors per track */
8, /* # of tracks per cylinder */
766, /* # of cylinders per unit */
17*8, /* # of sectors per cylinder */
766*8*17, /* # of sectors per unit */
0, /* # of spare sectors per track */
0, /* # of spare sectors per cylinder */
0, /* # of alt. cylinders per unit */
3600, /* rotational speed */
1, /* hardware sector interleave */
0, /* sector 0 skew, per track */
0, /* sector 0 skew, per cylinder */
0, /* head switch time, usec */
0, /* track-to-track seek, usec */
0, /* generic flags */
0,0,0,0,0,
0,0,0,0,0,
DISKMAGIC,
0,
8,
8192,
8192,
{{21600, 0, 0,0,0,0}, /* A=root filesystem */
{21600, 40, 0,0,0,0},
{660890, 0, 0,0,0,0}, /* C=whole disk */
{216000, 80, 0,0,0,0},
{0, 0, 0,0,0,0},
{0, 0, 0,0,0,0},
{0, 0, 0,0,0,0},
{399600, 480, 0,0,0,0}}
};
static struct dkbad dkbad[NWD];
struct disk wddrives[NWD] = {0}; /* table of units */
struct buf wdtab = {0};
struct buf wdutab[NWD] = {0}; /* head of queue per drive */
struct buf rwdbuf[NWD] = {0}; /* buffers for raw IO */
long wdxfer[NWD] = {0}; /* count of transfers */
int writeprotected[NWD] = { 0 };
int wdprobe(), wdattach(), wdintr();
struct isa_driver wddriver = {
wdprobe, wdattach, "wd",
};
static wdc;
/*
* Probe routine
*/
wdprobe(dvp)
struct isa_device *dvp;
{
wdc = dvp->id_iobase;
#ifdef lint
wdintr(0);
#endif
/* XXX sorry, needs to be better */
outb(wdc+wd_error, 0x5a) ; /* error register not writable */
outb(wdc+wd_cyl_lo, 0xa5) ; /* but all of cyllo are implemented */
if(inb(wdc+wd_error) != 0x5a && inb(wdc+wd_cyl_lo) == 0xa5)
return(1) ;
return (0);
}
/*
* attach each drive if possible.
*/
wdattach(dvp)
struct isa_device *dvp;
{
int unit = dvp->id_unit;
outb(wdc+wd_ctlr,12);
DELAY(1000);
outb(wdc+wd_ctlr,8);
}
/* Read/write routine for a buffer. Finds the proper unit, range checks
* arguments, and schedules the transfer. Does not wait for the transfer
* to complete. Multi-page transfers are supported. All I/O requests must
* be a multiple of a sector in length.
*/
wdstrategy(bp)
register struct buf *bp; /* IO operation to perform */
{
register struct buf *dp;
register struct disk *du; /* Disk unit to do the IO. */
register struct partition *p;
long maxsz, sz;
int unit = wdunit(bp->b_dev);
int s;
if ((unit >= NWD) || (bp->b_blkno < 0)) {
printf("wdstrat: unit = %d, blkno = %d, bcount = %d\n",
unit, bp->b_blkno, bp->b_bcount);
pg("wd:error in wdstrategy");
bp->b_flags |= B_ERROR;
goto bad;
}
if (writeprotected[unit] && (bp->b_flags & B_READ) == 0) {
printf("wd%d: write protected\n", unit);
goto bad;
}
du = &wddrives[unit];
if (DISKSTATE(du->dk_state) != OPEN)
goto q;
#ifdef old
/*
* Convert DEV_BSIZE "blocks" to sectors.
* Note: doing the conversions this way limits the partition size
* to about 8 million sectors (1-8 Gb).
*/
blknum = (unsigned long) bp->b_blkno * DEV_BSIZE / du->dk_dd.d_secsize;
if (((u_long) bp->b_blkno * DEV_BSIZE % du->dk_dd.d_secsize != 0) ||
bp->b_bcount >= MAXTRANSFER * CLBYTES) {
bp->b_flags |= B_ERROR;
goto bad;
}
nblocks = du->dk_dd.d_partitions[part].p_size;
cyloff = du->dk_dd.d_partitions[part].p_offset;
if (blknum + (bp->b_bcount / du->dk_dd.d_secsize) > nblocks) {
if (blknum == nblocks)
bp->b_resid = bp->b_bcount;
else
bp->b_flags |= B_ERROR;
goto bad;
}
bp->b_cylin = blknum / du->dk_dd.d_secpercyl + cyloff;
#else
/*
* Determine the size of the transfer, and make sure it is
* within the boundaries of the partition.
*/
p = &du->dk_dd.d_partitions[wdpart(bp->b_dev)];
maxsz = p->p_size;
sz = (bp->b_bcount + DEV_BSIZE - 1) >> DEV_BSHIFT;
if (bp->b_blkno + p->p_offset <= LABELSECTOR &&
#if LABELSECTOR != 0
bp->b_blkno + p->p_offset + sz > LABELSECTOR &&
#endif
(bp->b_flags & B_READ) == 0 && du->dk_wlabel == 0) {
bp->b_error = EROFS;
goto bad;
}
if (bp->b_blkno < 0 || bp->b_blkno + sz > maxsz) {
/* if exactly at end of disk, return an EOF */
if (bp->b_blkno == maxsz) {
bp->b_resid = bp->b_bcount;
biodone(bp);
return;
}
/* or truncate if part of it fits */
sz = maxsz - bp->b_blkno;
if (sz <= 0)
goto bad;
bp->b_bcount = sz << DEV_BSHIFT;
}
bp->b_cylin = (bp->b_blkno + p->p_offset) / du->dk_dd.d_secpercyl;
#endif
q:
dp = &wdutab[unit];
s = splhigh();
disksort(dp, bp);
if (dp->b_active == 0)
wdustart(du); /* start drive if idle */
if (wdtab.b_active == 0)
wdstart(s); /* start IO if controller idle */
splx(s);
return;
bad:
bp->b_error = EINVAL;
biodone(bp);
}
/* Routine to queue a read or write command to the controller. The request is
* linked into the active list for the controller. If the controller is idle,
* the transfer is started.
*/
wdustart(du)
register struct disk *du;
{
register struct buf *bp, *dp;
dp = &wdutab[du->dk_unit];
if (dp->b_active)
return;
bp = dp->b_actf;
if (bp == NULL)
return;
dp->b_forw = NULL;
if (wdtab.b_actf == NULL) /* link unit into active list */
wdtab.b_actf = dp;
else
wdtab.b_actl->b_forw = dp;
wdtab.b_actl = dp;
dp->b_active = 1; /* mark the drive as busy */
}
/*
* Controller startup routine. This does the calculation, and starts
* a single-sector read or write operation. Called to start a transfer,
* or from the interrupt routine to continue a multi-sector transfer.
* RESTRICTIONS:
* 1. The transfer length must be an exact multiple of the sector size.
*/
static wd_sebyse;
wdstart()
{
register struct disk *du; /* disk unit for IO */
register struct buf *bp;
struct buf *dp;
register struct bt_bad *bt_ptr;
long blknum, pagcnt, cylin, head, sector;
long secpertrk, secpercyl, addr, i;
int unit, s;
loop:
dp = wdtab.b_actf;
if (dp == NULL)
return;
bp = dp->b_actf;
if (bp == NULL) {
wdtab.b_actf = dp->b_forw;
goto loop;
}
unit = wdunit(bp->b_dev);
du = &wddrives[unit];
if (DISKSTATE(du->dk_state) <= RDLABEL) {
if (wdcontrol(bp)) {
dp->b_actf = bp->av_forw;
goto loop; /* done */
}
return;
}
secpertrk = du->dk_dd.d_nsectors;
secpercyl = du->dk_dd.d_secpercyl;
/*
* Convert DEV_BSIZE "blocks" to sectors.
*/
blknum = (unsigned long) bp->b_blkno * DEV_BSIZE / du->dk_dd.d_secsize
+ du->dk_skip;
#ifdef WDDEBUG
if (du->dk_skip == 0) {
dprintf(DDSK,"\nwdstart %d: %s %d@%d; map ", unit,
(bp->b_flags & B_READ) ? "read" : "write",
bp->b_bcount, blknum);
} else {
dprintf(DDSK," %d)%x", du->dk_skip, inb(wdc+wd_altsts));
}
#endif
addr = (int) bp->b_un.b_addr;
if(du->dk_skip==0) du->dk_bc = bp->b_bcount;
cylin = blknum / secpercyl;
head = (blknum % secpercyl) / secpertrk;
sector = blknum % secpertrk;
if (DISKSTATE(du->dk_state) == OPEN)
cylin += du->dk_dd.d_partitions[wdpart(bp->b_dev)].p_offset
/ secpercyl;
/*
* See if the current block is in the bad block list.
* (If we have one, and not formatting.)
*/
if (DISKSTATE(du->dk_state) == OPEN && wd_sebyse)
for (bt_ptr = dkbad[unit].bt_bad; bt_ptr->bt_cyl != -1; bt_ptr++) {
if (bt_ptr->bt_cyl > cylin)
/* Sorted list, and we passed our cylinder. quit. */
break;
if (bt_ptr->bt_cyl == cylin &&
bt_ptr->bt_trksec == (head << 8) + sector) {
/*
* Found bad block. Calculate new block addr.
* This starts at the end of the disk (skip the
* last track which is used for the bad block list),
* and works backwards to the front of the disk.
*/
#ifdef WDDEBUG
dprintf(DDSK,"--- badblock code -> Old = %d; ",
blknum);
#endif
blknum = du->dk_dd.d_secperunit - du->dk_dd.d_nsectors
- (bt_ptr - dkbad[unit].bt_bad) - 1;
cylin = blknum / secpercyl;
head = (blknum % secpercyl) / secpertrk;
sector = blknum % secpertrk;
#ifdef WDDEBUG
dprintf(DDSK, "new = %d\n", blknum);
#endif
break;
}
}
sector += 1; /* sectors begin with 1, not 0 */
wdtab.b_active = 1; /* mark controller active */
if(du->dk_skip==0 || wd_sebyse) {
if(wdtab.b_errcnt && (bp->b_flags & B_READ) == 0) du->dk_bc += 512;
while ((inb(wdc+wd_status) & WDCS_BUSY) != 0) ;
/*while ((inb(wdc+wd_status) & WDCS_DRQ)) inb(wdc+wd_data);*/
outb(wdc+wd_precomp, 0xff);
/*wr(wdc+wd_precomp, du->dk_dd.dk_precompcyl / 4);*/
/*if (bp->b_flags & B_FORMAT) {
wr(wdc+wd_sector, du->dk_dd.dk_gap3);
wr(wdc+wd_seccnt, du->dk_dd.dk_nsectors);
} else {*/
if(wd_sebyse)
outb(wdc+wd_seccnt, 1);
else
outb(wdc+wd_seccnt, ((du->dk_bc +511) / 512));
outb(wdc+wd_sector, sector);
outb(wdc+wd_cyl_lo, cylin);
outb(wdc+wd_cyl_hi, cylin >> 8);
/* Set up the SDH register (select drive). */
outb(wdc+wd_sdh, WDSD_IBM | (unit<<4) | (head & 0xf));
while ((inb(wdc+wd_status) & WDCS_READY) == 0) ;
/*if (bp->b_flags & B_FORMAT)
wr(wdc+wd_command, WDCC_FORMAT);
else*/
outb(wdc+wd_command,
(bp->b_flags & B_READ)? WDCC_READ : WDCC_WRITE);
#ifdef WDDEBUG
dprintf(DDSK,"sector %d cylin %d head %d addr %x sts %x\n",
sector, cylin, head, addr, inb(wdc+wd_altsts));
#endif
}
/* If this is a read operation, just go away until it's done. */
if (bp->b_flags & B_READ) return;
/* Ready to send data? */
while ((inb(wdc+wd_status) & WDCS_DRQ) == 0);
/* ASSUMES CONTIGUOUS MEMORY */
outsw (wdc+wd_data, addr+du->dk_skip*512, 256);
du->dk_bc -= 512;
}
/*
* these are globally defined so they can be found
* by the debugger easily in the case of a system crash
*/
daddr_t wd_errsector;
daddr_t wd_errbn;
unsigned char wd_errstat;
/* Interrupt routine for the controller. Acknowledge the interrupt, check for
* errors on the current operation, mark it done if necessary, and start
* the next request. Also check for a partially done transfer, and
* continue with the next chunk if so.
*/
wdintr(unit)
{
register struct disk *du;
register struct buf *bp, *dp;
int status;
char partch ;
static wd_haderror;
/* Shouldn't need this, but it may be a slow controller. */
while ((status = inb(wdc+wd_status)) & WDCS_BUSY) ;
if (!wdtab.b_active) {
printf("wd: extra interrupt\n");
return;
}
#ifdef WDDEBUG
dprintf(DDSK,"I ");
#endif
dp = wdtab.b_actf;
bp = dp->b_actf;
du = &wddrives[wdunit(bp->b_dev)];
partch = wdpart(bp->b_dev) + 'a';
if (DISKSTATE(du->dk_state) <= RDLABEL) {
if (wdcontrol(bp))
goto done;
return;
}
if (status & (WDCS_ERR | WDCS_ECCCOR)) {
wd_errstat = inb(wdc+wd_error); /* save error status */
#ifdef WDDEBUG
printf("status %x error %x\n", status, wd_errstat);
#endif
if(wd_sebyse == 0) {
wd_haderror = 1;
goto outt;
}
/*if (bp->b_flags & B_FORMAT) {
du->dk_status = status;
du->dk_error = wdp->wd_error;
bp->b_flags |= B_ERROR;
goto done;
}*/
wd_errsector = (bp->b_cylin * du->dk_dd.d_secpercyl) +
(((unsigned long) bp->b_blkno * DEV_BSIZE /
du->dk_dd.d_secsize) % du->dk_dd.d_secpercyl) +
du->dk_skip;
wd_errbn = bp->b_blkno
+ du->dk_skip * du->dk_dd.d_secsize / DEV_BSIZE ;
if (status & WDCS_ERR) {
if (++wdtab.b_errcnt < RETRIES) {
wdtab.b_active = 0;
} else {
printf("wd%d%c: ", du->dk_unit, partch);
printf(
"hard %s error, sn %d bn %d status %b error %b\n",
(bp->b_flags & B_READ)? "read":"write",
wd_errsector, wd_errbn, status, WDCS_BITS,
wd_errstat, WDERR_BITS);
bp->b_flags |= B_ERROR; /* flag the error */
}
} else
log(LOG_WARNING,"wd%d%c: soft ecc sn %d bn %d\n",
du->dk_unit, partch, wd_errsector,
wd_errbn);
}
outt:
/*
* If this was a successful read operation, fetch the data.
*/
if (((bp->b_flags & (B_READ | B_ERROR)) == B_READ) && wdtab.b_active) {
int chk, dummy;
chk = min(256,du->dk_bc/2);
/* Ready to receive data? */
while ((inb(wdc+wd_status) & WDCS_DRQ) == 0) ;
/*dprintf(DDSK,"addr %x\n", (int)bp->b_un.b_addr + du->dk_skip * 512);*/
insw(wdc+wd_data,(int)bp->b_un.b_addr + du->dk_skip * 512 ,chk);
du->dk_bc -= 2*chk;
while (chk++ < 256) insw (wdc+wd_data,&dummy,1);
}
wdxfer[du->dk_unit]++;
if (wdtab.b_active) {
if ((bp->b_flags & B_ERROR) == 0) {
du->dk_skip++; /* Add to successful sectors. */
if (wdtab.b_errcnt) {
log(LOG_WARNING, "wd%d%c: ",
du->dk_unit, partch);
log(LOG_WARNING,
"soft %s error, sn %d bn %d error %b retries %d\n",
(bp->b_flags & B_READ) ? "read" : "write",
wd_errsector, wd_errbn, wd_errstat,
WDERR_BITS, wdtab.b_errcnt);
}
wdtab.b_errcnt = 0;
/* see if more to transfer */
/*if (du->dk_skip < (bp->b_bcount + 511) / 512) {*/
if (du->dk_bc > 0 && wd_haderror == 0) {
wdstart();
return; /* next chunk is started */
} else if (wd_haderror && wd_sebyse == 0) {
du->dk_skip = 0;
wd_haderror = 0;
wd_sebyse = 1;
wdstart();
return; /* redo xfer sector by sector */
}
}
done:
wd_sebyse = 0;
/* done with this transfer, with or without error */
wdtab.b_actf = dp->b_forw;
wdtab.b_errcnt = 0;
du->dk_skip = 0;
dp->b_active = 0;
dp->b_actf = bp->av_forw;
dp->b_errcnt = 0;
bp->b_resid = 0;
biodone(bp);
}
wdtab.b_active = 0;
if (dp->b_actf)
wdustart(du); /* requeue disk if more io to do */
if (wdtab.b_actf)
wdstart(); /* start IO on next drive */
}
/*
* Initialize a drive.
*/
wdopen(dev, flags, fmt)
dev_t dev;
int flags, fmt;
{
register unsigned int unit;
register struct buf *bp;
register struct disk *du;
int part = wdpart(dev), mask = 1 << part;
struct partition *pp;
struct dkbad *db;
int i, error = 0;
unit = wdunit(dev);
if (unit >= NWD) return (ENXIO) ;
du = &wddrives[unit];
#ifdef notdef
if (du->dk_open){
du->dk_open++ ;
return(0); /* already is open, don't mess with it */
}
#endif
du->dk_unit = unit;
wdutab[unit].b_actf = NULL;
/*if (flags & O_NDELAY)
du->dk_state = WANTOPENRAW;
else*/
du->dk_state = WANTOPEN;
/*
* Use the default sizes until we've read the label,
* or longer if there isn't one there.
*/
du->dk_dd = dflt_sizes;
/*
* Recal, read of disk label will be done in wdcontrol
* during first read operation.
*/
bp = geteblk(512);
bp->b_dev = dev & 0xff00;
bp->b_bcount = 0;
bp->b_blkno = LABELSECTOR;
bp->b_flags = B_READ;
wdstrategy(bp);
biowait(bp);
if (bp->b_flags & B_ERROR) {
error = ENXIO;
du->dk_state = CLOSED;
goto done;
}
if (du->dk_state == OPENRAW) {
du->dk_state = OPENRAW;
goto done;
}
/*
* Read bad sector table into memory.
*/
i = 0;
do {
bp->b_flags = B_BUSY | B_READ;
bp->b_blkno = du->dk_dd.d_secperunit - du->dk_dd.d_nsectors
+ i;
if (du->dk_dd.d_secsize > DEV_BSIZE)
bp->b_blkno *= du->dk_dd.d_secsize / DEV_BSIZE;
else
bp->b_blkno /= DEV_BSIZE / du->dk_dd.d_secsize;
bp->b_bcount = du->dk_dd.d_secsize;
bp->b_cylin = du->dk_dd.d_ncylinders - 1;
wdstrategy(bp);
biowait(bp);
} while ((bp->b_flags & B_ERROR) && (i += 2) < 10 &&
i < du->dk_dd.d_nsectors);
db = (struct dkbad *)(bp->b_un.b_addr);
#define DKBAD_MAGIC 0x4321
if ((bp->b_flags & B_ERROR) == 0 && db->bt_mbz == 0 &&
db->bt_flag == DKBAD_MAGIC) {
dkbad[unit] = *db;
du->dk_state = OPEN;
} else {
printf("wd%d: %s bad-sector file\n", unit,
(bp->b_flags & B_ERROR) ? "can't read" : "format error in");
error = ENXIO ;
du->dk_state = OPENRAW;
}
done:
bp->b_flags = B_INVAL | B_AGE;
brelse(bp);
if (error == 0)
du->dk_open = 1;
/*
* Warn if a partion is opened
* that overlaps another partition which is open
* unless one is the "raw" partition (whole disk).
*/
#define RAWPART 8 /* 'x' partition */ /* XXX */
if ((du->dk_openpart & mask) == 0 && part != RAWPART) {
int start, end;
pp = &du->dk_dd.d_partitions[part];
start = pp->p_offset;
end = pp->p_offset + pp->p_size;
for (pp = du->dk_dd.d_partitions;
pp < &du->dk_dd.d_partitions[du->dk_dd.d_npartitions];
pp++) {
if (pp->p_offset + pp->p_size <= start ||
pp->p_offset >= end)
continue;
if (pp - du->dk_dd.d_partitions == RAWPART)
continue;
if (du->dk_openpart & (1 << (pp -
du->dk_dd.d_partitions)))
log(LOG_WARNING,
"wd%d%c: overlaps open partition (%c)\n",
unit, part + 'a',
pp - du->dk_dd.d_partitions + 'a');
}
}
if (part >= du->dk_dd.d_npartitions)
return (ENXIO);
du->dk_openpart |= mask;
switch (fmt) {
case S_IFCHR:
du->dk_copenpart |= mask;
break;
case S_IFBLK:
du->dk_bopenpart |= mask;
break;
}
return (error);
}
/*
* Implement operations other than read/write.
* Called from wdstart or wdintr during opens and formats.
* Uses finite-state-machine to track progress of operation in progress.
* Returns 0 if operation still in progress, 1 if completed.
*/
wdcontrol(bp)
register struct buf *bp;
{
register struct disk *du;
register unit;
unsigned char stat;
int s, cnt;
extern int bootdev, cyloffset;
du = &wddrives[wdunit(bp->b_dev)];
unit = du->dk_unit;
switch (DISKSTATE(du->dk_state)) {
tryagainrecal:
case WANTOPEN: /* set SDH, step rate, do restore */
#ifdef WDDEBUG
dprintf(DDSK,"wd%d: recal ", unit);
#endif
s = splbio(); /* not called from intr level ... */
outb(wdc+wd_sdh, WDSD_IBM | (unit << 4));
wdtab.b_active = 1;
outb(wdc+wd_command, WDCC_RESTORE | WD_STEP);
du->dk_state++;
splx(s);
return(0);
case RECAL:
if ((stat = inb(wdc+wd_status)) & WDCS_ERR) {
printf("wd%d: recal", du->dk_unit);
if (unit == 0) {
printf(": status %b error %b\n",
stat, WDCS_BITS,
inb(wdc+wd_error), WDERR_BITS);
if (++wdtab.b_errcnt < RETRIES)
goto tryagainrecal;
}
goto badopen;
}
/* some compaq controllers require this ... */
wdsetctlr(bp->b_dev, du);
wdtab.b_errcnt = 0;
if (ISRAWSTATE(du->dk_state)) {
du->dk_state = OPENRAW;
return(1);
}
retry:
#ifdef WDDEBUG
dprintf(DDSK,"rdlabel ");
#endif
if( cyloffset < 0 || cyloffset > 8192) cyloffset=0;
/*
* Read in sector LABELSECTOR to get the pack label
* and geometry.
*/
outb(wdc+wd_precomp, 0xff);/* sometimes this is head bit 3 */
outb(wdc+wd_seccnt, 1);
outb(wdc+wd_sector, LABELSECTOR+1);
/*if (bp->b_dev == bootdev) {
(wdc+wd_cyl_lo = cyloffset & 0xff;
(wdc+wd_cyl_hi = cyloffset >> 8;
} else {
(wdc+wd_cyl_lo = 0;
(wdc+wd_cyl_hi = 0;
}*/
outb(wdc+wd_cyl_lo, (cyloffset & 0xff));
outb(wdc+wd_cyl_hi, (cyloffset >> 8));
outb(wdc+wd_sdh, WDSD_IBM | (unit << 4));
outb(wdc+wd_command, WDCC_READ);
du->dk_state = RDLABEL;
return(0);
case RDLABEL:
if ((stat = inb(wdc+wd_status)) & WDCS_ERR) {
if (++wdtab.b_errcnt < RETRIES)
goto retry;
printf("wd%d: read label", unit);
goto badopen;
}
insw(wdc+wd_data, bp->b_un.b_addr, 256);
if (((struct disklabel *)
(bp->b_un.b_addr + LABELOFFSET))->d_magic == DISKMAGIC) {
du->dk_dd =
* (struct disklabel *) (bp->b_un.b_addr + LABELOFFSET);
} else {
printf("wd%d: bad disk label\n", du->dk_unit);
du->dk_state = OPENRAW;
}
s = splbio(); /* not called from intr level ... */
while ((stat = inb(wdc+wd_status)) & WDCS_BUSY);
wdsetctlr(bp->b_dev, du);
outb(wdc+wd_seccnt, 0);
splx(s);
if (du->dk_state == RDLABEL)
du->dk_state = RDBADTBL;
/*
* The rest of the initialization can be done
* by normal means.
*/
return(1);
default:
panic("wdcontrol");
}
/* NOTREACHED */
badopen:
printf(": status %b error %b\n",
stat, WDCS_BITS, inb(wdc+wd_error), WDERR_BITS);
du->dk_state = OPENRAW;
return(1);
}
wdsetctlr(dev, du) dev_t dev; struct disk *du; {
int stat;
outb(wdc+wd_cyl_lo, du->dk_dd.d_ncylinders);
outb(wdc+wd_cyl_hi, (du->dk_dd.d_ncylinders)>>8);
outb(wdc+wd_sdh, WDSD_IBM | (wdunit(dev) << 4) + du->dk_dd.d_ntracks-1);
outb(wdc+wd_seccnt, du->dk_dd.d_nsectors);
outb(wdc+wd_command, 0x91);
while ((stat = inb(wdc+wd_status)) & WDCS_BUSY) ;
stat = inb(wdc+wd_error);
return(stat);
}
/* ARGSUSED */
wdclose(dev, flags, fmt)
dev_t dev;
int flags, fmt;
{
register struct disk *du;
du = &wddrives[wdunit(dev)];
du->dk_open-- ;
/*if (du->dk_open == 0) du->dk_state = CLOSED ; does not work */
}
wdioctl(dev,cmd,addr,flag)
dev_t dev;
caddr_t addr;
{
int unit = wdunit(dev);
register struct disk *du;
int error = 0;
struct uio auio;
struct iovec aiov;
/*int wdformat();*/
du = &wddrives[unit];
switch (cmd) {
case DIOCGDINFO:
*(struct disklabel *)addr = du->dk_dd;
break;
case DIOCGPART:
((struct partinfo *)addr)->disklab = &du->dk_dd;
((struct partinfo *)addr)->part =
&du->dk_dd.d_partitions[wdpart(dev)];
break;
case DIOCSDINFO:
if ((flag & FWRITE) == 0)
error = EBADF;
else
error = setdisklabel(&du->dk_dd,
(struct disklabel *)addr,
0 /*(dk->dk_state == OPENRAW) ? 0 : dk->dk_openpart*/);
/*if (error == 0 && dk->dk_state == OPENRAW &&
vdreset_drive(vddinfo[unit]))
dk->dk_state = OPEN;*/
wdsetctlr(dev, du);
break;
case DIOCWLABEL:
if ((flag & FWRITE) == 0)
error = EBADF;
else
du->dk_wlabel = *(int *)addr;
break;
case DIOCWDINFO:
if ((flag & FWRITE) == 0)
error = EBADF;
else if ((error = setdisklabel(&du->dk_dd, (struct disklabel *)addr,
0/*(dk->dk_state == OPENRAW) ? 0 : dk->dk_openpart*/)) == 0) {
int wlab;
/*if (error == 0 && dk->dk_state == OPENRAW &&
vdreset_drive(vddinfo[unit]))
dk->dk_state = OPEN; */
wdsetctlr(dev, du);
/* simulate opening partition 0 so write succeeds */
/* dk->dk_openpart |= (1 << 0); /* XXX */
wlab = du->dk_wlabel;
du->dk_wlabel = 1;
error = writedisklabel(dev, wdstrategy, &du->dk_dd,wdpart(dev));
/*dk->dk_openpart = dk->dk_copenpart | dk->dk_bopenpart;*/
du->dk_wlabel = wlab;
}
break;
#ifdef notyet
case DIOCGDINFOP:
*(struct disklabel **)addr = &(du->dk_dd);
break;
case DIOCWFORMAT:
if ((flag & FWRITE) == 0)
error = EBADF;
else {
register struct format_op *fop;
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 * du->dk_dd.d_secsize;
error = physio(wdformat, &rwdbuf[unit], dev, B_WRITE,
minphys, &auio);
fop->df_count -= auio.uio_resid;
fop->df_reg[0] = du->dk_status;
fop->df_reg[1] = du->dk_error;
}
break;
#endif
default:
error = ENOTTY;
break;
}
return (error);
}
/*wdformat(bp)
struct buf *bp;
{
bp->b_flags |= B_FORMAT;
return (wdstrategy(bp));
}*/
/*
* Routines to do raw IO for a unit.
*/
wdread(dev, uio) /* character read routine */
dev_t dev;
struct uio *uio;
{
int unit = wdunit(dev) ;
if (unit >= NWD) return(ENXIO);
return(physio(wdstrategy, &rwdbuf[unit], dev, B_READ, minphys, uio));
}
wdwrite(dev, uio) /* character write routine */
dev_t dev;
struct uio *uio;
{
int unit = wdunit(dev) ;
if (unit >= NWD) return(ENXIO);
return(physio(wdstrategy, &rwdbuf[unit], dev, B_WRITE, minphys, uio));
}
wdsize(dev)
dev_t dev;
{
register unit = wdunit(dev);
register part = wdpart(dev);
register struct disk *du;
register val ;
if (unit >= NWD) return(-1);
if (wddrives[unit].dk_state == 0) {
val = wdopen (dev, 0);
if (val < 0)
return (-1);
}
du = &wddrives[unit];
return((int)((u_long)du->dk_dd.d_partitions[part].p_size *
du->dk_dd.d_secsize / 512));
}
extern char *vmmap; /* poor name! */
wddump(dev) /* dump core after a system crash */
dev_t dev;
{
register struct disk *du; /* disk unit to do the IO */
register struct bt_bad *bt_ptr;
long num; /* number of sectors to write */
int unit, part;
long cyloff, blknum, blkcnt;
long cylin, head, sector, stat;
long secpertrk, secpercyl, nblocks, i;
char *addr;
extern int Maxmem;
static wddoingadump = 0 ;
extern CMAP1;
extern char CADDR1[];
#ifdef ARGO
outb(0x461,0); /* disable failsafe timer */
#endif
addr = (char *) 0; /* starting address */
/* size of memory to dump */
num = Maxmem;
unit = wdunit(dev); /* eventually support floppies? */
part = wdpart(dev); /* file system */
/* check for acceptable drive number */
if (unit >= NWD) return(ENXIO);
du = &wddrives[unit];
/* was it ever initialized ? */
if (du->dk_state < OPEN) return (ENXIO) ;
/* Convert to disk sectors */
num = (u_long) num * NBPG / du->dk_dd.d_secsize;
/* check if controller active */
/*if (wdtab.b_active) return(EFAULT); */
if (wddoingadump) return(EFAULT);
secpertrk = du->dk_dd.d_nsectors;
secpercyl = du->dk_dd.d_secpercyl;
nblocks = du->dk_dd.d_partitions[part].p_size;
cyloff = du->dk_dd.d_partitions[part].p_offset / secpercyl;
/*pg("xunit %x, nblocks %d, dumplo %d num %d\n", part,nblocks,dumplo,num);*/
/* check transfer bounds against partition size */
if ((dumplo < 0) || ((dumplo + num) > nblocks))
return(EINVAL);
/*wdtab.b_active = 1; /* mark controller active for if we
panic during the dump */
wddoingadump = 1 ; i = 100000 ;
while ((inb(wdc+wd_status) & WDCS_BUSY) && (i-- > 0)) ;
outb(wdc+wd_sdh, WDSD_IBM | (unit << 4));
outb(wdc+wd_command, WDCC_RESTORE | WD_STEP);
while (inb(wdc+wd_status) & WDCS_BUSY) ;
/* some compaq controllers require this ... */
wdsetctlr(dev, du);
blknum = dumplo;
while (num > 0) {
#ifdef notdef
if (blkcnt > MAXTRANSFER) blkcnt = MAXTRANSFER;
if ((blknum + blkcnt - 1) / secpercyl != blknum / secpercyl)
blkcnt = secpercyl - (blknum % secpercyl);
/* keep transfer within current cylinder */
#endif
pmap_enter(kernel_pmap, vmmap, addr, VM_PROT_READ, TRUE);
/* compute disk address */
cylin = blknum / secpercyl;
head = (blknum % secpercyl) / secpertrk;
sector = blknum % secpertrk;
cylin += cyloff;
#ifdef notyet
/*
* See if the current block is in the bad block list.
* (If we have one.)
*/
for (bt_ptr = dkbad[unit].bt_bad;
bt_ptr->bt_cyl != -1; bt_ptr++) {
if (bt_ptr->bt_cyl > cylin)
/* Sorted list, and we passed our cylinder.
quit. */
break;
if (bt_ptr->bt_cyl == cylin &&
bt_ptr->bt_trksec == (head << 8) + sector) {
/*
* Found bad block. Calculate new block addr.
* This starts at the end of the disk (skip the
* last track which is used for the bad block list),
* and works backwards to the front of the disk.
*/
blknum = (du->dk_dd.d_secperunit)
- du->dk_dd.d_nsectors
- (bt_ptr - dkbad[unit].bt_bad) - 1;
cylin = blknum / secpercyl;
head = (blknum % secpercyl) / secpertrk;
sector = blknum % secpertrk;
break;
}
#endif
sector++; /* origin 1 */
/* select drive. */
outb(wdc+wd_sdh, WDSD_IBM | (unit<<4) | (head & 0xf));
while ((inb(wdc+wd_status) & WDCS_READY) == 0) ;
/* transfer some blocks */
outb(wdc+wd_sector, sector);
outb(wdc+wd_seccnt,1);
outb(wdc+wd_cyl_lo, cylin);
outb(wdc+wd_cyl_hi, cylin >> 8);
#ifdef notdef
/* lets just talk about this first...*/
pg ("sdh 0%o sector %d cyl %d addr 0x%x",
inb(wdc+wd_sdh), inb(wdc+wd_sector),
inb(wdc+wd_cyl_hi)*256+inb(wdc+wd_cyl_lo), addr) ;
#endif
#ifdef ODYSSEUS
if(cylin < 46 || cylin > 91)pg("oops");
#endif
#ifdef PRIAM
if(cylin < 40 || cylin > 79)pg("oops");
#endif
outb(wdc+wd_command, WDCC_WRITE);
/* Ready to send data? */
while ((inb(wdc+wd_status) & WDCS_DRQ) == 0) ;
if (inb(wdc+wd_status) & WDCS_ERR) return(EIO) ;
outsw (wdc+wd_data, CADDR1+((int)addr&(NBPG-1)), 256);
(int) addr += 512;
if (inb(wdc+wd_status) & WDCS_ERR) return(EIO) ;
/* Check data request (should be done). */
if (inb(wdc+wd_status) & WDCS_DRQ) return(EIO) ;
/* wait for completion */
for ( i = 1000000 ; inb(wdc+wd_status) & WDCS_BUSY ; i--) {
if (i < 0) return (EIO) ;
}
/* error check the xfer */
if (inb(wdc+wd_status) & WDCS_ERR) return(EIO) ;
/* update block count */
num--;
blknum++ ;
if (num % 100 == 0) printf(".") ;
}
return(0);
}
#endif