ide: Implement disk shock protection support (v4)
authorElias Oltmanns <eo@nebensachen.de>
Mon, 13 Oct 2008 19:39:50 +0000 (21:39 +0200)
committerBartlomiej Zolnierkiewicz <bzolnier@gmail.com>
Mon, 13 Oct 2008 19:39:50 +0000 (21:39 +0200)
On user request (through sysfs), the IDLE IMMEDIATE command with UNLOAD
FEATURE as specified in ATA-7 is issued to the device and processing of
the request queue is stopped thereafter until the specified timeout
expires or user space asks to resume normal operation. This is supposed
to prevent the heads of a hard drive from accidentally crashing onto the
platter when a heavy shock is anticipated (like a falling laptop expected
to hit the floor). Port resets are deferred whenever a device on that
port is in the parked state.

v3:
Elias Oltmanns <eo@nebensachen.de> wrote:
[...]
> >> 1. Make sure that no negative value is being passed to
> >>    jiffies_to_msecs() in ide_park_show().
> >> 2. Drop the superfluous variable hwif in ide_special_rq().
> >> 3. Skip initialisation of task and tf in ide_special_rq() if we are not
> >>    handling a (un)park request.
> >
> > Well, #3 should have been done differently because we donn't want to
> > check for REQ_(UN)?PARK_HEADS more often than is necessary.
>
> While preparing the backport to 2.6.27, it has just occurred to me that
> we need to clear the IDE_DFLAG_PARKED flag in ide_disk_pre_reset()
> because this flag must not be set after *any* sort of access to the
> device.

v4:
Fix a memory leak due to a missing blk_put_request() in
issue_park_cmd(). Additionally, we should plug the queue when enqueueing
the unpark request because there is no guarantee that the park timeout
has not expired by then. Even though the chance for that to happen is
very slim, the request might end up hanging in the queue until the next
I/O operation is queued up. While at it, clean up the code a little:
- make issue_park_cmd() a function of type void since nobody cares for
  the return value anyway;
- use blk_start_queueing() instead of __blk_run_queue() since we don't
  have to worry about recursion;
- remove a superfluous pointer deference in task_no_data_intr().

Signed-off-by: Elias Oltmanns <eo@nebensachen.de>
Cc: Jeff Garzik <jeff@garzik.org>,
Cc: Randy Dunlap <randy.dunlap@oracle.com>
Cc: Tejun Heo <htejun@gmail.com>
Signed-off-by: Bartlomiej Zolnierkiewicz <bzolnier@gmail.com>
drivers/ide/Makefile
drivers/ide/ide-io.c
drivers/ide/ide-iops.c
drivers/ide/ide-park.c [new file with mode: 0644]
drivers/ide/ide-probe.c
drivers/ide/ide-taskfile.c
drivers/ide/ide.c
include/linux/ide.h

index 0c30adb115c87e3bb5745258c645ce37bd677191..ceaf779054eac07246702bf789b0ffbc6a97b7b3 100644 (file)
@@ -5,7 +5,7 @@
 EXTRA_CFLAGS                           += -Idrivers/ide
 
 ide-core-y += ide.o ide-ioctls.o ide-io.o ide-iops.o ide-lib.o ide-probe.o \
-             ide-taskfile.o ide-pio-blacklist.o
+             ide-taskfile.o ide-park.o ide-pio-blacklist.o
 
 # core IDE code
 ide-core-$(CONFIG_IDE_TIMINGS)         += ide-timings.o
