[xiph-commits] r15183 - in trunk/cdparanoia: . interface

xiphmont at svn.xiph.org xiphmont at svn.xiph.org
Thu Aug 14 16:24:00 PDT 2008


Author: xiphmont
Date: 2008-08-14 16:23:59 -0700 (Thu, 14 Aug 2008)
New Revision: 15183

Modified:
   trunk/cdparanoia/interface/cdda_interface.h
   trunk/cdparanoia/interface/common_interface.c
   trunk/cdparanoia/interface/cooked_interface.c
   trunk/cdparanoia/interface/interface.c
   trunk/cdparanoia/interface/low_interface.h
   trunk/cdparanoia/interface/scan_devices.c
   trunk/cdparanoia/interface/scsi_interface.c
   trunk/cdparanoia/main.c
Log:
Commit ongoing cache timing analysis code.
Also modify internal setup flow to allow setspeed to be issued before cdda_open()
Add SCSI version of setspeed.



Modified: trunk/cdparanoia/interface/cdda_interface.h
===================================================================
--- trunk/cdparanoia/interface/cdda_interface.h	2008-08-14 16:51:39 UTC (rev 15182)
+++ trunk/cdparanoia/interface/cdda_interface.h	2008-08-14 23:23:59 UTC (rev 15183)
@@ -198,7 +198,10 @@
 402: Track not audio data
 403: No audio tracks on disc
 404: No medium present
+405: Option not supported by drive
 
+500: Drive timing analysis read failure
+501: Drive timing analysis indeterminate
 */
 #endif
 

Modified: trunk/cdparanoia/interface/common_interface.c
===================================================================
--- trunk/cdparanoia/interface/common_interface.c	2008-08-14 16:51:39 UTC (rev 15182)
+++ trunk/cdparanoia/interface/common_interface.c	2008-08-14 23:23:59 UTC (rev 15183)
@@ -175,42 +175,50 @@
    For that reason, we need to empirically determine cache size used
    for reads */
 
