From peterh@talri.sapros.com  Wed Jul 10 13:48:58 2002
Return-Path: <peterh@talri.sapros.com>
Received: from mx1.FreeBSD.org (mx1.FreeBSD.org [216.136.204.125])
	by hub.freebsd.org (Postfix) with ESMTP id B4C7937B400
	for <FreeBSD-gnats-submit@freebsd.org>; Wed, 10 Jul 2002 13:48:58 -0700 (PDT)
Received: from talri.sapros.com (rularan.sapros.com [204.182.55.17])
	by mx1.FreeBSD.org (Postfix) with ESMTP id 247EB43E09
	for <FreeBSD-gnats-submit@freebsd.org>; Wed, 10 Jul 2002 13:48:58 -0700 (PDT)
	(envelope-from peterh@talri.sapros.com)
Received: from talri.sapros.com (localhost [127.0.0.1])
	by talri.sapros.com (8.12.3/8.12.3) with ESMTP id g6AKms7W000327
	for <FreeBSD-gnats-submit@freebsd.org>; Wed, 10 Jul 2002 13:48:54 -0700 (PDT)
	(envelope-from peterh@talri.sapros.com)
Message-Id: <200207102048.g6AKms7W000327@talri.sapros.com>
Date: Wed, 10 Jul 2002 13:48:54 -0700
From: Peter Haight <peterh@sapros.com>
Sender: peterh@talri.sapros.com
Reply-To: Peter Haight <peterh@sapros.com>
To: FreeBSD-gnats-submit@freebsd.org
Subject: Writing to DVD+RW using burncd does not work.
X-Send-Pr-Version: 3.113
X-GNATS-Notify:

>Number:         40430
>Category:       misc
>Synopsis:       Writing to DVD+RW using burncd does not work.
>Confidential:   no
>Severity:       non-critical
>Priority:       low
>Responsible:    freebsd-bugs
>State:          closed
>Quarter:        
>Keywords:       
>Date-Required:  
>Class:          change-request
>Submitter-Id:   current-users
>Arrival-Date:   Wed Jul 10 13:50:03 PDT 2002
>Closed-Date:    Thu Aug 08 01:02:27 PDT 2002
>Last-Modified:  Thu Aug 08 01:02:27 PDT 2002
>Originator:     Peter Haight
>Release:        FreeBSD 4.6-STABLE i386
>Organization:
>Environment:
System: FreeBSD talri.sapros.com 4.6-STABLE FreeBSD 4.6-STABLE #15: Sun Jun 30 18:34:25 PDT 2002 peterh@talri.sapros.com:/usr/src/sys/compile/TALRI i386

Sony DVD+RW DRU-120A IDE

>Description:

When you try to use burncd to write to a DVD+RW, you get an Input/Output
error. I've come up with a patch that lets you write to a DVD+RW using
burncd.


>How-To-Repeat:

burncd -d /dev/acd0c data foo.iso


>Fix:

I've provided a patch to the kernel and burncd that lets you write to DVD+RW
drives.

There are two reasons why it wasn't working before this patch. The first is
that DVD+RW media needs to be formatted before it is used. You can
accomplish this using the ATAPI_FORMAT_UNIT command. I added an ioctl
command that will let you read the format capacities (DVDREADFORMATCAPS) and
then use one of those format capacity descriptiors to format the DVD+RW
(DVDFORMATUNIT).  I added code to burncd to execute these commands. (You can
use 'burncd format' to make it do this).

The other problem was that burncd expects tracks like on a CD-R and DVD+RW
doesn't really have tracks. I added a new write command to burncd to take
this lack of tracks into account. You can use 'burncd dvdwrite <file_name>'
to use this method of writing.

Here's the patch. It is against 4.6-STABLE that I cvsupped from 6/30/02.


