diff -crN /usr/src/sys/dev/ata/ata-disk.c /usr/src/sys/dev/ata/ata-disk.c
*** /usr/src/sys/dev/ata/ata-disk.c	Tue Oct 22 16:00:00 2002
--- /usr/src/sys/dev/ata/ata-disk.c	Sat Oct 26 15:33:16 2002
***************
*** 78,83 ****
--- 78,84 ----
  static void ad_timeout(struct ad_request *);
  static void ad_free(struct ad_request *);
  static int ad_version(u_int16_t);
+ static void ad_counters_ovf_fixup(struct ad_softc *adp);
  
  /* misc defines */
  #define AD_MAX_RETRIES	3
***************
*** 177,187 ****
      }
  
      /* use DMA if allowed and if drive/controller supports it */
!     if (ata_dma)
! 	ata_dmainit(atadev, ata_pmode(atadev->param),
! 		    ata_wmode(atadev->param), ata_umode(atadev->param));
!     else
! 	ata_dmainit(atadev, ata_pmode(atadev->param), -1, -1);
  
      /* use tagged queueing if allowed and supported */
      if (ata_tags && ad_tagsupported(adp)) {
--- 178,192 ----
      }
  
      /* use DMA if allowed and if drive/controller supports it */
!     if (ata_dma) {
! 	adp->best_wdma = ata_wmode(atadev->param);
! 	adp->best_udma = ata_umode(atadev->param);
!     } else {
! 	adp->best_wdma =
! 	adp->best_udma = -1;
!     }	
!     ata_dmainit(atadev, ata_pmode(atadev->param),
!     	    adp->best_wdma, adp->best_udma);
  
      /* use tagged queueing if allowed and supported */
      if (ata_tags && ad_tagsupported(adp)) {
***************
*** 385,390 ****
--- 390,405 ----
      TAILQ_INSERT_TAIL(&atadev->channel->ata_queue, request, chain);
  }
  
+ void
+ ad_counters_ovf_fixup(struct ad_softc *adp) {
+     int i;
+     adp->io_count /= 4;
+     for(i=0; i<AD_UDMA_MAX_RETRIES; i++) {
+         adp->io_error_count[i] /= 4;
+         adp->io_recover_count[i] /= 4;
+     }
+ }
+ 
  int
  ad_transfer(struct ad_request *request)
  {
***************
*** 397,402 ****
--- 412,421 ----
      /* get request params */
      adp = request->softc;
  
+     /* update io counter */
+     if(adp->io_count++ >= 0x7fffffff)
+         ad_counters_ovf_fixup(adp);
+ 
      /* calculate transfer details */
      lba = request->blockaddr + (request->donecount / DEV_BSIZE);
  
***************
*** 571,601 ****
  		"UDMA ICRC error" : "hard error",
  		request->blockaddr + (request->donecount / DEV_BSIZE), 1);
  
! 	/* if this is a UDMA CRC error, reinject request */
! 	if (request->flags & ADR_F_DMA_USED &&
! 	    adp->device->channel->error & ATA_E_ICRC) {
  	    untimeout((timeout_t *)ad_timeout, request,request->timeout_handle);
  	    ad_invalidatequeue(adp, request);
  
! 	    if (request->retries++ < AD_MAX_RETRIES)
  		printf(" retrying\n");
! 	    else {
! 		ata_dmainit(adp->device, ata_pmode(adp->device->param), -1, -1);
! 		printf(" falling back to PIO mode\n");
  	    }
- 	    TAILQ_INSERT_HEAD(&adp->device->channel->ata_queue, request, chain);
- 	    return ATA_OP_FINISHED;
- 	}
- 
- 	/* if using DMA, try once again in PIO mode */
- 	if (request->flags & ADR_F_DMA_USED) {
- 	    untimeout((timeout_t *)ad_timeout, request,request->timeout_handle);
- 	    ad_invalidatequeue(adp, request);
- 	    ata_dmainit(adp->device, ata_pmode(adp->device->param), -1, -1);
- 	    request->flags |= ADR_F_FORCE_PIO;
- 	    printf(" trying PIO mode\n");
- 	    TAILQ_INSERT_HEAD(&adp->device->channel->ata_queue, request, chain);
- 	    return ATA_OP_FINISHED;
  	}
  
  	request->flags |= ADR_F_ERROR;
--- 590,646 ----
  		"UDMA ICRC error" : "hard error",
  		request->blockaddr + (request->donecount / DEV_BSIZE), 1);
  
!         if(adp->io_all_error_count++ >= 0x7fffffff)
!                 ad_counters_ovf_fixup(adp);
! 
! 	/* skip obsolete retries */
! 	if(!request->retries) {
! 
!             if (request->flags & ADR_F_DMA_USED) {
!                 if(!(adp->device->channel->error & ATA_E_ICRC) &&
!                     (adp->best_udma < 2) ) {
!                     request->retries = AD_RETRY_WDMA;
!                 }
!             } else {
!                 request->retries = AD_RETRY_PIO;
!             }
! 	}
! 
! 	/* if this is a UDMA CRC error or
! 	   retried request, reinject request */
! 	if(request->retries++ < AD_UDMA_MAX_RETRIES) {
  	    untimeout((timeout_t *)ad_timeout, request,request->timeout_handle);
  	    ad_invalidatequeue(adp, request);
  
! 	    if (request->retries++ < AD_UDMA_MAX_RETRIES) {
! 
!                 /* update error counter */
!                 if(adp->io_error_count[request->retries]++ >= 0x7fffffff)
!                     ad_counters_ovf_fixup(adp);
! 
  		printf(" retrying\n");
! 	        if(request->retries > AD_RETRY_PIO) {
!           	    request->flags |= ADR_F_FORCE_PIO;
! 		    printf(" falling back to PIO mode\n");
! 		    ata_dmainit(adp->device, ata_pmode(adp->device->param), -1, -1);
! 	        } else
! 	        if(request->retries > AD_RETRY_WDMA) {
!           	    request->flags |= ADR_F_FORCE_PIO;
! 		    printf(" falling back to WDMA mode\n");
! 		    ata_dmainit(adp->device, ata_pmode(adp->device->param), 2, -1);
! 		} else
! 	        if(request->retries > AD_RETRY_UDMA2) {
!           	    request->flags |= ADR_F_FORCE_PIO;
! 		    printf(" falling back to UDMA2 mode\n");
! 		    ata_dmainit(adp->device, ata_pmode(adp->device->param), 2, 2);
! 		}
! 		TAILQ_INSERT_HEAD(&adp->device->channel->ata_queue, request, chain);
! 		return ATA_OP_FINISHED;
! 	    } else {
!                 printf(" restore \"best\" transfer mode\n");
!  	        ata_dmainit(adp->device, ata_pmode(adp->device->param),
!         		    adp->best_wdma, adp->best_udma);
  	    }
  	}
  
  	request->flags |= ADR_F_ERROR;
***************
*** 604,611 ****
      }
  
      /* if we arrived here with forced PIO mode, DMA doesn't work right */
!     if (request->flags & ADR_F_FORCE_PIO && !(request->flags & ADR_F_ERROR))
! 	ata_prtdev(adp->device, "DMA problem fallback to PIO mode\n");
  
      /* if this was a PIO read operation, get the data */
      if (!(request->flags & ADR_F_DMA_USED) &&
--- 649,678 ----
      }
  
      /* if we arrived here with forced PIO mode, DMA doesn't work right */
!     if (request->flags & ADR_F_FORCE_PIO && !(request->flags & ADR_F_ERROR)) {
! 	ata_prtdev(adp->device, "DMA problem fallback to more slow mode\n");
!         /* update error recovery counter */
!         if(adp->io_recover_count[request->retries]++ >= 0x7fffffff)
!             ad_counters_ovf_fixup(adp);
!         if(adp->io_recover_count[request->retries] >= adp->io_count/3) {
!             if(request->retries > AD_RETRY_PIO) {
!                 adp->best_wdma =
!                 adp->best_udma = -1;
!             } else
!             if(request->retries > AD_RETRY_WDMA) {
!                 adp->best_wdma = 2;
!                 adp->best_udma = -1;
!             } else
!             if(request->retries > AD_RETRY_UDMA2) {
!                 adp->best_wdma = 2;
!                 adp->best_udma = 2;
!             }
!         } else {
!             printf(" restore \"best\" transfer mode\n");
! 	    ata_dmainit(adp->device, ata_pmode(adp->device->param),
!     		    adp->best_wdma, adp->best_udma);
!         }
!     }	
  
      /* if this was a PIO read operation, get the data */
      if (!(request->flags & ADR_F_DMA_USED) &&
***************
*** 867,878 ****
      ad_invalidatequeue(atadev->driver, NULL);
      ata_command(atadev, ATA_C_SET_MULTI, 0,
  		adp->transfersize / DEV_BSIZE, 0, ATA_WAIT_READY);
!     if (adp->device->mode >= ATA_DMA)
! 	ata_dmainit(atadev, ata_pmode(adp->device->param),
! 		    ata_wmode(adp->device->param),
! 		    ata_umode(adp->device->param));
!     else
! 	ata_dmainit(atadev, ata_pmode(adp->device->param), -1, -1);
  }
  
  void
--- 934,948 ----
      ad_invalidatequeue(atadev->driver, NULL);
      ata_command(atadev, ATA_C_SET_MULTI, 0,
  		adp->transfersize / DEV_BSIZE, 0, ATA_WAIT_READY);
!     if (ata_dma) {
! 	adp->best_wdma = ata_wmode(atadev->param);
! 	adp->best_udma = ata_umode(atadev->param);
!     } else {
! 	adp->best_wdma =
! 	adp->best_udma = -1;
!     }	
!     ata_dmainit(atadev, ata_pmode(atadev->param),
!     	    adp->best_wdma, adp->best_udma);
  }
  
  void
diff -crN /usr/src/sys/dev/ata/ata-disk.h /usr/src/sys/dev/ata/ata-disk.h
*** /usr/src/sys/dev/ata/ata-disk.h	Tue Oct 22 16:00:00 2002
--- /usr/src/sys/dev/ata/ata-disk.h	Sat Oct 26 15:33:16 2002
***************
*** 28,33 ****
--- 28,38 ----
   * $FreeBSD: src/sys/dev/ata/ata-disk.h,v 1.42 2002/07/22 18:35:01 sos Exp $
   */
  
+ #define AD_UDMA_MAX_RETRIES	6
+ #define AD_RETRY_UDMA2          1
+ #define AD_RETRY_WDMA           2
+ #define AD_RETRY_PIO            3
+ 
  /* structure describing an ATA disk request */
  struct ad_request {
      struct ad_softc		*softc;		/* ptr to parent device */
***************
*** 74,79 ****
--- 79,91 ----
      struct devstat		stats;		/* devstat entry */
      struct disk			disk;		/* disklabel/slice stuff */
      dev_t			dev;		/* device place holder */
+ 
+     int				best_udma;      /* */
+     int				best_wdma;
+     int				io_count;
+     int                         io_all_error_count;
+     int				io_error_count[AD_UDMA_MAX_RETRIES];
+     int				io_recover_count[AD_UDMA_MAX_RETRIES];
  };
  
  void ad_attach(struct ata_device *);
