libata: accept late unlocking of HPA
authorTejun Heo <tj@kernel.org>
Wed, 15 Jul 2009 08:11:41 +0000 (17:11 +0900)
committerJeff Garzik <jgarzik@redhat.com>
Wed, 29 Jul 2009 01:07:09 +0000 (21:07 -0400)
On certain configurations, HPA isn't or can't be unlocked during
probing but it somehow ends up unlocked afterwards.  In the following
thread, the problem can be reliably reproduced after resuming from
STR.  The BIOS turns on HPA during boot but forgets to do it during
resume.

  http://thread.gmane.org/gmane.linux.kernel/858310

This patch updates libata revalidation such that it considers native
n_sectors.  If the device size has increased to match native
n_sectors, it's assumed that HPA has been unlocked involuntarily and
the device is recognized as the same one.  This should be fairly safe
while nicely working around the problem.

Signed-off-by: Tejun Heo <tj@kernel.org>
Reported-by: Christof Warlich <christof@warlich.name>
Signed-off-by: Jeff Garzik <jgarzik@redhat.com>
drivers/ata/libata-core.c
include/linux/libata.h

index 2c6aedaef7185e649f506b7c656ce8eb042c3e9e..8ac98ff16d7deb65f083851013c090a1cb9cfa60 100644 (file)
@@ -1515,6 +1515,7 @@ static int ata_hpa_resize(struct ata_device *dev)
 
                return rc;
        }
+       dev->n_native_sectors = native_sectors;
 
        /* nothing to do? */
        if (native_sectors <= sectors || !ata_ignore_hpa) {
@@ -4099,6 +4100,7 @@ int ata_dev_revalidate(struct ata_device *dev, unsigned int new_class,
                       unsigned int readid_flags)
 {
        u64 n_sectors = dev->n_sectors;
+       u64 n_native_sectors = dev->n_native_sectors;
        int rc;
 
        if (!ata_dev_enabled(dev))
@@ -4128,16 +4130,30 @@ int ata_dev_revalidate(struct ata_device *dev, unsigned int new_class,
        /* verify n_sectors hasn't changed */
        if (dev->class == ATA_DEV_ATA && n_sectors &&
            dev->n_sectors != n_sectors) {
-               ata_dev_printk(dev, KERN_INFO, "n_sectors mismatch "
+               ata_dev_printk(dev, KERN_WARNING, "n_sectors mismatch "
                               "%llu != %llu\n",
                               (unsigned long long)n_sectors,
                               (unsigned long long)dev->n_sectors);
-
-               /* restore original n_sectors */
-               dev->n_sectors = n_sectors;
-
-               rc = -ENODEV;
-               goto fail;
+               /*
+                * Something could have caused HPA to be unlocked
+                * involuntarily.  If n_native_sectors hasn't changed
+                * and the new size matches it, keep the device.
+                */
+               if (dev->n_native_sectors == n_native_sectors &&
+                   dev->n_sectors > n_sectors &&
+                   dev->n_sectors == n_native_sectors) {
+                       ata_dev_printk(dev, KERN_WARNING,
+                                      "new n_sectors matches native, probably "
+                                      "late HPA unlock, continuing\n");
+                       /* keep using the old n_sectors */
+                       dev->n_sectors = n_sectors;
+               } else {
+                       /* restore original n_[native]_sectors and fail */
+                       dev->n_native_sectors = n_native_sectors;
+                       dev->n_sectors = n_sectors;
+                       rc = -ENODEV;
+                       goto fail;
+               }
        }
 
        return 0;
index 79b6d7fd4ac2d217bea7bfebe81b6c4b7a5b770b..e5b6e33c65710a6f82593387e1f8e18663394ff4 100644 (file)
@@ -589,6 +589,7 @@ struct ata_device {
 #endif
        /* n_sector is CLEAR_BEGIN, read comment above CLEAR_BEGIN */
        u64                     n_sectors;      /* size of device, if ATA */
+       u64                     n_native_sectors; /* native size, if ATA */
        unsigned int            class;          /* ATA_DEV_xxx */
        unsigned long           unpark_deadline;