index e205f46c3c7aa4de4ff944dc1a7b22f7c401f4cb..77c6eaeacefafaf987f0d7500eb82f394e189284 100644 (file)
@@ -672,7 +672,32 @@ EXPORT_SYMBOL_GPL(ide_devset_execute);
 
 static ide_startstop_t ide_special_rq(ide_drive_t *drive, struct request *rq)
 {
-       switch (rq->cmd[0]) {
+       u8 cmd = rq->cmd[0];
+
+       if (cmd == REQ_PARK_HEADS || cmd == REQ_UNPARK_HEADS) {
+               ide_task_t task;
+               struct ide_taskfile *tf = &task.tf;
+
+               memset(&task, 0, sizeof(task));
+               if (cmd == REQ_PARK_HEADS) {
+                       drive->sleep = *(unsigned long *)rq->special;
+                       drive->dev_flags |= IDE_DFLAG_SLEEPING;
+                       tf->command = ATA_CMD_IDLEIMMEDIATE;
+                       tf->feature = 0x44;
+                       tf->lbal = 0x4c;
+                       tf->lbam = 0x4e;
+                       tf->lbah = 0x55;
+                       task.tf_flags |= IDE_TFLAG_CUSTOM_HANDLER;
+               } else          /* cmd == REQ_UNPARK_HEADS */
+                       tf->command = ATA_CMD_CHK_POWER;
+
+               task.tf_flags |= IDE_TFLAG_TF | IDE_TFLAG_DEVICE;
+               task.rq = rq;
+               drive->hwif->data_phase = task.data_phase = TASKFILE_NO_DATA;
+               return do_rw_taskfile(drive, &task);
+       }
+
+       switch (cmd) {
        case REQ_DEVSET_EXEC:
        {
                int err, (*setfunc)(ide_drive_t *, int) = rq->special;
@@ -1008,7 +1033,7 @@ static void ide_do_request (ide_hwgroup_t *hwgroup, int masked_irq)
                }
                hwgroup->hwif = hwif;
                hwgroup->drive = drive;
-               drive->dev_flags &= ~IDE_DFLAG_SLEEPING;
+               drive->dev_flags &= ~(IDE_DFLAG_SLEEPING | IDE_DFLAG_PARKED);
                drive->service_start = jiffies;
 
                if (blk_queue_plugged(drive->queue)) {
index 91182ebed468b61f485ac05ddcb93a45ed110946..b762deb2dacb34f408ff55d0104581593f117e5d 100644 (file)
@@ -1020,6 +1020,7 @@ static void ide_disk_pre_reset(ide_drive_t *drive)
        drive->special.b.recalibrate  = legacy;
 
        drive->mult_count = 0;
+       drive->dev_flags &= ~IDE_DFLAG_PARKED;
 
        if ((drive->dev_flags & IDE_DFLAG_KEEP_SETTINGS) == 0 &&
            (drive->dev_flags & IDE_DFLAG_USING_DMA) == 0)
@@ -1079,12 +1080,13 @@ static void pre_reset(ide_drive_t *drive)
 static ide_startstop_t do_reset1 (ide_drive_t *drive, int do_not_try_atapi)
 {
        unsigned int unit;
-       unsigned long flags;
+       unsigned long flags, timeout;
        ide_hwif_t *hwif;
        ide_hwgroup_t *hwgroup;
        struct ide_io_ports *io_ports;
        const struct ide_tp_ops *tp_ops;
        const struct ide_port_ops *port_ops;
+       DEFINE_WAIT(wait);
 
        spin_lock_irqsave(&ide_lock, flags);
        hwif = HWIF(drive);
@@ -1111,6 +1113,31 @@ static ide_startstop_t do_reset1 (ide_drive_t *drive, int do_not_try_atapi)
                return ide_started;
        }
 
+       /* We must not disturb devices in the IDE_DFLAG_PARKED state. */
+       do {
+               unsigned long now;
+
+               prepare_to_wait(&ide_park_wq, &wait, TASK_UNINTERRUPTIBLE);
+               timeout = jiffies;
+               for (unit = 0; unit < MAX_DRIVES; unit++) {
+                       ide_drive_t *tdrive = &hwif->drives[unit];
+
+                       if (tdrive->dev_flags & IDE_DFLAG_PRESENT &&
+                           tdrive->dev_flags & IDE_DFLAG_PARKED &&
+                           time_after(tdrive->sleep, timeout))
+                               timeout = tdrive->sleep;
+               }
+
+               now = jiffies;
+               if (time_before_eq(timeout, now))
+                       break;
+
+               spin_unlock_irqrestore(&ide_lock, flags);
+               timeout = schedule_timeout_uninterruptible(timeout - now);
+               spin_lock_irqsave(&ide_lock, flags);
+       } while (timeout);
+       finish_wait(&ide_park_wq, &wait);
+
        /*
         * First, reset any device state data we were maintaining
         * for any of the drives on this interface.
diff --git a/drivers/ide/ide-park.c b/drivers/ide/ide-park.c
new file mode 100644 (file)
index 0000000..03b00e5
--- /dev/null
@@ -0,0 +1,121 @@
+#include <linux/kernel.h>
+#include <linux/ide.h>
+#include <linux/jiffies.h>
+#include <linux/blkdev.h>
+
+DECLARE_WAIT_QUEUE_HEAD(ide_park_wq);
+
+static void issue_park_cmd(ide_drive_t *drive, unsigned long timeout)
+{
+       struct request_queue *q = drive->queue;
+       struct request *rq;
+       int rc;
+
+       timeout += jiffies;
+       spin_lock_irq(&ide_lock);
+       if (drive->dev_flags & IDE_DFLAG_PARKED) {
+               ide_hwgroup_t *hwgroup = drive->hwif->hwgroup;
+               int reset_timer;
+
+               reset_timer = time_before(timeout, drive->sleep);
+               drive->sleep = timeout;
+               wake_up_all(&ide_park_wq);
+               if (reset_timer && hwgroup->sleeping &&
+                   del_timer(&hwgroup->timer)) {
+                       hwgroup->sleeping = 0;
+                       hwgroup->busy = 0;
+                       blk_start_queueing(q);
+               }
+               spin_unlock_irq(&ide_lock);
+               return;
+       }
+       spin_unlock_irq(&ide_lock);
+
+       rq = blk_get_request(q, READ, __GFP_WAIT);
+       rq->cmd[0] = REQ_PARK_HEADS;
+       rq->cmd_len = 1;
+       rq->cmd_type = REQ_TYPE_SPECIAL;
+       rq->special = &timeout;
+       rc = blk_execute_rq(q, NULL, rq, 1);
+       blk_put_request(rq);
+       if (rc)
+               goto out;
+
+       /*
+        * Make sure that *some* command is sent to the drive after the
+        * timeout has expired, so power management will be reenabled.
+        */
+       rq = blk_get_request(q, READ, GFP_NOWAIT);
+       if (unlikely(!rq))
+               goto out;
+
+       rq->cmd[0] = REQ_UNPARK_HEADS;
+       rq->cmd_len = 1;
+       rq->cmd_type = REQ_TYPE_SPECIAL;
+       elv_add_request(q, rq, ELEVATOR_INSERT_FRONT, 1);
+
+out:
+       return;
+}
+
+ssize_t ide_park_show(struct device *dev, struct device_attribute *attr,
+                     char *buf)
+{
+       ide_drive_t *drive = to_ide_device(dev);
+       unsigned long now;
+       unsigned int msecs;
+
+       if (drive->dev_flags & IDE_DFLAG_NO_UNLOAD)
+               return -EOPNOTSUPP;
+
+       spin_lock_irq(&ide_lock);
+       now = jiffies;
+       if (drive->dev_flags & IDE_DFLAG_PARKED &&
+           time_after(drive->sleep, now))
+               msecs = jiffies_to_msecs(drive->sleep - now);
+       else
+               msecs = 0;
+       spin_unlock_irq(&ide_lock);
+
+       return snprintf(buf, 20, "%u\n", msecs);
+}
+
+ssize_t ide_park_store(struct device *dev, struct device_attribute *attr,
+                      const char *buf, size_t len)
+{
+#define MAX_PARK_TIMEOUT 30000
+       ide_drive_t *drive = to_ide_device(dev);
+       long int input;
+       int rc;
+
+       rc = strict_strtol(buf, 10, &input);
+       if (rc || input < -2)
+               return -EINVAL;
+       if (input > MAX_PARK_TIMEOUT) {
+               input = MAX_PARK_TIMEOUT;
+               rc = -EOVERFLOW;
+       }
+
+       mutex_lock(&ide_setting_mtx);
+       if (input >= 0) {
+               if (drive->dev_flags & IDE_DFLAG_NO_UNLOAD)
+                       rc = -EOPNOTSUPP;
+               else if (input || drive->dev_flags & IDE_DFLAG_PARKED)
+                       issue_park_cmd(drive, msecs_to_jiffies(input));
+       } else {
+               if (drive->media == ide_disk)
+                       switch (input) {
+                       case -1:
+                               drive->dev_flags &= ~IDE_DFLAG_NO_UNLOAD;
+                               break;
+                       case -2:
+                               drive->dev_flags |= IDE_DFLAG_NO_UNLOAD;
+                               break;
+                       }
+               else
+                       rc = -EOPNOTSUPP;
+       }
+       mutex_unlock(&ide_setting_mtx);
+
+       return rc ? rc : len;
+}
index de8edd306c79b6ea723b87ff430971a321ea15f0..f27baa5f140e903cff37678d6be157be55f4decd 100644 (file)
@@ -208,6 +208,8 @@ static inline void do_identify (ide_drive_t *drive, u8 cmd)
                drive->ready_stat = 0;
                if (ata_id_cdb_intr(id))
                        drive->atapi_flags |= IDE_AFLAG_DRQ_INTERRUPT;
+               /* we don't do head unloading on ATAPI devices */
+               drive->dev_flags |= IDE_DFLAG_NO_UNLOAD;
                return;
        }
 
@@ -223,6 +225,9 @@ static inline void do_identify (ide_drive_t *drive, u8 cmd)
 
        drive->media = ide_disk;
 
+       if (!ata_id_has_unload(drive->id))
+               drive->dev_flags |= IDE_DFLAG_NO_UNLOAD;
+
        printk(KERN_CONT "%s DISK drive\n", is_cfa ? "CFA" : "ATA");
 
        return;
index a4c2d91179b36647e53a362ddea4e2354b4e7daa..bf4fb9d8d176f2f91efa7a91a06d229f80f4d502 100644 (file)
@@ -152,7 +152,16 @@ static ide_startstop_t task_no_data_intr(ide_drive_t *drive)
 
        if (!custom)
                ide_end_drive_cmd(drive, stat, ide_read_error(drive));
-       else if (tf->command == ATA_CMD_SET_MULTI)
+       else if (tf->command == ATA_CMD_IDLEIMMEDIATE) {
+               hwif->tp_ops->tf_read(drive, task);
+               if (tf->lbal != 0xc4) {
+                       printk(KERN_ERR "%s: head unload failed!\n",
+                              drive->name);
+                       ide_tf_dump(drive->name, tf);
+               } else
+                       drive->dev_flags |= IDE_DFLAG_PARKED;
+               ide_end_drive_cmd(drive, stat, ide_read_error(drive));
+       } else if (tf->command == ATA_CMD_SET_MULTI)
                drive->mult_count = drive->mult_req;
 
        return ide_stopped;
index 083783e851d12f2346918ca7607e23f6218c4ac6..04f8f13cb9d74d697f65f3c37bb03117b3bc0057 100644 (file)
@@ -587,6 +587,7 @@ static struct device_attribute ide_dev_attrs[] = {
        __ATTR_RO(model),
        __ATTR_RO(firmware),
        __ATTR(serial, 0400, serial_show, NULL),
+       __ATTR(unload_heads, 0644, ide_park_show, ide_park_store),
        __ATTR_NULL
 };
 
index 8247b286838d2a6891d311a855b095d30f9603a7..c47e371554c1ccb122945ab087eb4c9914480010 100644 (file)
@@ -156,6 +156,8 @@ enum {
  */
 #define REQ_DRIVE_RESET                0x20
 #define REQ_DEVSET_EXEC                0x21
+#define REQ_PARK_HEADS         0x22
+#define REQ_UNPARK_HEADS       0x23
 
 /*
  * Check for an interrupt and acknowledge the interrupt status
@@ -573,6 +575,10 @@ enum {
        /* retrying in PIO */
        IDE_DFLAG_DMA_PIO_RETRY         = (1 << 25),
        IDE_DFLAG_LBA                   = (1 << 26),
+       /* don't unload heads */
+       IDE_DFLAG_NO_UNLOAD             = (1 << 27),
+       /* heads unloaded, please don't reset port */
+       IDE_DFLAG_PARKED                = (1 << 28)
 };
 
 struct ide_drive_s {
@@ -1207,6 +1213,13 @@ int ide_check_atapi_device(ide_drive_t *, const char *);
 
 void ide_init_pc(struct ide_atapi_pc *);
 
+/* Disk head parking */
+extern wait_queue_head_t ide_park_wq;
+ssize_t ide_park_show(struct device *dev, struct device_attribute *attr,
+                     char *buf);
+ssize_t ide_park_store(struct device *dev, struct device_attribute *attr,
+                      const char *buf, size_t len);
+
 /*
  * Special requests for ide-tape block device strategy routine.
  *