/*
XXX
SIZEOF testen !!!
*/
/* @(#)scsi_cdr.c	1.136 04/03/02 Copyright 1995-2004 J. Schilling */
#ifndef lint
static	char sccsid[] =
	"@(#)scsi_cdr.c	1.136 04/03/02 Copyright 1995-2004 J. Schilling";
#endif
/*
 *	SCSI command functions for cdrecord
 *	covering pre-MMC standard functions up to MMC-2
 *
 *	Copyright (c) 1995-2004 J. Schilling
 */
/*
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2, or (at your option)
 * any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along with
 * this program; see the file COPYING.  If not, write to the Free Software
 * Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */

/*
 * NOTICE:	The Philips CDD 521 has several firmware bugs.
 *		One of them is not to respond to a SCSI selection
 *		within 200ms if the general load on the
 *		SCSI bus is high. To deal with this problem
 *		most of the SCSI commands are send with the
 *		SCG_CMD_RETRY flag enabled.
 */
#include <mconfig.h>

#include <stdio.h>
#include <standard.h>
#include <stdxlib.h>
#include <unixstd.h>
#include <fctldefs.h>
#include <errno.h>
#include <strdefs.h>
#include <timedefs.h>

#include <utypes.h>
#include <btorder.h>
#include <intcvt.h>
#include <schily.h>

#include <scg/scgcmd.h>
#include <scg/scsidefs.h>
#include <scg/scsireg.h>
#include <scg/scsitransp.h>

#include "scsimmc.h"
#include "cdrecord.h"

#define	strbeg(s1, s2)	(strstr((s2), (s1)) == (s2))

EXPORT	BOOL	unit_ready	__PR((SCSI *scgp));
EXPORT	BOOL	wait_unit_ready	__PR((SCSI *scgp, int secs));
EXPORT	BOOL	scsi_in_progress __PR((SCSI *scgp));
EXPORT	BOOL	cdr_underrun	__PR((SCSI *scgp));
EXPORT	int	test_unit_ready	__PR((SCSI *scgp));
EXPORT	int	rezero_unit	__PR((SCSI *scgp));
EXPORT	int	request_sense	__PR((SCSI *scgp));
EXPORT	int	request_sense_b	__PR((SCSI *scgp, caddr_t bp, int cnt));
EXPORT	int	inquiry		__PR((SCSI *scgp, caddr_t, int));
EXPORT	int	read_capacity	__PR((SCSI *scgp));
EXPORT	void	print_capacity	__PR((SCSI *scgp, FILE *f));
EXPORT	int	scsi_load_unload __PR((SCSI *scgp, int));
EXPORT	int	scsi_prevent_removal __PR((SCSI *scgp, int));
EXPORT	int	scsi_start_stop_unit __PR((SCSI *scgp, int, int, BOOL immed));
EXPORT	int	scsi_set_speed	__PR((SCSI *scgp, int readspeed, int writespeed, int rotctl));
EXPORT	int	scsi_get_speed	__PR((SCSI *scgp, int *readspeedp, int *writespeedp));
EXPORT	int	qic02		__PR((SCSI *scgp, int));
EXPORT	int	write_xscsi	__PR((SCSI *scgp, caddr_t, long, long, int));
EXPORT	int	write_xg0	__PR((SCSI *scgp, caddr_t, long, long, int));
EXPORT	int	write_xg1	__PR((SCSI *scgp, caddr_t, long, long, int));
EXPORT	int	write_xg5	__PR((SCSI *scgp, caddr_t, long, long, int));
EXPORT	int	seek_scsi	__PR((SCSI *scgp, long addr));
EXPORT	int	seek_g0		__PR((SCSI *scgp, long addr));
EXPORT	int	seek_g1		__PR((SCSI *scgp, long addr));
EXPORT	int	scsi_flush_cache __PR((SCSI *scgp, BOOL immed));
EXPORT	int	read_buffer	__PR((SCSI *scgp, caddr_t bp, int cnt, int mode));
EXPORT	int	read_subchannel	__PR((SCSI *scgp, caddr_t bp, int track,
					int cnt, int msf, int subq, int fmt));
EXPORT	int	read_toc	__PR((SCSI *scgp, caddr_t, int, int, int, int));
EXPORT	int	read_toc_philips __PR((SCSI *scgp, caddr_t, int, int, int, int));
EXPORT	int	read_header	__PR((SCSI *scgp, caddr_t, long, int, int));
EXPORT	int	read_disk_info	__PR((SCSI *scgp, caddr_t, int));
EXPORT	int	read_track_info	__PR((SCSI *scgp, caddr_t, int type, int addr, int cnt));
EXPORT	int	read_rzone_info	__PR((SCSI *scgp, caddr_t bp, int cnt));
EXPORT	int	reserve_tr_rzone __PR((SCSI *scgp, long size));
EXPORT	int	read_dvd_structure __PR((SCSI *scgp, caddr_t bp, int cnt, int addr, int layer, int fmt));
EXPORT	int	send_opc	__PR((SCSI *scgp, caddr_t, int cnt, int doopc));
EXPORT	int	read_track_info_philips	__PR((SCSI *scgp, caddr_t, int, int));
EXPORT	int	scsi_close_tr_session __PR((SCSI *scgp, int type, int track, BOOL immed));
EXPORT	int	read_master_cue	__PR((SCSI *scgp, caddr_t bp, int sheet, int cnt));
EXPORT	int	send_cue_sheet	__PR((SCSI *scgp, caddr_t bp, long size));
EXPORT	int	read_buff_cap	__PR((SCSI *scgp, long *, long *));
EXPORT	int	scsi_blank	__PR((SCSI *scgp, long addr, int blanktype, BOOL immed));
EXPORT	BOOL	allow_atapi	__PR((SCSI *scgp, BOOL new));
EXPORT	int	mode_select	__PR((SCSI *scgp, Uchar *, int, int, int));
EXPORT	int	mode_sense	__PR((SCSI *scgp, Uchar *dp, int cnt, int page, int pcf));
EXPORT	int	mode_select_sg0	__PR((SCSI *scgp, Uchar *, int, int, int));
EXPORT	int	mode_sense_sg0	__PR((SCSI *scgp, Uchar *dp, int cnt, int page, int pcf));
EXPORT	int	mode_select_g0	__PR((SCSI *scgp, Uchar *, int, int, int));
EXPORT	int	mode_select_g1	__PR((SCSI *scgp, Uchar *, int, int, int));
EXPORT	int	mode_sense_g0	__PR((SCSI *scgp, Uchar *dp, int cnt, int page, int pcf));
EXPORT	int	mode_sense_g1	__PR((SCSI *scgp, Uchar *dp, int cnt, int page, int pcf));
EXPORT	int	read_tochdr	__PR((SCSI *scgp, cdr_t *, int *, int *));
EXPORT	int	read_cdtext	__PR((SCSI *scgp));
EXPORT	int	read_trackinfo	__PR((SCSI *scgp, int, long *, struct msf *, int *, int *, int *));
EXPORT	int	read_B0		__PR((SCSI *scgp, BOOL isbcd, long *b0p, long *lop));
EXPORT	int	read_session_offset __PR((SCSI *scgp, long *));
EXPORT	int	read_session_offset_philips __PR((SCSI *scgp, long *));
EXPORT	int	sense_secsize	__PR((SCSI *scgp, int current));
EXPORT	int	select_secsize	__PR((SCSI *scgp, int));
EXPORT	BOOL	is_cddrive	__PR((SCSI *scgp));
EXPORT	BOOL	is_unknown_dev	__PR((SCSI *scgp));
EXPORT	int	read_scsi	__PR((SCSI *scgp, caddr_t, long, int));
EXPORT	int	read_g0		__PR((SCSI *scgp, caddr_t, long, int));
EXPORT	int	read_g1		__PR((SCSI *scgp, caddr_t, long, int));
EXPORT	BOOL	getdev		__PR((SCSI *scgp, BOOL));
EXPORT	void	printinq	__PR((SCSI *scgp, FILE *f));
EXPORT	void	printdev	__PR((SCSI *scgp));
EXPORT	BOOL	do_inquiry	__PR((SCSI *scgp, BOOL));
EXPORT	BOOL	recovery_needed	__PR((SCSI *scgp, cdr_t *));
EXPORT	int	scsi_load	__PR((SCSI *scgp, cdr_t *));
EXPORT	int	scsi_unload	__PR((SCSI *scgp, cdr_t *));
EXPORT	int	scsi_cdr_write	__PR((SCSI *scgp, caddr_t bp, long sectaddr, long size, int blocks, BOOL islast));
EXPORT	struct cd_mode_page_2A * mmc_cap __PR((SCSI *scgp, Uchar *modep));
EXPORT	void	mmc_getval	__PR((struct cd_mode_page_2A *mp,
					BOOL *cdrrp, BOOL *cdwrp,
					BOOL *cdrrwp, BOOL *cdwrwp,
					BOOL *dvdp, BOOL *dvdwp));
EXPORT	BOOL	is_mmc		__PR((SCSI *scgp, BOOL *cdwp, BOOL *dvdwp));
EXPORT	BOOL	mmc_check	__PR((SCSI *scgp, BOOL *cdrrp, BOOL *cdwrp,
					BOOL *cdrrwp, BOOL *cdwrwp,
					BOOL *dvdp, BOOL *dvdwp));
LOCAL	void	print_speed	__PR((char *fmt, int val));
EXPORT	void	print_capabilities __PR((SCSI *scgp));

EXPORT BOOL
unit_ready(scgp)
	SCSI	*scgp;
{
	register struct	scg_cmd	*scmd = scgp->scmd;

	if (test_unit_ready(scgp) >= 0)		/* alles OK */
		return (TRUE);
	else if (scmd->error >= SCG_FATAL)	/* nicht selektierbar */
		return (FALSE);

	if (scg_sense_key(scgp) == SC_UNIT_ATTENTION) {
		if (test_unit_ready(scgp) >= 0)	/* alles OK */
			return (TRUE);
	}
	if ((scg_cmd_status(scgp) & ST_BUSY) != 0) {
		/*
		 * Busy/reservation_conflict
		 */
		usleep(500000);
		if (test_unit_ready(scgp) >= 0)	/* alles OK */
			return (TRUE);
	}
	if (scg_sense_key(scgp) == -1) {	/* non extended Sense */
		if (scg_sense_code(scgp) == 4)	/* NOT_READY */
			return (FALSE);
		return (TRUE);
	}
						/* FALSE wenn NOT_READY */
	return (scg_sense_key(scgp) != SC_NOT_READY);
}

EXPORT BOOL
wait_unit_ready(scgp, secs)
	SCSI	*scgp;
	int	secs;
{
	int	i;
	int	c;
	int	k;
	int	ret;

	scgp->silent++;
	ret = test_unit_ready(scgp);		/* eat up unit attention */
	if (ret < 0)
		ret = test_unit_ready(scgp);	/* got power on condition? */
	scgp->silent--;

	if (ret >= 0)				/* success that's enough */
		return (TRUE);

	scgp->silent++;
	for (i = 0; i < secs && (ret = test_unit_ready(scgp)) < 0; i++) {
		if (scgp->scmd->scb.busy != 0) {
			sleep(1);
			continue;
		}
		c = scg_sense_code(scgp);
		k = scg_sense_key(scgp);
		/*
		 * Abort quickly if it does not make sense to wait.
		 * 0x30 == Cannot read medium
		 * 0x3A == Medium not present
		 */
		if ((k == SC_NOT_READY && (c == 0x3A || c == 0x30)) ||
		    (k == SC_MEDIUM_ERROR)) {
			if (scgp->silent <= 1)
				scg_printerr(scgp);
			scgp->silent--;
			return (FALSE);
		}
		sleep(1);
	}
	scgp->silent--;
	if (ret < 0)
		return (FALSE);
	return (TRUE);
}

EXPORT BOOL
scsi_in_progress(scgp)
	SCSI	*scgp;
{
	if (scg_sense_key(scgp) == SC_NOT_READY &&
		/*
		 * Logigal unit not ready operation/long_write in progress
		 */
	    scg_sense_code(scgp) == 0x04 &&
	    (scg_sense_qual(scgp) == 0x04 || /* CyberDr. "format in progress"*/
	    scg_sense_qual(scgp) == 0x07 || /* "operation in progress"	    */
	    scg_sense_qual(scgp) == 0x08)) { /* "long write in progress"    */
		return (TRUE);
	} else {
		if (scgp->silent <= 1)
			scg_printerr(scgp);
	}
	return (FALSE);
}

EXPORT BOOL
cdr_underrun(scgp)
	SCSI	*scgp;
{
	if ((scg_sense_key(scgp) != SC_ILLEGAL_REQUEST &&
	    scg_sense_key(scgp) != SC_MEDIUM_ERROR))
		return (FALSE);

	if ((scg_sense_code(scgp) == 0x21 &&
	    (scg_sense_qual(scgp) == 0x00 ||	/* logical block address out of range */
	    scg_sense_qual(scgp) == 0x02)) ||	/* invalid address for write */

	    (scg_sense_code(scgp) == 0x0C &&
	    scg_sense_qual(scgp) == 0x09)) {	/* write error - loss of streaming */
		return (TRUE);
	}
	/*
	 * XXX Bei manchen Brennern kommt mach dem der Brennvorgang bereits
	 * XXX eine Weile gelaufen ist ein 5/24/0 Invalid field in CDB.
	 * XXX Daher sollte man testen ob schon geschrieben wurde...
	 */
	return (FALSE);
}

EXPORT int
test_unit_ready(scgp)
	SCSI	*scgp;
{
	register struct	scg_cmd	*scmd = scgp->scmd;

	fillbytes((caddr_t)scmd, sizeof (*scmd), '\0');
	scmd->addr = (caddr_t)0;
	scmd->size = 0;
	scmd->flags = SCG_DISRE_ENA | (scgp->silent ? SCG_SILENT:0);
	scmd->cdb_len = SC_G0_CDBLEN;
	scmd->sense_len = CCS_SENSE_LEN;
	scmd->cdb.g0_cdb.cmd = SC_TEST_UNIT_READY;
	scmd->cdb.g0_cdb.lun = scg_lun(scgp);

	scgp->cmdname = "test unit ready";

	return (scg_cmd(scgp));
}

EXPORT int
rezero_unit(scgp)
	SCSI	*scgp;
{
	register struct	scg_cmd	*scmd = scgp->scmd;

	fillbytes((caddr_t)scmd, sizeof (*scmd), '\0');
	scmd->addr = (caddr_t)0;
	scmd->size = 0;
	scmd->flags = SCG_DISRE_ENA;
	scmd->cdb_len = SC_G0_CDBLEN;
	scmd->sense_len = CCS_SENSE_LEN;
	scmd->cdb.g0_cdb.cmd = SC_REZERO_UNIT;
	scmd->cdb.g0_cdb.lun = scg_lun(scgp);

	scgp->cmdname = "rezero unit";

	return (scg_cmd(scgp));
}

EXPORT int
request_sense(scgp)
	SCSI	*scgp;
{
		char	sensebuf[CCS_SENSE_LEN];
	register struct	scg_cmd	*scmd = scgp->scmd;


	fillbytes((caddr_t)scmd, sizeof (*scmd), '\0');
	scmd->addr = sensebuf;
	scmd->size = sizeof (sensebuf);
	scmd->flags = SCG_RECV_DATA|SCG_DISRE_ENA;
	scmd->cdb_len = SC_G0_CDBLEN;
	scmd->sense_len = CCS_SENSE_LEN;
	scmd->cdb.g0_cdb.cmd = SC_REQUEST_SENSE;
	scmd->cdb.g0_cdb.lun = scg_lun(scgp);
	scmd->cdb.g0_cdb.count = CCS_SENSE_LEN;

	scgp->cmdname = "request_sense";

	if (scg_cmd(scgp) < 0)
		return (-1);
	scg_prsense((Uchar *)sensebuf, CCS_SENSE_LEN - scg_getresid(scgp));
	return (0);
}

EXPORT int
request_sense_b(scgp, bp, cnt)
	SCSI	*scgp;
	caddr_t	bp;
	int	cnt;
{
	register struct	scg_cmd	*scmd = scgp->scmd;


	fillbytes((caddr_t)scmd, sizeof (*scmd), '\0');
	scmd->addr = bp;
	scmd->size = cnt;
	scmd->flags = SCG_RECV_DATA|SCG_DISRE_ENA;
	scmd->cdb_len = SC_G0_CDBLEN;
	scmd->sense_len = CCS_SENSE_LEN;
	scmd->cdb.g0_cdb.cmd = SC_REQUEST_SENSE;
	scmd->cdb.g0_cdb.lun = scg_lun(scgp);
	scmd->cdb.g0_cdb.count = cnt;

	scgp->cmdname = "request_sense";

	if (scg_cmd(scgp) < 0)
		return (-1);
	return (0);
}

EXPORT int
inquiry(scgp, bp, cnt)
	SCSI	*scgp;
	caddr_t	bp;
	int	cnt;
{
	register struct	scg_cmd	*scmd = scgp->scmd;

	fillbytes(bp, cnt, '\0');
	fillbytes((caddr_t)scmd, sizeof (*scmd), '\0');
	scmd->addr = bp;
	scmd->size = cnt;
	scmd->flags = SCG_RECV_DATA|SCG_DISRE_ENA;
	scmd->cdb_len = SC_G0_CDBLEN;
	scmd->sense_len = CCS_SENSE_LEN;
	scmd->cdb.g0_cdb.cmd = SC_INQUIRY;
	scmd->cdb.g0_cdb.lun = scg_lun(scgp);
	scmd->cdb.g0_cdb.count = cnt;

	scgp->cmdname = "inquiry";

	if (scg_cmd(scgp) < 0)
		return (-1);
	if (scgp->verbose)
		scg_prbytes("Inquiry Data   :", (Uchar *)bp, cnt - scg_getresid(scgp));
	return (0);
}

EXPORT int
read_capacity(scgp)
	SCSI	*scgp;
{
	register struct	scg_cmd	*scmd = scgp->scmd;

	fillbytes((caddr_t)scmd, sizeof (*scmd), '\0');
	scmd->addr = (caddr_t)scgp->cap;
	scmd->size = sizeof (struct scsi_capacity);
	scmd->flags = SCG_RECV_DATA|SCG_DISRE_ENA;
	scmd->cdb_len = SC_G1_CDBLEN;
	scmd->sense_len = CCS_SENSE_LEN;
	scmd->cdb.g1_cdb.cmd = 0x25;	/* Read Capacity */
	scmd->cdb.g1_cdb.lun = scg_lun(scgp);
	g1_cdblen(&scmd->cdb.g1_cdb, 0); /* Full Media */

	scgp->cmdname = "read capacity";

	if (scg_cmd(scgp) < 0) {
		return (-1);
	} else {
		long	cbsize;
		long	cbaddr;

		/*
		 * c_bsize & c_baddr are signed Int32_t
		 * so we use signed int conversion here.
		 */
		cbsize = a_to_4_byte(&scgp->cap->c_bsize);
		cbaddr = a_to_4_byte(&scgp->cap->c_baddr);
		scgp->cap->c_bsize = cbsize;
		scgp->cap->c_baddr = cbaddr;
	}
	return (0);
}

EXPORT void
print_capacity(scgp, f)
	SCSI	*scgp;
	FILE	*f;
{
	long	kb;
	long	mb;
	long	prmb;
	double	dkb;

	dkb =  (scgp->cap->c_baddr+1.0) * (scgp->cap->c_bsize/1024.0);
	kb = dkb;
	mb = dkb / 1024.0;
	prmb = dkb / 1000.0 * 1.024;
	fprintf(f, "Capacity: %ld Blocks = %ld kBytes = %ld MBytes = %ld prMB\n",
		(long)scgp->cap->c_baddr+1, kb, mb, prmb);
	fprintf(f, "Sectorsize: %ld Bytes\n", (long)scgp->cap->c_bsize);
}

EXPORT int
scsi_load_unload(scgp, load)
	SCSI	*scgp;
	int	load;
{
	register struct	scg_cmd	*scmd = scgp->scmd;

	fillbytes((caddr_t)scmd, sizeof (*scmd), '\0');
	scmd->flags = SCG_DISRE_ENA;
	scmd->cdb_len = SC_G5_CDBLEN;
	scmd->sense_len = CCS_SENSE_LEN;
	scmd->cdb.g5_cdb.cmd = 0xA6;
	scmd->cdb.g5_cdb.lun = scg_lun(scgp);
	scmd->cdb.g5_cdb.addr[1] = load?3:2;
	scmd->cdb.g5_cdb.count[2] = 0; /* slot # */

	scgp->cmdname = "medium load/unload";

	if (scg_cmd(scgp) < 0)
		return (-1);
	return (0);
}

EXPORT int
scsi_prevent_removal(scgp, prevent)
	SCSI	*scgp;
	int	prevent;
{
	register struct	scg_cmd	*scmd = scgp->scmd;

	fillbytes((caddr_t)scmd, sizeof (*scmd), '\0');
	scmd->flags = SCG_DISRE_ENA;
	scmd->cdb_len = SC_G0_CDBLEN;
	scmd->sense_len = CCS_SENSE_LEN;
	scmd->cdb.g0_cdb.cmd = 0x1E;
	scmd->cdb.g0_cdb.lun = scg_lun(scgp);
	scmd->cdb.g0_cdb.count = prevent & 1;

	scgp->cmdname = "prevent/allow medium removal";

	if (scg_cmd(scgp) < 0)
		return (-1);
	return (0);
}


EXPORT int
scsi_start_stop_unit(scgp, flg, loej, immed)
	SCSI	*scgp;
	int	flg;
	int	loej;
	BOOL	immed;
{
	register struct	scg_cmd	*scmd = scgp->scmd;

	fillbytes((caddr_t)scmd, sizeof (*scmd), '\0');
	scmd->flags = SCG_DISRE_ENA;
	scmd->cdb_len = SC_G0_CDBLEN;
	scmd->sense_len = CCS_SENSE_LEN;
	scmd->cdb.g0_cdb.cmd = 0x1B;	/* Start Stop Unit */
	scmd->cdb.g0_cdb.lun = scg_lun(scgp);
	scmd->cdb.g0_cdb.count = (flg ? 1:0) | (loej ? 2:0);

	if (immed)
		scmd->cdb.cmd_cdb[1] |= 0x01;

	scgp->cmdname = "start/stop unit";

	return (scg_cmd(scgp));
}

EXPORT int
scsi_set_speed(scgp, readspeed, writespeed, rotctl)
	SCSI	*scgp;
	int	readspeed;
	int	writespeed;
	int	rotctl;
{
	register struct	scg_cmd	*scmd = scgp->scmd;

	fillbytes((caddr_t)scmd, sizeof (*scmd), '\0');
	scmd->flags = SCG_DISRE_ENA;
	scmd->cdb_len = SC_G5_CDBLEN;
	scmd->sense_len = CCS_SENSE_LEN;
	scmd->cdb.g5_cdb.cmd = 0xBB;
	scmd->cdb.g5_cdb.lun = scg_lun(scgp);

	if (readspeed < 0)
		i_to_2_byte(&scmd->cdb.g5_cdb.addr[0], 0xFFFF);
	else
		i_to_2_byte(&scmd->cdb.g5_cdb.addr[0], readspeed);
	if (writespeed < 0)
		i_to_2_byte(&scmd->cdb.g5_cdb.addr[2], 0xFFFF);
	else
		i_to_2_byte(&scmd->cdb.g5_cdb.addr[2], writespeed);

	scmd->cdb.cmd_cdb[1] |= rotctl & 0x03;

	scgp->cmdname = "set cd speed";

	if (scg_cmd(scgp) < 0)
		return (-1);
	return (0);
}

EXPORT int
scsi_get_speed(scgp, readspeedp, writespeedp)
	SCSI	*scgp;
	int	*readspeedp;
	int	*writespeedp;
{
	struct	cd_mode_page_2A *mp;
	Uchar	m[256];
	int	val;

	scgp->silent++;
	mp = mmc_cap(scgp, m); /* Get MMC capabilities in allocated mp */
	scgp->silent--;
	if (mp == NULL)
		return (-1);	/* Pre SCSI-3/mmc drive		*/

	val = a_to_u_2_byte(mp->cur_read_speed);
	if (readspeedp)
		*readspeedp = val;

	if (mp->p_len >= 28)
		val = a_to_u_2_byte(mp->v3_cur_write_speed);
	else
		val = a_to_u_2_byte(mp->cur_write_speed);
	if (writespeedp)
		*writespeedp = val;

	return (0);
}


EXPORT int
qic02(scgp, cmd)
	SCSI	*scgp;
	int	cmd;
{
	register struct	scg_cmd	*scmd = scgp->scmd;

	fillbytes((caddr_t)scmd, sizeof (*scmd), '\0');
	scmd->addr = (caddr_t)0;
	scmd->size = 0;
	scmd->flags = SCG_DISRE_ENA;
	scmd->cdb_len = SC_G0_CDBLEN;
	scmd->sense_len = DEF_SENSE_LEN;
	scmd->cdb.g0_cdb.cmd = 0x0D;	/* qic02 Sysgen SC4000 */
	scmd->cdb.g0_cdb.lun = scg_lun(scgp);
	scmd->cdb.g0_cdb.mid_addr = cmd;

	scgp->cmdname = "qic 02";
	return (scg_cmd(scgp));
}

#define	G0_MAXADDR	0x1FFFFFL

EXPORT int
write_xscsi(scgp, bp, addr, size, cnt)
	SCSI	*scgp;
	caddr_t	bp;		/* address of buffer */
	long	addr;		/* disk address (sector) to put */
	long	size;		/* number of bytes to transfer */
	int	cnt;		/* sectorcount */
{
	if (addr <= G0_MAXADDR)
		return (write_xg0(scgp, bp, addr, size, cnt));
	else
		return (write_xg1(scgp, bp, addr, size, cnt));
}

EXPORT int
write_xg0(scgp, bp, addr, size, cnt)
	SCSI	*scgp;
	caddr_t	bp;		/* address of buffer */
	long	addr;		/* disk address (sector) to put */
	long	size;		/* number of bytes to transfer */
	int	cnt;		/* sectorcount */
{
	register struct	scg_cmd	*scmd = scgp->scmd;

	fillbytes((caddr_t)scmd, sizeof (*scmd), '\0');
	scmd->addr = bp;
	scmd->size = size;
	scmd->flags = SCG_DISRE_ENA|SCG_CMD_RETRY;
/*	scmd->flags = SCG_DISRE_ENA;*/
	scmd->cdb_len = SC_G0_CDBLEN;
	scmd->sense_len = CCS_SENSE_LEN;
	scmd->cdb.g0_cdb.cmd = SC_WRITE;
	scmd->cdb.g0_cdb.lun = scg_lun(scgp);
	g0_cdbaddr(&scmd->cdb.g0_cdb, addr);
	scmd->cdb.g0_cdb.count = cnt;

	scgp->cmdname = "write_g0";

	if (scg_cmd(scgp) < 0)
		return (-1);
	return (size - scg_getresid(scgp));
}

EXPORT int
write_xg1(scgp, bp, addr, size, cnt)
	SCSI	*scgp;
	caddr_t	bp;		/* address of buffer */
	long	addr;		/* disk address (sector) to put */
	long	size;		/* number of bytes to transfer */
	int	cnt;		/* sectorcount */
{
	register struct	scg_cmd	*scmd = scgp->scmd;

	fillbytes((caddr_t)scmd, sizeof (*scmd), '\0');
	scmd->addr = bp;
	scmd->size = size;
	scmd->flags = SCG_DISRE_ENA|SCG_CMD_RETRY;
/*	scmd->flags = SCG_DISRE_ENA;*/
	scmd->cdb_len = SC_G1_CDBLEN;
	scmd->sense_len = CCS_SENSE_LEN;
	scmd->cdb.g1_cdb.cmd = SC_EWRITE;
	scmd->cdb.g1_cdb.lun = scg_lun(scgp);
	g1_cdbaddr(&scmd->cdb.g1_cdb, addr);
	g1_cdblen(&scmd->cdb.g1_cdb, cnt);

	scgp->cmdname = "write_g1";

	if (scg_cmd(scgp) < 0)
		return (-1);
	return (size - scg_getresid(scgp));
}

EXPORT int
write_xg5(scgp, bp, addr, size, cnt)
	SCSI	*scgp;
	caddr_t	bp;		/* address of buffer */
	long	addr;		/* disk address (sector) to put */
	long	size;		/* number of bytes to transfer */
	int	cnt;		/* sectorcount */
{
	register struct	scg_cmd	*scmd = scgp->scmd;

	fillbytes((caddr_t)scmd, sizeof (*scmd), '\0');
	scmd->addr = bp;
	scmd->size = size;
	scmd->flags = SCG_DISRE_ENA|SCG_CMD_RETRY;
/*	scmd->flags = SCG_DISRE_ENA;*/
	scmd->cdb_len = SC_G5_CDBLEN;
	scmd->sense_len = CCS_SENSE_LEN;
	scmd->cdb.g5_cdb.cmd = 0xAA;
	scmd->cdb.g5_cdb.lun = scg_lun(scgp);
	g5_cdbaddr(&scmd->cdb.g5_cdb, addr);
	g5_cdblen(&scmd->cdb.g5_cdb, cnt);

	scgp->cmdname = "write_g5";

	if (scg_cmd(scgp) < 0)
		return (-1);
	return (size - scg_getresid(scgp));
}

EXPORT int
seek_scsi(scgp, addr)
	SCSI	*scgp;
	long	addr;
{
	if (addr <= G0_MAXADDR)
		return (seek_g0(scgp, addr));
	else
		return (seek_g1(scgp, addr));
}

EXPORT int
seek_g0(scgp, addr)
	SCSI	*scgp;
	long	addr;
{
	register struct	scg_cmd	*scmd = scgp->scmd;

	fillbytes((caddr_t)scmd, sizeof (*scmd), '\0');
	scmd->flags = SCG_DISRE_ENA;
	scmd->cdb_len = SC_G0_CDBLEN;
	scmd->sense_len = CCS_SENSE_LEN;
	scmd->cdb.g0_cdb.cmd = 0x0B;	/* Seek */
	scmd->cdb.g0_cdb.lun = scg_lun(scgp);
	g0_cdbaddr(&scmd->cdb.g0_cdb, addr);

	scgp->cmdname = "seek_g0";

	return (scg_cmd(scgp));
}

EXPORT int
seek_g1(scgp, addr)
	SCSI	*scgp;
	long	addr;
{
	register struct	scg_cmd	*scmd = scgp->scmd;

	fillbytes((caddr_t)scmd, sizeof (*scmd), '\0');
	scmd->flags = SCG_DISRE_ENA;
	scmd->cdb_len = SC_G1_CDBLEN;
	scmd->sense_len = CCS_SENSE_LEN;
	scmd->cdb.g1_cdb.cmd = 0x2B;	/* Seek G1 */
	scmd->cdb.g1_cdb.lun = scg_lun(scgp);
	g1_cdbaddr(&scmd->cdb.g1_cdb, addr);

	scgp->cmdname = "seek_g1";

	return (scg_cmd(scgp));
}

EXPORT int
scsi_flush_cache(scgp, immed)
	SCSI	*scgp;
	BOOL	immed;
{
	register struct	scg_cmd	*scmd = scgp->scmd;

	fillbytes((caddr_t)scmd, sizeof (*scmd), '\0');
	scmd->flags = SCG_DISRE_ENA;
	scmd->cdb_len = SC_G1_CDBLEN;
	scmd->sense_len = CCS_SENSE_LEN;
	scmd->timeout = 2 * 60;		/* Max: sizeof (CDR-cache)/150KB/s */
	scmd->cdb.g1_cdb.cmd = 0x35;
	scmd->cdb.g1_cdb.lun = scg_lun(scgp);

	if (immed)
		scmd->cdb.cmd_cdb[1] |= 0x02;

	scgp->cmdname = "flush cache";

	if (scg_cmd(scgp) < 0)
		return (-1);
	return (0);
}

EXPORT int
read_buffer(scgp, bp, cnt, mode)
	SCSI	*scgp;
	caddr_t	bp;
	int	cnt;
	int	mode;
{
	register struct	scg_cmd	*scmd = scgp->scmd;

	fillbytes((caddr_t)scmd, sizeof (*scmd), '\0');
	scmd->addr = bp;
	scmd->size = cnt;
	scmd->dma_read = 1;
	scmd->cdb_len = SC_G1_CDBLEN;
	scmd->sense_len = CCS_SENSE_LEN;
	scmd->cdb.g1_cdb.cmd = 0x3C;	/* Read Buffer */
	scmd->cdb.g1_cdb.lun = scg_lun(scgp);
	scmd->cdb.cmd_cdb[1] |= (mode & 7);
	g1_cdblen(&scmd->cdb.g1_cdb, cnt);

	scgp->cmdname = "read buffer";

	return (scg_cmd(scgp));
}

EXPORT int
read_subchannel(scgp, bp, track, cnt, msf, subq, fmt)
	SCSI	*scgp;
	caddr_t	bp;
	int	track;
	int	cnt;
	int	msf;
	int	subq;
	int	fmt;

{
	register struct	scg_cmd	*scmd = scgp->scmd;

	fillbytes((caddr_t)scmd, sizeof (*scmd), '\0');
	scmd->addr = bp;
	scmd->size = cnt;
	scmd->flags = SCG_RECV_DATA|SCG_DISRE_ENA;
	scmd->cdb_len = SC_G1_CDBLEN;
	scmd->sense_len = CCS_SENSE_LEN;
	scmd->cdb.g1_cdb.cmd = 0x42;
	scmd->cdb.g1_cdb.lun = scg_lun(scgp);
	if (msf)
		scmd->cdb.g1_cdb.res = 1;
	if (subq)
		scmd->cdb.g1_cdb.addr[0] = 0x40;
	scmd->cdb.g1_cdb.addr[1] = fmt;
	scmd->cdb.g1_cdb.res6 = track;
	g1_cdblen(&scmd->cdb.g1_cdb, cnt);

	scgp->cmdname = "read subchannel";

	if (scg_cmd(scgp) < 0)
		return (-1);
	return (0);
}

EXPORT int
read_toc(scgp, bp, track, cnt, msf, fmt)
	SCSI	*scgp;
	caddr_t	bp;
	int	track;
	int	cnt;
	int	msf;
	int	fmt;

{
	register struct	scg_cmd	*scmd = scgp->scmd;

	fillbytes((caddr_t)scmd, sizeof (*scmd), '\0');
	scmd->addr = bp;
	scmd->size = cnt;
	scmd->flags = SCG_RECV_DATA|SCG_DISRE_ENA;
	scmd->cdb_len = SC_G1_CDBLEN;
	scmd->sense_len = CCS_SENSE_LEN;
	scmd->cdb.g1_cdb.cmd = 0x43;
	scmd->cdb.g1_cdb.lun = scg_lun(scgp);
	if (msf)
		scmd->cdb.g1_cdb.res = 1;
	scmd->cdb.g1_cdb.addr[0] = fmt & 0x0F;
	scmd->cdb.g1_cdb.res6 = track;
	g1_cdblen(&scmd->cdb.g1_cdb, cnt);

	scgp->cmdname = "read toc";

	if (scg_cmd(scgp) < 0)
		return (-1);
	return (0);
}

EXPORT int
read_toc_philips(scgp, bp, track, cnt, msf, fmt)
	SCSI	*scgp;
	caddr_t	bp;
	int	track;
	int	cnt;
	int	msf;
	int	fmt;

{
	register struct	scg_cmd	*scmd = scgp->scmd;

	fillbytes((caddr_t)scmd, sizeof (*scmd), '\0');
	scmd->addr = bp;
	scmd->size = cnt;
	scmd->flags = SCG_RECV_DATA|SCG_DISRE_ENA;
	scmd->cdb_len = SC_G1_CDBLEN;
	scmd->sense_len = CCS_SENSE_LEN;
	scmd->timeout = 4 * 60;		/* May last  174s on a TEAC CD-R55S */
	scmd->cdb.g1_cdb.cmd = 0x43;
	scmd->cdb.g1_cdb.lun = scg_lun(scgp);
	if (msf)
		scmd->cdb.g1_cdb.res = 1;
	scmd->cdb.g1_cdb.res6 = track;
	g1_cdblen(&scmd->cdb.g1_cdb, cnt);

	if (fmt & 1)
		scmd->cdb.g1_cdb.vu_96 = 1;
	if (fmt & 2)
		scmd->cdb.g1_cdb.vu_97 = 1;

	scgp->cmdname = "read toc";

	if (scg_cmd(scgp) < 0)
		return (-1);
	return (0);
}

EXPORT int
read_header(scgp, bp, addr, cnt, msf)
	SCSI	*scgp;
	caddr_t	bp;
	long	addr;
	int	cnt;
	int	msf;
{
	register struct	scg_cmd	*scmd = scgp->scmd;

	fillbytes((caddr_t)scmd, sizeof (*scmd), '\0');
	scmd->addr = bp;
	scmd->size = cnt;
	scmd->flags = SCG_RECV_DATA|SCG_DISRE_ENA;
	scmd->cdb_len = SC_G1_CDBLEN;
	scmd->sense_len = CCS_SENSE_LEN;
	scmd->cdb.g1_cdb.cmd = 0x44;
	scmd->cdb.g1_cdb.lun = scg_lun(scgp);
	if (msf)
		scmd->cdb.g1_cdb.res = 1;
	g1_cdbaddr(&scmd->cdb.g1_cdb, addr);
	g1_cdblen(&scmd->cdb.g1_cdb, cnt);

	scgp->cmdname = "read header";

	if (scg_cmd(scgp) < 0)
		return (-1);
	return (0);
}

EXPORT int
read_disk_info(scgp, bp, cnt)
	SCSI	*scgp;
	caddr_t	bp;
	int	cnt;

{
	register struct	scg_cmd	*scmd = scgp->scmd;

	fillbytes((caddr_t)scmd, sizeof (*scmd), '\0');
	scmd->addr = bp;
	scmd->size = cnt;
	scmd->flags = SCG_RECV_DATA|SCG_DISRE_ENA;
	scmd->cdb_len = SC_G1_CDBLEN;
	scmd->sense_len = CCS_SENSE_LEN;
	scmd->timeout = 4 * 60;		/* Needs up to 2 minutes */
	scmd->cdb.g1_cdb.cmd = 0x51;
	scmd->cdb.g1_cdb.lun = scg_lun(scgp);
	g1_cdblen(&scmd->cdb.g1_cdb, cnt);

	scgp->cmdname = "read disk info";

	if (scg_cmd(scgp) < 0)
		return (-1);
	return (0);
}

EXPORT int
read_track_info(scgp, bp, type, addr, cnt)
	SCSI	*scgp;
	caddr_t	bp;
	int	type;
	int	addr;
	int	cnt;

{
	register struct	scg_cmd	*scmd = scgp->scmd;

	fillbytes((caddr_t)scmd, sizeof (*scmd), '\0');
	scmd->addr = bp;
	scmd->size = cnt;
	scmd->flags = SCG_RECV_DATA|SCG_DISRE_ENA;
	scmd->cdb_len = SC_G1_CDBLEN;
	scmd->sense_len = CCS_SENSE_LEN;
	scmd->timeout = 4 * 60;		/* Needs up to 2 minutes */
	scmd->cdb.g1_cdb.cmd = 0x52;
	scmd->cdb.g1_cdb.lun = scg_lun(scgp);
/*	scmd->cdb.cmd_cdb[1] = type & 0x03;*/
	scmd->cdb.cmd_cdb[1] = type;
	g1_cdbaddr(&scmd->cdb.g1_cdb, addr);	/* LBA/Track/Session */
	g1_cdblen(&scmd->cdb.g1_cdb, cnt);

	scgp->cmdname = "read track info";

	if (scg_cmd(scgp) < 0)
		return (-1);
	return (0);
}

EXPORT int
read_rzone_info(scgp, bp, cnt)
	SCSI	*scgp;
	caddr_t	bp;
	int	cnt;
{
	return (read_track_info(scgp, bp, TI_TYPE_LBA, 0, cnt));
}

EXPORT int
reserve_tr_rzone(scgp, size)
	SCSI	*scgp;
	long	size;		/* number of blocks */
{
	register struct	scg_cmd	*scmd = scgp->scmd;

	fillbytes((caddr_t)scmd, sizeof (*scmd), '\0');
	scmd->addr = (caddr_t)0;
	scmd->size = 0;
	scmd->flags = SCG_DISRE_ENA|SCG_CMD_RETRY;
	scmd->cdb_len = SC_G1_CDBLEN;
	scmd->sense_len = CCS_SENSE_LEN;
	scmd->cdb.g1_cdb.cmd = 0x53;
	scmd->cdb.g1_cdb.lun = scg_lun(scgp);

	i_to_4_byte(&scmd->cdb.g1_cdb.addr[3], size);

	scgp->cmdname = "reserve_track_rzone";

	if (scg_cmd(scgp) < 0)
		return (-1);
	return (0);
}

