aboutsummaryrefslogtreecommitdiffstats
path: root/drivers
diff options
context:
space:
mode:
authorElias Oltmanns <eo@nebensachen.de>2008-10-13 15:39:50 -0400
committerBartlomiej Zolnierkiewicz <bzolnier@gmail.com>2008-10-13 15:39:50 -0400
commit4abdc6ee7c47a1a6e12f95717e461baeebee5df7 (patch)
tree51cac3d66891d026483838563e35124467a8eb86 /drivers
parent08243ba731ee08ff42cf1589379c81567690218f (diff)
ide: Implement disk shock protection support (v4)
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>
Diffstat (limited to 'drivers')
-rw-r--r--drivers/ide/Makefile2
-rw-r--r--drivers/ide/ide-io.c29
-rw-r--r--drivers/ide/ide-iops.c29
-rw-r--r--drivers/ide/ide-park.c121
-rw-r--r--drivers/ide/ide-probe.c5
-rw-r--r--drivers/ide/ide-taskfile.c11
-rw-r--r--drivers/ide/ide.c1
7 files changed, 193 insertions, 5 deletions
diff --git a/drivers/ide/Makefile b/drivers/ide/Makefile
index 0c30adb115c8..ceaf779054ea 100644
--- a/drivers/ide/Makefile
+++ b/drivers/ide/Makefile
@@ -5,7 +5,7 @@
5EXTRA_CFLAGS += -Idrivers/ide 5EXTRA_CFLAGS += -Idrivers/ide
6 6
7ide-core-y += ide.o ide-ioctls.o ide-io.o ide-iops.o ide-lib.o ide-probe.o \ 7ide-core-y += ide.o ide-ioctls.o ide-io.o ide-iops.o ide-lib.o ide-probe.o \
8 ide-taskfile.o ide-pio-blacklist.o 8 ide-taskfile.o ide-park.o ide-pio-blacklist.o
9 9
10# core IDE code 10# core IDE code
11ide-core-$(CONFIG_IDE_TIMINGS) += ide-timings.o 11ide-core-$(CONFIG_IDE_TIMINGS) += ide-timings.o
diff --git a/drivers/ide/ide-io.c b/drivers/ide/ide-io.c
index e205f46c3c7a..77c6eaeacefa 100644
--- a/drivers/ide/ide-io.c
+++ b/drivers/ide/ide-io.c
@@ -672,7 +672,32 @@ EXPORT_SYMBOL_GPL(ide_devset_execute);
672 672
673static ide_startstop_t ide_special_rq(ide_drive_t *drive, struct request *rq) 673static ide_startstop_t ide_special_rq(ide_drive_t *drive, struct request *rq)
674{ 674{
675 switch (rq->cmd[0]) { 675 u8 cmd = rq->cmd[0];
676
677 if (cmd == REQ_PARK_HEADS || cmd == REQ_UNPARK_HEADS) {
678 ide_task_t task;
679 struct ide_taskfile *tf = &task.tf;
680
681 memset(&task, 0, sizeof(task));
682 if (cmd == REQ_PARK_HEADS) {
683 drive->sleep = *(unsigned long *)rq->special;
684 drive->dev_flags |= IDE_DFLAG_SLEEPING;
685 tf->command = ATA_CMD_IDLEIMMEDIATE;
686 tf->feature = 0x44;
687 tf->lbal = 0x4c;
688 tf->lbam = 0x4e;
689 tf->lbah = 0x55;
690 task.tf_flags |= IDE_TFLAG_CUSTOM_HANDLER;
691 } else /* cmd == REQ_UNPARK_HEADS */
692 tf->command = ATA_CMD_CHK_POWER;
693
694 task.tf_flags |= IDE_TFLAG_TF | IDE_TFLAG_DEVICE;
695 task.rq = rq;
696 drive->hwif->data_phase = task.data_phase = TASKFILE_NO_DATA;
697 return do_rw_taskfile(drive, &task);
698 }
699
700 switch (cmd) {
676 case REQ_DEVSET_EXEC: 701 case REQ_DEVSET_EXEC:
677 { 702 {
678 int err, (*setfunc)(ide_drive_t *, int) = rq->special; 703 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)
1008 } 1033 }
1009 hwgroup->hwif = hwif; 1034 hwgroup->hwif = hwif;
1010 hwgroup->drive = drive; 1035 hwgroup->drive = drive;
1011 drive->dev_flags &= ~IDE_DFLAG_SLEEPING; 1036 drive->dev_flags &= ~(IDE_DFLAG_SLEEPING | IDE_DFLAG_PARKED);
1012 drive->service_start = jiffies; 1037 drive->service_start = jiffies;
1013 1038
1014 if (blk_queue_plugged(drive->queue)) { 1039 if (blk_queue_plugged(drive->queue)) {
diff --git a/drivers/ide/ide-iops.c b/drivers/ide/ide-iops.c
index 91182ebed468..b762deb2dacb 100644
--- a/drivers/ide/ide-iops.c
+++ b/drivers/ide/ide-iops.c
@@ -1020,6 +1020,7 @@ static void ide_disk_pre_reset(ide_drive_t *drive)
1020 drive->special.b.recalibrate = legacy; 1020 drive->special.b.recalibrate = legacy;
1021 1021
1022 drive->mult_count = 0; 1022 drive->mult_count = 0;
1023 drive->dev_flags &= ~IDE_DFLAG_PARKED;
1023 1024
1024 if ((drive->dev_flags & IDE_DFLAG_KEEP_SETTINGS) == 0 && 1025 if ((drive->dev_flags & IDE_DFLAG_KEEP_SETTINGS) == 0 &&
1025 (drive->dev_flags & IDE_DFLAG_USING_DMA) == 0) 1026 (drive->dev_flags & IDE_DFLAG_USING_DMA) == 0)
@@ -1079,12 +1080,13 @@ static void pre_reset(ide_drive_t *drive)
1079static ide_startstop_t do_reset1 (ide_drive_t *drive, int do_not_try_atapi) 1080static ide_startstop_t do_reset1 (ide_drive_t *drive, int do_not_try_atapi)
1080{ 1081{
1081 unsigned int unit; 1082 unsigned int unit;
1082 unsigned long flags; 1083 unsigned long flags, timeout;
1083 ide_hwif_t *hwif; 1084 ide_hwif_t *hwif;
1084 ide_hwgroup_t *hwgroup; 1085 ide_hwgroup_t *hwgroup;
1085 struct ide_io_ports *io_ports; 1086 struct ide_io_ports *io_ports;
1086 const struct ide_tp_ops *tp_ops; 1087 const struct ide_tp_ops *tp_ops;
1087 const struct ide_port_ops *port_ops; 1088 const struct ide_port_ops *port_ops;
1089 DEFINE_WAIT(wait);
1088 1090
1089 spin_lock_irqsave(&ide_lock, flags); 1091 spin_lock_irqsave(&ide_lock, flags);
1090 hwif = HWIF(drive); 1092 hwif = HWIF(drive);
@@ -1111,6 +1113,31 @@ static ide_startstop_t do_reset1 (ide_drive_t *drive, int do_not_try_atapi)
1111 return ide_started; 1113 return ide_started;
1112 } 1114 }
1113 1115
1116 /* We must not disturb devices in the IDE_DFLAG_PARKED state. */
1117 do {
1118 unsigned long now;
1119
1120 prepare_to_wait(&ide_park_wq, &wait, TASK_UNINTERRUPTIBLE);
1121 timeout = jiffies;
1122 for (unit = 0; unit < MAX_DRIVES; unit++) {
1123 ide_drive_t *tdrive = &hwif->drives[unit];
1124
1125 if (tdrive->dev_flags & IDE_DFLAG_PRESENT &&
1126 tdrive->dev_flags & IDE_DFLAG_PARKED &&
1127 time_after(tdrive->sleep, timeout))
1128 timeout = tdrive->sleep;
1129 }
1130
1131 now = jiffies;
1132 if (time_before_eq(timeout, now))
1133 break;
1134
1135 spin_unlock_irqrestore(&ide_lock, flags);
1136 timeout = schedule_timeout_uninterruptible(timeout - now);
1137 spin_lock_irqsave(&ide_lock, flags);
1138 } while (timeout);
1139 finish_wait(&ide_park_wq, &wait);
1140
1114 /* 1141 /*
1115 * First, reset any device state data we were maintaining 1142 * First, reset any device state data we were maintaining
1116 * for any of the drives on this interface. 1143 * 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
index 000000000000..03b00e57e93f
--- /dev/null
+++ b/drivers/ide/ide-park.c
@@ -0,0 +1,121 @@
1#include <linux/kernel.h>
2#include <linux/ide.h>
3#include <linux/jiffies.h>
4#include <linux/blkdev.h>
5
6DECLARE_WAIT_QUEUE_HEAD(ide_park_wq);
7
8static void issue_park_cmd(ide_drive_t *drive, unsigned long timeout)
9{
10 struct request_queue *q = drive->queue;
11 struct request *rq;
12 int rc;
13
14 timeout += jiffies;
15 spin_lock_irq(&ide_lock);
16 if (drive->dev_flags & IDE_DFLAG_PARKED) {
17 ide_hwgroup_t *hwgroup = drive->hwif->hwgroup;
18 int reset_timer;
19
20 reset_timer = time_before(timeout, drive->sleep);
21 drive->sleep = timeout;
22 wake_up_all(&ide_park_wq);
23 if (reset_timer && hwgroup->sleeping &&
24 del_timer(&hwgroup->timer)) {
25 hwgroup->sleeping = 0;
26 hwgroup->busy = 0;
27 blk_start_queueing(q);
28 }
29 spin_unlock_irq(&ide_lock);
30 return;
31 }
32 spin_unlock_irq(&ide_lock);
33
34 rq = blk_get_request(q, READ, __GFP_WAIT);
35 rq->cmd[0] = REQ_PARK_HEADS;
36 rq->cmd_len = 1;
37 rq->cmd_type = REQ_TYPE_SPECIAL;
38 rq->special = &timeout;
39 rc = blk_execute_rq(q, NULL, rq, 1);
40 blk_put_request(rq);
41 if (rc)
42 goto out;
43
44 /*
45 * Make sure that *some* command is sent to the drive after the
46 * timeout has expired, so power management will be reenabled.
47 */
48 rq = blk_get_request(q, READ, GFP_NOWAIT);
49 if (unlikely(!rq))
50 goto out;
51
52 rq->cmd[0] = REQ_UNPARK_HEADS;
53 rq->cmd_len = 1;
54 rq->cmd_type = REQ_TYPE_SPECIAL;
55 elv_add_request(q, rq, ELEVATOR_INSERT_FRONT, 1);
56
57out:
58 return;
59}
60
61ssize_t ide_park_show(struct device *dev, struct device_attribute *attr,
62 char *buf)
63{
64 ide_drive_t *drive = to_ide_device(dev);
65 unsigned long now;
66 unsigned int msecs;
67
68 if (drive->dev_flags & IDE_DFLAG_NO_UNLOAD)
69 return -EOPNOTSUPP;
70
71 spin_lock_irq(&ide_lock);
72 now = jiffies;
73 if (drive->dev_flags & IDE_DFLAG_PARKED &&
74 time_after(drive->sleep, now))
75 msecs = jiffies_to_msecs(drive->sleep - now);
76 else
77 msecs = 0;
78 spin_unlock_irq(&ide_lock);
79
80 return snprintf(buf, 20, "%u\n", msecs);
81}
82
83ssize_t ide_park_store(struct device *dev, struct device_attribute *attr,
84 const char *buf, size_t len)
85{
86#define MAX_PARK_TIMEOUT 30000
87 ide_drive_t *drive = to_ide_device(dev);
88 long int input;
89 int rc;
90
91 rc = strict_strtol(buf, 10, &input);
92 if (rc || input < -2)
93 return -EINVAL;
94 if (input > MAX_PARK_TIMEOUT) {
95 input = MAX_PARK_TIMEOUT;
96 rc = -EOVERFLOW;
97 }
98
99 mutex_lock(&ide_setting_mtx);
100 if (input >= 0) {
101 if (drive->dev_flags & IDE_DFLAG_NO_UNLOAD)
102 rc = -EOPNOTSUPP;
103 else if (input || drive->dev_flags & IDE_DFLAG_PARKED)
104 issue_park_cmd(drive, msecs_to_jiffies(input));
105 } else {
106 if (drive->media == ide_disk)
107 switch (input) {
108 case -1:
109 drive->dev_flags &= ~IDE_DFLAG_NO_UNLOAD;
110 break;
111 case -2:
112 drive->dev_flags |= IDE_DFLAG_NO_UNLOAD;
113 break;
114 }
115 else
116 rc = -EOPNOTSUPP;
117 }
118 mutex_unlock(&ide_setting_mtx);
119
120 return rc ? rc : len;
121}
diff --git a/drivers/ide/ide-probe.c b/drivers/ide/ide-probe.c
index de8edd306c79..f27baa5f140e 100644
--- a/drivers/ide/ide-probe.c
+++ b/drivers/ide/ide-probe.c
@@ -208,6 +208,8 @@ static inline void do_identify (ide_drive_t *drive, u8 cmd)
208 drive->ready_stat = 0; 208 drive->ready_stat = 0;
209 if (ata_id_cdb_intr(id)) 209 if (ata_id_cdb_intr(id))
210 drive->atapi_flags |= IDE_AFLAG_DRQ_INTERRUPT; 210 drive->atapi_flags |= IDE_AFLAG_DRQ_INTERRUPT;
211 /* we don't do head unloading on ATAPI devices */
212 drive->dev_flags |= IDE_DFLAG_NO_UNLOAD;
211 return; 213 return;
212 } 214 }
213 215
@@ -223,6 +225,9 @@ static inline void do_identify (ide_drive_t *drive, u8 cmd)
223 225
224 drive->media = ide_disk; 226 drive->media = ide_disk;
225 227
228 if (!ata_id_has_unload(drive->id))
229 drive->dev_flags |= IDE_DFLAG_NO_UNLOAD;
230
226 printk(KERN_CONT "%s DISK drive\n", is_cfa ? "CFA" : "ATA"); 231 printk(KERN_CONT "%s DISK drive\n", is_cfa ? "CFA" : "ATA");
227 232
228 return; 233 return;
diff --git a/drivers/ide/ide-taskfile.c b/drivers/ide/ide-taskfile.c
index a4c2d91179b3..bf4fb9d8d176 100644
--- a/drivers/ide/ide-taskfile.c
+++ b/drivers/ide/ide-taskfile.c
@@ -152,7 +152,16 @@ static ide_startstop_t task_no_data_intr(ide_drive_t *drive)
152 152
153 if (!custom) 153 if (!custom)
154 ide_end_drive_cmd(drive, stat, ide_read_error(drive)); 154 ide_end_drive_cmd(drive, stat, ide_read_error(drive));
155 else if (tf->command == ATA_CMD_SET_MULTI) 155 else if (tf->command == ATA_CMD_IDLEIMMEDIATE) {
156 hwif->tp_ops->tf_read(drive, task);
157 if (tf->lbal != 0xc4) {
158 printk(KERN_ERR "%s: head unload failed!\n",
159 drive->name);
160 ide_tf_dump(drive->name, tf);
161 } else
162 drive->dev_flags |= IDE_DFLAG_PARKED;
163 ide_end_drive_cmd(drive, stat, ide_read_error(drive));
164 } else if (tf->command == ATA_CMD_SET_MULTI)
156 drive->mult_count = drive->mult_req; 165 drive->mult_count = drive->mult_req;
157 166
158 return ide_stopped; 167 return ide_stopped;
diff --git a/drivers/ide/ide.c b/drivers/ide/ide.c
index 083783e851d1..04f8f13cb9d7 100644
--- a/drivers/ide/ide.c
+++ b/drivers/ide/ide.c
@@ -587,6 +587,7 @@ static struct device_attribute ide_dev_attrs[] = {
587 __ATTR_RO(model), 587 __ATTR_RO(model),
588 __ATTR_RO(firmware), 588 __ATTR_RO(firmware),
589 __ATTR(serial, 0400, serial_show, NULL), 589 __ATTR(serial, 0400, serial_show, NULL),
590 __ATTR(unload_heads, 0644, ide_park_show, ide_park_store),
590 __ATTR_NULL 591 __ATTR_NULL
591}; 592};
592 593