4.4BSD/usr/src/sys/news3400/iodev/sd.c

Compare this file to the similar file:
Show the results in this format:

/*
 * Copyright (c) 1992, 1993
 *	The Regents of the University of California.  All rights reserved.
 *
 * This code is derived from software contributed to Berkeley by
 * Sony Corp. and Kazumasa Utashiro of Software Research Associates, Inc.
 *
 * 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.
 *
 * from: $Hdr: sd.c,v 4.300 91/06/27 20:42:56 root Rel41 $ SONY
 *
 *	@(#)sd.c	8.1 (Berkeley) 6/11/93
 */
#define	dkblock(bp)	bp->b_blkno

/*
 * Copyright (c) 1987-1991 by SONY Corporation.
 */

#include "sd.h"
#if NSD > 0

#include <sys/param.h>
#include <sys/buf.h>
#include <sys/proc.h>
#include <sys/user.h>
#include <sys/dkstat.h>
#include <sys/uio.h>
#include <sys/kernel.h>
#include <sys/reboot.h>
#include <sys/ioctl.h>
#include <sys/systm.h>
#include <sys/mtio.h>
#include <sys/stat.h>
#include <sys/disklabel.h>
#include <vm/vm.h>
#include <sys/syslog.h>

#include <ufs/ffs/fs.h>

# include <machine/cpu.h>

#ifdef IPC_MRX
# include "../iop/iopvar.h"
# include "../ipc/newsipc.h"
#endif

#ifdef CPU_SINGLE
# include <news3400/hbdev/hbvar.h>
# include <news3400/iodev/ioptohb.h>
#endif

#include <news3400/iodev/scsireg.h>
#include <news3400/iodev/scu.h>
#include <news3400/iodev/dkio.h>
#include <news3400/iodev/sdreg.h>

#define	sce_sdecode	sce_hdecode

#define	dev2unit(x)	((minor(x) & ~0x80) >> 3)
#define	dev2part(x)	(minor(x) & 0x7)

/* /sys/sys/file.h */
#define	FREAD		00001		/* descriptor read/receive'able */
#define	FWRITE		00002		/* descriptor write/send'able */

#define	PART_A		0
#define	PART_B		1
#define	PART_C		2
#define	PART_D		3
#define	PART_E		4
#define	PART_F		5
#define	PART_G		6
#define	PART_H		7

#define	MAXPROBERETRY	100
#define	NRETRY		10
#define	MAXHRDERR	100
#define	MAXRETRYCNT	16

#define	SDBSIZE1K	(DEV_BSIZE * 2)
#define	MAXSDPHYS	((NSCMAP - 1) * NBPG)

#define	D100MSEC	100000

#if OD_STOPTIME < 1
# define	OD_STOPTIME	5
#endif /* OD_STOPTIME < 1 */

#define FORMAT_MODE_CORRUPTED	0x31
#define	ONLY_ONE	1

/************** PARTITIONS *************************************/

#define	PART_UNUSED	(0)
#define	PART_SPEC	(-1)
#define	PART_CALCF	(-2)
#define	PART_CALCG	(-3)

struct defpart {
	int range_min;
	int range_max;
	int partsize[PNUM];
};

struct defpart defpart_std[] = {
	{
		0,		/* range_min */
		20,		/* range_max */

		  PART_SPEC,		/* A: */
		PART_UNUSED,		/* B: */
		  PART_SPEC,		/* C: */
		PART_UNUSED,		/* D: */
		PART_UNUSED,		/* E: */
		PART_UNUSED,		/* F: */
		PART_UNUSED,		/* G: */
		PART_UNUSED,		/* H: */
	},
	{
		20,		/* range_min */
		61,		/* range_max */

		      15884,		/* A: */
		      10032,		/* B: */
		  PART_SPEC,		/* C: */
		      15884,		/* D: */
		PART_UNUSED,		/* E: */
		 PART_CALCF,		/* F: */
		 PART_CALCG,		/* G: */
		PART_UNUSED,		/* H: */
	},
	{
		61,		/* range_min */
		206,		/* range_max */

		      15884,		/* A: */
		      33440,		/* B: */
		  PART_SPEC,		/* C: */
		      15884,		/* D: */
		      55936,		/* E: */
		 PART_CALCF,		/* F: */
		 PART_CALCG,		/* G: */
		PART_UNUSED,		/* H: */
	},
	{
		206,		/* range_min */
		356,		/* range_max */

		      15884,		/* A: */
		      33440,		/* B: */
		  PART_SPEC,		/* C: */
		      15884,		/* D: */
		      55936,		/* E: */
		 PART_CALCF,		/* F: */
		 PART_CALCG,		/* G: */
		     291346,		/* H: */
	},
	{
		356,		/* range_min */
		99999999,	/* range_max */

		      15884,		/* A: */
		      66880,		/* B: */
		  PART_SPEC,		/* C: */
		      15884,		/* D: */
		     307200,		/* E: */
		 PART_CALCF,		/* F: */
		 PART_CALCG,		/* G: */
		     291346,		/* H: */
	},
	{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }
};

/************* ADDITIONAL SENSE ERROR CODES *************************/

struct msg_list {
	int	ml_code;		/* message code */
	int	ml_msglvl;		/* message level */
	char	*ml_msgstr;		/* message string */
};

#define	sdskeylist	skeylist

struct msg_list ecodelist_mo[] = {
	{ 0x80, 0, NULL },
	{ 0x81, 0, NULL },
	{ 0x82, 0, NULL },
	{ 0x83, 0, NULL },

	{ -1,   0, NULL }
};

/************** Ref. sd_var.c ********************************/

extern struct iop/**/_ctlr *sdminfo[];
extern struct iop/**/_device *sddinfo[];
extern struct iop/**/_device *sdip[][MAXSLAVE];

extern struct buf rsdbuf[];		/* buffer for raw I/O */
extern struct buf csdbuf[];		/* buffer for controll */
extern struct buf sdutab[];		/* per drive buffers */

extern struct sdc_softc sdc_softc[];
extern struct sdd_softc sdd_softc[];
extern u_char sd_b_openf[][PNUM];
extern u_char sd_c_openf[][PNUM];

extern struct scsi kernscsi[];
extern struct sdst sdstdrv[];
extern struct disklabel sdlabel[];
extern struct size sdsizedrv[][PNUM];

extern u_char sdc_rsense[][RSEN_CNT];

extern struct sync_param sd_sync_param[];

extern int nsd;
extern int nsdc;

/************** Ref. sddefs.c *********************************/

extern struct sddevinfo sddevinfo[];

/**************************************************************/

extern struct msg_list skeylist[];
extern struct msg_list ecodelist[];

extern int	boothowto;
extern int	rsense_msg_disp;	/* RSENSE-message display flag */
extern int	mo_disp_format;		/* MO format mode display flag */

int sd_ignore_error;

static int re_init_done;

static u_char sdwork[2340];		/* buffer for error recovery */
static u_char sdtmp[DEV_BSIZE];		/* buffer for temporary */
#ifdef mips
volatile static int sdtmp_stat = 0;	/* status of sdtmp */
#else
static int sdtmp_stat = 0;		/* status of sdtmp */
#endif

char	pname[] = "abcdefgh";

struct scsi *get_scsi();
struct sc_map *get_sc_map();
struct sc_inq *get_sc_inq();

int	sdprobe(), sdslave(), sdattach(), sddgo(), sdintr();
int	sdwstart, sdwatch(), sdstop();	/* Have started guardian */
void	sdexec();

static sd_check(), sd_tstdrv(), sd_other_pages(), sd_err_rcv(), sd_synctr_on();
static disklabel2sdst(), sdst2disklabel(), sd_scu_exec();

#ifdef CPU_SINGLE
struct hb_driver sdcdriver =
    {sdprobe, sdslave, sdattach, sddgo, sdintr, "sd", sddinfo, "sdc", sdminfo};
#else
struct iop_driver sdcdriver =
    {sdprobe, sdslave, sdattach, sddgo, "sd", sddinfo, "sdc", sdminfo};
#endif

/*ARGSUSED*/
sdprobe(im)
	struct iop/**/_ctlr *im;
{
	static int sdd_init = 0;
	register struct sc_inq *sci;
	register int ctlr;
	register int fw;
	int i;

	if (sdd_init == 0) {
		sdd_init++;
		for (i = 0; i < nsd; i++)
			sdd_softc[i].sdd_start = -2;
	}

	sci = get_sc_inq(im->im_intr);
	ctlr = im->im_ctlr;
	/*
	 * Check device type
	 *	0x00: Direct access device.
	 *	0x01: Sequential access device.
	 *	0x04: Write-once read-multiple device.
	 *	0x05: Read-only Direct-access device.
	 *	0x7f: Specified device is nonexistent.
	 */
	fw = sdc_softc[ctlr].sdc_firmware & ~SDCFW_DEVMASK;

	switch (sci->sci_devtype) {

	case 0x00:
		/*
		 * Assumed that the device is HD.
		 *	Later, distinguish MO from HD.
		 */
		sdc_softc[ctlr].sdc_firmware = fw | SDCFW_HD;
		break;

	default:
		/*
		 * device type mis-match
		 */
		return (0);
	}

	/*
	 * Set interrupt handler routine
	 */
	if (set_inthandler(im, sdintr) == 0)
		return (0);

	return (1);
}


/*ARGSUSED*/
sdslave(ii, reg, intr)
	register struct iop/**/_device *ii;
	caddr_t reg;
	int intr;
{
	register struct scsi *sc;

	sc = get_scsi(intr);
	sdip[ii->ii_ctlr][ii->ii_slave] = ii;
	ii->ii_intr = intr;

	/*
	 * check what the device is.
	 */
	if ((ii->ii_type = sd_check(ii, sc)) < 0)
		goto bad_slave;

	/*
	 * set up ERROR RECOVERY PARAMETERS
	 */
	if (sd_err_rcv(ii, sc) < 0)
		goto bad_slave;

	/*
	 * set up OTHER PARAMETERS
	 */
	if (sd_other_pages(ii, sc) < 0)
		goto bad_slave;

	/*
	 * set up Synchronous Transfer
	 */
	sd_synctr_on(ii, sc);

	return (1);

bad_slave:
	/*
	 * no such slave
	 */
	ii->ii_intr = -1;
	return (0);
}

identity_check(sci, capacity, unit)
	register struct sc_inq *sci;
	int capacity;
	int unit;
{
	register struct sddevinfo *sdi;
	register u_char *id_name;
	register int index;
	register int i;
	int id_pass;

	id_name = sci->sci_vendid;
	while (*id_name == ' ')
		id_name++;

	index = UNKNOWN_DISK;
	id_pass = 0;
	for (sdi = sddevinfo; sdi->id_len >= 0; sdi++) {
		/*
		 * check vendor & product ID
		 */
		if (strncmp(id_name, sdi->id_name, sdi->id_len) != 0)
			continue;
		id_pass = sdi - sddevinfo;

		/*
		 * check revision
		 */
		if (strncmp(sdi->revs, sci->sci_revision, 4) == 0)
			index = id_pass;
		else {
			for (i = 0; i < 4; i++) {
				if (*(sdi->revs + i) == '?')
					continue;
				if (*(sdi->revs + i) != sci->sci_revision[i])
					break;
			}
			if (i < 4)
				continue;
		}

		/*
		 * check capacity
		 */
		if (capacity == -1)
			break;
		if (sdi->capacity == -1) {
			printf("sd%d: capacity=0x%x(%d)\n",
				unit, capacity, capacity);
			break;
		}
		if (capacity == sdi->capacity)
			break;
	}
	if (index == 0)
		index = id_pass;

	return (index);
}

search_index(type)
	register int type;
{
	register struct sddevinfo *sdi;
	register int i;
	int index;

	index = UNKNOWN_DISK;
	i = 0;
	for (sdi = sddevinfo; sdi->id_len > 0; sdi++) {
		if (sdi->type == type) {
			index = i;
			break;
		}
		i++;
	}
	return (index);
}

static
sd_check(ii, sc)
	register struct iop/**/_device *ii;
	register struct scsi *sc;
{
	register struct sc_inq *sci;
	register struct sc_rcap *scr;
	register int intr;
	register int slave;
	register int unit;
	struct sdc_softc *sdc;
	struct sdd_softc *sdd;
	struct sc_extnd *sce;
	int retrycnt;
	int index;
	int media_in;

	intr = ii->ii_intr;
	slave = ii->ii_slave;
	unit = ii->ii_unit;
	sdc = &sdc_softc[ii->ii_ctlr];
	sdd = &sdd_softc[unit];
	scr = (struct sc_rcap *)sc->sc_param;
	sce = (struct sc_extnd *)&sdc_rsense[ii->ii_ctlr][0];

	/*
	 * check if the logical unit is ready.
	 *	(by TEST UNIT READY command)
	 */
	media_in = sd_tstdrv(ii, sc);
	if (media_in < 0)
		return (-3);

	/*
	 * Get controller and drive information.
	 *	(by INQUIRY command)
	 */
	retrycnt = 0;
	sci = get_sc_inq(intr);
loop_inq:
	if (retrycnt++ > MAXPROBERETRY)
		return (-1);

	scop_inquiry(intr, sc, slave, SCSI_INTDIS, sizeof(struct sc_inq), sci);
	sc->sc_tstatus &= TGSTMASK;

	if (sc->sc_istatus != INST_EP || sc->sc_tstatus != TGST_GOOD) {

		bzero((caddr_t)sce, RSEN_CNT);
		scop_rsense(intr, sc, slave, SCSI_INTDIS, RSEN_CNT,
				(caddr_t)sce);
		sc->sc_tstatus &= TGSTMASK;
		if (sc->sc_istatus != INST_EP || sc->sc_tstatus != TGST_GOOD)
			return (-1);
		if (sce->sce_extend != 0x70)
			goto loop_inq;

		switch (sce->sce_sdecode) {

		case 0x04:	/* Drive Not Ready */
		case 0x28:	/* Medium Changed */
		case 0x29:	/* Power On or Reset or Bus Device Reset */
		case 0x2a:	/* Mode Select Parameter Changed */
			break;

		default:
			return (-1);
		}
		DELAY(D100MSEC);		/* wait 100 ms. */
		goto loop_inq;
	}

	index = identity_check(sci, -1, unit);

	switch (sddevinfo[index].type) {

	case SMO_S501:
	case SMO_S501_ISO:
	case SMO_S501_ISO2:
		sdc->sdc_firmware =
			SDCFW_MO | (sdc->sdc_firmware & ~SDCFW_DEVMASK);
		break;

	defaults:
		break;
	}

	if (sci->sci_qual & 0x80) {
		/*
		 * removable medium device
		 */
		sdc->sdc_firmware |= SDCFW_RMB;
		if ((media_in == 0) || ((sdc->sdc_firmware & SDCFW_MO) == 0))
			return (index);
	}

	/****************/
	/* HD & MO only */
	/****************/
	/*
	 * Get drive capacity
	 *	(by READ CAPACITY command)
	 */
	retrycnt = 0;
loop_rcap:
	if (retrycnt++ > MAXPROBERETRY)
		return (-4);

	scop_rcap(intr, sc, slave, SCSI_INTDIS, 8, (caddr_t)0);
	sc->sc_tstatus &= TGSTMASK;
	if (sc->sc_istatus != INST_EP)
		return (-5);
	if (sc->sc_tstatus == TGST_CC) {
		bzero((caddr_t)sce, RSEN_CNT);
		scop_rsense(intr, sc, slave, SCSI_INTDIS, RSEN_CNT,
				(caddr_t)sce);
		sc->sc_tstatus &= TGSTMASK;
		if (sc->sc_istatus != INST_EP || sc->sc_tstatus != TGST_GOOD)
			return (-6);
		if (sderrordisp((caddr_t)sce, ii) == FORMAT_MODE_CORRUPTED) {
			scr->scr_nblock = 0;
			scr->scr_blocklen = DEV_BSIZE;
			sdd->sdd_flags |= SDDF_NONFMT;
		} else {
			DELAY(D100MSEC);		/* wait 100 ms. */
			goto loop_rcap;
		}
	}
	else if (sc->sc_tstatus != TGST_GOOD) {
		DELAY(D100MSEC);		/* wait 100 ms. */
		goto loop_rcap;
	}

	sdd->sdd_nsect = scr->scr_nblock + 1;
	sdd->sdd_sectsize = scr->scr_blocklen;

	index = identity_check(sci, scr->scr_nblock +1, unit);

	return (index);
}

static
sd_tstdrv(ii, sc)
	register struct iop/**/_device *ii;
	register struct scsi *sc;
{
	register struct sc_extnd *sce;
	register int intr;
	register int slave;
	register int retrycnt;
	struct sdc_softc *sdc;
	struct sdd_softc *sdd;

	sdc = &sdc_softc[ii->ii_ctlr];
	sdd = &sdd_softc[ii->ii_unit];

	intr = ii->ii_intr;
	slave = ii->ii_slave;
	sce = (struct sc_extnd *)&sdc_rsense[ii->ii_ctlr][0];
	retrycnt = 0;
loop_tst:
	if (retrycnt++ > MAXPROBERETRY)
		return (-1);

	scop_tst(intr, sc, slave, SCSI_INTDIS);
	sc->sc_tstatus &= TGSTMASK;
	if (sc->sc_istatus != INST_EP) {
		DELAY(D100MSEC);		/* wait 100 ms. */
		goto loop_tst;
	}

	switch (sc->sc_tstatus) {

	case TGST_CC:
		/* Get error code */
		bzero((caddr_t)sce, RSEN_CNT);
		scop_rsense(intr, sc, slave, SCSI_INTDIS, RSEN_CNT,
				(caddr_t)sce);
		sc->sc_tstatus &= TGSTMASK;
		if (sc->sc_istatus != INST_EP || sc->sc_tstatus != TGST_GOOD) {
			DELAY(D100MSEC);	/* wait 100 ms. */
			goto loop_tst;
		}

		if (sce->sce_extend != 0x70)
			goto loop_tst;

		switch (sce->sce_skey) {

		case 0x0:		/* No Sense */
		case 0x4:		/* Hardware error */
		case 0x6:		/* Unit attention */
			goto loop_tst;

		case 0x2:		/* Not ready */
			switch (sce->sce_sdecode) {

			case 0x04:	/* Not ready */
				/*
				 * Drive not ready... so start..
				 */
				scop_stst(intr, sc, slave, SCSI_INTDIS, SDSS_START);
				DELAY(D100MSEC * 10);	/* wait 1 sec. */
				goto loop_tst;

			case 0x0a:	/* No Disk *//*MO*/
			default:
				DELAY(D100MSEC);
				goto loop_tst;
			}
			break;

		case 0x03:
			if (sce->sce_sdecode == FORMAT_MODE_CORRUPTED)
				return (1);	/* ignore error */
			/* fall through */

		default:
			return (-2);
		}
		break;
			
	case TGST_BUSY:
		goto loop_tst;

	case TGST_GOOD:
		break;

	default:
		return (-3);
	}

	return (1);
}

static
sd_other_pages(ii, sc)
	register struct iop/**/_device *ii;
	register struct scsi *sc;
{
	register struct sddevinfo *sdi;
	char **p;
	char *page;
	int length;
	int retrycnt;
	int len;

	sdi = &sddevinfo[ii->ii_type];
	if ((p = sdi->other_pages) == NULL)
		return (0);

	/*
	 * set other parameters
	 */
	while (page = *p++) {
		retrycnt = 0;
loop_other_pages:
		bzero((caddr_t)sdtmp, 4);
		length = *(page + 1) + 2;
		bcopy(page, &sdtmp[4], length);
		if (retrycnt++ > MAXPROBERETRY)
			return (-1);

		scop_mselect(ii->ii_intr, sc, ii->ii_slave, SCSI_INTDIS,
				(SDM_PF<<24) + length +4, (caddr_t)sdtmp);
		sc->sc_tstatus &= TGSTMASK;
		if ((sc->sc_istatus != INST_EP)
				|| (sc->sc_tstatus != TGST_GOOD)) {
			struct sc_extnd *sce;

			sce = (struct sc_extnd *)&sdc_rsense[ii->ii_ctlr][0];
			scop_rsense(ii->ii_intr, sc, ii->ii_slave, SCSI_INTDIS,
						RSEN_CNT, (caddr_t)sce);
			switch (sce->sce_skey) {

			case 0x00:
			case 0x02:
			case 0x04:
			case 0x06:
				DELAY(D100MSEC);   /* 100 ms. */
				goto loop_other_pages;

			default:
				return (-1);
			}
		}
	}

	if (sdi->firm_flags & FIRM_CACHE_ON)
		sdc_softc[ii->ii_ctlr].sdc_firmware |= SDCFW_CACHE;
	else
		sdc_softc[ii->ii_ctlr].sdc_firmware &= ~SDCFW_CACHE;

	return (1);
}

static
sd_err_rcv(ii, sc)
	register struct iop/**/_device *ii;
	register struct scsi *sc;
{
	register struct sdc_softc *sdc;
	register int intr;
	register int slave;
	register int len;
	struct sc_extnd *sce;
	struct sdd_softc *sdd;
	struct sddevinfo *sdi;
	int retrycnt;
	char *erp_page;

	intr = ii->ii_intr;
	slave = ii->ii_slave;
	sdc = &sdc_softc[ii->ii_ctlr];
	sdd = &sdd_softc[ii->ii_unit];
	sdi = &sddevinfo[ii->ii_type];
	sce = (struct sc_extnd *)&sdc_rsense[ii->ii_ctlr][0];

	/*
	 * set Default DISK sector size
	 */
	if (sdd->sdd_sectsize == 0)
		sdd->sdd_sectsize = DEV_BSIZE;


	if (sdi->ERP_page == NULL) {
		/*
		 * use default error recovery parameters
		 */
		sdc->sdc_firmware |= SDCFW_DEFMODE;
		return (0);
	}

	if (sdi->firm_flags & FIRM_AWRE)
		sdc->sdc_firmware |= SDCFW_AWRE;
	if (sdi->firm_flags & FIRM_ARRE)
		sdc->sdc_firmware |= SDCFW_ARRE;
	/*
	 * set ERROR RECOVERY PARAMETERS
	 */
loop_err_rcv:
	bzero((caddr_t)sdtmp, 4);
	erp_page = sdi->ERP_page;
	len = *(erp_page + 1) + 2;
	bcopy(erp_page, &sdtmp[4], len);

	scop_mselect(intr, sc, slave, SCSI_INTDIS,
			(SDM_PF<<24) + len +4, (caddr_t)sdtmp);
	sc->sc_tstatus &= TGSTMASK;
	if (sc->sc_istatus != INST_EP || sc->sc_tstatus != TGST_GOOD) {
		if (sc->sc_tstatus == TGST_CC) {
			bzero((caddr_t)sce, RSEN_CNT);
			scop_rsense(intr, sc, slave, SCSI_INTDIS, RSEN_CNT,
					(caddr_t)sce);
			if (sce->sce_sdecode == 0x2a) {
				/* mode select parameter changed */
				goto ercv_done;
			} else if (sce->sce_skey == 0x6) {
				/* unit attention */
				goto loop_err_rcv;
			}
		}
		/*
		 * use default ERROR RECOVERY mode
		 */
		sdc->sdc_firmware |= SDCFW_DEFMODE;
		sdc->sdc_firmware &= ~(SDCFW_AWRE|SDCFW_ARRE);
	}

ercv_done:

	return (1);
}

static
sd_synctr_on(ii, sc)
	register struct iop/**/_device *ii;
	register struct scsi *sc;
{
	register struct sddevinfo *sdi;
	register struct sync_param *syncp;

	sdi = &sddevinfo[ii->ii_type];

	if (sdi->firm_flags & FIRM_SYNCTR) {
		scinit(sc, ii->ii_slave, DEV_BSIZE); 
		sc->sc_opcode = SCOP_TST;
		sc->sc_message = MSG_EXTND;	/* extended message */
		sc->sc_param[0] = MSG_EXTND;
		sc->sc_param[1] = 0x03;
		sc->sc_param[2] = 0x01;		/* synchronous transfer */
		sc->sc_param[3] = sdi->tr_period;	/* transfer period */
		sc->sc_param[4] = sdi->tr_offset;	/* REQ offset */

		if (sdc_softc[ii->ii_ctlr].sdc_firmware & SDCFW_CACHE)
			sc->sc_tstatus |= TS_CONTR_ON;	/* contiguous TR ON */
		else
			sc->sc_tstatus |= TS_CONTR_OFF;	/* contiguous TR OFF */

#ifdef news1800
		if (scsi_berr_bug() != 0) {
			sc->sc_tstatus &= ~TS_CONTR_ON;
			sc->sc_tstatus |= TS_CONTR_OFF;
		}
#endif

		if (sc->sc_tstatus & TS_CONTR_OFF)
			sdc_softc[ii->ii_ctlr].sdc_firmware &= ~SDCFW_CONTR;
		else
			sdc_softc[ii->ii_ctlr].sdc_firmware |= SDCFW_CONTR;

		sc_go(ii->ii_intr, sc, SCSI_INTDIS);

		syncp = &sd_sync_param[ii->ii_unit];
		syncp->tr_period = sc->sc_param[3];
		syncp->tr_offset = sc->sc_param[4];
		if (sc->sc_param[4])
			sdd_softc[ii->ii_unit].sdd_flags |= SDDF_SYNCTR;
	}
}


sdattach(ii)
	register struct iop/**/_device *ii;
{
	register int unit;
	register int i;
	struct sdc_softc *sdc;
	struct sdd_softc *sdd;
	int dummy;

	sdc = &sdc_softc[ii->ii_ctlr];
	sdc->sdc_timeo = 60;			/* timeout 60 sec */

	unit = ii->ii_unit;
	sdd = &sdd_softc[unit];
	sdd->sdd_stoptime = OD_STOPTIME;	/* REMOVABLE MEDIA */
	sdd->sdd_start = -2;

	sdmaptype(ii);

	if (sdwstart == 0) {
		sdwstart++;
		timeout(sdwatch, (caddr_t)0, hz);
		timeout(sdstop, (caddr_t)0, hz);
	}

	/*
	 * initialize open flag
	 */
	for (i = 0; i < PNUM; i++) {
		sd_b_openf[unit][i] = 0;
		sd_c_openf[unit][i] = 0;
	}

	if (re_init_done > 0)
		return;

	if (sdc->sdc_firmware & SDCFW_HD) {
		/*
		 * If device is Hard Disk,
		 *	then get partition information.
		 */
		sdrpartinfo(ii);
		dummy = DEV_BSIZE * sdstdrv[unit].rps * sdstdrv[unit].nsect;
	} else
		dummy = DEV_BSIZE * 40 * 31;

	if (ii->ii_dk >= 0 && dummy)
		dk_wpms[ii->ii_dk] = dummy / (2 * 1000000);
}

sdmaptype(ii)
	register struct iop/**/_device *ii;
{
	printf("sd%d: %s\n", ii->ii_unit, sddevinfo[ii->ii_type].call_name);
}

int sd_b_major = -1;

sd_b_open(dev, flag)
	dev_t dev;
	int flag;
{
	sd_b_major = major(dev);
	return (_sdopen(dev, flag, S_IFBLK));
}

int sd_c_major = -1;

sd_c_open(dev, flag)
	dev_t dev;
	int flag;
{
	sd_c_major = major(dev);
	return (_sdopen(dev, flag, S_IFCHR));
}

_sdopen(dev, flag, fmt)
	register dev_t dev;
	int flag;
	int fmt;
{
	register struct iop/**/_device *ii;
	register struct sdd_softc *sdd;
	register struct sdc_softc *sdc;
	register struct sddevinfo *sdi;
	register int unit;
	register int i;
	u_char *sdopfp;
	u_char	old_sdopf;
	int media_changed;
	int s;
	int stat;
	struct scsi uscsi;

	unit = dev2unit(dev);
	if (unit >= nsd || (ii = sddinfo[unit]) == 0 || ii->ii_alive == 0)
		return (ENXIO);

	sdd = &sdd_softc[unit];
	sdc = &sdc_softc[ii->ii_ctlr];
	sdi = &sddevinfo[ii->ii_type];

	if (sdd->sdd_flags & SDDF_XUSE)
		return (EBUSY);

	/*
	 * LOCK while sdstop() running.
	 */
	s = splclock();
	while (sdc->sdc_state & SDCS_SCUNLOCK) {
		sdc->sdc_state |= SDCS_OPEN_WAIT;
		sleep((caddr_t)sdc, PRIBIO);
	}
	splx(s);

	/*
	 * LOCK sdtmp buffer
	 */
	s = splclock();
	while (sdtmp_stat & B_BUSY) {
		sdtmp_stat |= B_WANTED;
		sleep((caddr_t)sdtmp, PRIBIO);
	}
	sdtmp_stat |= B_BUSY;
	splx(s);
	sdd->sdd_flags |= SDDF_GETTMP;

	if ((fmt & S_IFMT) == S_IFBLK)
		sdopfp = &sd_b_openf[unit][dev2part(dev)];
	else
		sdopfp = &sd_c_openf[unit][dev2part(dev)];
	old_sdopf = *sdopfp;
	if (old_sdopf <= 1)
		*sdopfp += 1;		/* 1: 1st open (ONLY_ONE) */
					/* 2: already opened */
	stat = 0;
	media_changed = 0;

	/*
	 * From here on until pre_open_done is only for removable devices
	 */
	if ((sdc->sdc_firmware & SDCFW_RMB) == 0)
		goto pre_open_done;

	if ((minor(dev) & 0x80) || (dev == rootdev))
		sdd->sdd_stoptime = 0x7fffffff;		/*XXX*/

	/*
	 * Start Unit
	 */
	s = splclock();	/* inhibit clock interrupt */
	i = sdd->sdd_start;
	sdd->sdd_start = sdd->sdd_stoptime;
	splx(s);
	if (i <= 0) {
		scinit(&uscsi, ii->ii_slave, sdd->sdd_sectsize); 
		uscsi.sc_opcode = SCOP_STST;
		uscsi.sc_count = SDSS_START;

		if (sdcmd(dev, &uscsi)) {
			sdd->sdd_start = i;
			if ((flag & FWRITE) == 0)
				goto sdopen_setup;
			stat = EIO;
			goto pre_open_done;
		}
	}

	/*
	 * prevent medium removal
	 */
	scinit(&uscsi, ii->ii_slave, sdd->sdd_sectsize); 
	uscsi.sc_opcode = SCOP_MEDRMV;
	uscsi.sc_count = SDRMV_PREV;
	if (sdcmd(dev, &uscsi)) {
		stat = EIO;
		goto pre_open_done;
	}
	sdd->sdd_flags |= SDDF_INHRMV;

sdopen_setup:
	if ((sdd->sdd_flags & SDDF_SAMEDSK) == SDDF_DSKCHGD) {
		sdd->sdd_flags |= SDDF_SAMEDSK;
		media_changed = 1;

		/*
		 * From here on until mo_check_done is only for MO device
		 */
		if ((sdc->sdc_firmware & SDCFW_MO) == 0)
			goto mo_check_done;

		/*
		 * Mode Sense
		 */
		bzero(sdtmp, 36);
		scinit(&uscsi, ii->ii_slave, sdd->sdd_sectsize); 
		uscsi.sc_cpoint = sdtmp;
		uscsi.sc_ctrnscnt = 36;
		uscsi.sc_opcode = SCOP_MSENSE;
		uscsi.sc_lad = (SDM_PF << 16)|((SDM_PC_CUR|SDM_PCODE_ALL) << 8);
		uscsi.sc_count = 36;

		if (sdcmd(dev, &uscsi) == 0) {
			/*
			 * check Write Protect mode
			 */
			if (sdtmp[2] & 0x80)
				sdd->sdd_flags |= SDDF_WPROTECT;
			else
				sdd->sdd_flags &= ~SDDF_WPROTECT;
			/*
			 * check Format Mode
			 */
			if (sdtmp[26] == 2) {
				ii->ii_type = search_index(SMO_S501);
				if (mo_disp_format)
					printf("sd%d: format mode 2 (original format)\n", unit);
			} else if (sdtmp[26] == 3) {
				int spare;

				spare = *(short *)&sdtmp[32];
				if (spare == 2048)
					ii->ii_type =
						search_index(SMO_S501_ISO2);
				else
					ii->ii_type =
						search_index(SMO_S501_ISO);
				if (mo_disp_format)
					printf("sd%d: format mode 3 (ISO format) spare=%d\n", unit, spare);
			} else {
				sdd->sdd_flags |= SDDF_NONFMT;
				if (mo_disp_format)
					printf("sd%d: Non format\n", unit);
			}
			sdi = &sddevinfo[ii->ii_type];
		}

		/*
		 * Mode Select
		 *	Error Recovery Parameters set
		 */
		i = *(sdi->ERP_page +1) + 2;	/* page length */
		bzero(sdtmp, i + 4);
		bcopy(sdi->ERP_page, (caddr_t)&sdtmp[4], i);
		scinit(&uscsi, ii->ii_slave, sdd->sdd_sectsize); 
		uscsi.sc_cpoint = sdtmp;
		uscsi.sc_ctrnscnt = i + 4;
		uscsi.sc_opcode = SCOP_MSELECT;
		uscsi.sc_lad = (SDM_PF << 16);
		uscsi.sc_count = i + 4;

		(void) sdcmd(dev, &uscsi);

		/*
		 * Read Grown Defect list
		 */
		scinit(&uscsi, ii->ii_slave, sdd->sdd_sectsize); 
		uscsi.sc_cpoint = sdtmp;
		uscsi.sc_ctrnscnt = 4;
		uscsi.sc_opcode = SCOP_RDL;
		uscsi.sc_cdb.un_type1.t1_ladhi = (SDDL_GLIST|SDDL_PHYSFMT) << 8;
		uscsi.sc_cdb.un_type1.t1_p3 = 4;

		(void) sdcmd(dev, &uscsi);
		i = *(short *)&sdtmp[2] / 8;
		if (i > (1024*9/10))
			printf("sd%d: WARNING: DEFECT SPARE LOCATION < 10%\n",
				unit);
mo_check_done:
		/*
		 * Read Capacity
		 */
		bzero((caddr_t)sdtmp, 8);
		scinit(&uscsi, ii->ii_slave, sdd->sdd_sectsize); 
		uscsi.sc_cpoint = sdtmp;
		uscsi.sc_ctrnscnt = 8;
		uscsi.sc_opcode = SCOP_RCAP;

		(void) sdcmd(dev, &uscsi);
		sdd->sdd_nsect = *(int *)&sdtmp[0] + 1;
		sdd->sdd_sectsize = *(int *)&sdtmp[4];

		if ((sdd->sdd_sectsize != DEV_BSIZE)
				&& (sdd->sdd_sectsize != SDBSIZE1K))
			sdd->sdd_sectsize = DEV_BSIZE;
	}

	if ((sdd->sdd_flags & SDDF_WPROTECT) && (flag & FWRITE))
		stat = EROFS;

pre_open_done:

	if (stat == 0) {
		if ((isalone(unit) == ONLY_ONE) || media_changed) {
			/*
			 * read partition information from sector zero.
			 */
			sdrpartinfo(ii);
			if (ii->ii_dk >= 0) {
				dk_wpms[ii->ii_dk] =
					sdd->sdd_sectsize * sdstdrv[unit].rps
					* sdstdrv[unit].nsect / (2 * 1000000);
			}
		}
	} else {
		/*
		 * open error
		 */
		*sdopfp = old_sdopf;
		if ((sdd->sdd_flags & SDDF_INHRMV) && (isalone(unit) == 0)) {
			sdd->sdd_flags &= ~SDDF_INHRMV;
			scinit(&uscsi, ii->ii_slave, sdd->sdd_sectsize); 
			uscsi.sc_opcode = SCOP_MEDRMV;
			uscsi.sc_count = SDRMV_ALLOW;
			(void) sdcmd(dev, &uscsi);
		}
	}

	/*
	 * UNLOCK open
	 */
	s = splclock();
	sdd->sdd_flags &= ~SDDF_GETTMP;
	if (sdtmp_stat & B_WANTED)
		wakeup((caddr_t)sdtmp);
	sdtmp_stat &= ~(B_BUSY|B_WANTED);
	splx(s);
	return (stat);
}

int sd_access_check_on;		/* Common flags for sd_access_check() */

sd_access_check(bp)
	register struct buf *bp;
{
	register struct iop/**/_device *ii;
	register struct sdd_softc *sdd;
	int unit;
	int check_part;
	int limit;
	int over;
	register struct size *sizes;
	register int lba;	/* logical block address */
	register int sz;
	register int i;

	check_part = 0;
	unit = dev2unit(bp->b_dev);
	ii = sddinfo[unit];
	sdd = &sdd_softc[unit];
	sizes = sdstdrv[unit].sizes;

	lba = sizes[dev2part(bp->b_dev)].sd_blkoff + dkblock(bp);
	sz = howmany(bp->b_bcount, sdd->sdd_sectsize);

	/*
	 * When block device is used,
	 *	inhibit raw device write operation.
	 */
	if ((major(bp->b_dev) == sd_c_major)			/* RAW I/O */
	    && ((bp->b_flags & B_READ) == 0)			/* WRITE */
	    && ((ii->ii_flags & SD_F_ENW) == 0)			/* INHIBIT */
	    && ((sd_access_check_on & SD_F_ENW) == 0)) {

		for (i = 0; i < PNUM; i++) {
			if (sd_b_openf[unit][i] == 0)
				continue;
			/*
			 *   |----|========|---|======|-------|
			 * 1 |---+++--------------------------| CUT OFF
			 * 2 |---++++++++++++-----------------| CUT OFF
			 * 3 |---++++++++++++++++-------------| CUT OFF
			 * 4 |---++++++++++++++++++++++++-----| CUT OFF
			 * 5 |-------+++----------------------| ERROR
			 * 6 |------------+++-----------------| ERROR
			 * 7 |------------+++++++-------------| ERROR
			 * 8 |------------+++++++++++++++-----| ERROR
			 */
			if ((lba < (sizes[i].sd_blkoff + sizes[i].sd_nblocks))
			    && ((lba + sz) > sizes[i].sd_blkoff))
				check_part |= (1 << i);
		}
	}

	if (check_part) {
		limit = 0x7fffffff;	/* XXX */
		for (i = 0; i < PNUM; i++) {
			if ((check_part & (1 << i)) == 0)
				continue;

			if (lba >= sizes[i].sd_blkoff) {
				bp->b_flags |= B_ERROR;
				bp->b_error = EIO;
				bp->b_resid = bp->b_bcount;

				printf("sd%d%c: RAW DEVICE WRITE PROTECTED: ",
					unit, pname[dev2part(bp->b_dev)]);
				printf("sn = 0x%x(%d), off = 0x%x(%d)\n",
					dkblock(bp)*DEV_BSIZE/sdd->sdd_sectsize,
					dkblock(bp)*DEV_BSIZE/sdd->sdd_sectsize,
					sizes[dev2part(bp->b_dev)].sd_blkoff,
					sizes[dev2part(bp->b_dev)].sd_blkoff);

				return (-1);
			}

			if (sizes[i].sd_blkoff < limit)
				limit = sizes[i].sd_blkoff;
		}
	} else {
		limit = sizes[dev2part(bp->b_dev)].sd_blkoff
			+ sizes[dev2part(bp->b_dev)].sd_nblocks;
	}

	if ((over = (lba + sz) - limit) > 0) {
		/*
		 * Logical Block Address is outside the valid area.
		 */
		if (((ii->ii_flags & SD_F_EOLBA) != 0)
			|| ((sd_access_check_on & SD_F_EOLBA) != 0)) {
			/*
			 * error if outside LBA
			 */
			return(-1);
		}
		bp->b_resid = bp->b_bcount - (sz - over) * sdd->sdd_sectsize;
		if (bp->b_resid >= bp->b_bcount) {
			bp->b_resid = bp->b_bcount;
			return(-1);
		}
	}

	return (0);
}

sd_b_close(dev, flag)
	dev_t dev;
	int flag;
{
	return (_sdclose(dev, flag, S_IFBLK));
}

sd_c_close(dev, flag)
	dev_t dev;
	int flag;
{
	return (_sdclose(dev, flag, S_IFCHR));
}

_sdclose(dev, flag, fmt)
	register dev_t dev;
	int flag;
	int fmt;
{
	register struct iop/**/_device *ii;
	register struct sdd_softc *sdd;
	register int unit;
	struct sdc_softc *sdc;
	struct scsi uscsi;
	struct sc_extnd *sce;

	unit = dev2unit(dev);
	if (unit >= nsd || (ii = sddinfo[unit]) == 0 || ii->ii_alive == 0)
		return (ENXIO);

	sdd = &sdd_softc[unit];
	sdc = &sdc_softc[ii->ii_ctlr];
	sce = (struct sc_extnd *)&sdc_rsense[ii->ii_ctlr][0];

	/*
	 * still remain jobs
	 *	sleep about 10ms -> 1sec
	 */
	while (ii->ii_mi->im_tab.b_actf != NULL)
		sleep((caddr_t)&lbolt, PRIBIO);

	if ((fmt & S_IFMT) == S_IFBLK)
		sd_b_openf[unit][dev2part(dev)] = 0;
	else
		sd_c_openf[unit][dev2part(dev)] = 0;
	sdd->sdd_flags &= ~SDDF_XUSE;

	if ((sdc->sdc_firmware & SDCFW_RMB) && (isalone(unit) == 0)) {
		sdd->sdd_flags &= ~SDDF_INHRMV;
		scinit(&uscsi, ii->ii_slave, sdd->sdd_sectsize); 
		uscsi.sc_opcode = SCOP_MEDRMV;
		uscsi.sc_count = SDRMV_ALLOW;
		(void) sdcmd(dev, &uscsi);
	}
	return (0);
}

sdcmd(dev, usc)
	dev_t dev;
	struct scsi *usc;
{
	register struct buf *bp;
	register struct scsi *ksc;
	register u_char *point;
	register int unit;
	int error;
	int s;
	int cnt;

	if (usc == 0)
		return (ENXIO);

	error = 0;
	unit = dev2unit(dev);
	bp = &csdbuf[unit];

	/*
	 * LOCK csdbuf
	 */
	s = splclock();
	while (bp->b_flags & B_BUSY) {
		bp->b_flags |= B_WANTED;
		sleep((caddr_t)bp, PRIBIO);
	}
	bzero((caddr_t)bp, sizeof(struct buf));
	bp->b_flags = B_BUSY|B_READ;
	splx(s);

	ksc = &kernscsi[unit];
	bcopy((caddr_t)usc, (caddr_t)ksc, sizeof(struct scsi));
	/*
	 * setup command buffer
	 */
	bp->b_dev = dev;
	bp->b_proc = curproc;
	cnt = ksc->sc_ctrnscnt;
	bp->b_bcount = cnt;

	point = ksc->sc_cpoint;
	bp->b_un.b_addr = (caddr_t)point;
	if (cnt > 0) {
		if (point == NULL) {
			ksc->sc_cpoint = point = get_scsi(unit)->sc_param;
			if (cnt > 20) {
				error = EFAULT;
				goto done;
			}
		}
		if (point < (u_char *)KERNBASE) {
			if (useracc(point, cnt, B_WRITE) == NULL) {
				error = EFAULT;
				goto done;
			}
			curproc->p_flag |= SPHYSIO;
			vslock(point, cnt);
			bp->b_flags |= B_PHYS;
		}
#ifndef mips
		else {
			if (kernacc(point, cnt, B_WRITE) == NULL) {
				error = EFAULT;
				goto done;
			}
		}
#endif
		ksc->sc_tstatus = TS_MAPPED_PIO;	/* XXX */
	}

	/*
	 * call strategy entry, and wait command done.
	 */
	sdstrategy(bp);
	iowait(bp);

	if ((cnt > 0) && (point < (u_char *)KERNBASE)) {
		vsunlock(point, cnt, B_READ);
		curproc->p_flag &= ~SPHYSIO;
	}
	if ((bp->b_flags & B_ERROR) == 0)
		error = 0;
	else {
		if (bp->b_error)
			error = bp->b_error;
		else
			error = EIO;
	}
	bcopy((caddr_t)ksc, (caddr_t)usc, sizeof(struct scsi));

done:
	/*
	 * UNLOCK csdbuf
	 */
	s = splclock();
	if (bp->b_flags & B_WANTED)
		wakeup((caddr_t)bp);
	bp->b_flags = 0;
	splx(s);
	return (error);
}

/*
 * read partition information from sector zero.
 */
sdrpartinfo(ii)
	register struct iop/**/_device *ii;
{
	register struct disklabel *dlp;
	register struct sdst *hsp;
	register struct sdst *st;
	register int unit;
	register int i;
	struct firstsector *fsp;
	struct sdc_softc *sdc;
	struct sdd_softc *sdd;
	struct sddevinfo *sdi;
	struct scsi uscsi;
	int s;

	sdi = &sddevinfo[ii->ii_type];
	unit = ii->ii_unit;

	sdd = &sdd_softc[unit];
	sdc = &sdc_softc[ii->ii_ctlr];

	if ((sdd->sdd_flags & (SDDF_NONFMT|SDDF_FMTDONE)) == 0) {
		register struct sc_rcap *scr = (struct sc_rcap *)sdtmp;

		sdd->sdd_flags |= SDDF_FMTDONE;

		bzero((caddr_t)sdtmp, 8);
		scinit(&uscsi, ii->ii_slave, sdd->sdd_sectsize); 
		uscsi.sc_cpoint = (u_char *)scr;
		uscsi.sc_ctrnscnt = 8;
		uscsi.sc_opcode = SCOP_RCAP;
		(void) sdcmd(unit << 3, &uscsi);

		sdd->sdd_nsect = scr->scr_nblock + 1;
		sdd->sdd_sectsize = scr->scr_blocklen;
		if (sdd->sdd_sectsize == 0)
			sdd->sdd_sectsize = SDBSIZE1K;
	}

	bzero(sdtmp, DEV_BSIZE);

	if ((sdd->sdd_flags & SDDF_NONFMT) == 0) {
		scinit(&uscsi, ii->ii_slave, sdd->sdd_sectsize); 
		uscsi.sc_cpoint = sdtmp;
		uscsi.sc_ctrnscnt = DEV_BSIZE;
		uscsi.sc_opcode = SCOP_READ;
		uscsi.sc_lad = 0;
		uscsi.sc_count = 1;

		(void) sdcmd(unit << 3, &uscsi);
		sdd->sdd_flags &= ~SDDF_SKIPCHECK;
	}

	fsp = (struct firstsector *)sdtmp;
	dlp = (struct disklabel *)(sdtmp + LABELOFFSET);

	s = splclock();
	hsp = &sdstdrv[unit];
	bzero((caddr_t)hsp, sizeof (struct sdst));
	bzero(&sdlabel[unit], sizeof (struct disklabel));

	if ((dlp->d_magic == DISKMAGIC)
	    && ((ii->ii_flags & SD_F_IGNLABEL) == 0)) {
		sdlabel[unit] = *dlp;
		disklabel2sdst(unit, dlp, hsp);
	} else {
		if ((ii->ii_type == UNKNOWN_DISK)
				|| (sdi->sdstp->sizes == calc_disk_sizes)) {
			/*
			 * If device is UNKNOWN PARTITION SIZE,
			 *	calculate default partition from capacity.
			 */
			st = sdi->sdstp;
			hsp->nsect = st->nsect;		/* # sectors/track */
			hsp->ntrak = st->ntrak;		/* # tracks/cylinder */
			hsp->nspc = st->nspc;		/* # sectors/cylinder */
			hsp->ncyl = st->ncyl;		/* # cylinders */
			hsp->rps = st->rps;		/* # revolutions/sec */
			hsp->sizes = sdsizedrv[unit];	/* partition table */

			sd_calcpart(ii, hsp->sizes,
					sdd->sdd_nsect, sdd->sdd_sectsize);
			sdst2disklabel(unit, hsp, &sdlabel[unit]);
		} else {
			/*
			 * If device is support disk,
			 *	copy default partition from size table.
			 */
			st = sdi->sdstp;

			hsp->nsect = st->nsect;	/* # sectors/track */
			hsp->ntrak = st->ntrak;	/* # tracks/cylinder */
			hsp->nspc = st->nspc;	/* # sectors/cylinder */
			hsp->ncyl = st->ncyl;	/* # cylinders */
			hsp->rps = st->rps;	/* # revolutions / second */
			hsp->sizes = sdsizedrv[unit];	/* partition table */

			for (i = 0; i < PNUM; i++) {
			    hsp->sizes[i].sd_nblocks = st->sizes[i].sd_nblocks;
			    hsp->sizes[i].sd_blkoff = st->sizes[i].sd_blkoff;
			}
			sdst2disklabel(unit, hsp, &sdlabel[unit]);
		}
	}

	/* BEGIN XXX*/
	if (hsp->rps == 0) {
		/*
		 * If device is support disk,
		 *	copy default partition from size table.
		 */
		st = sdi->sdstp;

		hsp->nsect = st->nsect;		/* # sectors/track */
		hsp->ntrak = st->ntrak;		/* # tracks/cylinder */
		hsp->nspc = st->nspc;		/* # sectors/cylinder */
		hsp->ncyl = st->ncyl;		/* # cylinders */
		hsp->rps = st->rps;		/* # revolutions / second */
		sdst2disklabel(unit, hsp, &sdlabel[unit]);
	}
	/* END XXX*/
	(void)splx(s);
}

static char Warn_Part[] = "sd%d: PARTITION TABLE CHANGED\n";
static char Pr_Part_Fmt[] = "sd%d%c: nblk=%d, off=%d\n";

#define stsz(N) st->sizes[(N)].sd_nblocks
#define stof(N) st->sizes[(N)].sd_blkoff
#define dlsz(N) dlp->d_partitions[(N)].p_size
#define dlof(N) dlp->d_partitions[(N)].p_offset
#define disz(N) dip->di_part[(N)].dp_nblocks
#define diof(N) dip->di_part[(N)].dp_blkoff

#ifndef BBSIZE
#define BBSIZE 8192
#endif

static
check_sdst(unit, st)
	int unit;
	struct sdst *st;
{
	if (st->nsect == 0) {
		st->nsect = 1;
		printf("sd%d: nsect SHOULD BE != 0, 1 assumed\n", unit);
	}

	if (st->rps == 0) {
		st->rps = 60;
		printf("sd%d: rps SHOULD BE != 0, 60 assumed\n", unit);
	}
}

static
disklabel2sdst(unit, dlp, st)
	int unit;
	register struct disklabel *dlp;
	register struct sdst *st;
{
	register int i;
	int msg_header_printed;

	msg_header_printed = 0;

	st->nsect = dlp->d_nsectors;	/* # sectors/track */
	st->ntrak = dlp->d_ntracks;	/* # tracks/cylinder */
	st->nspc = dlp->d_secpercyl;	/* # sectors/cylinder */
	st->ncyl = dlp->d_ncylinders;	/* # cylinders */
	st->rps = dlp->d_rpm / 60;	/* # revolutions / second */
	st->sizes = sdsizedrv[unit];	/* partition table */

	check_sdst(unit, st);

	for (i = 0; i < PNUM; i++) {
		if (msg_header_printed == 0) {
			if (((stsz(i) != 0) || (stof(i) != 0))
			    && ((stsz(i) != dlsz(i)) || (stof(i) != dlof(i)))) {
				msg_header_printed = 1;
			}
		}
	}

	for (i = 0; i < PNUM; i++) {
		stsz(i) = dlsz(i);
		stof(i) = dlof(i);
	}

	if (msg_header_printed) {
		printf(Warn_Part, unit);
		for (i = 0; i < PNUM; i++)
			printf(Pr_Part_Fmt, unit, pname[i], stsz(i), stof(i));
	}
}

static
sdst2disklabel(unit, st, dlp)
	int unit;	/*XXX*/
	register struct sdst *st;
	register struct disklabel *dlp;
{
	register int i;

	dlp->d_type = DTYPE_SCSI;			/* drive type */
	dlp->d_secsize = sdd_softc[unit].sdd_sectsize;	/* # of bytes per sector */
	dlp->d_nsectors = st->nsect;	/* # sectors/track */
	dlp->d_ntracks = st->ntrak;	/* # tracks/cylinder */
	dlp->d_ncylinders = st->ncyl;	/* # cylinders */
	dlp->d_secpercyl = st->nspc;	/* # sectors/cylinder */
	dlp->d_rpm = st->rps * 60;	/* # revolutions / minute */
	dlp->d_bbsize = BBSIZE;	/*XXX*/	/* size of boot area at sn0, bytes */
	dlp->d_sbsize = SBSIZE;	/*XXX*/	/* max size of fs superblock, bytes */

	for (i = 0; i < PNUM; i++) {
		dlsz(i) = stsz(i);
		dlof(i) = stof(i);
	}
}

#undef stsz
#undef stof
#undef dlsz
#undef dlof
#undef disz
#undef diof

sd_calcpart(ii, disk_sizes, nsect, sectsize)
	register struct iop/**/_device *ii;
	register struct size disk_sizes[];
	int nsect;
	int sectsize;
{
	register struct defpart *dp;
	register int size_mb;
	register int i;
	int psize;

	size_mb = nsect * sectsize / (1024 * 1024);

	for (dp = defpart_std; dp->range_max; dp++)
		if ((dp->range_min <= size_mb) && (size_mb < dp->range_max))
			break;

	/* PASS1 */
	for (i = 0; i < PNUM; i++) {
		psize = dp->partsize[i];

		switch (psize) {

		case PART_UNUSED:
			disk_sizes[i].sd_nblocks = 0;
			break;

		case PART_SPEC:
			disk_sizes[i].sd_nblocks = nsect * sectsize / DEV_BSIZE;
			break;

		case PART_CALCF:
		case PART_CALCG:
			break;

		default:
			disk_sizes[i].sd_nblocks = psize;
			break;
		}
	}

	/* PASS2 */
	for (i = 0; i < PNUM; i++) {
		psize = dp->partsize[i];

		switch (psize) {

		case PART_UNUSED:
		case PART_SPEC:
			break;

		case PART_CALCF:
			disk_sizes[i].sd_nblocks =
				disk_sizes[PART_C].sd_nblocks - 
				(disk_sizes[PART_A].sd_nblocks +
				 disk_sizes[PART_B].sd_nblocks +
				 disk_sizes[PART_D].sd_nblocks +
				 disk_sizes[PART_E].sd_nblocks +
				 disk_sizes[PART_H].sd_nblocks);
			break;

		case PART_CALCG:
			disk_sizes[i].sd_nblocks =
				disk_sizes[PART_C].sd_nblocks - 
				(disk_sizes[PART_A].sd_nblocks +
				 disk_sizes[PART_B].sd_nblocks +
				 disk_sizes[PART_H].sd_nblocks);
			break;

		default:
			break;
		}
	}

	/* OFFSET */
	disk_sizes[PART_A].sd_blkoff = 0;
	disk_sizes[PART_B].sd_blkoff = disk_sizes[PART_A].sd_nblocks;
	disk_sizes[PART_C].sd_blkoff = 0;
	disk_sizes[PART_D].sd_blkoff = disk_sizes[PART_A].sd_nblocks
					+ disk_sizes[PART_B].sd_nblocks
					+ disk_sizes[PART_H].sd_nblocks;
	disk_sizes[PART_E].sd_blkoff = disk_sizes[PART_D].sd_blkoff
					+ disk_sizes[PART_D].sd_nblocks;
	disk_sizes[PART_F].sd_blkoff = disk_sizes[PART_E].sd_blkoff
					+ disk_sizes[PART_E].sd_nblocks;
	disk_sizes[PART_G].sd_blkoff = disk_sizes[PART_D].sd_blkoff;

	if (disk_sizes[PART_H].sd_nblocks == 0)
		disk_sizes[PART_H].sd_blkoff = 0;
	else {
		disk_sizes[PART_H].sd_blkoff =
			disk_sizes[PART_A].sd_nblocks +
			disk_sizes[PART_B].sd_nblocks;
	}

	for (i = 0; i < PNUM; i++)
		if (disk_sizes[i].sd_nblocks == 0)
			disk_sizes[i].sd_blkoff = 0;
}

int sd_str_pr = 0;

sdstrategy(bp)
	register struct buf *bp;
{
	register struct iop/**/_device *ii;
	register struct sdst *st;
	register struct buf *dp;
	register int unit;
	register int ssize;
	struct sdd_softc *sdd;
	struct sdc_softc *sdc;
	long bn;
	int xunit;
	int s;

	xunit = dev2part(bp->b_dev);
	unit = dev2unit(bp->b_dev);
	if (unit >= nsd || (ii = sddinfo[unit]) == 0 || ii->ii_alive == 0)
		goto bad;

	if (bp != &csdbuf[unit]) {
		/*
		 * READ / WRITE command
		 */
		sdd = &sdd_softc[unit];
		if (sdd->sdd_flags & SDDF_NONFMT)
			goto bad;
		sdc = &sdc_softc[ii->ii_ctlr];
		ssize = sdd->sdd_sectsize;
		if ((ssize != DEV_BSIZE)
			&& ((((dkblock(bp) * DEV_BSIZE) % ssize) != 0)
				|| (((bp->b_flags & B_READ) == 0) &&
				    ((bp->b_bcount % ssize) != 0)))) {
			goto bad;
		}

		st = &sdstdrv[unit];
		bn = dkblock(bp);
		bp->b_resid = 0;
		if ((bn < 0) || (bn >= st->sizes[xunit].sd_nblocks))
			goto bad2;
		if (sd_access_check(bp) < 0)
			goto bad2;

#ifdef notdef /* KU: XXX */
		bp->b_cylin = (bn + st->sizes[xunit].sd_blkoff) / st->nspc;
	} else {
		bp->b_cylin = 0;
#endif
	}

	s = splsc();
	dp = &sdutab[ii->ii_unit];
	disksort(dp, bp);
	if (dp->b_active == 0) {
		sdustart(ii);
		bp = &ii->ii_mi->im_tab;
		if (bp->b_actf && bp->b_active == 0)
			sdstart(ii->ii_mi);
	}
	splx(s);
	return;

bad:
	bp->b_flags |= B_ERROR;
	goto done;
bad2:
	bp->b_resid = bp->b_bcount;
done:
	iodone(bp);
}

/*
 * Unit start routine.
 */
sdustart(ii)
	register struct iop/**/_device *ii;
{
	register struct iop/**/_ctlr *im;
	register struct buf *dp;

	if (ii == NULL)
		return;
	im = ii->ii_mi;
	dk_busy &= ~(1 << ii->ii_dk);
	dp = &sdutab[ii->ii_unit];
	if (dp->b_actf == NULL)
		return;
	/*
	 * If the controller is active, just remember
	 * that this device would like to be positioned ...
	 * if we tried to position now we would confuse the SD.
	 */
	if (im->im_tab.b_active) {
		sdc_softc[im->im_ctlr].sdc_softas |= (1 << ii->ii_slave);
		return;
	}
	/*
	 * If we have already positioned this drive,
	 * then just put it on the ready queue.
	 */
	if (dp->b_active == 0)
		dp->b_active = 1;
	/*
	 * Device is ready to go
	 * put it on the ready queue for the controller
	 * (unless its already there.)
	 */
	if (dp->b_active != 2) {
		im->im_tab.b_actf = dp;
		dp->b_active = 2;
	}
}

/*
 * Start up a transfer on a drive.
 */
sdstart(im)
	register struct iop/**/_ctlr *im;
{
	register struct buf *bp;
	register struct buf *dp;
	register struct sdc_softc *sdc;

loop:
	/*
	 * Pull a request off the controller queue.
	 */
	if ((dp = im->im_tab.b_actf) == NULL)
		return;
	if ((bp = dp->b_actf) == NULL)
		return;
	/*
	 * Mark controller busy, and
	 * determine destination of this request.
	 */
	im->im_tab.b_active++;

	sdexec(bp);
}

void
sdexec(bp)
	register struct buf *bp;
{
	register struct iop/**/_device *ii;
	register struct buf_stat *bs;
	register struct scsi *sc;
	register int ssize;
	register int unit;
	register int intr;
	register int bn;
	struct sdc_softc *sdc;
	struct sdd_softc *sdd;
	struct sdst *st;
	int sz;
	int over;
	struct sc_map *map;

	unit = dev2unit(bp->b_dev);
	ii = sddinfo[unit];
	intr = ii->ii_intr;
	sdd = &sdd_softc[unit];
	sdc = &sdc_softc[ii->ii_ctlr];

	sc = get_scsi(intr);

	if (bp == &csdbuf[unit]) {	/* do sdcmd() */
		bcopy((caddr_t)&kernscsi[unit],
			(caddr_t)sc, sizeof(struct scsi));
		if (bp->b_un.b_addr == NULL) {
			map = 0;
		} else {
			map = get_sc_map(intr);
			sc->sc_map = (struct sc_map *)ipc_phys(map);
		}
	} else {			/* R/W */
		ssize = sdd->sdd_sectsize;

		st = &sdstdrv[unit];
		bn = dkblock(bp);
		if (sdd->sdd_lastblk / st->nspc != bn / st->nspc)
			dk_seek[ii->ii_dk]++;

		st = &sdstdrv[unit];
		bn = dkblock(bp);

		/*
		 * Setup for the transfer, and get in the IOP queue.
		 */
		scinit(sc, ii->ii_slave, ssize); 
		sc->sc_ctrnscnt = bp->b_bcount - bp->b_resid;
		map = get_sc_map(intr);
		sc->sc_map = (struct sc_map *)ipc_phys(map);

		/* cdb */
		sc->sc_cdb.un_type1.t1_opcode =
				(bp->b_flags & B_READ) ?	SCOP_EREAD :
				(ii->ii_flags & SD_F_WRTVRFY) ?	SCOP_WRTVRFY :
								SCOP_EWRITE;
#ifdef mips
		{
		int v;

		v = (bn + st->sizes[dev2part(bp->b_dev)].sd_blkoff)
					* DEV_BSIZE / sdd->sdd_sectsize;
		sc->sc_ladhi = v >> 16;
		sc->sc_ladlo = v;

		v = (sc->sc_ctrnscnt + ssize - 1) / ssize;
		sc->sc_cdb.un_type1.t1_p2 = v >> 8;
		sc->sc_cdb.un_type1.t1_p3 = v;
		}
#else
		*(u_int *)(&sc->sc_cdb.un_type1.t1_ladhi) =
				(bn + st->sizes[dev2part(bp->b_dev)].sd_blkoff)
					* DEV_BSIZE / sdd->sdd_sectsize;
		*(u_short *)(&sc->sc_cdb.un_type1.t1_p2) =
				    (sc->sc_ctrnscnt + ssize -1) / ssize;
#endif
		if ((sdd->sdd_flags & SDDF_ERASEOFF)
				&& ((bp->b_flags & B_READ) == 0)) {
			sc->sc_ctrl = 0x40;
		}
	}

	sdc->sdc_firmware |= SDCFW_BUSY;
	iop/**/go(ii, map);
}

/*
 * Now all ready to go.
 */
sddgo(im)
	register struct iop/**/_ctlr *im;
{
	register int intr;

	im->im_tab.b_active = 2;
	intr = im->im_intr;

	sc_go(intr, get_scsi(intr), SCSI_INTEN);
}

/*
 * copyin(), copyout() can't use in the interrupt routine.
 *	because user process is changed.
 */
/*
 * Handle a disk interrupt.
 *	d: controller number
 */
