summaryrefslogtreecommitdiff
path: root/drivers/ata/libata-core.c
diff options
context:
space:
mode:
authorNiklas Cassel <niklas.cassel@wdc.com>2023-05-11 03:13:52 +0200
committerMartin K. Petersen <martin.petersen@oracle.com>2023-05-22 17:05:20 -0400
commit18bd7718b5c489b3161b6c2ab4685d57c1e2da3b (patch)
treee610d9db43c4dd75e14f72d12ff5262fbef323df /drivers/ata/libata-core.c
parenteafe804bda7ba01da562c43351068b8a76a579af (diff)
scsi: ata: libata: Handle completion of CDL commands using policy 0xD
A CDL timeout for policy 0xF is defined as a NCQ error, just with a CDL specific sk/asc/ascq in the sense data. Therefore, the existing code in libata does not need to be modified to handle a policy 0xF CDL timeout. For Command Duration Limits policy 0xD: The device shall complete the command without error with the additional sense code set to DATA CURRENTLY UNAVAILABLE. Since a CDL timeout for policy 0xD is not an error, we cannot use the NCQ Command Error log (10h). Instead, we need to read the Sense Data for Successful NCQ Commands log (0Fh). In the success case, just like in the error case, we cannot simply read a log page from the interrupt handler itself, since reading a log page involves sending a READ LOG DMA EXT or READ LOG EXT command. Therefore, we add a new EH action ATA_EH_GET_SUCCESS_SENSE. When a command completes without error, and when the ATA_SENSE bit is set, this new action is set as pending, and EH is scheduled. This way, similar to the NCQ error case, the log page will be read from EH context. An alternative would have been to add a new kthread or workqueue to handle this. However, extending EH can be done with minimal changes and avoids the need to synchronize a new kthread/workqueue with EH. Co-developed-by: Damien Le Moal <dlemoal@kernel.org> Signed-off-by: Damien Le Moal <dlemoal@kernel.org> Reviewed-by: Hannes Reinecke <hare@suse.de> Reviewed-by: Christoph Hellwig <hch@lst.de> Signed-off-by: Niklas Cassel <niklas.cassel@wdc.com> Link: https://lore.kernel.org/r/20230511011356.227789-20-nks@flawful.org Signed-off-by: Martin K. Petersen <martin.petersen@oracle.com>
Diffstat (limited to 'drivers/ata/libata-core.c')
-rw-r--r--drivers/ata/libata-core.c88
1 files changed, 86 insertions, 2 deletions
diff --git a/drivers/ata/libata-core.c b/drivers/ata/libata-core.c
index e63773740fc2..ddc3d9f8fa02 100644
--- a/drivers/ata/libata-core.c
+++ b/drivers/ata/libata-core.c
@@ -677,8 +677,12 @@ static inline void ata_set_tf_cdl(struct ata_queued_cmd *qc, int cdl)
else
tf->feature |= cdl;
- /* Mark this command as having a CDL */
- qc->flags |= ATA_QCFLAG_HAS_CDL;
+ /*
+ * Mark this command as having a CDL and request the result
+ * task file so that we can inspect the sense data available
+ * bit on completion.
+ */
+ qc->flags |= ATA_QCFLAG_HAS_CDL | ATA_QCFLAG_RESULT_TF;
}
/**
@@ -2425,6 +2429,24 @@ static void ata_dev_config_cdl(struct ata_device *dev)
"Command duration guideline is not supported\n");
/*
+ * We must have support for the sense data for successful NCQ commands
+ * log indicated by the successful NCQ command sense data supported bit.
+ */
+ val = get_unaligned_le64(&ap->sector_buf[8]);
+ if (!(val & BIT_ULL(63)) || !(val & BIT_ULL(47))) {
+ ata_dev_warn(dev,
+ "CDL supported but Successful NCQ Command Sense Data is not supported\n");
+ goto not_supported;
+ }
+
+ /* Without NCQ autosense, the successful NCQ commands log is useless. */
+ if (!ata_id_has_ncq_autosense(dev->id)) {
+ ata_dev_warn(dev,
+ "CDL supported but NCQ autosense is not supported\n");
+ goto not_supported;
+ }
+
+ /*
* If CDL is marked as enabled, make sure the feature is enabled too.
* Conversely, if CDL is disabled, make sure the feature is turned off.
*/
@@ -2459,6 +2481,35 @@ static void ata_dev_config_cdl(struct ata_device *dev)
}
/*
+ * While CDL itself has to be enabled using sysfs, CDL requires that
+ * sense data for successful NCQ commands is enabled to work properly.
+ * Just like ata_dev_config_sense_reporting(), enable it unconditionally
+ * if supported.
+ */
+ if (!(val & BIT_ULL(63)) || !(val & BIT_ULL(18))) {
+ err_mask = ata_dev_set_feature(dev,
+ SETFEATURE_SENSE_DATA_SUCC_NCQ, 0x1);
+ if (err_mask) {
+ ata_dev_warn(dev,
+ "failed to enable Sense Data for successful NCQ commands, Emask 0x%x\n",
+ err_mask);
+ goto not_supported;
+ }
+ }
+
+ /*
+ * Allocate a buffer to handle reading the sense data for successful
+ * NCQ Commands log page for commands using a CDL with one of the limit
+ * policy set to 0xD (successful completion with sense data available
+ * bit set).
+ */
+ if (!ap->ncq_sense_buf) {
+ ap->ncq_sense_buf = kmalloc(ATA_LOG_SENSE_NCQ_SIZE, GFP_KERNEL);
+ if (!ap->ncq_sense_buf)
+ goto not_supported;
+ }
+
+ /*
* Command duration limits is supported: cache the CDL log page 18h
* (command duration descriptors).
*/
@@ -2475,6 +2526,8 @@ static void ata_dev_config_cdl(struct ata_device *dev)
not_supported:
dev->flags &= ~(ATA_DFLAG_CDL | ATA_DFLAG_CDL_ENABLED);
+ kfree(ap->ncq_sense_buf);
+ ap->ncq_sense_buf = NULL;
}
static int ata_dev_config_lba(struct ata_device *dev)
@@ -4878,6 +4931,36 @@ void ata_qc_complete(struct ata_queued_cmd *qc)
fill_result_tf(qc);
trace_ata_qc_complete_done(qc);
+
+ /*
+ * For CDL commands that completed without an error, check if
+ * we have sense data (ATA_SENSE is set). If we do, then the
+ * command may have been aborted by the device due to a limit
+ * timeout using the policy 0xD. For these commands, invoke EH
+ * to get the command sense data.
+ */
+ if (qc->result_tf.status & ATA_SENSE &&
+ ((ata_is_ncq(qc->tf.protocol) &&
+ dev->flags & ATA_DFLAG_CDL_ENABLED) ||
+ (!(ata_is_ncq(qc->tf.protocol) &&
+ ata_id_sense_reporting_enabled(dev->id))))) {
+ /*
+ * Tell SCSI EH to not overwrite scmd->result even if
+ * this command is finished with result SAM_STAT_GOOD.
+ */
+ qc->scsicmd->flags |= SCMD_FORCE_EH_SUCCESS;
+ qc->flags |= ATA_QCFLAG_EH_SUCCESS_CMD;
+ ehi->dev_action[dev->devno] |= ATA_EH_GET_SUCCESS_SENSE;
+
+ /*
+ * set pending so that ata_qc_schedule_eh() does not
+ * trigger fast drain, and freeze the port.
+ */
+ ap->pflags |= ATA_PFLAG_EH_PENDING;
+ ata_qc_schedule_eh(qc);
+ return;
+ }
+
/* Some commands need post-processing after successful
* completion.
*/
@@ -5510,6 +5593,7 @@ static void ata_host_release(struct kref *kref)
kfree(ap->pmp_link);
kfree(ap->slave_link);
+ kfree(ap->ncq_sense_buf);
kfree(ap);
host->ports[i] = NULL;
}