-int cdda_cache_sectors(cdrom_drive *d){
+int analyze_timing_and_cache(cdrom_drive *d){
 
   /* Some assumptions about timing: 
 
-     The physically fastest drives are about 50x, and usually only at
-     the rim.  This has been stable for nearly ten years at this
-     point.  It's possible to make faster drives using multiple read
-     pickups and interleaving, but it doesn't appear anyone cares to.
+     We can't perform cache determination timing based on looking at
+     average transfer times; on slow setups, the speed of a drive
+     reading sectors via PIO will not be reliably distinguishable from
+     the same drive returning data from the cache via pio.  We need
+     something even more noticable and reliable: the seek time.  A
+     seek will reliably be approximately 1.5 orders of magnitude
+     faster than a sequential sector access or cache hit, and slower
+     systems will also tend to have slower seeks.  It is unlikely we'd
+     ever see a seek latency of under ~10ms given the synchronization
+     requirements of a CD and the maximum possible rotational
+     velocity.
 
-     The slowest interface we're likely to see is UDMA40, which would
-     probably be able to maintain 100x in practice to a chain with a
-     single device on an otherwise unloaded machine.  This is a bit
-     too close for comfort to rely on simple timing thresholding,
-     especially as the kernel is going to be inserting its own
-     unpredictable timing latencies.
+     Further complicating things, we have to watch the data collection
+     carefully as we're not always going to be on an unloaded system,
+     and we even have to guard against other apps accessing the drive
+     (something that should never happen on purpose, but could happen
+     by accident).  As we know in our testing when seeks should never
+     occur, a sudden seek-sized latency popping up in the middle of a
+     collection is an indication that collection is possibly invalid.
 
-     The bus itself adds a timing overhead; the SATA 150 machines at my disposal appear to fairly universally insert a roughly .06ms/sector (~200x) transfer latency. PATA UDMA 133 appears to be ~ .1 ms/sector.
+     A second cause of 'spurious latency' would be media damage; if
+     we're consistently hitting latency on the same sector during
+     initial collection, may need to move past it. */
 
-It's possible to make
-     drives faster (multiread, etc), but actual bus throughput at the
-     moment only abounts to 100x-200x,
-
-  /* let's assume the (physically) fastest drives are 60x; this is true in practice, and drives that fast are usually only that fast out at the rim */
-  float ms_per_sector = 1./75./100.*1000;
-  int i;
-  int lo = 75;
-  int current = lo;
-  int max = 75*256;
+  int i,ret;
   int firstsector=-1;
   int lastsector=-1;
   int firsttest=-1;
   int lasttest=-1;
-  int under=1;
+  char buffer[80];
+  int max_retries=20;
+  float median;
+  int offset;
 
-  cdmessage(d,"\nChecking CDROM drive cache behavior...\n");
+  /* set up a default pessimal take on drive behavior */
+  d->private->cache_backseekflush=0;
+  d->private->cache_sectors=1200;
 
+  cdmessage(d,"\nChecking drive timing behavior...");
+
   /* find the longest stretch of available audio data */
 
   for(i=0;i<d->tracks;i++){
@@ -229,28 +237,156 @@
   }
 
   if(firstsector==-1){
-    cdmessage(d,"\n\tNo audio on disc; Cannot determine audio caching behavior.\n");
+    cdmessage(d,"\n\tNo audio on disc; Cannot determine timing behavior...");
     return -1;
   }
 
-  while(current <= max && under){
-    int offset = (lastsector - firstsector - current)/2; 
+  /* initial timing data collection of 100 sequential sectors; we need a median, verify an initial seek */
+  {
+    int x;
+    int current=100;
+    int histogram[10000];
+    int latency[current];
+    int retry;
+    offset = (lastsector - firstsector - current)*2/3 + firstsector;
+
+    for(retry=0;retry<max_retries;retry++){
+      int acc=0;
+      int prev=0;
+
+      if(retry){
+	offset-=current+1;
+	offset-=offset/32;
+      }
+      if(offset<firstsector)break;
+      
+      memset(histogram,0,sizeof(histogram));
+      if((ret=d->read_audio(d,NULL,offset+current+1,1))<0){
+	/* media error! grr!  retry elsewhere */
+	cdmessage(d,"\n\tWARNING: unrecoverable media error while performing test"
+		  "\n\treads; picking new location and trying again.");
+	continue;
+      }
+      for(i=0;i<current;i++){
+	if(d->read_audio(d,NULL,offset+i,1)<0){
+	  /* media error! grr!  retry elsewhere */
+	  cdmessage(d,"\n\tWARNING: unrecoverable media error while performing test"
+		    "\n\treads; picking new location and trying again.");
+	  break;
+	}
+	x = d->private->last_milliseconds;
+	if(x>9999)x=9999;
+	if(x<0)x=0;
+	histogram[x]++;
+	latency[i]=x;
+      }
+      if(i<current){
+	offset-=current+1;
+	continue;
+      }	
+
+      for(i=0;i<10000;i++){
+	prev=acc;
+	acc+=histogram[i];
+	if(acc>current/2) break;
+      }
+      
+      median = (i*(acc-prev) + (i-1)*prev)/(float)acc;
+      
+      snprintf(buffer,80,"\n\tsmall seek latency (%d sectors): %d ms",current,latency[0]);
+      cdmessage(d,buffer);
+      snprintf(buffer,80,"\n\tmedian read latency per sector: %.1f ms",median);
+      cdmessage(d,buffer);
+
+      /* verify slow spinup did not compromise median */
+      for(i=1;i<current;i++)
+	if(latency[i]>latency[i-1] || latency[i]<=(median+1.))break;
+      if(i>5){
+	cdmessage(d,"\n\tDrive appears to spin up slowly... retrying...");
+	offset-=current+1;
+	continue;
+      }
+
+      /* verify against spurious latency; any additional 5x blocks that
+	 are not continuous with read start */
+      acc=0;
+      if(median<.6)median=.6;
+      for(i=5;i<current;i++)
+	if(latency[i]>median*10)acc++;
+
+      if(acc){
+	cderror(d,"\n\tWARNING: Read timing displayed bursts of unexpected"
+		"\n\tlatency; retrying for a clean read.\n");
+	continue;
+      }
+	
+      break;
+    }
+
+    if(offset<firstsector){
+      cderror(d,"\n500: Unable to find sufficiently large area of"
+	      "\n\tgood media to perform timing tests.  Aborting.\n");
+      return -500;
+    }
+    if(retry==max_retries){
+      cderror(d,"\n500: Too many retries; aborting analysis.\n");
+      return -500;
+    }    
+  }
+  
+  /* look to see if drive is caching at all; read first sector N
+     times, if any reads are near or under the median latency, we're
+     caching */
+  {
+    for(i=0;i<max_retries;i++){
+      if(d->read_audio(d,NULL,offset,1)==1){
+	if(d->private->last_milliseconds<median*10) break;
+
+      }else{
+	/* error handling */
+      }
+    }
+
+    if(i<max_retries){
+      cdmessage(d,"\n\tCaching test result: DRIVE IS CACHING (bad)\n");
+    }else{
+      cdmessage(d,"\n\tCaching test result: Drive is not caching (good)\n");
+      d->private->cache_sectors=0;
+      return 0;
+    }
+  }
+
+
+
+
+      
+
+
+  /* bisection search on cache size */
+
+  int lo=10;
+  int hi=15000;
+  int current=lo;
+  int under=1;
+  float ms_per_sector = 1./75.*1000./100.;
+  d->nsectors=1;
+  while(current <= hi && under){
+    int offset = (lastsector - firstsector - current)/2+firstsector; 
     int i,j;
     under=0;
 
     {
       char buffer[80];
-      snprintf(buffer,80,"\tTesting reads for caching (%d sectors):\n\t",current);
+      snprintf(buffer,80,"\n\tTesting reads for caching (%d sectors):\n\t",current);
       cdmessage(d,buffer);
     }
 
     for(i=0;i<10;i++){
       int sofar=0;
       int fulltime=0;
-      offset--;
+
       while(sofar<current){
-	for(j=0;;j++){
-	  
+	for(j=0;;j++){	  
 	  int readsectors = d->read_audio(d,NULL,offset+sofar,current-sofar);
 	  if(readsectors<=0){
 	    if(j==2){
@@ -259,7 +395,6 @@
 	      return(-1);
 	    }
 	  }else{
-	    sofar+=readsectors;
 	    if(d->private->last_milliseconds==-1){
 	      if(j==2){
 		d->enable_cdda(d,0);
@@ -268,35 +403,29 @@
 	      }
 	    }else{
 
-	      fprintf(stderr,">%d:%dms ",readsectors, d->private->last_milliseconds);
-	      fulltime += d->private->last_milliseconds;
+	      if(sofar==0){
+		fprintf(stderr,">%d:%dms ",readsectors, d->private->last_milliseconds);
+		fulltime = d->private->last_milliseconds;
+	      }
+	      sofar+=readsectors;
 	      break;
 	    }
 	  }
 	}
       }
-      {
-	char buffer[80];
-	snprintf(buffer,80," %d:%fms/sec",i,(float)fulltime/current);
-	cdmessage(d,buffer);
-      }
-      if((float)fulltime/current < ms_per_sector) under=1;
+      if(fulltime < median*10) under=1;
     }
     cdmessage(d,"\n");
 
     current*=2;
   } 
 
-#if 0
-  if(current > max){
-    cdmessage(d,"\nGiving up; drive cache is too large to defeat using overflow.\n");
-    cdmessage(d,"\n(Drives should not cache redbook reads, this drive does anyway."
-	        "\n Worse, the cache is too large to have any hope of defeating."
-	        "\n Cdparanoia has no chance of catching errors from this drive.\n");
 
-    return INT_MAX;
-  }
-#endif
+
+   
+
+  /* XXXXXX IN PROGRESS */
+  cdmessage(d,"\n");
   return 0;
 }
 

Modified: trunk/cdparanoia/interface/cooked_interface.c
===================================================================
--- trunk/cdparanoia/interface/cooked_interface.c	2008-08-14 16:51:39 UTC (rev 15182)
+++ trunk/cdparanoia/interface/cooked_interface.c	2008-08-14 23:23:59 UTC (rev 15183)
@@ -79,7 +79,6 @@
     return 0;
 }
 
-
 /* read 'SectorBurst' adjacent sectors of audio sectors 
  * to Buffer '*p' beginning at sector 'lSector'
  */
@@ -218,6 +217,11 @@
   }
 }
 