--- sys/dev/ata/atapi-all.h.orig	Sun Jun 30 20:07:17 2002
+++ sys/dev/ata/atapi-all.h	Sat Jun 29 15:37:22 2002
@@ -66,6 +66,7 @@
 #define ATAPI_TEST_UNIT_READY		0x00	/* check if device is ready */
 #define ATAPI_REZERO			0x01	/* rewind */
 #define ATAPI_REQUEST_SENSE		0x03	/* get sense data */
+#define ATAPI_FORMAT_UNIT		0x04	/* format unit */
 #define ATAPI_READ			0x08	/* read data */
 #define ATAPI_WRITE			0x0a	/* write data */
 #define ATAPI_WEOF			0x10	/* write filemark */
@@ -81,6 +82,7 @@
 #define	    SS_RETENSION			0x02
 #define	    SS_EJECT				0x04
 #define ATAPI_PREVENT_ALLOW		0x1e	/* media removal */
+#define ATAPI_READ_FORMAT_CAPACITIES	0x23	/* get format capacities */
 #define ATAPI_READ_CAPACITY		0x25	/* get volume capacity */
 #define ATAPI_READ_BIG			0x28	/* read data */
 #define ATAPI_WRITE_BIG			0x2a	/* write data */
--- sys/dev/ata/atapi-cd.c.orig	Sat Jun 29 13:11:37 2002
+++ sys/dev/ata/atapi-cd.c	Sun Jun 30 18:34:06 2002
@@ -100,6 +100,10 @@
 static int acd_mode_select(struct acd_softc *, caddr_t, int);
 static int acd_set_speed(struct acd_softc *, int, int);
 static void acd_get_cap(struct acd_softc *);
+static int acd_read_format_caps(struct acd_softc *, struct dvd_format_cap_trans *);
+static int acd_format_unit(struct acd_softc *, struct dvd_format_params *);
+static int acd_get_format_progress(struct acd_softc *, int *);
+
 
 /* internal vars */
 static u_int32_t acd_lun_map = 0;
@@ -1046,6 +1050,18 @@
 	((struct partinfo *)addr)->part = &cdp->disklabel.d_partitions[0];
 	break;
 
+    case DVDIOREADFORMATCAPS:
+    	error = acd_read_format_caps(cdp, (struct dvd_format_cap_trans *)addr);
+	break;
+
+    case DVDIOFORMATUNIT:
+    	error = acd_format_unit(cdp, (struct dvd_format_params *)addr);
+	break;
+
+    case DVDIOFORMATPROGRESS:
+    	error = acd_get_format_progress(cdp, (int *) addr);
+	break;
+
     default:
 	error = ENOTTY;
     }
@@ -1991,4 +2007,69 @@
     cdp->cap.cur_write_speed = max(ntohs(cdp->cap.cur_write_speed), 177);
     cdp->cap.max_vol_levels = ntohs(cdp->cap.max_vol_levels);
     cdp->cap.buf_size = ntohs(cdp->cap.buf_size);