EXPORT int
read_dvd_structure(scgp, bp, cnt, addr, layer, fmt)
	SCSI	*scgp;
	caddr_t	bp;
	int	cnt;
	int	addr;
	int	layer;
	int	fmt;
{
	register struct	scg_cmd	*scmd = scgp->scmd;

	fillbytes((caddr_t)scmd, sizeof (*scmd), '\0');
	scmd->addr = bp;
	scmd->size = cnt;
	scmd->flags = SCG_RECV_DATA|SCG_DISRE_ENA;
	scmd->cdb_len = SC_G5_CDBLEN;
	scmd->sense_len = CCS_SENSE_LEN;
	scmd->timeout = 4 * 60;		/* Needs up to 2 minutes ??? */
	scmd->cdb.g5_cdb.cmd = 0xAD;
	scmd->cdb.g5_cdb.lun = scg_lun(scgp);
	g5_cdbaddr(&scmd->cdb.g5_cdb, addr);
	g5_cdblen(&scmd->cdb.g5_cdb, cnt);
	scmd->cdb.g5_cdb.count[0] = layer;
	scmd->cdb.g5_cdb.count[1] = fmt;

	scgp->cmdname = "read dvd structure";

	if (scg_cmd(scgp) < 0)
		return (-1);
	return (0);
}

EXPORT int
send_opc(scgp, bp, cnt, doopc)
	SCSI	*scgp;
	caddr_t	bp;
	int	cnt;
	int	doopc;
{
	register struct	scg_cmd	*scmd = scgp->scmd;

	fillbytes((caddr_t)scmd, sizeof (*scmd), '\0');
	scmd->addr = bp;
	scmd->size = cnt;
	scmd->flags = SCG_DISRE_ENA;
	scmd->cdb_len = SC_G1_CDBLEN;
	scmd->sense_len = CCS_SENSE_LEN;
	scmd->timeout = 60;
	scmd->cdb.g1_cdb.cmd = 0x54;
	scmd->cdb.g1_cdb.lun = scg_lun(scgp);
	scmd->cdb.g1_cdb.reladr = doopc?1:0;
	g1_cdblen(&scmd->cdb.g1_cdb, cnt);

	scgp->cmdname = "send opc";

	if (scg_cmd(scgp) < 0)
		return (-1);
	return (0);
}

EXPORT int
read_track_info_philips(scgp, bp, track, cnt)
	SCSI	*scgp;
	caddr_t	bp;
	int	track;
	int	cnt;

{
	register struct	scg_cmd	*scmd = scgp->scmd;

	fillbytes((caddr_t)scmd, sizeof (*scmd), '\0');
	scmd->addr = bp;
	scmd->size = cnt;
	scmd->flags = SCG_RECV_DATA|SCG_DISRE_ENA;
	scmd->cdb_len = SC_G1_CDBLEN;
	scmd->sense_len = CCS_SENSE_LEN;
	scmd->cdb.g1_cdb.cmd = 0xE5;
	scmd->cdb.g1_cdb.lun = scg_lun(scgp);
	g1_cdbaddr(&scmd->cdb.g1_cdb, track);
	g1_cdblen(&scmd->cdb.g1_cdb, cnt);

	scgp->cmdname = "read track info";

	if (scg_cmd(scgp) < 0)
		return (-1);
	return (0);
}

EXPORT int
scsi_close_tr_session(scgp, type, track, immed)
	SCSI	*scgp;
	int	type;
	int	track;
	BOOL	immed;
{
	register struct	scg_cmd	*scmd = scgp->scmd;

	fillbytes((caddr_t)scmd, sizeof (*scmd), '\0');
	scmd->flags = SCG_DISRE_ENA;
	scmd->cdb_len = SC_G1_CDBLEN;
	scmd->sense_len = CCS_SENSE_LEN;
	scmd->timeout = 8 * 60;		/* Needs up to 4 minutes */
	scmd->cdb.g1_cdb.cmd = 0x5B;
	scmd->cdb.g1_cdb.lun = scg_lun(scgp);
	scmd->cdb.g1_cdb.addr[0] = type;
	scmd->cdb.g1_cdb.addr[3] = track;

	if (immed)
		scmd->cdb.g1_cdb.reladr = 1;
/*		scmd->cdb.cmd_cdb[1] |= 0x01;*/
#ifdef	nono
	scmd->cdb.g1_cdb.reladr = 1;	/* IMM hack to test Mitsumi behaviour*/
#endif

	scgp->cmdname = "close track/session";

	if (scg_cmd(scgp) < 0)
		return (-1);
	return (0);
}

EXPORT int
read_master_cue(scgp, bp, sheet, cnt)
	SCSI	*scgp;
	caddr_t	bp;		/* address of master cue sheet	*/
	int	sheet;		/* Sheet number			*/
	int	cnt;		/* Transfer count		*/
{
	register struct	scg_cmd	*scmd = scgp->scmd;

	fillbytes((caddr_t)scmd, sizeof (*scmd), '\0');
	scmd->addr = bp;
	scmd->size = cnt;
	scmd->flags = SCG_RECV_DATA|SCG_DISRE_ENA;
	scmd->cdb_len = SC_G1_CDBLEN;
	scmd->sense_len = CCS_SENSE_LEN;
	scmd->cdb.g1_cdb.cmd = 0x59;		/* Read master cue */
	scmd->cdb.g1_cdb.lun = scg_lun(scgp);
	scmd->cdb.g1_cdb.addr[2] = sheet;
	g1_cdblen(&scmd->cdb.g1_cdb, cnt);

	scgp->cmdname = "read master cue";

	if (scg_cmd(scgp) < 0)
		return (-1);
	return (0);
}

EXPORT int
send_cue_sheet(scgp, bp, size)
	SCSI	*scgp;
	caddr_t	bp;		/* address of cue sheet buffer */
	long	size;		/* number of bytes to transfer */
{
	register struct	scg_cmd	*scmd = scgp->scmd;

	fillbytes((caddr_t)scmd, sizeof (*scmd), '\0');
	scmd->addr = bp;
	scmd->size = size;
	scmd->flags = SCG_DISRE_ENA;
	scmd->cdb_len = SC_G1_CDBLEN;
	scmd->sense_len = CCS_SENSE_LEN;
	scmd->cdb.g1_cdb.cmd = 0x5D;	/* Send CUE sheet */
	scmd->cdb.g1_cdb.lun = scg_lun(scgp);
	g1_cdblen(&scmd->cdb.g1_cdb, size);

	scgp->cmdname = "send_cue_sheet";

	if (scg_cmd(scgp) < 0)
		return (-1);
	return (size - scmd->resid);
}

EXPORT int
read_buff_cap(scgp, sp, fp)
	SCSI	*scgp;
	long	*sp;	/* Size pointer */
	long	*fp;	/* Free pointer */
{
	char	resp[12];
	Ulong	freespace;
	Ulong	bufsize;
	int	per;
	register struct	scg_cmd	*scmd = scgp->scmd;

	fillbytes((caddr_t)scmd, sizeof (*scmd), '\0');
	scmd->addr = (caddr_t)resp;
	scmd->size = sizeof (resp);
	scmd->flags = SCG_RECV_DATA|SCG_DISRE_ENA;
	scmd->cdb_len = SC_G1_CDBLEN;
	scmd->sense_len = CCS_SENSE_LEN;
	scmd->cdb.g1_cdb.cmd = 0x5C;		/* Read buffer cap */
	scmd->cdb.g1_cdb.lun = scg_lun(scgp);
	g1_cdblen(&scmd->cdb.g1_cdb, sizeof (resp));

	scgp->cmdname = "read buffer cap";

	if (scg_cmd(scgp) < 0)
		return (-1);

	bufsize   = a_to_u_4_byte(&resp[4]);
	freespace = a_to_u_4_byte(&resp[8]);
	if (sp)
		*sp = bufsize;
	if (fp)
		*fp = freespace;

	if (scgp->verbose || (sp == 0 && fp == 0))
		printf("BFree: %ld K BSize: %ld K\n", freespace >> 10, bufsize >> 10);

	if (bufsize == 0)
		return (0);
	per = (100 * (bufsize - freespace)) / bufsize;
	if (per < 0)
		return (0);
	if (per > 100)
		return (100);
	return (per);
}

EXPORT int
scsi_blank(scgp, addr, blanktype, immed)
	SCSI	*scgp;
	long	addr;
	int	blanktype;
	BOOL	immed;
{
	register struct	scg_cmd	*scmd = scgp->scmd;

	fillbytes((caddr_t)scmd, sizeof (*scmd), '\0');
	scmd->flags = SCG_DISRE_ENA;
	scmd->cdb_len = SC_G5_CDBLEN;
	scmd->sense_len = CCS_SENSE_LEN;
	scmd->timeout = 160 * 60; /* full blank at 1x could take 80 minutes */
	scmd->cdb.g5_cdb.cmd = 0xA1;	/* Blank */
	scmd->cdb.g0_cdb.high_addr = blanktype;
	g1_cdbaddr(&scmd->cdb.g5_cdb, addr);

	if (immed)
		scmd->cdb.g5_cdb.res |= 8;
/*		scmd->cdb.cmd_cdb[1] |= 0x10;*/

	scgp->cmdname = "blank unit";

	return (scg_cmd(scgp));
}

/*
 * XXX First try to handle ATAPI:
 * XXX ATAPI cannot handle SCSI 6 byte commands.
 * XXX We try to simulate 6 byte mode sense/select.
 */
LOCAL BOOL	is_atapi;

EXPORT BOOL
allow_atapi(scgp, new)
	SCSI	*scgp;
	BOOL	new;
{
	BOOL	old = is_atapi;
	Uchar	mode[256];

	if (new == old)
		return (old);

	scgp->silent++;
	/*
	 * If a bad drive has been reset before, we may need to fire up two
	 * test unit ready commands to clear status.
	 */
	(void) unit_ready(scgp);
	if (new &&
	    mode_sense_g1(scgp, mode, 8, 0x3F, 0) < 0) {	/* All pages current */
		new = FALSE;
	}
	scgp->silent--;

	is_atapi = new;
	return (old);
}

EXPORT int
mode_select(scgp, dp, cnt, smp, pf)
	SCSI	*scgp;
	Uchar	*dp;
	int	cnt;
	int	smp;
	int	pf;
{
	if (is_atapi)
		return (mode_select_sg0(scgp, dp, cnt, smp, pf));
	return (mode_select_g0(scgp, dp, cnt, smp, pf));
}

EXPORT int
mode_sense(scgp, dp, cnt, page, pcf)
	SCSI	*scgp;
	Uchar	*dp;
	int	cnt;
	int	page;
	int	pcf;
{
	if (is_atapi)
		return (mode_sense_sg0(scgp, dp, cnt, page, pcf));
	return (mode_sense_g0(scgp, dp, cnt, page, pcf));
}

/*
 * Simulate mode select g0 with mode select g1.
 */
EXPORT int
mode_select_sg0(scgp, dp, cnt, smp, pf)
	SCSI	*scgp;
	Uchar	*dp;
	int	cnt;
	int	smp;
	int	pf;
{
	Uchar	xmode[256+4];
	int	amt = cnt;

	if (amt < 1 || amt > 255) {
		/* XXX clear SCSI error codes ??? */
		return (-1);
	}

	if (amt < 4) {		/* Data length. medium type & VU */
		amt += 1;
	} else {
		amt += 4;
		movebytes(&dp[4], &xmode[8], cnt-4);
	}
	xmode[0] = 0;
	xmode[1] = 0;
	xmode[2] = dp[1];
	xmode[3] = dp[2];
	xmode[4] = 0;
	xmode[5] = 0;
	i_to_2_byte(&xmode[6], (unsigned int)dp[3]);

	if (scgp->verbose) scg_prbytes("Mode Parameters (un-converted)", dp, cnt);

	return (mode_select_g1(scgp, xmode, amt, smp, pf));
}

/*
 * Simulate mode sense g0 with mode sense g1.
 */
EXPORT int
mode_sense_sg0(scgp, dp, cnt, page, pcf)
	SCSI	*scgp;
	Uchar	*dp;
	int	cnt;
	int	page;
	int	pcf;
{
	Uchar	xmode[256+4];
	int	amt = cnt;
	int	len;

	if (amt < 1 || amt > 255) {
		/* XXX clear SCSI error codes ??? */
		return (-1);
	}

	fillbytes((caddr_t)xmode, sizeof (xmode), '\0');
	if (amt < 4) {		/* Data length. medium type & VU */
		amt += 1;
	} else {
		amt += 4;
	}
	if (mode_sense_g1(scgp, xmode, amt, page, pcf) < 0)
		return (-1);

	amt = cnt - scg_getresid(scgp);
/*
 * For tests: Solaris 8 & LG CD-ROM always returns resid == amt
 */
/*	amt = cnt;*/
	if (amt > 4)
		movebytes(&xmode[8], &dp[4], amt-4);
	len = a_to_u_2_byte(xmode);
	if (len == 0) {
		dp[0] = 0;
	} else if (len < 6) {
		if (len > 2)
			len = 2;
		dp[0] = len;
	} else {
		dp[0] = len - 3;
	}
	dp[1] = xmode[2];
	dp[2] = xmode[3];
	len = a_to_u_2_byte(&xmode[6]);
	dp[3] = len;

	if (scgp->verbose) scg_prbytes("Mode Sense Data (converted)", dp, amt);
	return (0);
}

EXPORT int
mode_select_g0(scgp, dp, cnt, smp, pf)
	SCSI	*scgp;
	Uchar	*dp;
	int	cnt;
	int	smp;
	int	pf;
{
	register struct	scg_cmd	*scmd = scgp->scmd;

	fillbytes((caddr_t)scmd, sizeof (*scmd), '\0');
	scmd->addr = (caddr_t)dp;
	scmd->size = cnt;
	scmd->flags = SCG_DISRE_ENA;
	scmd->cdb_len = SC_G0_CDBLEN;
	scmd->sense_len = CCS_SENSE_LEN;
	scmd->cdb.g0_cdb.cmd = SC_MODE_SELECT;
	scmd->cdb.g0_cdb.lun = scg_lun(scgp);
	scmd->cdb.g0_cdb.high_addr = smp ? 1 : 0 | pf ? 0x10 : 0;
	scmd->cdb.g0_cdb.count = cnt;

	if (scgp->verbose) {
		error("%s ", smp?"Save":"Set ");
		scg_prbytes("Mode Parameters", dp, cnt);
	}

	scgp->cmdname = "mode select g0";

	return (scg_cmd(scgp));
}

EXPORT int
mode_select_g1(scgp, dp, cnt, smp, pf)
	SCSI	*scgp;
	Uchar	*dp;
	int	cnt;
	int	smp;
	int	pf;
{
	register struct	scg_cmd	*scmd = scgp->scmd;

	fillbytes((caddr_t)scmd, sizeof (*scmd), '\0');
	scmd->addr = (caddr_t)dp;
	scmd->size = cnt;
	scmd->flags = SCG_DISRE_ENA;
	scmd->cdb_len = SC_G1_CDBLEN;
	scmd->sense_len = CCS_SENSE_LEN;
	scmd->cdb.g1_cdb.cmd = 0x55;
	scmd->cdb.g1_cdb.lun = scg_lun(scgp);
	scmd->cdb.g0_cdb.high_addr = smp ? 1 : 0 | pf ? 0x10 : 0;
	g1_cdblen(&scmd->cdb.g1_cdb, cnt);

	if (scgp->verbose) {
		printf("%s ", smp?"Save":"Set ");
		scg_prbytes("Mode Parameters", dp, cnt);
	}

	scgp->cmdname = "mode select g1";

	return (scg_cmd(scgp));
}

EXPORT int
mode_sense_g0(scgp, dp, cnt, page, pcf)
	SCSI	*scgp;
	Uchar	*dp;
	int	cnt;
	int	page;
	int	pcf;
{
	register struct	scg_cmd	*scmd = scgp->scmd;

	fillbytes((caddr_t)scmd, sizeof (*scmd), '\0');
	scmd->addr = (caddr_t)dp;
	scmd->size = 0xFF;
	scmd->size = cnt;
	scmd->flags = SCG_RECV_DATA|SCG_DISRE_ENA;
	scmd->cdb_len = SC_G0_CDBLEN;
	scmd->sense_len = CCS_SENSE_LEN;
	scmd->cdb.g0_cdb.cmd = SC_MODE_SENSE;
	scmd->cdb.g0_cdb.lun = scg_lun(scgp);
#ifdef	nonono
	scmd->cdb.g0_cdb.high_addr = 1<<4;	/* DBD Disable Block desc. */
#endif
	scmd->cdb.g0_cdb.mid_addr = (page&0x3F) | ((pcf<<6)&0xC0);
	scmd->cdb.g0_cdb.count = page ? 0xFF : 24;
	scmd->cdb.g0_cdb.count = cnt;

	scgp->cmdname = "mode sense g0";

	if (scg_cmd(scgp) < 0)
		return (-1);
	if (scgp->verbose) scg_prbytes("Mode Sense Data", dp, cnt - scg_getresid(scgp));
	return (0);
}

EXPORT int
mode_sense_g1(scgp, dp, cnt, page, pcf)
	SCSI	*scgp;
	Uchar	*dp;
	int	cnt;
	int	page;
	int	pcf;
{
	register struct	scg_cmd	*scmd = scgp->scmd;

	fillbytes((caddr_t)scmd, sizeof (*scmd), '\0');
	scmd->addr = (caddr_t)dp;
	scmd->size = cnt;
	scmd->flags = SCG_RECV_DATA|SCG_DISRE_ENA;
	scmd->cdb_len = SC_G1_CDBLEN;
	scmd->sense_len = CCS_SENSE_LEN;
	scmd->cdb.g1_cdb.cmd = 0x5A;
	scmd->cdb.g1_cdb.lun = scg_lun(scgp);
#ifdef	nonono
	scmd->cdb.g0_cdb.high_addr = 1<<4;	/* DBD Disable Block desc. */
#endif
	scmd->cdb.g1_cdb.addr[0] = (page&0x3F) | ((pcf<<6)&0xC0);
	g1_cdblen(&scmd->cdb.g1_cdb, cnt);

	scgp->cmdname = "mode sense g1";

	if (scg_cmd(scgp) < 0)
		return (-1);
	if (scgp->verbose) scg_prbytes("Mode Sense Data", dp, cnt - scg_getresid(scgp));
	return (0);
}

struct trackdesc {
	Uchar	res0;

#if defined(_BIT_FIELDS_LTOH)		/* Intel byteorder */
	Ucbit	control		: 4;
	Ucbit	adr		: 4;
#else					/* Motorola byteorder */
	Ucbit	adr		: 4;
	Ucbit	control		: 4;
#endif

	Uchar	track;
	Uchar	res3;
	Uchar	addr[4];
};

struct diskinfo {
	struct tocheader	hd;
	struct trackdesc	desc[1];
};

struct siheader {
	Uchar	len[2];
	Uchar	finished;
	Uchar	unfinished;
};

struct sidesc {
	Uchar	sess_number;
	Uchar	res1;
	Uchar	track;
	Uchar	res3;
	Uchar	addr[4];
};

struct sinfo {
	struct siheader	hd;
	struct sidesc	desc[1];
};

struct trackheader {
	Uchar	mode;
	Uchar	res[3];
	Uchar	addr[4];
};
#define	TRM_ZERO	0
#define	TRM_USER_ECC	1	/* 2048 bytes user data + 288 Bytes ECC/EDC */
#define	TRM_USER	2	/* All user data (2336 bytes) */


EXPORT	int
read_tochdr(scgp, dp, fp, lp)
	SCSI	*scgp;
	cdr_t	*dp;
	int	*fp;
	int	*lp;
{
	struct	tocheader *tp;
	char	xb[256];
	int	len;

	tp = (struct tocheader *)xb;

	fillbytes((caddr_t)xb, sizeof (xb), '\0');
	if (read_toc(scgp, xb, 0, sizeof (struct tocheader), 0, FMT_TOC) < 0) {
		if (scgp->silent == 0)
			errmsgno(EX_BAD, "Cannot read TOC header\n");
		return (-1);
	}
	len = a_to_u_2_byte(tp->len) + sizeof (struct tocheader)-2;
	if (len >= 4) {
		if (fp)
			*fp = tp->first;
		if (lp)
			*lp = tp->last;
		return (0);
	}
	return (-1);
}

EXPORT	int
read_cdtext(scgp)
	SCSI	*scgp;
{
	struct	tocheader *tp;
	char	xb[256];
	int	len;
	char	xxb[10000];

	tp = (struct tocheader *)xb;

	fillbytes((caddr_t)xb, sizeof (xb), '\0');
	if (read_toc(scgp, xb, 0, sizeof (struct tocheader), 0, FMT_CDTEXT) < 0) {
		if (scgp->silent == 0 || scgp->verbose > 0)
			errmsgno(EX_BAD, "Cannot read CD-Text header\n");
		return (-1);
	}
	len = a_to_u_2_byte(tp->len) + sizeof (struct tocheader)-2;
	printf("CD-Text len: %d\n", len);

	if (read_toc(scgp, xxb, 0, len, 0, FMT_CDTEXT) < 0) {
		if (scgp->silent == 0)
			errmsgno(EX_BAD, "Cannot read CD-Text\n");
		return (-1);
	}
	{
		FILE	*f = fileopen("cdtext.dat", "wctb");
		filewrite(f, xxb, len);
	}
	return (0);
}

EXPORT	int
read_trackinfo(scgp, track, offp, msfp, adrp, controlp, modep)
	SCSI	*scgp;
	int	track;
	long	*offp;
	struct msf *msfp;
	int	*adrp;
	int	*controlp;
	int	*modep;
{
	struct	diskinfo *dp;
	char	xb[256];
	int	len;

	dp = (struct diskinfo *)xb;

	fillbytes((caddr_t)xb, sizeof (xb), '\0');
	if (read_toc(scgp, xb, track, sizeof (struct diskinfo), 0, FMT_TOC) < 0) {
		if (scgp->silent <= 0)
			errmsgno(EX_BAD, "Cannot read TOC\n");
		return (-1);
	}
	len = a_to_u_2_byte(dp->hd.len) + sizeof (struct tocheader)-2;
	if (len <  (int)sizeof (struct diskinfo))
		return (-1);

	if (offp)
		*offp = a_to_4_byte(dp->desc[0].addr);
	if (adrp)
		*adrp = dp->desc[0].adr;
	if (controlp)
		*controlp = dp->desc[0].control;

	if (msfp) {
		scgp->silent++;
		if (read_toc(scgp, xb, track, sizeof (struct diskinfo), 1, FMT_TOC)
									>= 0) {
			msfp->msf_min = dp->desc[0].addr[1];
			msfp->msf_sec = dp->desc[0].addr[2];
			msfp->msf_frame = dp->desc[0].addr[3];
		} else if (read_toc(scgp, xb, track, sizeof (struct diskinfo), 0, FMT_TOC)
									>= 0) {
			/*
			 * Some drives (e.g. the Philips CDD-522) don't support
			 * to read the TOC in MSF mode.
			 */
			long off = a_to_4_byte(dp->desc[0].addr);

			lba_to_msf(off, msfp);
		} else {
			msfp->msf_min = 0;
			msfp->msf_sec = 0;
			msfp->msf_frame = 0;
		}
		scgp->silent--;
	}

	if (modep == NULL)
		return (0);

	if (track == 0xAA) {
		*modep = -1;
		return (0);
	}

	fillbytes((caddr_t)xb, sizeof (xb), '\0');

	scgp->silent++;
	if (read_header(scgp, xb, *offp, 8, 0) >= 0) {
		*modep = xb[0];
	} else if (read_track_info_philips(scgp, xb, track, 14) >= 0) {
		*modep = xb[0xb] & 0xF;
	} else {
		*modep = -1;
	}
	scgp->silent--;
	return (0);
}

EXPORT	int
read_B0(scgp, isbcd, b0p, lop)
	SCSI	*scgp;
	BOOL	isbcd;
	long	*b0p;
	long	*lop;
{
	struct	fdiskinfo *dp;
	struct	ftrackdesc *tp;
	char	xb[8192];
	char	*pe;
	int	len;
	long	l;

	dp = (struct fdiskinfo *)xb;

	fillbytes((caddr_t)xb, sizeof (xb), '\0');
	if (read_toc_philips(scgp, xb, 1, sizeof (struct tocheader), 0, FMT_FULLTOC) < 0) {
		return (-1);
	}
	len = a_to_u_2_byte(dp->hd.len) + sizeof (struct tocheader)-2;
	if (len <  (int)sizeof (struct fdiskinfo))
		return (-1);
	if (read_toc_philips(scgp, xb, 1, len, 0, FMT_FULLTOC) < 0) {
		return (-1);
	}
	if (scgp->verbose) {
		scg_prbytes("TOC data: ", (Uchar *)xb,
			len > (int)sizeof (xb) - scg_getresid(scgp) ?
				sizeof (xb) - scg_getresid(scgp) : len);

		tp = &dp->desc[0];
		pe = &xb[len];

		while ((char *)tp < pe) {
			scg_prbytes("ENT: ", (Uchar *)tp, 11);
			tp++;
		}
	}
	tp = &dp->desc[0];
	pe = &xb[len];

	for (; (char *)tp < pe; tp++) {
		if (tp->sess_number != dp->hd.last)
			continue;
		if (tp->point != 0xB0)
			continue;
		if (scgp->verbose)
			scg_prbytes("B0: ", (Uchar *)tp, 11);
		if (isbcd) {
			l = msf_to_lba(from_bcd(tp->amin),
				from_bcd(tp->asec),
				from_bcd(tp->aframe), TRUE);
		} else {
			l = msf_to_lba(tp->amin,
				tp->asec,
				tp->aframe, TRUE);
		}
		if (b0p)
			*b0p = l;

		if (scgp->verbose)
			printf("B0 start: %ld\n", l);

		if (isbcd) {
			l = msf_to_lba(from_bcd(tp->pmin),
				from_bcd(tp->psec),
				from_bcd(tp->pframe), TRUE);
		} else {
			l = msf_to_lba(tp->pmin,
				tp->psec,
				tp->pframe, TRUE);
		}

		if (scgp->verbose)
			printf("B0 lout: %ld\n", l);
		if (lop)
			*lop = l;
		return (0);
	}
	return (-1);
}


/*
 * Return address of first track in last session (SCSI-3/mmc version).
 */
EXPORT int
read_session_offset(scgp, offp)
	SCSI	*scgp;
	long	*offp;
{
	struct	diskinfo *dp;
	char	xb[256];
	int	len;

	dp = (struct diskinfo *)xb;

	fillbytes((caddr_t)xb, sizeof (xb), '\0');
	if (read_toc(scgp, (caddr_t)xb, 0, sizeof (struct tocheader), 0, FMT_SINFO) < 0)
		return (-1);

	if (scgp->verbose)
		scg_prbytes("tocheader: ",
		(Uchar *)xb, sizeof (struct tocheader) - scg_getresid(scgp));

	len = a_to_u_2_byte(dp->hd.len) + sizeof (struct tocheader)-2;
	if (len > (int)sizeof (xb)) {
		errmsgno(EX_BAD, "Session info too big.\n");
		return (-1);
	}
	if (read_toc(scgp, (caddr_t)xb, 0, len, 0, FMT_SINFO) < 0)
		return (-1);

	if (scgp->verbose)
		scg_prbytes("tocheader: ",
			(Uchar *)xb, len - scg_getresid(scgp));

	dp = (struct diskinfo *)xb;
	if (offp)
		*offp = a_to_u_4_byte(dp->desc[0].addr);
	return (0);
}

/*
 * Return address of first track in last session (pre SCSI-3 version).
 */
EXPORT int
read_session_offset_philips(scgp, offp)
	SCSI	*scgp;
	long	*offp;
{
	struct	sinfo *sp;
	char	xb[256];
	int	len;

	sp = (struct sinfo *)xb;

	fillbytes((caddr_t)xb, sizeof (xb), '\0');
	if (read_toc_philips(scgp, (caddr_t)xb, 0, sizeof (struct siheader), 0, FMT_SINFO) < 0)
		return (-1);
	len = a_to_u_2_byte(sp->hd.len) + sizeof (struct siheader)-2;
	if (len > (int)sizeof (xb)) {
		errmsgno(EX_BAD, "Session info too big.\n");
		return (-1);
	}
	if (read_toc_philips(scgp, (caddr_t)xb, 0, len, 0, FMT_SINFO) < 0)
		return (-1);
	/*
	 * Old drives return the number of finished sessions in first/finished
	 * a descriptor is returned for each session.
	 * New drives return the number of the first and last session
	 * one descriptor for the last finished session is returned
	 * as in SCSI-3
	 * In all cases the lowest session number is set to 1.
	 */
	sp = (struct sinfo *)xb;
	if (offp)
		*offp = a_to_u_4_byte(sp->desc[sp->hd.finished-1].addr);
	return (0);
}

EXPORT int
sense_secsize(scgp, current)
	SCSI	*scgp;
	int	current;
{
	Uchar	mode[0x100];
	Uchar	*p;
	Uchar	*ep;
	int	len;
	int	secsize = -1;

	scgp->silent++;
	(void) unit_ready(scgp);
	scgp->silent--;

	/* XXX Quick and dirty, musz verallgemeinert werden !!! */

	fillbytes(mode, sizeof (mode), '\0');
	scgp->silent++;

	len =	sizeof (struct scsi_mode_header) +
		sizeof (struct scsi_mode_blockdesc);
	/*
	 * Wenn wir hier get_mode_params() nehmen bekommen wir die Warnung:
	 * Warning: controller returns wrong page 1 for All pages page (3F).
	 */
	if (mode_sense(scgp, mode, len, 0x3F, current?0:2) < 0) {
		fillbytes(mode, sizeof (mode), '\0');
		if (mode_sense(scgp, mode, len, 0, current?0:2) < 0)	{ /* VU (block desc) */
			scgp->silent--;
			return (-1);
		}
	}
	if (mode[3] == 8) {
		if (scgp->debug) {
			printf("Density: 0x%X\n", mode[4]);
			printf("Blocks:  %ld\n", a_to_u_3_byte(&mode[5]));
			printf("Blocklen:%ld\n", a_to_u_3_byte(&mode[9]));
		}
		secsize = a_to_u_3_byte(&mode[9]);
	}
	fillbytes(mode, sizeof (mode), '\0');
	/*
	 * The ACARD TECH AEC-7720 ATAPI<->SCSI adaptor
	 * chokes if we try to transfer more than 0x40 bytes with
	 * mode_sense of all pages. So try to avoid to run this
	 * command if possible.
	 */
	if (scgp->debug &&
	    mode_sense(scgp, mode, 0xFE, 0x3F, current?0:2) >= 0) {	/* All Pages */

		ep = mode+mode[0];	/* Points to last byte of data */
		p = &mode[4];
		p += mode[3];
		printf("Pages: ");
		while (p < ep) {
			printf("0x%X ", *p&0x3F);
			p += p[1]+2;
		}
		printf("\n");
	}
	scgp->silent--;

	return (secsize);
}

EXPORT int
select_secsize(scgp, secsize)
	SCSI	*scgp;
	int	secsize;
{
	struct scsi_mode_data md;
	int	count = sizeof (struct scsi_mode_header) +
			sizeof (struct scsi_mode_blockdesc);

	(void) test_unit_ready(scgp);	/* clear any error situation */

	fillbytes((caddr_t)&md, sizeof (md), '\0');
	md.header.blockdesc_len = 8;
	i_to_3_byte(md.blockdesc.lblen, secsize);

	return (mode_select(scgp, (Uchar *)&md, count, 0, scgp->inq->data_format >= 2));
}

EXPORT BOOL
is_cddrive(scgp)
	SCSI	*scgp;
{
	return (scgp->inq->type == INQ_ROMD || scgp->inq->type == INQ_WORM);
}

EXPORT BOOL
is_unknown_dev(scgp)
	SCSI	*scgp;
{
	return (scgp->dev == DEV_UNKNOWN);
}

#ifndef	DEBUG
#define	DEBUG
#endif
#ifdef	DEBUG

EXPORT int
read_scsi(scgp, bp, addr, cnt)
	SCSI	*scgp;
	caddr_t	bp;
	long	addr;
	int	cnt;
{
	if (addr <= G0_MAXADDR && cnt < 256 && !is_atapi)
		return (read_g0(scgp, bp, addr, cnt));
	else
		return (read_g1(scgp, bp, addr, cnt));
}

EXPORT int
read_g0(scgp, bp, addr, cnt)
	SCSI	*scgp;
	caddr_t	bp;
	long	addr;
	int	cnt;
{
	register struct	scg_cmd	*scmd = scgp->scmd;

	if (scgp->cap->c_bsize <= 0)
		raisecond("capacity_not_set", 0L);

	fillbytes((caddr_t)scmd, sizeof (*scmd), '\0');
	scmd->addr = bp;
	scmd->size = cnt*scgp->cap->c_bsize;
	scmd->flags = SCG_RECV_DATA|SCG_DISRE_ENA;
	scmd->cdb_len = SC_G0_CDBLEN;
	scmd->sense_len = CCS_SENSE_LEN;
	scmd->cdb.g0_cdb.cmd = SC_READ;
	scmd->cdb.g0_cdb.lun = scg_lun(scgp);
	g0_cdbaddr(&scmd->cdb.g0_cdb, addr);
	scmd->cdb.g0_cdb.count = cnt;
/*	scmd->cdb.g0_cdb.vu_56 = 1;*/

	scgp->cmdname = "read_g0";

	return (scg_cmd(scgp));
}

EXPORT int
read_g1(scgp, bp, addr, cnt)
	SCSI	*scgp;
	caddr_t	bp;
	long	addr;
	int	cnt;
{
	register struct	scg_cmd	*scmd = scgp->scmd;

	if (scgp->cap->c_bsize <= 0)
		raisecond("capacity_not_set", 0L);

	fillbytes((caddr_t)scmd, sizeof (*scmd), '\0');
	scmd->addr = bp;
	scmd->size = cnt*scgp->cap->c_bsize;
	scmd->flags = SCG_RECV_DATA|SCG_DISRE_ENA;
	scmd->cdb_len = SC_G1_CDBLEN;
	scmd->sense_len = CCS_SENSE_LEN;
	scmd->cdb.g1_cdb.cmd = SC_EREAD;
	scmd->cdb.g1_cdb.lun = scg_lun(scgp);
	g1_cdbaddr(&scmd->cdb.g1_cdb, addr);
	g1_cdblen(&scmd->cdb.g1_cdb, cnt);

	scgp->cmdname = "read_g1";

	return (scg_cmd(scgp));
}
#endif	/* DEBUG */

