Commit 445d211b authored by Tejun Heo's avatar Tejun Heo Committed by Jeff Garzik

libata: unlock HPA if device shrunk

Some BIOSes don't configure HPA during boot but do so while resuming.
This causes harddrives to shrink during resume making libata detach
and reattach them.  This can be worked around by unlocking HPA if old
size equals native size.

Add ATA_DFLAG_UNLOCK_HPA so that HPA unlocking can be controlled
per-device and update ata_dev_revalidate() such that it sets
ATA_DFLAG_UNLOCK_HPA and fails with -EIO when the above condition is
detected.

This patch fixes the following bug.

  https://bugzilla.kernel.org/show_bug.cgi?id=15396Signed-off-by: default avatarTejun Heo <tj@kernel.org>
Reported-by: default avatarOleksandr Yermolenko <yaa.bta@gmail.com>
Signed-off-by: default avatarJeff Garzik <jgarzik@redhat.com>
parent 68b0ddb2
...@@ -1494,6 +1494,7 @@ static int ata_hpa_resize(struct ata_device *dev) ...@@ -1494,6 +1494,7 @@ static int ata_hpa_resize(struct ata_device *dev)
{ {
struct ata_eh_context *ehc = &dev->link->eh_context; struct ata_eh_context *ehc = &dev->link->eh_context;
int print_info = ehc->i.flags & ATA_EHI_PRINTINFO; int print_info = ehc->i.flags & ATA_EHI_PRINTINFO;
bool unlock_hpa = ata_ignore_hpa || dev->flags & ATA_DFLAG_UNLOCK_HPA;
u64 sectors = ata_id_n_sectors(dev->id); u64 sectors = ata_id_n_sectors(dev->id);
u64 native_sectors; u64 native_sectors;
int rc; int rc;
...@@ -1510,7 +1511,7 @@ static int ata_hpa_resize(struct ata_device *dev) ...@@ -1510,7 +1511,7 @@ static int ata_hpa_resize(struct ata_device *dev)
/* If device aborted the command or HPA isn't going to /* If device aborted the command or HPA isn't going to
* be unlocked, skip HPA resizing. * be unlocked, skip HPA resizing.
*/ */
if (rc == -EACCES || !ata_ignore_hpa) { if (rc == -EACCES || !unlock_hpa) {
ata_dev_printk(dev, KERN_WARNING, "HPA support seems " ata_dev_printk(dev, KERN_WARNING, "HPA support seems "
"broken, skipping HPA handling\n"); "broken, skipping HPA handling\n");
dev->horkage |= ATA_HORKAGE_BROKEN_HPA; dev->horkage |= ATA_HORKAGE_BROKEN_HPA;
...@@ -1525,7 +1526,7 @@ static int ata_hpa_resize(struct ata_device *dev) ...@@ -1525,7 +1526,7 @@ static int ata_hpa_resize(struct ata_device *dev)
dev->n_native_sectors = native_sectors; dev->n_native_sectors = native_sectors;
/* nothing to do? */ /* nothing to do? */
if (native_sectors <= sectors || !ata_ignore_hpa) { if (native_sectors <= sectors || !unlock_hpa) {
if (!print_info || native_sectors == sectors) if (!print_info || native_sectors == sectors)
return 0; return 0;
...@@ -4186,36 +4187,51 @@ int ata_dev_revalidate(struct ata_device *dev, unsigned int new_class, ...@@ -4186,36 +4187,51 @@ int ata_dev_revalidate(struct ata_device *dev, unsigned int new_class,
goto fail; goto fail;
/* verify n_sectors hasn't changed */ /* verify n_sectors hasn't changed */
if (dev->class == ATA_DEV_ATA && n_sectors && if (dev->class != ATA_DEV_ATA || !n_sectors ||
dev->n_sectors != n_sectors) { dev->n_sectors == n_sectors)
ata_dev_printk(dev, KERN_WARNING, "n_sectors mismatch " return 0;
"%llu != %llu\n",
/* n_sectors has changed */
ata_dev_printk(dev, KERN_WARNING, "n_sectors mismatch %llu != %llu\n",
(unsigned long long)n_sectors, (unsigned long long)n_sectors,
(unsigned long long)dev->n_sectors); (unsigned long long)dev->n_sectors);
/* /*
* Something could have caused HPA to be unlocked * Something could have caused HPA to be unlocked
* involuntarily. If n_native_sectors hasn't changed * involuntarily. If n_native_sectors hasn't changed and the
* and the new size matches it, keep the device. * new size matches it, keep the device.
*/ */
if (dev->n_native_sectors == n_native_sectors && if (dev->n_native_sectors == n_native_sectors &&
dev->n_sectors > n_sectors && dev->n_sectors > n_sectors && dev->n_sectors == n_native_sectors) {
dev->n_sectors == n_native_sectors) {
ata_dev_printk(dev, KERN_WARNING, ata_dev_printk(dev, KERN_WARNING,
"new n_sectors matches native, probably " "new n_sectors matches native, probably "
"late HPA unlock, continuing\n"); "late HPA unlock, continuing\n");
/* keep using the old n_sectors */ /* keep using the old n_sectors */
dev->n_sectors = n_sectors; dev->n_sectors = n_sectors;
} else { return 0;
/* 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; /*
* Some BIOSes boot w/o HPA but resume w/ HPA locked. Try
* unlocking HPA in those cases.
*
* https://bugzilla.kernel.org/show_bug.cgi?id=15396
*/
if (dev->n_native_sectors == n_native_sectors &&
dev->n_sectors < n_sectors && n_sectors == n_native_sectors &&
!(dev->horkage & ATA_HORKAGE_BROKEN_HPA)) {
ata_dev_printk(dev, KERN_WARNING,
"old n_sectors matches native, probably "
"late HPA lock, will try to unlock HPA\n");
/* try unlocking HPA */
dev->flags |= ATA_DFLAG_UNLOCK_HPA;
rc = -EIO;
} else
rc = -ENODEV;
/* restore original n_[native_]sectors and fail */
dev->n_native_sectors = n_native_sectors;
dev->n_sectors = n_sectors;
fail: fail:
ata_dev_printk(dev, KERN_ERR, "revalidation failed (errno=%d)\n", rc); ata_dev_printk(dev, KERN_ERR, "revalidation failed (errno=%d)\n", rc);
return rc; return rc;
......
...@@ -146,6 +146,7 @@ enum { ...@@ -146,6 +146,7 @@ enum {
ATA_DFLAG_SLEEPING = (1 << 15), /* device is sleeping */ ATA_DFLAG_SLEEPING = (1 << 15), /* device is sleeping */
ATA_DFLAG_DUBIOUS_XFER = (1 << 16), /* data transfer not verified */ ATA_DFLAG_DUBIOUS_XFER = (1 << 16), /* data transfer not verified */
ATA_DFLAG_NO_UNLOAD = (1 << 17), /* device doesn't support unload */ ATA_DFLAG_NO_UNLOAD = (1 << 17), /* device doesn't support unload */
ATA_DFLAG_UNLOCK_HPA = (1 << 18), /* unlock HPA */
ATA_DFLAG_INIT_MASK = (1 << 24) - 1, ATA_DFLAG_INIT_MASK = (1 << 24) - 1,
ATA_DFLAG_DETACH = (1 << 24), ATA_DFLAG_DETACH = (1 << 24),
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment