xref: /kernel/linux/linux-5.10/drivers/ide/ide-park.c (revision 8c2ecf20)
18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
28c2ecf20Sopenharmony_ci#include <linux/kernel.h>
38c2ecf20Sopenharmony_ci#include <linux/gfp.h>
48c2ecf20Sopenharmony_ci#include <linux/ide.h>
58c2ecf20Sopenharmony_ci#include <linux/jiffies.h>
68c2ecf20Sopenharmony_ci#include <linux/blkdev.h>
78c2ecf20Sopenharmony_ci
88c2ecf20Sopenharmony_ciDECLARE_WAIT_QUEUE_HEAD(ide_park_wq);
98c2ecf20Sopenharmony_ci
108c2ecf20Sopenharmony_cistatic void issue_park_cmd(ide_drive_t *drive, unsigned long timeout)
118c2ecf20Sopenharmony_ci{
128c2ecf20Sopenharmony_ci	ide_hwif_t *hwif = drive->hwif;
138c2ecf20Sopenharmony_ci	struct request_queue *q = drive->queue;
148c2ecf20Sopenharmony_ci	struct request *rq;
158c2ecf20Sopenharmony_ci	int rc;
168c2ecf20Sopenharmony_ci
178c2ecf20Sopenharmony_ci	timeout += jiffies;
188c2ecf20Sopenharmony_ci	spin_lock_irq(&hwif->lock);
198c2ecf20Sopenharmony_ci	if (drive->dev_flags & IDE_DFLAG_PARKED) {
208c2ecf20Sopenharmony_ci		int reset_timer = time_before(timeout, drive->sleep);
218c2ecf20Sopenharmony_ci		int start_queue = 0;
228c2ecf20Sopenharmony_ci
238c2ecf20Sopenharmony_ci		drive->sleep = timeout;
248c2ecf20Sopenharmony_ci		wake_up_all(&ide_park_wq);
258c2ecf20Sopenharmony_ci		if (reset_timer && del_timer(&hwif->timer))
268c2ecf20Sopenharmony_ci			start_queue = 1;
278c2ecf20Sopenharmony_ci		spin_unlock_irq(&hwif->lock);
288c2ecf20Sopenharmony_ci
298c2ecf20Sopenharmony_ci		if (start_queue)
308c2ecf20Sopenharmony_ci			blk_mq_run_hw_queues(q, true);
318c2ecf20Sopenharmony_ci		return;
328c2ecf20Sopenharmony_ci	}
338c2ecf20Sopenharmony_ci	spin_unlock_irq(&hwif->lock);
348c2ecf20Sopenharmony_ci
358c2ecf20Sopenharmony_ci	rq = blk_get_request(q, REQ_OP_DRV_IN, 0);
368c2ecf20Sopenharmony_ci	scsi_req(rq)->cmd[0] = REQ_PARK_HEADS;
378c2ecf20Sopenharmony_ci	scsi_req(rq)->cmd_len = 1;
388c2ecf20Sopenharmony_ci	ide_req(rq)->type = ATA_PRIV_MISC;
398c2ecf20Sopenharmony_ci	ide_req(rq)->special = &timeout;
408c2ecf20Sopenharmony_ci	blk_execute_rq(q, NULL, rq, 1);
418c2ecf20Sopenharmony_ci	rc = scsi_req(rq)->result ? -EIO : 0;
428c2ecf20Sopenharmony_ci	blk_put_request(rq);
438c2ecf20Sopenharmony_ci	if (rc)
448c2ecf20Sopenharmony_ci		goto out;
458c2ecf20Sopenharmony_ci
468c2ecf20Sopenharmony_ci	/*
478c2ecf20Sopenharmony_ci	 * Make sure that *some* command is sent to the drive after the
488c2ecf20Sopenharmony_ci	 * timeout has expired, so power management will be reenabled.
498c2ecf20Sopenharmony_ci	 */
508c2ecf20Sopenharmony_ci	rq = blk_get_request(q, REQ_OP_DRV_IN, BLK_MQ_REQ_NOWAIT);
518c2ecf20Sopenharmony_ci	if (IS_ERR(rq))
528c2ecf20Sopenharmony_ci		goto out;
538c2ecf20Sopenharmony_ci
548c2ecf20Sopenharmony_ci	scsi_req(rq)->cmd[0] = REQ_UNPARK_HEADS;
558c2ecf20Sopenharmony_ci	scsi_req(rq)->cmd_len = 1;
568c2ecf20Sopenharmony_ci	ide_req(rq)->type = ATA_PRIV_MISC;
578c2ecf20Sopenharmony_ci	spin_lock_irq(&hwif->lock);
588c2ecf20Sopenharmony_ci	ide_insert_request_head(drive, rq);
598c2ecf20Sopenharmony_ci	spin_unlock_irq(&hwif->lock);
608c2ecf20Sopenharmony_ci
618c2ecf20Sopenharmony_ciout:
628c2ecf20Sopenharmony_ci	return;
638c2ecf20Sopenharmony_ci}
648c2ecf20Sopenharmony_ci
658c2ecf20Sopenharmony_ciide_startstop_t ide_do_park_unpark(ide_drive_t *drive, struct request *rq)
668c2ecf20Sopenharmony_ci{
678c2ecf20Sopenharmony_ci	struct ide_cmd cmd;
688c2ecf20Sopenharmony_ci	struct ide_taskfile *tf = &cmd.tf;
698c2ecf20Sopenharmony_ci
708c2ecf20Sopenharmony_ci	memset(&cmd, 0, sizeof(cmd));
718c2ecf20Sopenharmony_ci	if (scsi_req(rq)->cmd[0] == REQ_PARK_HEADS) {
728c2ecf20Sopenharmony_ci		drive->sleep = *(unsigned long *)ide_req(rq)->special;
738c2ecf20Sopenharmony_ci		drive->dev_flags |= IDE_DFLAG_SLEEPING;
748c2ecf20Sopenharmony_ci		tf->command = ATA_CMD_IDLEIMMEDIATE;
758c2ecf20Sopenharmony_ci		tf->feature = 0x44;
768c2ecf20Sopenharmony_ci		tf->lbal = 0x4c;
778c2ecf20Sopenharmony_ci		tf->lbam = 0x4e;
788c2ecf20Sopenharmony_ci		tf->lbah = 0x55;
798c2ecf20Sopenharmony_ci		cmd.valid.out.tf = IDE_VALID_OUT_TF | IDE_VALID_DEVICE;
808c2ecf20Sopenharmony_ci		cmd.valid.in.tf  = IDE_VALID_IN_TF  | IDE_VALID_DEVICE;
818c2ecf20Sopenharmony_ci	} else		/* cmd == REQ_UNPARK_HEADS */
828c2ecf20Sopenharmony_ci		tf->command = ATA_CMD_CHK_POWER;
838c2ecf20Sopenharmony_ci
848c2ecf20Sopenharmony_ci	cmd.tf_flags |= IDE_TFLAG_CUSTOM_HANDLER;
858c2ecf20Sopenharmony_ci	cmd.protocol = ATA_PROT_NODATA;
868c2ecf20Sopenharmony_ci
878c2ecf20Sopenharmony_ci	cmd.rq = rq;
888c2ecf20Sopenharmony_ci
898c2ecf20Sopenharmony_ci	return do_rw_taskfile(drive, &cmd);
908c2ecf20Sopenharmony_ci}
918c2ecf20Sopenharmony_ci
928c2ecf20Sopenharmony_cissize_t ide_park_show(struct device *dev, struct device_attribute *attr,
938c2ecf20Sopenharmony_ci		      char *buf)
948c2ecf20Sopenharmony_ci{
958c2ecf20Sopenharmony_ci	ide_drive_t *drive = to_ide_device(dev);
968c2ecf20Sopenharmony_ci	ide_hwif_t *hwif = drive->hwif;
978c2ecf20Sopenharmony_ci	unsigned long now;
988c2ecf20Sopenharmony_ci	unsigned int msecs;
998c2ecf20Sopenharmony_ci
1008c2ecf20Sopenharmony_ci	if (drive->dev_flags & IDE_DFLAG_NO_UNLOAD)
1018c2ecf20Sopenharmony_ci		return -EOPNOTSUPP;
1028c2ecf20Sopenharmony_ci
1038c2ecf20Sopenharmony_ci	spin_lock_irq(&hwif->lock);
1048c2ecf20Sopenharmony_ci	now = jiffies;
1058c2ecf20Sopenharmony_ci	if (drive->dev_flags & IDE_DFLAG_PARKED &&
1068c2ecf20Sopenharmony_ci	    time_after(drive->sleep, now))
1078c2ecf20Sopenharmony_ci		msecs = jiffies_to_msecs(drive->sleep - now);
1088c2ecf20Sopenharmony_ci	else
1098c2ecf20Sopenharmony_ci		msecs = 0;
1108c2ecf20Sopenharmony_ci	spin_unlock_irq(&hwif->lock);
1118c2ecf20Sopenharmony_ci
1128c2ecf20Sopenharmony_ci	return snprintf(buf, 20, "%u\n", msecs);
1138c2ecf20Sopenharmony_ci}
1148c2ecf20Sopenharmony_ci
1158c2ecf20Sopenharmony_cissize_t ide_park_store(struct device *dev, struct device_attribute *attr,
1168c2ecf20Sopenharmony_ci		       const char *buf, size_t len)
1178c2ecf20Sopenharmony_ci{
1188c2ecf20Sopenharmony_ci#define MAX_PARK_TIMEOUT 30000
1198c2ecf20Sopenharmony_ci	ide_drive_t *drive = to_ide_device(dev);
1208c2ecf20Sopenharmony_ci	long int input;
1218c2ecf20Sopenharmony_ci	int rc;
1228c2ecf20Sopenharmony_ci
1238c2ecf20Sopenharmony_ci	rc = kstrtol(buf, 10, &input);
1248c2ecf20Sopenharmony_ci	if (rc)
1258c2ecf20Sopenharmony_ci		return rc;
1268c2ecf20Sopenharmony_ci	if (input < -2)
1278c2ecf20Sopenharmony_ci		return -EINVAL;
1288c2ecf20Sopenharmony_ci	if (input > MAX_PARK_TIMEOUT) {
1298c2ecf20Sopenharmony_ci		input = MAX_PARK_TIMEOUT;
1308c2ecf20Sopenharmony_ci		rc = -EOVERFLOW;
1318c2ecf20Sopenharmony_ci	}
1328c2ecf20Sopenharmony_ci
1338c2ecf20Sopenharmony_ci	mutex_lock(&ide_setting_mtx);
1348c2ecf20Sopenharmony_ci	if (input >= 0) {
1358c2ecf20Sopenharmony_ci		if (drive->dev_flags & IDE_DFLAG_NO_UNLOAD)
1368c2ecf20Sopenharmony_ci			rc = -EOPNOTSUPP;
1378c2ecf20Sopenharmony_ci		else if (input || drive->dev_flags & IDE_DFLAG_PARKED)
1388c2ecf20Sopenharmony_ci			issue_park_cmd(drive, msecs_to_jiffies(input));
1398c2ecf20Sopenharmony_ci	} else {
1408c2ecf20Sopenharmony_ci		if (drive->media == ide_disk)
1418c2ecf20Sopenharmony_ci			switch (input) {
1428c2ecf20Sopenharmony_ci			case -1:
1438c2ecf20Sopenharmony_ci				drive->dev_flags &= ~IDE_DFLAG_NO_UNLOAD;
1448c2ecf20Sopenharmony_ci				break;
1458c2ecf20Sopenharmony_ci			case -2:
1468c2ecf20Sopenharmony_ci				drive->dev_flags |= IDE_DFLAG_NO_UNLOAD;
1478c2ecf20Sopenharmony_ci				break;
1488c2ecf20Sopenharmony_ci			}
1498c2ecf20Sopenharmony_ci		else
1508c2ecf20Sopenharmony_ci			rc = -EOPNOTSUPP;
1518c2ecf20Sopenharmony_ci	}
1528c2ecf20Sopenharmony_ci	mutex_unlock(&ide_setting_mtx);
1538c2ecf20Sopenharmony_ci
1548c2ecf20Sopenharmony_ci	return rc ? rc : len;
1558c2ecf20Sopenharmony_ci}
156