/* @(#)drv_dvd.c	1.145 07/07/04 Copyright 1998-2007 J. Schilling */
#ifndef lint
static	char sccsid[] =
	"@(#)drv_dvd.c	1.145 07/07/04 Copyright 1998-2007 J. Schilling";
#endif
/*
 *	DVD-R device implementation for
 *	SCSI-3/mmc-2 conforming drives
 *	Currently it only supports the Pioneer DVD-R S101
 *	as for the near future, there are no other drives.
 *
 *		Check recovery		- DUMMY
 *		Load Media		- OK
 *		Get Disktype		- Disk Status & Size (read ATIP -> DVD structure)
 *		Check Session ??	- Nicht vorhanden
 *		Check Disk size		- Nach Status & size + Next wr. Addr.
 *		Set Speed/Dummy		- Speed auf DVD -> dummy
 *		Open Session		- Set Write Parameter & ??? -> DAO
 *		LOOP
 *			Open Track	- Set Write Parameter ???
 *			Get Next wr. Addr.	-> DUMMY "0" ???
 *			Write Track Data	OK
 *			Close Track	- Flush Cache -> DUMMY
 *		END
 *		Fixate			- Close Track/Session -> Flush Cache
 *		Unload Media		- OK
 *
 *	Verbose levels:
 *			0		silent
 *			1		print laser log & track sizes
 *			2		print disk info & write parameters
 *			3		print log pages & dvd structure
 *
 *	Copyright (c) 1998-2007 J. Schilling
 */
/*
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * See the file CDDL.Schily.txt in this distribution for details.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file CDDL.Schily.txt from this distribution.
 */

#ifndef	DEBUG
#define	DEBUG
#endif
#include <schily/mconfig.h>

#include <stdio.h>
#include <schily/stdlib.h>
#include <schily/unistd.h>	/* Include sys/types.h to make off_t available */
#include <schily/standard.h>
#include <schily/string.h>

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

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

#include "scsimmc.h"
#include "scsilog.h"
#include "mmcvendor.h"
#include "cdrecord.h"


extern	char	*driveropts;

extern	int	lverbose;
extern	int	xdebug;

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

LOCAL	cdr_t	*identify_dvd		__PR((SCSI *scgp, cdr_t *, struct scsi_inquiry *));
LOCAL	int	attach_dvd		__PR((SCSI *scgp, cdr_t *));
LOCAL	void	di_to_dstat		__PR((struct disk_info *dip, dstat_t *dsp));
LOCAL	int	init_dvd		__PR((SCSI *scgp, cdr_t *dp));
LOCAL	int	getdisktype_dvd		__PR((SCSI *scgp, cdr_t *dp));
LOCAL	int	prdiskstatus_dvd	__PR((SCSI *scgp, cdr_t *dp));
LOCAL	int	speed_select_dvd	__PR((SCSI *scgp, cdr_t *dp, int *speedp));
LOCAL	int 	session_offset_dvd	__PR((SCSI *scgp, long *offp));
LOCAL	int	next_wr_addr_dvd	__PR((SCSI *scgp, track_t *trackp, long *ap));
LOCAL	int	open_track_dvd		__PR((SCSI *scgp, cdr_t *dp, track_t *trackp));
LOCAL	long	rzone_size		__PR((track_t *trackp));
LOCAL	int	close_track_dvd		__PR((SCSI *scgp, cdr_t *dp, track_t *trackp));
LOCAL	int	open_session_dvd	__PR((SCSI *scgp, cdr_t *dp, track_t *trackp));
LOCAL	int	fixate_dvd		__PR((SCSI *scgp, cdr_t *dp, track_t *trackp));
LOCAL	int	blank_dvd		__PR((SCSI *scgp, cdr_t *dp, long addr, int blanktype));
LOCAL	int	stats_dvd		__PR((SCSI *scgp, cdr_t *dp));
#ifdef	__needed__
LOCAL	int	read_rzone_info		__PR((SCSI *scgp, caddr_t bp, int cnt));
LOCAL	int	reserve_rzone		__PR((SCSI *scgp, long size));
#endif
/*LOCAL	int	send_dvd_structure	__PR((SCSI *scgp, caddr_t bp, int cnt));*/
LOCAL	int	set_layerbreak		__PR((SCSI *scgp, long	tsize, Int32_t lbreak));
LOCAL	void	print_dvd00		__PR((struct dvd_structure_00 *dp));
LOCAL	void	print_dvd01		__PR((struct dvd_structure_01 *dp));
LOCAL	void	print_dvd04		__PR((struct dvd_structure_04 *dp));
LOCAL	void	print_dvd05		__PR((struct dvd_structure_05 *dp));
LOCAL	void	print_dvd0D		__PR((struct dvd_structure_0D *dp));
LOCAL	void	print_dvd0E		__PR((struct dvd_structure_0E *dp));
LOCAL	void	print_dvd0F		__PR((struct dvd_structure_0F *dp));
LOCAL	void	print_dvd20		__PR((struct dvd_structure_20 *dp));
LOCAL	void	print_dvd22		__PR((struct dvd_structure_22 *dp));
LOCAL	void	print_dvd23		__PR((struct dvd_structure_23 *dp));
LOCAL	void	send_dvd0F		__PR((SCSI *scgp));
/*LOCAL	void	print_dvd_info		__PR((SCSI *scgp));*/
EXPORT	void	print_dvd_info		__PR((SCSI *scgp));
LOCAL	void	print_laserlog		__PR((SCSI *scgp));

cdr_t	cdr_dvd = {
	0, 0,
	CDR_DVD|CDR_SWABAUDIO,
	CDR_CDRW_ALL,
	WM_SAO,
	1000, 1000,
	"mmc_dvd",
	"generic SCSI-3/mmc-2 DVD-R/DVD-RW/DVD-RAM driver",
	0,
	(dstat_t *)0,
	identify_dvd,
	attach_dvd,
	init_dvd,
	getdisktype_dvd,
	prdiskstatus_dvd,
	scsi_load,
	scsi_unload,
	read_buff_cap,
	cmd_dummy,					/* recovery_needed */
	(int(*)__PR((SCSI *, cdr_t *, int)))cmd_dummy,	/* recover	*/
	speed_select_dvd,
	select_secsize,
	next_wr_addr_dvd,
	(int(*)__PR((SCSI *, Ulong)))cmd_ill,		/* reserve_track */
	scsi_cdr_write,
	(int(*)__PR((track_t *, void *, BOOL)))cmd_dummy,	/* gen_cue */
	(int(*)__PR((SCSI *scgp, cdr_t *, track_t *)))cmd_dummy, /* send_cue */
	(int(*)__PR((SCSI *, cdr_t *, track_t *)))cmd_dummy, /* leadin */
	open_track_dvd,
	close_track_dvd,
	open_session_dvd,
	cmd_dummy,
	cmd_dummy,					/* abort	*/
	session_offset_dvd,
	fixate_dvd,
	stats_dvd,
	blank_dvd,
	format_dummy,
	(int(*)__PR((SCSI *, caddr_t, int, int)))NULL,	/* no OPC	*/
	cmd_dummy,					/* opt1		*/
	cmd_dummy,					/* opt2		*/
};


LOCAL cdr_t *
identify_dvd(scgp, dp, ip)
	SCSI			*scgp;
	cdr_t			*dp;
	struct scsi_inquiry	*ip;
{
	BOOL	dvd	 = FALSE;	/* DVD writer	*/
	Uchar	mode[0x100];
	struct	cd_mode_page_2A *mp;
	int	profile;

	if (ip->type != INQ_WORM && ip->type != INQ_ROMD)
		return ((cdr_t *)0);

	allow_atapi(scgp, TRUE); /* Try to switch to 10 byte mode cmds */

	scgp->silent++;
	mp = mmc_cap(scgp, mode); /* Get MMC capabilities */
	scgp->silent--;
	if (mp == NULL)
		return (NULL);	/* Pre SCSI-2/mmc drive		*/

	/*
	 * At this point we know that we have a SCSI-3/mmc compliant drive.
	 * Unfortunately ATAPI drives violate the SCSI spec in returning
	 * a response data format of '1' which from the SCSI spec would
	 * tell us not to use the "PF" bit in mode select. As ATAPI drives
	 * require the "PF" bit to be set, we 'correct' the inquiry data.
	 *
	 * XXX xxx_identify() should not have any side_effects ??
	 */
	if (ip->data_format < 2)
		ip->data_format = 2;

	dvd = mp->dvd_r_write;		/* Mode page 0x2A DVD-R writer */

	/*
	 * Be careful, Lite-ON drives are lying in mode page 0x2A.
	 * We need to check the MMC-3 profile list too.
	 */
	profile = get_curprofile(scgp);
	if (profile >= 0x11 && profile <= 0x19)
		dvd = TRUE;

	if (!dvd)
		get_wproflist(scgp, NULL, &dvd, NULL, NULL);

	if (!dvd)			/* Any DVD- writer		*/
		return (NULL);

	return (dp);
}

LOCAL int
attach_dvd(scgp, dp)
	SCSI	*scgp;
	cdr_t	*dp;
{
	Uchar	mode[0x100];
	struct	cd_mode_page_2A *mp;
	struct	ricoh_mode_page_30 *rp = NULL;
	Ulong	xspeed;
	Ulong	mp2Aspeed;


	allow_atapi(scgp, TRUE); /* Try to switch to 10 byte mode cmds */

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

	dp->cdr_cdcap = mp;	/* Store MMC cap pointer	*/

	/*
	 * XXX hier sollte drive max write speed & drive cur write speed
	 * XXX gesetzt werden.
	 */
	dp->cdr_dstat->ds_dr_max_rspeed = a_to_u_2_byte(mp->max_read_speed)/1385;
	if (dp->cdr_dstat->ds_dr_max_rspeed == 0)
		dp->cdr_dstat->ds_dr_max_rspeed = 47;
	dp->cdr_dstat->ds_dr_cur_rspeed = a_to_u_2_byte(mp->cur_read_speed)/1385;
	if (dp->cdr_dstat->ds_dr_cur_rspeed == 0)
		dp->cdr_dstat->ds_dr_cur_rspeed = 47;

	dp->cdr_dstat->ds_dr_max_wspeed = a_to_u_2_byte(mp->max_write_speed)/1385;
	if (mp->p_len >= 28)
		dp->cdr_dstat->ds_dr_cur_wspeed = a_to_u_2_byte(mp->v3_cur_write_speed)/1385;
	else
		dp->cdr_dstat->ds_dr_cur_wspeed = a_to_u_2_byte(mp->cur_write_speed)/1385;

	/*
	 * NEC drives incorrectly return CD speed values in mode page 2A.
	 * Try MMC3 get performance in hope that values closer to DVD speeds
	 * are always more correct than what is found in mode page 2A.
	 */
	xspeed = 0;
	scsi_get_perf_maxspeed(scgp, NULL, &xspeed, NULL);

	mp2Aspeed = a_to_u_2_byte(mp->max_write_speed);

	if (lverbose > 2) {
		printf("max page 2A speed %lu (%lux), max perf speed %lu (%lux)\n",
			mp2Aspeed, mp2Aspeed/1385,
			xspeed, xspeed/1385);
	}

	if ((is_cdspeed(mp2Aspeed) && !is_cdspeed(xspeed)) ||
	    (mp2Aspeed < 10000 && xspeed > 10000)) {
		dp->cdr_dstat->ds_dr_max_wspeed = xspeed/1385;
		xspeed = 0;
		scsi_get_perf_curspeed(scgp, NULL, &xspeed, NULL);
		if (xspeed > 0)
			dp->cdr_dstat->ds_dr_cur_wspeed = xspeed / 1385;
	}

	if (dp->cdr_speedmax > dp->cdr_dstat->ds_dr_max_wspeed)
		dp->cdr_speedmax = dp->cdr_dstat->ds_dr_max_wspeed;

	if (dp->cdr_speeddef > dp->cdr_speedmax)
		dp->cdr_speeddef = dp->cdr_speedmax;

	rp = get_justlink_ricoh(scgp, mode);

	if (mp->p_len >= 28)
		dp->cdr_flags |= CDR_MMC3;
	if (mp->p_len >= 24)
		dp->cdr_flags |= CDR_MMC2;
	dp->cdr_flags |= CDR_MMC;

	if (mp->loading_type == LT_TRAY)
		dp->cdr_flags |= CDR_TRAYLOAD;
	else if (mp->loading_type == LT_CADDY)
		dp->cdr_flags |= CDR_CADDYLOAD;

	if (mp->BUF != 0)
		dp->cdr_flags |= CDR_BURNFREE;

	check_writemodes_mmc(scgp, dp);
	/*
	 * To avoid that silly people try to call cdrecord will write modes
	 * that are illegal for DVDs, we clear anything that does now work.
	 */
	dp->cdr_flags &= ~(CDR_RAW|CDR_RAW16|CDR_RAW96P|CDR_RAW96R|CDR_SRAW96P|CDR_SRAW96R);
	dp->cdr_flags &= ~(CDR_TAO);

	if (scgp->inq != NULL) {
		if (strbeg("PIONEER", scgp->inq->vendor_info)) {
			if (strbeg("DVD-RW  DVR-103", scgp->inq->prod_ident) ||
			    strbeg("DVD-R DVD-R7322", scgp->inq->prod_ident)) {
				mp->BUF = 1;
			}
		}
	}
	if (mp->BUF != 0) {
		dp->cdr_flags |= CDR_BURNFREE;
	} else if (rp) {
		if ((dp->cdr_cmdflags & F_DUMMY) && rp->TWBFS && rp->BUEFS)
			dp->cdr_flags |= CDR_BURNFREE;

		if (rp->BUEFS)
			dp->cdr_flags |= CDR_BURNFREE;
	}

	if (rp && rp->AWSCS)
		dp->cdr_flags |= CDR_FORCESPEED;


	if ((dp->cdr_flags & (CDR_SAO)) != (CDR_SAO)) {
		/*
		 * XXX Ist dies ueberhaupt noch notwendig seit wir nicht
		 * XXX mehr CDR_TAO vorgaukeln muessen?
		 *
		 * Das Panasonic DVD-R mag check_writemodes_mmc() nicht
		 * hilft das vielleicht?
		 */
		dp->cdr_flags |= CDR_SAO;
	}

	if (driveropts != NULL) {
		char	*p;

		if (strcmp(driveropts, "help") == 0) {
			mmc_opthelp(scgp, dp, 0);
		}

		p = hasdrvopt(driveropts, "burnfree");
		if (p == NULL)
			p = hasdrvopt(driveropts, "burnproof");
		if (p != NULL && (dp->cdr_flags & CDR_BURNFREE) != 0) {
			if (*p == '1') {
				dp->cdr_dstat->ds_cdrflags |= RF_BURNFREE;
			} else if (*p == '0') {
				dp->cdr_dstat->ds_cdrflags &= ~RF_BURNFREE;
			}
		}

		p = hasdrvopt(driveropts, "forcespeed");
		if (p != NULL && *p == '1' && (dp->cdr_flags & CDR_FORCESPEED) != 0) {
			dp->cdr_dstat->ds_cdrflags |= RF_FORCESPEED;
		}

		p = hasdrvoptx(driveropts, "layerbreak", 0);
		if (p != NULL && *p != '\0') {
			char	*ep;
			Llong	ll;
			Int32_t	lb;

			ep = astoll(p, &ll);
			lb = ll;
			if ((*ep != '\0' && *ep != ',') ||
			    ll <= 0 || ll != lb) {
				errmsgno(EX_BAD,
					"Bad layer break value '%s'.\n", p);
				return (-1);
			}
			dp->cdr_dstat->ds_layer_break = lb;
		} else {
			p = hasdrvopt(driveropts, "layerbreak");
			if (p != NULL && *p == '1')
				dp->cdr_dstat->ds_layer_break = 0;
		}
		if (dp->cdr_dstat->ds_layer_break >= 0 &&
		    (dp->cdr_flags & CDR_LAYER_JUMP) == 0) {
			errmsgno(EX_BAD,
			"Cannot set layer break on this drive/medium.\n");
			return (-1);
		}
		if (dp->cdr_dstat->ds_layer_break != -1 &&
		    dp->cdr_dstat->ds_layer_break !=
		    roundup(dp->cdr_dstat->ds_layer_break, 16)) {
			errmsgno(EX_BAD,
			"Layer break at %u is not properly aligned.\n",
				dp->cdr_dstat->ds_layer_break);
			return (-1);
		}
	}

	/*
	 * Raise the default timeout.
	 * The first write takes a long time as it writes the lead in.
	 */
	scgp->deftimeout = 100;		/* 1:40				*/

	return (0);
}

LOCAL void
di_to_dstat(dip, dsp)
	struct disk_info	*dip;
	dstat_t	*dsp;
{
	dsp->ds_diskid = a_to_u_4_byte(dip->disk_id);

	dsp->ds_flags |= DSF_DVD;	/* This is a DVD */

	if (dip->did_v)
		dsp->ds_flags |= DSF_DID_V;
	dsp->ds_disktype = dip->disk_type;
	dsp->ds_diskstat = dip->disk_status;
	dsp->ds_sessstat = dip->sess_status;
	if (dip->erasable)
		dsp->ds_flags |= DSF_ERA;

	dsp->ds_trfirst	   = dip->first_track;
	dsp->ds_trlast	   = dip->last_track_ls;
	dsp->ds_trfirst_ls = dip->first_track_ls;

#ifdef	nono
	/*
	 * On DVD systems, there is no lead out start time
	 * in the disk info because there is no time based data.
	 */
	dsp->ds_maxblocks = msf_to_lba(dip->last_lead_out[1],
					dip->last_lead_out[2],
					dip->last_lead_out[3], TRUE);
#endif
}

LOCAL int
init_dvd(scgp, dp)
	SCSI	*scgp;
	cdr_t	*dp;
{
	return (speed_select_dvd(scgp, dp, NULL));
}

LOCAL int
getdisktype_dvd(scgp, dp)
	SCSI	*scgp;
	cdr_t	*dp;
{
extern	char	*buf;
	dstat_t	*dsp = dp->cdr_dstat;
	struct disk_info *dip;
	Uchar	mode[0x100];
	struct rzone_info rz;
	struct rzone_info *rp;
	struct dvd_structure_00 *sp;
	int	len;
	BOOL	did_dummy = FALSE;

	if (lverbose > 0)
		print_laserlog(scgp);

	if (lverbose > 2)
		print_logpages(scgp);

	if ((dp->cdr_dstat->ds_cdrflags & RF_PRATIP) != 0) {
		if (((dsp->ds_cdrflags & (RF_WRITE|RF_BLANK)) == 0) ||
				lverbose > 1) {
			/*
			 * Das DVD Medieninfo ist so lang, daß wir es
			 * beim Schreiben mit -v noch nicht ausgeben.
			 */
			print_dvd_info(scgp);
		}
	}

again:
	dip = (struct disk_info *)buf;
	if (get_diskinfo(scgp, dip, sizeof (*dip)) < 0)
		return (-1);

	/*
	 * Check for non writable disk first.
	 */
	if (dip->disk_status == DS_COMPLETE &&
			(dsp->ds_cdrflags & (RF_WRITE|RF_BLANK)) == RF_WRITE) {
		if (!did_dummy) {
			int	xspeed = 0xFFFF;
			int	oflags = dp->cdr_cmdflags;

			/*
			 * Try to clear the dummy bit to reset the virtual
			 * drive status. Not all drives support it even though
			 * it is mentioned in the MMC standard.
			 */
			if (lverbose)
				printf("Trying to clear drive status.\n");

			dp->cdr_cmdflags &= ~F_DUMMY;
			speed_select_dvd(scgp, dp, &xspeed);
			dp->cdr_cmdflags = oflags;
			did_dummy = TRUE;
			goto again;
		}
		/*
		 * Trying to clear drive status did not work...
		 */
		reload_media(scgp, dp);
	}
	if (get_diskinfo(scgp, dip, sizeof (*dip)) < 0)
		return (-1);
	di_to_dstat(dip, dsp);

	/*
	 * This information is based on a logical recording zone
	 * and may not always be correct.
	 * XXX Check this if we want to support anything else but
	 * XXX one data track on DAO mode. The current firmware
	 * XXX of the recorder supports only this method but future
	 * XXX releases may support more.
	 */
	fillbytes((caddr_t)mode, sizeof (mode), '\0');
	rp = (struct rzone_info *)mode;
	read_rzone_info(scgp, (caddr_t)rp, sizeof (struct rzone_info));

	if ((dp->cdr_dstat->ds_cdrflags & RF_PRATIP) != 0) {
		if (((dsp->ds_cdrflags & (RF_WRITE|RF_BLANK)) == 0) ||
				lverbose > 1) {
			przone(rp);
			print_format_capacities(scgp);
		}
	}
	movebytes(mode, (caddr_t)&rz, sizeof (struct rzone_info));
#ifdef	nonono_old_code
	/*
	 * Old code used with the S101 seems to be a bad idea....
	 * The new code seems to work with all DVD drives.
	 */
	dsp->ds_maxblocks = a_to_u_4_byte(rp->rzone_size);
	if (dsp->ds_maxblocks == 0)
#endif
	dsp->ds_maxblocks = a_to_u_4_byte(rp->free_blocks);
	if (rp->nwa_v)
		dsp->ds_maxblocks += a_to_u_4_byte(rp->next_recordable_addr);

	/*
	 * This information is based on the physical pre recorded information.
	 * First try to find the len supported by the actual drive.
	 */
	fillbytes((caddr_t)mode, sizeof (mode), '\0');
	if (read_dvd_structure(scgp, (caddr_t)mode, 2, 0, 0, 0, 0) < 0) {
		errmsgno(EX_BAD, "Cannot read DVD structure.\n");
		return (-1);
	}
	len = a_to_u_2_byte(mode);
	len += 2;			/* Data len is not included */

	if (len > sizeof (struct dvd_structure_00)) {
		len = sizeof (struct dvd_structure_00);
		/*
		 * The ACARD TECH AEC-7720 ATAPI<->SCSI adaptor
		 * chokes if we try to transfer odd byte counts (rounds up to
		 * even byte counts and thus causes a DMA overflow and a
		 * bus reset), so make the byte count even.
		 */
		len += 1;
		len &= ~1;
	}
	fillbytes((caddr_t)mode, sizeof (mode), '\0');
	sp = (struct dvd_structure_00 *)mode;
	read_dvd_structure(scgp, (caddr_t)sp, len, 0, 0, 0, 0);
/*	if (lverbose > 1)*/
/*		print_dvd00(sp);*/
	if (((struct dvd_structure_00 *)sp)->book_type == 1) {
		dsp->ds_type = DST_DVD_RAM;
	} else {
		dsp->ds_type = DST_UNKNOWN;
	}
	if (get_curprofile(scgp) == 0x12) {
		dsp->ds_type = DST_DVD_RAM;
	}
	if (dsp->ds_type == DST_DVD_RAM)
		dsp->ds_maxblocks = a_to_u_4_byte(rz.rzone_size);
	/*
	 * Bei Pioneer ist Phys End ist nur bei dem S101 != 0.
	 * Bei Panasonic ist Phys End == Phys Start.
	 * Do not print this for -msinfo
	 */
	if ((dp->cdr_cmdflags & F_MSINFO) == 0 &&
	    (a_to_u_3_byte(sp->phys_end) != 0) &&
			(dsp->ds_maxblocks !=
			(long)(a_to_u_3_byte(sp->phys_end) - a_to_u_3_byte(sp->phys_start) + 1))) {
		printf("WARNING: Phys disk size %ld differs from rzone size %ld! Prerecorded disk?\n",
			(long)(a_to_u_3_byte(sp->phys_end) - a_to_u_3_byte(sp->phys_start) + 1),
			(long)dsp->ds_maxblocks);
		printf("WARNING: Phys start: %ld Phys end %ld\n",
			(long)a_to_u_3_byte(sp->phys_start),
			(long)a_to_u_3_byte(sp->phys_end));
	}

	return (drive_getdisktype(scgp, dp));
}

LOCAL int
prdiskstatus_dvd(scgp, dp)
	SCSI	*scgp;
	cdr_t	*dp;
{
	return (prdiskstatus(scgp, dp, FALSE));
}