sdintr(d)
	int d;
{
	register struct iop/**/_ctlr *im;
	register struct sdc_softc *sdc;
	register struct sdd_softc *sdd;
	register struct scsi *sc;
	register int intr;
	register int unit;
	register int slave;
	register int as;
	struct iop/**/_device *ii;
	struct sddevinfo *sdi;
	struct sc_extnd *sce;
	struct sdst *st;
	struct buf *bp;
	struct buf *dp;
	char *erp_page;
	int code;
	int len;
	int tstatus;
	int delay_start();
	int delay_medrmv();
	int wait_re_init_done();

	im = sdminfo[d];
	sdc = &sdc_softc[im->im_ctlr];
	intr = im->im_intr;
	as = sdc->sdc_softas;

	sdc->sdc_wticks = 0;
	sc = get_scsi(intr);
	/*
	 * If SDCS_IOCTL bit is set, then don't check error.
	 */
	if (sdc->sdc_state & SDCS_IOCTL) {
		sdc->sdc_state &= ~SDCS_IOCTL;
		sdd = &sdd_softc[(sdip[d][sc->sc_identify & IDT_DRMASK])->ii_unit];

		if (sdc->sdc_state & SDCS_SCUNLOCK) {
			int s;

			sdc->sdc_state &= ~SDCS_SCUNLOCK;
			s = splclock();
			if (sdc->sdc_state & SDCS_OPEN_WAIT) {
				sdc->sdc_state &= ~SDCS_OPEN_WAIT;
				wakeup((caddr_t)sdc);
			}
			splx(s);
			/*
			 * UNLOCK SCSI access
			 */
			sdc->sdc_firmware &= ~SDCFW_BUSY;
		}
		return;
	}

	im->im_tab.b_active = 1;
	/*
	 * Get device and block structures, and a pointer
	 * to the iop_device for the drive.
	 */
	dp = im->im_tab.b_actf;
	bp = dp->b_actf;
	unit = dev2unit(bp->b_dev);

	ii = sddinfo[unit];
	slave = ii->ii_slave;
	st = &sdstdrv[unit];
	dk_busy &= ~(1 << ii->ii_dk);
	sdd = &sdd_softc[unit];
	sdi = &sddevinfo[ii->ii_type];
	sce = (struct sc_extnd *)&sdc_rsense[ii->ii_ctlr][0];

	/*
	 * Check error on the drive.
	 */
	tstatus = sc->sc_tstatus & TGSTMASK;
	if (sc->sc_istatus != INST_EP) {
		/*
		 * initiator status is bad.
		 *	check & retry !!
		 */
		if ((sc->sc_istatus&(INST_EP|INST_PRE)) == (INST_EP|INST_PRE)) {
			/* detect parity error or abnormal terminate */
			if ((sc->sc_istatus & INST_LB) == 0)
				printf("sd%d: SCSI bus parity error\n", unit);
			sdc->sdc_countcc--;
			goto sdintr_exec;
		}
		if ((sc->sc_istatus & INST_EP) == 0) {
			if (sc->sc_istatus & (INST_WAIT | INST_IP | INST_WR)) {
				if (++sdc->sdc_retrycnt < NRETRY) {
					im->im_tab.b_active = 2;
					/*
					 * Konomama return sitemo,
					 * lost interrupt ni narudake deha
					 * naidarou ka ?
					 * Isso error ni sitahou ga
					 * ii nodeha naidarou ka ?
					 */
					return;
				}
			}
			printf("SCSI%d: abnormal termination\n", intr);
			printf("ISTAT = 0x%x, TSTAT = 0x%x\n",
					sc->sc_istatus, sc->sc_tstatus);
			if (++sdc->sdc_nhrderr >= MAXHRDERR) {
				printf("SCSI%d: too many hard errors\n", intr);
				sdc->sdc_nhrderr = 0;
				goto sdintr_error;
			}
			screset(intr);
			goto sdintr_exec;
		}
		if ((sc->sc_istatus & (INST_TO|INST_HE)) != 0) {
			if (sc->sc_istatus & INST_HE) {
				/*
				 * SCSI bus reset is occured.
				 *	to be continue --> hdreset()
				 */
				re_init_done = 0;
				timeout(wait_re_init_done, bp, 10*hz);
				return;
			}
			if (++sdc->sdc_nhrderr >= MAXHRDERR) {
				printf("SCSI%d: too many hard errors (ISTAT=0x%x)\n",
					intr, sc->sc_istatus);
				sdc->sdc_nhrderr = 0;
				goto sdintr_error;
			}
			if (++sdc->sdc_retrycnt >= NRETRY) {
				printf("SCSI%d: too many initiator errors (ISTAT=0x%x)\n",
					intr, sc->sc_istatus);
				goto sdintr_error;
			}
			DELAY(D100MSEC * 10);
			goto sdintr_exec;
		}
	}

	if (sdd->sdd_flags & SDDF_SKIPCHECK)
		goto sdintr_done;

check_target_status:
	/*
	 * check target status
	 */
	switch (sdc->sdc_state) {

	/********************************/
	/*				*/
	/*	NORMAL OPERATION	*/
	/*				*/
	/********************************/
	case SDCS_NORMAL:
		switch (tstatus) {

		case TGST_GOOD:
			break;

		case TGST_CC:
			sdc->sdc_state |= SDCS_RSENSE;
sdintr_rsense:
			im->im_tab.b_active = 2;
			bzero((caddr_t)sce, RSEN_CNT);
			scop_rsense(intr, sc, slave,
					SCSI_INTEN, RSEN_CNT, (caddr_t)sce);
			return;

		case TGST_BUSY:
			if (++sdc->sdc_retrycnt > MAXRETRYCNT) {
				goto sdintr_error;
			}
			timeout(sdexec, (caddr_t)bp, hz);
			return;

		default:
			printf("sd%d: bad target status 0x%x\n",
						unit, sc->sc_tstatus);
			goto sdintr_error;
		}
		break;

	/****************************************/
	/*					*/
	/*	REQUEST SENSE analysis		*/
	/*					*/
	/****************************************/
	case SDCS_RSENSE:
	case SDCS_PREVRMB|SDCS_RSENSE:
	case SDCS_ECC|SDCS_RASREAD|SDCS_RSENSE:
	case SDCS_ECC|SDCS_RASWRITE|SDCS_RSENSE:
	case SDCS_ECCOFF|SDCS_RSENSE:
	case SDCS_ECCOFF|SDCS_RASBLK|SDCS_RSENSE:
	case SDCS_ECCOFF|SDCS_RASBLK|SDCS_LOSTDATA|SDCS_RSENSE:
	case SDCS_ECC|SDCS_RASBLK|SDCS_RSENSE:
	case SDCS_ECC|SDCS_RASBLK|SDCS_LOSTDATA|SDCS_RSENSE:
		if (tstatus != TGST_GOOD) {
			printf("sd%d: bad target status 0x%x\n",
						unit, sc->sc_tstatus);
			goto sdintr_error;
		}
		/*
		 * error message print out
		 */
		code = sderrordisp(sce, ii);


		if ((sdc->sdc_state == (SDCS_ECC|SDCS_RASBLK|SDCS_RSENSE)) ||
		    (sdc->sdc_state == (SDCS_ECC|SDCS_RASBLK|SDCS_LOSTDATA|SDCS_RSENSE))) {
			printf("sd%d: cannot reassign block %d\n",
						unit, sdd->sdd_badsect);
			goto sdintr_error;
		}
		if (sdc->sdc_state == (SDCS_PREVRMB|SDCS_RSENSE)) {
			if (sce->sce_skey == 0x2) {
				/*
				 * Not ready
				 */
				sdc->sdc_state = SDCS_PREVRMB;
				timeout(delay_medrmv, (caddr_t)ii, hz);
				return;
			}
		}

		/*			*/
		/* RSENSE error handler	*/
		/*			*/
		switch (code) {

		/********************************/
		/*	continue		*/
		/********************************/
		/* NO SENSE */
		case 0x00:	/* No Additional Sense Information */
		/* RECOVERED ERROR */
		case 0x38:	/* Recovered with Auto-Reallocation */
			sdc->sdc_state &= ~SDCS_RSENSE;
			goto check_target_status;

		/********************************/
		/*	continue or error	*/
		/********************************/
		/* ILLEGAL REQUEST */

		case 0x21:	/* illegal Logical Block Address */
			if (&st->sizes[dev2part(bp->b_dev)] == NULL)
				goto sdintr_error;
			if (bp->b_bcount > 0) {
				bp->b_resid = bp->b_bcount
				    - (sdd->sdd_badsect * sdd->sdd_sectsize
				    - (st->sizes[dev2part(bp->b_dev)].sd_blkoff
				    + dkblock(bp)) * DEV_BSIZE);
			}
			if (bp->b_resid >= bp->b_bcount || bp->b_resid <= 0) {
				/*
				 * all I/O failure
				 */
				bp->b_resid = bp->b_bcount;
				goto sdintr_error;
			}
			/* Ignore error */
			break;

		/* MEDIUM ERROR */
		case 0x31:	/* Medium Format Corrupted */
			sdd->sdd_flags |= SDDF_NONFMT;
			/* Ignore error */
			break;

		/********************************/
		/*	more retry		*/
		/********************************/
		/* MEDIUM or RECOVERED ERROR */
		case 0x10:	/* ID CRC Error */
		case 0x12:	/* No Address Mark found in ID field */
		case 0x13:	/* No Address Mark found in Data field */
		case 0x14:	/* No recode found */
		/* H/W or MEDIUM or RECOVERED ERROR */
		case 0x15:	/* Seek Positioning Error */
			if (sd_ignore_error) {
				sdc->sdc_state = SDCS_NORMAL;
				goto check_target_status;
			}
			/* fall through */

		/* H/W ERROR */
		case 0x01:	/* No Index/Address Mark Found signal */
		case 0x02:	/* No Seek Complete */
		case 0x06:	/* No Track Zero found */
		/* H/W ERROR or RECOVERED ERROR */
		case 0x03:	/* Write Fault */
		case 0x08:	/* Logical Unit Communication Failure */
		case 0x09:	/* Track Following Error */
		case 0x0b:	/* Load/Unload Failure */
		case 0x0c:	/* Spindle Failure */
		case 0x0d:	/* Focus Failure */
		case 0x0e:	/* Tracking Failure */
		case 0x0f:	/* Drive Initialization Failure */
			sdc->sdc_state = SDCS_ECC|SDCS_ECC_HOLD|SDCS_REZERO;

			scinit(sc, slave, sdd->sdd_sectsize); 
			/* sc_cdb */
			sc->sc_opcode = SCOP_REZERO;

			sddgo(im);
			return;

		/********************************/
		/*	re-allocate & retry	*/
		/********************************/
		/* MEDIUM or RECOVERED ERROR */
		case 0x11:	/* Unrecovered Read Error */
			if (sdc->sdc_state & SDCS_RASREAD) {
sdintr_lostdata:
				sdc->sdc_state =
					SDCS_ECC|SDCS_RASBLK|SDCS_LOSTDATA;
				im->im_tab.b_active = 2;
				scop_rasblk(intr, sc, slave,
					SCSI_INTEN, sdd->sdd_badsect);
				sdd->sdd_flags &= ~SDDF_VBADSECT;
				return;
			}
			/* fall through */

		/* RECOVERED ERROR */
		case 0x17:	/* Recovered read data with retries */
		case 0x18:	/* Recovered read data with ECC */
			/*
			 * set ECC ON & more retry
			 */
			if (sdc->sdc_firmware & SDCFW_DEFMODE)
				goto sdintr_ecc;

			if (sdc->sdc_state & SDCS_RASREAD)
				goto sdintr_rasblk;

			sdc->sdc_state = SDCS_ECC;
			goto sdintr_msel_set;

		/********************************/
		/*	unit start & retry	*/
		/********************************/
		/* NOT READY */
		case 0x04:	/* Drive Not Ready */
			if (sdc->sdc_state & SDCS_ECC)
				sdc->sdc_state = SDCS_ECCOFF|SDCS_RETRY;
			else
				sdc->sdc_state = SDCS_RETRY;
			goto sdintr_stst;

		/********************************/
		/*	retry			*/
		/********************************/
		/* UNIT ATTENTION */
		case 0x28:	/* Medium Changed */
			sdd->sdd_flags &= ~(SDDF_NONFMT
					    |SDDF_SAMEDSK
					    |SDDF_REQ_EJECT
					    |SDDF_XUSE
					    |SDDF_INHRMV
					    |SDDF_ERASEOFF);
			/* fall through */

		case 0x29: /* Power On or Reset or Bus Device Reset */
			if (sdc->sdc_firmware & SDCFW_RMB) {
				/***************************/
				/* medium removable device */
				/***************************/
				sdc->sdc_state = SDCS_PREVRMB;
				im->im_tab.b_active = 2;
				scop_medrmv(intr, sc, slave,
					    SCSI_INTEN, SDRMV_PREV);
				return;
			}

		case 0x2a:	/* Mode Select Parameter Changed */
		case 0x47:	/* SCSI interface bus parity error */
			if (sdc->sdc_state & SDCS_ECC) {
				sdc->sdc_state = SDCS_RETRY;
				goto sdintr_msel_reset;
			}
			sdc->sdc_state = SDCS_NORMAL;
			goto sdintr_exec;

		/********************************/
		/*	set error flag		*/
		/********************************/
		case 0x40:	/* RAM failure */
		case 0x41:	/* Data Path diagnostic failure */
		case 0x42:	/* Power On diagnostic failure */

		case 0xb2:	/* Caddy load/eject failed */
		case 0xb4:	/* Focus servo failure */
		case 0xb5:	/* Spindle servo failure */
		case 0xb6:	/* Caddy load mechanism failed */
			goto sdintr_error;

/*MO*/		case 0x80:	/* Limit Laser Life */
/*MO*/		case 0x81:	/* Focus Coil Over-current Failure */
/*MO*/		case 0x82:	/* Tracking Coil Over-current Failure */
/*MO*/		case 0x83:	/* Temperature Alarm */
/*CD*/	/*	case 0x80: */	/* Prevent bit is set */
/*CD*/	/*	case 0x81: */	/* Logical unit is reserved */
/*CD*/	/*	case 0x82: */	/* End of usr area encountered */
/*CD*/	/*	case 0x83: */	/* Overlapped commands attempted */
			goto sdintr_error;

		default:
			/*
			 *	error detect, but what shall we do ?
			 */
	/*	case 0x05: */	/* Drive Not Selected */
	/*	case 0x07: */	/* Multiple Drives Selected */
	/*	case 0x0a: */	/* No disk */
	/*	case 0x1a: */	/* Parameter overrun */
	/*	case 0x1b: */	/* Synchronous transfer error */
	/*	case 0x1d: */	/* Compare error */
	/*	case 0x22: */	/* Illegal function for device type */
	/*	case 0x23: */	/* Illegal function for Medium type */
	/*	case 0x25: */	/* Illegal LUN */
	/*	case 0x27: */	/* Write Protected */
	/*	case 0x2b: */	/* Firmware has been downloaded */
	/*	case 0x39: */	/* Automatic Reallocation Failure */
	/*	case 0x43: */	/* Message Reject Error */
	/*	case 0x45: */	/* Selection/Reselection failure */
	/*	case 0x48: */	/* Initiator detected error */
	/*	case 0x49: */	/* Inappropriate/illegal message */
	/*	case 0x60: */	/* COPY: STATUS error */
	/*	case 0x85: */	/* Audio address not valid */
	/*	case 0xb0: */	/* Caddy not inserted in drive */
	/*	case 0xb1: */	/* Unable to recover TOC */
	/*	case 0xb3: */	/* CIRC unrecovered data error(L-EC off) */
	/*	case 0xc3: */	/* COPY: Illegale CDB length */
	/*	case 0xc5: */	/* COPY: Catastrophic error */
	/*	case 0xc6: */	/* COPY: Illegal phase change */
	/*	case 0xfc: */	/* COPY: MODE SENSE failed */

			/*
			 *	medium error
			 */
	/*	case 0x19: */	/* Defect list error */
	/*	case 0x1c: */	/* Primary Defect List not found */
	/*	case 0x30: */	/* Incompatible Cartridge */
	/*	case 0x32: */	/* No Spare Defect Location Available */
	/*	case 0x3a: */	/* Defect List Update Failure */
	/*	case 0x3d: */	/* Defect List Not Available */
			goto sdintr_error;
		}
		/*
		 * No error detected or ignored.
		 */
		break;

	/************************************************/
	/*						*/
	/*	PREVENT MEDIUM REMOVABLE COMMAND	*/
	/*						*/
	/************************************************/
	case SDCS_PREVRMB:
		if (tstatus == TGST_CC) {
			sdc->sdc_state = SDCS_PREVRMB|SDCS_RSENSE;
			goto sdintr_rsense;
		}
		sdd->sdd_flags |= SDDF_INHRMV;
		if (sdc->sdc_state & SDCS_ECC) {
			sdc->sdc_state = SDCS_RETRY;
			goto sdintr_msel_reset;
		}
		sdc->sdc_state = SDCS_NORMAL;
		goto sdintr_exec;
		break;

	/****************************************/
	/*					*/
	/*	REZERO done & RETRY COMMAND	*/
	/*					*/
	/****************************************/
	case SDCS_ECC|SDCS_ECC_HOLD|SDCS_REZERO:
		if (sdc->sdc_firmware & SDCFW_DEFMODE) {
			sdc->sdc_state = SDCS_ECC|SDCS_ECC_HOLD|SDCS_RETRY;
			goto sdintr_stst;
		}

		sdc->sdc_state = SDCS_ECC|SDCS_ECC_HOLD;
		goto sdintr_msel_set;

	/********************************/
	/*				*/
	/*	RETRY COMMAND		*/
	/*				*/
	/********************************/
	case SDCS_RETRY:
		sdc->sdc_state = SDCS_NORMAL;
		goto sdintr_exec;

	/************************************************/
	/*						*/
	/*	ERROR CORRECTION ON MODE SELECT result	*/
	/*						*/
	/************************************************/
	case SDCS_ECC:
		if (tstatus != TGST_GOOD) {
			printf("sd%d: bad target status 0x%x\n",
						unit, sc->sc_tstatus);
			goto sdintr_error;
		}
sdintr_ecc:
		if (bp->b_flags & B_READ) {
			sdc->sdc_state = SDCS_ECC|SDCS_RASREAD;
			im->im_tab.b_active = 2;
			scop_rdwr(intr, sc, slave, SCSI_INTEN,
					B_READ, sdwork,
					sdd->sdd_badsect, sdd->sdd_sectsize);
			return;
		}
		goto sdintr_rasblk;

	/************************************************/
	/*						*/
	/*	READ DATA from BAD BLOCK result		*/
	/*						*/
	/************************************************/
	case SDCS_ECC|SDCS_RASREAD:
		if (tstatus == TGST_CC) {
			sdc->sdc_state = SDCS_ECC|SDCS_RASREAD|SDCS_RSENSE;
			goto sdintr_rsense;
		} else if (tstatus != TGST_GOOD) {
			printf("sd%d: bad target status 0x%x\n",
						unit, sc->sc_tstatus);
			printf("sd%d: cannot read block\n", unit);
			goto sdintr_error;
		}
sdintr_rasblk:
		if (sdd->sdd_flags & SDDF_WPROTECT)
			goto sdintr_error;
		sdc->sdc_state = SDCS_ECC|SDCS_RASBLK;
		im->im_tab.b_active = 2;
		scop_rasblk(intr, sc, slave, SCSI_INTEN, sdd->sdd_badsect);
		sdd->sdd_flags &= ~SDDF_VBADSECT;
		return;

	/****************************************/
	/*					*/
	/*	REASSIGN BLOCK result		*/
	/*					*/
	/****************************************/
	case SDCS_ECC|SDCS_RASBLK:
		if (tstatus == TGST_CC) {
			sdc->sdc_state = SDCS_ECC|SDCS_RASBLK|SDCS_RSENSE;
			goto sdintr_rsense;
		} else if (tstatus != TGST_GOOD) {
			printf("sd%d: bad target status 0x%x\n",
						unit, sc->sc_tstatus);
			goto sdintr_error;
		}
		printf("sd%d: block %d is reassigned\n",
			unit, sdd->sdd_badsect);
		if (bp->b_flags & B_READ) {
sdintr_raswrite:
			sdc->sdc_state = SDCS_ECC|SDCS_RASWRITE;
			im->im_tab.b_active = 2;
			scop_rdwr(intr, sc, slave, SCSI_INTEN,
					B_WRITE, sdwork,
					sdd->sdd_badsect, sdd->sdd_sectsize);
			return;
		}
		sdc->sdc_state = SDCS_RETRY;
		goto sdintr_msel_reset;

	/************************************************/
	/*						*/
	/*	WRITE DATA to REASSIGNED BLOCK result	*/
	/*						*/
	/************************************************/
	case SDCS_ECC|SDCS_RASWRITE:
		if (tstatus == TGST_CC) {
			sdc->sdc_state = SDCS_ECC|SDCS_RASWRITE|SDCS_RSENSE;
			goto sdintr_rsense;
		} else if (tstatus != TGST_GOOD) {
			printf("sd%d: bad target status 0x%x\n",
						unit, sc->sc_tstatus);
			goto sdintr_error;
		}
		sdc->sdc_state = SDCS_RETRY;
		goto sdintr_msel_reset;

	/****************************************/
	/*					*/
	/*	reset ECC & RETRY TIMES		*/
	/*					*/
	/****************************************/
	case SDCS_ECCOFF|SDCS_RETRY:
		sdc->sdc_state = SDCS_RETRY;
		goto sdintr_msel_reset;

	/********************************************************/
	/*							*/
	/*	READ DATA from BAD BLOCK result in faliure	*/
	/*							*/
	/********************************************************/
	case SDCS_ECC|SDCS_RASBLK|SDCS_LOSTDATA:
		if (tstatus == TGST_CC) {
			sdc->sdc_state =
				SDCS_ECC|SDCS_RASBLK|SDCS_LOSTDATA|SDCS_RSENSE;
			goto sdintr_rsense;
		} else if (tstatus != TGST_GOOD) {
			printf("sd%d: rasblk: bad target status 0x%x\n",
				unit, sc->sc_tstatus);
			goto sdintr_error;
		}
		bzero(sdwork, sdd->sdd_sectsize);
		scop_rdwr(intr, sc, slave, SCSI_INTDIS,
			B_WRITE, sdwork, sdd->sdd_badsect, sdd->sdd_sectsize);
		printf("sd%d: block %d is reassigned (lost data)\n",
			unit, sdd->sdd_badsect);
		goto sdintr_error;

	/****************************************/
	/*					*/
	/*	issue START UNIT command	*/
	/*					*/
	/****************************************/
	case SDCS_ECC|SDCS_ECC_HOLD:
		/*
		 * Drive not ready... so start..
		 */
		sdc->sdc_state = SDCS_ECC|SDCS_ECC_HOLD|SDCS_RETRY;
sdintr_stst:
		timeout(delay_start, (caddr_t)ii, hz);
		return;

	/****************************************/
	/*					*/
	/*	RETRY with ECC & more RETRYS	*/
	/*					*/
	/****************************************/
	case SDCS_ECC|SDCS_ECC_HOLD|SDCS_RETRY:
		sdc->sdc_state = SDCS_ECCOFF;
sdintr_exec:
		if (sdc->sdc_countcc++ > MAXRETRYCNT)
			goto sdintr_error;
		sdexec(bp);
		return;

	/****************************************/
	/*					*/
	/*	reset ECC & RETRY TIMES		*/
	/*					*/
	/****************************************/
	case SDCS_ECCOFF:
		if (tstatus == TGST_CC) {
			sdc->sdc_state = SDCS_ECCOFF|SDCS_RSENSE;
			goto sdintr_rsense;
		} else if (tstatus != TGST_GOOD) {
			printf("sd%d: bad target status 0x%x\n",
				unit, sc->sc_tstatus);
			goto sdintr_error;
		}
		sdc->sdc_state = SDCS_NORMAL;
		goto sdintr_msel_reset;

sdintr_msel_set:
		/*
		 * set more ERROR RECOVERY PARAMETERS
		 */
		if ((erp_page = sdi->max_ERP_page) == NULL)
			goto check_target_status;
		bzero((caddr_t)sc->sc_param, 4);
		len = *(erp_page + 1) + 2;
		bcopy(erp_page, &sc->sc_param[4], len);

		im->im_tab.b_active = 2;
		scop_mselect(intr, sc, slave, SCSI_INTEN,
				(SDM_PF<<24) + len +4, (caddr_t)0);
		return;

sdintr_msel_reset:
		if (sdc->sdc_firmware & SDCFW_DEFMODE)
			goto sdintr_exec;

		/*
		 * set normal ERROR RECOVERY PARAMETERS
		 */
		erp_page = sdi->ERP_page;
		bzero((caddr_t)sc->sc_param, 4);
		len = *(erp_page + 1) + 2;
		bcopy(erp_page, &sc->sc_param[4], len);

		im->im_tab.b_active = 2;
		scop_mselect(intr, sc, slave, SCSI_INTEN,
				(SDM_PF<<24) + len +4, (caddr_t)0);
		return;

sdintr_error:
		bp->b_flags |= B_ERROR;
		if (sdc->sdc_state & SDCS_ECC) {
			sdc->sdc_state = SDCS_NORMAL;
			goto sdintr_msel_reset;
		}
		break;

	/*
	 * UNKNOWN STATUS
	 */
	default:
		printf("sd%d: unknown status (0x%x)\n", unit, sdc->sdc_state);
		goto sdintr_error;
	}

sdintr_done:

	if (bp->b_flags & B_ERROR) {
        printf("%s%d%c: hard error sn%d ", "sd",
            minor(bp->b_dev) >> 3, 'a'+(minor(bp->b_dev)&07), bp->b_blkno);
		printf("\n");
	}
	sdd->sdd_lastblk = dkblock(bp) + btodb(bp->b_bcount - bp->b_resid);
	sdc->sdc_countcc = 0;
	sdc->sdc_retrycnt = 0;
	sdd->sdd_flags &= ~SDDF_VBADSECT;

	if (im->im_tab.b_active) {
		im->im_tab.b_active = 0;
		im->im_tab.b_errcnt = 0;
		im->im_tab.b_actf = 0;
		dp->b_active = 0;
		dp->b_errcnt = 0;
		dp->b_actf = bp->b_actf;
		if (bp == &csdbuf[unit]) {
			register struct scsi *ksc = &kernscsi[unit];
			/* copy result */
			bcopy((caddr_t)sc, (caddr_t)ksc, sizeof(struct scsi));
		}

		iodone(bp);

		/*
		 * If this unit has more work to do,
		 * then start it up right away.
		 */
		if (dp->b_actf)
			sdustart(ii);
	}
	as &= ~(1 << slave);

	sdc->sdc_state = SDCS_NORMAL;

	/*
	 * UNLOCK SCSI access
	 */
	sdc->sdc_firmware &= ~SDCFW_BUSY;

start:
	/*
	 * Process other units which need attention.
	 * For each unit which needs attention, call
	 * the unit start routine to place the slave
	 * on the controller device queue.
	 */
	sdc->sdc_softas = 0;
	while (unit = ffs(as)) {
		unit--;
		as &= ~(1 << unit);
		sdustart(sdip[im->im_ctlr][unit]);
	}
	/*
	 * If the controller is not transferring,
	 * but there are devices ready to transfer,
	 * start the controller.
	 */
	if (im->im_tab.b_actf && im->im_tab.b_active == 0)
		(void) sdstart(im);
}