EXPORT BOOL
getdev(scgp, print)
	SCSI	*scgp;
	BOOL	print;
{
		BOOL	got_inquiry = TRUE;
		char	vendor_info[8+1];
		char	prod_ident[16+1];
		char	prod_revision[4+1];
		int	inq_len = 0;
	register struct	scg_cmd	*scmd = scgp->scmd;
	register struct scsi_inquiry *inq = scgp->inq;


	fillbytes((caddr_t)inq, sizeof (*inq), '\0');
	scgp->dev = DEV_UNKNOWN;
	scgp->silent++;
	(void) unit_ready(scgp);
	if (scmd->error >= SCG_FATAL &&
				!(scmd->scb.chk && scmd->sense_count > 0)) {
		scgp->silent--;
		return (FALSE);
	}


/*	if (scmd->error < SCG_FATAL || scmd->scb.chk && scmd->sense_count > 0){*/

	if (inquiry(scgp, (caddr_t)inq, sizeof (*inq)) < 0) {
		got_inquiry = FALSE;
	} else {
		inq_len = sizeof (*inq) - scg_getresid(scgp);
	}
	if (!got_inquiry) {
		if (scgp->verbose) {
			printf(
		"error: %d scb.chk: %d sense_count: %d sense.code: 0x%x\n",
				scmd->error, scmd->scb.chk,
				scmd->sense_count, scmd->sense.code);
		}
			/*
			 * Folgende Kontroller kennen das Kommando
			 * INQUIRY nicht:
			 *
			 * ADAPTEC	ACB-4000, ACB-4010, ACB 4070
			 * SYSGEN	SC4000
			 *
			 * Leider reagieren ACB40X0 und ACB5500 identisch
			 * wenn drive not ready (code == not ready),
			 * sie sind dann nicht zu unterscheiden.
			 */

		if (scmd->scb.chk && scmd->sense_count == 4) {
			/* Test auf SYSGEN				 */
			(void) qic02(scgp, 0x12);	/* soft lock on  */
			if (qic02(scgp, 1) < 0) {	/* soft lock off */
				scgp->dev = DEV_ACB40X0;
/*				scgp->dev = acbdev();*/
			} else {
				scgp->dev = DEV_SC4000;
				inq->type = INQ_SEQD;
				inq->removable = 1;
			}
		}
	} else if (scgp->verbose) {
		int	i;
		int	len = inq->add_len + 5;
		Uchar	ibuf[256+5];
		Uchar	*ip = (Uchar *)inq;
		Uchar	c;

		if (len > (int)sizeof (*inq) &&
				inquiry(scgp, (caddr_t)ibuf, inq->add_len+5) >= 0) {
			len = inq->add_len+5 - scg_getresid(scgp);
			ip = ibuf;
		} else {
			len = sizeof (*inq);
		}
		printf("Inquiry Data   : ");
		for (i = 0; i < len; i++) {
			c = ip[i];
			if (c >= ' ' && c < 0177)
				printf("%c", c);
			else
				printf(".");
		}
		printf("\n");
	}

	strncpy(vendor_info, inq->vendor_info, sizeof (inq->vendor_info));
	strncpy(prod_ident, inq->prod_ident, sizeof (inq->prod_ident));
	strncpy(prod_revision, inq->prod_revision, sizeof (inq->prod_revision));

	vendor_info[sizeof (inq->vendor_info)] = '\0';
	prod_ident[sizeof (inq->prod_ident)] = '\0';
	prod_revision[sizeof (inq->prod_revision)] = '\0';

	switch (inq->type) {

	case INQ_DASD:
		if (inq->add_len == 0 && inq->vendor_info[0] != '\0') {
			Uchar	*p;
			/*
			 * NT-4.0 creates fake inquiry data for IDE disks.
			 * Unfortunately, it does not set add_len wo we
			 * check if vendor_info, prod_ident and prod_revision
			 * contains valid chars for a CCS inquiry.
			 */
			if (inq_len >= 36)
				inq->add_len = 31;

			for (p = (Uchar *)&inq->vendor_info[0];
					p < (Uchar *)&inq->prod_revision[4];
									p++) {
				if (*p < 0x20 || *p > 0x7E) {
					inq->add_len = 0;
					break;
				}
			}
		}
		if (inq->add_len == 0) {
			if (scgp->dev == DEV_UNKNOWN && got_inquiry) {
				scgp->dev = DEV_ACB5500;
				strcpy(inq->vendor_info,
					"ADAPTEC ACB-5500        FAKE");

			} else switch (scgp->dev) {

			case DEV_ACB40X0:
				strcpy(inq->vendor_info,
					"ADAPTEC ACB-40X0        FAKE");
				break;
			case DEV_ACB4000:
				strcpy(inq->vendor_info,
					"ADAPTEC ACB-4000        FAKE");
				break;
			case DEV_ACB4010:
				strcpy(inq->vendor_info,
					"ADAPTEC ACB-4010        FAKE");
				break;
			case DEV_ACB4070:
				strcpy(inq->vendor_info,
					"ADAPTEC ACB-4070        FAKE");
				break;
			}
		} else if (inq->add_len < 31) {
			scgp->dev = DEV_NON_CCS_DSK;

		} else if (strbeg("EMULEX", vendor_info)) {
			if (strbeg("MD21", prod_ident))
				scgp->dev = DEV_MD21;
			if (strbeg("MD23", prod_ident))
				scgp->dev = DEV_MD23;
			else
				scgp->dev = DEV_CCS_GENDISK;
		} else if (strbeg("ADAPTEC", vendor_info)) {
			if (strbeg("ACB-4520", prod_ident))
				scgp->dev = DEV_ACB4520A;
			if (strbeg("ACB-4525", prod_ident))
				scgp->dev = DEV_ACB4525;
			else
				scgp->dev = DEV_CCS_GENDISK;
		} else if (strbeg("SONY", vendor_info) &&
					strbeg("SMO-C501", prod_ident)) {
			scgp->dev = DEV_SONY_SMO;
		} else {
			scgp->dev = DEV_CCS_GENDISK;
		}
		break;

	case INQ_SEQD:
		if (scgp->dev == DEV_SC4000) {
			strcpy(inq->vendor_info,
				"SYSGEN  SC4000          FAKE");
		} else if (inq->add_len == 0 &&
					inq->removable &&
						inq->ansi_version == 1) {
			scgp->dev = DEV_MT02;
			strcpy(inq->vendor_info,
				"EMULEX  MT02            FAKE");
		}
		break;

/*	case INQ_OPTD:*/
	case INQ_ROMD:
	case INQ_WORM:
		if (strbeg("RXT-800S", prod_ident))
			scgp->dev = DEV_RXT800S;

		/*
		 * Start of CD-Recorders:
		 */
		if (strbeg("ACER", vendor_info)) {
			if (strbeg("CR-4020C", prod_ident))
				scgp->dev = DEV_RICOH_RO_1420C;

		} else if (strbeg("CREATIVE", vendor_info)) {
			if (strbeg("CDR2000", prod_ident))
				scgp->dev = DEV_RICOH_RO_1060C;

		} else if (strbeg("GRUNDIG", vendor_info)) {
			if (strbeg("CDR100IPW", prod_ident))
				scgp->dev = DEV_CDD_2000;

		} else if (strbeg("JVC", vendor_info)) {
			if (strbeg("XR-W2001", prod_ident))
				scgp->dev = DEV_TEAC_CD_R50S;
			else if (strbeg("XR-W2010", prod_ident))
				scgp->dev = DEV_TEAC_CD_R50S;
			else if (strbeg("R2626", prod_ident))
				scgp->dev = DEV_TEAC_CD_R50S;

		} else if (strbeg("MITSBISH", vendor_info)) {

#ifdef	XXXX_REALLY
			/* It's MMC compliant */
			if (strbeg("CDRW226", prod_ident))
				scgp->dev = DEV_MMC_CDRW;
#else
			/* EMPTY */
#endif

		} else if (strbeg("MITSUMI", vendor_info)) {
			/* Don't know any product string */
			scgp->dev = DEV_CDD_522;

		} else if (strbeg("OPTIMA", vendor_info)) {
			if (strbeg("CD-R 650", prod_ident))
				scgp->dev = DEV_SONY_CDU_924;

		} else if (strbeg("PHILIPS", vendor_info) ||
				strbeg("IMS", vendor_info) ||
				strbeg("KODAK", vendor_info) ||
				strbeg("HP", vendor_info)) {

			if (strbeg("CDD521/00", prod_ident))
				scgp->dev = DEV_CDD_521_OLD;
			else if (strbeg("CDD521/02", prod_ident))
				scgp->dev = DEV_CDD_521_OLD;		/* PCD 200R? */
			else if (strbeg("CDD521", prod_ident))
				scgp->dev = DEV_CDD_521;

			if (strbeg("CDD522", prod_ident))
				scgp->dev = DEV_CDD_522;
			if (strbeg("PCD225", prod_ident))
				scgp->dev = DEV_CDD_522;
			if (strbeg("KHSW/OB", prod_ident))	/* PCD600 */
				scgp->dev = DEV_PCD_600;
			if (strbeg("CDR-240", prod_ident))
				scgp->dev = DEV_CDD_2000;

			if (strbeg("CDD20", prod_ident))
				scgp->dev = DEV_CDD_2000;
			if (strbeg("CDD26", prod_ident))
				scgp->dev = DEV_CDD_2600;

			if (strbeg("C4324/C4325", prod_ident))
				scgp->dev = DEV_CDD_2000;
			if (strbeg("CD-Writer 6020", prod_ident))
				scgp->dev = DEV_CDD_2600;

		} else if (strbeg("PINNACLE", vendor_info)) {
			if (strbeg("RCD-1000", prod_ident))
				scgp->dev = DEV_TEAC_CD_R50S;
			if (strbeg("RCD5020", prod_ident))
				scgp->dev = DEV_TEAC_CD_R50S;
			if (strbeg("RCD5040", prod_ident))
				scgp->dev = DEV_TEAC_CD_R50S;
			if (strbeg("RCD 4X4", prod_ident))
				scgp->dev = DEV_TEAC_CD_R50S;

		} else if (strbeg("PIONEER", vendor_info)) {
			if (strbeg("CD-WO DW-S114X", prod_ident))
				scgp->dev = DEV_PIONEER_DW_S114X;
			else if (strbeg("CD-WO DR-R504X", prod_ident))	/* Reoprt from philip@merge.com */
				scgp->dev = DEV_PIONEER_DW_S114X;
			else if (strbeg("DVD-R DVR-S101", prod_ident))
				scgp->dev = DEV_PIONEER_DVDR_S101;

		} else if (strbeg("PLASMON", vendor_info)) {
			if (strbeg("RF4100", prod_ident))
				scgp->dev = DEV_PLASMON_RF_4100;
			else if (strbeg("CDR4220", prod_ident))
				scgp->dev = DEV_CDD_2000;

		} else if (strbeg("PLEXTOR", vendor_info)) {
			if (strbeg("CD-R   PX-R24CS", prod_ident))
				scgp->dev = DEV_RICOH_RO_1420C;

		} else if (strbeg("RICOH", vendor_info)) {
			if (strbeg("RO-1420C", prod_ident))
				scgp->dev = DEV_RICOH_RO_1420C;
			if (strbeg("RO1060C", prod_ident))
				scgp->dev = DEV_RICOH_RO_1060C;

		} else if (strbeg("SAF", vendor_info)) {	/* Smart & Friendly */
			if (strbeg("CD-R2004", prod_ident) ||
			    strbeg("CD-R2006 ", prod_ident))
				scgp->dev = DEV_SONY_CDU_924;
			else if (strbeg("CD-R2006PLUS", prod_ident))
				scgp->dev = DEV_TEAC_CD_R50S;
			else if (strbeg("CD-RW226", prod_ident))
				scgp->dev = DEV_TEAC_CD_R50S;
			else if (strbeg("CD-R4012", prod_ident))
				scgp->dev = DEV_TEAC_CD_R50S;

		} else if (strbeg("SANYO", vendor_info)) {
			if (strbeg("CD-WO CRD-R24S", prod_ident))
				scgp->dev = DEV_CDD_521;

		} else if (strbeg("SONY", vendor_info)) {
			if (strbeg("CD-R   CDU92", prod_ident) ||
			    strbeg("CD-R   CDU94", prod_ident))
				scgp->dev = DEV_SONY_CDU_924;

		} else if (strbeg("TEAC", vendor_info)) {
			if (strbeg("CD-R50S", prod_ident) ||
			    strbeg("CD-R55S", prod_ident))
				scgp->dev = DEV_TEAC_CD_R50S;

		} else if (strbeg("TRAXDATA", vendor_info) ||
				strbeg("Traxdata", vendor_info)) {
			if (strbeg("CDR4120", prod_ident))
				scgp->dev = DEV_TEAC_CD_R50S;

		} else if (strbeg("T.YUDEN", vendor_info)) {
			if (strbeg("CD-WO EW-50", prod_ident))
				scgp->dev = DEV_TYUDEN_EW50;

		} else if (strbeg("WPI", vendor_info)) {	/* Wearnes */
			if (strbeg("CDR-632P", prod_ident))
				scgp->dev = DEV_CDD_2600;

		} else if (strbeg("YAMAHA", vendor_info)) {
			if (strbeg("CDR10", prod_ident))
				scgp->dev = DEV_YAMAHA_CDR_100;
			if (strbeg("CDR200", prod_ident))
				scgp->dev = DEV_YAMAHA_CDR_400;
			if (strbeg("CDR400", prod_ident))
				scgp->dev = DEV_YAMAHA_CDR_400;

		} else if (strbeg("MATSHITA", vendor_info)) {
			if (strbeg("CD-R   CW-7501", prod_ident))
				scgp->dev = DEV_MATSUSHITA_7501;
			if (strbeg("CD-R   CW-7502", prod_ident))
				scgp->dev = DEV_MATSUSHITA_7502;
		}
		if (scgp->dev == DEV_UNKNOWN) {
			/*
			 * We do not have Manufacturer strings for
			 * the following drives.
			 */
			if (strbeg("CDS615E", prod_ident))	/* Olympus */
				scgp->dev = DEV_SONY_CDU_924;
		}
		if (scgp->dev == DEV_UNKNOWN && inq->type == INQ_ROMD) {
			BOOL	cdrr	 = FALSE;
			BOOL	cdwr	 = FALSE;
			BOOL	cdrrw	 = FALSE;
			BOOL	cdwrw	 = FALSE;
			BOOL	dvd	 = FALSE;
			BOOL	dvdwr	 = FALSE;

			scgp->dev = DEV_CDROM;

			if (mmc_check(scgp, &cdrr, &cdwr, &cdrrw, &cdwrw,
								&dvd, &dvdwr))
				scgp->dev = DEV_MMC_CDROM;
			if (cdwr)
				scgp->dev = DEV_MMC_CDR;
			if (cdwrw)
				scgp->dev = DEV_MMC_CDRW;
			if (dvd)
				scgp->dev = DEV_MMC_DVD;
			if (dvdwr)
				scgp->dev = DEV_MMC_DVD_WR;
		}
		break;

	case INQ_PROCD:
		if (strbeg("BERTHOLD", vendor_info)) {
			if (strbeg("", prod_ident))
				scgp->dev = DEV_HRSCAN;
		}
		break;

	case INQ_SCAN:
		scgp->dev = DEV_MS300A;
		break;
	}
	scgp->silent--;
	if (!print)
		return (TRUE);

	if (scgp->dev == DEV_UNKNOWN && !got_inquiry) {
#ifdef	PRINT_INQ_ERR
		scg_printerr(scgp);
#endif
		return (FALSE);
	}

	printinq(scgp, stdout);
	return (TRUE);
}

EXPORT void
printinq(scgp, f)
	SCSI	*scgp;
	FILE	*f;
{
	register struct scsi_inquiry *inq = scgp->inq;

	fprintf(f, "Device type    : ");
	scg_fprintdev(f, inq);
	fprintf(f, "Version        : %d\n", inq->ansi_version);
	fprintf(f, "Response Format: %d\n", inq->data_format);
	if (inq->data_format >= 2) {
		fprintf(f, "Capabilities   : ");
		if (inq->aenc)		fprintf(f, "AENC ");
		if (inq->termiop)	fprintf(f, "TERMIOP ");
		if (inq->reladr)	fprintf(f, "RELADR ");
		if (inq->wbus32)	fprintf(f, "WBUS32 ");
		if (inq->wbus16)	fprintf(f, "WBUS16 ");
		if (inq->sync)		fprintf(f, "SYNC ");
		if (inq->linked)	fprintf(f, "LINKED ");
		if (inq->cmdque)	fprintf(f, "CMDQUE ");
		if (inq->softreset)	fprintf(f, "SOFTRESET ");
		fprintf(f, "\n");
	}
	if (inq->add_len >= 31 ||
			inq->vendor_info[0] ||
			inq->prod_ident[0] ||
			inq->prod_revision[0]) {
		fprintf(f, "Vendor_info    : '%.8s'\n", inq->vendor_info);
		fprintf(f, "Identifikation : '%.16s'\n", inq->prod_ident);
		fprintf(f, "Revision       : '%.4s'\n", inq->prod_revision);
	}
}

EXPORT void
printdev(scgp)
	SCSI	*scgp;
{
	printf("Device seems to be: ");

	switch (scgp->dev) {

	case DEV_UNKNOWN:	printf("unknown");		break;
	case DEV_ACB40X0:	printf("Adaptec 4000/4010/4070"); break;
	case DEV_ACB4000:	printf("Adaptec 4000");		break;
	case DEV_ACB4010:	printf("Adaptec 4010");		break;
	case DEV_ACB4070:	printf("Adaptec 4070");		break;
	case DEV_ACB5500:	printf("Adaptec 5500");		break;
	case DEV_ACB4520A:	printf("Adaptec 4520A");	break;
	case DEV_ACB4525:	printf("Adaptec 4525");		break;
	case DEV_MD21:		printf("Emulex MD21");		break;
	case DEV_MD23:		printf("Emulex MD23");		break;
	case DEV_NON_CCS_DSK:	printf("Generic NON CCS Disk");	break;
	case DEV_CCS_GENDISK:	printf("Generic CCS Disk");	break;
	case DEV_SONY_SMO:	printf("Sony SMO-C501");	break;
	case DEV_MT02:		printf("Emulex MT02");		break;
	case DEV_SC4000:	printf("Sysgen SC4000");	break;
	case DEV_RXT800S:	printf("Maxtor RXT800S");	break;
	case DEV_HRSCAN:	printf("Berthold HR-Scanner");	break;
	case DEV_MS300A:	printf("Microtek MS300A");	break;

	case DEV_CDROM:		printf("Generic CD-ROM");	break;
	case DEV_MMC_CDROM:	printf("Generic mmc CD-ROM");	break;
	case DEV_MMC_CDR:	printf("Generic mmc CD-R");	break;
	case DEV_MMC_CDRW:	printf("Generic mmc CD-RW");	break;
	case DEV_MMC_DVD:	printf("Generic mmc2 DVD-ROM");	break;
	case DEV_MMC_DVD_WR:	printf("Generic mmc2 DVD-R/DVD-RW"); break;
	case DEV_CDD_521_OLD:	printf("Philips old CDD-521");	break;
	case DEV_CDD_521:	printf("Philips CDD-521");	break;
	case DEV_CDD_522:	printf("Philips CDD-522");	break;
	case DEV_PCD_600:	printf("Kodak PCD-600");	break;
	case DEV_CDD_2000:	printf("Philips CDD-2000");	break;
	case DEV_CDD_2600:	printf("Philips CDD-2600");	break;
	case DEV_YAMAHA_CDR_100:printf("Yamaha CDR-100");	break;
	case DEV_YAMAHA_CDR_400:printf("Yamaha CDR-400");	break;
	case DEV_PLASMON_RF_4100:printf("Plasmon RF-4100");	break;
	case DEV_SONY_CDU_924:	printf("Sony CDU-924S");	break;
	case DEV_RICOH_RO_1060C:printf("Ricoh RO-1060C");	break;
	case DEV_RICOH_RO_1420C:printf("Ricoh RO-1420C");	break;
	case DEV_TEAC_CD_R50S:	printf("Teac CD-R50S");		break;
	case DEV_MATSUSHITA_7501:printf("Matsushita CW-7501");	break;
	case DEV_MATSUSHITA_7502:printf("Matsushita CW-7502");	break;

	case DEV_PIONEER_DW_S114X: printf("Pioneer DW-S114X");	break;
	case DEV_PIONEER_DVDR_S101:printf("Pioneer DVD-R S101"); break;

	default:		printf("Missing Entry for dev %d",
						scgp->dev);	break;

	}
	printf(".\n");

}

EXPORT BOOL
do_inquiry(scgp, print)
	SCSI	*scgp;
	int	print;
{
	if (getdev(scgp, print)) {
		if (print)
			printdev(scgp);
		return (TRUE);
	} else {
		return (FALSE);
	}
}

EXPORT BOOL
recovery_needed(scgp, dp)
	SCSI	*scgp;
	cdr_t	*dp;
{
		int err;
	register struct	scg_cmd	*scmd = scgp->scmd;

	scgp->silent++;
	err = test_unit_ready(scgp);
	scgp->silent--;

	if (err >= 0)
		return (FALSE);
	else if (scmd->error >= SCG_FATAL)	/* nicht selektierbar */
		return (FALSE);

	if (scmd->sense.code < 0x70)		/* non extended Sense */
		return (FALSE);

						/* XXX Old Philips code */
	return (((struct scsi_ext_sense *)&scmd->sense)->sense_code == 0xD0);
}

