/*
XXX
SIZEOF testen !!!
*/
/* @(#)scsi_cdr.c	1.111 01/12/07 Copyright 1995 J. Schilling */
#ifndef lint
static	char sccsid[] =
	"@(#)scsi_cdr.c	1.111 01/12/07 Copyright 1995 J. Schilling";
#endif
/*
 *	SCSI command functions for cdrecord
 *
 *	Copyright (c) 1995 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	int	test_unit_ready	__PR((SCSI *scgp));
EXPORT	int	rezero_unit	__PR((SCSI *scgp));
EXPORT	int	request_sense	__PR((SCSI *scgp));
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));
EXPORT	int	scsi_set_speed	__PR((SCSI *scgp, int readspeed, int writespeed));
EXPORT	int	scsi_get_speed	__PR((SCSI *scgp, int *readspeedp, int *writespeedp));
EXPORT	int	scsi_set_streaming __PR((SCSI *scgp, int readspeed, int writespeed));
EXPORT	int	qic02		__PR((SCSI *scgp, 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	scsi_flush_cache __PR((SCSI *scgp));
EXPORT	int	read_buffer	__PR((SCSI *scgp, caddr_t bp, int cnt, int mode));
EXPORT	int	get_configuration __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, int));
EXPORT	int	read_dvd_structure __PR((SCSI *scgp, caddr_t bp, int cnt, 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 allow));
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));
EXPORT	int	scsi_load	__PR((SCSI *scgp));
EXPORT	int	scsi_unload	__PR((SCSI *scgp));
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));
EXPORT	void	print_capabilities __PR((SCSI *scgp));

EXPORT BOOL unit_ready(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(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);
		if (k == SC_NOT_READY && (c == 0x3A || c == 0x30)) {
			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(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 int test_unit_ready(SCSI *scgp)
{
	register struct	scg_cmd	*scmd = scgp->scmd;

	memset((caddr_t)scmd, 0, sizeof(*scmd));
	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(SCSI *scgp)
{
	register struct	scg_cmd	*scmd = scgp->scmd;

	memset((caddr_t)scmd, 0, sizeof(*scmd));
	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(SCSI *scgp)
{
		 char	sensebuf[CCS_SENSE_LEN];
	register struct	scg_cmd	*scmd = scgp->scmd;


	memset((caddr_t)scmd, 0, sizeof(*scmd));
	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 inquiry(SCSI *scgp, caddr_t bp, int cnt)
{
	register struct	scg_cmd	*scmd = scgp->scmd;

	memset(bp, 0, cnt);
	memset((caddr_t)scmd, 0, sizeof(*scmd));
	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(SCSI *scgp)
{
	register struct	scg_cmd	*scmd = scgp->scmd;

	memset((caddr_t)scmd, 0, sizeof(*scmd));
	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(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(SCSI *scgp, int load)
{
	register struct	scg_cmd	*scmd = scgp->scmd;

	memset((caddr_t)scmd, 0, sizeof(*scmd));
	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(SCSI *scgp, int prevent)
{
	register struct	scg_cmd	*scmd = scgp->scmd;

	memset((caddr_t)scmd, 0, sizeof(*scmd));
	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(SCSI *scgp, int flg, int loej)
{
	register struct	scg_cmd	*scmd = scgp->scmd;

	memset((caddr_t)scmd, 0, sizeof(*scmd));
	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);
	
	scgp->cmdname = "start/stop unit";

	return (scg_cmd(scgp));
}

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

	memset((caddr_t)scmd, 0, sizeof(*scmd));
	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);
	i_to_2_byte(&scmd->cdb.g5_cdb.addr[0], readspeed);
	i_to_2_byte(&scmd->cdb.g5_cdb.addr[2], writespeed);

	scgp->cmdname = "set cd speed";

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

EXPORT int scsi_get_speed(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;

	val = a_to_u_2_byte(mp->cur_write_speed);
	if (writespeedp)
		*writespeedp = val;

	return (0);
}

EXPORT int scsi_set_streaming(SCSI *scgp, int readspeed, int writespeed)
{
	register struct	scg_cmd	*scmd = scgp->scmd;
	char pdesc[28];

	memset((caddr_t)scmd, 0, sizeof(*scmd));
	memset((caddr_t)pdesc, 0, sizeof(pdesc));
	scmd->addr = pdesc;
	scmd->size = sizeof(pdesc);
	scmd->flags = SCG_DISRE_ENA;
	scmd->cdb_len = SC_G5_CDBLEN;
	scmd->sense_len = CCS_SENSE_LEN;
	scmd->cdb.g5_cdb.cmd = 0xB6;
	scmd->cdb.g5_cdb.lun = scg_lun(scgp);
	i_to_2_byte(&scmd->cdb.g5_cdb.addr[7], sizeof(pdesc));

	i_to_4_byte(&pdesc[4], 0);
	i_to_4_byte(&pdesc[8], scgp->cap->c_baddr);
	i_to_4_byte(&pdesc[12], readspeed);
	i_to_4_byte(&pdesc[16], 1000);	/* read time (ms) */
	i_to_4_byte(&pdesc[20], writespeed);
	i_to_4_byte(&pdesc[24], 1000);	/* read time (ms) */

	scgp->cmdname = "set streaming";

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


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

	memset((caddr_t)scmd, 0, sizeof(*scmd));
	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));
}

/*	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 */
EXPORT int write_xg0(SCSI *scgp, caddr_t bp, long addr, long size, int cnt)
{
	register struct	scg_cmd	*scmd = scgp->scmd;

	memset((caddr_t)scmd, 0, sizeof(*scmd));
	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));
}

/*	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 */
EXPORT int write_xg1(SCSI *scgp, caddr_t bp, long addr, long size, int cnt)
{
	register struct	scg_cmd	*scmd = scgp->scmd;

	memset((caddr_t)scmd, 0, sizeof(*scmd));
	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));
}

/*	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 */
EXPORT int write_xg5(SCSI *scgp, caddr_t bp, long addr, long size, int cnt)
{
	register struct	scg_cmd	*scmd = scgp->scmd;

	memset((caddr_t)scmd, 0, sizeof(*scmd));
	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 scsi_flush_cache(SCSI *scgp)
{
	register struct	scg_cmd	*scmd = scgp->scmd;

	memset((caddr_t)scmd, 0, sizeof(*scmd));
	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);
	
	scgp->cmdname = "flush cache";

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

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

	memset((caddr_t)scmd, 0, sizeof(*scmd));
	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 get_configuration(SCSI *scgp, caddr_t bp, int cnt, int sfn)
{
	register struct	scg_cmd	*scmd = scgp->scmd;

	memset((caddr_t)scmd, 0, sizeof(*scmd));
	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 = 0x46;	/* get configuration */
	scmd->cdb.g1_cdb.lun = scg_lun(scgp);
	i_to_2_byte(scmd->cdb.g1_cdb.addr,sfn);
	g1_cdblen(&scmd->cdb.g1_cdb, cnt);
	
	scgp->cmdname = "get configuration";

	return (scg_cmd(scgp));
}


EXPORT int read_subchannel(SCSI *scgp, caddr_t bp, int track, int cnt, int msf, int subq, int fmt)
{
	register struct	scg_cmd	*scmd = scgp->scmd;

	memset((caddr_t)scmd, 0, sizeof(*scmd));
	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(SCSI *scgp, caddr_t bp, int track, int cnt, int msf, int fmt)
{
	register struct	scg_cmd	*scmd = scgp->scmd;

	memset((caddr_t)scmd, 0, sizeof(*scmd));
	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(SCSI *scgp, caddr_t bp, int track, int cnt, int msf, int fmt)
{
	register struct	scg_cmd	*scmd = scgp->scmd;

	memset((caddr_t)scmd, 0, sizeof(*scmd));
	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(SCSI *scgp, caddr_t bp, long addr, int cnt, int msf)
{
	register struct	scg_cmd	*scmd = scgp->scmd;

	memset((caddr_t)scmd, 0, sizeof(*scmd));
	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(SCSI *scgp, caddr_t bp, int cnt)
{
	register struct	scg_cmd	*scmd = scgp->scmd;

	memset((caddr_t)scmd, 0, sizeof(*scmd));
	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(SCSI *scgp, caddr_t bp, int track, int cnt)
{
	register struct	scg_cmd	*scmd = scgp->scmd;

	memset((caddr_t)scmd, 0, sizeof(*scmd));
	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.g1_cdb.reladr = 1;	/* Track */
	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);
}

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

	memset((caddr_t)scmd, 0, sizeof(*scmd));
	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.g1_cdb.reladr = 1;	
	g1_cdbaddr(&scmd->cdb.g1_cdb, track);
	g1_cdblen(&scmd->cdb.g1_cdb, cnt);

	scgp->cmdname = "read rzone info";

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

EXPORT int reserve_track(SCSI *scgp, Ulong size)
{
	register struct	scg_cmd	*scmd = scgp->scmd;

	memset((caddr_t)scmd, 0, sizeof(*scmd));
	scmd->flags = SCG_DISRE_ENA;
	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";

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

	return (0);

}


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

	memset((caddr_t)scmd, 0, sizeof(*scmd));
	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_cdblen(&scmd->cdb.g5_cdb, cnt);
	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(SCSI *scgp, caddr_t bp, int cnt, int doopc)
{
	register struct	scg_cmd	*scmd = scgp->scmd;

	memset((caddr_t)scmd, 0, sizeof(*scmd));
	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(SCSI *scgp, caddr_t bp, int track, int cnt)
{
	register struct	scg_cmd	*scmd = scgp->scmd;

	memset((caddr_t)scmd, 0, sizeof(*scmd));
	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(SCSI *scgp, int type, int track, BOOL immed)
{
	register struct	scg_cmd	*scmd = scgp->scmd;

	memset((caddr_t)scmd, 0, sizeof(*scmd));
	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;
#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);
}

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

	memset((caddr_t)scmd, 0, sizeof(*scmd));
	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);
}

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

	memset((caddr_t)scmd, 0, sizeof(*scmd));
	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(SCSI *scgp, long *sp, long *fp)
{
	char	resp[12];
	Ulong	freespace;
	Ulong	bufsize;
	int	per;
	register struct	scg_cmd	*scmd = scgp->scmd;

	memset((caddr_t)scmd, 0, sizeof(*scmd));
	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(SCSI *scgp, long addr, int blanktype, BOOL immed)
{
	register struct	scg_cmd	*scmd = scgp->scmd;

	memset((caddr_t)scmd, 0, sizeof(*scmd));
	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;

	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(SCSI *scgp, BOOL allow)
{
	BOOL	old = is_atapi;
	Uchar	mode[256];

	if (allow == 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 (allow &&
	    mode_sense_g1(scgp, mode, 8, 0x3F, 0) < 0) {	/* All pages current */
		allow = FALSE;
	}
	scgp->silent--;

	is_atapi = allow;
	return (old);
}

EXPORT int mode_select(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(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(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;
		memcpy(&xmode[8], &dp[4], 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(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);
	}

	memset((caddr_t)xmode, 0, sizeof(xmode));
	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)
		memcpy(&dp[4], &xmode[8], 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(SCSI *scgp, Uchar *dp, int cnt, int smp, int pf)
{
	register struct	scg_cmd	*scmd = scgp->scmd;

	memset((caddr_t)scmd, 0, sizeof(*scmd));
	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) {
		printf("%s ", smp?"Save":"Set ");
		scg_prbytes("Mode Parameters", dp, cnt);
	}

	scgp->cmdname = "mode select g0";

	return (scg_cmd(scgp));
}

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

	memset((caddr_t)scmd, 0, sizeof(*scmd));
	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(SCSI *scgp, Uchar *dp, int cnt, int page, int pcf)
{
	register struct	scg_cmd	*scmd = scgp->scmd;

	memset((caddr_t)scmd, 0, sizeof(*scmd));
	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(SCSI *scgp, Uchar *dp, int cnt, int page, int pcf)
{
	register struct	scg_cmd	*scmd = scgp->scmd;

	memset((caddr_t)scmd, 0, sizeof(*scmd));
	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(SCSI *scgp, cdr_t *dp, int *fp, int *lp)
{
	struct	tocheader *tp;
	char	xb[256];
	int	len;

	tp = (struct tocheader *)xb;

	memset((caddr_t)xb, 0, sizeof(xb));
	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(SCSI *scgp)
{
	struct	tocheader *tp;
	char	xb[256];
	int	len;
	char	xxb[10000];

	tp = (struct tocheader *)xb;

	memset((caddr_t)xb, 0, sizeof(xb));
	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(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;

	memset((caddr_t)xb, 0, sizeof(xb));
	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);
	}

	memset((caddr_t)xb, 0, sizeof(xb));

	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(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;

	memset((caddr_t)xb, 0, sizeof(xb));
	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(SCSI *scgp, long *offp)
{
	struct	diskinfo *dp;
	char	xb[256];
	int	len;

	dp = (struct diskinfo *)xb;

	memset((caddr_t)xb, 0, sizeof(xb));
	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(SCSI *scgp, long *offp)
{
	struct	sinfo *sp;
	char	xb[256];
	int	len;

	sp = (struct sinfo *)xb;

	memset((caddr_t)xb, 0, sizeof(xb));
	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(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 !!! */

	memset(mode, 0, sizeof(mode));
	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) {
		memset(mode, 0, sizeof(mode));
		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]);
	}
	memset(mode, 0, sizeof(mode));
	/*
	 * 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(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 */

	memset((caddr_t)&md, 0, sizeof(md));
	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(SCSI *scgp)
{
	return (scgp->inq->type == INQ_ROMD || scgp->inq->type == INQ_WORM);
}

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

#ifndef	DEBUG
#define	DEBUG
#endif
#ifdef	DEBUG
#define	G0_MAXADDR	0x1FFFFFL

EXPORT int read_scsi(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(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);

	memset((caddr_t)scmd, 0, sizeof(*scmd));
	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(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);

	memset((caddr_t)scmd, 0, sizeof(*scmd));
	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(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;


	memset((caddr_t)inq, 0, sizeof(*inq));
	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(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->info[0] || inq->ident[0] || inq->revision[0]) {
		fprintf(f, "Vendor_info    : '%.8s'\n", inq->info);
		fprintf(f, "Identifikation : '%.16s'\n", inq->ident);
		fprintf(f, "Revision       : '%.4s'\n", inq->revision);
	}
}

EXPORT void printdev(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");	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(SCSI *scgp, int print)
{
	if (getdev(scgp, print)) {
		if (print)
			printdev(scgp);
		return (TRUE);
	} else {
		return (FALSE);
	}
}

EXPORT BOOL recovery_needed(SCSI *scgp)
{
		 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(SCSI *scgp)
{
	return (scsi_start_stop_unit(scgp, 1, 1));
}

EXPORT int scsi_unload(SCSI *scgp)
{
	return (scsi_start_stop_unit(scgp, 0, 1));
}

/*	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 */
EXPORT int scsi_cdr_write(SCSI *scgp, caddr_t bp, long sectaddr, long size, int blocks, BOOL islast)
{
	return (write_xg1(scgp, bp, sectaddr, size, blocks));
}

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


retry:
	memset((caddr_t)mode, 0, sizeof(mode));

	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 = (struct cd_mode_page_2A *)malloc(len);
	if (mp2)
		memcpy(mp2, mp, len);

	return (mp2);
}

/*	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		*/
EXPORT void mmc_getval(struct cd_mode_page_2A *mp, BOOL *cdrrp, BOOL *cdwrp, BOOL *cdrrwp, BOOL *cdwrwp, BOOL *dvdp, BOOL *dvdwp)
{
	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(SCSI *scgp, BOOL *cdwp, BOOL *dvdwp)
{
	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(SCSI *scgp, BOOL *cdrrp, BOOL *cdwrp, BOOL *cdrrwp, BOOL *cdwrwp, BOOL *dvdp, BOOL *dvdwp)
{
	Uchar	mode[0x100];
	BOOL	was_atapi;
	struct	cd_mode_page_2A *mp;

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

	memset((caddr_t)mode, 0, sizeof(mode));

	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	*/
}

#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(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)" };


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

	memset((caddr_t)mode, 0, sizeof(mode));

	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 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");
	VAL("Maximum read  speed in kB/s", mp->max_read_speed);
	VAL("Current read  speed in kB/s", mp->cur_read_speed);
	VAL("Maximum write speed in kB/s", mp->max_write_speed);
	VAL("Current write speed in kB/s", mp->cur_write_speed);
	VAL("Buffer size in KB", mp->buffer_size);

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