wait_re_init_done(bp)
	register struct buf *bp;
{
	if (re_init_done >= 2)
		sdexec(bp);
	else
		timeout(wait_re_init_done, bp, 10*hz);
}

delay_start(ii)
	struct iop/**/_device *ii;
{
	ii->ii_mi->im_tab.b_active = 2;
	scop_stst(ii->ii_intr, get_scsi(ii->ii_intr), ii->ii_slave,
			SCSI_INTEN, SDSS_START);
}

delay_medrmv(ii)
	struct iop/**/_device *ii;
{
	ii->ii_mi->im_tab.b_active = 2;
	scop_medrmv(ii->ii_intr, get_scsi(ii->ii_intr), ii->ii_slave,
			SCSI_INTEN, SDRMV_PREV);
}

sderrordisp(rsen_data, ii)
	u_char *rsen_data;
	struct iop/**/_device *ii;
{
	register struct sc_extnd *sce;
	register struct sdc_softc *sdc;
	register struct sdd_softc *sdd;
	register int unit;
	register int code;
	struct sc_nextnd *scn;
	struct msg_list *ml;

	unit = ii->ii_unit;
	sdc = &sdc_softc[ii->ii_ctlr];
	sdd = &sdd_softc[unit];

	sce = (struct sc_extnd *)rsen_data;

	if (sce->sce_extend == 0x70) {
		/*
		 * Extended Sense data
		 */
		code = sce->sce_sdecode;

		if (code & 0x80)
			ml = ecodelist_mo;
		else
			ml = ecodelist;

		if (sce->sce_advalid) {
			if ((sdd->sdd_flags & SDDF_VBADSECT) == 0) {
#ifdef mips
				sdd->sdd_badsect = (sce->sce_infob1 << 24) +
						   (sce->sce_infob2 << 16) +
						   (sce->sce_infob3 <<  8) +
						   (sce->sce_infob4);
#else
				sdd->sdd_badsect = *((int *)&sce->sce_infob1);
#endif
				sdd->sdd_flags |= SDDF_VBADSECT;
			}
		}

		if (!rsense_msg_disp && !isdispmsg(code, ml, sdc->sdc_countcc))
			return (code);

		if (sce->sce_advalid) {
			int sn;
#ifdef mips
			sn = (sce->sce_infob1 << 24) +
			     (sce->sce_infob2 << 16) +
			     (sce->sce_infob3 <<  8) +
			     (sce->sce_infob4);
#else
			sn = *((int *)&sce->sce_infob1);
#endif
			if (sce->sce_addlen >= 5) {
				printf("sd%d(sn %d): skey=0x%x, code=0x%x\n",
					unit, sn, sce->sce_skey, code);
			} else {
				printf("sd%d(sn %d): skey=0x%x\n",
					unit, sn, sce->sce_skey);
			}
		} else {
			if (sce->sce_addlen >= 5)
				printf("sd%d: skey=0x%x, code=0x%x\n",
					unit, sce->sce_skey, code);
			else
				printf("sd%d: skey=0x%x\n",
					unit, sce->sce_skey);
		}
		if (sce->sce_addlen >= 6)
			printf("sd%d: ASCQ=0x%x\n", unit, sce->sce_ascq);
		{
			u_char *p;
			int len;

			len = 8 + sce->sce_addlen;
			if (len > RSEN_CNT)
				len = RSEN_CNT;
			p = (u_char *)sce;
			printf("sd%d: ", unit);
			while (len--)
				printf("%x ", *p++);
			printf("\n");
		}
	} else {
		/*
		 * Non-extended Sense data
		 */
		scn = (struct sc_nextnd *)rsen_data;

		code = scn->scn_ecode;
		ml = ecodelist;
		if (sce->sce_advalid) {
			if ((sdd->sdd_flags & SDDF_VBADSECT) == 0) {
				sdd->sdd_badsect = scn->scn_secno;
				sdd->sdd_flags |= SDDF_VBADSECT;
			}
		}
		if (rsense_msg_disp || isdispmsg(code, ml, sdc->sdc_countcc)) {
			if (sce->sce_advalid)
				printf("sd%d(sn %d): code=0x%x\n",
					unit, scn->scn_secno, code);
			else
				printf("sd%d: code=0x%x\n", unit, code);
		}
	}
	return (code);
}

void
sdminphys(bp)
	struct buf *bp;
{
	if (bp->b_bcount > MAXSDPHYS)
		bp->b_bcount = MAXSDPHYS;
}


#define	sdphysio	physio

sdread(dev, uio, flag)
	register dev_t dev;
	struct uio *uio;
	int flag;
{
	register struct iop/**/_device *ii;
	register int unit;

	unit = dev2unit(dev);
	if (unit >= nsd || (ii = sddinfo[unit]) == 0)
		return (ENXIO);

	return (sdphysio(sdstrategy, &rsdbuf[unit], dev, B_READ, sdminphys, uio));
}

sdwrite(dev, uio, flag)
	register dev_t dev;
	struct uio *uio;
	int flag;
{
	register struct iop/**/_device *ii;
	register int unit;

	unit = dev2unit(dev);
	if (unit >= nsd || (ii = sddinfo[unit]) == 0)
		return (ENXIO);

	return (sdphysio(sdstrategy, &rsdbuf[unit], dev, B_WRITE, sdminphys, uio));
}

#define MAXBL 256

struct Partinfo {
/*00*/	daddr_t dp_nblocks;		/* partition size (sector #) */
/*04*/	daddr_t dp_blkoff;		/* partition start block */
/*08*/
};

struct dkst {
/*00*/	int dks_ncyl;			/* # cylinders / drive */
/*04*/	int dks_ntrak;			/* # tracks / cylinder */
/*08*/	int dks_nsect;			/* # sectors / track */
/*0c*/	int dks_rps;			/* # revolutions / second */
/*10*/
};

/*ARGSUSED*/
sdioctl(dev, cmd, data, flag)
	dev_t dev;
	int cmd;
	caddr_t data;
	int flag;
{
	register struct iop/**/_device *ii;
	register struct sc_ureq *scu;
	register struct sdst *st;
	register struct scsi *sc;
	register int unit;
	register int ctlr;
	register int slave;
	struct sdc_softc *sdc;
	struct sdd_softc *sdd;
	struct Partinfo *pi;
	struct dkst *di;
	struct buf *bp;
	struct sddevinfo *sdi;
	struct sdst *stp;
	struct scsi uscsi;
	int error;
	int i;
	int s;
	int tstatus;
	char *p;
	int blkno, count;

	unit = dev2unit(dev);
	if (unit >= nsd || (ii = sddinfo[unit]) == 0 || ii->ii_alive == 0)
		return (ENXIO);

	slave = ii->ii_slave;
	ctlr = ii->ii_ctlr;
	sdc = &sdc_softc[ctlr];
	sdd = &sdd_softc[unit];
	sc = &uscsi;

	error = 0;
	switch (cmd) {

	case DIOCWLABEL:
		if (*(int *)data & SD_F_ENW)
			ii->ii_flags |= SD_F_ENW;
		else
			ii->ii_flags &= ~SD_F_ENW;
		break;

	case DIOCGDINFO:
		*(struct disklabel *)data = sdlabel[unit];
		break;

	case DIOCSDINFO:
		sdlabel[unit] = *(struct disklabel *)data;
		disklabel2sdst(unit, &sdlabel[unit], &sdstdrv[unit]);
		break;

	case DIOCWDINFO:
	case DKIOCRGEOM:
		switch (cmd) {
		case DKIOCRGEOM:
			st = &sdstdrv[unit];
			sdi = &sddevinfo[ii->ii_type];
			stp = sdi->sdstp;

			st->ncyl = stp->ncyl;	/* # cylinders / drive */
			st->ntrak = stp->ntrak;	/* # tracks / cylinder */
			st->nsect = stp->nsect;	/* # sectors / track */
			st->rps = stp->rps;	/* # revolutions / second */

			sdst2disklabel(unit, st, &sdlabel[unit]);

			if (*(int *)data == RGEOM_SDINFO)
				goto done;

			break;

		case DIOCWDINFO:
			sdlabel[unit] = *(struct disklabel *)data;

			break;
		}

		/*
		 * Common code for DIOCWDINFO and DKIOCRGEOM
		 */

		/**** READ sector 0 ****/
		/*
		 * LOCK sdtmp buffer
		 */
		s = splclock();
		while (sdtmp_stat & B_BUSY) {
			sdtmp_stat |= B_WANTED;
			sleep((caddr_t)sdtmp, PRIBIO);
		}
		sdtmp_stat |= B_BUSY;
		splx(s);

		bzero(sdtmp, DEV_BSIZE);

		if ((sdd->sdd_flags & SDDF_NONFMT) == 0) {
			scinit(&uscsi, ii->ii_slave, sdd->sdd_sectsize); 
			uscsi.sc_cpoint = sdtmp;
			uscsi.sc_ctrnscnt = DEV_BSIZE;
			uscsi.sc_opcode = SCOP_READ;
			uscsi.sc_lad = 0;
			uscsi.sc_count = 1;
			error = sdcmd(dev, &uscsi);
		} else {
			error = EIO;
		}

		if (error) {
			/*
			 * UNLOCK sdtmp buffer
			 */
			s = splclock();
			if (sdtmp_stat & B_WANTED)
				wakeup((caddr_t)sdtmp);
			sdtmp_stat &= ~(B_BUSY|B_WANTED);
			splx(s);

			break;
		}

		*(struct disklabel *)(sdtmp + LABELOFFSET) = sdlabel[unit];

		/**** WRITE sector 0 ****/

		if (error == 0) {
			if ((sdd->sdd_flags & SDDF_NONFMT) == 0) {
				scinit(&uscsi, ii->ii_slave, sdd->sdd_sectsize);
				uscsi.sc_cpoint = sdtmp;
				uscsi.sc_ctrnscnt = DEV_BSIZE;
				uscsi.sc_opcode = SCOP_WRITE;

				uscsi.sc_lad = 0;
				uscsi.sc_count = 1;

				error = sdcmd(dev, &uscsi);
			} else
				error = EIO;
		}

		/*
		 * UNLOCK sdtmp buffer
		 */
		s = splclock();
		if (sdtmp_stat & B_WANTED)
			wakeup((caddr_t)sdtmp);
		sdtmp_stat &= ~(B_BUSY|B_WANTED);
		splx(s);

		disklabel2sdst(unit, &sdlabel[unit], &sdstdrv[unit]);

		break;

	case DKIOCGGEOM:
		st = &sdstdrv[unit];
		di = (struct dkst *)data;

		di->dks_ncyl = st->ncyl;	/* # cylinders / drive */
		di->dks_ntrak = st->ntrak;	/* # tracks / cylinder */
		di->dks_nsect = st->nsect;	/* # sectors / track */
		di->dks_rps = st->rps;		/* # revolutions / second */

		break;

	case DKIOCSGEOM:
		st = &sdstdrv[unit];
		di = (struct dkst *)data;

		st->ncyl = di->dks_ncyl;	/* # cylinders / drive */
		st->ntrak = di->dks_ntrak;	/* # tracks / cylinder */
		st->nsect = di->dks_nsect;	/* # sectors / track */
		st->rps = di->dks_rps;		/* # revolutions / second */

		sdst2disklabel(unit, st, &sdlabel[unit]);
		break;

	case DKIOCGPART:
	case DKIOCSPART:
		/*
		 * partition information
		 */
		st = &sdstdrv[unit];
		pi = (struct Partinfo *)data;

		if (cmd == DKIOCGPART) {
			pi->dp_nblocks = st->sizes[dev2part(dev)].sd_nblocks;
			pi->dp_blkoff = st->sizes[dev2part(dev)].sd_blkoff;
		} else {
			st->sizes[dev2part(dev)].sd_nblocks = pi->dp_nblocks;
			st->sizes[dev2part(dev)].sd_blkoff = pi->dp_blkoff;

			sdst2disklabel(unit, st, &sdlabel[unit]);
		}
		break;

	case DKIOCGUNIT:
		*(int *)data = slave;
		break;

	case DKIOCGCHAN:
		*(int *)data = ii->ii_intr;
		break;

	case DKIOCSEEK:
		scinit(sc, slave, sdd->sdd_sectsize); 
		sc->sc_opcode = SCOP_SEEK;
		sc->sc_lad = *(int *)data;

		(void) sdcmd(dev, sc);
		tstatus = sc->sc_tstatus & TGSTMASK;
		if ((sc->sc_istatus != INST_EP) || (tstatus != TGST_GOOD))
			error = ESPIPE;
		break;

	case DKIOCRSEC0:
	case DKIOCRBOOT1:
	case DKIOCRBOOT:
		if (sdd->sdd_flags & SDDF_NONFMT) {
			error = EIO;
			break;
		}
		switch (cmd) {

		case DKIOCRSEC0:
			blkno = 0;
			count = 1;
			break;

		case DKIOCRBOOT1:
			blkno = 1;
			count = 15;
			break;

		default:
			blkno = 0;
			count = 16;
		}
		p = (char *)*(int *)data;
		for (i = 0; !error && i < count; i++) {
			s = splclock();
			while (sdtmp_stat & B_BUSY) {
				sdtmp_stat |= B_WANTED;
				sleep((caddr_t)sdtmp, PRIBIO);
			}
			sdtmp_stat |= B_BUSY;
			splx(s);
			scinit(&uscsi, ii->ii_slave, sdd->sdd_sectsize);
			uscsi.sc_cpoint = sdtmp;
			uscsi.sc_ctrnscnt = DEV_BSIZE;
			uscsi.sc_opcode = SCOP_READ;
			uscsi.sc_lad = blkno;
			uscsi.sc_count = 1;
			if (error = sdcmd(dev, &uscsi))
				goto dkior_done;
			if (error = copyout(sdtmp, p, DEV_BSIZE))
				goto dkior_done;
			blkno++;
			p += DEV_BSIZE;

dkior_done:
			s = splclock();
			if (sdtmp_stat & B_WANTED)
				wakeup((caddr_t)sdtmp);
			sdtmp_stat &= ~(B_BUSY|B_WANTED);
			splx(s);
		}
		break;

	case DKIOCWSEC0:
	case DKIOCWBOOT1:
	case DKIOCWBOOT:
		if (sdd->sdd_flags & SDDF_NONFMT) {
			error = EIO;
			break;
		}
		switch (cmd) {

		case DKIOCWSEC0:
			blkno = 0;
			count = 1;
			break;

		case DKIOCWBOOT1:
			blkno = 1;
			count = 15;
			break;

		default:
			blkno = 0;
			count = 16;
		}
		p = (char *)*(int *)data;
		for (i = 0; !error && i < count; i++) {
			s = splclock();
			while (sdtmp_stat & B_BUSY) {
				sdtmp_stat |= B_WANTED;
				sleep(sdtmp, PRIBIO);
			}
			sdtmp_stat |= B_BUSY;
			splx(s);
			if (error = copyin(p, sdtmp, DEV_BSIZE))
				goto dkiow_done;
			scinit(&uscsi, ii->ii_slave, sdd->sdd_sectsize);
			uscsi.sc_cpoint = sdtmp;
			uscsi.sc_ctrnscnt = DEV_BSIZE;
			uscsi.sc_opcode = SCOP_WRITE;
			uscsi.sc_lad = blkno;
			uscsi.sc_count = 1;
			if (error = sdcmd(dev, &uscsi))
				goto dkiow_done;
			blkno++;
			p += DEV_BSIZE;

dkiow_done:
			s = splclock();
			if (sdtmp_stat & B_WANTED)
				wakeup(sdtmp);
			sdtmp_stat &= ~(B_BUSY|B_WANTED);
			splx(s);
		}
		break;

	case SDIOC_PRVRMV:
	case SDIOC_ALWRMV:
		/*
		 * prevent/allow medium removal
		 */
		scinit(sc, ii->ii_slave, sdd->sdd_sectsize); 
		sc->sc_opcode = SCOP_MEDRMV;
		sc->sc_count = (cmd==SDIOC_PRVRMV)? SDRMV_PREV : SDRMV_ALLOW;
		if (cmd == SDIOC_PRVRMV)
			sdd->sdd_flags |= SDDF_INHRMV;
		else
			sdd->sdd_flags &= ~SDDF_INHRMV;

		error = sdcmd(dev, sc);
		break;

	case SDIOC_SXUSE:
		if (isalone(unit) != ONLY_ONE)
			return (EBUSY);
		sdd->sdd_flags |= SDDF_XUSE;
		break;

	case SDIOC_RXUSE:
		sdd->sdd_flags &= ~SDDF_XUSE;
		break;

	case SDIOC_ERSON:
		sdd->sdd_flags &= ~SDDF_ERASEOFF;
		break;

	case SDIOC_ERSOFF:
		if ((sdd->sdd_flags & SDDF_XUSE) != 0)
			return (EBUSY);
		sdd->sdd_flags |= SDDF_ERASEOFF;
		sdd->sdd_flags &= ~SDDF_NONFMT;
		break;

	case SDIOC_FORMAT:
		/*
		 * format unit
		 */
		if ((flag & FWRITE) == 0)
			return (EINVAL);
		if (isalone(unit) != ONLY_ONE)
			return (EBUSY);
		sdd->sdd_flags |= SDDF_XUSE;
		sdd->sdd_flags &= ~(SDDF_NONFMT|SDDF_FMTDONE|SDDF_SAMEDSK);
		scinit(sc, ii->ii_slave, sdd->sdd_sectsize); 
		sc->sc_ctrnscnt = 4;
		sc->sc_opcode = SCOP_FMT;

		sc->sc_lad = ((sddevinfo[ii->ii_type].fmt_opts & FMT_DLFMT)
				| SDF_FMTDAT) << 16;

		switch (sddevinfo[ii->ii_type].type) {

		case SMO_S501:
		case SMO_S501_ISO:
		case SMO_S501_ISO2:
			sc->sc_lad |= ((SDF_MKCDA|SDF_MKPLST) << 8);
			break;

		default:
			break;
		}

		{
			struct fmt_data *fdata = (struct fmt_data *)data;

			error = copyin((caddr_t)fdata->dlh, sc->sc_param, 4);
			if (error != 0) {
				sdd->sdd_flags &= ~SDDF_XUSE;
				break;
			}
			if (fdata->noglist)
				sc->sc_lad |= (SDF_CMPLST<<16);
		}

		if (sdd->sdd_flags & SDDF_ERASEOFF)
			sc->sc_ctrl = 0x40;

		error = sdcmd(dev, sc);
		sdd->sdd_flags &= ~SDDF_XUSE;
		break;

	case SDIOC_FORMAT2:
		/*
		 * format unit
		 */
		if ((flag & FWRITE) == 0)
			return (EINVAL);
		if (isalone(unit) != ONLY_ONE)
			return (EBUSY);
		sdd->sdd_flags |= SDDF_XUSE;
		sdd->sdd_flags &= ~(SDDF_NONFMT|SDDF_FMTDONE|SDDF_SAMEDSK);

		scu = (struct sc_ureq *)data;
		error = sd_scu_exec(dev, scu, sc);
		sdd->sdd_flags &= ~SDDF_XUSE;
		break;

	case SDIOC_GSTOPT:
	case SDIOC_SSTOPT:
		/*
		 * get/set stop-unit timer
		 */
		if (cmd == SDIOC_GSTOPT)
			*(int *)data = sdd->sdd_stoptime;
		else {
			if (*(int *)data == 0)
				return (EINVAL);
			sdd->sdd_stoptime = *(int *)data;
		}
		break;

	case SDIOC_SEJECT:
		/*
		 * set auto eject flag
		 */
		sdd->sdd_flags |= SDDF_REQ_EJECT;
		break;

	case SDIOC_GFLAGS:
		/*
		 * get ii->ii_flags
		 */
		*(int *)data = ii->ii_flags;
		break;

	case SDIOC_SFLAGS:
		/*
		 * set ii->ii_flags
		 */
		ii->ii_flags = *(int *)data;
		break;

	case SDIOC_RASBLK:
		/*
		 * reassign block
		 */
		{
			struct sc_rab *sca = (struct sc_rab *)sc->sc_param;

			scinit(sc, ii->ii_slave, sdd->sdd_sectsize); 
			sc->sc_opcode = SCOP_RASBLK;
			sc->sc_ctrnscnt = 8;

			sca->sca_dllen = 4;
			sca->sca_dlad[0] = *(int *)data;

			error = sdcmd(dev, sc);
		}
		break;

	case SDIOC_GNICKNAME:
		{
			int len;

			len = strlen(sddevinfo[ii->ii_type].call_name);

			if (len > IOCPARM_MASK)
				len = IOCPARM_MASK;

			error = copyout(
				(caddr_t) sddevinfo[ii->ii_type].call_name,
				(caddr_t) *(int *)data,
				len);
		}
		break;

	case SDIOC_GTYPINDEX:
		*(int *)data = (int)ii->ii_type;
		break;

#ifdef SDIOC_SSYNCPARAM
	case SDIOC_SSYNCPARAM:
		{
		struct sync_param *syncp;

		syncp = (struct sync_param *)data;
		scinit(sc, ii->ii_slave, sdd->sdd_sectsize); 
		sc->sc_opcode = SCOP_TST;
		sc->sc_message = MSG_EXTND;	/* extended message */
		sc->sc_param[0] = MSG_EXTND;
		sc->sc_param[1] = 0x03;
		sc->sc_param[2] = 0x01;		/* synchronous transfer */
		sc->sc_param[3] = syncp->tr_period;	/* transfer period */
		sc->sc_param[4] = syncp->tr_offset;	/* REQ offset */

		(void) sdcmd(dev, sc);
		
		syncp = &sd_sync_param[unit];
		syncp->tr_period = sc->sc_param[3];
		syncp->tr_offset = sc->sc_param[4];

		if (syncp->tr_offset)
			sdd->sdd_flags |= SDDF_SYNCTR;
		else
			sdd->sdd_flags &= ~SDDF_SYNCTR;
		}
		break;

	case SDIOC_GSYNCPARAM:
		{
		struct sync_param *syncp = (struct sync_param *)data;

		syncp->tr_period = sd_sync_param[unit].tr_period;
		syncp->tr_offset = sd_sync_param[unit].tr_offset;
		}
		break;
#endif /* SDIOC_SSYNCPARAM */

	case MTIOCTOP:
		{
			register struct mtop *mtop = (struct mtop *)data;
			register int lba;
			int rest;
			int blength;

			switch (mtop->mt_op) {

			case MTOFFL:
				/*
				 * set auto eject flag
				 */
				sdd->sdd_flags |= SDDF_REQ_EJECT;
				break;

#ifdef MTERASE
			case MTERASE:
				if (isalone(unit) != ONLY_ONE)
					return (EBUSY);
				sdd->sdd_flags |= SDDF_XUSE;
				st = &sdstdrv[unit];
				/*
				 * MO disk erase
				 *	block 0 to end (C partition)
				 */
				lba = 0;
				rest = sdd->sdd_nsect;	/* C part size */
				while (rest > 0) {
					blength = (rest > MAXBL)? MAXBL : rest;
					scinit(sc, ii->ii_slave,
							sdd->sdd_sectsize); 
					sc->sc_opcode = SCOP_MOERASE;
					sc->sc_lad = lba;
					sc->sc_count = (blength % MAXBL);

					(void) sdcmd(dev, sc);
					lba += blength;
					rest -= blength;
				}
				sdd->sdd_flags &= ~SDDF_XUSE;
				break;
#endif /* MTERASE */

			default:
				return (EINVAL);
			}
		}
		break;

	case SCSIIOCCMD:
		scu = (struct sc_ureq *)data;
		if ((scu->scu_count > 0) && scu->scu_addr) {
			if (useracc(scu->scu_addr, scu->scu_count, B_WRITE)
			    == NULL) {
				error = EFAULT;
				break;
			}
		}
		error = sd_scu_exec(dev, scu, sc);
		break;


	case SDIOC_INQUIRY:
		/*
		 * LOCK sdtmp buffer
		 */
		s = splclock();
		while (sdtmp_stat & B_BUSY) {
			sdtmp_stat |= B_WANTED;
			sleep((caddr_t)sdtmp, PRIBIO);
		}
		sdtmp_stat |= B_BUSY;
		splx(s);

		bzero((caddr_t)sdtmp, sizeof(struct sc_inq));
		scinit(&uscsi, ii->ii_slave, sdd->sdd_sectsize); 
		uscsi.sc_cpoint = sdtmp;
		uscsi.sc_ctrnscnt = sizeof(struct sc_inq);
		uscsi.sc_opcode = SCOP_INQUIRY;
		uscsi.sc_count = sizeof(struct sc_inq);

		if ((error = sdcmd(dev, &uscsi)) == 0)
			bcopy((caddr_t)sdtmp, data, sizeof(struct sc_inq));
		/*
		 * UNLOCK open
		 */
		s = splclock();
		if (sdtmp_stat & B_WANTED)
			wakeup((caddr_t)sdtmp);
		sdtmp_stat &= ~(B_BUSY|B_WANTED);
		splx(s);
		break;


	case SCSIIOCGTIMEO:
		*(int *)data = sdc->sdc_timeo;
		break;

	case SCSIIOCSTIMEO:
		if (*(int *)data == 0)
			return (EINVAL);
		sdc->sdc_timeo = *(int *)data;
		sdc->sdc_wticks = 0;
		break;

	default:
		error = EINVAL;
		break;
	}

done:
	return (error);
}

static
sd_scu_exec(dev, scu, sc)
	dev_t dev;
	register struct sc_ureq *scu;
	register struct scsi *sc;
{
	struct sdd_softc *sdd;
	int error;

	sdd = &sdd_softc[dev2unit(dev)];

	if (((scu->scu_identify & MSG_IDENT) == 0)
		|| (scu->scu_identify & ~(MSG_IDENT|IDT_DISCON|IDT_DRMASK))
		|| (scu->scu_addr && (scu->scu_bytesec == 0))) {
		return (EINVAL);
	}

	bzero((caddr_t)sc, sizeof(struct scsi));
	sc->sc_tstatus = scu->scu_tstatus;
	sc->sc_identify = scu->scu_identify;
	sc->sc_message = scu->scu_message;
	sc->sc_bytesec = scu->scu_bytesec;
	sc->sc_cpoint = scu->scu_addr;
	sc->sc_ctrnscnt = scu->scu_count;
	bcopy((caddr_t)scu->scu_cdb, &sc->sc_cdb, sizeof(sc->sc_cdb));

	bcopy((caddr_t)scu->scu_param, (caddr_t)sc->sc_param,
						sizeof(sc->sc_param));

	sdd->sdd_flags |= SDDF_SKIPCHECK;
	error = sdcmd(dev, sc);
	sdd->sdd_flags &= ~SDDF_SKIPCHECK;

	scu->scu_istatus = sc->sc_istatus;
	scu->scu_tstatus = sc->sc_tstatus;
	scu->scu_message = sc->sc_message;
	bcopy((caddr_t)sc->sc_param, (caddr_t)scu->scu_param,
		sizeof(sc->sc_param));
	return (error);
}

/*ARGSUSED*/
sddump(dev)
	dev_t dev;
{
	return (ENXIO);
}

sdsize(dev)
	register dev_t dev;
{
	register struct iop/**/_device *ii;
	register struct sdd_softc *sdd;
	register struct sdst *st;
	register int unit;
	int i;

	unit = dev2unit(dev);
	if (unit >= nsd || (ii = sddinfo[unit]) == 0 || ii->ii_alive == 0)
		return (-1);

	sdd = &sdd_softc[unit];
	switch (sdc_softc[ii->ii_ctlr].sdc_firmware & SDCFW_DEVMASK) {

	case SDCFW_HD:		/* Hard Disk */
		st = &sdstdrv[unit];
		break;

	case SDCFW_MO:		/* MO only */
		if ((sdd->sdd_flags & SDDF_SAMEDSK) == SDDF_DSKCHGD) {
			/*
			 * read partition information,
			 *	and set up sdstdrv[unit]
			 */
			if (sd_b_open(dev, FREAD|FWRITE) != 0) {
				/*
				 * block device open error 
				 */
				return (-1);
			} else {
				/*
				 * use disk partition information
				 */
				st = &sdstdrv[unit];
				if (isalone(unit) == ONLY_ONE)
					sd_b_close(dev, 0);
			}
		} else if (sdd->sdd_flags & SDDF_NONFMT) {
			/*
			 * medium is not initialized.
			 */
			return (-1);
		} else {
			st = &sdstdrv[unit];
		}
		break;

	default:
    /*  case SDCFW_CD: */
		return (-1);

	}

	if (st->sizes == NULL)
		return (-1);					/* XXX */
	else
		return (st->sizes[dev2part(dev)].sd_nblocks);	/* XXX */
}

/*
 * Reset driver.
 * Cancel software state of all pending transfers,
 * and restart all units and the controller.
 */
sdreset()
{
	register struct iop/**/_ctlr *im;
	register struct iop/**/_device *ii;
	register struct sdc_softc *sdc;
	register struct sdd_softc *sdd;
	register int i;
	register int unit;

	re_init_done = 1;
	for (i = 0; i < nsdc; i++) {
		im = sdminfo[i];
		if (im == 0)
			continue;
		if (im->im_alive == 0)
			continue;
		printf(" sdc%d: ", i);
		sdc = &sdc_softc[i];
		sdc->sdc_wticks = 0;

		/* scop_init() is already called by screset() */

		sdtmp_stat &= ~B_BUSY;

		for (unit = 0; unit < nsd; unit++) {
			ii = sddinfo[unit];
			if (ii == 0)
				continue;
			if (ii->ii_alive == 0)
				continue;
			if (ii->ii_mi != im)
				continue;

			csdbuf[unit].b_flags &= ~B_BUSY;

			sdd = &sdd_softc[unit];
			sdd->sdd_flags = 0;

			/*
			 * UNLOCK SCSI access
			 */
			sdc->sdc_firmware &= ~SDCFW_BUSY;

			if (sdslave(ii, ii->ii_addr, im->im_intr) == 0) {
				printf("sd%d: not ready\n", ii->ii_slave);
				continue;
			}
			sdattach(ii);
		}
	}
	re_init_done = 2;
}

int sd_long_timeout = 24 * 60 * 60;	/* 24 hours */

#define max(a, b) (((a)>(b))?(a):(b))

/*
 * Wake up every second and if interrupt is pending
 * but nothing has happened increment a counter.
 * If nothing happens for sdc_timeo seconds, reset the IOP
 * and begin anew.
 */
sdwatch()
{
	register struct iop/**/_ctlr *im;
	register struct sdc_softc *sdc;
	register int i;
	register int unit;
	int timeo;

	extern int Scsi_Disconnect;

	timeout(sdwatch, (caddr_t)0, hz);
	for (i = 0; i < nsdc; i++) {
		im = sdminfo[i];
		if (im == 0)
			continue;
		if (im->im_alive == 0)
			continue;
		sdc = &sdc_softc[i];

		if (im->im_tab.b_active)
			goto active;

		for (unit = 0; unit < nsd; unit++)
			if (sdutab[unit].b_active && sddinfo[unit]->ii_mi == im)
				goto active;

		sdc->sdc_wticks = 0;
		continue;
active:
		if (Scsi_Disconnect)
			timeo = sdc->sdc_timeo;
		else
			timeo = max(sdc->sdc_timeo, sd_long_timeout);

		if (sdc->sdc_wticks++ >= timeo) {
			register struct scsi *sc;

			sc = get_scsi(im->im_intr);
			sdc->sdc_wticks = 0;
			printf("sdc%d: lost interrupt\n", i);

			screset(im->im_intr);
		}
	}
}

/*
 * sdstop() is timer interrupt routine.
 *	So, can't use sleep().
 */
sdstop()
{
	register struct iop/**/_ctlr *im;
	register struct iop/**/_device *ii;
	register struct sdc_softc *sdc;
	register struct sdd_softc *sdd;
	register int unit;
	register int intr;
	register int i;
	struct scsi *sc;
	int eject_sw;

	timeout(sdstop, (caddr_t)0, hz);

	for (i = 0; i < nsdc; i++) {
		im = sdminfo[i];
		if (im == 0)
			continue;
		if (im->im_alive == 0)
			continue;
		for (unit = 0; unit < nsd; unit++) {
			if ((ii = sddinfo[unit]) == 0)
				continue;
			if (ii->ii_mi != im)
				continue;
			sdc = &sdc_softc[ii->ii_ctlr];
			if ((sdc->sdc_firmware & SDCFW_RMB) == 0)
				continue;
			intr = ii->ii_intr;
			if (isalone(unit))
				continue;
			/**********************/
			/*    MO & CD-ROM     */
			/**********************/
			/*
			 * there is no process which open the unit.
			 */
			sdd = &sdd_softc[unit];
			sc = get_scsi(intr);
			if (sdd->sdd_start > 0)
				sdd->sdd_start--;
			else if (sdd->sdd_start == 0) {
				/*
				 * Now stop the unit.
				 * check SCSI access
				 */
				if (sdc->sdc_firmware & SDCFW_BUSY)
					continue;
				sdc->sdc_firmware |= SDCFW_BUSY;
				sdc->sdc_state |= SDCS_IOCTL|SDCS_SCUNLOCK;

				eject_sw = (sdd->sdd_flags & SDDF_REQ_EJECT) ?
						SDSS_EJECT : SDSS_STOP;
				scop_stst(intr, sc, ii->ii_slave,
						SCSI_INTEN, eject_sw);
				sdd->sdd_start = -2;
			}
		}
	}
}

isalone(unit)
	register int unit;
{
	register int i, n;

	n = 0;
	for (i = 0; i < PNUM; i++)
		n += (sd_b_openf[unit][i] + sd_c_openf[unit][i]);
	return (n);
}

/************************************************
 * Convert Hex and RS code table definition	*
 ************************************************/

#define	X8_L	0x001d
#define	X8_H	0x1d00


#define	hextors(data)	hxtable[(int)((data) & 0xff)]
#define	XORMASK(code)	xortable[(unsigned int)(code)]

int	hxtable[256] = {
	0x00, 0x00, 0x01, 0x19, 0x02, 0x32, 0x1a, 0xc6, 
	0x03, 0xdf, 0x33, 0xee, 0x1b, 0x68, 0xc7, 0x4b, 
	0x04, 0x64, 0xe0, 0x0e, 0x34, 0x8d, 0xef, 0x81, 
	0x1c, 0xc1, 0x69, 0xf8, 0xc8, 0x08, 0x4c, 0x71, 
	0x05, 0x8a, 0x65, 0x2f, 0xe1, 0x24, 0x0f, 0x21, 
	0x35, 0x93, 0x8e, 0xda, 0xf0, 0x12, 0x82, 0x45, 
	0x1d, 0xb5, 0xc2, 0x7d, 0x6a, 0x27, 0xf9, 0xb9, 
	0xc9, 0x9a, 0x09, 0x78, 0x4d, 0xe4, 0x72, 0xa6, 
	0x06, 0xbf, 0x8b, 0x62, 0x66, 0xdd, 0x30, 0xfd, 
	0xe2, 0x98, 0x25, 0xb3, 0x10, 0x91, 0x22, 0x88, 
	0x36, 0xd0, 0x94, 0xce, 0x8f, 0x96, 0xdb, 0xbd, 
	0xf1, 0xd2, 0x13, 0x5c, 0x83, 0x38, 0x46, 0x40, 
	0x1e, 0x42, 0xb6, 0xa3, 0xc3, 0x48, 0x7e, 0x6e, 
	0x6b, 0x3a, 0x28, 0x54, 0xfa, 0x85, 0xba, 0x3d, 
	0xca, 0x5e, 0x9b, 0x9f, 0x0a, 0x15, 0x79, 0x2b, 
	0x4e, 0xd4, 0xe5, 0xac, 0x73, 0xf3, 0xa7, 0x57, 
	0x07, 0x70, 0xc0, 0xf7, 0x8c, 0x80, 0x63, 0x0d, 
	0x67, 0x4a, 0xde, 0xed, 0x31, 0xc5, 0xfe, 0x18, 
	0xe3, 0xa5, 0x99, 0x77, 0x26, 0xb8, 0xb4, 0x7c, 
	0x11, 0x44, 0x92, 0xd9, 0x23, 0x20, 0x89, 0x2e, 
	0x37, 0x3f, 0xd1, 0x5b, 0x95, 0xbc, 0xcf, 0xcd, 
	0x90, 0x87, 0x97, 0xb2, 0xdc, 0xfc, 0xbe, 0x61, 
	0xf2, 0x56, 0xd3, 0xab, 0x14, 0x2a, 0x5d, 0x9e, 
	0x84, 0x3c, 0x39, 0x53, 0x47, 0x6d, 0x41, 0xa2, 
	0x1f, 0x2d, 0x43, 0xd8, 0xb7, 0x7b, 0xa4, 0x76, 
	0xc4, 0x17, 0x49, 0xec, 0x7f, 0x0c, 0x6f, 0xf6, 
	0x6c, 0xa1, 0x3b, 0x52, 0x29, 0x9d, 0x55, 0xaa, 
	0xfb, 0x60, 0x86, 0xb1, 0xbb, 0xcc, 0x3e, 0x5a, 
	0xcb, 0x59, 0x5f, 0xb0, 0x9c, 0xa9, 0xa0, 0x51, 
	0x0b, 0xf5, 0x16, 0xeb, 0x7a, 0x75, 0x2c, 0xd7, 
	0x4f, 0xae, 0xd5, 0xe9, 0xe6, 0xe7, 0xad, 0xe8, 
	0x74, 0xd6, 0xf4, 0xea, 0xa8, 0x50, 0x58, 0xaf, 
};

int xortable[256] = {
	0x00000000, 0x90910101, 0x91210201, 0x01b00300,
	0x92410401, 0x02d00500, 0x03600600, 0x93f10701,
	0x94810801, 0x04100900, 0x05a00a00, 0x95310b01,
	0x06c00c00, 0x96510d01, 0x97e10e01, 0x07700f00,
	0x99011001, 0x09901100, 0x08201200, 0x98b11301,
	0x0b401400, 0x9bd11501, 0x9a611601, 0x0af01700,
	0x0d801800, 0x9d111901, 0x9ca11a01, 0x0c301b00,
	0x9fc11c01, 0x0f501d00, 0x0ee01e00, 0x9e711f01,
	0x82012001, 0x12902100, 0x13202200, 0x83b12301,
	0x10402400, 0x80d12501, 0x81612601, 0x11f02700,
	0x16802800, 0x86112901, 0x87a12a01, 0x17302b00,
	0x84c12c01, 0x14502d00, 0x15e02e00, 0x85712f01,
	0x1b003000, 0x8b913101, 0x8a213201, 0x1ab03300,
	0x89413401, 0x19d03500, 0x18603600, 0x88f13701,
	0x8f813801, 0x1f103900, 0x1ea03a00, 0x8e313b01,
	0x1dc03c00, 0x8d513d01, 0x8ce13e01, 0x1c703f00,
	0xb4014001, 0x24904100, 0x25204200, 0xb5b14301,
	0x26404400, 0xb6d14501, 0xb7614601, 0x27f04700,
	0x20804800, 0xb0114901, 0xb1a14a01, 0x21304b00,
	0xb2c14c01, 0x22504d00, 0x23e04e00, 0xb3714f01,
	0x2d005000, 0xbd915101, 0xbc215201, 0x2cb05300,
	0xbf415401, 0x2fd05500, 0x2e605600, 0xbef15701,
	0xb9815801, 0x29105900, 0x28a05a00, 0xb8315b01,
	0x2bc05c00, 0xbb515d01, 0xbae15e01, 0x2a705f00,
	0x36006000, 0xa6916101, 0xa7216201, 0x37b06300,
	0xa4416401, 0x34d06500, 0x35606600, 0xa5f16701,
	0xa2816801, 0x32106900, 0x33a06a00, 0xa3316b01,
	0x30c06c00, 0xa0516d01, 0xa1e16e01, 0x31706f00,
	0xaf017001, 0x3f907100, 0x3e207200, 0xaeb17301,
	0x3d407400, 0xadd17501, 0xac617601, 0x3cf07700,
	0x3b807800, 0xab117901, 0xaaa17a01, 0x3a307b00,
	0xa9c17c01, 0x39507d00, 0x38e07e00, 0xa8717f01,
	0xd8018001, 0x48908100, 0x49208200, 0xd9b18301,
	0x4a408400, 0xdad18501, 0xdb618601, 0x4bf08700,
	0x4c808800, 0xdc118901, 0xdda18a01, 0x4d308b00,
	0xdec18c01, 0x4e508d00, 0x4fe08e00, 0xdf718f01,
	0x41009000, 0xd1919101, 0xd0219201, 0x40b09300,
	0xd3419401, 0x43d09500, 0x42609600, 0xd2f19701,
	0xd5819801, 0x45109900, 0x44a09a00, 0xd4319b01,
	0x47c09c00, 0xd7519d01, 0xd6e19e01, 0x46709f00,
	0x5a00a000, 0xca91a101, 0xcb21a201, 0x5bb0a300,
	0xc841a401, 0x58d0a500, 0x5960a600, 0xc9f1a701,
	0xce81a801, 0x5e10a900, 0x5fa0aa00, 0xcf31ab01,
	0x5cc0ac00, 0xcc51ad01, 0xcde1ae01, 0x5d70af00,
	0xc301b001, 0x5390b100, 0x5220b200, 0xc2b1b301,
	0x5140b400, 0xc1d1b501, 0xc061b601, 0x50f0b700,
	0x5780b800, 0xc711b901, 0xc6a1ba01, 0x5630bb00,
	0xc5c1bc01, 0x5550bd00, 0x54e0be00, 0xc471bf01,
	0x6c00c000, 0xfc91c101, 0xfd21c201, 0x6db0c300,
	0xfe41c401, 0x6ed0c500, 0x6f60c600, 0xfff1c701,
	0xf881c801, 0x6810c900, 0x69a0ca00, 0xf931cb01,
	0x6ac0cc00, 0xfa51cd01, 0xfbe1ce01, 0x6b70cf00,
	0xf501d001, 0x6590d100, 0x6420d200, 0xf4b1d301,
	0x6740d400, 0xf7d1d501, 0xf661d601, 0x66f0d700,
	0x6180d800, 0xf111d901, 0xf0a1da01, 0x6030db00,
	0xf3c1dc01, 0x6350dd00, 0x62e0de00, 0xf271df01,
	0xee01e001, 0x7e90e100, 0x7f20e200, 0xefb1e301,
	0x7c40e400, 0xecd1e501, 0xed61e601, 0x7df0e700,
	0x7a80e800, 0xea11e901, 0xeba1ea01, 0x7b30eb00,
	0xe8c1ec01, 0x7850ed00, 0x79e0ee00, 0xe971ef01,
	0x7700f000, 0xe791f101, 0xe621f201, 0x76b0f300,
	0xe541f401, 0x75d0f500, 0x7460f600, 0xe4f1f701,
	0xe381f801, 0x7310f900, 0x72a0fa00, 0xe231fb01,
	0x71c0fc00, 0xe151fd01, 0xe0e1fe01, 0x7070ff00
};

/********************************************************
 * EDC (Error Detection Code) check			*
 *			(using CRC code)		*
 *	MODE 1 : Sync + Header + User data + edc	*
 *	MODE 2 : Sub header + User data + edc		*
 ********************************************************/

#define	SYNC_EDC	0x908eff4e	/* sector sync EDC value	*/
#define	HEADER		4		/* header length 4 bytes	*/
#define	SUB_H		8		/* Subheader length 8 bytes	*/
#define	USER_DATA	2048		/* User data length 2048 bytes	*/
#define	EDC_LEN		4		/* EDC code length 4 bytes	*/

cal_edc1(db, mode)
	register unsigned char	*db;
	int mode;
{
	register int j;
	register unsigned int work;

	j = USER_DATA + EDC_LEN;
	if (mode == 2) {
		j += SUB_H;
		work = 0;
	} else {
		j += HEADER;
		work = SYNC_EDC;
	}
	while (--j >= 0)
		work = ((work >> 8) | (*db++ << 24)) ^ XORMASK(work & 0xff);

	return (work);
}


/************************************************/
/*	error detection & correction		*/
/*	if form 1				*/
/*	    header area should be zero		*/
/************************************************/

/*
 *	error detection & correction  P-direction
 */
p_dir(dbuf)
	register unsigned short	*dbuf;
{
	unsigned short	s0, s1, d;
	register int	col, row;
	register int	x;
	unsigned char	s0l, s0h, s1l, s1h;
 
	/*
	 * calculate syndrome S0 S1
	 */
	for (col = 0; col < 43; col++) {
		s0 = s1 = 0;

		for (row = 0; row < 26; row++) {
			d = dbuf[43 * row + col];
			s0 ^= d;
			s1 = rsshift(s1) ^ d; 
		}

		s0h = s0 & 0x00ff;
		s1h = s1 & 0x00ff;
		s0l = (s0 >> 8) & 0x00ff;
		s1l = (s1 >> 8) & 0x00ff;
		/*
		 * calculate error position & correction
		 */
		if (s0l != 0) {
			if((x = hextors(s1l) - hextors(s0l)) < 0)
				x += 255;
			if ((x >= 0) && (x < 26)) {
				x = 25 - x;
				/*
				 * correction
				 */
				dbuf[43 * x + col] ^= (s0 & 0xff00);
			}
		}

		/*
		 * calculate error position & correction
		 */
		if (s0h != 0) {
			if((x = hextors(s1h) - hextors(s0h)) < 0)
				x += 255;
			if ((x >= 0) && (x < 26)) {
				x = 25 - x;
				/*
				 * correction
				 */
				dbuf[43 * x + col] ^= (s0 & 0x00ff);
			}
		}
	}
}

/*
 * error detection & correction  Q-direction
 */
q_dir(dbuf)
	register unsigned short	*dbuf;
{
	unsigned short s0, s1, d;
	register int col, row;
	register int x;
	unsigned char s0l, s0h, s1l, s1h;

	/*
	 * calculate syndrome S0 S1
	 */
	for (row = 0; row < 26; row++) {
		s0 = s1 = 0;

		for (col = 0; col < 45; col++) {
			if (col < 43)
				d = dbuf[(44 * col + 43 * row) % 1118];
			else if (col == 43)
				d = dbuf[43 * 26 + row];
			else
				d = dbuf[44 * 26 + row];
			s0 ^= d;
			s1 = rsshift(s1) ^ d; 
		}

		s0h = s0 & 0x00ff;
		s1h = s1 & 0x00ff;
		s0l = (s0 >> 8) & 0x00ff;
		s1l = (s1 >> 8) & 0x00ff;
		/*
		 * calculate error position & correction
		 */
		if (s0l != 0) {
			if((x = hextors(s1l) - hextors(s0l)) < 0)
				x += 255;
			if (x >= 0 && x < 45) {
				x = 44 - x;
				/*
				 * correction
				 */
				if (x < 43)
					dbuf[(44 * x + 43 * row) % 1118]
						^= s0 & 0xff00;
				else if (x == 43)
					dbuf[43 * 26 + row] ^= s0 & 0xff00;
				else
					dbuf[44 * 26 + row] ^= s0 & 0xff00;
			}
		}

		/*
		 * calculate error position & correction
		 */
		if (s0h != 0) {
			if((x = hextors(s1h) - hextors(s0h)) < 0)
				x += 255;
			if ((x >= 0) && (x < 45)) {
				x = 44 - x;
				/*
				 * correction
				 */
				if (x < 43)
					dbuf[(44 * x + 43 * row) % 1118]
						^= s0 & 0x00ff;
				else if ( x == 43)
					dbuf[43 * 26 + row] ^= s0 & 0x00ff;
				else
					dbuf[44 * 26 + row] ^= s0 & 0x00ff;
			}
		}
	}
}

/*
 *	shift high & low byte at the same time
 */
rsshift(d)
	unsigned short d;
{
	register int x;
	register int dmy;	/* This way is faster */

	dmy = (int)d;
	x = (dmy << 1) & 0xfefe;	/* clear LSB of high & low byte */
	if ((dmy & 0x0080) != 0)
		x ^= X8_L;
	if ((dmy & 0x8000) != 0)
		x ^= X8_H;
	return(x);
}
#endif /* NSD > 0 */