AHCI/SILI - Send dummy SETXFER mode even though it is SATA
authorMatthew Dillon <dillon@apollo.backplane.com>
Tue, 3 Nov 2009 18:00:14 +0000 (10:00 -0800)
committerMatthew Dillon <dillon@apollo.backplane.com>
Tue, 3 Nov 2009 18:00:14 +0000 (10:00 -0800)
* If the identify structure contains a non-zero/non-FFFF dmamode field
  we send a dummy SETXFER mode command to the device.

* Most SATA DISK devices set this field to 0 or FFFF and in this case no
  SETXFER mode command is sent.  SETXFER is non-applicable to SATA in
  general.

  Most ATAPI devices still initialize the dmamode field and some ATAPI
  devices still require SETXFER to be sent even though they are native SATA.

  SATA->PATA converters may require a SETXFER to be passed through before
  the PATA devices will operate properly.  In this case the dmamode field
  will likely be initialized.

sys/dev/disk/ahci/ahci_cam.c
sys/dev/disk/ahci/atascsi.h
sys/dev/disk/sili/atascsi.h
sys/dev/disk/sili/sili_cam.c

index d5f524d..9189fb7 100644 (file)
@@ -78,6 +78,7 @@ static void ahci_ata_atapi_sense(struct ata_fis_d2h *rfis,
 
 static int ahci_cam_probe_disk(struct ahci_port *ap, struct ata_port *at);
 static int ahci_cam_probe_atapi(struct ahci_port *ap, struct ata_port *at);
+static int ahci_set_xfer(struct ahci_port *ap, struct ata_port *atx);
 static void ahci_ata_dummy_done(struct ata_xfer *xa);
 static void ata_fix_identify(struct ata_identify *id);
 static void ahci_cam_rescan(struct ahci_port *ap);
@@ -494,6 +495,11 @@ ahci_cam_probe_disk(struct ahci_port *ap, struct ata_port *atx)
 
        at = atx ? atx : ap->ap_ata[0];
 
+       /*
+        * Set dummy xfer mode
+        */
+       ahci_set_xfer(ap, atx);
+
        /*
         * Enable write cache if supported
         *
@@ -580,6 +586,67 @@ ahci_cam_probe_disk(struct ahci_port *ap, struct ata_port *atx)
 static int
 ahci_cam_probe_atapi(struct ahci_port *ap, struct ata_port *atx)
 {
+       ahci_set_xfer(ap, atx);
+       return(0);
+}
+
+/*
+ * Setting the transfer mode is irrelevant for the SATA transport
+ * but some (atapi) devices seem to need it anyway.  In addition
+ * if we are running through a SATA->PATA converter for some reason
+ * beyond my comprehension we might have to set the mode.
+ */
+static int
+ahci_set_xfer(struct ahci_port *ap, struct ata_port *atx)
+{
+       struct ata_port *at;
+       struct ata_xfer *xa;
+       u_int16_t mode;
+
+       at = atx ? atx : ap->ap_ata[0];
+
+       /*
+        * Get the supported mode.  SATA hard drives usually set this
+        * field to zero because it's irrelevant for SATA.  The general
+        * ATA spec allows unsupported fields to be 0 or bits all 1's.
+        *
+        * If the dmamode is not set the device understands that it is
+        * SATA and we don't have to send the obsolete SETXFER command.
+        */
+       mode = le16toh(at->at_identify.dmamode);
+       if (mode == 0 || mode == 0xFFFF)
+               return(0);
+
+       /*
+        * SATA atapi devices often still report a dma mode, even though
+        * it is irrelevant for SATA transport.  It is also possible that
+        * we are running through a SATA->PATA converter and seeing the
+        * PATA dma mode.
+        *
+        * In this case the device may require a (dummy) SETXFER to be
+        * sent before it will work properly.
+        */
+       xa = ahci_ata_get_xfer(ap, atx);
+       xa->complete = ahci_ata_dummy_done;
+       xa->fis->command = ATA_C_SET_FEATURES;
+       xa->fis->features = ATA_SF_SETXFER;
+       xa->fis->flags = ATA_H2D_FLAGS_CMD | at->at_target;
+       xa->fis->device = 0;
+       xa->fis->sector_count = 0x40 | mode;
+       xa->fis->lba_low = 0;
+       xa->fis->lba_mid = 0;
+       xa->fis->lba_high = 0;
+       xa->flags = ATA_F_PIO | ATA_F_POLL;
+       xa->timeout = 1000;
+       xa->datalen = 0;
+       if (ahci_ata_cmd(xa) != ATA_S_COMPLETE) {
+               kprintf("%s: Unable to set dummy xfer mode \n",
+                       ATANAME(ap, atx));
+       } else if (bootverbose) {
+               kprintf("%s: Set dummy xfer mode to %02x\n",
+                       ATANAME(ap, atx), 0x40 | mode);
+       }
+       ahci_ata_put_xfer(xa);
        return(0);
 }
 
index a7c6624..66b7136 100644 (file)
@@ -55,6 +55,7 @@ struct scsi_link;
  * ATA SET FEATURES subcommands
  */
 #define ATA_SF_WRITECACHE_EN   0x02
+#define ATA_SF_SETXFER         0x03
 #define ATA_SF_LOOKAHEAD_EN    0xaa
 
 struct ata_identify {
index 10cc550..d4b1959 100644 (file)
@@ -55,6 +55,7 @@ struct scsi_link;
  * ATA SET FEATURES subcommands
  */
 #define ATA_SF_WRITECACHE_EN   0x02
+#define ATA_SF_SETXFER         0x03
 #define ATA_SF_LOOKAHEAD_EN    0xaa
 
 struct ata_identify {
index 2d18231..8ccf72c 100644 (file)
@@ -80,6 +80,7 @@ static int sili_cam_probe_disk(struct sili_port *ap, struct ata_port *at);
 static int sili_cam_probe_atapi(struct sili_port *ap, struct ata_port *at);
 static void sili_ata_dummy_done(struct ata_xfer *xa);
 static void ata_fix_identify(struct ata_identify *id);
+static int sili_set_xfer(struct sili_port *ap, struct ata_port *atx);
 static void sili_cam_rescan(struct sili_port *ap);
 static void sili_strip_string(const char **basep, int *lenp);
 
@@ -503,6 +504,11 @@ sili_cam_probe_disk(struct sili_port *ap, struct ata_port *atx)
 
        at = atx ? atx : ap->ap_ata;
 
+       /*
+        * Set dummy xfer mode
+        */
+       sili_set_xfer(ap, atx);
+
        /*
         * Enable write cache if supported
         *
@@ -589,6 +595,67 @@ sili_cam_probe_disk(struct sili_port *ap, struct ata_port *atx)
 static int
 sili_cam_probe_atapi(struct sili_port *ap, struct ata_port *atx)
 {
+       sili_set_xfer(ap, atx);
+       return(0);
+}
+
+/*
+ * Setting the transfer mode is irrelevant for the SATA transport
+ * but some (atapi) devices seem to need it anyway.  In addition
+ * if we are running through a SATA->PATA converter for some reason
+ * beyond my comprehension we might have to set the mode.
+ */
+static int
+sili_set_xfer(struct sili_port *ap, struct ata_port *atx)
+{
+       struct ata_port *at;
+       struct ata_xfer *xa;
+       u_int16_t mode;
+
+       at = atx ? atx : ap->ap_ata;
+
+       /*
+        * Get the supported mode.  SATA hard drives usually set this
+        * field to zero because it's irrelevant for SATA.  The general
+        * ATA spec allows unsupported fields to be 0 or bits all 1's.
+        *
+        * If the dmamode is not set the device understands that it is
+        * SATA and we don't have to send the obsolete SETXFER command.
+        */
+       mode = le16toh(at->at_identify.dmamode);
+       if (mode == 0 || mode == 0xFFFF)
+               return(0);
+
+       /*
+        * SATA atapi devices often still report a dma mode, even though
+        * it is irrelevant for SATA transport.  It is also possible that
+        * we are running through a SATA->PATA converter and seeing the
+        * PATA dma mode.
+        *
+        * In this case the device may require a (dummy) SETXFER to be
+        * sent before it will work properly.
+        */
+       xa = sili_ata_get_xfer(ap, atx);
+       xa->complete = sili_ata_dummy_done;
+       xa->fis->command = ATA_C_SET_FEATURES;
+       xa->fis->features = ATA_SF_SETXFER;
+       xa->fis->flags = ATA_H2D_FLAGS_CMD | at->at_target;
+       xa->fis->device = 0;
+       xa->fis->sector_count = 0x40 | mode;
+       xa->fis->lba_low = 0;
+       xa->fis->lba_mid = 0;
+       xa->fis->lba_high = 0;
+       xa->flags = ATA_F_PIO | ATA_F_POLL;
+       xa->timeout = 1000;
+       xa->datalen = 0;
+       if (sili_ata_cmd(xa) != ATA_S_COMPLETE) {
+               kprintf("%s: Unable to set dummy xfer mode \n",
+                       ATANAME(ap, atx));
+       } else if (bootverbose) {
+               kprintf("%s: Set dummy xfer mode to %02x\n",
+                       ATANAME(ap, atx), 0x40 | mode);
+       }
+       sili_ata_put_xfer(xa);
        return(0);
 }