+}
+
+static int
+acd_read_format_caps(struct acd_softc *cdp, struct dvd_format_cap_trans* trans)
+{
+    int error;
+    int8_t *kernel_buf;
+    int8_t ccb[16] = { ATAPI_READ_FORMAT_CAPACITIES, 0, 0, 0, 0, 0, 0,
+		       (trans->buf_len >> 8) & 0xff, trans->buf_len & 0xff, 
+		       0, 0, 0, 0, 0, 0, 0 };
+    
+    kernel_buf = malloc(trans->buf_len, M_ACD, M_NOWAIT);
+    if (kernel_buf == NULL) {
+	return ENOMEM;
+    }
+
+#define DVD_READ_FORMAT_CAPS_TIMEOUT 30
+    error = atapi_queue_cmd(cdp->device, ccb, kernel_buf, trans->buf_len,
+			    ATPR_F_READ, DVD_READ_FORMAT_CAPS_TIMEOUT, 
+			    NULL, NULL);
+    if (!error) {
+	error = copyout(kernel_buf, trans->buf, trans->buf_len);
+    }
+
+    free(kernel_buf, M_ACD);
+    
+    return error;
+}
+
+static int
+acd_format_unit(struct acd_softc *cdp, struct dvd_format_params* params)
+{
+    int error;
+    int8_t ccb[16] = { ATAPI_FORMAT_UNIT, 0x11, 0, 0, 0, 0, 0, 0, 0, 0, 
+		       0, 0, 0, 0, 0, 0 };
+
+#define DVD_FORMAT_UNIT_TIMEOUT 30
+    error = atapi_queue_cmd(cdp->device, ccb, (u_int8_t *)params, 
+			    sizeof(struct dvd_format_params), 0,
+			    DVD_FORMAT_UNIT_TIMEOUT, NULL, NULL);
+
+    return error;
+}
+
+static int
+acd_get_format_progress(struct acd_softc *cdp, int *finished)
+{
+    int error;
+    int8_t ccb[16] = { ATAPI_REQUEST_SENSE, 0, 0, 0, 
+		       sizeof(struct atapi_reqsense), 0, 0, 0,  
+		       0, 0, 0, 0, 0, 0, 0, 0 };
+    struct atapi_reqsense response;
+    
+    error = atapi_queue_cmd(cdp->device, ccb, (u_int8_t *) &response, 
+			    sizeof(struct atapi_reqsense), ATPR_F_READ, 
+			    30, NULL, NULL);
+    if (error)
+    {
+	return error; 
+    }
+    if (response.sksv)
+	*finished = ((response.sk_specific2 | (response.sk_specific1 << 8)) * 100) / 65535;
+    else
+	*finished = 0;
+    return 0;
 }
--- sys/sys/dvdio.h.orig	Sun Jun 30 20:10:24 2002
+++ sys/sys/dvdio.h	Sun Jun 30 18:24:03 2002
@@ -107,4 +107,46 @@
 #define DVDIOCSENDKEY		_IOWR('c', 201, struct dvd_authinfo)
 #define DVDIOCREADSTRUCTURE	_IOWR('c', 202, struct dvd_struct)
 
+struct dvd_format_cap {
+	u_int32_t num_blocks;
+	u_int8_t reserved:2;
+	u_int8_t format_type:6;
+	u_int8_t type_param[3];
+};
+
+struct dvd_format_cap_list_header {
+	u_int8_t reserved[3];
+	u_int8_t cap_list_len;
+};
+
+struct dvd_cur_cap_desc {
+	u_int32_t num_blocks;
+	u_int8_t descriptor_type:2;
+	u_int8_t reserved:6;
+	u_int8_t block_len[3];
+};
+
+struct dvd_format_cap_trans {
+	u_int16_t buf_len;
+	u_int8_t *buf;
+};
+
+struct dvd_format_params {
+	u_int8_t reserved;
+	u_int8_t vs:1;
+	u_int8_t immed:1;
+	u_int8_t try_out:1;
+	u_int8_t ip:1;
+	u_int8_t stpf:1;
+	u_int8_t dcrt:1;
+	u_int8_t dpry:1;
+	u_int8_t fov:1;
+	u_int16_t desc_len;
+	struct dvd_format_cap desc;
+};
+
+#define DVDIOREADFORMATCAPS	_IOWR('c', 203, struct dvd_format_cap_trans)
+#define DVDIOFORMATUNIT		_IOW('c', 204, struct dvd_format_params)
+#define DVDIOFORMATPROGRESS	_IOR('c', 205, struct dvd_format_params)
+
 #endif _SYS_DVDIO_H_
--- usr.sbin/burncd/burncd.c.orig	Sat Jun 29 16:09:23 2002
+++ usr.sbin/burncd/burncd.c	Sun Jun 30 19:39:48 2002
@@ -40,6 +40,7 @@
 #include <sys/stat.h>
 #include <sys/cdio.h>
 #include <sys/cdrio.h>
+#include <sys/dvdio.h>
 #include <sys/param.h>
 
 #define BLOCKS	16
