Commit 0c76106c authored by Damien Le Moal's avatar Damien Le Moal Committed by Martin K. Petersen
Browse files

scsi: sd: Fix TCG OPAL unlock on system resume

Commit 3cc2ffe5 ("scsi: sd: Differentiate system and runtime start/stop
management") introduced the manage_system_start_stop scsi_device flag to
allow libata to indicate to the SCSI disk driver that nothing should be
done when resuming a disk on system resume. This change turned the
execution of sd_resume() into a no-op for ATA devices on system
resume. While this solved deadlock issues during device resume, this change
also wrongly removed the execution of opal_unlock_from_suspend().  As a
result, devices with TCG OPAL locking enabled remain locked and
inaccessible after a system resume from sleep.

To fix this issue, introduce the SCSI driver resume method and implement it
with the sd_resume() function calling opal_unlock_from_suspend(). The
former sd_resume() function is renamed to sd_resume_common() and modified
to call the new sd_resume() function. For non-ATA devices, this result in
no functional changes.

In order for libata to explicitly execute sd_resume() when a device is
resumed during system restart, the function scsi_resume_device() is
introduced. libata calls this function from the revalidation work executed
on devie resume, a state that is indicated with the new device flag
ATA_DFLAG_RESUMING. Doing so, locked TCG OPAL enabled devices are unlocked
on resume, allowing normal operation.

Fixes: 3cc2ffe5 ("scsi: sd: Differentiate system and runtime start/stop management")
Link: https://bugzilla.kernel.org/show_bug.cgi?id=218538


Cc: stable@vger.kernel.org
Signed-off-by: default avatarDamien Le Moal <dlemoal@kernel.org>
Link: https://lore.kernel.org/r/20240319071209.1179257-1-dlemoal@kernel.org


Signed-off-by: default avatarMartin K. Petersen <martin.petersen@oracle.com>
parent 27f58c04
Loading
Loading
Loading
Loading
+4 −1
Original line number Diff line number Diff line
@@ -712,10 +712,12 @@ void ata_scsi_port_error_handler(struct Scsi_Host *host, struct ata_port *ap)
				ehc->saved_ncq_enabled |= 1 << devno;

			/* If we are resuming, wake up the device */
			if (ap->pflags & ATA_PFLAG_RESUMING)
			if (ap->pflags & ATA_PFLAG_RESUMING) {
				dev->flags |= ATA_DFLAG_RESUMING;
				ehc->i.dev_action[devno] |= ATA_EH_SET_ACTIVE;
			}
		}
	}

	ap->pflags |= ATA_PFLAG_EH_IN_PROGRESS;
	ap->pflags &= ~ATA_PFLAG_EH_PENDING;
@@ -3169,6 +3171,7 @@ static int ata_eh_revalidate_and_attach(struct ata_link *link,
	return 0;

 err:
	dev->flags &= ~ATA_DFLAG_RESUMING;
	*r_failed_dev = dev;
	return rc;
}
+9 −0
Original line number Diff line number Diff line
@@ -4730,6 +4730,7 @@ void ata_scsi_dev_rescan(struct work_struct *work)
	struct ata_link *link;
	struct ata_device *dev;
	unsigned long flags;
	bool do_resume;
	int ret = 0;

	mutex_lock(&ap->scsi_scan_mutex);
@@ -4751,7 +4752,15 @@ void ata_scsi_dev_rescan(struct work_struct *work)
			if (scsi_device_get(sdev))
				continue;

			do_resume = dev->flags & ATA_DFLAG_RESUMING;

			spin_unlock_irqrestore(ap->lock, flags);
			if (do_resume) {
				ret = scsi_resume_device(sdev);
				if (ret == -EWOULDBLOCK)
					goto unlock;
				dev->flags &= ~ATA_DFLAG_RESUMING;
			}
			ret = scsi_rescan_device(sdev);
			scsi_device_put(sdev);
			spin_lock_irqsave(ap->lock, flags);
