Subject: kern/10114: *SCSI* support for DVD (syssrc/dev/scsipi/cd.c) broken
To: None <gnats-bugs@gnats.netbsd.org>
From: None <smd@ebone.net>
List: netbsd-bugs
Date: 05/13/2000 14:52:15
>Number:         10114
>Category:       kern
>Synopsis:       DVD support in syssrc/dev/scsipi/cd.c broken with some SCSI controllers
>Confidential:   no
>Severity:       serious
>Priority:       medium
>Responsible:    kern-bug-people
>State:          open
>Class:          sw-bug
>Submitter-Id:   net
>Arrival-Date:   Sat May 13 14:53:01 PDT 2000
>Closed-Date:
>Last-Modified:
>Originator:     Sean Doran
>Release:        cvs as of 2000 05 09
>Organization:
	
>Environment:
	
System: NetBSD crasse.smd.ebone.net 1.4Y NetBSD 1.4Y (SCREAM) #0: Tue May 9 21:44:12 CEST 2000 smd@crasse.smd.ebone.net:/usr/src/sys/arch/i386/compile/SCREAM i386


>Description:
	tstdvd fails (ftp://ftp.netbsd.org/pub/incoming/mycroft/dvd-19991031.tgz)
	css-auth stuff fails (www.linuxvideo.org)

	error depends on SCSI controller (usually works with aic(4) PCMCIA card)
	
for example, with ahc(4):

: crasse (es) ; ./tstdvd /dev/rcd0d
not Authenticated
Request AGID [1]...     AGID 0
Host sending challenge:  09 08 07 06 05 04 03 02 01 00
Send challenge to LU failed

console says:

ahc0: target 1 synchronous at 10.0MHz, offset = 0xf
cd0(ahc0:1:0):  Check Condition on CDB: 0xa3 00 00 00 00 00 00 00 00 10 01 00
    SENSE KEY:  Illegal Request
     ASC/ASCQ:  System Resource Failure

The DVD auth stuff in syssrc/dev/scsipi/cd.c was directly 
copied from Linux, and is the likely bug location.  

       
>How-To-Repeat:

	1. grab a _SCSI_ DVD drive (RAM or ROM), put it on adw(4), isp(4) 
	   or ahc(4) controller

	2. grab mycroft's tstdvd port, run it against /dev/rcdXc
	3. observe one of a variety of different failures, depending
	   on combination of controller + drive

>Fix:

This information may be useful...

Excerpted from

http://www.us.kernel.org/pub/linux/kernel/people/axboe/cdvd/dvd-cd-2.2.15-A.diff.gz
- --
+  3.08 May 1, 2000 - Jens Axboe <axboe@suse.de>
+  -- Always return -EROFS for write opens
+  -- Convert to module_init/module_exit style init and remove some
+  of the #ifdef MODULE stuff
+  -- Fix several dvd errors - DVD_LU_SEND_ASF should pass agid,
+  DVD_HOST_SEND_RPC_STATE did not set buffer size in cdb, and
+  dvd_do_auth passed uninitialized data to drive because init_cdrom_command
+  did not clear a 0 sized buffer.
- --
+/* DVD handling */
+
+#define copy_key(dest,src)     memcpy((dest), (src), sizeof(dvd_key))
+#define copy_chal(dest,src)    memcpy((dest), (src), sizeof(dvd_challenge))
+
+static void setup_report_key(struct cdrom_generic_command *cgc, unsigned agid, unsigned type)
+{
+       cgc->cmd[0] = GPCMD_REPORT_KEY;
+       cgc->cmd[10] = type | (agid << 6);
+       switch (type) {
+               case 0: case 8: case 5: {
+                       cgc->buflen = 8;
+                       break;
+               }
+               case 1: {
+                       cgc->buflen = 16;
+                       break;
+               }
+               case 2: case 4: {
+                       cgc->buflen = 12;
+                       break;
+               }
+       }
+       cgc->cmd[9] = cgc->buflen;
+}
+
+static void setup_send_key(struct cdrom_generic_command *cgc, unsigned agid, unsigned type)
+{
+       cgc->cmd[0] = GPCMD_SEND_KEY;
+       cgc->cmd[10] = type | (agid << 6);
+       switch (type) {
+               case 1: {
+                       cgc->buflen = 16;
+                       break;
+               }
+               case 3: {
+                       cgc->buflen = 12;
+                       break;
+               }
+               case 6: {
+                       cgc->buflen = 8;
+                       break;
+               }
+       }
+       cgc->cmd[9] = cgc->buflen;
+}
+
+static int dvd_do_auth(struct cdrom_device_info *cdi, dvd_authinfo *ai)
+{
+       int ret;
+       u_char buf[20];
+       struct cdrom_generic_command cgc;
+       struct cdrom_device_ops *cdo = cdi->ops;
+       rpc_state_t rpc_state;
+
+       memset(buf, 0, sizeof(buf));
+       init_cdrom_command(&cgc, buf, 0);
+
+       switch (ai->type) {
+       /* LU data send */
+       case DVD_LU_SEND_AGID:
+               cdinfo(CD_DVD, "entering DVD_LU_SEND_AGID\n"); 
+               setup_report_key(&cgc, ai->lsa.agid, 0);
+
+               if ((ret = cdo->generic_packet(cdi, &cgc)))
+                       return ret;
+
+               ai->lsa.agid = buf[7] >> 6;
+               /* Returning data, let host change state */
+               break;
+
+       case DVD_LU_SEND_KEY1:
+               cdinfo(CD_DVD, "entering DVD_LU_SEND_KEY1\n"); 
+               setup_report_key(&cgc, ai->lsk.agid, 2);
+
+               if ((ret = cdo->generic_packet(cdi, &cgc)))
+                       return ret;
+
+               copy_key(ai->lsk.key, &buf[4]);
+               /* Returning data, let host change state */
+               break;
+
+       case DVD_LU_SEND_CHALLENGE:
+               cdinfo(CD_DVD, "entering DVD_LU_SEND_CHALLENGE\n"); 
+               setup_report_key(&cgc, ai->lsc.agid, 1);
+
+               if ((ret = cdo->generic_packet(cdi, &cgc)))
+                       return ret;
+
+               copy_chal(ai->lsc.chal, &buf[4]);
+               /* Returning data, let host change state */
+               break;
+
+       /* Post-auth key */
+       case DVD_LU_SEND_TITLE_KEY:
+               cdinfo(CD_DVD, "entering DVD_LU_SEND_TITLE_KEY\n"); 
+               setup_report_key(&cgc, ai->lstk.agid, 4);
+               cgc.cmd[5] = ai->lstk.lba;
+               cgc.cmd[4] = ai->lstk.lba >> 8;
+               cgc.cmd[3] = ai->lstk.lba >> 16;
+               cgc.cmd[2] = ai->lstk.lba >> 24;
+
+               if ((ret = cdo->generic_packet(cdi, &cgc)))
+                       return ret;
+
+               ai->lstk.cpm = (buf[4] >> 7) & 1;
+               ai->lstk.cp_sec = (buf[4] >> 6) & 1;
+               ai->lstk.cgms = (buf[4] >> 4) & 3;
+               copy_key(ai->lstk.title_key, &buf[5]);
+               /* Returning data, let host change state */
+               break;
+
+       case DVD_LU_SEND_ASF:
+               cdinfo(CD_DVD, "entering DVD_LU_SEND_ASF\n"); 
+               setup_report_key(&cgc, ai->lsasf.agid, 5);
+               
+               if ((ret = cdo->generic_packet(cdi, &cgc)))
+                       return ret;
+
+               ai->lsasf.asf = buf[7] & 1;
+               break;
+
+       /* LU data receive (LU changes state) */
+       case DVD_HOST_SEND_CHALLENGE:
+               cdinfo(CD_DVD, "entering DVD_HOST_SEND_CHALLENGE\n"); 
+               setup_send_key(&cgc, ai->hsc.agid, 1);
+               buf[1] = 0xe;
+               copy_chal(&buf[4], ai->hsc.chal);
+
+               if ((ret = cdo->generic_packet(cdi, &cgc)))
+                       return ret;
+
+               ai->type = DVD_LU_SEND_KEY1;
+               break;
+
+       case DVD_HOST_SEND_KEY2:
+               cdinfo(CD_DVD, "entering DVD_HOST_SEND_KEY2\n"); 
+               setup_send_key(&cgc, ai->hsk.agid, 3);
+               buf[1] = 0xa;
+               copy_key(&buf[4], ai->hsk.key);
+
+               if ((ret = cdo->generic_packet(cdi, &cgc))) {
+                       ai->type = DVD_AUTH_FAILURE;
+                       return ret;
+               }
+               ai->type = DVD_AUTH_ESTABLISHED;
+               break;
+
+       /* Misc */
+       case DVD_INVALIDATE_AGID:
+               cdinfo(CD_DVD, "entering DVD_INVALIDATE_AGID\n"); 
+               setup_report_key(&cgc, ai->lsa.agid, 0x3f);
+               if ((ret = cdo->generic_packet(cdi, &cgc)))
+                       return ret;
+               break;
+
+       /* Get region settings */
+       case DVD_LU_SEND_RPC_STATE:
+               cdinfo(CD_DVD, "entering DVD_LU_SEND_RPC_STATE\n");
+               setup_report_key(&cgc, 0, 8);
+
+               init_cdrom_command(&cgc, &rpc_state, 0);
+               if ((ret = cdo->generic_packet(cdi, &cgc)))
+                       return ret;
+
+               ai->lrpcs.type = rpc_state.type_code;
+               ai->lrpcs.vra = rpc_state.vra;
+               ai->lrpcs.ucca = rpc_state.ucca;
+               ai->lrpcs.region_mask = rpc_state.region_mask;
+               ai->lrpcs.rpc_scheme = rpc_state.rpc_scheme;
+               break;
+
+               /* Set region settings */
+       case DVD_HOST_SEND_RPC_STATE:
+               cdinfo(CD_DVD, "entering DVD_HOST_SEND_RPC_STATE\n");
+               setup_send_key(&cgc, 0, 6);
+               buf[1] = 6;
+               buf[4] = ai->hrpcs.pdrc;
+
+               if ((ret = cdo->generic_packet(cdi, &cgc)))
+                       return ret;
+               break;
+
+       default:
+               cdinfo(CD_WARNING, "Invalid DVD key ioctl (%d)\n", ai->type);
+               return -ENOTTY;
+       }
+
+       return 0;
+}

+
+static int dvd_read_physical(struct cdrom_device_info *cdi, dvd_struct *s)
+{
+       int ret, i;
+       u_char buf[4 + 4 * 20], *base;
+       struct dvd_layer *layer;
+       struct cdrom_generic_command cgc;
+       struct cdrom_device_ops *cdo = cdi->ops;
+
+       init_cdrom_command(&cgc, buf, sizeof(buf));
+       cgc.cmd[0] = GPCMD_READ_DVD_STRUCTURE;
+       cgc.cmd[6] = s->physical.layer_num;
+       cgc.cmd[7] = s->type;
+       cgc.cmd[9] = cgc.buflen & 0xff;
+
+       if ((ret = cdo->generic_packet(cdi, &cgc)))
+               return ret;
+
+       base = &buf[4];
+       layer = &s->physical.layer[0];
+
+       /* place the data... really ugly, but at least we won't have to
+          worry about endianess in userspace or here. */
+       for (i = 0; i < 4; ++i, base += 20, ++layer) {
+               memset(layer, 0, sizeof(*layer));
+               layer->book_version = base[0] & 0xf;
+               layer->book_type = base[0] >> 4;
+               layer->min_rate = base[1] & 0xf;
+               layer->disc_size = base[1] >> 4;
+               layer->layer_type = base[2] & 0xf;
+               layer->track_path = (base[2] >> 4) & 1;
+               layer->nlayers = (base[2] >> 5) & 3;
+               layer->track_density = base[3] & 0xf;
+               layer->linear_density = base[3] >> 4;
+               layer->start_sector = base[5] << 16 | base[6] << 8 | base[7];
+               layer->end_sector = base[9] << 16 | base[10] << 8 | base[11];
+               layer->end_sector_l0 = base[13] << 16 | base[14] << 8 | base[15];
+               layer->bca = base[16] >> 7;
+       }
+
+       return 0;
+}
+
+static int dvd_read_copyright(struct cdrom_device_info *cdi, dvd_struct *s)
+{
+       int ret;
+       u_char buf[8];
+       struct cdrom_generic_command cgc;
+       struct cdrom_device_ops *cdo = cdi->ops;
+
+       init_cdrom_command(&cgc, buf, sizeof(buf));
+       cgc.cmd[0] = GPCMD_READ_DVD_STRUCTURE;
+       cgc.cmd[6] = s->copyright.layer_num;
+       cgc.cmd[7] = s->type;
+       cgc.cmd[8] = cgc.buflen >> 8;
+       cgc.cmd[9] = cgc.buflen & 0xff;
+
+       if ((ret = cdo->generic_packet(cdi, &cgc)))
+               return ret;
+
+       s->copyright.cpst = buf[4];
+       s->copyright.rmi = buf[5];
+
+       return 0;
+}
+
+static int dvd_read_disckey(struct cdrom_device_info *cdi, dvd_struct *s)
+{
+       int ret, size;
+       u_char *buf;
+       struct cdrom_generic_command cgc;
+       struct cdrom_device_ops *cdo = cdi->ops;
+
+       size = sizeof(s->disckey.value) + 4;
+
+       if ((buf = (u_char *) kmalloc(size, GFP_KERNEL)) == NULL)
+               return -ENOMEM;
+
+       init_cdrom_command(&cgc, buf, size);
+       cgc.cmd[0] = GPCMD_READ_DVD_STRUCTURE;
+       cgc.cmd[7] = s->type;
+       cgc.cmd[8] = size >> 8;
+       cgc.cmd[9] = size & 0xff;
+       cgc.cmd[10] = s->disckey.agid << 6;
+
+       if (!(ret = cdo->generic_packet(cdi, &cgc)))
+               memcpy(s->disckey.value, &buf[4], sizeof(s->disckey.value));
+
+       kfree(buf);
+       return ret;
+}
+
+static int dvd_read_bca(struct cdrom_device_info *cdi, dvd_struct *s)
+{
+       int ret;
+       u_char buf[4 + 188];
+       struct cdrom_generic_command cgc;
+       struct cdrom_device_ops *cdo = cdi->ops;
+
+       init_cdrom_command(&cgc, buf, sizeof(buf));
+       cgc.cmd[0] = GPCMD_READ_DVD_STRUCTURE;
+       cgc.cmd[7] = s->type;
+       cgc.cmd[9] = cgc.buflen = 0xff;
+
+       if ((ret = cdo->generic_packet(cdi, &cgc)))
+               return ret;
+
+       s->bca.len = buf[0] << 8 | buf[1];
+       if (s->bca.len < 12 || s->bca.len > 188) {
+               cdinfo(CD_WARNING, "Received invalid BCA length (%d)\n", s->bca.len);
+               return -EIO;
+       }
+       memcpy(s->bca.value, &buf[4], s->bca.len);
+
+       return 0;
+}
+
+static int dvd_read_manufact(struct cdrom_device_info *cdi, dvd_struct *s)
+{
+       int ret = 0, size;
+       u_char *buf;
+       struct cdrom_generic_command cgc;
+       struct cdrom_device_ops *cdo = cdi->ops;
+
+       size = sizeof(s->manufact.value) + 4;
+
+       if ((buf = (u_char *) kmalloc(size, GFP_KERNEL)) == NULL)
+               return -ENOMEM;
+
+       init_cdrom_command(&cgc, buf, size);
+       cgc.cmd[0] = GPCMD_READ_DVD_STRUCTURE;
+       cgc.cmd[7] = s->type;
+       cgc.cmd[8] = size >> 8;
+       cgc.cmd[9] = size & 0xff;
+
+       if ((ret = cdo->generic_packet(cdi, &cgc))) {
+               kfree(buf);
+               return ret;
+       }
+
+       s->manufact.len = buf[0] << 8 | buf[1];
+       if (s->manufact.len < 0 || s->manufact.len > 2048) {
+               cdinfo(CD_WARNING, "Received invalid manufacture info length"
+                                  " (%d)\n", s->bca.len);
+               ret = -EIO;
+       } else {
+               memcpy(s->manufact.value, &buf[4], s->manufact.len);
+       }
+
+       kfree(buf);
+       return ret;
+}
+
+static int dvd_read_struct(struct cdrom_device_info *cdi, dvd_struct *s)
+{
+       switch (s->type) {
+       case DVD_STRUCT_PHYSICAL:
+               return dvd_read_physical(cdi, s);
+
+       case DVD_STRUCT_COPYRIGHT:
+               return dvd_read_copyright(cdi, s);
+
+       case DVD_STRUCT_DISCKEY:
+               return dvd_read_disckey(cdi, s);
+
+       case DVD_STRUCT_BCA:
+               return dvd_read_bca(cdi, s);
+
+       case DVD_STRUCT_MANUFACT:
+               return dvd_read_manufact(cdi, s);
+               
+       default:
+               cdinfo(CD_WARNING, ": Invalid DVD structure read requested (%d)\n",
+                                       s->type);
+               return -EINVAL;
+       }
+}
+

... dispatch table ...


+       case DVD_READ_STRUCT: {
+               dvd_struct *s;
+               int size = sizeof(dvd_struct);
+               if (!CDROM_CAN(CDC_DVD))
+                       return -ENOSYS;
+               if ((s = (dvd_struct *) kmalloc(size, GFP_KERNEL)) == NULL)
+                       return -ENOMEM;
+               cdinfo(CD_DO_IOCTL, "entering DVD_READ_STRUCT\n"); 
+               if (copy_from_user(s, (dvd_struct *)arg, size)) {
+                       kfree(s);
+                       return -EFAULT;
+               }
+               if ((ret = dvd_read_struct(cdi, s))) {
+                       kfree(s);
+                       return ret;
+               }
+               if (copy_to_user((dvd_struct *)arg, s, size))
+                       ret = -EFAULT;
+               kfree(s);
+               return ret;
+               }
+
+       case DVD_AUTH: {
+               dvd_authinfo ai;
+               if (!CDROM_CAN(CDC_DVD))
+                       return -ENOSYS;
+               cdinfo(CD_DO_IOCTL, "entering DVD_AUTH\n"); 
+               IOCTL_IN(arg, dvd_authinfo, ai);
+               if ((ret = dvd_do_auth (cdi, &ai)))
+                       return ret;
+               IOCTL_OUT(arg, dvd_authinfo, ai);
+               return 0;
+               }
+
- --
+  3.08 May 1, 2000 - Jens Axboe <axboe@suse.de>
+  -- Always return -EROFS for write opens
+  -- Convert to module_init/module_exit style init and remove some
+  of the #ifdef MODULE stuff
+  -- Fix several dvd errors - DVD_LU_SEND_ASF should pass agid,
+  DVD_HOST_SEND_RPC_STATE did not set buffer size in cdb, and
+  dvd_do_auth passed uninitialized data to drive because init_cdrom_command
+  did not clear a 0 sized buffer.
+ 
>Release-Note:
>Audit-Trail:
>Unformatted: