[PATCH] libata: implement presence detection via polling IDENTIFY
authorTejun Heo <htejun@gmail.com>
Fri, 10 Nov 2006 09:08:10 +0000 (18:08 +0900)
committerJeff Garzik <jeff@garzik.org>
Sat, 2 Dec 2006 03:45:55 +0000 (22:45 -0500)
On some controllers (ICHs in piix mode), there is *NO* reliable way to
determine device presence other than issuing IDENTIFY and see how the
transaction proceeds by watching the TF status register.

libata acted this way before irq-pio and phantom devices caused very
little problem but now that IDENTIFY is performed using IRQ drive PIO,
such phantom devices now result in multiple 30sec timeouts during
boot.

This patch implements ATA_FLAG_DETECT_POLLING.  If a LLD sets this
flag, libata core issues the initial IDENTIFY in polling mode and if
the initial data transfer fails w/ HSM violation, the port is
considered to be empty thus replicating the old libata and IDE
behavior.

Signed-off-by: Jeff Garzik <jeff@garzik.org>
drivers/ata/libata-core.c
drivers/ata/libata-eh.c
drivers/ata/libata.h
include/linux/libata.h

index 090abe4438203b7c12a9a4ea877e4d7a6f6470b7..21f8d61e5879faeed1af2aa4720082a413bb25b7 100644 (file)
@@ -1272,9 +1272,20 @@ int ata_dev_read_id(struct ata_device *dev, unsigned int *p_class,
 
        tf.protocol = ATA_PROT_PIO;
 
+       /* presence detection using polling IDENTIFY? */
+       if (flags & ATA_READID_DETECT)
+               tf.flags |= ATA_TFLAG_POLLING;
+
        err_mask = ata_exec_internal(dev, &tf, NULL, DMA_FROM_DEVICE,
                                     id, sizeof(id[0]) * ATA_ID_WORDS);
        if (err_mask) {
+               if ((flags & ATA_READID_DETECT) &&
+                   (err_mask & AC_ERR_NODEV_HINT)) {
+                       DPRINTK("ata%u.%d: NODEV after polling detection\n",
+                               ap->id, dev->devno);
+                       return -ENOENT;
+               }
+
                rc = -EIO;
                reason = "I/O error";
                goto err_out;
@@ -4285,8 +4296,12 @@ fsm_start:
                                        /* device stops HSM for abort/error */
                                        qc->err_mask |= AC_ERR_DEV;
                                else
-                                       /* HSM violation. Let EH handle this */
-                                       qc->err_mask |= AC_ERR_HSM;
+                                       /* HSM violation. Let EH handle this.
+                                        * Phantom devices also trigger this
+                                        * condition.  Mark hint.
+                                        */
+                                       qc->err_mask |= AC_ERR_HSM |
+                                                       AC_ERR_NODEV_HINT;
 
                                ap->hsm_task_state = HSM_ST_ERR;
                                goto fsm_start;
index 755fc68b537460096dab2154cd90dc99fb170a5d..e69f3df2ea3936b6f8babb6053309f04f7add43a 100644 (file)
@@ -1667,12 +1667,23 @@ static int ata_eh_revalidate_and_attach(struct ata_port *ap,
                           ata_class_enabled(ehc->classes[dev->devno])) {
                        dev->class = ehc->classes[dev->devno];
 
+                       if (ap->flags & ATA_FLAG_DETECT_POLLING)
+                               readid_flags |= ATA_READID_DETECT;
+
                        rc = ata_dev_read_id(dev, &dev->class, readid_flags,
                                             dev->id);
                        if (rc == 0) {
                                ehc->i.flags |= ATA_EHI_PRINTINFO;
                                rc = ata_dev_configure(dev);
                                ehc->i.flags &= ~ATA_EHI_PRINTINFO;
+                       } else if (rc == -ENOENT) {
+                               /* IDENTIFY was issued to non-existent
+                                * device.  No need to reset.  Just
+                                * thaw and kill the device.
+                                */
+                               ata_eh_thaw_port(ap);
+                               dev->class = ATA_DEV_UNKNOWN;
+                               rc = 0;
                        }
 
                        if (rc) {
@@ -1680,12 +1691,14 @@ static int ata_eh_revalidate_and_attach(struct ata_port *ap,
                                break;
                        }
 
-                       spin_lock_irqsave(ap->lock, flags);
-                       ap->pflags |= ATA_PFLAG_SCSI_HOTPLUG;
-                       spin_unlock_irqrestore(ap->lock, flags);
+                       if (ata_dev_enabled(dev)) {
+                               spin_lock_irqsave(ap->lock, flags);
+                               ap->pflags |= ATA_PFLAG_SCSI_HOTPLUG;
+                               spin_unlock_irqrestore(ap->lock, flags);
 
-                       /* new device discovered, configure transfer mode */
-                       ehc->i.flags |= ATA_EHI_SETMODE;
+                               /* new device discovered, configure xfermode */
+                               ehc->i.flags |= ATA_EHI_SETMODE;
+                       }
                }
        }
 
index bb98390aa01a21f147bd5b0bbd7a9a91042c926c..be2ac39f013b85389d026c56e6c2f9ed75f7e553 100644 (file)
@@ -42,6 +42,8 @@ struct ata_scsi_args {
 enum {
        /* flags for ata_dev_read_id() */
        ATA_READID_POSTRESET    = (1 << 0), /* reading ID after reset */
+       ATA_READID_DETECT       = (1 << 1), /* perform presence detection
+                                            * using polling IDENTIFY */
 };
 
 extern struct workqueue_struct *ata_aux_wq;
index 36e233cc3886ddf242abc02eb89c79b7e7b86444..9080789913f74ac33c62f9b33ef1b8a74ffd8115 100644 (file)
@@ -176,6 +176,8 @@ enum {
        ATA_FLAG_SKIP_D2H_BSY   = (1 << 12), /* can't wait for the first D2H
                                              * Register FIS clearing BSY */
        ATA_FLAG_DEBUGMSG       = (1 << 13),
+       ATA_FLAG_DETECT_POLLING = (1 << 14), /* detect device presence by
+                                             * polling IDENTIFY */
 
        /* The following flag belongs to ap->pflags but is kept in
         * ap->flags because it's referenced in many LLDs and will be
@@ -335,6 +337,7 @@ enum ata_completion_errors {
        AC_ERR_SYSTEM           = (1 << 6), /* system error */
        AC_ERR_INVALID          = (1 << 7), /* invalid argument */
        AC_ERR_OTHER            = (1 << 8), /* unknown */
+       AC_ERR_NODEV_HINT       = (1 << 9), /* polling device detection hint */
 };
 
 /* forward declarations */