+34 −0
Original line number Diff line number Diff line
@@ -1642,6 +1642,40 @@ int scsi_add_device(struct Scsi_Host *host, uint channel,
}
EXPORT_SYMBOL(scsi_add_device);

int scsi_resume_device(struct scsi_device *sdev)
{
	struct device *dev = &sdev->sdev_gendev;
	int ret = 0;

	device_lock(dev);

	/*
	 * Bail out if the device or its queue are not running. Otherwise,
	 * the rescan may block waiting for commands to be executed, with us
	 * holding the device lock. This can result in a potential deadlock
	 * in the power management core code when system resume is on-going.
	 */
	if (sdev->sdev_state != SDEV_RUNNING ||
	    blk_queue_pm_only(sdev->request_queue)) {
		ret = -EWOULDBLOCK;
		goto unlock;
	}

	if (dev->driver && try_module_get(dev->driver->owner)) {
		struct scsi_driver *drv = to_scsi_driver(dev->driver);

		if (drv->resume)
			ret = drv->resume(dev);
		module_put(dev->driver->owner);
	}

unlock:
	device_unlock(dev);

	return ret;
}
EXPORT_SYMBOL(scsi_resume_device);

int scsi_rescan_device(struct scsi_device *sdev)
{
	struct device *dev = &sdev->sdev_gendev;
+19 −4
Original line number Diff line number Diff line
@@ -4108,7 +4108,21 @@ static int sd_suspend_runtime(struct device *dev)
	return sd_suspend_common(dev, true);
}

static int sd_resume(struct device *dev, bool runtime)
static int sd_resume(struct device *dev)
{
	struct scsi_disk *sdkp = dev_get_drvdata(dev);

	sd_printk(KERN_NOTICE, sdkp, "Starting disk\n");

	if (opal_unlock_from_suspend(sdkp->opal_dev)) {
		sd_printk(KERN_NOTICE, sdkp, "OPAL unlock failed\n");
		return -EIO;
	}

	return 0;
}

static int sd_resume_common(struct device *dev, bool runtime)
{
	struct scsi_disk *sdkp = dev_get_drvdata(dev);
	int ret;
@@ -4124,7 +4138,7 @@ static int sd_resume(struct device *dev, bool runtime)
	sd_printk(KERN_NOTICE, sdkp, "Starting disk\n");
	ret = sd_start_stop_device(sdkp, 1);
	if (!ret) {
		opal_unlock_from_suspend(sdkp->opal_dev);
		sd_resume(dev);
		sdkp->suspended = false;
	}

@@ -4143,7 +4157,7 @@ static int sd_resume_system(struct device *dev)
		return 0;
	}

	return sd_resume(dev, false);
	return sd_resume_common(dev, false);
}

static int sd_resume_runtime(struct device *dev)
@@ -4170,7 +4184,7 @@ static int sd_resume_runtime(struct device *dev)
				  "Failed to clear sense data\n");
	}

	return sd_resume(dev, true);
	return sd_resume_common(dev, true);
}

static const struct dev_pm_ops sd_pm_ops = {
@@ -4193,6 +4207,7 @@ static struct scsi_driver sd_template = {
		.pm		= &sd_pm_ops,
	},
	.rescan			= sd_rescan,
	.resume			= sd_resume,
	.init_command		= sd_init_command,
	.uninit_command		= sd_uninit_command,
	.done			= sd_done,
+1 −0
Original line number Diff line number Diff line
@@ -107,6 +107,7 @@ enum {

	ATA_DFLAG_NCQ_PRIO_ENABLED = (1 << 20), /* Priority cmds sent to dev */
	ATA_DFLAG_CDL_ENABLED	= (1 << 21), /* cmd duration limits is enabled */
	ATA_DFLAG_RESUMING	= (1 << 22),  /* Device is resuming */
	ATA_DFLAG_DETACH	= (1 << 24),
	ATA_DFLAG_DETACHED	= (1 << 25),
	ATA_DFLAG_DA		= (1 << 26), /* device supports Device Attention */
Loading