@@ -58,7 +59,8 @@
 
 void add_track(char *, int, int, int);
 void do_DAO(int, int);
-void do_TAO(int, int);
+void do_TAO(int, int, int);
+void do_format(int, int);
 int write_file(struct track_info *);
 int roundup_blocks(struct track_info *);
 void cue_ent(struct cdr_cue_entry *, int, int, int, int, int, int, int);
@@ -70,11 +72,11 @@
 {
 	int ch, arg, addr;
 	int dao = 0, eject = 0, fixate = 0, list = 0, multi = 0, preemp = 0;
-	int nogap = 0, speed = 4, test_write = 0;
-	int block_size = 0, block_type = 0, cdopen = 0;
+	int nogap = 0, speed = 4, test_write = 0, format = 0, force_format = 0;
+	int block_size = 0, block_type = 0, cdopen = 0, dvdprw=0;
 	const char *dev = "/dev/acd0c";
 
-	while ((ch = getopt(argc, argv, "def:lmnpqs:tv")) != -1) {
+	while ((ch = getopt(argc, argv, "def:Flmnpqs:tv")) != -1) {
 		switch (ch) {
 		case 'd':
 			dao = 1;
@@ -87,6 +89,10 @@
 		case 'f':
 			dev = optarg;
 			break;
+	
+		case 'F':
+			force_format = 1;
+			break;
 
 		case 'l':
 			list = 1;
@@ -148,6 +154,10 @@
 			fixate = 1;
 			break;
 		}
+		if (!strcasecmp(argv[arg], "format")) {
+			format = 1;
+			break;
+		}
 		if (!strcasecmp(argv[arg], "msinfo")) {
 		        struct ioc_read_toc_single_entry entry;
 			struct ioc_toc_header header;
@@ -226,6 +236,13 @@
 			nogap = 1;
 			continue;
 		}
+		if (!strcasecmp(argv[arg], "dvdprw")) {
+			block_type = CDR_DB_ROM_MODE1;
+			block_size = 2048;
+			dvdprw = 1;
+			continue;
+		}
+
 		if (!block_size)
 			err(EX_NOINPUT, "no data format selected");
 		if (list) {
@@ -251,6 +268,8 @@
 			add_track(argv[arg], block_size, block_type, nogap);
 	}
 	if (notracks) {
+		if (dvdprw && notracks > 1)
+			err(EX_USAGE, "DVD+RW drives can only write one track");
 		if (ioctl(fd, CDIOCSTART, 0) < 0)
 			err(EX_IOERR, "ioctl(CDIOCSTART)");
 		if (!cdopen) {
@@ -261,7 +280,7 @@
 		if (dao) 
 			do_DAO(test_write, multi);
 		else
-			do_TAO(test_write, preemp);
+			do_TAO(test_write, preemp, dvdprw);
 	}
 	if (fixate && !dao) {
 		if (!quiet)
@@ -270,6 +289,10 @@
         		err(EX_IOERR, "ioctl(CDRIOCFIXATE)");
 	}
 
+	if (format) {
+		do_format(fd, force_format);
+	}
+
 	if (ioctl(fd, CDRIOCSETBLOCKSIZE, &saved_block_size) < 0) {
 		err_set_exit(NULL);
 		err(EX_IOERR, "ioctl(CDRIOCSETBLOCKSIZE)");
@@ -435,7 +458,7 @@
 }
 
 void
-do_TAO(int test_write, int preemp)
+do_TAO(int test_write, int preemp, int dvdprw)
 {
 	struct cdr_track track;
 	int i;
@@ -447,8 +470,13 @@
 		if (ioctl(fd, CDRIOCINITTRACK, &track) < 0)
 			err(EX_IOERR, "ioctl(CDRIOCINITTRACK)");
 
-		if (ioctl(fd, CDRIOCNEXTWRITEABLEADDR, &tracks[i].addr) < 0)
-			err(EX_IOERR, "ioctl(CDRIOCNEXTWRITEABLEADDR)");
+		if (dvdprw)
+			tracks[i].addr = 0;
+		else
+			if (ioctl(fd, CDRIOCNEXTWRITEABLEADDR, 
+				  &tracks[i].addr) < 0)
+				err(EX_IOERR, "ioctl(CDRIOCNEXTWRITEABLEADDR)");
+
 		if (!quiet)
 			fprintf(stderr, "next writeable LBA %d\n",
 				tracks[i].addr);
@@ -457,6 +485,86 @@
 		if (ioctl(fd, CDRIOCFLUSH) < 0)
 			err(EX_IOERR, "ioctl(CDRIOCFLUSH)");
 	}
+}
+
+#define BURNCD_MAX_FORMAT_CAP_LEN 1024
+void
+do_format(int fd, int force_format)
+{
+	int result;
+	int i;
+	int percent;
+	int started;
+	int num_caps;
+	int cap_to_use = -1;
+	u_int8_t *buf_pos;
+	struct dvd_format_cap_list_header *cap_list_header;
+	struct dvd_cur_cap_desc *cap_desc;
+	struct dvd_format_cap *format_cap;
+	struct dvd_format_cap_trans trans;
+	struct dvd_format_params format_params;
+	u_int8_t fmt_cap_buf[BURNCD_MAX_FORMAT_CAP_LEN];
+
+	trans.buf_len = BURNCD_MAX_FORMAT_CAP_LEN;
+	trans.buf = fmt_cap_buf;
+	if (ioctl(fd, DVDIOREADFORMATCAPS, &trans) == -1)
+		err(EX_IOERR, "ioctl(DVDIOREADFORMATCAPS)");
+
+	buf_pos = trans.buf;
+	cap_list_header = (struct dvd_format_cap_list_header *) buf_pos;
+	if (cap_list_header->cap_list_len + 
+	    sizeof(struct dvd_format_cap_list_header) + 
+	    sizeof(struct dvd_cur_cap_desc) > BURNCD_MAX_FORMAT_CAP_LEN)
+	    err(EX_SOFTWARE, "did not allocate enough memory for cap list");
+
+	buf_pos += sizeof(struct dvd_format_cap_list_header);
+	cap_desc = (struct dvd_cur_cap_desc *) buf_pos;
+	if (verbose) {
+		fprintf(stderr, "format_cap_header: cap_list_len=%d\n", 
+			cap_list_header->cap_list_len);
+		fprintf(stderr, "cur_cap_desc: num_blocks=%u, descriptor_type=0x%x, block_len=%u\n", ntohl(cap_desc->num_blocks), cap_desc->descriptor_type, cap_desc->block_len[0] << 16 | cap_desc->block_len[1] << 8 | cap_desc->block_len[2]);
+	}
+	buf_pos += sizeof(struct dvd_cur_cap_desc);
+
+	num_caps = cap_list_header->cap_list_len/sizeof(struct dvd_format_cap);
+	for (i = 0; i < num_caps; ++i) {
+		format_cap = (struct dvd_format_cap *) buf_pos;
+		if (verbose)
+			fprintf(stderr, "format_cap: num_blocks=%u, format_type=0x%x, type_param=%u\n", ntohl(format_cap->num_blocks), format_cap->format_type, format_cap->type_param[0] << 16 | format_cap->type_param[1] << 8 | format_cap->type_param[2]);
+		if (format_cap->format_type == 0x26)
+			cap_to_use = i;
+		buf_pos += sizeof(struct dvd_format_cap);
+	}
+	if (cap_to_use == -1)
+		err(EX_IOERR, "could not finde a format capacity with format type = 0x26.");
+	
+	if (!force_format && cap_desc->descriptor_type == 2)
+		err(EX_IOERR, "the media in the drive is already formatted (use -F to override)");
+
+	memset(&format_params, 0, sizeof(struct dvd_format_params));
+	format_params.immed = 1;
+	format_params.desc_len = ntohs(sizeof(struct dvd_format_cap));
+	buf_pos = trans.buf + sizeof(struct dvd_format_cap_list_header) +
+		  sizeof(struct dvd_cur_cap_desc) + 
+		  (cap_to_use * sizeof(struct dvd_format_cap));
+	memcpy(&format_params.desc, buf_pos, sizeof(struct dvd_format_cap));
+	if(ioctl(fd, DVDIOFORMATUNIT, &format_params) == -1)
+		err(EX_IOERR, "ioctl(DVDIOFORMATUNIT)");
+
+	started = 0;
+	while (1) {
+		sleep(1);
+		if (ioctl(fd, DVDIOFORMATPROGRESS, &percent) == -1)
+			err(EX_IOERR, "ioctl(DVDIOFORMATPROGRESS)");
+		if (percent > 0)
+			started = 1;
+		if (percent > 0 && !quiet)
+			fprintf(stderr, "formating DVD - %d %% done     \r", 
+				percent);
+		if (percent == 100 || (started && percent == 0))
+			break;
+	}
+
 }
 
 int


>Release-Note:
>Audit-Trail:

From: Soeren Schmidt <sos@freebsd.dk>
To: Peter Haight <peterh@sapros.com>
Cc: FreeBSD-gnats-submit@FreeBSD.ORG
Subject: Re: misc/40430: Writing to DVD+RW using burncd does not work.
Date: Wed, 10 Jul 2002 22:58:25 +0200 (CEST)

 It seems Peter Haight wrote:
 > >Description:
 > 
 > When you try to use burncd to write to a DVD+RW, you get an Input/Output
 > error. I've come up with a patch that lets you write to a DVD+RW using
 > burncd.
 
 I have something semilar in the queue, but its not finished yet (I just
 recently got a DVD+RW drive). One thing I do differently though is to
 just use the DVD+RW like the DVD-RAM ie R/W media, no need to "burn"
 the data with burncd, just the format needs to be done on blank media..
 
 Thanks for the patch anyways, I'll combine it with my own work, and
 get support committed soon...
 
 -Sren

From: Gregory Bond <gnb@itga.com.au>
To: Peter Haight <peterh@sapros.com>
Cc: FreeBSD-gnats-submit@FreeBSD.ORG
Subject: Re: misc/40430: Writing to DVD+RW using burncd does not work. 
Date: Thu, 11 Jul 2002 10:18:46 +1000

 I am not a kernel hacker, but a few things look a bit odd to me, and I am 
 seeking enlightenment:
 
 [acd ioctl handling]
 > +    case DVDIOFORMATPROGRESS:
 > +    	error = acd_get_format_progress(cdp, (int *) addr);
 
 [dvdio.h]
 >+#define DVDIOFORMATPROGRESS	_IOR('c', 205, struct dvd_format_params)
 
 Isn't this inconsistent with the above code?  Shouldn't it be "int' instead of 
 'struct dvd_format_params'?
 
 > +static int
 > +acd_get_format_progress(struct acd_softc *cdp, int *finished)
 > +{
 [snip]
 > +    if (response.sksv)
 > +	*finished = ((response.sk_specific2 | (response.sk_specific1 << 8)) * 1
 > 00) / 65535;
 > +    else
 > +	*finished = 0;
 > +    return 0;
 
 Doesn't this need to use copyout()?  What happens if a user program passes 
 NULL to the ioctl?
 
 Greg,
 whose memory of Leffler, McKusick et al is fading fast!
 
 
State-Changed-From-To: open->closed 
State-Changed-By: sos 
State-Changed-When: Thu Aug 8 00:59:45 PDT 2002 
State-Changed-Why:  
I've just committed the merge of this and my own work to -current. 
Thanks for the patches anyways! 
For those that do have a DVD-RW driver they coould try this also 
just use 'format dvd-rw' in burncd for that operation, writing 
should be the same, but beware I've no idea if it actually works :)  

http://www.freebsd.org/cgi/query-pr.cgi?pr=40430 
>Unformatted:
