diff --git a/drivers/ide/Makefile b/drivers/ide/Makefile index 0c30adb115c..ceaf779054e 100644 --- a/drivers/ide/Makefile +++ b/drivers/ide/Makefile @@ -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 diff --git a/drivers/ide/ide-io.c b/drivers/ide/ide-io.c index e205f46c3c7..77c6eaeacef 100644 --- a/drivers/ide/ide-io.c +++ b/drivers/ide/ide-io.c @@ -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)) { diff --git a/drivers/ide/ide-iops.c b/drivers/ide/ide-iops.c index 91182ebed46..b762deb2dac 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) 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 index 00000000000..03b00e57e93 --- /dev/null +++ b/drivers/ide/ide-park.c @@ -0,0 +1,121 @@ +#include +#include +#include +#include + +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; +} diff --git a/drivers/ide/ide-probe.c b/drivers/ide/ide-probe.c index de8edd306c7..f27baa5f140 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) 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; diff --git a/drivers/ide/ide-taskfile.c b/drivers/ide/ide-taskfile.c index a4c2d91179b..bf4fb9d8d17 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) 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; diff --git a/drivers/ide/ide.c b/drivers/ide/ide.c index 083783e851d..04f8f13cb9d 100644 --- a/drivers/ide/ide.c +++ b/drivers/ide/ide.c @@ -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 }; diff --git a/include/linux/ide.h b/include/linux/ide.h index 8247b286838..c47e371554c 100644 --- a/include/linux/ide.h +++ b/include/linux/ide.h @@ -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. *