EXPORT int
scsi_load(scgp, dp)
	SCSI	*scgp;
	cdr_t	*dp;
{
	int	key;
	int	code;

	if ((dp->cdr_flags & CDR_CADDYLOAD) == 0) {
		if (scsi_start_stop_unit(scgp, 1, 1, dp && (dp->cdr_cmdflags&F_IMMED)) >= 0)
			return (0);
	}

	if (wait_unit_ready(scgp, 60))
		return (0);

	key = scg_sense_key(scgp);
	code = scg_sense_code(scgp);

	if (key == SC_NOT_READY && (code == 0x3A || code == 0x30)) {
		errmsgno(EX_BAD, "Cannot load media with %s drive!\n",
			(dp->cdr_flags & CDR_CADDYLOAD) ? "caddy" : "this");
		errmsgno(EX_BAD, "Try to load media by hand.\n");
	}
	return (-1);
}

EXPORT int
scsi_unload(scgp, dp)
	SCSI	*scgp;
	cdr_t	*dp;
{
	return (scsi_start_stop_unit(scgp, 0, 1, dp && (dp->cdr_cmdflags&F_IMMED)));
}

EXPORT int
scsi_cdr_write(scgp, bp, sectaddr, size, blocks, islast)
	SCSI	*scgp;
	caddr_t	bp;		/* address of buffer */
	long	sectaddr;	/* disk address (sector) to put */
	long	size;		/* number of bytes to transfer */
	int	blocks;		/* sector count */
	BOOL	islast;		/* last write for track */
{
	return (write_xg1(scgp, bp, sectaddr, size, blocks));
}

EXPORT struct cd_mode_page_2A *
mmc_cap(scgp, modep)
	SCSI	*scgp;
	Uchar	*modep;
{
	int	len;
	int	val;
	Uchar	mode[0x100];
	struct	cd_mode_page_2A *mp;
	struct	cd_mode_page_2A *mp2;


retry:
	fillbytes((caddr_t)mode, sizeof (mode), '\0');

	if (!get_mode_params(scgp, 0x2A, "CD capabilities",
			mode, (Uchar *)0, (Uchar *)0, (Uchar *)0, &len)) {

		if (scg_sense_key(scgp) == SC_NOT_READY) {
			if (wait_unit_ready(scgp, 60))
				goto retry;
		}
		return (NULL);		/* Pre SCSI-3/mmc drive		*/
	}

	if (len == 0)			/* Pre SCSI-3/mmc drive		*/
		return (NULL);

	mp = (struct cd_mode_page_2A *)
		(mode + sizeof (struct scsi_mode_header) +
		((struct scsi_mode_header *)mode)->blockdesc_len);

	/*
	 * Do some heuristics against pre SCSI-3/mmc VU page 2A
	 * We should test for a minimum p_len of 0x14, but some
	 * buggy CD-ROM readers ommit the write speed values.
	 */
	if (mp->p_len < 0x10)
		return (NULL);

	val = a_to_u_2_byte(mp->max_read_speed);
	if (val != 0 && val < 176)
		return (NULL);

	val = a_to_u_2_byte(mp->cur_read_speed);
	if (val != 0 && val < 176)
		return (NULL);

	len -= sizeof (struct scsi_mode_header) +
		((struct scsi_mode_header *)mode)->blockdesc_len;
	if (modep)
		mp2 = (struct cd_mode_page_2A *)modep;
	else
		mp2 = malloc(len);
	if (mp2)
		movebytes(mp, mp2, len);

	return (mp2);
}

EXPORT void
mmc_getval(mp, cdrrp, cdwrp, cdrrwp, cdwrwp, dvdp, dvdwp)
	struct	cd_mode_page_2A *mp;
	BOOL	*cdrrp;				/* CD ROM		*/
	BOOL	*cdwrp;				/* CD-R writer		*/
	BOOL	*cdrrwp;			/* CD-RW reader		*/
	BOOL	*cdwrwp;			/* CD-RW writer		*/
	BOOL	*dvdp;				/* DVD reader		*/
	BOOL	*dvdwp;				/* DVD writer		*/
{
	BOOL	isdvd;				/* Any DVD reader	*/
	BOOL	isdvd_wr;			/* DVD writer (R / RAM)	*/
	BOOL	iscd_wr;			/* CD  writer		*/

	iscd_wr = (mp->cd_r_write != 0) ||	/* SCSI-3/mmc CD-R	*/
		    (mp->cd_rw_write != 0);	/* SCSI-3/mmc CD-RW	*/

	if (cdrrp)
		*cdrrp = (mp->cd_r_read != 0);	/* SCSI-3/mmc CD	*/
	if (cdwrp)
		*cdwrp = (mp->cd_r_write != 0);	/* SCSI-3/mmc CD-R	*/

	if (cdrrwp)
		*cdrrwp = (mp->cd_rw_read != 0); /* SCSI-3/mmc CD	*/
	if (cdwrwp)
		*cdwrwp = (mp->cd_rw_write != 0); /* SCSI-3/mmc CD-RW	*/

	isdvd =					/* SCSI-3/mmc2 DVD 	*/
		(mp->dvd_ram_read + mp->dvd_r_read  +
		    mp->dvd_rom_read) != 0;

	isdvd_wr =				/* SCSI-3/mmc2 DVD writer*/
		(mp->dvd_ram_write + mp->dvd_r_write) != 0;

	if (dvdp)
		*dvdp = isdvd;
	if (dvdwp)
		*dvdwp = isdvd_wr;
}

EXPORT BOOL
is_mmc(scgp, cdwp, dvdwp)
	SCSI	*scgp;
	BOOL	*cdwp;				/* CD  writer		*/
	BOOL	*dvdwp;				/* DVD writer		*/
{
	BOOL	cdwr	= FALSE;
	BOOL	cdwrw	= FALSE;

	if (cdwp)
		*cdwp = FALSE;
	if (dvdwp)
		*dvdwp = FALSE;

	if (!mmc_check(scgp, NULL, &cdwr, NULL, &cdwrw, NULL, dvdwp))
		return (FALSE);

	if (cdwp)
		*cdwp = cdwr | cdwrw;

	return (TRUE);
}

EXPORT BOOL
mmc_check(scgp, cdrrp, cdwrp, cdrrwp, cdwrwp, dvdp, dvdwp)
	SCSI	*scgp;
	BOOL	*cdrrp;				/* CD ROM		*/
	BOOL	*cdwrp;				/* CD-R writer		*/
	BOOL	*cdrrwp;			/* CD-RW reader		*/
	BOOL	*cdwrwp;			/* CD-RW writer		*/
	BOOL	*dvdp;				/* DVD reader		*/
	BOOL	*dvdwp;				/* DVD writer		*/
{
	Uchar	mode[0x100];
	BOOL	was_atapi;
	struct	cd_mode_page_2A *mp;

	if (scgp->inq->type != INQ_ROMD)
		return (FALSE);

	fillbytes((caddr_t)mode, sizeof (mode), '\0');

	was_atapi = allow_atapi(scgp, TRUE);
	scgp->silent++;
	mp = mmc_cap(scgp, mode);
	scgp->silent--;
	allow_atapi(scgp, was_atapi);
	if (mp == NULL)
		return (FALSE);

	mmc_getval(mp, cdrrp, cdwrp, cdrrwp, cdwrwp, dvdp, dvdwp);

	return (TRUE);			/* Generic SCSI-3/mmc CD	*/
}

LOCAL void
print_speed(fmt, val)
	char	*fmt;
	int	val;
{
	printf("  %s: %5d kB/s", fmt, val);
	printf(" (CD %3ux,", val/176);
	printf(" DVD %2ux)\n", val/1385);
}

#define	DOES(what, flag)	printf("  Does %s%s\n", flag?"":"not ", what)
#define	IS(what, flag)		printf("  Is %s%s\n", flag?"":"not ", what)
#define	VAL(what, val)		printf("  %s: %d\n", what, val[0]*256 + val[1])
#define	SVAL(what, val)		printf("  %s: %s\n", what, val)

EXPORT void
print_capabilities(scgp)
	SCSI	*scgp;
{
	BOOL	was_atapi;
	Uchar	mode[0x100];
	struct	cd_mode_page_2A *mp;
static	const	char	*bclk[4] = {"32", "16", "24", "24 (I2S)"};
static	const	char	*load[8] = {"caddy", "tray", "pop-up", "reserved(3)",
				"disc changer", "cartridge changer",
				"reserved(6)", "reserved(7)" };
static	const	char	*rotctl[4] = {"CLV/PCAV", "CAV", "reserved(2)", "reserved(3)"};


	if (scgp->inq->type != INQ_ROMD)
		return;

	fillbytes((caddr_t)mode, sizeof (mode), '\0');

	was_atapi = allow_atapi(scgp, TRUE);	/* Try to switch to 10 byte mode cmds */
	scgp->silent++;
	mp = mmc_cap(scgp, mode);
	scgp->silent--;
	allow_atapi(scgp, was_atapi);
	if (mp == NULL)
		return;

	printf("\nDrive capabilities, per");
	if (mp->p_len >= 28)
		printf(" MMC-3");
	else if (mp->p_len >= 24)
		printf(" MMC-2");
	else
		printf(" MMC");
	printf(" page 2A:\n\n");

	DOES("read CD-R media", mp->cd_r_read);
	DOES("write CD-R media", mp->cd_r_write);
	DOES("read CD-RW media", mp->cd_rw_read);
	DOES("write CD-RW media", mp->cd_rw_write);
	DOES("read DVD-ROM media", mp->dvd_rom_read);
	DOES("read DVD-R media", mp->dvd_r_read);
	DOES("write DVD-R media", mp->dvd_r_write);
	DOES("read DVD-RAM media", mp->dvd_ram_read);
	DOES("write DVD-RAM media", mp->dvd_ram_write);
	DOES("support test writing", mp->test_write);
	printf("\n");
	DOES("read Mode 2 Form 1 blocks", mp->mode_2_form_1);
	DOES("read Mode 2 Form 2 blocks", mp->mode_2_form_2);
	DOES("read digital audio blocks", mp->cd_da_supported);
	if (mp->cd_da_supported)
		DOES("restart non-streamed digital audio reads accurately", mp->cd_da_accurate);
	DOES("support Buffer-Underrun-Free recording", mp->BUF);
	DOES("read multi-session CDs", mp->multi_session);
	DOES("read fixed-packet CD media using Method 2", mp->method2);
	DOES("read CD bar code", mp->read_bar_code);
	DOES("read R-W subcode information", mp->rw_supported);
	if (mp->rw_supported)
		DOES("return R-W subcode de-interleaved and error-corrected", mp->rw_deint_corr);
	DOES("read raw P-W subcode data from lead in", mp->pw_in_lead_in);
	DOES("return CD media catalog number", mp->UPC);
	DOES("return CD ISRC information", mp->ISRC);
	DOES("support C2 error pointers", mp->c2_pointers);
	DOES("deliver composite A/V data", mp->composite);
	printf("\n");
	DOES("play audio CDs", mp->audio_play);
	if (mp->audio_play) {
		VAL("Number of volume control levels", mp->num_vol_levels);
		DOES("support individual volume control setting for each channel", mp->sep_chan_vol);
		DOES("support independent mute setting for each channel", mp->sep_chan_mute);
		DOES("support digital output on port 1", mp->digital_port_1);
		DOES("support digital output on port 2", mp->digital_port_2);
		if (mp->digital_port_1 || mp->digital_port_2) {
			DOES("send digital data LSB-first", mp->LSBF);
			DOES("set LRCK high for left-channel data", mp->RCK);
			DOES("have valid data on falling edge of clock", mp->BCK);
			SVAL("Length of data in BCLKs", bclk[mp->length]);
		}
	}
	printf("\n");
	SVAL("Loading mechanism type", load[mp->loading_type]);
	DOES("support ejection of CD via START/STOP command", mp->eject);
	DOES("lock media on power up via prevent jumper", mp->prevent_jumper);
	DOES("allow media to be locked in the drive via PREVENT/ALLOW command", mp->lock);
	IS("currently in a media-locked state", mp->lock_state);
	DOES("support changing side of disk", mp->side_change);
	DOES("have load-empty-slot-in-changer feature", mp->sw_slot_sel);
	DOES("support Individual Disk Present feature", mp->disk_present_rep);
	printf("\n");
	print_speed("Maximum read  speed", a_to_u_2_byte(mp->max_read_speed));
	print_speed("Current read  speed", a_to_u_2_byte(mp->cur_read_speed));
	print_speed("Maximum write speed", a_to_u_2_byte(mp->max_write_speed));
	if (mp->p_len >= 28)
		print_speed("Current write speed", a_to_u_2_byte(mp->v3_cur_write_speed));
	else
		print_speed("Current write speed", a_to_u_2_byte(mp->cur_write_speed));
	if (mp->p_len >= 28) {
		SVAL("Rotational control selected", rotctl[mp->rot_ctl_sel]);
	}
	VAL("Buffer size in KB", mp->buffer_size);

	if (mp->p_len >= 24) {
		VAL("Copy management revision supported", mp->copy_man_rev);
	}

	if (mp->p_len >= 28) {
		struct cd_wr_speed_performance *pp;
		Uint	ndesc;
		Uint	i;
		Uint	n;

		ndesc = a_to_u_2_byte(mp->num_wr_speed_des);
		pp = mp->wr_speed_des;
		printf("  Number of supported write speeds: %d\n", ndesc);
		for (i = 0; i < ndesc; i++, pp++) {
			printf("  Write speed # %d:", i);
			n = a_to_u_2_byte(pp->wr_speed_supp);
			printf(" %5d kB/s", n);
			printf(" %s", rotctl[pp->rot_ctl_sel]);
			printf(" (CD %3ux,", n/176);
			printf(" DVD %2ux)\n", n/1385);
		}
	}

	/* Generic SCSI-3/mmc CD	*/
}