LOCAL int
speed_select_dvd(scgp, dp, speedp)
	SCSI	*scgp;
	cdr_t	*dp;
	int	*speedp;
{
	Uchar	mode[0x100];
	Uchar	moder[0x100];
	int	len;
	struct	cd_mode_page_05 *mp;
	struct	ricoh_mode_page_30 *rp = NULL;
	int	val;
	Ulong	ul;
	BOOL	forcespeed = FALSE;
	BOOL	dummy = (dp->cdr_cmdflags & F_DUMMY) != 0;
	int	curspeed = 1;


	if (speedp)
		curspeed = *speedp;

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

	if (!get_mode_params(scgp, 0x05, "CD write parameter",
			mode, (Uchar *)0, (Uchar *)0, (Uchar *)0, &len))
		return (-1);
	if (len == 0)
		return (-1);

	mp = (struct cd_mode_page_05 *)
		(mode + sizeof (struct scsi_mode_header) +
		((struct scsi_mode_header *)mode)->blockdesc_len);
#ifdef	DEBUG
	if (lverbose > 1)
		scg_prbytes("CD write parameter:", (Uchar *)mode, len);
#endif

	if (dp->cdr_dstat->ds_type == DST_DVD_RAM && dummy != 0) {
		errmsgno(EX_BAD, "DVD-RAM has no -dummy mode.\n");
		return (-1);
	}

	mp->test_write = dummy != 0;
	/*
	 * Set default values:
	 * Write type = 02 (disk at once)
	 * Track mode = 00 Reserved on Pioneer DVR-S101
	 * Data block type = 00 Reserved on Pioneer DVR-S101
	 * Session format = 00 Reserved on Pioneer DVR-S101
	 * XXX DVR-S101 uses ls_v and link size violating
	 * XXX the current MMC2 spec.
	 */
	mp->write_type = WT_SAO;
	if (dp->cdr_dstat->ds_layer_break >= 0)
		mp->write_type = WT_LAYER_JUMP;

#ifdef	DEBUG
	if (lverbose > 1)
		scg_prbytes("CD write parameter:", (Uchar *)mode, len);
#endif
	if (!set_mode_params(scgp, "CD write parameter", mode, len, 0, -1))
		return (-1);

	/*
	 * Neither set nor get speed.
	 */
	if (speedp == 0)
		return (0);


	rp = get_justlink_ricoh(scgp, moder);
	if ((dp->cdr_flags & CDR_FORCESPEED) != 0) {
		forcespeed = rp && rp->AWSCD != 0;
	}

	if (lverbose && (dp->cdr_flags & CDR_FORCESPEED) != 0)
		printf("Forcespeed is %s.\n", forcespeed?"ON":"OFF");

	if (!forcespeed && (dp->cdr_dstat->ds_cdrflags & RF_FORCESPEED) != 0) {
		printf("Turning forcespeed on\n");
		forcespeed = TRUE;
	}
	if (forcespeed && (dp->cdr_dstat->ds_cdrflags & RF_FORCESPEED) == 0) {
		printf("Turning forcespeed off\n");
		forcespeed = FALSE;
	}
	if ((dp->cdr_flags & CDR_FORCESPEED) != 0) {

		if (rp) {
			rp->AWSCD = forcespeed?1:0;
			set_mode_params(scgp, "Ricoh Vendor Page", moder, moder[0]+1, 0, -1);
			rp = get_justlink_ricoh(scgp, moder);
		}
	}

	/*
	 * DVD single speed is 1385 kB/s
	 * Rounding down is guaranteed.
	 */
	val = curspeed*1390;
	if (val > 0x7FFFFFFF)
		val = 0x7FFFFFFF;
	if (dp->cdr_flags & CDR_MMC3) {
		if (speed_select_mdvd(scgp, -1, val) < 0)
			errmsgno(EX_BAD, "MMC-3 speed select did not work.\n");
	} else {
		if (val > 0xFFFF)
			val = 0xFFFF;
		scgp->silent++;
		if (scsi_set_speed(scgp, -1, val, ROTCTL_CLV) < 0) {
			/*
			 * Don't complain if it does not work,
			 * DVD drives may not have speed setting.
			 * The DVR-S101 and the DVR-S201 difinitely
			 * don't allow to set the speed.
			 */
		}
		scgp->silent--;
	}

	scgp->silent++;
	val = 0;
	if (scsi_get_speed(scgp, 0, &val) >= 0) {
		if (val > 0) {
			curspeed = val / 1385;
			*speedp = curspeed;
		}
	}
	/*
	 * NEC drives incorrectly return CD speed values in mode page 2A.
	 * Try MMC3 get performance in hope that values closer to DVD speeds
	 * are always more correct than what is found in mode page 2A.
	 */
	ul = 0;
	if (scsi_get_perf_curspeed(scgp, NULL, &ul, NULL) >= 0) {
		if (is_cdspeed(val) && !is_cdspeed(ul)) {
			curspeed = ul / 1385;
			*speedp = curspeed;
		}
	}

	scgp->silent--;
	return (0);
}

LOCAL int 
session_offset_dvd(scgp, offp) 
        SCSI    *scgp; 
        long    *offp; 
{
	return (sessstatus(scgp, FALSE, offp, (long *)NULL));
}

LOCAL long	dvd_next_addr;

LOCAL int
next_wr_addr_dvd(scgp, trackp, ap)
	SCSI	*scgp;
	track_t	*trackp;
	long	*ap;
{
	struct disk_info	di;
	struct rzone_info	rz;
	int			tracks;
	long			next_addr = -1;

	/*
	 * If the track pointer is set to NULL, our caller likes to get
	 * the next writable address for the next (unwritten) session.
	 */
	if (trackp == 0) {
		fillbytes((caddr_t)&di, sizeof (di), '\0');
		if (get_diskinfo(scgp, &di, sizeof (di)) < 0)
			return (-1);

		tracks = di.last_track_ls + di.last_track_ls_msb * 256;
		fillbytes((caddr_t)&rz, sizeof (rz), '\0');
		if (get_trackinfo(scgp, (caddr_t)&rz, TI_TYPE_TRACK, tracks, sizeof (rz)) < 0)
			return (-1);
		if (!rz.nwa_v)
			return (-1);
		next_addr = a_to_4_byte(rz.next_recordable_addr);
		if (ap)
			*ap = next_addr;
		return (0);
	}
	if (trackp->track <= 1) {
		/*
		 * XXX This is a workaround for the filesize > 2GB problem.
		 * XXX Check this if we support more than one track DAO
		 * XXX or if we give up this hack in favour of real 64bit
		 * XXX filesize support.
		 */
		fillbytes((caddr_t)&rz, sizeof (rz), '\0');
			read_rzone_info(scgp, (caddr_t)&rz, sizeof (struct rzone_info));
		dvd_next_addr = a_to_4_byte(rz.next_recordable_addr);
		if (lverbose > 1)
			printf("next writable addr: %ld valid: %d\n", dvd_next_addr, rz.nwa_v);
	}
	if (ap)
		*ap = dvd_next_addr;
	return (0);
}

LOCAL int
open_track_dvd(scgp, dp, trackp)
	SCSI	*scgp;
	cdr_t	*dp;
	track_t *trackp;
{
	Uchar	mode[0x100];
	int	len;
	long	sectors;
	struct	cd_mode_page_05 *mp;

	if (trackp->track > 1)	/* XXX Hack to make one 'track' from several */
		return (0);	/* XXX files in Disk at once mode only.	    */

	if (dp->cdr_dstat->ds_type == DST_DVD_RAM) {
		/*
		 * Compile vitual track list
		 */
		sectors = rzone_size(trackp);
		if (sectors < 0)
			return (-1);
		return (0);	/* No further setup needed */
	}

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

	if (!get_mode_params(scgp, 0x05, "CD write parameter",
			mode, (Uchar *)0, (Uchar *)0, (Uchar *)0, &len))
		return (-1);
	if (len == 0)
		return (-1);

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

	/*
	 * XXX as long as the Pioneer DVR-S101 only supports a single
	 * XXX data track in DAO mode,
	 * XXX do not set:
	 * XXX track_mode
	 * XXX copy
	 * XXX dbtype
	 *
	 * Track mode = 00 Reserved on Pioneer DVR-S101
	 * Data block type = 00 Reserved on Pioneer DVR-S101
	 * Session format = 00 Reserved on Pioneer DVR-S101
	 * XXX DVR-S101 uses ls_v and link size violating
	 * XXX the current MMC2 spec.
	 */
	/* XXX look into drv_mmc.c for re-integration of above settings */

#ifdef	DEBUG
	if (lverbose > 1)
		scg_prbytes("CD write parameter:", (Uchar *)mode, len);
#endif
	if (!set_mode_params(scgp, "CD write parameter", mode, len, 0, trackp->secsize))
		return (-1);

	/*
	 * Compile vitual track list
	 */
	sectors = rzone_size(trackp);
	if (sectors < 0)
		return (-1);
	return (reserve_tr_rzone(scgp, sectors));
}

/*
 * XXX Hack to make one 'track' from several
 * XXX files in Disk at once mode only.
 * XXX Calculate track size and reserve rzone.
 */
LOCAL long
rzone_size(trackp)
	track_t *trackp;	/* Called with &track[1] */
{
	int	i;
	BOOL	vtracks = FALSE;
	long	sectors = 0L;
	Llong	ttrsize = 0L;
	Llong	tamount = 0L;
	Llong	amount;
	long	secsize = trackp->secsize;

	for (i = 0; i < MAX_TRACK; i++) {
		if (is_last(&trackp[i]))
			break;
	}
	if (i >= 1)
		vtracks = TRUE;
	if (vtracks && lverbose)
		printf("Compiling virtual track list ...\n");

	for (i = 0; i < MAX_TRACK; i++) {
		if (trackp[i].tracksize < (tsize_t)0) {
			errmsgno(EX_BAD, "VTrack %d has unknown length.\n", i);
			return (-1);
		}
		amount = roundup(trackp[i].tracksize, secsize);
		amount += (Llong)trackp[i].padsecs * secsize;
		sectors += amount/secsize;
		ttrsize += trackp[i].tracksize;
		tamount += amount;
		if (vtracks && lverbose)
			printf("Vtrack:  %d size: %lld bytes %lld rounded (%lld sectors)\n",
				(int)trackp[i].track, (Llong)trackp[i].tracksize,
				amount, amount / (Llong)secsize);

		if (is_last(&trackp[i]))
			break;

		/*
		 * XXX Is it possible for a DVD that input sector size
		 * XXX differes from output sector size?
		 * XXX I believe that not.
		 */
		if (trackp[i].tracksize % secsize) {
			comerrno(EX_BAD, "Virtual track %d is not a multiple of secsize.\n", (int)trackp[i].track);
		}
	}

	if (vtracks && lverbose)
		printf("Vtracks: %d size: %lld bytes %lld rounded (%ld sectors) total\n",
			i+1, ttrsize, tamount, sectors);

	return (sectors);
}

LOCAL int
close_track_dvd(scgp, dp, trackp)
	SCSI	*scgp;
	cdr_t	*dp;
	track_t	*trackp;
{
	long	sectors = 0L;
	Llong	amount;
	long	secsize = trackp->secsize;

	/*
	 * Compute the start of the next "track" for the hack
	 * that allows to have a track in more than one file.
	 * XXX Check this if the vtrack code is removed.
	 */
	amount = roundup(trackp->tracksize, secsize);
	amount += (Llong)trackp->padsecs * secsize;
	sectors += amount/secsize;

	dvd_next_addr += sectors;

	return (0);
}

LOCAL int
open_session_dvd(scgp, dp, trackp)
	SCSI	*scgp;
	cdr_t	*dp;
	track_t	*trackp;
{
	Uchar	mode[0x100];
	Uchar	moder[0x100];
	int	len;
	struct	cd_mode_page_05 *mp;
	struct	ricoh_mode_page_30 *rp = NULL;
	BOOL	burnfree = FALSE;

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

	if (!get_mode_params(scgp, 0x05, "CD write parameter",
			mode, (Uchar *)0, (Uchar *)0, (Uchar *)0, &len))
		return (-1);
	if (len == 0)
		return (-1);

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

	/*
	 * XXX as long as the Pioneer DVR-S101 only supports a single
	 * XXX data track in DAO mode,
	 * XXX do not set:
	 * XXX multi_session
	 * XXX sessipon_format
	 *
	 * Track mode = 00 Reserved on Pioneer DVR-S101
	 * Data block type = 00 Reserved on Pioneer DVR-S101
	 * Session format = 00 Reserved on Pioneer DVR-S101
	 * XXX DVR-S101 uses ls_v and link size violating
	 * XXX the current MMC2 spec.
	 */
	/* XXX look into drv_mmc.c for re-integration of above settings */
	mp->write_type = WT_SAO;
	if (dp->cdr_dstat->ds_layer_break >= 0)
		mp->write_type = WT_LAYER_JUMP;


	rp = get_justlink_ricoh(scgp, moder);

	if (dp->cdr_cdcap->BUF != 0) {
		burnfree = mp->BUFE != 0;
	} else if ((dp->cdr_flags & CDR_BURNFREE) != 0) {
		burnfree = rp && rp->BUEFE != 0;
	}

	if (lverbose && (dp->cdr_flags & CDR_BURNFREE) != 0)
		printf("BURN-Free is %s.\n", burnfree?"ON":"OFF");

	if (!burnfree && (dp->cdr_dstat->ds_cdrflags & RF_BURNFREE) != 0) {
		printf("Turning BURN-Free on\n");
		burnfree = TRUE;
	}
	if (burnfree && (dp->cdr_dstat->ds_cdrflags & RF_BURNFREE) == 0) {
		printf("Turning BURN-Free off\n");
		burnfree = FALSE;
	}
	if (dp->cdr_cdcap->BUF != 0) {
		mp->BUFE = burnfree?1:0;
	} else if ((dp->cdr_flags & CDR_BURNFREE) != 0) {

		if (rp)
			rp->BUEFE = burnfree?1:0;
	}
	if (rp) {
		i_to_2_byte(rp->link_counter, 0);
		if (xdebug)
			scg_prbytes("Mode Select Data ", moder, moder[0]+1);

		set_mode_params(scgp, "Ricoh Vendor Page", moder, moder[0]+1, 0, -1);
		rp = get_justlink_ricoh(scgp, moder);
	}


#ifdef	DEBUG
	if (lverbose > 1)
		scg_prbytes("CD write parameter:", (Uchar *)mode, len);
#endif
	if (!set_mode_params(scgp, "CD write parameter", mode, len, 0, -1))
		return (-1);

	return (0);
}


LOCAL int
fixate_dvd(scgp, dp, trackp)
	SCSI	*scgp;
	cdr_t	*dp;
	track_t	*trackp;
{
	int	oldtimeout = scgp->deftimeout;
	int	ret = 0;

	/*
	 * This is only valid for DAO recording.
	 * XXX Check this if the DVR-S101 supports more.
	 * XXX flush cache currently makes sure that
	 * XXX at least ~ 800 MBytes written to the track.
	 * XXX flush cache triggers writing the lead out.
	 */
	scgp->deftimeout = 1000;

	if (scsi_flush_cache(scgp, FALSE) < 0) {
		printf("Trouble flushing the cache\n");
		scgp->deftimeout = oldtimeout;
		return (-1);
	}

	scgp->deftimeout = oldtimeout;

	if (dp->cdr_dstat->ds_type == DST_DVD_RAM) {
		/*
		 * XXX make sure we do not forget DVD-RAM once we did add
		 * XXX support for multi-border recording.
		 */
		return (ret);
	}

	return (ret);
}

LOCAL int
blank_dvd(scgp, dp, addr, blanktype)
	SCSI	*scgp;
	cdr_t	*dp;
	long	addr;
	int	blanktype;
{
/*XXX*/extern char *blank_types[];

	BOOL	cdrr	 = FALSE;	/* Read CD-R	*/
	BOOL	cdwr	 = FALSE;	/* Write CD-R	*/
	BOOL	cdrrw	 = FALSE;	/* Read CD-RW	*/
	BOOL	cdwrw	 = FALSE;	/* Write CD-RW	*/
	BOOL	dvdwr	 = FALSE;	/* DVD writer	*/
	int	profile;

	mmc_check(scgp, &cdrr, &cdwr, &cdrrw, &cdwrw, NULL, &dvdwr);
	/*
	 * If the drive supports MMC-3, check for a DVD-RW medium.
	 */
	profile = get_curprofile(scgp);
	if (profile > 0)
		dvdwr = (profile == 0x13) || (profile == 0x14);

	if (!dvdwr)
		return (blank_dummy(scgp, dp, addr, blanktype));

	if (lverbose) {
		printf("Blanking %s\n", blank_types[blanktype & 0x07]);
		flush();
	}

	return (scsi_blank(scgp, addr, blanktype, FALSE));
}

LOCAL int
stats_dvd(scgp, dp)
	SCSI	*scgp;
	cdr_t	*dp;
{
	Uchar mode[256];
	struct	ricoh_mode_page_30 *rp;
	UInt32_t count;

	if ((dp->cdr_dstat->ds_cdrflags & RF_BURNFREE) == 0)
		return (0);

	rp = get_justlink_ricoh(scgp, mode);
	if (rp) {
		count = a_to_u_2_byte(rp->link_counter);
		if (lverbose) {
			if (count == 0)
				printf("BURN-Free was not used.\n");
			else
				printf("BURN-Free was %d times used.\n",
					(int)count);
		}
	}
	return (0);
}

#ifdef	__needed__
LOCAL int
read_rzone_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->cdb.g1_cdb.cmd = 0x52;
	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 rzone info";

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

LOCAL int
reserve_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_rzone";

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

#ifdef	_is_this_pioneer_vendor_unique_
LOCAL int
send_dvd_structure(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_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 = 0xFB;
	scmd->cdb.g5_cdb.lun = scg_lun(scgp);
	g5_cdblen(&scmd->cdb.g5_cdb, cnt);

	scgp->cmdname = "read dvd structure";

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

LOCAL int
set_layerbreak(scgp, tsize, lbreak)
	SCSI	*scgp;
	long	tsize;
	Int32_t	lbreak;
{
#ifdef	USE_STRUC_22
	struct dvd_structure_22	jz;
#endif
	struct dvd_structure_23	lb;
	int			ret;
	UInt32_t		dsize;
#ifdef	USE_STRUC_22
	UInt32_t		jump_size;
#endif
	UInt32_t		jump_lba;

#ifdef	USE_STRUC_22
	/*
	 * Jump interval size 0x22
	 */
	fillbytes((caddr_t)&jz, sizeof (jz), '\0');
	ret = read_dvd_structure(scgp, (caddr_t)&jz, sizeof (jz), 0, 0, 0, 0x22);
	if (ret < 0)
		return (ret);

	jump_size = a_to_u_4_byte(jz.jump_interval_size);

	if (jump_size != 0) {

#ifdef	AAAA
		dsize = roundup(tsize, 16);
		jump_lba = dsize / 2;
		jump_lba = roundup(jump_lba, 16);
/*		jump_lba -= 1;*/
		error("jump lba %d\n", jump_lba);
		i_to_4_byte(lb.jump_lba, jump_lba);
#else
		i_to_4_byte(jz.jump_interval_size, 0);
#endif
		ret = send_dvd_structure(scgp, (caddr_t)&jz, sizeof (jz), 0x22);
		if (ret < 0)
			return (ret);
	}
#endif	/* USE_STRUC_22 */

	/*
	 * Jump logical block address 0x23
	 */
	fillbytes((caddr_t)&lb, sizeof (lb), '\0');
	ret = read_dvd_structure(scgp, (caddr_t)&lb, sizeof (lb), 0, 0, 0, 0x23);
	if (ret < 0)
		return (ret);

	jump_lba = a_to_u_4_byte(lb.jump_lba);
	if (lbreak > 0 && lbreak > jump_lba) {
		errmsgno(EX_BAD, "Manual layer break %d > %u not allowed.\n",
							lbreak, jump_lba);
		return (-1);
	}
	dsize = roundup(tsize, 16);
	if (lbreak <= 0 && dsize <= (jump_lba+1)) {
		/*
		 * Allow to write DL media with less than single layer size
		 * in case of manual layer break set up.
		 */
		errmsgno(EX_BAD,
			"Layer 0 size %u is bigger than expected disk size %u.\n",
			(jump_lba+1), dsize);
		errmsgno(EX_BAD, "Use single layer medium.\n");
		return (-1);
	}
	jump_lba = dsize / 2;
	jump_lba = roundup(jump_lba, 16);
	if (lbreak > 0 && lbreak < jump_lba) {
		errmsgno(EX_BAD, "Manual layer break %d < %u not allowed.\n",
							lbreak, jump_lba);
		return (-1);
	}
	if (lbreak > 0)
		jump_lba = lbreak;
	jump_lba -= 1;
	i_to_4_byte(lb.jump_lba, jump_lba);

	ret = send_dvd_structure(scgp, (caddr_t)&lb, sizeof (lb), 0x23);
	return (ret);
}

LOCAL	char	ill_booktype[] = "reserved book type";
char	*book_types[] = {
	"DVD-ROM",
	"DVD-RAM",
	"DVD-R",
	"DVD-RW",
	"HD DVD-ROM",
	"HD DVD-RAM",
	"HD DVD-R",
	ill_booktype,
	ill_booktype,
	"DVD+RW",
	"DVD+R",
	ill_booktype,
	ill_booktype,
	"DVD+RW/DL",
	"DVD+R/DL",
	ill_booktype,
};

LOCAL	char	res_bvers[] = "reserved book version";
LOCAL char	*R_vers[] = {
	"0.9x",
	"1.0x",
	"1.1x",
	res_bvers,
	"1.9x",
	"2.0x",
	"> 2.0x",
	res_bvers,
	res_bvers,
	res_bvers,
	res_bvers,
	res_bvers,
	res_bvers,
	res_bvers,
	res_bvers,
	res_bvers,
};

LOCAL char	*RW_vers[] = {
	"0.9x",
	"1.0x",
	"1.1x",
	"> 1.1x",
	res_bvers,
	res_bvers,
	res_bvers,
	res_bvers,
	res_bvers,
	res_bvers,
	res_bvers,
	res_bvers,
	res_bvers,
	res_bvers,
	res_bvers,
	res_bvers,
};

LOCAL	char	ill_dsize[] = "illegal size";
char	*disc_sizes[] = {
	"120mm",
	"80mm",
	ill_dsize,
	ill_dsize,
	ill_dsize,
	ill_dsize,
	ill_dsize,
	ill_dsize,
	ill_dsize,
	ill_dsize,
	ill_dsize,
	ill_dsize,
	ill_dsize,
	ill_dsize,
	ill_dsize,
	ill_dsize,
};

LOCAL	char	ill_rate[] = "illegal rate";
char	*tr_rates[] = {
	"2.52 MB/s",
	"5.04 MB/s",
	"10.08 MB/s",
	"20.16 MB/s",
	"30.24 MB/s",
	ill_rate,
	ill_rate,
	ill_rate,
	ill_rate,
	ill_rate,
	ill_rate,
	ill_rate,
	ill_rate,
	ill_rate,
	ill_rate,
	"Not specified",
};

LOCAL	char	ill_layer[] = "illegal layer type";
char	*layer_types[] = {
	"Embossed Data",
	"Recordable Area",
	"Rewritable Area",
	ill_layer,
	ill_layer,
	ill_layer,
	ill_layer,
	ill_layer,
	ill_layer,
	ill_layer,
	ill_layer,
	ill_layer,
	ill_layer,
	ill_layer,
	ill_layer,
	ill_layer,
};

LOCAL	char	ill_dens[] = "illegal density";
char	*ldensities[] = {
	"0.267 µm/bit",
	"0.293 µm/bit",
	"0.409-0.435 µm/bit",
	"0.280-0.291 µm/bit",
	"0.353 µm/bit",
	ill_dens,
	ill_dens,
	ill_dens,
	ill_dens,
	ill_dens,
	ill_dens,
	ill_dens,
	ill_dens,
	ill_dens,
	ill_dens,
	ill_dens,
};

char	*tdensities[] = {
	"0.74 µm/track",
	"0.80 µm/track",
	"0.615 µm/track",
	"0.40 µm/track",
	"0.34 µm/track",
	ill_dens,
	ill_dens,
	ill_dens,
	ill_dens,
	ill_dens,
	ill_dens,
	ill_dens,
	ill_dens,
	ill_dens,
	ill_dens,
	ill_dens,
};

LOCAL void
print_dvd00(dp)
	struct dvd_structure_00 *dp;
{
	int	len = a_to_2_byte(dp->data_len)+2;
	long	lbr;
	char	*vers = "";
	char	*ext_vers = "";
	char	ev[8];
	int	evers = 0;

	ev[0] = '\0';
	if (len >= (27+4))
		evers = ((char *)dp)[27+4] & 0xFF;

	if (dp->book_type == 2) {		/* DVD-R */
		vers = R_vers[dp->book_version];
		if ((dp->book_version == 5 ||
		    dp->book_version == 6) &&
		    evers != 0) {
			snprintf(ev, sizeof (ev),
					" -> %d.%d",
					(evers >> 4) & 0x0F,
					evers & 0x0F);
			ext_vers = ev;
		}

	} else if (dp->book_type == 3) {	/* DVD-RW */
		vers = RW_vers[dp->book_version];
		if ((dp->book_version == 2 ||
		    dp->book_version == 3) &&
		    evers != 0) {
			snprintf(ev, sizeof (ev),
					" -> %d.%d",
					(evers >> 4) & 0x0F,
					evers & 0x0F);
			ext_vers = ev;
		}
	}

	printf("book type:       %s, Version %s%s%s(%d.%d)\n",
					book_types[dp->book_type],
					vers, ext_vers, *vers ? " ":"",
					dp->book_type,
					dp->book_version);
	printf("disc size:       %s (%d)\n", disc_sizes[dp->disc_size], dp->disc_size);
	printf("maximum rate:    %s (%d)\n", tr_rates[dp->maximum_rate], dp->maximum_rate);
	printf("number of layers:%d\n", dp->numlayers+1);
	printf("track path:      %s Track Path (%d)\n",
					dp->track_path?"Opposite":"Parallel",
					dp->track_path);
	printf("layer type:      %s (%d)\n", layer_types[dp->layer_type],
					dp->layer_type);
	printf("linear density:  %s (%d)\n", ldensities[dp->linear_density],
					dp->linear_density);
	printf("track density:   %s (%d)\n", tdensities[dp->track_density],
					dp->track_density);
	printf("phys start:      %ld (0x%lX) \n",
					a_to_u_3_byte(dp->phys_start),
					a_to_u_3_byte(dp->phys_start));
	printf("phys end:        %ld\n", a_to_u_3_byte(dp->phys_end));
	printf("end layer 0:     %ld\n", a_to_u_3_byte(dp->end_layer0));
	printf("bca:             %d\n", dp->bca);
	printf("phys size:...    %ld\n", a_to_u_3_byte(dp->phys_end) - a_to_u_3_byte(dp->phys_start) + 1);
	lbr = a_to_u_3_byte(dp->end_layer0) - a_to_u_3_byte(dp->phys_start) + 1;
	if (lbr > 0)
		printf("layer break at:  %ld\n", lbr);
}

LOCAL void
print_dvd01(dp)
	struct dvd_structure_01 *dp;
{
	printf("copyr prot type: %d\n", dp->copyr_prot_type);
	printf("region mgt info: %d\n", dp->region_mgt_info);
}

LOCAL void
print_dvd04(dp)
	struct dvd_structure_04 *dp;
{
	if (cmpnullbytes(dp->man_info, sizeof (dp->man_info)) <
						sizeof (dp->man_info)) {
		printf("Manufacturing info: '%.2048s'\n", dp->man_info);
	}
}

LOCAL void
print_dvd05(dp)
	struct dvd_structure_05 *dp;
{
	printf("cpm:             %d\n", dp->cpm);
	printf("cgms:            %d\n", dp->cgms);
}

LOCAL void
print_dvd0D(dp)
	struct dvd_structure_0D *dp;
{
	printf("last rma sector: %d\n", a_to_u_2_byte(dp->last_rma_sector));
}

LOCAL void
print_dvd0E(dp)
	struct dvd_structure_0E *dp;
{
	int	i;
	int	len = 44;
	int	c;
	char	*p = (char *)dp;

	if (dp->field_id != 1)
	printf("field id:        %d\n", dp->field_id);
	printf("application code:%d\n", dp->application_code);
	printf("physical code:   %d\n", dp->phys_data);
	printf("last rec address:%ld\n", a_to_u_3_byte(dp->last_recordable_addr));
	printf("part v./ext code:%X/%X\n", (Uint)(dp->res_a[0] & 0xF0) >> 4,
						dp->res_a[0] & 0xF);

	if (dp->field_id_2 != 2)
	printf("field id2:       %d\n", dp->field_id_2);
	printf("ind wr. power:   %d\n", dp->ind_wr_power);
	printf("wavelength code: %d\n", dp->ind_wavelength);
	scg_fprbytes(stdout, "write str. code:", dp->opt_wr_strategy, 4);

	if (dp->field_id_3 != 3)
	printf("field id3:       %d\n", dp->field_id_3);
	if (dp->field_id_4 != 4)
	printf("field id4:       %d\n", dp->field_id_4);

	printf("Manufacturer:   '");
	for (i = 0; i < 6; i++) {
		c = dp->man_id[i];
		if (c >= ' ' && c < 0177)
			printf("%c", c);
		else if (c != 0)
			printf(".");
	}
	for (i = 0; i < 6; i++) {
		c = dp->man_id2[i];
		if (c >= ' ' && c < 0177)
			printf("%c", c);
		else if (c != 0)
			printf(".");
	}
	printf("'\n");
	/*
	 * Next field: opt wr str. II or Manufacturer part III
	 */
/*	scg_prbytes("write str. code:", dp->opt_wr_strategy, 4);*/

	if (lverbose <= 1)
		return;
	printf("Prerecorded info   : ");
	for (i = 0; i < len; i++) {
		c = p[i];
		if (c >= ' ' && c < 0177)
			printf("%c", c);
		else
			printf(".");
	}
	printf("\n");
}

LOCAL void
print_dvd0F(dp)
	struct dvd_structure_0F *dp;
{
	printf("random:          %d\n", a_to_u_2_byte(dp->random));
	printf("year:            %.4s\n", dp->year);
	printf("month:           %.2s\n", dp->month);
	printf("day:             %.2s\n", dp->day);
	printf("hour:            %.2s\n", dp->hour);
	printf("minute:          %.2s\n", dp->minute);
	printf("second:          %.2s\n", dp->second);
}


LOCAL void
send_dvd0F(scgp)
	SCSI	*scgp;
{
	struct dvd_structure_0F_w d;

	strncpy((char *)d.year,		"1998", 4);
	strncpy((char *)d.month,	"05", 2);
	strncpy((char *)d.day,		"12", 2);
	strncpy((char *)d.hour,		"22", 2);
	strncpy((char *)d.minute,	"59", 2);
	strncpy((char *)d.second,	"00", 2);
/*	send_dvd_structure(scgp, (caddr_t)&d, sizeof (d));*/
}

LOCAL void
print_dvd20(dp)
	struct dvd_structure_20 *dp;
{
	printf("L0 init status:  %d\n", dp->res47[0] & 0x80 ? 1 : 0);
	printf("L0 data areacap: %ld\n", a_to_u_4_byte(dp->l0_area_cap));
}

LOCAL void
print_dvd22(dp)
	struct dvd_structure_22 *dp;
{
	printf("Jump intervalsz: %ld\n", a_to_u_4_byte(dp->jump_interval_size));
}

LOCAL void
print_dvd23(dp)
	struct dvd_structure_23 *dp;
{
	printf("Jump LBA:        %ld\n", a_to_u_4_byte(dp->jump_lba));
}


#include "adip.h"
/*LOCAL void*/
EXPORT void
print_dvd_info(scgp)
	SCSI	*scgp;
{
	Uchar	mode[4096];
	int	ret;
	int	i;

	if (lverbose > 2)
		printf("Enterning DVD info....\n");
	/*
	 * The ACARD TECH AEC-7720 ATAPI<->SCSI adaptor
	 * chokes if we try to transfer odd byte counts (rounds up to
	 * even byte counts and thus causes a DMA overflow and a
	 * bus reset), so make the byte count even.
	 * It also chokes if we try to transfer more than 0x40 bytes with
	 * mode_sense of all pages. As we don't want to avoid this
	 * command here, we need to wait until the drive recoveres
	 * from the reset?? it receives after the adapter dies.
	 */
/*	mode_sense(scgp, mode, 255, 0x3F, 0);*/
/*	if (lverbose > 2)*/
/*		scg_prbytes("Mode: ", mode, 255 - scg_getresid(scgp));*/

	if (lverbose > 1)
		mode_sense(scgp, mode, 250, 0x3F, 0);
	if (lverbose > 2)
		scg_prbytes("Mode: ", mode, 250 - scg_getresid(scgp));
	wait_unit_ready(scgp, 120);
	if (lverbose > 1) {
		printf("Supported DVD (readable) structures:");
		scgp->silent++;
		for (i = 0; i <= 255; i++) {
			fillbytes((caddr_t)mode, sizeof (mode), '\0');
			ret = read_dvd_structure(scgp, (caddr_t)mode, sizeof (mode), 0, 0, 0, i);
			if (ret >= 0 && (sizeof (mode) - scg_getresid(scgp)) > 4)
				printf(" %02X", i);
		}
		scgp->silent--;
		printf("\n");
/*		printf("Page: %d ret: %d len: %d\n", i, ret, sizeof (mode) - scg_getresid(scgp));*/
		if (lverbose > 2)
			scg_prbytes("Page FF: ", mode, sizeof (mode) - scg_getresid(scgp));
		if (sizeof (mode) - scg_getresid(scgp) > 4) {
			int	len = a_to_u_2_byte(mode) - 2;
			Uchar	*p = &mode[4];
			int	m;

			len /= 4;
			for (i = 0; i < len; i++) {
				m = p[1] & 0xC0;
				printf("Page %02X %s  (%02X) len %d\n",
					*p & 0xFF,
					m == 0xC0 ?
					"read/write" :
					(m == 0x80 ? "     write" :
					(m == 0x40 ? "read      " : "unknown   ")),
					p[1] & 0xFF,
					a_to_u_2_byte(&p[2]));
				p += 4;
			}
		}
	}
	wait_unit_ready(scgp, 120);

	/*
	 * Physical Format information 0x00
	 */
	fillbytes((caddr_t)mode, sizeof (mode), '\0');
	scgp->silent++;
	ret = read_dvd_structure(scgp, (caddr_t)mode, sizeof (mode), 0, 0, 0, 0);
	scgp->silent--;
	if (ret >= 0) {
		if (lverbose > 2) {
			scg_prbytes("DVD structure[0]: ",
				mode, sizeof (mode) - scg_getresid(scgp));
/*			scg_prascii("DVD structure[0]: ", mode, sizeof (mode) - scg_getresid(scgp));*/
		}
		print_dvd00((struct dvd_structure_00 *)mode);
		ret = get_curprofile(scgp);
		if (ret == 0x001A || ret == 0x001B) {
			/*profile >= 0x0018 && profile < 0x0020*/
			printf("Manufacturer:    '%.8s'\n", &mode[23]);
			printf("Media type:      '%.3s'\n", &mode[23+8]);
		}
	}

	/*
	 * ADIP information 0x11
	 */
	fillbytes((caddr_t)mode, sizeof (mode), '\0');
	scgp->silent++;
	ret = read_dvd_structure(scgp, (caddr_t)mode, sizeof (mode), 0, 0, 0, 0x11);
	scgp->silent--;
	if (ret >= 0) {
		adip_t	*adp;
		if (lverbose > 2) {
			scg_prbytes("DVD structure[11]: ",
				mode, sizeof (mode) - scg_getresid(scgp));
			scg_prascii("DVD structure[11]: ",
				mode, sizeof (mode) - scg_getresid(scgp));
		}
/*		print_dvd0F((struct dvd_structure_0F *)mode);*/
		adp = (adip_t *)&mode[4];
#ifndef	offsetof
#define	offsetof(TYPE, MEMBER)  ((size_t) &((TYPE *)0)->MEMBER)
#endif
/*		printf("size %d %d\n", sizeof (adip_t), offsetof(adip_t, res_controldat));*/
		printf("Category/Version	%02X\n", adp->cat_vers);
		printf("Disk size		%02X\n", adp->disk_size);
		printf("Disk structure		%02X\n", adp->disk_struct);
		printf("Recoding density	%02X\n", adp->density);

		printf("Manufacturer:		'%.8s'\n", adp->man_id);
		printf("Media type:		'%.3s'\n", adp->media_id);
		printf("Product revision	%u\n", adp->prod_revision);
		printf("ADIP numbytes		%u\n", adp->adip_numbytes);
		printf("Reference speed		%u\n", adp->ref_speed);
		printf("Max speed		%u\n", adp->max_speed);
	}

	/*
	 * Layer boundary information 0x20
	 */
	fillbytes((caddr_t)mode, sizeof (mode), '\0');
	scgp->silent++;
	ret = read_dvd_structure(scgp, (caddr_t)mode, sizeof (mode), 0, 0, 0, 0x20);
	scgp->silent--;
	if (ret >= 0) {
		if (lverbose > 2) {
			scg_prbytes("DVD structure[20]: ",
				mode, sizeof (mode) - scg_getresid(scgp));
			scg_prascii("DVD structure[20]: ",
				mode, sizeof (mode) - scg_getresid(scgp));
		}
		print_dvd20((struct dvd_structure_20 *)mode);
	}

	/*
	 * Jump interval size 0x22
	 */
	fillbytes((caddr_t)mode, sizeof (mode), '\0');
	scgp->silent++;
	ret = read_dvd_structure(scgp, (caddr_t)mode, sizeof (mode), 0, 0, 0, 0x22);
	scgp->silent--;
	if (ret >= 0) {
		if (lverbose > 2) {
			scg_prbytes("DVD structure[22]: ",
				mode, sizeof (mode) - scg_getresid(scgp));
			scg_prascii("DVD structure[22]: ",
				mode, sizeof (mode) - scg_getresid(scgp));
		}
		print_dvd22((struct dvd_structure_22 *)mode);
	}

	/*
	 * Jump logical block address 0x23
	 */
	fillbytes((caddr_t)mode, sizeof (mode), '\0');
	scgp->silent++;
	ret = read_dvd_structure(scgp, (caddr_t)mode, sizeof (mode), 0, 0, 0, 0x23);
	scgp->silent--;
	if (ret >= 0) {
		if (lverbose > 2) {
			scg_prbytes("DVD structure[23]: ",
				mode, sizeof (mode) - scg_getresid(scgp));
			scg_prascii("DVD structure[23]: ",
				mode, sizeof (mode) - scg_getresid(scgp));
		}
		print_dvd23((struct dvd_structure_23 *)mode);
	}

	/*
	 * Copyright information 0x01
	 */
	fillbytes((caddr_t)mode, sizeof (mode), '\0');
	scgp->silent++;
	ret = read_dvd_structure(scgp, (caddr_t)mode, sizeof (mode), 0, 0, 0, 1);
	scgp->silent--;
	if (ret >= 0) {
		if (lverbose > 2) {
			scg_prbytes("DVD structure[1]: ",
				mode, sizeof (mode) - scg_getresid(scgp));
		}
		print_dvd01((struct dvd_structure_01 *)mode);
	}

	/*
	 * Manufacturer information 0x04
	 */
	fillbytes((caddr_t)mode, sizeof (mode), '\0');
	scgp->silent++;
	ret = read_dvd_structure(scgp, (caddr_t)mode, sizeof (mode), 0, 0, 0, 4);
	scgp->silent--;
	if (ret >= 0) {
		if (lverbose > 2) {
			scg_prbytes("DVD structure[4]: ",
				mode, sizeof (mode) - scg_getresid(scgp));
		}
		print_dvd04((struct dvd_structure_04 *)mode);
	}

	/*
	 * Copyright management information 0x05
	 */
	fillbytes((caddr_t)mode, sizeof (mode), '\0');
	scgp->silent++;
	ret = read_dvd_structure(scgp, (caddr_t)mode, sizeof (mode), 0, 0, 0, 5);
	scgp->silent--;
	if (ret >= 0) {
		if (lverbose > 2) {
			scg_prbytes("DVD structure[5]: ",
				mode, sizeof (mode) - scg_getresid(scgp));
		}
		print_dvd05((struct dvd_structure_05 *)mode);
	}

	/*
	 * Recording Management Area Data information 0x0D
	 */
	fillbytes((caddr_t)mode, sizeof (mode), '\0');
	scgp->silent++;
	ret = read_dvd_structure(scgp, (caddr_t)mode, sizeof (mode), 0, 0, 0, 0xD);
	scgp->silent--;
	if (ret >= 0) {
		if (lverbose > 2) {
			scg_prbytes("DVD structure[D]: ",
				mode, sizeof (mode) - scg_getresid(scgp));
		}
		print_dvd0D((struct dvd_structure_0D *)mode);
	}

	/*
	 * Prerecorded information 0x0E
	 */
	fillbytes((caddr_t)mode, sizeof (mode), '\0');
	scgp->silent++;
	ret = read_dvd_structure(scgp, (caddr_t)mode, sizeof (mode), 0, 0, 0, 0xE);
	scgp->silent--;
	if (ret >= 0) {
		if (lverbose > 2) {
			scg_prbytes("DVD structure[E]: ",
				mode, sizeof (mode) - scg_getresid(scgp));
		}
		print_dvd0E((struct dvd_structure_0E *)mode);
	}

	if (lverbose <= 1)
		return;

	/*
	 * Unique Disk identifier 0x0F
	 */
/*	send_dvd0F();*/
	fillbytes((caddr_t)mode, sizeof (mode), '\0');
	scgp->silent++;
	ret = read_dvd_structure(scgp, (caddr_t)mode, sizeof (mode), 0, 0, 0, 0xF);
	scgp->silent--;
	if (ret >= 0) {
		if (lverbose > 2) {
			scg_prbytes("DVD structure[F]: ",
				mode, sizeof (mode) - scg_getresid(scgp));
		}
		print_dvd0F((struct dvd_structure_0F *)mode);
	}

	fillbytes((caddr_t)mode, sizeof (mode), '\0');
	read_rzone_info(scgp, (caddr_t)mode, sizeof (mode));
	if (lverbose > 2)
		scg_prbytes("Rzone info: ", mode, sizeof (mode) - scg_getresid(scgp));
	przone((struct rzone_info *)mode);

	scgp->verbose++;
	log_sense(scgp, (caddr_t)mode, 255, 0x3, 1, 0);
	log_sense(scgp, (caddr_t)mode, 255, 0x31, 1, 0);
	scgp->verbose--;

	if (lverbose > 2)
		printf("Leaving DVD info.\n");
}

LOCAL void
print_laserlog(scgp)
	SCSI	*scgp;
{
	Uchar	log[256];
	Uchar	*p;
	int	len = sizeof (log);
	long	val;

	if (!has_log_page(scgp, 0x30, LOG_CUMUL))
		return;

	p = log + sizeof (scsi_log_hdr);

	/*
	 * This is Pioneer specific so other drives will not have it.
	 * Not all values may be available with newer Pioneer drives.
	 */
	scgp->silent++;
	fillbytes((caddr_t)log, sizeof (log), '\0');
	if (get_log(scgp, (caddr_t)log, &len, 0x30, LOG_CUMUL, 0) < 0) {
		scgp->silent--;
		return;
	}
	scgp->silent--;

	val = a_to_u_4_byte(((struct pioneer_logpage_30_0 *)p)->total_poh);
	if (((struct scsi_logp_header *)log)->p_len > 0)
		printf("Total power on  hours: %ld\n", val);

	scgp->silent++;
	fillbytes((caddr_t)log, sizeof (log), '\0');
	if (get_log(scgp, (caddr_t)log, &len, 0x30, LOG_CUMUL, 1) < 0) {
		scgp->silent--;
		return;
	}
	scgp->silent--;

	val = a_to_u_4_byte(((struct pioneer_logpage_30_1 *)p)->laser_poh);
	if (((struct scsi_logp_header *)log)->p_len > 0)
		printf("Total laser on  hours: %ld\n", val);

	scgp->silent++;
	fillbytes((caddr_t)log, sizeof (log), '\0');
	if (get_log(scgp, (caddr_t)log, &len, 0x30, LOG_CUMUL, 2) < 0) {
		scgp->silent--;
		return;
	}
	scgp->silent--;

	val = a_to_u_4_byte(((struct pioneer_logpage_30_2 *)p)->record_poh);
	if (((struct scsi_logp_header *)log)->p_len > 0)
		printf("Total recording hours: %ld\n", val);
}