+
+int cooked_preinit_drive(cdrom_drive *d){
+  d->set_speed = cooked_setspeed;
+}
+
 /* set function pointers to use the ioctl routines */
 int cooked_init_drive (cdrom_drive *d){
   int ret;
@@ -274,7 +278,6 @@
   }
   d->enable_cdda = Dummy;
   d->read_audio = cooked_read;
-  d->set_speed = cooked_setspeed;
   d->read_toc = cooked_readtoc;
   ret=d->tracks=d->read_toc(d);
   if(d->tracks<1)

Modified: trunk/cdparanoia/interface/interface.c
===================================================================
--- trunk/cdparanoia/interface/interface.c	2008-08-14 16:51:39 UTC (rev 15182)
+++ trunk/cdparanoia/interface/interface.c	2008-08-14 23:23:59 UTC (rev 15183)
@@ -90,12 +90,17 @@
     
   /*  d->select_speed(d,d->maxspeed); most drives are full speed by default */
   if(d->bigendianp==-1)d->bigendianp=data_bigendianp(d);
+  analyze_timing_and_cache(d);
   return(0);
 }
 
 int cdda_speed_set(cdrom_drive *d, int speed)
 {
-  return d->set_speed ? d->set_speed(d, speed) : 0;
+  if(d->set_speed)
+    if(!d->set_speed(d,speed)) return 0;
+      
+  cderror(d,"405: Option not supported by drive\n");
+  return -405;
 }
 
 long cdda_read(cdrom_drive *d, void *buffer, long beginsector, long sectors){

Modified: trunk/cdparanoia/interface/low_interface.h
===================================================================
--- trunk/cdparanoia/interface/low_interface.h	2008-08-14 16:51:39 UTC (rev 15182)
+++ trunk/cdparanoia/interface/low_interface.h	2008-08-14 23:23:59 UTC (rev 15183)
@@ -101,6 +101,8 @@
   unsigned char *sg_buffer; /* points into sg_hd */
 
   int last_milliseconds;
+  int cache_backseekflush;
+  int cache_sectors;
 };
 
 #define MAX_RETRIES 8
@@ -108,8 +110,10 @@
 #define MIN_BIG_BUFF_SIZE 4096
 #define SG_OFF sizeof(struct sg_header)
 
+extern int  cooked_preinit_drive (cdrom_drive *d);
 extern int  cooked_init_drive (cdrom_drive *d);
 extern unsigned char *scsi_inquiry (cdrom_drive *d);
+extern int  scsi_preinit_drive (cdrom_drive *d);
 extern int  scsi_init_drive (cdrom_drive *d);
 #ifdef CDDA_TEST
 extern int  test_init_drive (cdrom_drive *d);

Modified: trunk/cdparanoia/interface/scan_devices.c
===================================================================
--- trunk/cdparanoia/interface/scan_devices.c	2008-08-14 16:51:39 UTC (rev 15182)
+++ trunk/cdparanoia/interface/scan_devices.c	2008-08-14 23:23:59 UTC (rev 15183)
@@ -265,7 +265,8 @@
   d->nsectors=-1;
   d->private=calloc(1,sizeof(*d->private));
   idmessage(messagedest,messages,"\t\tCDROM sensed: %s\n",description);
-  
+
+  cooked_preinit_drive(d);
   return(d);
 }
 
@@ -748,14 +749,14 @@
   memcpy(d->inqbytes,p,4);
   d->cdda_device_name=copystring(generic_device);
   d->ioctl_device_name=copystring(specialized_device);
-
   d->drive_model=calloc(36,1);
   strscat(d->drive_model,p+8,8);
   strscat(d->drive_model,p+16,16);
   strscat(d->drive_model,p+32,4);
 
   idmessage(messagedest,messages,"\nCDROM model sensed sensed: %s",d->drive_model);
-  
+
+  scsi_preinit_drive(d);
   return(d);
   
 cdda_identify_scsi_fail:

Modified: trunk/cdparanoia/interface/scsi_interface.c
===================================================================
--- trunk/cdparanoia/interface/scsi_interface.c	2008-08-14 16:51:39 UTC (rev 15182)
+++ trunk/cdparanoia/interface/scsi_interface.c	2008-08-14 23:23:59 UTC (rev 15183)
@@ -611,46 +611,6 @@
   return(0);
 }
 
