// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Support for SATA devices on Serial Attached SCSI (SAS) controllers
*
* Copyright (C) 2006 IBM Corporation
*
* Written by: Darrick J. Wong <djwong@us.ibm.com>, IBM Corporation
*/
#include <linux/scatterlist.h>
#include <linux/slab.h>
#include <linux/async.h>
#include <linux/export.h>
#include <scsi/sas_ata.h>
#include "sas_internal.h"
#include <scsi/scsi_host.h>
#include <scsi/scsi_device.h>
#include <scsi/scsi_tcq.h>
#include <scsi/scsi.h>
#include <scsi/scsi_transport.h>
#include <scsi/scsi_transport_sas.h>
#include "scsi_sas_internal.h"
#include "scsi_transport_api.h"
#include <scsi/scsi_eh.h>
static enum ata_completion_errors sas_to_ata_err(struct task_status_struct *ts)
{
/* Cheesy attempt to translate SAS errors into ATA. Hah! */
/* transport error */
if (ts->resp == SAS_TASK_UNDELIVERED)
return AC_ERR_ATA_BUS;
/* ts->resp == SAS_TASK_COMPLETE */
/* task delivered, what happened afterwards? */
switch (ts->stat) {
case SAS_DEV_NO_RESPONSE:
return AC_ERR_TIMEOUT;
case SAS_INTERRUPTED:
case SAS_PHY_DOWN:
case SAS_NAK_R_ERR:
return AC_ERR_ATA_BUS;
case SAS_DATA_UNDERRUN:
/*
* Some programs that use the taskfile interface
* (smartctl in particular) can cause underrun
* problems. Ignore these errors, perhaps at our
* peril.
*/
return 0;
case SAS_DATA_OVERRUN:
case SAS_QUEUE_FULL:
case SAS_DEVICE_UNKNOWN:
case SAS_OPEN_TO:
case SAS_OPEN_REJECT:
pr_warn("%s: Saw error %d. What to do?\n",
__func__, ts->stat);
return AC_ERR_OTHER;
case SAM_STAT_CHECK_CONDITION:
case SAS_ABORTED_TASK:
return AC_ERR_DEV;
case SAS_PROTO_RESPONSE:
/* This means the ending_fis has the error
* value; return 0 here to collect it
*/
return 0;
default:
return 0;
}
}
static void sas_ata_task_done(struct sas_task *task)
{
struct ata_queued_cmd *qc = task->uldd_task;
struct domain_device *dev = task->dev;
struct task_status_struct *stat = &task->task_status;
struct ata_task_resp *resp = (struct ata_task_resp *)stat->buf;
struct sas_ha_struct *sas_ha = dev->port->ha;
enum ata_completion_errors ac;
unsigned long flags;
struct ata_link *link;
struct ata_port *ap;
spin_lock_irqsave(&dev->done_lock, flags);
if (test_bit(SAS_HA_FROZEN, &sas_ha->state))
task = NULL;
else if (qc && qc->scsicmd)
ASSIGN_SAS_TASK(qc->scsicmd, NULL);
spin_unlock_irqrestore(&dev->done_lock, flags);
/* check if libsas-eh got to the task before us */
if (unlikely(!task))
return;
if (!qc)
goto qc_already_gone;
ap = qc->ap;
link = &ap->link;
spin_lock_irqsave(ap->lock, flags);
/* check if we lost the race with libata/sas_ata_post_internal() */
if (unlikely(ata_port_is_frozen(ap))) {
spin_unlock_irqrestore(ap->lock, flags);
if (qc->scsicmd)
goto qc_already_gone;
else {
/* if eh is not involved and the port is frozen then the
* ata internal abort process has taken responsibility
* for this sas_task
*/
return;
}
}
if (stat->stat == SAS_PROTO_RESPONSE ||
stat->stat == SAS_SAM_STAT_GOOD ||
(stat->stat == SAS_SAM_STAT_CHECK_CONDITION &&
dev->sata_dev.class == ATA_DEV_ATAPI)) {
memcpy(dev->sata_dev.fis, resp->ending_fis, ATA_RESP_FIS_SIZE);
if (!link->sactive) {
qc->err_mask |= ac_err_mask(dev->sata_dev.fis[2]);
} else {
link->eh_info.err_mask |= ac_err_mask(dev->sata_dev.fis[2]);
if (unlikely(link->eh_info.err_mask))
qc->flags |= ATA_QCFLAG_EH;
}
} else {
ac = sas_to_ata_err(stat);
if (ac) {
pr_warn("%s: SAS error 0x%x\n", __func__, stat->stat);
/* We saw a SAS error. Send a vague error. */
if (!link->sactive) {
qc->err_mask = ac;
} else {
link->eh_info.err_mask |= AC_ERR_DEV;
qc->flags |= ATA_QCFLAG_EH;
}
dev->sata_dev.fis[2] = ATA_ERR | ATA_DRDY; /* tf status */
dev->sata_dev.fis[3] = ATA_ABORTED; /* tf error */
}
}
qc->lldd_task = NULL;
ata_qc_complete(qc);
spin_unlock_irqrestore(ap->lock, flags);
qc_already_gone:
sas_free_task(task);
}
static unsigned int sas_ata_qc_issue(struct ata_queued_cmd *qc)
__must_hold(ap->lock)
{
struct sas_task *task;
struct scatterlist *sg;
int ret = AC_ERR_SYSTEM;
unsigned int si, xfer = 0;
struct ata_port *ap = qc->ap;
struct domain_device *dev = ap->private_data;
struct sas_ha_struct *sas_ha = dev->port->ha;
struct Scsi_Host *host = sas_ha->shost;
struct sas_internal *i = to_sas_internal(host->transportt);
/* TODO: we should try to remove that unlock */
spin_unlock(ap->lock);
/* If the device fell off, no sense in issuing commands */
if