885 lines
24 KiB
C
885 lines
24 KiB
C
/*
|
|
* Copyright (c) 1990, 1992 The Regents of the University of California.
|
|
* All rights reserved.
|
|
*
|
|
* This software was developed by the Computer Systems Engineering group
|
|
* at Lawrence Berkeley Laboratory under DARPA contract BG 91-66 and
|
|
* contributed to Berkeley.
|
|
*
|
|
* 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, Lawrence Berkeley Laboratory.
|
|
*
|
|
* %sccs.include.redist.c%
|
|
*
|
|
* %W% (Berkeley) %G%
|
|
*
|
|
* from: Header: sd.c,v 1.27 93/04/29 01:22:19 torek Exp
|
|
* $Id: sd.c,v 1.5 1994/02/01 06:01:30 deraadt Exp $
|
|
*/
|
|
|
|
/*
|
|
* SCSI CCS (Command Command Set) disk driver.
|
|
*
|
|
* MACHINE INDEPENDENT (do not put machine dependent goo in here!)
|
|
*
|
|
* (from sd.c,v 1.7 90/12/15 14:11:26 van Exp)
|
|
*/
|
|
|
|
#include <sys/param.h>
|
|
#include <sys/systm.h>
|
|
#include <sys/proc.h>
|
|
#include <sys/buf.h>
|
|
#include <sys/errno.h>
|
|
#include <sys/device.h>
|
|
#include <sys/disklabel.h>
|
|
#include <sys/dkstat.h>
|
|
#include <sys/disk.h>
|
|
#include <sys/ioctl.h>
|
|
#include <sys/malloc.h>
|
|
|
|
#include <sparc/scsi/scsi.h>
|
|
#include <sparc/scsi/disk.h>
|
|
#include <sparc/scsi/scsivar.h>
|
|
#include <sparc/scsi/scsi_ioctl.h>
|
|
|
|
#include <machine/cpu.h>
|
|
|
|
#include <sparc/scsi/sdtrace.h>
|
|
|
|
#ifdef sparc /* XXX */
|
|
#define SUN_LABEL_HACK /* XXX */
|
|
#endif /* XXX */
|
|
|
|
#ifdef SUN_LABEL_HACK
|
|
#include <sparc/scsi/sun_disklabel.h>
|
|
#endif
|
|
|
|
/*
|
|
* Per-disk variables.
|
|
*
|
|
* sd_dk contains all the `disk' specific stuff (label/partitions,
|
|
* transfer rate, etc). We need only things that are special to
|
|
* scsi disks. Note that our blocks are in terms of DEV_BSIZE blocks.
|
|
*/
|
|
struct sd_softc {
|
|
struct dkdevice sc_dk; /* base disk device, must be first */
|
|
struct unit sc_unit; /* scsi unit */
|
|
pid_t sc_format_pid; /* process using "format" mode */
|
|
u_char sc_type; /* drive type */
|
|
u_char sc_bshift; /* convert device blocks to DEV_BSIZE blks */
|
|
short sc_flags; /* see below */
|
|
u_int sc_blks; /* number of blocks on device */
|
|
int sc_blksize; /* device block size in bytes */
|
|
|
|
/* should be in dkdevice?? */
|
|
struct buf sc_tab; /* transfer queue */
|
|
|
|
/* statistics */
|
|
long sc_resets; /* number of times reset */
|
|
long sc_transfers; /* count of total transfers */
|
|
long sc_partials; /* count of `partial' transfers */
|
|
|
|
/* for user formatting */
|
|
struct scsi_cdb sc_cmd;
|
|
struct scsi_fmt_sense sc_sense;
|
|
};
|
|
|
|
#define SDF_ALIVE 1 /* drive OK for regular kernel use */
|
|
|
|
/* definition of the autoconfig driver */
|
|
int sdmatch __P((struct device *, struct cfdata *, void *));
|
|
void sdattach __P((struct device *, struct device *, void *));
|
|
|
|
struct cfdriver sdcd =
|
|
{ NULL, "sd", sdmatch, sdattach, DV_DISK, sizeof(struct sd_softc) };
|
|
|
|
/* definition of the unit driver, for hba */
|
|
void sdigo __P((struct device *, struct scsi_cdb *));
|
|
void sdgo __P((struct device *, struct scsi_cdb *));
|
|
void sdintr __P((struct device *, int, int));
|
|
void sdreset __P((struct unit *));
|
|
|
|
static struct unitdriver sdunitdriver = { /*sdgo, sdintr*/ sdreset };
|
|
|
|
/* definition of the disk driver, for kernel */
|
|
void sdstrategy __P((struct buf *));
|
|
|
|
static struct dkdriver sddkdriver = { sdstrategy };
|
|
|
|
#ifdef DEBUG
|
|
int sddebug = 1;
|
|
#define SDB_ERROR 0x01
|
|
#define SDB_PARTIAL 0x02
|
|
#endif
|
|
|
|
#define sdunit(x) (minor(x) >> 3)
|
|
#define sdpart(x) (minor(x) & 0x7)
|
|
|
|
#define b_cylin b_resid
|
|
|
|
#define SDRETRY 2
|
|
|
|
/*
|
|
* Table of scsi commands users are allowed to access via `format'
|
|
* mode. 0 means not legal. 1 means `immediate' (doesn't need dma).
|
|
* -1 means needs dma and/or wait for intr (i.e., `slow').
|
|
*/
|
|
static char legal_cmds[256] = {
|
|
/***** 0 1 2 3 4 5 6 7 8 9 A B C D E F */
|
|
/*00*/ 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
/*10*/ 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0,
|
|
/*20*/ 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
/*30*/ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
/*40*/ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
/*50*/ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
/*60*/ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
/*70*/ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
/*80*/ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
/*90*/ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
/*a0*/ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
/*b0*/ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
/*c0*/ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
/*d0*/ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
/*e0*/ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
/*f0*/ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
};
|
|
|
|
int
|
|
sdmatch(parent, cf, aux)
|
|
struct device *parent;
|
|
register struct cfdata *cf;
|
|
void *aux;
|
|
{
|
|
register struct scsi_attach_args *sa = aux;
|
|
#ifdef DEBUG
|
|
char *whynot;
|
|
#endif
|
|
|
|
/*
|
|
* unit number must match, or be given as `any'
|
|
*/
|
|
if (cf->cf_loc[0] != -1 && cf->cf_loc[0] != sa->sa_unit)
|
|
return (0);
|
|
/*
|
|
* drive must be a disk, and of a kind we recognize
|
|
*/
|
|
if ((sa->sa_inq_status & STS_MASK) != STS_GOOD) {
|
|
#ifdef DEBUG
|
|
whynot = "INQUIRY failed";
|
|
#endif
|
|
goto notdisk;
|
|
}
|
|
|
|
switch (sa->sa_si.si_type & TYPE_TYPE_MASK) {
|
|
|
|
case TYPE_DAD: /* disk */
|
|
case TYPE_WORM: /* WORM */
|
|
case TYPE_ROM: /* CD-ROM */
|
|
case TYPE_MO: /* Magneto-optical */
|
|
case TYPE_JUKEBOX: /* medium changer */
|
|
break;
|
|
|
|
default:
|
|
notdisk:
|
|
#ifdef DEBUG
|
|
whynot = "not a disk";
|
|
printf("[not matching `sd' at unit %d: %s]\n",
|
|
sa->sa_unit, whynot);
|
|
#endif
|
|
return (0);
|
|
}
|
|
|
|
/*
|
|
* It is a disk of some kind; take it. We will figure out
|
|
* the rest in the attach routine.
|
|
*/
|
|
return (1);
|
|
}
|
|
|
|
/*
|
|
* Attach a disk (called after sdmatch returns true).
|
|
* Note that this routine is never reentered (so we can use statics).
|
|
*/
|
|
void
|
|
sdattach(parent, self, aux)
|
|
struct device *parent, *self;
|
|
void *aux;
|
|
{
|
|
register struct sd_softc *sc = (struct sd_softc *)self;
|
|
register struct scsi_attach_args *sa = aux;
|
|
register int i;
|
|
char vendor[10], drive[17], rev[5];
|
|
static u_char capbuf[8];
|
|
static struct scsi_cdb cap = { CMD_READ_CAPACITY };
|
|
#ifdef SUN_LABEL_HACK
|
|
static struct scsi_cdb rd0 = { CMD_READ10, 0, 0, 0, 0, 0, 0, 0, 1, 0 };
|
|
caddr_t sector;
|
|
#endif
|
|
|
|
/*
|
|
* Declare our existence.
|
|
*/
|
|
sc->sc_unit.u_driver = &sdunitdriver;
|
|
scsi_establish(&sc->sc_unit, &sc->sc_dk.dk_dev, sa->sa_unit);
|
|
|
|
/*
|
|
* Figure out what kind of disk this is.
|
|
* We only accepted it if the inquiry succeeded, so
|
|
* we can inspect those fields.
|
|
*/
|
|
i = (sa->sa_si.si_version >> VER_ANSI_SHIFT) & VER_ANSI_MASK;
|
|
if (i == 1 || i == 2) {
|
|
scsi_inq_ansi((struct scsi_inq_ansi *)&sa->sa_si,
|
|
vendor, drive, rev);
|
|
printf(": %s %s", vendor, drive);
|
|
/* XXX should we even ever bother printing this? */
|
|
if (rev[0])
|
|
printf(" %s", rev);
|
|
} else {
|
|
/* bleah */
|
|
bcopy("<unknown>", vendor, 10);
|
|
bcopy("<unknown>", drive, 10);
|
|
printf(": type 0x%x, qual 0x%x, ver %d",
|
|
sa->sa_si.si_type, sa->sa_si.si_qual,
|
|
sa->sa_si.si_version);
|
|
}
|
|
|
|
CDB10(&cap)->cdb_lun_rel = sc->sc_unit.u_unit << 5;
|
|
i = (*sc->sc_unit.u_hbd->hd_icmd)(sc->sc_unit.u_hba,
|
|
sc->sc_unit.u_targ, &cap, (char *)capbuf, sizeof capbuf, B_READ);
|
|
i &= STS_MASK;
|
|
if (i == STS_GOOD) {
|
|
#define NUMBER(p) (((p)[0] << 24) | ((p)[1] << 16) | ((p)[2] << 8) | (p)[3])
|
|
sc->sc_blks = NUMBER(&capbuf[0]);
|
|
sc->sc_blksize = NUMBER(&capbuf[4]);
|
|
} else if (i == STS_CHECKCOND &&
|
|
(strcmp(vendor, "HP") == 0 && strcmp(drive, "S6300.650A") == 0)) {
|
|
/* XXX unformatted or nonexistent MO medium: fake it */
|
|
sc->sc_blks = 318664;
|
|
sc->sc_blksize = 1024;
|
|
} else {
|
|
/* XXX shouldn't bail for removable media */
|
|
printf(": unable to determine drive capacity [sts=%x]\n", i);
|
|
return;
|
|
}
|
|
/* return value from read capacity is last valid block, not nblocks */
|
|
sc->sc_blks++;
|
|
printf(", %u %d byte blocks\n", sc->sc_blks, sc->sc_blksize);
|
|
if (sc->sc_blksize != DEV_BSIZE) {
|
|
for (i = sc->sc_blksize; i > DEV_BSIZE; i >>= 1)
|
|
++sc->sc_bshift;
|
|
if (i != DEV_BSIZE) {
|
|
printf("%s: blksize not multiple of %d: cannot use\n",
|
|
sc->sc_dk.dk_dev.dv_xname, DEV_BSIZE);
|
|
return;
|
|
}
|
|
sc->sc_blks <<= sc->sc_bshift;
|
|
}
|
|
sc->sc_type = sa->sa_si.si_type; /* sufficient? */
|
|
|
|
sc->sc_dk.dk_driver = &sddkdriver;
|
|
#ifdef notyet
|
|
dk_establish(&sc->sc_dk);
|
|
/* READ DISK LABEL HERE, UNLESS REMOVABLE MEDIUM... NEEDS THOUGHT */
|
|
#else
|
|
sc->sc_dk.dk_label.d_secsize = 512; /* XXX */
|
|
sc->sc_dk.dk_bps = (3600/60) * 32 * 512;/* XXX */
|
|
|
|
#ifdef SUN_LABEL_HACK
|
|
sector = (caddr_t)malloc(sc->sc_blksize, M_DEVBUF, M_NOWAIT);
|
|
CDB10(&rd0)->cdb_lun_rel = sc->sc_unit.u_unit << 5;
|
|
i = (*sc->sc_unit.u_hbd->hd_icmd)(sc->sc_unit.u_hba,
|
|
sc->sc_unit.u_targ, &rd0, sector, sc->sc_blksize, B_READ);
|
|
if (i == STS_GOOD) {
|
|
printf("%s: <%s>\n", sc->sc_dk.dk_dev.dv_xname,
|
|
((struct sun_disklabel *)sector)->sl_text);
|
|
if (sun_disklabel(sector, &sc->sc_dk.dk_label))
|
|
sc->sc_flags |= SDF_ALIVE;
|
|
else
|
|
printf("%s: sun_disklabel fails\n",
|
|
sc->sc_dk.dk_dev.dv_xname);
|
|
} else
|
|
printf("%s: could not read sector 0 for disk label\n",
|
|
sc->sc_dk.dk_dev.dv_xname);
|
|
free(sector, M_DEVBUF);
|
|
#endif
|
|
#endif /* notyet */
|
|
}
|
|
|
|
/*
|
|
* Reset a disk, after a SCSI bus reset.
|
|
*
|
|
* XXX untested and probably incomplete/incorrect
|
|
*/
|
|
void
|
|
sdreset(u)
|
|
register struct unit *u;
|
|
{
|
|
register struct sd_softc *sc = (struct sd_softc *)u->u_dev;
|
|
|
|
printf(" %s", sc->sc_dk.dk_dev.dv_xname);
|
|
sc->sc_resets++;
|
|
}
|
|
|
|
/* dev_t is short, must use prototype syntax */
|
|
int
|
|
sdopen(dev_t dev, int flags, int ifmt, struct proc *p)
|
|
{
|
|
register int unit = sdunit(dev);
|
|
register struct sd_softc *sc;
|
|
|
|
if (unit >= sdcd.cd_ndevs || (sc = sdcd.cd_devs[unit]) == NULL)
|
|
return (ENXIO);
|
|
if ((sc->sc_flags & SDF_ALIVE) == 0 && suser(p->p_ucred, &p->p_acflag))
|
|
return (ENXIO);
|
|
return (0);
|
|
}
|
|
|
|
int
|
|
sdclose(dev_t dev, int flags, int ifmt, struct proc *p)
|
|
{
|
|
register struct sd_softc *sc = sdcd.cd_devs[sdunit(dev)];
|
|
|
|
sc->sc_format_pid = 0;
|
|
return (0);
|
|
}
|
|
|
|
/*
|
|
* This routine is called for partial block transfers and non-aligned
|
|
* transfers (the latter only being possible on devices with a block size
|
|
* larger than DEV_BSIZE). The operation is performed in three steps
|
|
* using a locally allocated buffer:
|
|
* 1. transfer any initial partial block
|
|
* 2. transfer full blocks
|
|
* 3. transfer any final partial block
|
|
*/
|
|
static void
|
|
sdlblkstrat(bp, bsize)
|
|
register struct buf *bp;
|
|
register int bsize;
|
|
{
|
|
register int bn, resid, boff, count;
|
|
register caddr_t addr, cbuf;
|
|
struct buf *tbp;
|
|
|
|
/* should probably use geteblk() here, but I fear consequences */
|
|
cbuf = (caddr_t)malloc(bsize, M_DEVBUF, M_WAITOK);
|
|
tbp = (struct buf *)malloc(sizeof *tbp, M_DEVBUF, M_WAITOK);
|
|
bzero((caddr_t)tbp, sizeof *tbp);
|
|
tbp->b_proc = curproc;
|
|
tbp->b_dev = bp->b_dev;
|
|
bn = bp->b_blkno;
|
|
resid = bp->b_bcount;
|
|
addr = bp->b_un.b_addr;
|
|
#ifdef DEBUG
|
|
if (sddebug & SDB_PARTIAL)
|
|
printf("sdlblkstrat: bp %x flags %x bn %x resid %x addr %x\n",
|
|
bp, bp->b_flags, bn, resid, addr);
|
|
#endif
|
|
|
|
while (resid > 0) {
|
|
boff = dbtob(bn) & (bsize - 1);
|
|
if (boff || resid < bsize) {
|
|
struct sd_softc *sc = sdcd.cd_devs[sdunit(bp->b_dev)];
|
|
sc->sc_partials++;
|
|
count = min(resid, bsize - boff);
|
|
tbp->b_flags = B_BUSY | B_READ;
|
|
tbp->b_blkno = bn - btodb(boff);
|
|
tbp->b_un.b_addr = cbuf;
|
|
tbp->b_bcount = bsize;
|
|
#ifdef DEBUG
|
|
if (sddebug & SDB_PARTIAL)
|
|
printf(" readahead: bn %x cnt %x off %x addr %x\n",
|
|
tbp->b_blkno, count, boff, addr);
|
|
#endif
|
|
sdstrategy(tbp);
|
|
biowait(tbp);
|
|
if (tbp->b_flags & B_ERROR) {
|
|
bp->b_flags |= B_ERROR;
|
|
bp->b_error = tbp->b_error;
|
|
break;
|
|
}
|
|
if (bp->b_flags & B_READ) {
|
|
bcopy(&cbuf[boff], addr, count);
|
|
goto done;
|
|
}
|
|
bcopy(addr, &cbuf[boff], count);
|
|
#ifdef DEBUG
|
|
if (sddebug & SDB_PARTIAL)
|
|
printf(" writeback: bn %x cnt %x off %x addr %x\n",
|
|
tbp->b_blkno, count, boff, addr);
|
|
#endif
|
|
} else {
|
|
count = resid & ~(bsize - 1);
|
|
tbp->b_blkno = bn;
|
|
tbp->b_un.b_addr = addr;
|
|
tbp->b_bcount = count;
|
|
#ifdef DEBUG
|
|
if (sddebug & SDB_PARTIAL)
|
|
printf(" fulltrans: bn %x cnt %x addr %x\n",
|
|
tbp->b_blkno, count, addr);
|
|
#endif
|
|
}
|
|
tbp->b_flags = B_BUSY | (bp->b_flags & B_READ);
|
|
sdstrategy(tbp);
|
|
biowait(tbp);
|
|
if (tbp->b_flags & B_ERROR) {
|
|
bp->b_flags |= B_ERROR;
|
|
bp->b_error = tbp->b_error;
|
|
break;
|
|
}
|
|
done:
|
|
bn += btodb(count);
|
|
resid -= count;
|
|
addr += count;
|
|
#ifdef DEBUG
|
|
if (sddebug & SDB_PARTIAL)
|
|
printf(" done: bn %x resid %x addr %x\n",
|
|
bn, resid, addr);
|
|
#endif
|
|
}
|
|
free(cbuf, M_DEVBUF);
|
|
free((caddr_t)tbp, M_DEVBUF);
|
|
biodone(bp);
|
|
}
|
|
|
|
/*
|
|
* Start a transfer on sc as described by bp
|
|
* (i.e., call hba or target start).
|
|
* If in format mode, we may not need dma.
|
|
*/
|
|
#define sdstart(sc, bp) { \
|
|
SD_TRACE(T_START, sc, bp); \
|
|
if ((sc)->sc_format_pid && legal_cmds[(sc)->sc_cmd.cdb_bytes[0]] > 0) \
|
|
(*(sc)->sc_unit.u_start)((sc)->sc_unit.u_updev, \
|
|
&(sc)->sc_unit.u_forw, (struct buf *)NULL, \
|
|
sdigo, &(sc)->sc_dk.dk_dev); \
|
|
else \
|
|
(*(sc)->sc_unit.u_start)((sc)->sc_unit.u_updev, \
|
|
&(sc)->sc_unit.u_forw, bp, sdgo, &(sc)->sc_dk.dk_dev); \
|
|
}
|
|
|
|
void
|
|
sdstrategy(bp)
|
|
register struct buf *bp;
|
|
{
|
|
register struct sd_softc *sc = sdcd.cd_devs[sdunit(bp->b_dev)];
|
|
register int s;
|
|
|
|
if (sc->sc_format_pid) {
|
|
/* XXXXXXXXX SHOULD NOT COMPARE curproc IN HERE!?! */
|
|
/*
|
|
* In format mode, only allow the owner to mess
|
|
* with the drive. Skip all the partition checks.
|
|
*/
|
|
if (sc->sc_format_pid != curproc->p_pid) {
|
|
bp->b_error = EPERM;
|
|
bp->b_flags |= B_ERROR;
|
|
biodone(bp);
|
|
return;
|
|
}
|
|
bp->b_cylin = 0;
|
|
} else {
|
|
register daddr_t bn = bp->b_blkno;
|
|
register int sz = howmany(bp->b_bcount, DEV_BSIZE);
|
|
register struct partition *p;
|
|
|
|
/*
|
|
* Make sure transfer is within partition.
|
|
* If it starts at the end, return EOF; if
|
|
* it extends past the end, truncate it.
|
|
*/
|
|
p = &sc->sc_dk.dk_label.d_partitions[sdpart(bp->b_dev)];
|
|
if ((unsigned)bn >= p->p_size) {
|
|
if ((unsigned)bn > p->p_size) {
|
|
bp->b_error = EINVAL;
|
|
bp->b_flags |= B_ERROR;
|
|
} else
|
|
bp->b_resid = bp->b_bcount;
|
|
biodone(bp);
|
|
return;
|
|
}
|
|
if (bn + sz > p->p_size) {
|
|
sz = p->p_size - bn;
|
|
bp->b_bcount = dbtob(sz);
|
|
}
|
|
/*
|
|
* Non-aligned or partial-block transfers handled specially.
|
|
* SHOULD THIS BE AT A HIGHER LEVEL?
|
|
*/
|
|
s = sc->sc_blksize - 1;
|
|
if ((dbtob(bn) & s) || (bp->b_bcount & s)) {
|
|
sdlblkstrat(bp, sc->sc_blksize);
|
|
return;
|
|
}
|
|
bp->b_cylin = (bn + p->p_offset) >> sc->sc_bshift;
|
|
}
|
|
|
|
/*
|
|
* Transfer valid, or format mode. Queue the request
|
|
* on the drive, and maybe try to start it.
|
|
*/
|
|
s = splbio();
|
|
disksort(&sc->sc_tab, bp);
|
|
if (sc->sc_tab.b_active == 0) {
|
|
sc->sc_tab.b_active = 1;
|
|
sdstart(sc, bp);
|
|
}
|
|
splx(s);
|
|
}
|
|
|
|
int
|
|
sderror(sc, stat)
|
|
register struct sd_softc *sc;
|
|
register int stat;
|
|
{
|
|
register struct scsi_sense *sn;
|
|
int retry = 0;
|
|
|
|
sc->sc_sense.status = stat;
|
|
if ((stat & STS_MASK) == STS_CHECKCOND) {
|
|
sn = (struct scsi_sense *)sc->sc_sense.sense;
|
|
stat = scsi_request_sense(sc->sc_unit.u_hba,
|
|
sc->sc_unit.u_targ, sc->sc_unit.u_unit,
|
|
(caddr_t)sn, sizeof sc->sc_sense.sense);
|
|
sc->sc_sense.status = stat; /* ??? */
|
|
if ((stat & STS_MASK) != STS_GOOD) {
|
|
printf("%s: sense failed, status %x\n",
|
|
sc->sc_dk.dk_dev.dv_xname, stat);
|
|
return (0);
|
|
}
|
|
printf("%s: scsi sense class %d, code %d",
|
|
sc->sc_dk.dk_dev.dv_xname,
|
|
SENSE_ECLASS(sn), SENSE_ECODE(sn));
|
|
if (SENSE_ISXSENSE(sn) && XSENSE_ISSTD(sn)) {
|
|
int key;
|
|
|
|
/*
|
|
* Standard extended sense: can examine sense key
|
|
* and (if valid) info.
|
|
*/
|
|
key = XSENSE_KEY(sn);
|
|
printf(", key %d", key);
|
|
if (XSENSE_IVALID(sn))
|
|
printf(", blk %d", XSENSE_INFO(sn));
|
|
/* no sense or recovered error, try again */
|
|
if (key == 0 || key == 1)
|
|
retry = 1;
|
|
}
|
|
printf("\n");
|
|
}
|
|
return (retry);
|
|
}
|
|
|
|
/*
|
|
* sdigo is called from the hba driver when it has got the scsi bus
|
|
* for us, and we were doing a format op that did not need dma.
|
|
*/
|
|
void
|
|
sdigo(sc0, cdb)
|
|
struct device *sc0;
|
|
struct scsi_cdb *cdb;
|
|
{
|
|
register struct sd_softc *sc = (struct sd_softc *)sc0;
|
|
register struct buf *bp = sc->sc_tab.b_actf;
|
|
register int stat;
|
|
|
|
stat = (*sc->sc_unit.u_hbd->hd_icmd)(sc->sc_unit.u_hba,
|
|
sc->sc_unit.u_targ, &sc->sc_cmd, bp->b_un.b_addr, bp->b_bcount,
|
|
bp->b_flags & B_READ);
|
|
sc->sc_sense.status = stat;
|
|
if (stat & 0xfe) { /* XXX */
|
|
(void) sderror(sc, stat);
|
|
bp->b_flags |= B_ERROR;
|
|
bp->b_error = EIO;
|
|
}
|
|
/*
|
|
* Done with SCSI bus, before we `ought' to be. Release it.
|
|
*/
|
|
(*sc->sc_unit.u_rel)(sc->sc_unit.u_updev);
|
|
bp->b_resid = 0;
|
|
sc->sc_tab.b_errcnt = 0;
|
|
sc->sc_tab.b_actf = bp->b_actf;
|
|
biodone(bp);
|
|
if ((bp = sc->sc_tab.b_actf) == NULL)
|
|
sc->sc_tab.b_active = 0;
|
|
else
|
|
sdstart(sc, bp);
|
|
}
|
|
|
|
/*
|
|
* sdgo is called from the hba driver or target code when it has
|
|
* allocated the scsi bus and DMA resources and target datapath for us.
|
|
*/
|
|
void
|
|
sdgo(sc0, cdb)
|
|
struct device *sc0;
|
|
register struct scsi_cdb *cdb;
|
|
{
|
|
register struct sd_softc *sc = (struct sd_softc *)sc0;
|
|
register struct buf *bp = sc->sc_tab.b_actf;
|
|
register int n;
|
|
register unsigned int u;
|
|
|
|
SD_TRACE(T_MKCDB, sc, bp);
|
|
if (sc->sc_format_pid) {
|
|
*cdb = sc->sc_cmd;
|
|
n = 0;
|
|
} else {
|
|
CDB10(cdb)->cdb_cmd = bp->b_flags & B_READ ? CMD_READ10 :
|
|
CMD_WRITE10;
|
|
CDB10(cdb)->cdb_lun_rel = sc->sc_unit.u_unit << 5;
|
|
u = bp->b_cylin;
|
|
CDB10(cdb)->cdb_lbah = u >> 24;
|
|
CDB10(cdb)->cdb_lbahm = u >> 16;
|
|
CDB10(cdb)->cdb_lbalm = u >> 8;
|
|
CDB10(cdb)->cdb_lbal = u;
|
|
CDB10(cdb)->cdb_xxx = 0;
|
|
n = sc->sc_blksize - 1;
|
|
u = (bp->b_bcount + n) >> (DEV_BSHIFT + sc->sc_bshift);
|
|
CDB10(cdb)->cdb_lenh = u >> 8;
|
|
CDB10(cdb)->cdb_lenl = u;
|
|
CDB10(cdb)->cdb_ctrl = 0;
|
|
n = (bp->b_bcount & n) != 0;
|
|
#ifdef DEBUG
|
|
if (n)
|
|
printf("%s: partial block xfer -- %x bytes\n",
|
|
sc->sc_dk.dk_dev.dv_xname, bp->b_bcount);
|
|
#endif
|
|
sc->sc_transfers++;
|
|
}
|
|
if ((*sc->sc_unit.u_go)(sc->sc_unit.u_updev, sc->sc_unit.u_targ,
|
|
sdintr, (void *)sc, bp, n) == 0) {
|
|
#ifdef notyet
|
|
sc->sc_dk.dk_busy = 1;
|
|
sc->sc_dk.dk_seek++; /* XXX */
|
|
sc->sc_dk.dk_xfer++;
|
|
sc->sc_dk.dk_wds += bp->b_bcount >> 6;
|
|
#endif
|
|
return;
|
|
}
|
|
/*
|
|
* Some sort of nasty unrecoverable error: clobber the
|
|
* transfer. Call the bus release function first, though.
|
|
*/
|
|
(*sc->sc_unit.u_rel)(sc->sc_unit.u_updev);
|
|
#ifdef DEBUG
|
|
if (sddebug & SDB_ERROR)
|
|
printf("%s: sdgo: %s adr %d blk %d len %d ecnt %d\n",
|
|
sc->sc_dk.dk_dev.dv_xname,
|
|
bp->b_flags & B_READ? "read" : "write",
|
|
bp->b_un.b_addr, bp->b_cylin, bp->b_bcount,
|
|
sc->sc_tab.b_errcnt);
|
|
#endif
|
|
bp->b_flags |= B_ERROR;
|
|
bp->b_error = EIO;
|
|
bp->b_resid = 0;
|
|
sc->sc_tab.b_errcnt = 0;
|
|
sc->sc_tab.b_actf = bp->b_actf;
|
|
biodone(bp);
|
|
if ((bp = sc->sc_tab.b_actf) == NULL)
|
|
sc->sc_tab.b_active = 0;
|
|
else
|
|
sdstart(sc, bp);
|
|
}
|
|
|
|
/*
|
|
* A transfer finished (or, someday, disconnected).
|
|
* We are already off the target/hba queues.
|
|
* Restart this one for error recovery, or start the next, as appropriate.
|
|
*/
|
|
void
|
|
sdintr(sc0, stat, resid)
|
|
struct device *sc0;
|
|
int stat, resid;
|
|
{
|
|
register struct sd_softc *sc = (struct sd_softc *)sc0;
|
|
register struct buf *bp = sc->sc_tab.b_actf;
|
|
int retry;
|
|
|
|
if (bp == NULL)
|
|
panic("sdintr");
|
|
SD_TRACE(T_INTR, sc, bp);
|
|
#ifdef notyet
|
|
sc->sc_dk.dk_busy = 0;
|
|
#endif
|
|
if ((stat & STS_MASK) != STS_GOOD) {
|
|
#ifdef DEBUG
|
|
if (sddebug & SDB_ERROR)
|
|
printf("%s: sdintr scsi status 0x%x resid %d\n",
|
|
sc->sc_dk.dk_dev.dv_xname, stat, resid);
|
|
#endif
|
|
retry = sderror(sc, stat);
|
|
if (retry && ++sc->sc_tab.b_errcnt <= SDRETRY) {
|
|
printf("%s: retry %d\n",
|
|
sc->sc_dk.dk_dev.dv_xname, sc->sc_tab.b_errcnt);
|
|
goto restart;
|
|
}
|
|
bp->b_flags |= B_ERROR;
|
|
bp->b_error = EIO;
|
|
}
|
|
bp->b_resid = resid;
|
|
sc->sc_tab.b_errcnt = 0;
|
|
sc->sc_tab.b_actf = bp->b_actf;
|
|
biodone(bp);
|
|
if ((bp = sc->sc_tab.b_actf) == NULL)
|
|
sc->sc_tab.b_active = 0;
|
|
else {
|
|
restart:
|
|
sdstart(sc, bp);
|
|
}
|
|
}
|
|
|
|
int
|
|
sdioctl(dev_t dev, int cmd, register caddr_t data, int flag, struct proc *p)
|
|
{
|
|
register struct sd_softc *sc = sdcd.cd_devs[sdunit(dev)];
|
|
#ifdef COMPAT_SUNOS
|
|
int error;
|
|
|
|
error = sun_dkioctl(&sc->sc_dk, cmd, data, sdpart(dev));
|
|
if (error >= 0)
|
|
return (error);
|
|
#endif
|
|
switch (cmd) {
|
|
|
|
case SDIOCSFORMAT:
|
|
/* take this device into or out of "format" mode */
|
|
if (suser(p->p_ucred, &p->p_acflag))
|
|
return (EPERM);
|
|
if (*(int *)data) {
|
|
if (sc->sc_format_pid)
|
|
return (EPERM);
|
|
sc->sc_format_pid = p->p_pid;
|
|
} else
|
|
sc->sc_format_pid = 0;
|
|
break;
|
|
|
|
case SDIOCGFORMAT:
|
|
/* find out who has the device in format mode */
|
|
*(int *)data = sc->sc_format_pid;
|
|
break;
|
|
|
|
case SDIOCSCSICOMMAND:
|
|
#define cdb ((struct scsi_cdb *)data)
|
|
/*
|
|
* Save what user gave us as SCSI cdb to use with next
|
|
* read or write to the char device. Be sure to replace
|
|
* the lun field with the actual unit number.
|
|
*/
|
|
if (sc->sc_format_pid != p->p_pid)
|
|
return (EPERM);
|
|
if (legal_cmds[cdb->cdb_bytes[0]] == 0)
|
|
return (EINVAL);
|
|
sc->sc_cmd = *cdb;
|
|
sc->sc_cmd.cdb_bytes[1] =
|
|
(sc->sc_cmd.cdb_bytes[1] & ~(7 << 5)) |
|
|
(sc->sc_unit.u_unit << 5);
|
|
#undef cdb
|
|
break;
|
|
|
|
case SDIOCSENSE:
|
|
/*
|
|
* return the SCSI sense data saved after the last
|
|
* operation that completed with "check condition" status.
|
|
*/
|
|
sc->sc_sense = *(struct scsi_fmt_sense *)data;
|
|
break;
|
|
|
|
case DIOCGDINFO:
|
|
*(struct disklabel *)data = sc->sc_dk.dk_label;
|
|
break;
|
|
|
|
case DIOCGPART:
|
|
((struct partinfo *)data)->disklab = &sc->sc_dk.dk_label;
|
|
((struct partinfo *)data)->part =
|
|
&sc->sc_dk.dk_label.d_partitions[sdpart(dev)];
|
|
break;
|
|
|
|
default:
|
|
return (ENOTTY);
|
|
}
|
|
return (0);
|
|
}
|
|
|
|
int
|
|
sdsize(dev_t dev)
|
|
{
|
|
register int unit = sdunit(dev);
|
|
register struct sd_softc *sc;
|
|
|
|
if (unit >= sdcd.cd_ndevs || (sc = sdcd.cd_devs[unit]) == NULL ||
|
|
(sc->sc_flags & SDF_ALIVE) == 0)
|
|
return (-1);
|
|
return (sc->sc_dk.dk_label.d_partitions[sdpart(dev)].p_size);
|
|
}
|
|
|
|
/*
|
|
* Write `len' bytes from address `addr' to drive and partition in `dev',
|
|
* at block blkoff from the beginning of the partition. The address is
|
|
* either kernel virtual or physical (some machines may never use one or
|
|
* the other, but we need it in the protocol to stay machine-independent).
|
|
*/
|
|
int
|
|
sddump(dev_t dev, daddr_t blkoff, caddr_t addr, int len)
|
|
{
|
|
register struct sd_softc *sc;
|
|
register struct partition *p;
|
|
register daddr_t bn, n, nblks;
|
|
register struct hba_softc *hba;
|
|
register int stat, unit;
|
|
struct scsi_cdb cdb;
|
|
|
|
/* drive ok? */
|
|
unit = sdunit(dev);
|
|
if (unit >= sdcd.cd_ndevs || (sc = sdcd.cd_devs[unit]) == NULL ||
|
|
(sc->sc_flags & SDF_ALIVE) == 0)
|
|
return (ENXIO);
|
|
|
|
/* blocks in range? */
|
|
p = &sc->sc_dk.dk_label.d_partitions[sdpart(dev)];
|
|
n = (len + sc->sc_blksize - 1) >> DEV_BSHIFT;
|
|
if (blkoff < 0 || blkoff >= p->p_size || blkoff + n > p->p_size)
|
|
return (EINVAL);
|
|
bn = blkoff + p->p_offset;
|
|
bn >>= sc->sc_bshift;
|
|
|
|
/* scsi bus idle? */
|
|
hba = sc->sc_unit.u_hba;
|
|
if (hba->hba_head) {
|
|
(*hba->hba_driver->hd_reset)(hba, 0);
|
|
printf("[reset %s] ", sc->sc_dk.dk_dev.dv_xname);
|
|
}
|
|
|
|
CDB10(&cdb)->cdb_cmd = CMD_WRITE10;
|
|
CDB10(&cdb)->cdb_lun_rel = sc->sc_unit.u_unit << 5;
|
|
CDB10(&cdb)->cdb_xxx = 0;
|
|
CDB10(&cdb)->cdb_ctrl = 0;
|
|
|
|
#define DUMP_MAX (32 * 1024) /* no more than 32k per write */
|
|
for (;;) {
|
|
if ((n = len) > DUMP_MAX)
|
|
n = DUMP_MAX;
|
|
CDB10(&cdb)->cdb_lbah = bn >> 24;
|
|
CDB10(&cdb)->cdb_lbahm = bn >> 16;
|
|
CDB10(&cdb)->cdb_lbalm = bn >> 8;
|
|
CDB10(&cdb)->cdb_lbal = bn;
|
|
nblks = n >> (DEV_BSHIFT + sc->sc_bshift);
|
|
CDB10(&cdb)->cdb_lenh = nblks >> 8;
|
|
CDB10(&cdb)->cdb_lenl = nblks;
|
|
stat = hba->hba_driver->hd_dump(hba, sc->sc_unit.u_targ,
|
|
&cdb, addr, n);
|
|
if ((stat & STS_MASK) != STS_GOOD) {
|
|
printf("%s: scsi write error 0x%x\ndump ",
|
|
sc->sc_dk.dk_dev.dv_xname, stat);
|
|
return (EIO);
|
|
}
|
|
if ((len -= n) == 0)
|
|
return (0);
|
|
addr += n;
|
|
bn += nblks;
|
|
}
|
|
}
|