-static int set_read_ahead (cdrom_drive *d, int start, int end){
-  int err;
-  unsigned char sense[SG_MAX_SENSE];
-  unsigned char cmd[12]={0xA7, /* SET READ AHEAD */
-			0x00, /* reserved */
-			0x00, /* lba */
-			0x00, /* lba */
-			0x00, /* lba */
-			0x00, /* lba */
-			0x00, /* lba */
-			0x00, /* lba */
-			0x00, /* lba */
-			0x00, /* lba */
-			0,    /* reserved */
-			0};   /* control */ 
-  
-  cmd[2] = (start>>24)&0xff;
-  cmd[3] = (start>>16)&0xff;
-  cmd[4] = (start>>8)&0xff;
-  cmd[5] = (start)&0xff;
-
-  cmd[6] = (end>>24)&0xff;
-  cmd[7] = (end>>16)&0xff;
-  cmd[8] = (end>>8)&0xff;
-  cmd[9] = (end)&0xff;
-  
-  if (err=handle_scsi_cmd (d, cmd, 12, 0, 0, 0, 0, sense)){
-    fprintf(stderr,"Unable to disable cache\n");
-    fprintf(stderr,"                 Sense key: %x ASC: %x ASCQ: %x\n",
-	    (int)(sense[2]&0xf),
-	    (int)(sense[12]),
-	    (int)(sense[13]));
-    fprintf(stderr,"                 Transport error: %s\n",strerror_tr[err]);
-    fprintf(stderr,"                 System error: %s\n",strerror(errno));
-    
-  }
-
-  return(0);
-}
-
 typedef struct scsi_TOC {  /* structure of scsi table of contents (cdrom) */
   unsigned char reserved1;
   unsigned char bFlags;
@@ -807,6 +767,20 @@
   return(tracks);
 }
 
+static int scsi_set_speed (cdrom_drive *d, int speed){
+  int ret;
+  char b[80];
+  unsigned char cmd[12]={0xBB, 0, 0, 0, 0xff, 0xff, 0, 0, 0, 0};
+  unsigned char sense[SG_MAX_SENSE];
+
+  speed=speed*44100*4/1024;
+  cmd[2] = (speed >> 8) & 0xFF;
+  cmd[3] = (speed) & 0xFF;
+  ret=handle_scsi_cmd(d,cmd,12,0,0,0,0,sense);
+
+  return check_sbp_error(ret,sense);
+}
+
 /* These do one 'extra' copy in the name of clean code */
 
 static int i_read_28 (cdrom_drive *d, void *p, long begin, long sectors, unsigned char *sense){
@@ -1679,6 +1653,9 @@
   return (d->private->sg_buffer);
 }
 
+int scsi_preinit_drive(cdrom_drive *d){
+  d->set_speed = scsi_set_speed;
+}
 
 int scsi_init_drive(cdrom_drive *d){
   int ret;
@@ -1723,8 +1700,6 @@
 
   d->read_toc = (!memcmp(d->drive_model, "IMS", 3) && !d->is_atapi) ? scsi_read_toc2 : 
     scsi_read_toc;
-  d->set_speed = NULL;
-  
 
   if(!d->is_atapi){
     unsigned sector_size= get_orig_sectorsize(d);

Modified: trunk/cdparanoia/main.c
===================================================================
--- trunk/cdparanoia/main.c	2008-08-14 16:51:39 UTC (rev 15182)
+++ trunk/cdparanoia/main.c	2008-08-14 23:23:59 UTC (rev 15183)
@@ -654,7 +654,7 @@
   char *force_cdrom_device=NULL;
   char *force_generic_device=NULL;
   char *force_cooked_device=NULL;
-  int force_cdrom_speed=-1;
+  int force_cdrom_speed=0;
   int max_retries=20;
   char *span=NULL;
   int output_type=1; /* 0=raw, 1=wav, 2=aifc */
@@ -937,6 +937,19 @@
     }
   }
 
+  /* We want the speed set to apply to cache and timing testing */
+  if(force_cdrom_speed!=0){
+    char buf[80];
+    sprintf(buf,"\nAttempting to set speed to %dx... ",force_cdrom_speed);
+    report(buf);
+    if(cdda_speed_set(d,force_cdrom_speed)){
+      report("\tFAILED.");
+      exit(1);
+    }else{
+      report("\tdrive returned OK.");
+    }
+  }
+
   switch(cdda_open(d)){
   case -2:case -3:case -4:case -5:
     report("\nUnable to open disc.  Is there an audio CD in the drive?");
@@ -951,9 +964,6 @@
     exit(1);
   }
 
-  /* Determine drive caching behavior for cache-busting purposes */
-  cdda_cache_sectors(d); 
-
   /* Dump the TOC */
   if(query_only || verbose)display_toc(d);
   if(query_only)exit(0);
@@ -978,10 +988,6 @@
     d->disc_toc[i].dwStartSector+=toc_offset;
 
 
-  if(force_cdrom_speed!=-1){
-    cdda_speed_set(d,force_cdrom_speed);
-  }
-
   if(d->nsectors==1){
     report("WARNING: The autosensed/selected sectors per read value is\n"
 	   "         one sector, making it very unlikely Paranoia can \n"



More information about the commits mailing list