xref: /onnv-gate/usr/src/cmd/cdrw/misc_scsi.c (revision 5426:483ab0686314)
10Sstevel@tonic-gate /*
20Sstevel@tonic-gate  * CDDL HEADER START
30Sstevel@tonic-gate  *
40Sstevel@tonic-gate  * The contents of this file are subject to the terms of the
52925Smikeri  * Common Development and Distribution License (the "License").
62925Smikeri  * You may not use this file except in compliance with the License.
70Sstevel@tonic-gate  *
80Sstevel@tonic-gate  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
90Sstevel@tonic-gate  * or http://www.opensolaris.org/os/licensing.
100Sstevel@tonic-gate  * See the License for the specific language governing permissions
110Sstevel@tonic-gate  * and limitations under the License.
120Sstevel@tonic-gate  *
130Sstevel@tonic-gate  * When distributing Covered Code, include this CDDL HEADER in each
140Sstevel@tonic-gate  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
150Sstevel@tonic-gate  * If applicable, add the following below this CDDL HEADER, with the
160Sstevel@tonic-gate  * fields enclosed by brackets "[]" replaced with your own identifying
170Sstevel@tonic-gate  * information: Portions Copyright [yyyy] [name of copyright owner]
180Sstevel@tonic-gate  *
190Sstevel@tonic-gate  * CDDL HEADER END
200Sstevel@tonic-gate  */
210Sstevel@tonic-gate /*
224281Sec158148  * Copyright 2007 Sun Microsystems, Inc.  All rights reserved.
230Sstevel@tonic-gate  * Use is subject to license terms.
240Sstevel@tonic-gate  */
250Sstevel@tonic-gate 
260Sstevel@tonic-gate #pragma ident	"%Z%%M%	%I%	%E% SMI"
270Sstevel@tonic-gate 
280Sstevel@tonic-gate #include <sys/types.h>
290Sstevel@tonic-gate #include <stdlib.h>
300Sstevel@tonic-gate #include <string.h>
310Sstevel@tonic-gate #include <stdio.h>
320Sstevel@tonic-gate #include <sys/dkio.h>
330Sstevel@tonic-gate #include <unistd.h>
340Sstevel@tonic-gate #include <errno.h>
350Sstevel@tonic-gate #include <libintl.h>
360Sstevel@tonic-gate #include <sys/time.h>
370Sstevel@tonic-gate 
380Sstevel@tonic-gate #include "mmc.h"
390Sstevel@tonic-gate #include "util.h"
400Sstevel@tonic-gate #include "misc_scsi.h"
410Sstevel@tonic-gate #include "transport.h"
420Sstevel@tonic-gate #include "main.h"
430Sstevel@tonic-gate #include "toshiba.h"
440Sstevel@tonic-gate #include "msgs.h"
45936Sarutz #include "device.h"
460Sstevel@tonic-gate 
474281Sec158148 static int check_track_size(cd_device *dev, int trk_num,
484281Sec158148     struct track_info *tip);
494281Sec158148 static int rtoc_get_trk_sess_num(uchar_t *rtoc, size_t rtoc_len, int trk_num,
504281Sec158148     int *sess_nump);
514281Sec158148 static int rtoc_get_sess_last_trk_num(uchar_t *rtoc, size_t rtoc_len,
524281Sec158148     int sess_num, int *last_trk_nump);
534281Sec158148 static int rtoc_get_sess_leadout_lba(uchar_t *rtoc, size_t rtoc_len,
544281Sec158148     int sess_num, uint32_t *leadout_lba);
554281Sec158148 static rtoc_td_t *get_rtoc_td(rtoc_td_t *begin_tdp, rtoc_td_t *end_tdp,
564281Sec158148     uchar_t adr, uchar_t point);
574281Sec158148 
580Sstevel@tonic-gate uint32_t
read_scsi32(void * addr)590Sstevel@tonic-gate read_scsi32(void *addr)
600Sstevel@tonic-gate {
610Sstevel@tonic-gate 	uchar_t *ad = (uchar_t *)addr;
620Sstevel@tonic-gate 	uint32_t ret;
630Sstevel@tonic-gate 
640Sstevel@tonic-gate 	ret = ((((uint32_t)ad[0]) << 24) | (((uint32_t)ad[1]) << 16) |
650Sstevel@tonic-gate 	    (((uint32_t)ad[2]) << 8) | ad[3]);
660Sstevel@tonic-gate 	return (ret);
670Sstevel@tonic-gate }
680Sstevel@tonic-gate 
690Sstevel@tonic-gate uint16_t
read_scsi16(void * addr)700Sstevel@tonic-gate read_scsi16(void *addr)
710Sstevel@tonic-gate {
720Sstevel@tonic-gate 	uchar_t *ad = (uchar_t *)addr;
730Sstevel@tonic-gate 	uint16_t ret;
740Sstevel@tonic-gate 
750Sstevel@tonic-gate 	ret = ((((uint16_t)ad[0]) << 8) | ad[1]);
760Sstevel@tonic-gate 	return (ret);
770Sstevel@tonic-gate }
780Sstevel@tonic-gate 
790Sstevel@tonic-gate void
load_scsi32(void * addr,uint32_t v)800Sstevel@tonic-gate load_scsi32(void *addr, uint32_t v)
810Sstevel@tonic-gate {
820Sstevel@tonic-gate 	uchar_t *ad = (uchar_t *)addr;
830Sstevel@tonic-gate 
840Sstevel@tonic-gate 	ad[0] = (uchar_t)(v >> 24);
850Sstevel@tonic-gate 	ad[1] = (uchar_t)(v >> 16);
860Sstevel@tonic-gate 	ad[2] = (uchar_t)(v >> 8);
870Sstevel@tonic-gate 	ad[3] = (uchar_t)v;
880Sstevel@tonic-gate }
890Sstevel@tonic-gate 
900Sstevel@tonic-gate void
load_scsi16(void * addr,uint16_t v)910Sstevel@tonic-gate load_scsi16(void *addr, uint16_t v)
920Sstevel@tonic-gate {
930Sstevel@tonic-gate 	uchar_t *ad = (uchar_t *)addr;
940Sstevel@tonic-gate 	ad[0] = (uchar_t)(v >> 8);
950Sstevel@tonic-gate 	ad[1] = (uchar_t)v;
960Sstevel@tonic-gate }
970Sstevel@tonic-gate /*
980Sstevel@tonic-gate  * will get the mode page only i.e. will strip off the header.
990Sstevel@tonic-gate  */
1000Sstevel@tonic-gate int
get_mode_page(int fd,int page_no,int pc,int buf_len,uchar_t * buffer)1010Sstevel@tonic-gate get_mode_page(int fd, int page_no, int pc, int buf_len, uchar_t *buffer)
1020Sstevel@tonic-gate {
1030Sstevel@tonic-gate 	int ret;
1040Sstevel@tonic-gate 	uchar_t byte2, *buf;
1050Sstevel@tonic-gate 	uint_t header_len, page_len, copy_cnt;
1060Sstevel@tonic-gate 
1070Sstevel@tonic-gate 	byte2 = (uchar_t)(((pc << 6) & 0xC0) | (page_no & 0x3f));
1080Sstevel@tonic-gate 	buf = (uchar_t *)my_zalloc(256);
1090Sstevel@tonic-gate 
1100Sstevel@tonic-gate 	/* Ask 254 bytes only to make our IDE driver happy */
1110Sstevel@tonic-gate 	ret = mode_sense(fd, byte2, 1, 254, buf);
1120Sstevel@tonic-gate 	if (ret == 0) {
1130Sstevel@tonic-gate 		free(buf);
1140Sstevel@tonic-gate 		return (0);
1150Sstevel@tonic-gate 	}
1160Sstevel@tonic-gate 
1170Sstevel@tonic-gate 	header_len = 8 + read_scsi16(&buf[6]);
1180Sstevel@tonic-gate 	page_len = buf[header_len + 1] + 2;
1190Sstevel@tonic-gate 
1200Sstevel@tonic-gate 	copy_cnt = (page_len > buf_len) ? buf_len : page_len;
1210Sstevel@tonic-gate 	(void) memcpy(buffer, &buf[header_len], copy_cnt);
1220Sstevel@tonic-gate 	free(buf);
1230Sstevel@tonic-gate 
1240Sstevel@tonic-gate 	return (1);
1250Sstevel@tonic-gate }
1260Sstevel@tonic-gate 
1270Sstevel@tonic-gate /*
1280Sstevel@tonic-gate  * will take care of adding mode header and any extra bytes at the end.
1290Sstevel@tonic-gate  */
1300Sstevel@tonic-gate int
set_mode_page(int fd,uchar_t * buffer)1310Sstevel@tonic-gate set_mode_page(int fd, uchar_t *buffer)
1320Sstevel@tonic-gate {
1330Sstevel@tonic-gate 	int ret;
1340Sstevel@tonic-gate 	uchar_t *buf;
1350Sstevel@tonic-gate 	uint_t total, p_len;
1360Sstevel@tonic-gate 
1370Sstevel@tonic-gate 	p_len = buffer[1] + 2;
1380Sstevel@tonic-gate 	total = p_len + 8;
1390Sstevel@tonic-gate 	buf = (uchar_t *)my_zalloc(total);
1400Sstevel@tonic-gate 
1410Sstevel@tonic-gate 	(void) memcpy(&buf[8], buffer, p_len);
1420Sstevel@tonic-gate 	if (debug) {
1430Sstevel@tonic-gate 		int i;
1440Sstevel@tonic-gate 
1450Sstevel@tonic-gate 		(void) printf("MODE: [");
1460Sstevel@tonic-gate 		for (i = 0; i < p_len; i++) {
1470Sstevel@tonic-gate 			(void) printf("0x%02x ", (uchar_t)buffer[i]);
1480Sstevel@tonic-gate 		}
1490Sstevel@tonic-gate 
1500Sstevel@tonic-gate 		(void) printf("]\n");
1510Sstevel@tonic-gate 	}
1520Sstevel@tonic-gate 	ret = mode_select(fd, total, buf);
1530Sstevel@tonic-gate 	free(buf);
1540Sstevel@tonic-gate 
1550Sstevel@tonic-gate 	return (ret);
1560Sstevel@tonic-gate }
1570Sstevel@tonic-gate 
1580Sstevel@tonic-gate /*
1590Sstevel@tonic-gate  * Builds track information database for track trackno. If trackno is
1600Sstevel@tonic-gate  * -1, builds the database for next blank track.
1610Sstevel@tonic-gate  */
1620Sstevel@tonic-gate int
build_track_info(cd_device * dev,int trackno,struct track_info * t_info)1630Sstevel@tonic-gate build_track_info(cd_device *dev, int trackno, struct track_info *t_info)
1640Sstevel@tonic-gate {
1650Sstevel@tonic-gate 	uchar_t *ti;
1660Sstevel@tonic-gate 	uchar_t toc[20];		/* 2 entries + 4 byte header */
1670Sstevel@tonic-gate 	int ret;
1680Sstevel@tonic-gate 
1690Sstevel@tonic-gate 	(void) memset(t_info, 0, sizeof (*t_info));
1700Sstevel@tonic-gate 	/* 1st try READ TRACK INFORMATION */
1710Sstevel@tonic-gate 	ti = (uchar_t *)my_zalloc(TRACK_INFO_SIZE);
1720Sstevel@tonic-gate 	t_info->ti_track_no = trackno;
1730Sstevel@tonic-gate 
1740Sstevel@tonic-gate 	/* Gererate faked information for writing to DVD */
1750Sstevel@tonic-gate 	if (device_type != CD_RW) {
1760Sstevel@tonic-gate 		uint_t bsize;
1770Sstevel@tonic-gate 
1780Sstevel@tonic-gate 		t_info->ti_flags = 0x3000;
1790Sstevel@tonic-gate 		t_info->ti_track_no = 1;
1800Sstevel@tonic-gate 		t_info->ti_session_no = 1;
1810Sstevel@tonic-gate 		t_info->ti_track_mode = 0x4;
1820Sstevel@tonic-gate 		t_info->ti_data_mode = 1;
1830Sstevel@tonic-gate 		t_info->ti_start_address = 0;
1840Sstevel@tonic-gate 
1850Sstevel@tonic-gate 		/* only 1 track on DVD make it max size */
1860Sstevel@tonic-gate 		t_info->ti_track_size = read_format_capacity(target->d_fd,
1870Sstevel@tonic-gate 		    &bsize);
1880Sstevel@tonic-gate 		if (t_info->ti_track_size < MAX_CD_BLKS) {
1890Sstevel@tonic-gate 			t_info->ti_track_size = MAX_DVD_BLKS;
1900Sstevel@tonic-gate 		}
1910Sstevel@tonic-gate 
1920Sstevel@tonic-gate 		t_info->ti_nwa = 0;
1930Sstevel@tonic-gate 		t_info->ti_lra = 0;
1940Sstevel@tonic-gate 		t_info->ti_packet_size = 0x10;
1950Sstevel@tonic-gate 		t_info->ti_free_blocks = 0;
1960Sstevel@tonic-gate 	}
1970Sstevel@tonic-gate 
1980Sstevel@tonic-gate 	if (read_track_info(dev->d_fd, trackno, ti)) {
1990Sstevel@tonic-gate 
2000Sstevel@tonic-gate 		if (debug)
2010Sstevel@tonic-gate 			(void) printf("using read_track_info for TOC \n");
2020Sstevel@tonic-gate 
2030Sstevel@tonic-gate 		t_info->ti_track_no = ti[2];
2040Sstevel@tonic-gate 		t_info->ti_session_no = ti[3];
2050Sstevel@tonic-gate 		t_info->ti_flags = (ti[6] >> 4) & 0xf;
2060Sstevel@tonic-gate 		t_info->ti_flags |= (uint32_t)(ti[5] & 0xf0);
2070Sstevel@tonic-gate 		t_info->ti_flags |= (uint32_t)(ti[7]) << 8;
2080Sstevel@tonic-gate 		t_info->ti_flags |= TI_SESSION_NO_VALID | TI_FREE_BLOCKS_VALID;
2090Sstevel@tonic-gate 		t_info->ti_track_mode = ti[5] & 0xf;
2100Sstevel@tonic-gate 		if ((ti[6] & 0xf) == 0xf)
2110Sstevel@tonic-gate 			t_info->ti_data_mode = 0xff;
2120Sstevel@tonic-gate 		else
2130Sstevel@tonic-gate 			t_info->ti_data_mode = ti[6] & 0xf;
2140Sstevel@tonic-gate 		t_info->ti_start_address = read_scsi32(&ti[8]);
2150Sstevel@tonic-gate 		t_info->ti_nwa = read_scsi32(&ti[12]);
2160Sstevel@tonic-gate 		t_info->ti_free_blocks = read_scsi32(&ti[16]);
2170Sstevel@tonic-gate 		t_info->ti_packet_size = read_scsi32(&ti[20]);
2180Sstevel@tonic-gate 		t_info->ti_track_size = read_scsi32(&ti[24]);
2190Sstevel@tonic-gate 		t_info->ti_lra = read_scsi32(&ti[28]);
2200Sstevel@tonic-gate 		free(ti);
2210Sstevel@tonic-gate 		return (1);
2220Sstevel@tonic-gate 	}
2230Sstevel@tonic-gate 	/* READ TRACK INFORMATION not supported, try other options */
2240Sstevel@tonic-gate 	free(ti);
2250Sstevel@tonic-gate 	/*
2260Sstevel@tonic-gate 	 * We can get info for next blank track if READ TRACK INFO is not
2270Sstevel@tonic-gate 	 * supported.
2280Sstevel@tonic-gate 	 */
2290Sstevel@tonic-gate 	if (trackno == -1)
2300Sstevel@tonic-gate 		return (0);
2310Sstevel@tonic-gate 
2320Sstevel@tonic-gate 	if (debug)
2330Sstevel@tonic-gate 		(void) printf("using READ_TOC for TOC\n");
2340Sstevel@tonic-gate 
2350Sstevel@tonic-gate 	/* Try Read TOC */
2360Sstevel@tonic-gate 	if (!read_toc(dev->d_fd, 0, trackno, 20, toc)) {
2370Sstevel@tonic-gate 		return (0);
2380Sstevel@tonic-gate 	}
2390Sstevel@tonic-gate 	t_info->ti_start_address = read_scsi32(&toc[8]);
2400Sstevel@tonic-gate 	t_info->ti_track_mode = toc[5] & 0xf;
2410Sstevel@tonic-gate 	t_info->ti_track_size = read_scsi32(&toc[16]) - read_scsi32(&toc[8]);
2420Sstevel@tonic-gate 	t_info->ti_data_mode = get_data_mode(dev->d_fd, read_scsi32(&toc[8]));
2430Sstevel@tonic-gate 
2440Sstevel@tonic-gate 	/* Numbers for audio tracks are always in 2K chunks */
2450Sstevel@tonic-gate 	if ((dev->d_blksize == 512) && ((t_info->ti_track_mode & 4) == 0)) {
2460Sstevel@tonic-gate 		t_info->ti_start_address /= 4;
2470Sstevel@tonic-gate 		t_info->ti_track_size /= 4;
2480Sstevel@tonic-gate 	}
2490Sstevel@tonic-gate 
2500Sstevel@tonic-gate 	/* Now find out the session thing */
2510Sstevel@tonic-gate 	ret = read_toc(dev->d_fd, 1, trackno, 12, toc);
2520Sstevel@tonic-gate 
2530Sstevel@tonic-gate 	/*
2540Sstevel@tonic-gate 	 * Make sure that the call succeeds and returns the requested
2550Sstevel@tonic-gate 	 * TOC size correctly.
2560Sstevel@tonic-gate 	 */
2570Sstevel@tonic-gate 
2580Sstevel@tonic-gate 	if ((ret == 0) || (toc[1] != 0x0a)) {
2590Sstevel@tonic-gate 
2600Sstevel@tonic-gate 		/* For ATAPI drives or old Toshiba drives */
2610Sstevel@tonic-gate 		ret = read_toc_as_per_8020(dev->d_fd, 1, trackno, 12, toc);
2620Sstevel@tonic-gate 	}
2630Sstevel@tonic-gate 	/* If this goes through well TOC length will always be 0x0a */
2640Sstevel@tonic-gate 	if (ret && (toc[1] == 0x0a)) {
2650Sstevel@tonic-gate 		if (trackno >= toc[6]) {
2660Sstevel@tonic-gate 			t_info->ti_session_no = toc[3];
2670Sstevel@tonic-gate 			t_info->ti_flags |= TI_SESSION_NO_VALID;
2680Sstevel@tonic-gate 		}
2690Sstevel@tonic-gate 		/*
2700Sstevel@tonic-gate 		 * This might be the last track of this session. If so,
2710Sstevel@tonic-gate 		 * exclude the leadout and next lead in.
2720Sstevel@tonic-gate 		 */
2730Sstevel@tonic-gate 		if (trackno == (toc[6] - 1)) {
2740Sstevel@tonic-gate 			/*
2750Sstevel@tonic-gate 			 * 1.5 Min leadout + 1 min. leadin + 2 sec. pre-gap.
2760Sstevel@tonic-gate 			 * For 2nd+ leadout it will be 0.5 min. But currently
2770Sstevel@tonic-gate 			 * there is no direct way. And it will not happen
2780Sstevel@tonic-gate 			 * for any normal case.
2790Sstevel@tonic-gate 			 *
2800Sstevel@tonic-gate 			 * 75 frames/sec, 60 sec/min, so leadin gap is
2810Sstevel@tonic-gate 			 * ((1.5 +1)*60 + 2)*75 = 11400 frames (blocks)
2820Sstevel@tonic-gate 			 */
2830Sstevel@tonic-gate 			t_info->ti_track_size -= 11400;
2840Sstevel@tonic-gate 		}
2854281Sec158148 	} else {
2864281Sec158148 		if (check_track_size(dev, trackno, t_info) != 1)
2874281Sec158148 			return (0);
2884281Sec158148 	}
2894281Sec158148 
2904281Sec158148 	return (1);
2914281Sec158148 }
2924281Sec158148 
2934281Sec158148 /*
2944281Sec158148  * The size of the last track in one of the first N - 1 sessions of an
2954281Sec158148  * N-session (N > 1) disc is reported incorrectly by some drives and calculated
2964281Sec158148  * incorrectly for others, because a pre-gap/lead-out/lead-in section that ends
2974281Sec158148  * a session is erroneously considered part of that track. This function checks
2984281Sec158148  * for this corner case, and adjusts the track size if necessary.
2994281Sec158148  */
3004281Sec158148 static int
check_track_size(cd_device * dev,int trk_num,struct track_info * tip)3014281Sec158148 check_track_size(cd_device *dev, int trk_num, struct track_info *tip)
3024281Sec158148 {
3034281Sec158148 	size_t raw_toc_len;
3044281Sec158148 	uchar_t *raw_toc;
3054281Sec158148 	rtoc_hdr_t hdr;
3064281Sec158148 	uint32_t sess_leadout_lba;
3074281Sec158148 	int sess_last_trk_num;
3084281Sec158148 	int trk_sess_num;
3094281Sec158148 	uint32_t trk_size;
3104281Sec158148 
3114281Sec158148 	/* Request Raw TOC Header for session count. */
3124281Sec158148 	if (read_toc(dev->d_fd, FORMAT_RAW_TOC, 1,
3134281Sec158148 	    sizeof (rtoc_hdr_t), (uchar_t *)&hdr) != 1)
3144281Sec158148 		return (0);
3154281Sec158148 
3164281Sec158148 	/* Is this a multi-session medium? */
3174281Sec158148 	if (hdr.rh_last_sess_num > hdr.rh_first_sess_num) {
3184281Sec158148 		/* Yes; request entire Raw TOC. */
3194281Sec158148 		raw_toc_len = read_scsi16(&hdr.rh_data_len1) + RTOC_DATA_LEN_SZ;
3204281Sec158148 		raw_toc = (uchar_t *)my_zalloc(raw_toc_len);
3214281Sec158148 
3224281Sec158148 		if (read_toc(dev->d_fd, FORMAT_RAW_TOC, 1, raw_toc_len, raw_toc)
3234281Sec158148 		    != 1)
3244281Sec158148 			goto fail;
3254281Sec158148 
3264281Sec158148 		if (rtoc_get_trk_sess_num(raw_toc, raw_toc_len, trk_num,
3274281Sec158148 		    &trk_sess_num) != 1)
3284281Sec158148 			goto fail;
3294281Sec158148 
3304281Sec158148 		tip->ti_session_no = trk_sess_num;
3314281Sec158148 		tip->ti_flags |= TI_SESSION_NO_VALID;
3324281Sec158148 
3334281Sec158148 		/* Is the track in one of the first N - 1 sessions? */
3344281Sec158148 		if (trk_sess_num < hdr.rh_last_sess_num) {
3354281Sec158148 			if (rtoc_get_sess_last_trk_num(raw_toc, raw_toc_len,
3364281Sec158148 			    trk_sess_num, &sess_last_trk_num) != 1)
3374281Sec158148 				goto fail;
3384281Sec158148 
3394281Sec158148 			/* Is the track the last track in the session? */
3404281Sec158148 			if (trk_num == sess_last_trk_num) {
3414281Sec158148 				if (rtoc_get_sess_leadout_lba(raw_toc,
3424281Sec158148 				    raw_toc_len, trk_sess_num,
3434281Sec158148 				    &sess_leadout_lba) != 1)
3444281Sec158148 					goto fail;
3454281Sec158148 
3464281Sec158148 				trk_size = sess_leadout_lba -
3474281Sec158148 				    tip->ti_start_address;
3484281Sec158148 
3494281Sec158148 				/* Fix track size if it was too big. */
3504281Sec158148 				if (tip->ti_track_size > trk_size)
3514281Sec158148 					tip->ti_track_size = trk_size;
3524281Sec158148 			}
3534281Sec158148 		}
3544281Sec158148 		free(raw_toc);
3550Sstevel@tonic-gate 	}
3560Sstevel@tonic-gate 	return (1);
3574281Sec158148 
3584281Sec158148 fail:
3594281Sec158148 	free(raw_toc);
3604281Sec158148 	return (0);
3614281Sec158148 }
3624281Sec158148 
3634281Sec158148 /*
3644281Sec158148  * Determine what session number a track is in by parsing the Raw TOC format of
3654281Sec158148  * the the READ TOC/PMA/ATIP command response data.
3664281Sec158148  */
3674281Sec158148 static int
rtoc_get_trk_sess_num(uchar_t * rtoc,size_t rtoc_len,int trk_num,int * sess_nump)3684281Sec158148 rtoc_get_trk_sess_num(uchar_t *rtoc, size_t rtoc_len, int trk_num,
3694281Sec158148     int *sess_nump)
3704281Sec158148 {
3714281Sec158148 	rtoc_td_t *tdp = (rtoc_td_t *)(rtoc + sizeof (rtoc_hdr_t));
3724281Sec158148 	rtoc_td_t *last_tdp = (rtoc_td_t *)(rtoc + rtoc_len -
3734281Sec158148 	    sizeof (rtoc_td_t));
3744281Sec158148 
3754281Sec158148 	if ((tdp = get_rtoc_td(tdp, last_tdp, Q_MODE_1, (uchar_t)trk_num)) !=
3764281Sec158148 	    NULL) {
3774281Sec158148 		*sess_nump = tdp->rt_session_num;
3784281Sec158148 		return (1);
3794281Sec158148 	} else
3804281Sec158148 		return (0);
3814281Sec158148 }
3824281Sec158148 
3834281Sec158148 /*
3844281Sec158148  * Determine the last track number in a specified session number by parsing the
3854281Sec158148  * Raw TOC format of the READ TOC/PMA/ATIP command response data.
3864281Sec158148  */
3874281Sec158148 static int
rtoc_get_sess_last_trk_num(uchar_t * rtoc,size_t rtoc_len,int sess_num,int * last_trk_nump)3884281Sec158148 rtoc_get_sess_last_trk_num(uchar_t *rtoc, size_t rtoc_len, int sess_num,
3894281Sec158148     int *last_trk_nump)
3904281Sec158148 {
3914281Sec158148 	rtoc_td_t *tdp = (rtoc_td_t *)(rtoc + sizeof (rtoc_hdr_t));
3924281Sec158148 	rtoc_td_t *last_tdp = (rtoc_td_t *)(rtoc + rtoc_len -
3934281Sec158148 	    sizeof (rtoc_td_t));
3944281Sec158148 
3954281Sec158148 	while ((tdp = get_rtoc_td(tdp, last_tdp, Q_MODE_1,
3964281Sec158148 	    POINT_SESS_LAST_TRK)) != NULL) {
3974281Sec158148 		if (tdp->rt_session_num == sess_num) {
3984281Sec158148 			*last_trk_nump = tdp->rt_pmin;
3994281Sec158148 			return (1);
4004281Sec158148 		} else {
4014281Sec158148 			++tdp;
4024281Sec158148 		}
4034281Sec158148 	}
4044281Sec158148 
4054281Sec158148 	return (0);
4064281Sec158148 }
4074281Sec158148 
4084281Sec158148 /*
4094281Sec158148  * Determine the starting LBA of the the session leadout by parsing the Raw TOC
4104281Sec158148  * format of the READ TOC/PMA/ATIP command response data.
4114281Sec158148  */
4124281Sec158148 static int
rtoc_get_sess_leadout_lba(uchar_t * rtoc,size_t rtoc_len,int sess_num,uint32_t * leadout_lba)4134281Sec158148 rtoc_get_sess_leadout_lba(uchar_t *rtoc, size_t rtoc_len, int sess_num,
4144281Sec158148     uint32_t *leadout_lba)
4154281Sec158148 {
4164281Sec158148 	rtoc_td_t *tdp = (rtoc_td_t *)(rtoc + sizeof (rtoc_hdr_t));
4174281Sec158148 	rtoc_td_t *last_tdp = (rtoc_td_t *)(rtoc + rtoc_len -
4184281Sec158148 	    sizeof (rtoc_td_t));
4194281Sec158148 
4204281Sec158148 	while ((tdp = get_rtoc_td(tdp, last_tdp, Q_MODE_1,
4214281Sec158148 	    POINT_LEADOUT_ADDR)) != NULL) {
4224281Sec158148 		if (tdp->rt_session_num == sess_num) {
4234281Sec158148 			*leadout_lba = MSF2LBA(tdp->rt_pmin, tdp->rt_psec,
4244281Sec158148 			    tdp->rt_pframe);
4254281Sec158148 			return (1);
4264281Sec158148 		} else {
4274281Sec158148 			++tdp;
4284281Sec158148 		}
4294281Sec158148 	}
4304281Sec158148 
4314281Sec158148 	return (0);
4324281Sec158148 }
4334281Sec158148 
4344281Sec158148 /*
4354281Sec158148  * Search a set of Raw TOC Track Descriptors using <'adr', 'point'> as the
4364281Sec158148  * search key. Return a pointer to the first Track Descriptor that matches.
4374281Sec158148  */
4384281Sec158148 static rtoc_td_t *
get_rtoc_td(rtoc_td_t * begin_tdp,rtoc_td_t * end_tdp,uchar_t adr,uchar_t point)4394281Sec158148 get_rtoc_td(rtoc_td_t *begin_tdp, rtoc_td_t *end_tdp, uchar_t adr,
4404281Sec158148     uchar_t point)
4414281Sec158148 {
4424281Sec158148 	rtoc_td_t *cur_tdp = begin_tdp;
4434281Sec158148 
4444281Sec158148 	while (cur_tdp <= end_tdp) {
4454281Sec158148 		if ((cur_tdp->rt_adr == adr) && (cur_tdp->rt_point == point))
4464281Sec158148 			return (cur_tdp);
4474281Sec158148 		else
4484281Sec158148 			cur_tdp++;
4494281Sec158148 	}
4504281Sec158148 
4514281Sec158148 	return (NULL);
4520Sstevel@tonic-gate }
4530Sstevel@tonic-gate 
4540Sstevel@tonic-gate uchar_t
get_data_mode(int fd,uint32_t lba)4550Sstevel@tonic-gate get_data_mode(int fd, uint32_t lba)
4560Sstevel@tonic-gate {
4570Sstevel@tonic-gate 	int ret;
4580Sstevel@tonic-gate 	uchar_t *buf;
4590Sstevel@tonic-gate 	uchar_t mode;
4600Sstevel@tonic-gate 
4610Sstevel@tonic-gate 	buf = (uchar_t *)my_zalloc(8);
4620Sstevel@tonic-gate 	ret = read_header(fd, lba, buf);
4630Sstevel@tonic-gate 	if (ret == 0)
4640Sstevel@tonic-gate 		mode = 0xff;
4650Sstevel@tonic-gate 	else
4660Sstevel@tonic-gate 		mode = buf[0];
4670Sstevel@tonic-gate 	free(buf);
4680Sstevel@tonic-gate 	return (mode);
4690Sstevel@tonic-gate }
4700Sstevel@tonic-gate 
4710Sstevel@tonic-gate /*
4720Sstevel@tonic-gate  * Set page code 5 for TAO mode.
4730Sstevel@tonic-gate  */
4740Sstevel@tonic-gate int
prepare_for_write(cd_device * dev,int track_mode,int test_write,int keep_disc_open)4750Sstevel@tonic-gate prepare_for_write(cd_device *dev, int track_mode, int test_write,
4760Sstevel@tonic-gate     int keep_disc_open)
4770Sstevel@tonic-gate {
4780Sstevel@tonic-gate 	uchar_t *buf;
4790Sstevel@tonic-gate 	int no_err;
4800Sstevel@tonic-gate 	int reset_device;
4810Sstevel@tonic-gate 
4820Sstevel@tonic-gate 	if ((write_mode == DAO_MODE) && keep_disc_open) {
4830Sstevel@tonic-gate 		(void) printf(gettext(
4840Sstevel@tonic-gate 		    "Multi-session is not supported on DVD media\n"));
4850Sstevel@tonic-gate 		exit(1);
4860Sstevel@tonic-gate 	}
4870Sstevel@tonic-gate 
4880Sstevel@tonic-gate 	if ((write_mode == DAO_MODE) && debug) {
4890Sstevel@tonic-gate 		(void) printf("Preparing to write in DAO\n");
4900Sstevel@tonic-gate 	}
4910Sstevel@tonic-gate 
4920Sstevel@tonic-gate 	(void) start_stop(dev->d_fd, 1);
4930Sstevel@tonic-gate 	/* Some drives do not support this command but still do it */
4940Sstevel@tonic-gate 	(void) rezero_unit(dev->d_fd);
4950Sstevel@tonic-gate 
4960Sstevel@tonic-gate 	buf = (uchar_t *)my_zalloc(64);
4970Sstevel@tonic-gate 
4980Sstevel@tonic-gate 	no_err = get_mode_page(dev->d_fd, 5, 0, 64, buf);
4990Sstevel@tonic-gate 	if (no_err)
5000Sstevel@tonic-gate 		no_err = ((buf[1] + 2) > 64) ? 0 : 1;
5010Sstevel@tonic-gate 	/*
5020Sstevel@tonic-gate 	 * If the device is already in simulation mode and again a
5030Sstevel@tonic-gate 	 * simulation is requested, then set the device in non-simulation
5040Sstevel@tonic-gate 	 * 1st and then take it to simulation mode. This will flush any
5050Sstevel@tonic-gate 	 * previous fake state in the drive.
5060Sstevel@tonic-gate 	 */
5070Sstevel@tonic-gate 	if (no_err && test_write && (buf[2] & 0x10)) {
5080Sstevel@tonic-gate 		reset_device = 1;
5090Sstevel@tonic-gate 	} else {
5100Sstevel@tonic-gate 		reset_device = 0;
5110Sstevel@tonic-gate 	}
5120Sstevel@tonic-gate 	if (no_err != 0) {
5130Sstevel@tonic-gate 		buf[0] &= 0x3f;
5140Sstevel@tonic-gate 
5150Sstevel@tonic-gate 		/* set TAO or DAO writing mode */
5160Sstevel@tonic-gate 		buf[2] = (write_mode == TAO_MODE)?1:2;
5170Sstevel@tonic-gate 
5180Sstevel@tonic-gate 		/* set simulation flag */
5190Sstevel@tonic-gate 		if (test_write && (!reset_device)) {
5200Sstevel@tonic-gate 			buf[2] |= 0x10;
5210Sstevel@tonic-gate 		} else {
5220Sstevel@tonic-gate 			buf[2] &= ~0x10;
5230Sstevel@tonic-gate 		}
5240Sstevel@tonic-gate 
5250Sstevel@tonic-gate 		/* Turn on HW buffer underrun protection (BUFE) */
5260Sstevel@tonic-gate 		if (!test_write) {
5270Sstevel@tonic-gate 			buf[2] |= 0x40;
5280Sstevel@tonic-gate 		}
5290Sstevel@tonic-gate 
5300Sstevel@tonic-gate 		/* set track mode type */
5310Sstevel@tonic-gate 		if (device_type == CD_RW) {
5320Sstevel@tonic-gate 			buf[3] = track_mode & 0x0f;	/* ctrl nibble */
5330Sstevel@tonic-gate 		}
5340Sstevel@tonic-gate 
5350Sstevel@tonic-gate 		if (keep_disc_open) {
5360Sstevel@tonic-gate 			buf[3] |= 0xc0;		/* Allow more sessions */
5370Sstevel@tonic-gate 		}
5380Sstevel@tonic-gate 
5390Sstevel@tonic-gate 		/* Select track type (audio or data) */
5400Sstevel@tonic-gate 		if (track_mode == TRACK_MODE_DATA) {
5410Sstevel@tonic-gate 			buf[4] = 8;		/* 2048 byte sector */
5420Sstevel@tonic-gate 		} else {
5430Sstevel@tonic-gate 			buf[4] = 0;		/* 2352 byte sector */
5440Sstevel@tonic-gate 		}
5450Sstevel@tonic-gate 		buf[7] = buf[8] = 0;
5460Sstevel@tonic-gate 
5470Sstevel@tonic-gate 		/* Need to clear these fields for setting into DAO */
5480Sstevel@tonic-gate 		if (write_mode == DAO_MODE)
5490Sstevel@tonic-gate 			buf[5] = buf[15] = 0;
5500Sstevel@tonic-gate 
5510Sstevel@tonic-gate 		/* print out mode for detailed log */
5520Sstevel@tonic-gate 		if (debug && verbose) {
5530Sstevel@tonic-gate 			int i;
5540Sstevel@tonic-gate 
5550Sstevel@tonic-gate 			(void) printf("setting = [ ");
5560Sstevel@tonic-gate 			for (i = 0; i < 15; i++)
5570Sstevel@tonic-gate 				(void) printf("0x%x ", buf[i]);
5580Sstevel@tonic-gate 			(void) printf("]\n");
5590Sstevel@tonic-gate 		}
5600Sstevel@tonic-gate 
5610Sstevel@tonic-gate 		no_err = set_mode_page(dev->d_fd, buf);
5620Sstevel@tonic-gate 
5630Sstevel@tonic-gate 		if (no_err && reset_device) {
5640Sstevel@tonic-gate 			/* Turn the test write bit back on */
5650Sstevel@tonic-gate 			buf[2] |= 0x10;
5660Sstevel@tonic-gate 			no_err = set_mode_page(dev->d_fd, buf);
5670Sstevel@tonic-gate 		}
5680Sstevel@tonic-gate 
5690Sstevel@tonic-gate 		/*
5700Sstevel@tonic-gate 		 * Since BUFE is the only optional flag we are
5710Sstevel@tonic-gate 		 * setting we will try to turn it off if the command
5720Sstevel@tonic-gate 		 * fails.
5730Sstevel@tonic-gate 		 */
5740Sstevel@tonic-gate 		if (!no_err) {
5750Sstevel@tonic-gate 			/*
5760Sstevel@tonic-gate 			 * Some old drives may not support HW
5770Sstevel@tonic-gate 			 * buffer underrun protection, try again
5780Sstevel@tonic-gate 			 * after turning it off.
5790Sstevel@tonic-gate 			 */
5800Sstevel@tonic-gate 			if (debug)
5810Sstevel@tonic-gate 				(void) printf("Turning off BUFE\n");
5820Sstevel@tonic-gate 			buf[2] &= ~0x40;
5830Sstevel@tonic-gate 			no_err = set_mode_page(dev->d_fd, buf);
5840Sstevel@tonic-gate 		}
5850Sstevel@tonic-gate 	}
5860Sstevel@tonic-gate 
5870Sstevel@tonic-gate 	free(buf);
5880Sstevel@tonic-gate 	return (no_err);
5890Sstevel@tonic-gate }
5900Sstevel@tonic-gate 
5910Sstevel@tonic-gate /*
5920Sstevel@tonic-gate  * Close session. This will write TOC.
5930Sstevel@tonic-gate  */
5940Sstevel@tonic-gate int
finalize(cd_device * dev)5950Sstevel@tonic-gate finalize(cd_device *dev)
5960Sstevel@tonic-gate {
5970Sstevel@tonic-gate 	uchar_t *di;
5980Sstevel@tonic-gate 	int count, ret, err;
5990Sstevel@tonic-gate 	int immediate;
6000Sstevel@tonic-gate 	int finalize_max;
6010Sstevel@tonic-gate 
6020Sstevel@tonic-gate 	/*
6030Sstevel@tonic-gate 	 * For ATAPI devices we will use the immediate mode and will
6040Sstevel@tonic-gate 	 * poll the command for completion so that this command may
6050Sstevel@tonic-gate 	 * not hog the channel. But for SCSI, we will use the treditional
6060Sstevel@tonic-gate 	 * way of issuing the command with a large enough timeout. This
6070Sstevel@tonic-gate 	 * is done because immediate mode was designed for ATAPI and some
6080Sstevel@tonic-gate 	 * SCSI RW drives might not be even tested with it.
6090Sstevel@tonic-gate 	 */
6100Sstevel@tonic-gate 	if ((dev->d_inq[2] & 7) != 0) {
6110Sstevel@tonic-gate 		/* SCSI device */
6120Sstevel@tonic-gate 		immediate = 0;
6130Sstevel@tonic-gate 	} else {
6140Sstevel@tonic-gate 		/* non-SCSI (e.g ATAPI) device */
6150Sstevel@tonic-gate 		immediate = 1;
6160Sstevel@tonic-gate 	}
6170Sstevel@tonic-gate 
6180Sstevel@tonic-gate 	/* We need to close track before close session */
6190Sstevel@tonic-gate 	if (device_type == DVD_PLUS) {
6200Sstevel@tonic-gate 		if (!close_track(dev->d_fd, 0, 0, immediate))
6210Sstevel@tonic-gate 			return (0);
6220Sstevel@tonic-gate 	}
6230Sstevel@tonic-gate 
6240Sstevel@tonic-gate 	if (!close_track(dev->d_fd, 0, 1, immediate)) {
6250Sstevel@tonic-gate 		/*
6261305Sminht 		 * For DAO mode which we use for DVD-RW, the latest MMC
6271305Sminht 		 * specification does not mention close_track. Some
6281305Sminht 		 * newer drives will return an ILLEGAL INSTRUCTION
6291305Sminht 		 * which we will ignore. We have also found a Panasonic
6301305Sminht 		 * drive which will return a MEDIA ERROR. It is safe
6311305Sminht 		 * to ignore both errors as this is not needed for
6321305Sminht 		 * these drives.
6331305Sminht 		 * This is kept for older drives which had needed
6341305Sminht 		 * us to issue close_track to flush the cache fully.
6351305Sminht 		 * once we are certain these drives have cleared the
6361305Sminht 		 * market, this can be removed.
6370Sstevel@tonic-gate 		 */
6380Sstevel@tonic-gate 		if (device_type == DVD_MINUS) {
6390Sstevel@tonic-gate 			return (0);
6400Sstevel@tonic-gate 		}
6410Sstevel@tonic-gate 	} else {
6420Sstevel@tonic-gate 		if (!immediate)
6430Sstevel@tonic-gate 			return (1);
6440Sstevel@tonic-gate 	}
6450Sstevel@tonic-gate 	if (immediate) {
6460Sstevel@tonic-gate 		(void) sleep(10);
6470Sstevel@tonic-gate 
6480Sstevel@tonic-gate 		di = (uchar_t *)my_zalloc(DISC_INFO_BLOCK_SIZE);
6490Sstevel@tonic-gate 		err = 0;
6500Sstevel@tonic-gate 
6510Sstevel@tonic-gate 		if (device_type == CD_RW) {
6520Sstevel@tonic-gate 			/* Finalization should not take more than 6 minutes */
6530Sstevel@tonic-gate 			finalize_max = FINALIZE_TIMEOUT;
6540Sstevel@tonic-gate 		} else {
6550Sstevel@tonic-gate 			/* some DVD-RW drives take longer than 6 minutes */
6560Sstevel@tonic-gate 			finalize_max = FINALIZE_TIMEOUT*2;
6570Sstevel@tonic-gate 		}
6580Sstevel@tonic-gate 
6590Sstevel@tonic-gate 		for (count = 0; count < finalize_max; count++) {
6600Sstevel@tonic-gate 			ret = read_disc_info(dev->d_fd, di);
6610Sstevel@tonic-gate 			if (ret != 0)
6620Sstevel@tonic-gate 				break;
6630Sstevel@tonic-gate 			if (uscsi_status != 2)
6640Sstevel@tonic-gate 				err = 1;
6650Sstevel@tonic-gate 			if (SENSE_KEY(rqbuf) == 2) {
6660Sstevel@tonic-gate 				/* not ready but not becoming ready */
6670Sstevel@tonic-gate 				if (ASC(rqbuf) != 4)
6680Sstevel@tonic-gate 					err = 1;
6690Sstevel@tonic-gate 			} else if (SENSE_KEY(rqbuf) == 5) {
6700Sstevel@tonic-gate 				/* illegal mode for this track */
6710Sstevel@tonic-gate 				if (ASC(rqbuf) != 0x64)
6720Sstevel@tonic-gate 					err = 1;
6730Sstevel@tonic-gate 			} else {
6740Sstevel@tonic-gate 				err = 1;
6750Sstevel@tonic-gate 			}
6760Sstevel@tonic-gate 			if (err == 1) {
6770Sstevel@tonic-gate 				if (debug) {
6780Sstevel@tonic-gate 					(void) printf("Finalization failed\n");
6790Sstevel@tonic-gate 					(void) printf("%x %x %x %x\n",
6800Sstevel@tonic-gate 					    uscsi_status, SENSE_KEY(rqbuf),
6810Sstevel@tonic-gate 					    ASC(rqbuf), ASCQ(rqbuf));
6820Sstevel@tonic-gate 				}
6830Sstevel@tonic-gate 				free(di);
6840Sstevel@tonic-gate 				return (0);
6850Sstevel@tonic-gate 			}
6860Sstevel@tonic-gate 			if (uscsi_status == 2) {
6870Sstevel@tonic-gate 				int i;
6880Sstevel@tonic-gate 				/* illegal field in command packet */
6890Sstevel@tonic-gate 				if (ASC(rqbuf) == 0x24) {
6900Sstevel@tonic-gate 					/* print it out! */
6910Sstevel@tonic-gate 					(void) printf("\n");
6920Sstevel@tonic-gate 					for (i = 0; i < 18; i++)
6930Sstevel@tonic-gate 						(void) printf("%x ",
6940Sstevel@tonic-gate 						    (unsigned)(rqbuf[i]));
6950Sstevel@tonic-gate 					(void) printf("\n");
6960Sstevel@tonic-gate 				}
6970Sstevel@tonic-gate 			}
6980Sstevel@tonic-gate 			(void) sleep(5);
6990Sstevel@tonic-gate 		}
7000Sstevel@tonic-gate 		free(di);
7010Sstevel@tonic-gate 	}
7020Sstevel@tonic-gate 	return (ret);
7030Sstevel@tonic-gate }
7040Sstevel@tonic-gate 
7050Sstevel@tonic-gate /*
7060Sstevel@tonic-gate  * Find out media capacity.
7070Sstevel@tonic-gate  */
7085337Szk194757 uint32_t
get_last_possible_lba(cd_device * dev)7090Sstevel@tonic-gate get_last_possible_lba(cd_device *dev)
7100Sstevel@tonic-gate {
7110Sstevel@tonic-gate 	uchar_t *di;
7125337Szk194757 	uint32_t cap;
7130Sstevel@tonic-gate 
7140Sstevel@tonic-gate 	di = (uchar_t *)my_zalloc(DISC_INFO_BLOCK_SIZE);
7150Sstevel@tonic-gate 	if (!read_disc_info(dev->d_fd, di)) {
7160Sstevel@tonic-gate 		free(di);
7170Sstevel@tonic-gate 		return (0);
7180Sstevel@tonic-gate 	}
7195337Szk194757 
7205337Szk194757 	/*
7215337Szk194757 	 * If we have a DVD+R this field is an LBA. If the media is
7225337Szk194757 	 * a CD-R/W the field is MSF formatted. Otherwise this field
7235337Szk194757 	 * is not valid and will be zero.
7245337Szk194757 	 */
7255337Szk194757 	if (device_type == DVD_PLUS) {
7265337Szk194757 		if (read_scsi32(&di[20]) != 0xffffffff) {
7275337Szk194757 			cap = read_scsi32(&di[20]);
7285337Szk194757 		} else {
7295337Szk194757 			cap = 0;
7305337Szk194757 		}
7310Sstevel@tonic-gate 	} else {
7325337Szk194757 		if ((di[21] != 0) && (di[21] != 0xff)) {
733*5426Szk194757 			cap = MSF2LBA(di[21], di[22], di[23]);
7345337Szk194757 		} else {
7355337Szk194757 			cap = 0;
7365337Szk194757 		}
7370Sstevel@tonic-gate 	}
7380Sstevel@tonic-gate 
7390Sstevel@tonic-gate 	free(di);
7400Sstevel@tonic-gate 	return (cap);
7410Sstevel@tonic-gate }
7420Sstevel@tonic-gate 
7430Sstevel@tonic-gate int
read_audio_through_read_cd(cd_device * dev,uint_t start_lba,uint_t nblks,uchar_t * buf)7440Sstevel@tonic-gate read_audio_through_read_cd(cd_device *dev, uint_t start_lba, uint_t nblks,
7450Sstevel@tonic-gate     uchar_t *buf)
7460Sstevel@tonic-gate {
7470Sstevel@tonic-gate 	int retry;
7480Sstevel@tonic-gate 	int ret;
7490Sstevel@tonic-gate 
7500Sstevel@tonic-gate 	for (retry = 0; retry < 3; retry++) {
7510Sstevel@tonic-gate 		ret = read_cd(dev->d_fd, (uint32_t)start_lba, (uint16_t)nblks,
7520Sstevel@tonic-gate 		    1, buf, (uint32_t)(nblks * 2352));
7530Sstevel@tonic-gate 		if (ret)
7540Sstevel@tonic-gate 			break;
7550Sstevel@tonic-gate 	}
7560Sstevel@tonic-gate 	return (ret);
7570Sstevel@tonic-gate }
7580Sstevel@tonic-gate 
7590Sstevel@tonic-gate int
eject_media(cd_device * dev)7600Sstevel@tonic-gate eject_media(cd_device *dev)
7610Sstevel@tonic-gate {
7620Sstevel@tonic-gate 	if (vol_running) {
7630Sstevel@tonic-gate 		/* If there is a media, try using DKIOCEJECT 1st */
7640Sstevel@tonic-gate 		if (check_device(dev, CHECK_NO_MEDIA) == 0) {
7652979Szk194757 			/*
7662979Szk194757 			 * The check_device() call will issue
7672979Szk194757 			 * a TEST UNIT READY (TUR) and retry many
7682979Szk194757 			 * times when a DVD-R is present. The DKIOCEJECT
7692979Szk194757 			 * ioctl will subsequently fail causing us to
7702979Szk194757 			 * issue the LOAD/UNLOAD SCSI command to the device
7712979Szk194757 			 * with out ejecting the media. Insted of letting
7722979Szk194757 			 * this happen, issue a reset to the device before
7732979Szk194757 			 * issuing the DKIOCEJCET ioctl.
7742979Szk194757 			 */
7752979Szk194757 			if (device_type == DVD_MINUS)
7762979Szk194757 				reset_dev(dev->d_fd);
7772979Szk194757 
7780Sstevel@tonic-gate 			if (ioctl(dev->d_fd, DKIOCEJECT, 0) == 0) {
7790Sstevel@tonic-gate 				return (1);
7800Sstevel@tonic-gate 			}
7810Sstevel@tonic-gate 		}
7820Sstevel@tonic-gate 	}
7830Sstevel@tonic-gate 	if (load_unload(dev->d_fd, 0) == 0) {
7840Sstevel@tonic-gate 		/* if eject fails */
7850Sstevel@tonic-gate 		if ((uscsi_status == 2) && (ASC(rqbuf) == 0x53)) {
7860Sstevel@tonic-gate 			/*
7870Sstevel@tonic-gate 			 * check that eject is not blocked on the device
7880Sstevel@tonic-gate 			 */
7890Sstevel@tonic-gate 			if (!prevent_allow_mr(dev->d_fd, 1))
7900Sstevel@tonic-gate 				return (0);
7910Sstevel@tonic-gate 			return (load_unload(dev->d_fd, 0));
7920Sstevel@tonic-gate 		}
7930Sstevel@tonic-gate 		return (0);
7940Sstevel@tonic-gate 	}
7950Sstevel@tonic-gate 	return (1);
7960Sstevel@tonic-gate }
7970Sstevel@tonic-gate 
7980Sstevel@tonic-gate /*
799936Sarutz  * Get current Read or Write Speed from Mode Page 0x2a.
800936Sarutz  *
801936Sarutz  * Use the size of the Page to determine which Multimedia Command
802936Sarutz  * set (MMC) is present.  Based on the MMC version, get the
803936Sarutz  * specified Read/Write Speed.
804936Sarutz  *
805936Sarutz  * Note that some MMC versions do not necessarily support a
806936Sarutz  * (current) Read or Write Speed.  As a result, this function
807936Sarutz  * _can_ return a value of zero.
808936Sarutz  *
809936Sarutz  * The newer standards (reserve and) mark the field(s) as Obsolete,
810936Sarutz  * yet many vendors populate the Obsolete fields with valid values
811936Sarutz  * (assumedly for backward compatibility).  This is important, as
812936Sarutz  * a command like GET PERFORMANCE cannot return _the_ speed; it can
813936Sarutz  * only return a Logical-Block-Address-dependent (LBA) speed.  Such
814936Sarutz  * values can vary widely between the innermost and outermost Track.
815936Sarutz  * Mode Page 0x2a is the best solution identifying "the current
816936Sarutz  * (nominal) speed".
8170Sstevel@tonic-gate  */
8180Sstevel@tonic-gate static uint16_t
cd_speed_get(cd_device * dev,int cmd)819936Sarutz cd_speed_get(cd_device *dev, int cmd)
8200Sstevel@tonic-gate {
821936Sarutz 	uchar_t		*mp2a;
822936Sarutz 	uint16_t	rate = 0;
823936Sarutz 	int		offset;
824936Sarutz 	uint_t		buflen = 254;
825936Sarutz 
826936Sarutz 	/*
827936Sarutz 	 * Allocate a buffer acceptably larger than any nominal
828936Sarutz 	 * Page for Page Code 0x2A.
829936Sarutz 	 */
830936Sarutz 	mp2a = (uchar_t *)my_zalloc(buflen);
831936Sarutz 	if (get_mode_page(dev->d_fd, 0x2A, 0, buflen, mp2a) == 0)
832936Sarutz 		goto end;
833936Sarutz 
834936Sarutz 	/* Determine MMC version based on 'Page Length' field */
835936Sarutz 	switch (mp2a[1]) {
836936Sarutz 	case 0x14:  /* MMC-1 */
837936Sarutz 		if (debug)
838936Sarutz 			(void) printf("Mode Page 2A: MMC-1\n");
839936Sarutz 
840936Sarutz 		offset = (cmd == GET_READ_SPEED) ? 14 : 20;
841936Sarutz 		rate = read_scsi16(&mp2a[offset]);
842936Sarutz 		break;
843936Sarutz 
844936Sarutz 
845936Sarutz 	case 0x18: /* MMC-2 */
846936Sarutz 		if (debug)
847936Sarutz 			(void) printf("Mode Page 2A: MMC-2;"
848936Sarutz 			    " Read and Write Speeds are "
849936Sarutz 			    "obsolete\n");
8500Sstevel@tonic-gate 
851936Sarutz 		/* see if "Obsolete" values are valid: */
852936Sarutz 		offset = (cmd == GET_READ_SPEED) ? 14 : 20;
853936Sarutz 		rate = read_scsi16(&mp2a[offset]);
854936Sarutz 		break;
855936Sarutz 
856936Sarutz 	default: /* MMC-3 or newer */
857936Sarutz 		if (debug)
858936Sarutz 			(void) printf("Mode Page 2A: MMC-3 or"
859936Sarutz 			    " newer; Read Speed is obsolete.\n");
860936Sarutz 
8610Sstevel@tonic-gate 		if (cmd == GET_READ_SPEED) {
862936Sarutz 			/* this is Obsolete, but try it */
863936Sarutz 			offset = 14;
864936Sarutz 			rate = read_scsi16(&mp2a[offset]);
8650Sstevel@tonic-gate 		} else {
866936Sarutz 			/* Write Speed is not obsolete */
867936Sarutz 			offset = 28;
868936Sarutz 			rate = read_scsi16(&mp2a[offset]);
869936Sarutz 
870936Sarutz 			if (rate == 0) {
871936Sarutz 				/*
872936Sarutz 				 * then try an Obsolete field
873936Sarutz 				 * (but this shouldn't happen!)
874936Sarutz 				 */
875936Sarutz 				offset = 20;
876936Sarutz 				rate = read_scsi16(&mp2a[offset]);
877936Sarutz 			}
8780Sstevel@tonic-gate 		}
879936Sarutz 		break;
8800Sstevel@tonic-gate 	}
881936Sarutz end:
8820Sstevel@tonic-gate 	free(mp2a);
883936Sarutz 
884936Sarutz 	if (debug)
885936Sarutz 		(void) printf("cd_speed_get: %s Speed is "
886936Sarutz 		    "%uX\n", (cmd == GET_READ_SPEED) ?
887936Sarutz 		    "Read" : "Write", cdrw_bandwidth_to_x(rate));
8880Sstevel@tonic-gate 	return (rate);
8890Sstevel@tonic-gate }
8900Sstevel@tonic-gate 
8910Sstevel@tonic-gate /*
8920Sstevel@tonic-gate  * CD speed related functions (ioctl style) for drives which do not support
8930Sstevel@tonic-gate  * real time streaming.
894936Sarutz  *
895936Sarutz  * For the SET operations, the SET CD SPEED command needs
896936Sarutz  * both the Read Speed and the Write Speed.  Eg, if
897936Sarutz  * we're trying to set the Write Speed (SET_WRITE_SPEED),
898936Sarutz  * then we first need to obtain the current Read Speed.
899936Sarutz  * That speed is specified along with the chosen_speed (the
900936Sarutz  * Write Speed in this case) in the SET CD SPEED command.
9010Sstevel@tonic-gate  */
9020Sstevel@tonic-gate int
cd_speed_ctrl(cd_device * dev,int cmd,int speed)9030Sstevel@tonic-gate cd_speed_ctrl(cd_device *dev, int cmd, int speed)
9040Sstevel@tonic-gate {
9050Sstevel@tonic-gate 	uint16_t rate;
9060Sstevel@tonic-gate 
907936Sarutz 	switch (cmd) {
908936Sarutz 	case GET_READ_SPEED:
909936Sarutz 		rate = cd_speed_get(dev, GET_READ_SPEED);
910936Sarutz 		return (cdrw_bandwidth_to_x(rate));
911936Sarutz 
912936Sarutz 	case GET_WRITE_SPEED:
913936Sarutz 		rate = cd_speed_get(dev, GET_WRITE_SPEED);
914936Sarutz 		return (cdrw_bandwidth_to_x(rate));
915936Sarutz 
916936Sarutz 	case SET_READ_SPEED:
917936Sarutz 		rate = cd_speed_get(dev, GET_WRITE_SPEED);
918936Sarutz 		return (set_cd_speed(dev->d_fd,
919936Sarutz 		    cdrw_x_to_bandwidth(speed), rate));
920936Sarutz 		break;
921936Sarutz 
922936Sarutz 	case SET_WRITE_SPEED:
923936Sarutz 		rate = cd_speed_get(dev, GET_READ_SPEED);
924936Sarutz 		return (set_cd_speed(dev->d_fd, rate,
925936Sarutz 		    cdrw_x_to_bandwidth(speed)));
926936Sarutz 		break;
927936Sarutz 
928936Sarutz 	default:
929936Sarutz 		return (0);
9300Sstevel@tonic-gate 	}
9310Sstevel@tonic-gate }
9320Sstevel@tonic-gate 
9330Sstevel@tonic-gate /*
934936Sarutz  * Manage sending of SET STREAMING command using the specified
935936Sarutz  * read_speed and write_speed.
936936Sarutz  *
937936Sarutz  * This function allocates and initializes a Performance
938936Sarutz  * Descriptor, which is sent as part of the SET STREAMING
939936Sarutz  * command.  The descriptor is deallocated before function
940936Sarutz  * exit.
941936Sarutz  */
942936Sarutz static int
do_set_streaming(cd_device * dev,uint_t read_speed,uint_t write_speed)943936Sarutz do_set_streaming(cd_device *dev, uint_t read_speed,
944936Sarutz 	uint_t write_speed)
945936Sarutz {
946936Sarutz 	int ret;
947936Sarutz 	uchar_t *str;
948936Sarutz 
949936Sarutz 	/* Allocate and initialize the Performance Descriptor */
950936Sarutz 	str = (uchar_t *)my_zalloc(SET_STREAM_DATA_LEN);
951936Sarutz 
952936Sarutz 	/* Read Time (in milliseconds) */
953936Sarutz 	load_scsi32(&str[16], 1000);
954936Sarutz 	/* Write Time (in milliseconds) */
955936Sarutz 	load_scsi32(&str[24], 1000);
956936Sarutz 
957936Sarutz 	/* Read Speed */
958936Sarutz 	load_scsi32(&str[12], (uint32_t)read_speed);
959936Sarutz 	/* Write Speed */
960936Sarutz 	load_scsi32(&str[20], (uint32_t)write_speed);
961936Sarutz 
962936Sarutz 	/* issue SET STREAMING command */
963936Sarutz 	ret = set_streaming(dev->d_fd, str);
964936Sarutz 	free(str);
965936Sarutz 
966936Sarutz 	return (ret);
967936Sarutz }
968936Sarutz 
969936Sarutz /*
970936Sarutz  * cd speed related functions for drives which support
971936Sarutz  * Real-Time Streaming Feature.
972936Sarutz  *
973936Sarutz  * For the SET operations, the SET STREAMING command needs
974936Sarutz  * both the Read Speed and the Write Speed.  Eg, if
975936Sarutz  * we're trying to set the Write Speed (SET_WRITE_SPEED),
976936Sarutz  * then we first need to obtain the current Read Speed.
977936Sarutz  * That speed is specified along with the chosen_speed (the
978936Sarutz  * Write Speed in this case) in the SET STREAMING command.
9790Sstevel@tonic-gate  */
9800Sstevel@tonic-gate int
rt_streaming_ctrl(cd_device * dev,int cmd,int speed)9810Sstevel@tonic-gate rt_streaming_ctrl(cd_device *dev, int cmd, int speed)
9820Sstevel@tonic-gate {
983936Sarutz 	int ret = 0;
984936Sarutz 	uint_t rate;
985936Sarutz 
986936Sarutz 	switch (cmd) {
987936Sarutz 	case GET_WRITE_SPEED:
988936Sarutz 		rate = cd_speed_get(dev, GET_WRITE_SPEED);
989936Sarutz 		ret = (int)cdrw_bandwidth_to_x(rate);
990936Sarutz 		break;
991936Sarutz 
992936Sarutz 	case GET_READ_SPEED:
993936Sarutz 		rate = cd_speed_get(dev, GET_READ_SPEED);
994936Sarutz 		ret = (int)cdrw_bandwidth_to_x(rate);
995936Sarutz 		break;
9960Sstevel@tonic-gate 
997936Sarutz 	case SET_READ_SPEED: {
998936Sarutz 		uint_t write_speed = cd_speed_get(dev, GET_WRITE_SPEED);
999936Sarutz 
1000936Sarutz 		/* set Read Speed using SET STREAMING */
1001936Sarutz 		ret = do_set_streaming(dev,
1002936Sarutz 		    cdrw_x_to_bandwidth(speed), write_speed);
10030Sstevel@tonic-gate 
1004936Sarutz 		/* If rt_speed_ctrl fails for any reason use cd_speed_ctrl */
1005936Sarutz 		if (ret == 0) {
1006936Sarutz 			if (debug)
1007936Sarutz 				(void) printf(" real time speed control"
1008936Sarutz 				    " failed, using CD speed control\n");
10090Sstevel@tonic-gate 
1010936Sarutz 			dev->d_speed_ctrl = cd_speed_ctrl;
1011936Sarutz 			ret = dev->d_speed_ctrl(dev, cmd, speed);
1012936Sarutz 		}
1013936Sarutz 		break;
10140Sstevel@tonic-gate 	}
10150Sstevel@tonic-gate 
1016936Sarutz 	case SET_WRITE_SPEED: {
1017936Sarutz 		uint_t read_speed = cd_speed_get(dev, GET_READ_SPEED);
1018936Sarutz 
1019936Sarutz 		/* set Write Speed using SET STREAMING */
1020936Sarutz 		ret = do_set_streaming(dev, read_speed,
1021936Sarutz 		    cdrw_x_to_bandwidth(speed));
1022936Sarutz 
1023936Sarutz 		/* If rt_speed_ctrl fails for any reason use cd_speed_ctrl */
1024936Sarutz 		if (ret == 0) {
1025936Sarutz 			if (debug)
1026936Sarutz 				(void) printf(" real time speed control"
1027936Sarutz 				    " failed, using CD speed control\n");
1028936Sarutz 
1029936Sarutz 			dev->d_speed_ctrl = cd_speed_ctrl;
1030936Sarutz 			ret = dev->d_speed_ctrl(dev, cmd, speed);
1031936Sarutz 		}
1032936Sarutz 		break;
1033936Sarutz 	}
1034936Sarutz 
1035936Sarutz 	default:
1036936Sarutz 		break;
1037936Sarutz 	}
1038936Sarutz 
10390Sstevel@tonic-gate 	return (ret);
10400Sstevel@tonic-gate }
10410Sstevel@tonic-gate 
10420Sstevel@tonic-gate /*
10430Sstevel@tonic-gate  * Initialize device for track-at-once mode of writing. All of the data will
10440Sstevel@tonic-gate  * need to be written to the track without interruption.
10450Sstevel@tonic-gate  * This initialized TAO by setting page code 5 and speed.
10460Sstevel@tonic-gate  */
10470Sstevel@tonic-gate void
write_init(int mode)10480Sstevel@tonic-gate write_init(int mode)
10490Sstevel@tonic-gate {
10500Sstevel@tonic-gate 	(void) printf(gettext("Initializing device"));
10510Sstevel@tonic-gate 	if (simulation)
10520Sstevel@tonic-gate 		(void) printf(gettext("(Simulation mode)"));
10530Sstevel@tonic-gate 	print_n_flush("...");
10540Sstevel@tonic-gate 
10550Sstevel@tonic-gate 	get_media_type(target->d_fd);
10560Sstevel@tonic-gate 
10570Sstevel@tonic-gate 	/* DVD- requires DAO mode */
10580Sstevel@tonic-gate 	if (device_type == DVD_MINUS) {
10590Sstevel@tonic-gate 		write_mode = DAO_MODE;
10600Sstevel@tonic-gate 	}
10610Sstevel@tonic-gate 
10620Sstevel@tonic-gate 	/* DVD+ and DVD- have no support for AUDIO, bail out */
10630Sstevel@tonic-gate 	if ((mode == TRACK_MODE_AUDIO) && (device_type != CD_RW)) {
10640Sstevel@tonic-gate 		err_msg(gettext("Audio mode is only supported for CD media\n"));
10650Sstevel@tonic-gate 		exit(1);
10660Sstevel@tonic-gate 	}
1067808Srameshc 	if (simulation &&
1068808Srameshc 	    check_device(target, CHECK_MEDIA_IS_NOT_BLANK) &&
1069808Srameshc 	    !check_device(target, CHECK_MEDIA_IS_NOT_ERASABLE) &&
1070808Srameshc 	    device_type != DVD_PLUS_W) {
1071808Srameshc 		/*
1072808Srameshc 		 * If we were in simulation mode, and media wasn't blank,
1073808Srameshc 		 * but medium was erasable, then cdrw goes to erase the
1074808Srameshc 		 * contents of the media after the simulation writing in order
1075808Srameshc 		 * to cleanup the ghost TOC (see write_fini() calls blank()).
1076808Srameshc 		 * This is bad because it removes existing data if media was
1077808Srameshc 		 * multi-session. Therefore, we no longer allow simulation
1078808Srameshc 		 * writing if such condition is met. we don't blank the DVD+RW
1079808Srameshc 		 * media, so DVD+RWs are fine.
1080808Srameshc 		 */
1081808Srameshc 		err_msg(gettext(
1082808Srameshc 		    "Cannot perform simulation for non-blank media\n"));
1083808Srameshc 		exit(1);
1084808Srameshc 	}
10850Sstevel@tonic-gate 
10860Sstevel@tonic-gate 	if (!prepare_for_write(target, mode, simulation, keep_disc_open)) {
10870Sstevel@tonic-gate 		/* l10n_NOTE : 'failed' as in Initializing device...failed  */
10880Sstevel@tonic-gate 		(void) printf(gettext("failed.\n"));
10890Sstevel@tonic-gate 		err_msg(gettext("Cannot initialize device for write\n"));
10900Sstevel@tonic-gate 		exit(1);
10910Sstevel@tonic-gate 	}
10920Sstevel@tonic-gate 	/* l10n_NOTE : 'done' as in "Initializing device...done"  */
10930Sstevel@tonic-gate 	(void) printf(gettext("done.\n"));
10940Sstevel@tonic-gate 
10950Sstevel@tonic-gate 	/* if speed change option was used (-p) then try to set the speed */
10960Sstevel@tonic-gate 	if (requested_speed != 0) {
10970Sstevel@tonic-gate 		if (verbose)
10980Sstevel@tonic-gate 			(void) printf(gettext("Trying to set speed to %dX.\n"),
10990Sstevel@tonic-gate 			    requested_speed);
11000Sstevel@tonic-gate 		if (target->d_speed_ctrl(target, SET_WRITE_SPEED,
11010Sstevel@tonic-gate 		    requested_speed) == 0) {
11020Sstevel@tonic-gate 			err_msg(gettext("Unable to set speed.\n"));
11030Sstevel@tonic-gate 			exit(1);
11040Sstevel@tonic-gate 		}
11050Sstevel@tonic-gate 		if (verbose) {
11060Sstevel@tonic-gate 			int speed;
11070Sstevel@tonic-gate 			speed = target->d_speed_ctrl(target,
11080Sstevel@tonic-gate 			    GET_WRITE_SPEED, 0);
11090Sstevel@tonic-gate 			if (speed == requested_speed) {
11100Sstevel@tonic-gate 				(void) printf(gettext("Speed set to %dX.\n"),
11110Sstevel@tonic-gate 				    speed);
1112936Sarutz 			} else if (speed == 0) {
1113936Sarutz 				(void) printf(gettext("Could not obtain "
1114936Sarutz 				    "current Write Speed.\n"));
11150Sstevel@tonic-gate 			} else {
11160Sstevel@tonic-gate 				(void) printf(
11170Sstevel@tonic-gate 				gettext("Speed set to closest approximation "
11180Sstevel@tonic-gate 				    "of %dX allowed by device (%dX).\n"),
11190Sstevel@tonic-gate 				    requested_speed, speed);
11200Sstevel@tonic-gate 			}
11210Sstevel@tonic-gate 		}
11220Sstevel@tonic-gate 	}
11230Sstevel@tonic-gate }
11240Sstevel@tonic-gate 
11250Sstevel@tonic-gate void
write_fini(void)11260Sstevel@tonic-gate write_fini(void)
11270Sstevel@tonic-gate {
11280Sstevel@tonic-gate 	print_n_flush(gettext("Finalizing (Can take several minutes)..."));
11290Sstevel@tonic-gate 	/* Some drives don't like this while in test write mode */
11300Sstevel@tonic-gate 	if (!simulation) {
11310Sstevel@tonic-gate 		if (!finalize(target)) {
11320Sstevel@tonic-gate 			/*
11330Sstevel@tonic-gate 			 * It is possible that the drive is busy writing the
11340Sstevel@tonic-gate 			 * buffered portion. So do not get upset yet.
11350Sstevel@tonic-gate 			 */
11360Sstevel@tonic-gate 			(void) sleep(10);
11370Sstevel@tonic-gate 			if (!finalize(target)) {
11380Sstevel@tonic-gate 				if (debug) {
11390Sstevel@tonic-gate 					(void) printf("status %x, %x/%x/%x\n",
11400Sstevel@tonic-gate 					    uscsi_status, SENSE_KEY(rqbuf),
11410Sstevel@tonic-gate 					    ASC(rqbuf), ASCQ(rqbuf));
11420Sstevel@tonic-gate 				}
11430Sstevel@tonic-gate 
11441305Sminht 				/*
11451305Sminht 				 * Different vendor drives return different
11461305Sminht 				 * sense error info for CLOSE SESSION command.
11471305Sminht 				 * The Panasonic drive that we are using is
11481305Sminht 				 * one such drive.
11491305Sminht 				 */
11501305Sminht 				if (device_type == DVD_MINUS) {
11510Sstevel@tonic-gate 					if (verbose) {
11520Sstevel@tonic-gate 						(void) printf(
11530Sstevel@tonic-gate 						    "skipping finalizing\n");
11540Sstevel@tonic-gate 					}
11550Sstevel@tonic-gate 				} else {
11560Sstevel@tonic-gate 
11570Sstevel@tonic-gate 			/* l10n_NOTE : 'failed' as in finishing up...failed  */
11580Sstevel@tonic-gate 					(void) printf(gettext("failed.\n"));
11590Sstevel@tonic-gate 
11600Sstevel@tonic-gate 					err_msg(gettext(
11610Sstevel@tonic-gate 					    "Could not finalize the disc.\n"));
11620Sstevel@tonic-gate 					exit(1);
11630Sstevel@tonic-gate 				}
11640Sstevel@tonic-gate 
11650Sstevel@tonic-gate 
11660Sstevel@tonic-gate 			}
11670Sstevel@tonic-gate 		}
11680Sstevel@tonic-gate 		if (vol_running) {
11690Sstevel@tonic-gate 			(void) eject_media(target);
11700Sstevel@tonic-gate 		}
11710Sstevel@tonic-gate 	} else if (check_device(target, CHECK_MEDIA_IS_NOT_BLANK)) {
11720Sstevel@tonic-gate 		/*
11730Sstevel@tonic-gate 		 * Some drives such as the pioneer A04 will retain a
11740Sstevel@tonic-gate 		 * ghost TOC after a simulation write is done. The
11750Sstevel@tonic-gate 		 * media will actually be blank, but the drive will
11760Sstevel@tonic-gate 		 * report a TOC. There is currently no other way to
11770Sstevel@tonic-gate 		 * re-initialize the media other than ejecting or
11780Sstevel@tonic-gate 		 * to ask the drive to clear the leadout. The laser
11790Sstevel@tonic-gate 		 * is currently off so nothing is written to the
11800Sstevel@tonic-gate 		 * media (on a good behaving drive).
11810Sstevel@tonic-gate 		 * NOTE that a device reset does not work to make
11820Sstevel@tonic-gate 		 * the drive re-initialize the media.
11830Sstevel@tonic-gate 		 */
11840Sstevel@tonic-gate 
1185808Srameshc 		blanking_type = "clear_ghost";
1186808Srameshc 		blank();
11870Sstevel@tonic-gate 
11880Sstevel@tonic-gate 	}
11890Sstevel@tonic-gate 	/* l10n_NOTE : 'done' as in "Finishing up...done"  */
11900Sstevel@tonic-gate 	(void) printf(gettext("done.\n"));
11910Sstevel@tonic-gate }
1192