mirror of
https://github.com/AetherDroid/android_kernel_samsung_on5xelte.git
synced 2025-09-09 01:28:05 -04:00
Fixed MTP to work with TWRP
This commit is contained in:
commit
f6dfaef42e
50820 changed files with 20846062 additions and 0 deletions
48
drivers/scsi/libsas/Kconfig
Normal file
48
drivers/scsi/libsas/Kconfig
Normal file
|
@ -0,0 +1,48 @@
|
|||
#
|
||||
# Kernel configuration file for the SAS Class
|
||||
#
|
||||
# Copyright (C) 2005 Adaptec, Inc. All rights reserved.
|
||||
# Copyright (C) 2005 Luben Tuikov <luben_tuikov@adaptec.com>
|
||||
#
|
||||
# This file is licensed under GPLv2.
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License as
|
||||
# published by the Free Software Foundation; version 2 of the
|
||||
# License.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
# General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
|
||||
# USA
|
||||
#
|
||||
|
||||
config SCSI_SAS_LIBSAS
|
||||
tristate "SAS Domain Transport Attributes"
|
||||
depends on SCSI
|
||||
select SCSI_SAS_ATTRS
|
||||
help
|
||||
This provides transport specific helpers for SAS drivers which
|
||||
use the domain device construct (like the aic94xxx).
|
||||
|
||||
config SCSI_SAS_ATA
|
||||
bool "ATA support for libsas (requires libata)"
|
||||
depends on SCSI_SAS_LIBSAS
|
||||
depends on ATA = y || ATA = SCSI_SAS_LIBSAS
|
||||
help
|
||||
Builds in ATA support into libsas. Will necessitate
|
||||
the loading of libata along with libsas.
|
||||
|
||||
config SCSI_SAS_HOST_SMP
|
||||
bool "Support for SMP interpretation for SAS hosts"
|
||||
default y
|
||||
depends on SCSI_SAS_LIBSAS
|
||||
help
|
||||
Allows sas hosts to receive SMP frames. Selecting this
|
||||
option builds an SMP interpreter into libsas. Say
|
||||
N here if you want to save the few kb this consumes.
|
35
drivers/scsi/libsas/Makefile
Normal file
35
drivers/scsi/libsas/Makefile
Normal file
|
@ -0,0 +1,35 @@
|
|||
#
|
||||
# Kernel Makefile for the libsas helpers
|
||||
#
|
||||
# Copyright (C) 2005 Adaptec, Inc. All rights reserved.
|
||||
# Copyright (C) 2005 Luben Tuikov <luben_tuikov@adaptec.com>
|
||||
#
|
||||
# This file is licensed under GPLv2.
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License as
|
||||
# published by the Free Software Foundation; version 2 of the
|
||||
# License.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
# General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
|
||||
# USA
|
||||
|
||||
obj-$(CONFIG_SCSI_SAS_LIBSAS) += libsas.o
|
||||
libsas-y += sas_init.o \
|
||||
sas_phy.o \
|
||||
sas_port.o \
|
||||
sas_event.o \
|
||||
sas_dump.o \
|
||||
sas_discover.o \
|
||||
sas_expander.o \
|
||||
sas_scsi_host.o \
|
||||
sas_task.o
|
||||
libsas-$(CONFIG_SCSI_SAS_ATA) += sas_ata.o
|
||||
libsas-$(CONFIG_SCSI_SAS_HOST_SMP) += sas_host_smp.o
|
915
drivers/scsi/libsas/sas_ata.c
Normal file
915
drivers/scsi/libsas/sas_ata.c
Normal file
|
@ -0,0 +1,915 @@
|
|||
/*
|
||||
* 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
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation; either version 2 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
|
||||
* USA
|
||||
*/
|
||||
|
||||
#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_SG_ERR:
|
||||
return AC_ERR_INVALID;
|
||||
|
||||
case SAS_OPEN_TO:
|
||||
case SAS_OPEN_REJECT:
|
||||
SAS_DPRINTK("%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(ap->pflags & ATA_PFLAG_FROZEN)) {
|
||||
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 == SAM_STAT_GOOD ||
|
||||
((stat->stat == SAM_STAT_CHECK_CONDITION &&
|
||||
dev->sata_dev.command_set == ATAPI_COMMAND_SET))) {
|
||||
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_FAILED;
|
||||
}
|
||||
} else {
|
||||
ac = sas_to_ata_err(stat);
|
||||
if (ac) {
|
||||
SAS_DPRINTK("%s: SAS error %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_FAILED;
|
||||
}
|
||||
|
||||
dev->sata_dev.fis[3] = 0x04; /* status err */
|
||||
dev->sata_dev.fis[2] = ATA_ERR;
|
||||
}
|
||||
}
|
||||
|
||||
qc->lldd_task = NULL;
|
||||
ata_qc_complete(qc);
|
||||
spin_unlock_irqrestore(ap->lock, flags);
|
||||
|
||||
qc_already_gone:
|
||||
list_del_init(&task->list);
|
||||
sas_free_task(task);
|
||||
}
|
||||
|
||||
static unsigned int sas_ata_qc_issue(struct ata_queued_cmd *qc)
|
||||
{
|
||||
unsigned long flags;
|
||||
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->core.shost;
|
||||
struct sas_internal *i = to_sas_internal(host->transportt);
|
||||
|
||||
/* TODO: audit callers to ensure they are ready for qc_issue to
|
||||
* unconditionally re-enable interrupts
|
||||
*/
|
||||
local_irq_save(flags);
|
||||
spin_unlock(ap->lock);
|
||||
|
||||
/* If the device fell off, no sense in issuing commands */
|
||||
if (test_bit(SAS_DEV_GONE, &dev->state))
|
||||
goto out;
|
||||
|
||||
task = sas_alloc_task(GFP_ATOMIC);
|
||||
if (!task)
|
||||
goto out;
|
||||
task->dev = dev;
|
||||
task->task_proto = SAS_PROTOCOL_STP;
|
||||
task->task_done = sas_ata_task_done;
|
||||
|
||||
if (qc->tf.command == ATA_CMD_FPDMA_WRITE ||
|
||||
qc->tf.command == ATA_CMD_FPDMA_READ) {
|
||||
/* Need to zero out the tag libata assigned us */
|
||||
qc->tf.nsect = 0;
|
||||
}
|
||||
|
||||
ata_tf_to_fis(&qc->tf, qc->dev->link->pmp, 1, (u8 *)&task->ata_task.fis);
|
||||
task->uldd_task = qc;
|
||||
if (ata_is_atapi(qc->tf.protocol)) {
|
||||
memcpy(task->ata_task.atapi_packet, qc->cdb, qc->dev->cdb_len);
|
||||
task->total_xfer_len = qc->nbytes;
|
||||
task->num_scatter = qc->n_elem;
|
||||
} else {
|
||||
for_each_sg(qc->sg, sg, qc->n_elem, si)
|
||||
xfer += sg->length;
|
||||
|
||||
task->total_xfer_len = xfer;
|
||||
task->num_scatter = si;
|
||||
}
|
||||
|
||||
task->data_dir = qc->dma_dir;
|
||||
task->scatter = qc->sg;
|
||||
task->ata_task.retry_count = 1;
|
||||
task->task_state_flags = SAS_TASK_STATE_PENDING;
|
||||
qc->lldd_task = task;
|
||||
|
||||
switch (qc->tf.protocol) {
|
||||
case ATA_PROT_NCQ:
|
||||
task->ata_task.use_ncq = 1;
|
||||
/* fall through */
|
||||
case ATAPI_PROT_DMA:
|
||||
case ATA_PROT_DMA:
|
||||
task->ata_task.dma_xfer = 1;
|
||||
break;
|
||||
}
|
||||
|
||||
if (qc->scsicmd)
|
||||
ASSIGN_SAS_TASK(qc->scsicmd, task);
|
||||
|
||||
if (sas_ha->lldd_max_execute_num < 2)
|
||||
ret = i->dft->lldd_execute_task(task, 1, GFP_ATOMIC);
|
||||
else
|
||||
ret = sas_queue_up(task);
|
||||
|
||||
/* Examine */
|
||||
if (ret) {
|
||||
SAS_DPRINTK("lldd_execute_task returned: %d\n", ret);
|
||||
|
||||
if (qc->scsicmd)
|
||||
ASSIGN_SAS_TASK(qc->scsicmd, NULL);
|
||||
sas_free_task(task);
|
||||
ret = AC_ERR_SYSTEM;
|
||||
}
|
||||
|
||||
out:
|
||||
spin_lock(ap->lock);
|
||||
local_irq_restore(flags);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static bool sas_ata_qc_fill_rtf(struct ata_queued_cmd *qc)
|
||||
{
|
||||
struct domain_device *dev = qc->ap->private_data;
|
||||
|
||||
ata_tf_from_fis(dev->sata_dev.fis, &qc->result_tf);
|
||||
return true;
|
||||
}
|
||||
|
||||
static struct sas_internal *dev_to_sas_internal(struct domain_device *dev)
|
||||
{
|
||||
return to_sas_internal(dev->port->ha->core.shost->transportt);
|
||||
}
|
||||
|
||||
static void sas_get_ata_command_set(struct domain_device *dev);
|
||||
|
||||
int sas_get_ata_info(struct domain_device *dev, struct ex_phy *phy)
|
||||
{
|
||||
if (phy->attached_tproto & SAS_PROTOCOL_STP)
|
||||
dev->tproto = phy->attached_tproto;
|
||||
if (phy->attached_sata_dev)
|
||||
dev->tproto |= SAS_SATA_DEV;
|
||||
|
||||
if (phy->attached_dev_type == SAS_SATA_PENDING)
|
||||
dev->dev_type = SAS_SATA_PENDING;
|
||||
else {
|
||||
int res;
|
||||
|
||||
dev->dev_type = SAS_SATA_DEV;
|
||||
res = sas_get_report_phy_sata(dev->parent, phy->phy_id,
|
||||
&dev->sata_dev.rps_resp);
|
||||
if (res) {
|
||||
SAS_DPRINTK("report phy sata to %016llx:0x%x returned "
|
||||
"0x%x\n", SAS_ADDR(dev->parent->sas_addr),
|
||||
phy->phy_id, res);
|
||||
return res;
|
||||
}
|
||||
memcpy(dev->frame_rcvd, &dev->sata_dev.rps_resp.rps.fis,
|
||||
sizeof(struct dev_to_host_fis));
|
||||
/* TODO switch to ata_dev_classify() */
|
||||
sas_get_ata_command_set(dev);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int sas_ata_clear_pending(struct domain_device *dev, struct ex_phy *phy)
|
||||
{
|
||||
int res;
|
||||
|
||||
/* we weren't pending, so successfully end the reset sequence now */
|
||||
if (dev->dev_type != SAS_SATA_PENDING)
|
||||
return 1;
|
||||
|
||||
/* hmmm, if this succeeds do we need to repost the domain_device to the
|
||||
* lldd so it can pick up new parameters?
|
||||
*/
|
||||
res = sas_get_ata_info(dev, phy);
|
||||
if (res)
|
||||
return 0; /* retry */
|
||||
else
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int smp_ata_check_ready(struct ata_link *link)
|
||||
{
|
||||
int res;
|
||||
struct ata_port *ap = link->ap;
|
||||
struct domain_device *dev = ap->private_data;
|
||||
struct domain_device *ex_dev = dev->parent;
|
||||
struct sas_phy *phy = sas_get_local_phy(dev);
|
||||
struct ex_phy *ex_phy = &ex_dev->ex_dev.ex_phy[phy->number];
|
||||
|
||||
res = sas_ex_phy_discover(ex_dev, phy->number);
|
||||
sas_put_local_phy(phy);
|
||||
|
||||
/* break the wait early if the expander is unreachable,
|
||||
* otherwise keep polling
|
||||
*/
|
||||
if (res == -ECOMM)
|
||||
return res;
|
||||
if (res != SMP_RESP_FUNC_ACC)
|
||||
return 0;
|
||||
|
||||
switch (ex_phy->attached_dev_type) {
|
||||
case SAS_SATA_PENDING:
|
||||
return 0;
|
||||
case SAS_END_DEVICE:
|
||||
if (ex_phy->attached_sata_dev)
|
||||
return sas_ata_clear_pending(dev, ex_phy);
|
||||
default:
|
||||
return -ENODEV;
|
||||
}
|
||||
}
|
||||
|
||||
static int local_ata_check_ready(struct ata_link *link)
|
||||
{
|
||||
struct ata_port *ap = link->ap;
|
||||
struct domain_device *dev = ap->private_data;
|
||||
struct sas_internal *i = dev_to_sas_internal(dev);
|
||||
|
||||
if (i->dft->lldd_ata_check_ready)
|
||||
return i->dft->lldd_ata_check_ready(dev);
|
||||
else {
|
||||
/* lldd's that don't implement 'ready' checking get the
|
||||
* old default behavior of not coordinating reset
|
||||
* recovery with libata
|
||||
*/
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
static int sas_ata_printk(const char *level, const struct domain_device *ddev,
|
||||
const char *fmt, ...)
|
||||
{
|
||||
struct ata_port *ap = ddev->sata_dev.ap;
|
||||
struct device *dev = &ddev->rphy->dev;
|
||||
struct va_format vaf;
|
||||
va_list args;
|
||||
int r;
|
||||
|
||||
va_start(args, fmt);
|
||||
|
||||
vaf.fmt = fmt;
|
||||
vaf.va = &args;
|
||||
|
||||
r = printk("%ssas: ata%u: %s: %pV",
|
||||
level, ap->print_id, dev_name(dev), &vaf);
|
||||
|
||||
va_end(args);
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
static int sas_ata_hard_reset(struct ata_link *link, unsigned int *class,
|
||||
unsigned long deadline)
|
||||
{
|
||||
int ret = 0, res;
|
||||
struct sas_phy *phy;
|
||||
struct ata_port *ap = link->ap;
|
||||
int (*check_ready)(struct ata_link *link);
|
||||
struct domain_device *dev = ap->private_data;
|
||||
struct sas_internal *i = dev_to_sas_internal(dev);
|
||||
|
||||
res = i->dft->lldd_I_T_nexus_reset(dev);
|
||||
if (res == -ENODEV)
|
||||
return res;
|
||||
|
||||
if (res != TMF_RESP_FUNC_COMPLETE)
|
||||
sas_ata_printk(KERN_DEBUG, dev, "Unable to reset ata device?\n");
|
||||
|
||||
phy = sas_get_local_phy(dev);
|
||||
if (scsi_is_sas_phy_local(phy))
|
||||
check_ready = local_ata_check_ready;
|
||||
else
|
||||
check_ready = smp_ata_check_ready;
|
||||
sas_put_local_phy(phy);
|
||||
|
||||
ret = ata_wait_after_reset(link, deadline, check_ready);
|
||||
if (ret && ret != -EAGAIN)
|
||||
sas_ata_printk(KERN_ERR, dev, "reset failed (errno=%d)\n", ret);
|
||||
|
||||
/* XXX: if the class changes during the reset the upper layer
|
||||
* should be informed, if the device has gone away we assume
|
||||
* libsas will eventually delete it
|
||||
*/
|
||||
switch (dev->sata_dev.command_set) {
|
||||
case ATA_COMMAND_SET:
|
||||
*class = ATA_DEV_ATA;
|
||||
break;
|
||||
case ATAPI_COMMAND_SET:
|
||||
*class = ATA_DEV_ATAPI;
|
||||
break;
|
||||
}
|
||||
|
||||
ap->cbl = ATA_CBL_SATA;
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* notify the lldd to forget the sas_task for this internal ata command
|
||||
* that bypasses scsi-eh
|
||||
*/
|
||||
static void sas_ata_internal_abort(struct sas_task *task)
|
||||
{
|
||||
struct sas_internal *si = dev_to_sas_internal(task->dev);
|
||||
unsigned long flags;
|
||||
int res;
|
||||
|
||||
spin_lock_irqsave(&task->task_state_lock, flags);
|
||||
if (task->task_state_flags & SAS_TASK_STATE_ABORTED ||
|
||||
task->task_state_flags & SAS_TASK_STATE_DONE) {
|
||||
spin_unlock_irqrestore(&task->task_state_lock, flags);
|
||||
SAS_DPRINTK("%s: Task %p already finished.\n", __func__,
|
||||
task);
|
||||
goto out;
|
||||
}
|
||||
task->task_state_flags |= SAS_TASK_STATE_ABORTED;
|
||||
spin_unlock_irqrestore(&task->task_state_lock, flags);
|
||||
|
||||
res = si->dft->lldd_abort_task(task);
|
||||
|
||||
spin_lock_irqsave(&task->task_state_lock, flags);
|
||||
if (task->task_state_flags & SAS_TASK_STATE_DONE ||
|
||||
res == TMF_RESP_FUNC_COMPLETE) {
|
||||
spin_unlock_irqrestore(&task->task_state_lock, flags);
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* XXX we are not prepared to deal with ->lldd_abort_task()
|
||||
* failures. TODO: lldds need to unconditionally forget about
|
||||
* aborted ata tasks, otherwise we (likely) leak the sas task
|
||||
* here
|
||||
*/
|
||||
SAS_DPRINTK("%s: Task %p leaked.\n", __func__, task);
|
||||
|
||||
if (!(task->task_state_flags & SAS_TASK_STATE_DONE))
|
||||
task->task_state_flags &= ~SAS_TASK_STATE_ABORTED;
|
||||
spin_unlock_irqrestore(&task->task_state_lock, flags);
|
||||
|
||||
return;
|
||||
out:
|
||||
list_del_init(&task->list);
|
||||
sas_free_task(task);
|
||||
}
|
||||
|
||||
static void sas_ata_post_internal(struct ata_queued_cmd *qc)
|
||||
{
|
||||
if (qc->flags & ATA_QCFLAG_FAILED)
|
||||
qc->err_mask |= AC_ERR_OTHER;
|
||||
|
||||
if (qc->err_mask) {
|
||||
/*
|
||||
* Find the sas_task and kill it. By this point, libata
|
||||
* has decided to kill the qc and has frozen the port.
|
||||
* In this state sas_ata_task_done() will no longer free
|
||||
* the sas_task, so we need to notify the lldd (via
|
||||
* ->lldd_abort_task) that the task is dead and free it
|
||||
* ourselves.
|
||||
*/
|
||||
struct sas_task *task = qc->lldd_task;
|
||||
|
||||
qc->lldd_task = NULL;
|
||||
if (!task)
|
||||
return;
|
||||
task->uldd_task = NULL;
|
||||
sas_ata_internal_abort(task);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void sas_ata_set_dmamode(struct ata_port *ap, struct ata_device *ata_dev)
|
||||
{
|
||||
struct domain_device *dev = ap->private_data;
|
||||
struct sas_internal *i = dev_to_sas_internal(dev);
|
||||
|
||||
if (i->dft->lldd_ata_set_dmamode)
|
||||
i->dft->lldd_ata_set_dmamode(dev);
|
||||
}
|
||||
|
||||
static void sas_ata_sched_eh(struct ata_port *ap)
|
||||
{
|
||||
struct domain_device *dev = ap->private_data;
|
||||
struct sas_ha_struct *ha = dev->port->ha;
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&ha->lock, flags);
|
||||
if (!test_and_set_bit(SAS_DEV_EH_PENDING, &dev->state))
|
||||
ha->eh_active++;
|
||||
ata_std_sched_eh(ap);
|
||||
spin_unlock_irqrestore(&ha->lock, flags);
|
||||
}
|
||||
|
||||
void sas_ata_end_eh(struct ata_port *ap)
|
||||
{
|
||||
struct domain_device *dev = ap->private_data;
|
||||
struct sas_ha_struct *ha = dev->port->ha;
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&ha->lock, flags);
|
||||
if (test_and_clear_bit(SAS_DEV_EH_PENDING, &dev->state))
|
||||
ha->eh_active--;
|
||||
spin_unlock_irqrestore(&ha->lock, flags);
|
||||
}
|
||||
|
||||
static struct ata_port_operations sas_sata_ops = {
|
||||
.prereset = ata_std_prereset,
|
||||
.hardreset = sas_ata_hard_reset,
|
||||
.postreset = ata_std_postreset,
|
||||
.error_handler = ata_std_error_handler,
|
||||
.post_internal_cmd = sas_ata_post_internal,
|
||||
.qc_defer = ata_std_qc_defer,
|
||||
.qc_prep = ata_noop_qc_prep,
|
||||
.qc_issue = sas_ata_qc_issue,
|
||||
.qc_fill_rtf = sas_ata_qc_fill_rtf,
|
||||
.port_start = ata_sas_port_start,
|
||||
.port_stop = ata_sas_port_stop,
|
||||
.set_dmamode = sas_ata_set_dmamode,
|
||||
.sched_eh = sas_ata_sched_eh,
|
||||
.end_eh = sas_ata_end_eh,
|
||||
};
|
||||
|
||||
static struct ata_port_info sata_port_info = {
|
||||
.flags = ATA_FLAG_SATA | ATA_FLAG_PIO_DMA | ATA_FLAG_NCQ,
|
||||
.pio_mask = ATA_PIO4,
|
||||
.mwdma_mask = ATA_MWDMA2,
|
||||
.udma_mask = ATA_UDMA6,
|
||||
.port_ops = &sas_sata_ops
|
||||
};
|
||||
|
||||
int sas_ata_init(struct domain_device *found_dev)
|
||||
{
|
||||
struct sas_ha_struct *ha = found_dev->port->ha;
|
||||
struct Scsi_Host *shost = ha->core.shost;
|
||||
struct ata_port *ap;
|
||||
int rc;
|
||||
|
||||
ata_host_init(&found_dev->sata_dev.ata_host, ha->dev, &sas_sata_ops);
|
||||
ap = ata_sas_port_alloc(&found_dev->sata_dev.ata_host,
|
||||
&sata_port_info,
|
||||
shost);
|
||||
if (!ap) {
|
||||
SAS_DPRINTK("ata_sas_port_alloc failed.\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
ap->private_data = found_dev;
|
||||
ap->cbl = ATA_CBL_SATA;
|
||||
ap->scsi_host = shost;
|
||||
rc = ata_sas_port_init(ap);
|
||||
if (rc) {
|
||||
ata_sas_port_destroy(ap);
|
||||
return rc;
|
||||
}
|
||||
found_dev->sata_dev.ap = ap;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void sas_ata_task_abort(struct sas_task *task)
|
||||
{
|
||||
struct ata_queued_cmd *qc = task->uldd_task;
|
||||
struct completion *waiting;
|
||||
|
||||
/* Bounce SCSI-initiated commands to the SCSI EH */
|
||||
if (qc->scsicmd) {
|
||||
struct request_queue *q = qc->scsicmd->device->request_queue;
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(q->queue_lock, flags);
|
||||
blk_abort_request(qc->scsicmd->request);
|
||||
spin_unlock_irqrestore(q->queue_lock, flags);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Internal command, fake a timeout and complete. */
|
||||
qc->flags &= ~ATA_QCFLAG_ACTIVE;
|
||||
qc->flags |= ATA_QCFLAG_FAILED;
|
||||
qc->err_mask |= AC_ERR_TIMEOUT;
|
||||
waiting = qc->private_data;
|
||||
complete(waiting);
|
||||
}
|
||||
|
||||
static void sas_get_ata_command_set(struct domain_device *dev)
|
||||
{
|
||||
struct dev_to_host_fis *fis =
|
||||
(struct dev_to_host_fis *) dev->frame_rcvd;
|
||||
|
||||
if (dev->dev_type == SAS_SATA_PENDING)
|
||||
return;
|
||||
|
||||
if ((fis->sector_count == 1 && /* ATA */
|
||||
fis->lbal == 1 &&
|
||||
fis->lbam == 0 &&
|
||||
fis->lbah == 0 &&
|
||||
fis->device == 0)
|
||||
||
|
||||
(fis->sector_count == 0 && /* CE-ATA (mATA) */
|
||||
fis->lbal == 0 &&
|
||||
fis->lbam == 0xCE &&
|
||||
fis->lbah == 0xAA &&
|
||||
(fis->device & ~0x10) == 0))
|
||||
|
||||
dev->sata_dev.command_set = ATA_COMMAND_SET;
|
||||
|
||||
else if ((fis->interrupt_reason == 1 && /* ATAPI */
|
||||
fis->lbal == 1 &&
|
||||
fis->byte_count_low == 0x14 &&
|
||||
fis->byte_count_high == 0xEB &&
|
||||
(fis->device & ~0x10) == 0))
|
||||
|
||||
dev->sata_dev.command_set = ATAPI_COMMAND_SET;
|
||||
|
||||
else if ((fis->sector_count == 1 && /* SEMB */
|
||||
fis->lbal == 1 &&
|
||||
fis->lbam == 0x3C &&
|
||||
fis->lbah == 0xC3 &&
|
||||
fis->device == 0)
|
||||
||
|
||||
(fis->interrupt_reason == 1 && /* SATA PM */
|
||||
fis->lbal == 1 &&
|
||||
fis->byte_count_low == 0x69 &&
|
||||
fis->byte_count_high == 0x96 &&
|
||||
(fis->device & ~0x10) == 0))
|
||||
|
||||
/* Treat it as a superset? */
|
||||
dev->sata_dev.command_set = ATAPI_COMMAND_SET;
|
||||
}
|
||||
|
||||
void sas_probe_sata(struct asd_sas_port *port)
|
||||
{
|
||||
struct domain_device *dev, *n;
|
||||
|
||||
mutex_lock(&port->ha->disco_mutex);
|
||||
list_for_each_entry(dev, &port->disco_list, disco_list_node) {
|
||||
if (!dev_is_sata(dev))
|
||||
continue;
|
||||
|
||||
ata_sas_async_probe(dev->sata_dev.ap);
|
||||
}
|
||||
mutex_unlock(&port->ha->disco_mutex);
|
||||
|
||||
list_for_each_entry_safe(dev, n, &port->disco_list, disco_list_node) {
|
||||
if (!dev_is_sata(dev))
|
||||
continue;
|
||||
|
||||
sas_ata_wait_eh(dev);
|
||||
|
||||
/* if libata could not bring the link up, don't surface
|
||||
* the device
|
||||
*/
|
||||
if (ata_dev_disabled(sas_to_ata_dev(dev)))
|
||||
sas_fail_probe(dev, __func__, -ENODEV);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static void sas_ata_flush_pm_eh(struct asd_sas_port *port, const char *func)
|
||||
{
|
||||
struct domain_device *dev, *n;
|
||||
|
||||
list_for_each_entry_safe(dev, n, &port->dev_list, dev_list_node) {
|
||||
if (!dev_is_sata(dev))
|
||||
continue;
|
||||
|
||||
sas_ata_wait_eh(dev);
|
||||
|
||||
/* if libata failed to power manage the device, tear it down */
|
||||
if (ata_dev_disabled(sas_to_ata_dev(dev)))
|
||||
sas_fail_probe(dev, func, -ENODEV);
|
||||
}
|
||||
}
|
||||
|
||||
void sas_suspend_sata(struct asd_sas_port *port)
|
||||
{
|
||||
struct domain_device *dev;
|
||||
|
||||
mutex_lock(&port->ha->disco_mutex);
|
||||
list_for_each_entry(dev, &port->dev_list, dev_list_node) {
|
||||
struct sata_device *sata;
|
||||
|
||||
if (!dev_is_sata(dev))
|
||||
continue;
|
||||
|
||||
sata = &dev->sata_dev;
|
||||
if (sata->ap->pm_mesg.event == PM_EVENT_SUSPEND)
|
||||
continue;
|
||||
|
||||
ata_sas_port_suspend(sata->ap);
|
||||
}
|
||||
mutex_unlock(&port->ha->disco_mutex);
|
||||
|
||||
sas_ata_flush_pm_eh(port, __func__);
|
||||
}
|
||||
|
||||
void sas_resume_sata(struct asd_sas_port *port)
|
||||
{
|
||||
struct domain_device *dev;
|
||||
|
||||
mutex_lock(&port->ha->disco_mutex);
|
||||
list_for_each_entry(dev, &port->dev_list, dev_list_node) {
|
||||
struct sata_device *sata;
|
||||
|
||||
if (!dev_is_sata(dev))
|
||||
continue;
|
||||
|
||||
sata = &dev->sata_dev;
|
||||
if (sata->ap->pm_mesg.event == PM_EVENT_ON)
|
||||
continue;
|
||||
|
||||
ata_sas_port_resume(sata->ap);
|
||||
}
|
||||
mutex_unlock(&port->ha->disco_mutex);
|
||||
|
||||
sas_ata_flush_pm_eh(port, __func__);
|
||||
}
|
||||
|
||||
/**
|
||||
* sas_discover_sata -- discover an STP/SATA domain device
|
||||
* @dev: pointer to struct domain_device of interest
|
||||
*
|
||||
* Devices directly attached to a HA port, have no parents. All other
|
||||
* devices do, and should have their "parent" pointer set appropriately
|
||||
* before calling this function.
|
||||
*/
|
||||
int sas_discover_sata(struct domain_device *dev)
|
||||
{
|
||||
int res;
|
||||
|
||||
if (dev->dev_type == SAS_SATA_PM)
|
||||
return -ENODEV;
|
||||
|
||||
sas_get_ata_command_set(dev);
|
||||
sas_fill_in_rphy(dev, dev->rphy);
|
||||
|
||||
res = sas_notify_lldd_dev_found(dev);
|
||||
if (res)
|
||||
return res;
|
||||
|
||||
sas_discover_event(dev->port, DISCE_PROBE);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void async_sas_ata_eh(void *data, async_cookie_t cookie)
|
||||
{
|
||||
struct domain_device *dev = data;
|
||||
struct ata_port *ap = dev->sata_dev.ap;
|
||||
struct sas_ha_struct *ha = dev->port->ha;
|
||||
|
||||
sas_ata_printk(KERN_DEBUG, dev, "dev error handler\n");
|
||||
ata_scsi_port_error_handler(ha->core.shost, ap);
|
||||
sas_put_device(dev);
|
||||
}
|
||||
|
||||
void sas_ata_strategy_handler(struct Scsi_Host *shost)
|
||||
{
|
||||
struct sas_ha_struct *sas_ha = SHOST_TO_SAS_HA(shost);
|
||||
ASYNC_DOMAIN_EXCLUSIVE(async);
|
||||
int i;
|
||||
|
||||
/* it's ok to defer revalidation events during ata eh, these
|
||||
* disks are in one of three states:
|
||||
* 1/ present for initial domain discovery, and these
|
||||
* resets will cause bcn flutters
|
||||
* 2/ hot removed, we'll discover that after eh fails
|
||||
* 3/ hot added after initial discovery, lost the race, and need
|
||||
* to catch the next train.
|
||||
*/
|
||||
sas_disable_revalidation(sas_ha);
|
||||
|
||||
spin_lock_irq(&sas_ha->phy_port_lock);
|
||||
for (i = 0; i < sas_ha->num_phys; i++) {
|
||||
struct asd_sas_port *port = sas_ha->sas_port[i];
|
||||
struct domain_device *dev;
|
||||
|
||||
spin_lock(&port->dev_list_lock);
|
||||
list_for_each_entry(dev, &port->dev_list, dev_list_node) {
|
||||
if (!dev_is_sata(dev))
|
||||
continue;
|
||||
|
||||
/* hold a reference over eh since we may be
|
||||
* racing with final remove once all commands
|
||||
* are completed
|
||||
*/
|
||||
kref_get(&dev->kref);
|
||||
|
||||
async_schedule_domain(async_sas_ata_eh, dev, &async);
|
||||
}
|
||||
spin_unlock(&port->dev_list_lock);
|
||||
}
|
||||
spin_unlock_irq(&sas_ha->phy_port_lock);
|
||||
|
||||
async_synchronize_full_domain(&async);
|
||||
|
||||
sas_enable_revalidation(sas_ha);
|
||||
}
|
||||
|
||||
void sas_ata_eh(struct Scsi_Host *shost, struct list_head *work_q,
|
||||
struct list_head *done_q)
|
||||
{
|
||||
struct scsi_cmnd *cmd, *n;
|
||||
struct domain_device *eh_dev;
|
||||
|
||||
do {
|
||||
LIST_HEAD(sata_q);
|
||||
eh_dev = NULL;
|
||||
|
||||
list_for_each_entry_safe(cmd, n, work_q, eh_entry) {
|
||||
struct domain_device *ddev = cmd_to_domain_dev(cmd);
|
||||
|
||||
if (!dev_is_sata(ddev) || TO_SAS_TASK(cmd))
|
||||
continue;
|
||||
if (eh_dev && eh_dev != ddev)
|
||||
continue;
|
||||
eh_dev = ddev;
|
||||
list_move(&cmd->eh_entry, &sata_q);
|
||||
}
|
||||
|
||||
if (!list_empty(&sata_q)) {
|
||||
struct ata_port *ap = eh_dev->sata_dev.ap;
|
||||
|
||||
sas_ata_printk(KERN_DEBUG, eh_dev, "cmd error handler\n");
|
||||
ata_scsi_cmd_error_handler(shost, ap, &sata_q);
|
||||
/*
|
||||
* ata's error handler may leave the cmd on the list
|
||||
* so make sure they don't remain on a stack list
|
||||
* about to go out of scope.
|
||||
*
|
||||
* This looks strange, since the commands are
|
||||
* now part of no list, but the next error
|
||||
* action will be ata_port_error_handler()
|
||||
* which takes no list and sweeps them up
|
||||
* anyway from the ata tag array.
|
||||
*/
|
||||
while (!list_empty(&sata_q))
|
||||
list_del_init(sata_q.next);
|
||||
}
|
||||
} while (eh_dev);
|
||||
}
|
||||
|
||||
void sas_ata_schedule_reset(struct domain_device *dev)
|
||||
{
|
||||
struct ata_eh_info *ehi;
|
||||
struct ata_port *ap;
|
||||
unsigned long flags;
|
||||
|
||||
if (!dev_is_sata(dev))
|
||||
return;
|
||||
|
||||
ap = dev->sata_dev.ap;
|
||||
ehi = &ap->link.eh_info;
|
||||
|
||||
spin_lock_irqsave(ap->lock, flags);
|
||||
ehi->err_mask |= AC_ERR_TIMEOUT;
|
||||
ehi->action |= ATA_EH_RESET;
|
||||
ata_port_schedule_eh(ap);
|
||||
spin_unlock_irqrestore(ap->lock, flags);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(sas_ata_schedule_reset);
|
||||
|
||||
void sas_ata_wait_eh(struct domain_device *dev)
|
||||
{
|
||||
struct ata_port *ap;
|
||||
|
||||
if (!dev_is_sata(dev))
|
||||
return;
|
||||
|
||||
ap = dev->sata_dev.ap;
|
||||
ata_port_wait_eh(ap);
|
||||
}
|
592
drivers/scsi/libsas/sas_discover.c
Normal file
592
drivers/scsi/libsas/sas_discover.c
Normal file
|
@ -0,0 +1,592 @@
|
|||
/*
|
||||
* Serial Attached SCSI (SAS) Discover process
|
||||
*
|
||||
* Copyright (C) 2005 Adaptec, Inc. All rights reserved.
|
||||
* Copyright (C) 2005 Luben Tuikov <luben_tuikov@adaptec.com>
|
||||
*
|
||||
* This file is licensed under GPLv2.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation; either version 2 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/scatterlist.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/async.h>
|
||||
#include <scsi/scsi_host.h>
|
||||
#include <scsi/scsi_eh.h>
|
||||
#include "sas_internal.h"
|
||||
|
||||
#include <scsi/scsi_transport.h>
|
||||
#include <scsi/scsi_transport_sas.h>
|
||||
#include <scsi/sas_ata.h>
|
||||
#include "../scsi_sas_internal.h"
|
||||
|
||||
/* ---------- Basic task processing for discovery purposes ---------- */
|
||||
|
||||
void sas_init_dev(struct domain_device *dev)
|
||||
{
|
||||
switch (dev->dev_type) {
|
||||
case SAS_END_DEVICE:
|
||||
INIT_LIST_HEAD(&dev->ssp_dev.eh_list_node);
|
||||
break;
|
||||
case SAS_EDGE_EXPANDER_DEVICE:
|
||||
case SAS_FANOUT_EXPANDER_DEVICE:
|
||||
INIT_LIST_HEAD(&dev->ex_dev.children);
|
||||
mutex_init(&dev->ex_dev.cmd_mutex);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* ---------- Domain device discovery ---------- */
|
||||
|
||||
/**
|
||||
* sas_get_port_device -- Discover devices which caused port creation
|
||||
* @port: pointer to struct sas_port of interest
|
||||
*
|
||||
* Devices directly attached to a HA port, have no parent. This is
|
||||
* how we know they are (domain) "root" devices. All other devices
|
||||
* do, and should have their "parent" pointer set appropriately as
|
||||
* soon as a child device is discovered.
|
||||
*/
|
||||
static int sas_get_port_device(struct asd_sas_port *port)
|
||||
{
|
||||
struct asd_sas_phy *phy;
|
||||
struct sas_rphy *rphy;
|
||||
struct domain_device *dev;
|
||||
int rc = -ENODEV;
|
||||
|
||||
dev = sas_alloc_device();
|
||||
if (!dev)
|
||||
return -ENOMEM;
|
||||
|
||||
spin_lock_irq(&port->phy_list_lock);
|
||||
if (list_empty(&port->phy_list)) {
|
||||
spin_unlock_irq(&port->phy_list_lock);
|
||||
sas_put_device(dev);
|
||||
return -ENODEV;
|
||||
}
|
||||
phy = container_of(port->phy_list.next, struct asd_sas_phy, port_phy_el);
|
||||
spin_lock(&phy->frame_rcvd_lock);
|
||||
memcpy(dev->frame_rcvd, phy->frame_rcvd, min(sizeof(dev->frame_rcvd),
|
||||
(size_t)phy->frame_rcvd_size));
|
||||
spin_unlock(&phy->frame_rcvd_lock);
|
||||
spin_unlock_irq(&port->phy_list_lock);
|
||||
|
||||
if (dev->frame_rcvd[0] == 0x34 && port->oob_mode == SATA_OOB_MODE) {
|
||||
struct dev_to_host_fis *fis =
|
||||
(struct dev_to_host_fis *) dev->frame_rcvd;
|
||||
if (fis->interrupt_reason == 1 && fis->lbal == 1 &&
|
||||
fis->byte_count_low==0x69 && fis->byte_count_high == 0x96
|
||||
&& (fis->device & ~0x10) == 0)
|
||||
dev->dev_type = SAS_SATA_PM;
|
||||
else
|
||||
dev->dev_type = SAS_SATA_DEV;
|
||||
dev->tproto = SAS_PROTOCOL_SATA;
|
||||
} else {
|
||||
struct sas_identify_frame *id =
|
||||
(struct sas_identify_frame *) dev->frame_rcvd;
|
||||
dev->dev_type = id->dev_type;
|
||||
dev->iproto = id->initiator_bits;
|
||||
dev->tproto = id->target_bits;
|
||||
}
|
||||
|
||||
sas_init_dev(dev);
|
||||
|
||||
dev->port = port;
|
||||
switch (dev->dev_type) {
|
||||
case SAS_SATA_DEV:
|
||||
rc = sas_ata_init(dev);
|
||||
if (rc) {
|
||||
rphy = NULL;
|
||||
break;
|
||||
}
|
||||
/* fall through */
|
||||
case SAS_END_DEVICE:
|
||||
rphy = sas_end_device_alloc(port->port);
|
||||
break;
|
||||
case SAS_EDGE_EXPANDER_DEVICE:
|
||||
rphy = sas_expander_alloc(port->port,
|
||||
SAS_EDGE_EXPANDER_DEVICE);
|
||||
break;
|
||||
case SAS_FANOUT_EXPANDER_DEVICE:
|
||||
rphy = sas_expander_alloc(port->port,
|
||||
SAS_FANOUT_EXPANDER_DEVICE);
|
||||
break;
|
||||
default:
|
||||
printk("ERROR: Unidentified device type %d\n", dev->dev_type);
|
||||
rphy = NULL;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!rphy) {
|
||||
sas_put_device(dev);
|
||||
return rc;
|
||||
}
|
||||
|
||||
rphy->identify.phy_identifier = phy->phy->identify.phy_identifier;
|
||||
memcpy(dev->sas_addr, port->attached_sas_addr, SAS_ADDR_SIZE);
|
||||
sas_fill_in_rphy(dev, rphy);
|
||||
sas_hash_addr(dev->hashed_sas_addr, dev->sas_addr);
|
||||
port->port_dev = dev;
|
||||
dev->linkrate = port->linkrate;
|
||||
dev->min_linkrate = port->linkrate;
|
||||
dev->max_linkrate = port->linkrate;
|
||||
dev->pathways = port->num_phys;
|
||||
memset(port->disc.fanout_sas_addr, 0, SAS_ADDR_SIZE);
|
||||
memset(port->disc.eeds_a, 0, SAS_ADDR_SIZE);
|
||||
memset(port->disc.eeds_b, 0, SAS_ADDR_SIZE);
|
||||
port->disc.max_level = 0;
|
||||
sas_device_set_phy(dev, port->port);
|
||||
|
||||
dev->rphy = rphy;
|
||||
get_device(&dev->rphy->dev);
|
||||
|
||||
if (dev_is_sata(dev) || dev->dev_type == SAS_END_DEVICE)
|
||||
list_add_tail(&dev->disco_list_node, &port->disco_list);
|
||||
else {
|
||||
spin_lock_irq(&port->dev_list_lock);
|
||||
list_add_tail(&dev->dev_list_node, &port->dev_list);
|
||||
spin_unlock_irq(&port->dev_list_lock);
|
||||
}
|
||||
|
||||
spin_lock_irq(&port->phy_list_lock);
|
||||
list_for_each_entry(phy, &port->phy_list, port_phy_el)
|
||||
sas_phy_set_target(phy, dev);
|
||||
spin_unlock_irq(&port->phy_list_lock);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ---------- Discover and Revalidate ---------- */
|
||||
|
||||
int sas_notify_lldd_dev_found(struct domain_device *dev)
|
||||
{
|
||||
int res = 0;
|
||||
struct sas_ha_struct *sas_ha = dev->port->ha;
|
||||
struct Scsi_Host *shost = sas_ha->core.shost;
|
||||
struct sas_internal *i = to_sas_internal(shost->transportt);
|
||||
|
||||
if (!i->dft->lldd_dev_found)
|
||||
return 0;
|
||||
|
||||
res = i->dft->lldd_dev_found(dev);
|
||||
if (res) {
|
||||
printk("sas: driver on pcidev %s cannot handle "
|
||||
"device %llx, error:%d\n",
|
||||
dev_name(sas_ha->dev),
|
||||
SAS_ADDR(dev->sas_addr), res);
|
||||
}
|
||||
set_bit(SAS_DEV_FOUND, &dev->state);
|
||||
kref_get(&dev->kref);
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
void sas_notify_lldd_dev_gone(struct domain_device *dev)
|
||||
{
|
||||
struct sas_ha_struct *sas_ha = dev->port->ha;
|
||||
struct Scsi_Host *shost = sas_ha->core.shost;
|
||||
struct sas_internal *i = to_sas_internal(shost->transportt);
|
||||
|
||||
if (!i->dft->lldd_dev_gone)
|
||||
return;
|
||||
|
||||
if (test_and_clear_bit(SAS_DEV_FOUND, &dev->state)) {
|
||||
i->dft->lldd_dev_gone(dev);
|
||||
sas_put_device(dev);
|
||||
}
|
||||
}
|
||||
|
||||
static void sas_probe_devices(struct work_struct *work)
|
||||
{
|
||||
struct domain_device *dev, *n;
|
||||
struct sas_discovery_event *ev = to_sas_discovery_event(work);
|
||||
struct asd_sas_port *port = ev->port;
|
||||
|
||||
clear_bit(DISCE_PROBE, &port->disc.pending);
|
||||
|
||||
/* devices must be domain members before link recovery and probe */
|
||||
list_for_each_entry(dev, &port->disco_list, disco_list_node) {
|
||||
spin_lock_irq(&port->dev_list_lock);
|
||||
list_add_tail(&dev->dev_list_node, &port->dev_list);
|
||||
spin_unlock_irq(&port->dev_list_lock);
|
||||
}
|
||||
|
||||
sas_probe_sata(port);
|
||||
|
||||
list_for_each_entry_safe(dev, n, &port->disco_list, disco_list_node) {
|
||||
int err;
|
||||
|
||||
err = sas_rphy_add(dev->rphy);
|
||||
if (err)
|
||||
sas_fail_probe(dev, __func__, err);
|
||||
else
|
||||
list_del_init(&dev->disco_list_node);
|
||||
}
|
||||
}
|
||||
|
||||
static void sas_suspend_devices(struct work_struct *work)
|
||||
{
|
||||
struct asd_sas_phy *phy;
|
||||
struct domain_device *dev;
|
||||
struct sas_discovery_event *ev = to_sas_discovery_event(work);
|
||||
struct asd_sas_port *port = ev->port;
|
||||
struct Scsi_Host *shost = port->ha->core.shost;
|
||||
struct sas_internal *si = to_sas_internal(shost->transportt);
|
||||
|
||||
clear_bit(DISCE_SUSPEND, &port->disc.pending);
|
||||
|
||||
sas_suspend_sata(port);
|
||||
|
||||
/* lldd is free to forget the domain_device across the
|
||||
* suspension, we force the issue here to keep the reference
|
||||
* counts aligned
|
||||
*/
|
||||
list_for_each_entry(dev, &port->dev_list, dev_list_node)
|
||||
sas_notify_lldd_dev_gone(dev);
|
||||
|
||||
/* we are suspending, so we know events are disabled and
|
||||
* phy_list is not being mutated
|
||||
*/
|
||||
list_for_each_entry(phy, &port->phy_list, port_phy_el) {
|
||||
if (si->dft->lldd_port_formed)
|
||||
si->dft->lldd_port_deformed(phy);
|
||||
phy->suspended = 1;
|
||||
port->suspended = 1;
|
||||
}
|
||||
}
|
||||
|
||||
static void sas_resume_devices(struct work_struct *work)
|
||||
{
|
||||
struct sas_discovery_event *ev = to_sas_discovery_event(work);
|
||||
struct asd_sas_port *port = ev->port;
|
||||
|
||||
clear_bit(DISCE_RESUME, &port->disc.pending);
|
||||
|
||||
sas_resume_sata(port);
|
||||
}
|
||||
|
||||
/**
|
||||
* sas_discover_end_dev -- discover an end device (SSP, etc)
|
||||
* @end: pointer to domain device of interest
|
||||
*
|
||||
* See comment in sas_discover_sata().
|
||||
*/
|
||||
int sas_discover_end_dev(struct domain_device *dev)
|
||||
{
|
||||
int res;
|
||||
|
||||
res = sas_notify_lldd_dev_found(dev);
|
||||
if (res)
|
||||
return res;
|
||||
sas_discover_event(dev->port, DISCE_PROBE);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ---------- Device registration and unregistration ---------- */
|
||||
|
||||
void sas_free_device(struct kref *kref)
|
||||
{
|
||||
struct domain_device *dev = container_of(kref, typeof(*dev), kref);
|
||||
|
||||
put_device(&dev->rphy->dev);
|
||||
dev->rphy = NULL;
|
||||
|
||||
if (dev->parent)
|
||||
sas_put_device(dev->parent);
|
||||
|
||||
sas_port_put_phy(dev->phy);
|
||||
dev->phy = NULL;
|
||||
|
||||
/* remove the phys and ports, everything else should be gone */
|
||||
if (dev->dev_type == SAS_EDGE_EXPANDER_DEVICE || dev->dev_type == SAS_FANOUT_EXPANDER_DEVICE)
|
||||
kfree(dev->ex_dev.ex_phy);
|
||||
|
||||
if (dev_is_sata(dev) && dev->sata_dev.ap) {
|
||||
ata_sas_port_destroy(dev->sata_dev.ap);
|
||||
dev->sata_dev.ap = NULL;
|
||||
}
|
||||
|
||||
kfree(dev);
|
||||
}
|
||||
|
||||
static void sas_unregister_common_dev(struct asd_sas_port *port, struct domain_device *dev)
|
||||
{
|
||||
struct sas_ha_struct *ha = port->ha;
|
||||
|
||||
sas_notify_lldd_dev_gone(dev);
|
||||
if (!dev->parent)
|
||||
dev->port->port_dev = NULL;
|
||||
else
|
||||
list_del_init(&dev->siblings);
|
||||
|
||||
spin_lock_irq(&port->dev_list_lock);
|
||||
list_del_init(&dev->dev_list_node);
|
||||
if (dev_is_sata(dev))
|
||||
sas_ata_end_eh(dev->sata_dev.ap);
|
||||
spin_unlock_irq(&port->dev_list_lock);
|
||||
|
||||
spin_lock_irq(&ha->lock);
|
||||
if (dev->dev_type == SAS_END_DEVICE &&
|
||||
!list_empty(&dev->ssp_dev.eh_list_node)) {
|
||||
list_del_init(&dev->ssp_dev.eh_list_node);
|
||||
ha->eh_active--;
|
||||
}
|
||||
spin_unlock_irq(&ha->lock);
|
||||
|
||||
sas_put_device(dev);
|
||||
}
|
||||
|
||||
static void sas_destruct_devices(struct work_struct *work)
|
||||
{
|
||||
struct domain_device *dev, *n;
|
||||
struct sas_discovery_event *ev = to_sas_discovery_event(work);
|
||||
struct asd_sas_port *port = ev->port;
|
||||
|
||||
clear_bit(DISCE_DESTRUCT, &port->disc.pending);
|
||||
|
||||
list_for_each_entry_safe(dev, n, &port->destroy_list, disco_list_node) {
|
||||
list_del_init(&dev->disco_list_node);
|
||||
|
||||
sas_remove_children(&dev->rphy->dev);
|
||||
sas_rphy_delete(dev->rphy);
|
||||
sas_unregister_common_dev(port, dev);
|
||||
}
|
||||
}
|
||||
|
||||
void sas_unregister_dev(struct asd_sas_port *port, struct domain_device *dev)
|
||||
{
|
||||
if (!test_bit(SAS_DEV_DESTROY, &dev->state) &&
|
||||
!list_empty(&dev->disco_list_node)) {
|
||||
/* this rphy never saw sas_rphy_add */
|
||||
list_del_init(&dev->disco_list_node);
|
||||
sas_rphy_free(dev->rphy);
|
||||
sas_unregister_common_dev(port, dev);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!test_and_set_bit(SAS_DEV_DESTROY, &dev->state)) {
|
||||
sas_rphy_unlink(dev->rphy);
|
||||
list_move_tail(&dev->disco_list_node, &port->destroy_list);
|
||||
sas_discover_event(dev->port, DISCE_DESTRUCT);
|
||||
}
|
||||
}
|
||||
|
||||
void sas_unregister_domain_devices(struct asd_sas_port *port, int gone)
|
||||
{
|
||||
struct domain_device *dev, *n;
|
||||
|
||||
list_for_each_entry_safe_reverse(dev, n, &port->dev_list, dev_list_node) {
|
||||
if (gone)
|
||||
set_bit(SAS_DEV_GONE, &dev->state);
|
||||
sas_unregister_dev(port, dev);
|
||||
}
|
||||
|
||||
list_for_each_entry_safe(dev, n, &port->disco_list, disco_list_node)
|
||||
sas_unregister_dev(port, dev);
|
||||
|
||||
port->port->rphy = NULL;
|
||||
|
||||
}
|
||||
|
||||
void sas_device_set_phy(struct domain_device *dev, struct sas_port *port)
|
||||
{
|
||||
struct sas_ha_struct *ha;
|
||||
struct sas_phy *new_phy;
|
||||
|
||||
if (!dev)
|
||||
return;
|
||||
|
||||
ha = dev->port->ha;
|
||||
new_phy = sas_port_get_phy(port);
|
||||
|
||||
/* pin and record last seen phy */
|
||||
spin_lock_irq(&ha->phy_port_lock);
|
||||
if (new_phy) {
|
||||
sas_port_put_phy(dev->phy);
|
||||
dev->phy = new_phy;
|
||||
}
|
||||
spin_unlock_irq(&ha->phy_port_lock);
|
||||
}
|
||||
|
||||
/* ---------- Discovery and Revalidation ---------- */
|
||||
|
||||
/**
|
||||
* sas_discover_domain -- discover the domain
|
||||
* @port: port to the domain of interest
|
||||
*
|
||||
* NOTE: this process _must_ quit (return) as soon as any connection
|
||||
* errors are encountered. Connection recovery is done elsewhere.
|
||||
* Discover process only interrogates devices in order to discover the
|
||||
* domain.
|
||||
*/
|
||||
static void sas_discover_domain(struct work_struct *work)
|
||||
{
|
||||
struct domain_device *dev;
|
||||
int error = 0;
|
||||
struct sas_discovery_event *ev = to_sas_discovery_event(work);
|
||||
struct asd_sas_port *port = ev->port;
|
||||
|
||||
clear_bit(DISCE_DISCOVER_DOMAIN, &port->disc.pending);
|
||||
|
||||
if (port->port_dev)
|
||||
return;
|
||||
|
||||
error = sas_get_port_device(port);
|
||||
if (error)
|
||||
return;
|
||||
dev = port->port_dev;
|
||||
|
||||
SAS_DPRINTK("DOING DISCOVERY on port %d, pid:%d\n", port->id,
|
||||
task_pid_nr(current));
|
||||
|
||||
switch (dev->dev_type) {
|
||||
case SAS_END_DEVICE:
|
||||
error = sas_discover_end_dev(dev);
|
||||
break;
|
||||
case SAS_EDGE_EXPANDER_DEVICE:
|
||||
case SAS_FANOUT_EXPANDER_DEVICE:
|
||||
error = sas_discover_root_expander(dev);
|
||||
break;
|
||||
case SAS_SATA_DEV:
|
||||
case SAS_SATA_PM:
|
||||
#ifdef CONFIG_SCSI_SAS_ATA
|
||||
error = sas_discover_sata(dev);
|
||||
break;
|
||||
#else
|
||||
SAS_DPRINTK("ATA device seen but CONFIG_SCSI_SAS_ATA=N so cannot attach\n");
|
||||
/* Fall through */
|
||||
#endif
|
||||
default:
|
||||
error = -ENXIO;
|
||||
SAS_DPRINTK("unhandled device %d\n", dev->dev_type);
|
||||
break;
|
||||
}
|
||||
|
||||
if (error) {
|
||||
sas_rphy_free(dev->rphy);
|
||||
list_del_init(&dev->disco_list_node);
|
||||
spin_lock_irq(&port->dev_list_lock);
|
||||
list_del_init(&dev->dev_list_node);
|
||||
spin_unlock_irq(&port->dev_list_lock);
|
||||
|
||||
sas_put_device(dev);
|
||||
port->port_dev = NULL;
|
||||
}
|
||||
|
||||
SAS_DPRINTK("DONE DISCOVERY on port %d, pid:%d, result:%d\n", port->id,
|
||||
task_pid_nr(current), error);
|
||||
}
|
||||
|
||||
static void sas_revalidate_domain(struct work_struct *work)
|
||||
{
|
||||
int res = 0;
|
||||
struct sas_discovery_event *ev = to_sas_discovery_event(work);
|
||||
struct asd_sas_port *port = ev->port;
|
||||
struct sas_ha_struct *ha = port->ha;
|
||||
struct domain_device *ddev = port->port_dev;
|
||||
|
||||
/* prevent revalidation from finding sata links in recovery */
|
||||
mutex_lock(&ha->disco_mutex);
|
||||
if (test_bit(SAS_HA_ATA_EH_ACTIVE, &ha->state)) {
|
||||
SAS_DPRINTK("REVALIDATION DEFERRED on port %d, pid:%d\n",
|
||||
port->id, task_pid_nr(current));
|
||||
goto out;
|
||||
}
|
||||
|
||||
clear_bit(DISCE_REVALIDATE_DOMAIN, &port->disc.pending);
|
||||
|
||||
SAS_DPRINTK("REVALIDATING DOMAIN on port %d, pid:%d\n", port->id,
|
||||
task_pid_nr(current));
|
||||
|
||||
if (ddev && (ddev->dev_type == SAS_FANOUT_EXPANDER_DEVICE ||
|
||||
ddev->dev_type == SAS_EDGE_EXPANDER_DEVICE))
|
||||
res = sas_ex_revalidate_domain(ddev);
|
||||
|
||||
SAS_DPRINTK("done REVALIDATING DOMAIN on port %d, pid:%d, res 0x%x\n",
|
||||
port->id, task_pid_nr(current), res);
|
||||
out:
|
||||
mutex_unlock(&ha->disco_mutex);
|
||||
}
|
||||
|
||||
/* ---------- Events ---------- */
|
||||
|
||||
static void sas_chain_work(struct sas_ha_struct *ha, struct sas_work *sw)
|
||||
{
|
||||
/* chained work is not subject to SA_HA_DRAINING or
|
||||
* SAS_HA_REGISTERED, because it is either submitted in the
|
||||
* workqueue, or known to be submitted from a context that is
|
||||
* not racing against draining
|
||||
*/
|
||||
scsi_queue_work(ha->core.shost, &sw->work);
|
||||
}
|
||||
|
||||
static void sas_chain_event(int event, unsigned long *pending,
|
||||
struct sas_work *sw,
|
||||
struct sas_ha_struct *ha)
|
||||
{
|
||||
if (!test_and_set_bit(event, pending)) {
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&ha->lock, flags);
|
||||
sas_chain_work(ha, sw);
|
||||
spin_unlock_irqrestore(&ha->lock, flags);
|
||||
}
|
||||
}
|
||||
|
||||
int sas_discover_event(struct asd_sas_port *port, enum discover_event ev)
|
||||
{
|
||||
struct sas_discovery *disc;
|
||||
|
||||
if (!port)
|
||||
return 0;
|
||||
disc = &port->disc;
|
||||
|
||||
BUG_ON(ev >= DISC_NUM_EVENTS);
|
||||
|
||||
sas_chain_event(ev, &disc->pending, &disc->disc_work[ev].work, port->ha);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* sas_init_disc -- initialize the discovery struct in the port
|
||||
* @port: pointer to struct port
|
||||
*
|
||||
* Called when the ports are being initialized.
|
||||
*/
|
||||
void sas_init_disc(struct sas_discovery *disc, struct asd_sas_port *port)
|
||||
{
|
||||
int i;
|
||||
|
||||
static const work_func_t sas_event_fns[DISC_NUM_EVENTS] = {
|
||||
[DISCE_DISCOVER_DOMAIN] = sas_discover_domain,
|
||||
[DISCE_REVALIDATE_DOMAIN] = sas_revalidate_domain,
|
||||
[DISCE_PROBE] = sas_probe_devices,
|
||||
[DISCE_SUSPEND] = sas_suspend_devices,
|
||||
[DISCE_RESUME] = sas_resume_devices,
|
||||
[DISCE_DESTRUCT] = sas_destruct_devices,
|
||||
};
|
||||
|
||||
disc->pending = 0;
|
||||
for (i = 0; i < DISC_NUM_EVENTS; i++) {
|
||||
INIT_SAS_WORK(&disc->disc_work[i].work, sas_event_fns[i]);
|
||||
disc->disc_work[i].port = port;
|
||||
}
|
||||
}
|
73
drivers/scsi/libsas/sas_dump.c
Normal file
73
drivers/scsi/libsas/sas_dump.c
Normal file
|
@ -0,0 +1,73 @@
|
|||
/*
|
||||
* Serial Attached SCSI (SAS) Dump/Debugging routines
|
||||
*
|
||||
* Copyright (C) 2005 Adaptec, Inc. All rights reserved.
|
||||
* Copyright (C) 2005 Luben Tuikov <luben_tuikov@adaptec.com>
|
||||
*
|
||||
* This file is licensed under GPLv2.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation; either version 2 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*
|
||||
*/
|
||||
|
||||
#include "sas_dump.h"
|
||||
|
||||
static const char *sas_hae_str[] = {
|
||||
[0] = "HAE_RESET",
|
||||
};
|
||||
|
||||
static const char *sas_porte_str[] = {
|
||||
[0] = "PORTE_BYTES_DMAED",
|
||||
[1] = "PORTE_BROADCAST_RCVD",
|
||||
[2] = "PORTE_LINK_RESET_ERR",
|
||||
[3] = "PORTE_TIMER_EVENT",
|
||||
[4] = "PORTE_HARD_RESET",
|
||||
};
|
||||
|
||||
static const char *sas_phye_str[] = {
|
||||
[0] = "PHYE_LOSS_OF_SIGNAL",
|
||||
[1] = "PHYE_OOB_DONE",
|
||||
[2] = "PHYE_OOB_ERROR",
|
||||
[3] = "PHYE_SPINUP_HOLD",
|
||||
[4] = "PHYE_RESUME_TIMEOUT",
|
||||
};
|
||||
|
||||
void sas_dprint_porte(int phyid, enum port_event pe)
|
||||
{
|
||||
SAS_DPRINTK("phy%d: port event: %s\n", phyid, sas_porte_str[pe]);
|
||||
}
|
||||
void sas_dprint_phye(int phyid, enum phy_event pe)
|
||||
{
|
||||
SAS_DPRINTK("phy%d: phy event: %s\n", phyid, sas_phye_str[pe]);
|
||||
}
|
||||
|
||||
void sas_dprint_hae(struct sas_ha_struct *sas_ha, enum ha_event he)
|
||||
{
|
||||
SAS_DPRINTK("ha %s: %s event\n", dev_name(sas_ha->dev),
|
||||
sas_hae_str[he]);
|
||||
}
|
||||
|
||||
void sas_dump_port(struct asd_sas_port *port)
|
||||
{
|
||||
SAS_DPRINTK("port%d: class:0x%x\n", port->id, port->class);
|
||||
SAS_DPRINTK("port%d: sas_addr:%llx\n", port->id,
|
||||
SAS_ADDR(port->sas_addr));
|
||||
SAS_DPRINTK("port%d: attached_sas_addr:%llx\n", port->id,
|
||||
SAS_ADDR(port->attached_sas_addr));
|
||||
SAS_DPRINTK("port%d: iproto:0x%x\n", port->id, port->iproto);
|
||||
SAS_DPRINTK("port%d: tproto:0x%x\n", port->id, port->tproto);
|
||||
SAS_DPRINTK("port%d: oob_mode:0x%x\n", port->id, port->oob_mode);
|
||||
SAS_DPRINTK("port%d: num_phys:%d\n", port->id, port->num_phys);
|
||||
}
|
30
drivers/scsi/libsas/sas_dump.h
Normal file
30
drivers/scsi/libsas/sas_dump.h
Normal file
|
@ -0,0 +1,30 @@
|
|||
/*
|
||||
* Serial Attached SCSI (SAS) Dump/Debugging routines header file
|
||||
*
|
||||
* Copyright (C) 2005 Adaptec, Inc. All rights reserved.
|
||||
* Copyright (C) 2005 Luben Tuikov <luben_tuikov@adaptec.com>
|
||||
*
|
||||
* This file is licensed under GPLv2.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation; either version 2 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*
|
||||
*/
|
||||
|
||||
#include "sas_internal.h"
|
||||
|
||||
void sas_dprint_porte(int phyid, enum port_event pe);
|
||||
void sas_dprint_phye(int phyid, enum phy_event pe);
|
||||
void sas_dprint_hae(struct sas_ha_struct *sas_ha, enum ha_event he);
|
||||
void sas_dump_port(struct asd_sas_port *port);
|
165
drivers/scsi/libsas/sas_event.c
Normal file
165
drivers/scsi/libsas/sas_event.c
Normal file
|
@ -0,0 +1,165 @@
|
|||
/*
|
||||
* Serial Attached SCSI (SAS) Event processing
|
||||
*
|
||||
* Copyright (C) 2005 Adaptec, Inc. All rights reserved.
|
||||
* Copyright (C) 2005 Luben Tuikov <luben_tuikov@adaptec.com>
|
||||
*
|
||||
* This file is licensed under GPLv2.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation; either version 2 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/export.h>
|
||||
#include <scsi/scsi_host.h>
|
||||
#include "sas_internal.h"
|
||||
#include "sas_dump.h"
|
||||
|
||||
void sas_queue_work(struct sas_ha_struct *ha, struct sas_work *sw)
|
||||
{
|
||||
if (!test_bit(SAS_HA_REGISTERED, &ha->state))
|
||||
return;
|
||||
|
||||
if (test_bit(SAS_HA_DRAINING, &ha->state)) {
|
||||
/* add it to the defer list, if not already pending */
|
||||
if (list_empty(&sw->drain_node))
|
||||
list_add(&sw->drain_node, &ha->defer_q);
|
||||
} else
|
||||
scsi_queue_work(ha->core.shost, &sw->work);
|
||||
}
|
||||
|
||||
static void sas_queue_event(int event, unsigned long *pending,
|
||||
struct sas_work *work,
|
||||
struct sas_ha_struct *ha)
|
||||
{
|
||||
if (!test_and_set_bit(event, pending)) {
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&ha->lock, flags);
|
||||
sas_queue_work(ha, work);
|
||||
spin_unlock_irqrestore(&ha->lock, flags);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void __sas_drain_work(struct sas_ha_struct *ha)
|
||||
{
|
||||
struct workqueue_struct *wq = ha->core.shost->work_q;
|
||||
struct sas_work *sw, *_sw;
|
||||
|
||||
set_bit(SAS_HA_DRAINING, &ha->state);
|
||||
/* flush submitters */
|
||||
spin_lock_irq(&ha->lock);
|
||||
spin_unlock_irq(&ha->lock);
|
||||
|
||||
drain_workqueue(wq);
|
||||
|
||||
spin_lock_irq(&ha->lock);
|
||||
clear_bit(SAS_HA_DRAINING, &ha->state);
|
||||
list_for_each_entry_safe(sw, _sw, &ha->defer_q, drain_node) {
|
||||
list_del_init(&sw->drain_node);
|
||||
sas_queue_work(ha, sw);
|
||||
}
|
||||
spin_unlock_irq(&ha->lock);
|
||||
}
|
||||
|
||||
int sas_drain_work(struct sas_ha_struct *ha)
|
||||
{
|
||||
int err;
|
||||
|
||||
err = mutex_lock_interruptible(&ha->drain_mutex);
|
||||
if (err)
|
||||
return err;
|
||||
if (test_bit(SAS_HA_REGISTERED, &ha->state))
|
||||
__sas_drain_work(ha);
|
||||
mutex_unlock(&ha->drain_mutex);
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(sas_drain_work);
|
||||
|
||||
void sas_disable_revalidation(struct sas_ha_struct *ha)
|
||||
{
|
||||
mutex_lock(&ha->disco_mutex);
|
||||
set_bit(SAS_HA_ATA_EH_ACTIVE, &ha->state);
|
||||
mutex_unlock(&ha->disco_mutex);
|
||||
}
|
||||
|
||||
void sas_enable_revalidation(struct sas_ha_struct *ha)
|
||||
{
|
||||
int i;
|
||||
|
||||
mutex_lock(&ha->disco_mutex);
|
||||
clear_bit(SAS_HA_ATA_EH_ACTIVE, &ha->state);
|
||||
for (i = 0; i < ha->num_phys; i++) {
|
||||
struct asd_sas_port *port = ha->sas_port[i];
|
||||
const int ev = DISCE_REVALIDATE_DOMAIN;
|
||||
struct sas_discovery *d = &port->disc;
|
||||
|
||||
if (!test_and_clear_bit(ev, &d->pending))
|
||||
continue;
|
||||
|
||||
sas_queue_event(ev, &d->pending, &d->disc_work[ev].work, ha);
|
||||
}
|
||||
mutex_unlock(&ha->disco_mutex);
|
||||
}
|
||||
|
||||
static void notify_ha_event(struct sas_ha_struct *sas_ha, enum ha_event event)
|
||||
{
|
||||
BUG_ON(event >= HA_NUM_EVENTS);
|
||||
|
||||
sas_queue_event(event, &sas_ha->pending,
|
||||
&sas_ha->ha_events[event].work, sas_ha);
|
||||
}
|
||||
|
||||
static void notify_port_event(struct asd_sas_phy *phy, enum port_event event)
|
||||
{
|
||||
struct sas_ha_struct *ha = phy->ha;
|
||||
|
||||
BUG_ON(event >= PORT_NUM_EVENTS);
|
||||
|
||||
sas_queue_event(event, &phy->port_events_pending,
|
||||
&phy->port_events[event].work, ha);
|
||||
}
|
||||
|
||||
void sas_notify_phy_event(struct asd_sas_phy *phy, enum phy_event event)
|
||||
{
|
||||
struct sas_ha_struct *ha = phy->ha;
|
||||
|
||||
BUG_ON(event >= PHY_NUM_EVENTS);
|
||||
|
||||
sas_queue_event(event, &phy->phy_events_pending,
|
||||
&phy->phy_events[event].work, ha);
|
||||
}
|
||||
|
||||
int sas_init_events(struct sas_ha_struct *sas_ha)
|
||||
{
|
||||
static const work_func_t sas_ha_event_fns[HA_NUM_EVENTS] = {
|
||||
[HAE_RESET] = sas_hae_reset,
|
||||
};
|
||||
|
||||
int i;
|
||||
|
||||
for (i = 0; i < HA_NUM_EVENTS; i++) {
|
||||
INIT_SAS_WORK(&sas_ha->ha_events[i].work, sas_ha_event_fns[i]);
|
||||
sas_ha->ha_events[i].ha = sas_ha;
|
||||
}
|
||||
|
||||
sas_ha->notify_ha_event = notify_ha_event;
|
||||
sas_ha->notify_port_event = notify_port_event;
|
||||
sas_ha->notify_phy_event = sas_notify_phy_event;
|
||||
|
||||
return 0;
|
||||
}
|
2186
drivers/scsi/libsas/sas_expander.c
Normal file
2186
drivers/scsi/libsas/sas_expander.c
Normal file
File diff suppressed because it is too large
Load diff
383
drivers/scsi/libsas/sas_host_smp.c
Normal file
383
drivers/scsi/libsas/sas_host_smp.c
Normal file
|
@ -0,0 +1,383 @@
|
|||
/*
|
||||
* Serial Attached SCSI (SAS) Expander discovery and configuration
|
||||
*
|
||||
* Copyright (C) 2007 James E.J. Bottomley
|
||||
* <James.Bottomley@HansenPartnership.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation; version 2 only.
|
||||
*/
|
||||
#include <linux/scatterlist.h>
|
||||
#include <linux/blkdev.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/export.h>
|
||||
|
||||
#include "sas_internal.h"
|
||||
|
||||
#include <scsi/scsi_transport.h>
|
||||
#include <scsi/scsi_transport_sas.h>
|
||||
#include "../scsi_sas_internal.h"
|
||||
|
||||
static void sas_host_smp_discover(struct sas_ha_struct *sas_ha, u8 *resp_data,
|
||||
u8 phy_id)
|
||||
{
|
||||
struct sas_phy *phy;
|
||||
struct sas_rphy *rphy;
|
||||
|
||||
if (phy_id >= sas_ha->num_phys) {
|
||||
resp_data[2] = SMP_RESP_NO_PHY;
|
||||
return;
|
||||
}
|
||||
resp_data[2] = SMP_RESP_FUNC_ACC;
|
||||
|
||||
phy = sas_ha->sas_phy[phy_id]->phy;
|
||||
resp_data[9] = phy_id;
|
||||
resp_data[13] = phy->negotiated_linkrate;
|
||||
memcpy(resp_data + 16, sas_ha->sas_addr, SAS_ADDR_SIZE);
|
||||
memcpy(resp_data + 24, sas_ha->sas_phy[phy_id]->attached_sas_addr,
|
||||
SAS_ADDR_SIZE);
|
||||
resp_data[40] = (phy->minimum_linkrate << 4) |
|
||||
phy->minimum_linkrate_hw;
|
||||
resp_data[41] = (phy->maximum_linkrate << 4) |
|
||||
phy->maximum_linkrate_hw;
|
||||
|
||||
if (!sas_ha->sas_phy[phy_id]->port ||
|
||||
!sas_ha->sas_phy[phy_id]->port->port_dev)
|
||||
return;
|
||||
|
||||
rphy = sas_ha->sas_phy[phy_id]->port->port_dev->rphy;
|
||||
resp_data[12] = rphy->identify.device_type << 4;
|
||||
resp_data[14] = rphy->identify.initiator_port_protocols;
|
||||
resp_data[15] = rphy->identify.target_port_protocols;
|
||||
}
|
||||
|
||||
/**
|
||||
* to_sas_gpio_gp_bit - given the gpio frame data find the byte/bit position of 'od'
|
||||
* @od: od bit to find
|
||||
* @data: incoming bitstream (from frame)
|
||||
* @index: requested data register index (from frame)
|
||||
* @count: total number of registers in the bitstream (from frame)
|
||||
* @bit: bit position of 'od' in the returned byte
|
||||
*
|
||||
* returns NULL if 'od' is not in 'data'
|
||||
*
|
||||
* From SFF-8485 v0.7:
|
||||
* "In GPIO_TX[1], bit 0 of byte 3 contains the first bit (i.e., OD0.0)
|
||||
* and bit 7 of byte 0 contains the 32nd bit (i.e., OD10.1).
|
||||
*
|
||||
* In GPIO_TX[2], bit 0 of byte 3 contains the 33rd bit (i.e., OD10.2)
|
||||
* and bit 7 of byte 0 contains the 64th bit (i.e., OD21.0)."
|
||||
*
|
||||
* The general-purpose (raw-bitstream) RX registers have the same layout
|
||||
* although 'od' is renamed 'id' for 'input data'.
|
||||
*
|
||||
* SFF-8489 defines the behavior of the LEDs in response to the 'od' values.
|
||||
*/
|
||||
static u8 *to_sas_gpio_gp_bit(unsigned int od, u8 *data, u8 index, u8 count, u8 *bit)
|
||||
{
|
||||
unsigned int reg;
|
||||
u8 byte;
|
||||
|
||||
/* gp registers start at index 1 */
|
||||
if (index == 0)
|
||||
return NULL;
|
||||
|
||||
index--; /* make index 0-based */
|
||||
if (od < index * 32)
|
||||
return NULL;
|
||||
|
||||
od -= index * 32;
|
||||
reg = od >> 5;
|
||||
|
||||
if (reg >= count)
|
||||
return NULL;
|
||||
|
||||
od &= (1 << 5) - 1;
|
||||
byte = 3 - (od >> 3);
|
||||
*bit = od & ((1 << 3) - 1);
|
||||
|
||||
return &data[reg * 4 + byte];
|
||||
}
|
||||
|
||||
int try_test_sas_gpio_gp_bit(unsigned int od, u8 *data, u8 index, u8 count)
|
||||
{
|
||||
u8 *byte;
|
||||
u8 bit;
|
||||
|
||||
byte = to_sas_gpio_gp_bit(od, data, index, count, &bit);
|
||||
if (!byte)
|
||||
return -1;
|
||||
|
||||
return (*byte >> bit) & 1;
|
||||
}
|
||||
EXPORT_SYMBOL(try_test_sas_gpio_gp_bit);
|
||||
|
||||
static int sas_host_smp_write_gpio(struct sas_ha_struct *sas_ha, u8 *resp_data,
|
||||
u8 reg_type, u8 reg_index, u8 reg_count,
|
||||
u8 *req_data)
|
||||
{
|
||||
struct sas_internal *i = to_sas_internal(sas_ha->core.shost->transportt);
|
||||
int written;
|
||||
|
||||
if (i->dft->lldd_write_gpio == NULL) {
|
||||
resp_data[2] = SMP_RESP_FUNC_UNK;
|
||||
return 0;
|
||||
}
|
||||
|
||||
written = i->dft->lldd_write_gpio(sas_ha, reg_type, reg_index,
|
||||
reg_count, req_data);
|
||||
|
||||
if (written < 0) {
|
||||
resp_data[2] = SMP_RESP_FUNC_FAILED;
|
||||
written = 0;
|
||||
} else
|
||||
resp_data[2] = SMP_RESP_FUNC_ACC;
|
||||
|
||||
return written;
|
||||
}
|
||||
|
||||
static void sas_report_phy_sata(struct sas_ha_struct *sas_ha, u8 *resp_data,
|
||||
u8 phy_id)
|
||||
{
|
||||
struct sas_rphy *rphy;
|
||||
struct dev_to_host_fis *fis;
|
||||
int i;
|
||||
|
||||
if (phy_id >= sas_ha->num_phys) {
|
||||
resp_data[2] = SMP_RESP_NO_PHY;
|
||||
return;
|
||||
}
|
||||
|
||||
resp_data[2] = SMP_RESP_PHY_NO_SATA;
|
||||
|
||||
if (!sas_ha->sas_phy[phy_id]->port)
|
||||
return;
|
||||
|
||||
rphy = sas_ha->sas_phy[phy_id]->port->port_dev->rphy;
|
||||
fis = (struct dev_to_host_fis *)
|
||||
sas_ha->sas_phy[phy_id]->port->port_dev->frame_rcvd;
|
||||
if (rphy->identify.target_port_protocols != SAS_PROTOCOL_SATA)
|
||||
return;
|
||||
|
||||
resp_data[2] = SMP_RESP_FUNC_ACC;
|
||||
resp_data[9] = phy_id;
|
||||
memcpy(resp_data + 16, sas_ha->sas_phy[phy_id]->attached_sas_addr,
|
||||
SAS_ADDR_SIZE);
|
||||
|
||||
/* check to see if we have a valid d2h fis */
|
||||
if (fis->fis_type != 0x34)
|
||||
return;
|
||||
|
||||
/* the d2h fis is required by the standard to be in LE format */
|
||||
for (i = 0; i < 20; i += 4) {
|
||||
u8 *dst = resp_data + 24 + i, *src =
|
||||
&sas_ha->sas_phy[phy_id]->port->port_dev->frame_rcvd[i];
|
||||
dst[0] = src[3];
|
||||
dst[1] = src[2];
|
||||
dst[2] = src[1];
|
||||
dst[3] = src[0];
|
||||
}
|
||||
}
|
||||
|
||||
static void sas_phy_control(struct sas_ha_struct *sas_ha, u8 phy_id,
|
||||
u8 phy_op, enum sas_linkrate min,
|
||||
enum sas_linkrate max, u8 *resp_data)
|
||||
{
|
||||
struct sas_internal *i =
|
||||
to_sas_internal(sas_ha->core.shost->transportt);
|
||||
struct sas_phy_linkrates rates;
|
||||
struct asd_sas_phy *asd_phy;
|
||||
|
||||
if (phy_id >= sas_ha->num_phys) {
|
||||
resp_data[2] = SMP_RESP_NO_PHY;
|
||||
return;
|
||||
}
|
||||
|
||||
asd_phy = sas_ha->sas_phy[phy_id];
|
||||
switch (phy_op) {
|
||||
case PHY_FUNC_NOP:
|
||||
case PHY_FUNC_LINK_RESET:
|
||||
case PHY_FUNC_HARD_RESET:
|
||||
case PHY_FUNC_DISABLE:
|
||||
case PHY_FUNC_CLEAR_ERROR_LOG:
|
||||
case PHY_FUNC_CLEAR_AFFIL:
|
||||
case PHY_FUNC_TX_SATA_PS_SIGNAL:
|
||||
break;
|
||||
|
||||
default:
|
||||
resp_data[2] = SMP_RESP_PHY_UNK_OP;
|
||||
return;
|
||||
}
|
||||
|
||||
rates.minimum_linkrate = min;
|
||||
rates.maximum_linkrate = max;
|
||||
|
||||
/* filter reset requests through libata eh */
|
||||
if (phy_op == PHY_FUNC_LINK_RESET && sas_try_ata_reset(asd_phy) == 0) {
|
||||
resp_data[2] = SMP_RESP_FUNC_ACC;
|
||||
return;
|
||||
}
|
||||
|
||||
if (i->dft->lldd_control_phy(asd_phy, phy_op, &rates))
|
||||
resp_data[2] = SMP_RESP_FUNC_FAILED;
|
||||
else
|
||||
resp_data[2] = SMP_RESP_FUNC_ACC;
|
||||
}
|
||||
|
||||
int sas_smp_host_handler(struct Scsi_Host *shost, struct request *req,
|
||||
struct request *rsp)
|
||||
{
|
||||
u8 *req_data = NULL, *resp_data = NULL, *buf;
|
||||
struct sas_ha_struct *sas_ha = SHOST_TO_SAS_HA(shost);
|
||||
int error = -EINVAL;
|
||||
|
||||
/* eight is the minimum size for request and response frames */
|
||||
if (blk_rq_bytes(req) < 8 || blk_rq_bytes(rsp) < 8)
|
||||
goto out;
|
||||
|
||||
if (bio_offset(req->bio) + blk_rq_bytes(req) > PAGE_SIZE ||
|
||||
bio_offset(rsp->bio) + blk_rq_bytes(rsp) > PAGE_SIZE) {
|
||||
shost_printk(KERN_ERR, shost,
|
||||
"SMP request/response frame crosses page boundary");
|
||||
goto out;
|
||||
}
|
||||
|
||||
req_data = kzalloc(blk_rq_bytes(req), GFP_KERNEL);
|
||||
|
||||
/* make sure frame can always be built ... we copy
|
||||
* back only the requested length */
|
||||
resp_data = kzalloc(max(blk_rq_bytes(rsp), 128U), GFP_KERNEL);
|
||||
|
||||
if (!req_data || !resp_data) {
|
||||
error = -ENOMEM;
|
||||
goto out;
|
||||
}
|
||||
|
||||
local_irq_disable();
|
||||
buf = kmap_atomic(bio_page(req->bio));
|
||||
memcpy(req_data, buf, blk_rq_bytes(req));
|
||||
kunmap_atomic(buf - bio_offset(req->bio));
|
||||
local_irq_enable();
|
||||
|
||||
if (req_data[0] != SMP_REQUEST)
|
||||
goto out;
|
||||
|
||||
/* always succeeds ... even if we can't process the request
|
||||
* the result is in the response frame */
|
||||
error = 0;
|
||||
|
||||
/* set up default don't know response */
|
||||
resp_data[0] = SMP_RESPONSE;
|
||||
resp_data[1] = req_data[1];
|
||||
resp_data[2] = SMP_RESP_FUNC_UNK;
|
||||
|
||||
switch (req_data[1]) {
|
||||
case SMP_REPORT_GENERAL:
|
||||
req->resid_len -= 8;
|
||||
rsp->resid_len -= 32;
|
||||
resp_data[2] = SMP_RESP_FUNC_ACC;
|
||||
resp_data[9] = sas_ha->num_phys;
|
||||
break;
|
||||
|
||||
case SMP_REPORT_MANUF_INFO:
|
||||
req->resid_len -= 8;
|
||||
rsp->resid_len -= 64;
|
||||
resp_data[2] = SMP_RESP_FUNC_ACC;
|
||||
memcpy(resp_data + 12, shost->hostt->name,
|
||||
SAS_EXPANDER_VENDOR_ID_LEN);
|
||||
memcpy(resp_data + 20, "libsas virt phy",
|
||||
SAS_EXPANDER_PRODUCT_ID_LEN);
|
||||
break;
|
||||
|
||||
case SMP_READ_GPIO_REG:
|
||||
/* FIXME: need GPIO support in the transport class */
|
||||
break;
|
||||
|
||||
case SMP_DISCOVER:
|
||||
req->resid_len -= 16;
|
||||
if ((int)req->resid_len < 0) {
|
||||
req->resid_len = 0;
|
||||
error = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
rsp->resid_len -= 56;
|
||||
sas_host_smp_discover(sas_ha, resp_data, req_data[9]);
|
||||
break;
|
||||
|
||||
case SMP_REPORT_PHY_ERR_LOG:
|
||||
/* FIXME: could implement this with additional
|
||||
* libsas callbacks providing the HW supports it */
|
||||
break;
|
||||
|
||||
case SMP_REPORT_PHY_SATA:
|
||||
req->resid_len -= 16;
|
||||
if ((int)req->resid_len < 0) {
|
||||
req->resid_len = 0;
|
||||
error = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
rsp->resid_len -= 60;
|
||||
sas_report_phy_sata(sas_ha, resp_data, req_data[9]);
|
||||
break;
|
||||
|
||||
case SMP_REPORT_ROUTE_INFO:
|
||||
/* Can't implement; hosts have no routes */
|
||||
break;
|
||||
|
||||
case SMP_WRITE_GPIO_REG: {
|
||||
/* SFF-8485 v0.7 */
|
||||
const int base_frame_size = 11;
|
||||
int to_write = req_data[4];
|
||||
|
||||
if (blk_rq_bytes(req) < base_frame_size + to_write * 4 ||
|
||||
req->resid_len < base_frame_size + to_write * 4) {
|
||||
resp_data[2] = SMP_RESP_INV_FRM_LEN;
|
||||
break;
|
||||
}
|
||||
|
||||
to_write = sas_host_smp_write_gpio(sas_ha, resp_data, req_data[2],
|
||||
req_data[3], to_write, &req_data[8]);
|
||||
req->resid_len -= base_frame_size + to_write * 4;
|
||||
rsp->resid_len -= 8;
|
||||
break;
|
||||
}
|
||||
|
||||
case SMP_CONF_ROUTE_INFO:
|
||||
/* Can't implement; hosts have no routes */
|
||||
break;
|
||||
|
||||
case SMP_PHY_CONTROL:
|
||||
req->resid_len -= 44;
|
||||
if ((int)req->resid_len < 0) {
|
||||
req->resid_len = 0;
|
||||
error = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
rsp->resid_len -= 8;
|
||||
sas_phy_control(sas_ha, req_data[9], req_data[10],
|
||||
req_data[32] >> 4, req_data[33] >> 4,
|
||||
resp_data);
|
||||
break;
|
||||
|
||||
case SMP_PHY_TEST_FUNCTION:
|
||||
/* FIXME: should this be implemented? */
|
||||
break;
|
||||
|
||||
default:
|
||||
/* probably a 2.0 function */
|
||||
break;
|
||||
}
|
||||
|
||||
local_irq_disable();
|
||||
buf = kmap_atomic(bio_page(rsp->bio));
|
||||
memcpy(buf, resp_data, blk_rq_bytes(rsp));
|
||||
flush_kernel_dcache_page(bio_page(rsp->bio));
|
||||
kunmap_atomic(buf - bio_offset(rsp->bio));
|
||||
local_irq_enable();
|
||||
|
||||
out:
|
||||
kfree(req_data);
|
||||
kfree(resp_data);
|
||||
return error;
|
||||
}
|
622
drivers/scsi/libsas/sas_init.c
Normal file
622
drivers/scsi/libsas/sas_init.c
Normal file
|
@ -0,0 +1,622 @@
|
|||
/*
|
||||
* Serial Attached SCSI (SAS) Transport Layer initialization
|
||||
*
|
||||
* Copyright (C) 2005 Adaptec, Inc. All rights reserved.
|
||||
* Copyright (C) 2005 Luben Tuikov <luben_tuikov@adaptec.com>
|
||||
*
|
||||
* This file is licensed under GPLv2.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation; either version 2 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
|
||||
* USA
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/spinlock.h>
|
||||
#include <scsi/sas_ata.h>
|
||||
#include <scsi/scsi_host.h>
|
||||
#include <scsi/scsi_device.h>
|
||||
#include <scsi/scsi_transport.h>
|
||||
#include <scsi/scsi_transport_sas.h>
|
||||
|
||||
#include "sas_internal.h"
|
||||
|
||||
#include "../scsi_sas_internal.h"
|
||||
|
||||
static struct kmem_cache *sas_task_cache;
|
||||
|
||||
struct sas_task *sas_alloc_task(gfp_t flags)
|
||||
{
|
||||
struct sas_task *task = kmem_cache_zalloc(sas_task_cache, flags);
|
||||
|
||||
if (task) {
|
||||
INIT_LIST_HEAD(&task->list);
|
||||
spin_lock_init(&task->task_state_lock);
|
||||
task->task_state_flags = SAS_TASK_STATE_PENDING;
|
||||
}
|
||||
|
||||
return task;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(sas_alloc_task);
|
||||
|
||||
struct sas_task *sas_alloc_slow_task(gfp_t flags)
|
||||
{
|
||||
struct sas_task *task = sas_alloc_task(flags);
|
||||
struct sas_task_slow *slow = kmalloc(sizeof(*slow), flags);
|
||||
|
||||
if (!task || !slow) {
|
||||
if (task)
|
||||
kmem_cache_free(sas_task_cache, task);
|
||||
kfree(slow);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
task->slow_task = slow;
|
||||
init_timer(&slow->timer);
|
||||
init_completion(&slow->completion);
|
||||
|
||||
return task;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(sas_alloc_slow_task);
|
||||
|
||||
void sas_free_task(struct sas_task *task)
|
||||
{
|
||||
if (task) {
|
||||
BUG_ON(!list_empty(&task->list));
|
||||
kfree(task->slow_task);
|
||||
kmem_cache_free(sas_task_cache, task);
|
||||
}
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(sas_free_task);
|
||||
|
||||
/*------------ SAS addr hash -----------*/
|
||||
void sas_hash_addr(u8 *hashed, const u8 *sas_addr)
|
||||
{
|
||||
const u32 poly = 0x00DB2777;
|
||||
u32 r = 0;
|
||||
int i;
|
||||
|
||||
for (i = 0; i < 8; i++) {
|
||||
int b;
|
||||
for (b = 7; b >= 0; b--) {
|
||||
r <<= 1;
|
||||
if ((1 << b) & sas_addr[i]) {
|
||||
if (!(r & 0x01000000))
|
||||
r ^= poly;
|
||||
} else if (r & 0x01000000)
|
||||
r ^= poly;
|
||||
}
|
||||
}
|
||||
|
||||
hashed[0] = (r >> 16) & 0xFF;
|
||||
hashed[1] = (r >> 8) & 0xFF ;
|
||||
hashed[2] = r & 0xFF;
|
||||
}
|
||||
|
||||
|
||||
/* ---------- HA events ---------- */
|
||||
|
||||
void sas_hae_reset(struct work_struct *work)
|
||||
{
|
||||
struct sas_ha_event *ev = to_sas_ha_event(work);
|
||||
struct sas_ha_struct *ha = ev->ha;
|
||||
|
||||
clear_bit(HAE_RESET, &ha->pending);
|
||||
}
|
||||
|
||||
int sas_register_ha(struct sas_ha_struct *sas_ha)
|
||||
{
|
||||
int error = 0;
|
||||
|
||||
mutex_init(&sas_ha->disco_mutex);
|
||||
spin_lock_init(&sas_ha->phy_port_lock);
|
||||
sas_hash_addr(sas_ha->hashed_sas_addr, sas_ha->sas_addr);
|
||||
|
||||
if (sas_ha->lldd_queue_size == 0)
|
||||
sas_ha->lldd_queue_size = 1;
|
||||
else if (sas_ha->lldd_queue_size == -1)
|
||||
sas_ha->lldd_queue_size = 128; /* Sanity */
|
||||
|
||||
set_bit(SAS_HA_REGISTERED, &sas_ha->state);
|
||||
spin_lock_init(&sas_ha->lock);
|
||||
mutex_init(&sas_ha->drain_mutex);
|
||||
init_waitqueue_head(&sas_ha->eh_wait_q);
|
||||
INIT_LIST_HEAD(&sas_ha->defer_q);
|
||||
INIT_LIST_HEAD(&sas_ha->eh_dev_q);
|
||||
|
||||
error = sas_register_phys(sas_ha);
|
||||
if (error) {
|
||||
printk(KERN_NOTICE "couldn't register sas phys:%d\n", error);
|
||||
return error;
|
||||
}
|
||||
|
||||
error = sas_register_ports(sas_ha);
|
||||
if (error) {
|
||||
printk(KERN_NOTICE "couldn't register sas ports:%d\n", error);
|
||||
goto Undo_phys;
|
||||
}
|
||||
|
||||
error = sas_init_events(sas_ha);
|
||||
if (error) {
|
||||
printk(KERN_NOTICE "couldn't start event thread:%d\n", error);
|
||||
goto Undo_ports;
|
||||
}
|
||||
|
||||
if (sas_ha->lldd_max_execute_num > 1) {
|
||||
error = sas_init_queue(sas_ha);
|
||||
if (error) {
|
||||
printk(KERN_NOTICE "couldn't start queue thread:%d, "
|
||||
"running in direct mode\n", error);
|
||||
sas_ha->lldd_max_execute_num = 1;
|
||||
}
|
||||
}
|
||||
|
||||
INIT_LIST_HEAD(&sas_ha->eh_done_q);
|
||||
INIT_LIST_HEAD(&sas_ha->eh_ata_q);
|
||||
|
||||
return 0;
|
||||
|
||||
Undo_ports:
|
||||
sas_unregister_ports(sas_ha);
|
||||
Undo_phys:
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
static void sas_disable_events(struct sas_ha_struct *sas_ha)
|
||||
{
|
||||
/* Set the state to unregistered to avoid further unchained
|
||||
* events to be queued, and flush any in-progress drainers
|
||||
*/
|
||||
mutex_lock(&sas_ha->drain_mutex);
|
||||
spin_lock_irq(&sas_ha->lock);
|
||||
clear_bit(SAS_HA_REGISTERED, &sas_ha->state);
|
||||
spin_unlock_irq(&sas_ha->lock);
|
||||
__sas_drain_work(sas_ha);
|
||||
mutex_unlock(&sas_ha->drain_mutex);
|
||||
}
|
||||
|
||||
int sas_unregister_ha(struct sas_ha_struct *sas_ha)
|
||||
{
|
||||
sas_disable_events(sas_ha);
|
||||
sas_unregister_ports(sas_ha);
|
||||
|
||||
/* flush unregistration work */
|
||||
mutex_lock(&sas_ha->drain_mutex);
|
||||
__sas_drain_work(sas_ha);
|
||||
mutex_unlock(&sas_ha->drain_mutex);
|
||||
|
||||
if (sas_ha->lldd_max_execute_num > 1) {
|
||||
sas_shutdown_queue(sas_ha);
|
||||
sas_ha->lldd_max_execute_num = 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int sas_get_linkerrors(struct sas_phy *phy)
|
||||
{
|
||||
if (scsi_is_sas_phy_local(phy)) {
|
||||
struct Scsi_Host *shost = dev_to_shost(phy->dev.parent);
|
||||
struct sas_ha_struct *sas_ha = SHOST_TO_SAS_HA(shost);
|
||||
struct asd_sas_phy *asd_phy = sas_ha->sas_phy[phy->number];
|
||||
struct sas_internal *i =
|
||||
to_sas_internal(sas_ha->core.shost->transportt);
|
||||
|
||||
return i->dft->lldd_control_phy(asd_phy, PHY_FUNC_GET_EVENTS, NULL);
|
||||
}
|
||||
|
||||
return sas_smp_get_phy_events(phy);
|
||||
}
|
||||
|
||||
int sas_try_ata_reset(struct asd_sas_phy *asd_phy)
|
||||
{
|
||||
struct domain_device *dev = NULL;
|
||||
|
||||
/* try to route user requested link resets through libata */
|
||||
if (asd_phy->port)
|
||||
dev = asd_phy->port->port_dev;
|
||||
|
||||
/* validate that dev has been probed */
|
||||
if (dev)
|
||||
dev = sas_find_dev_by_rphy(dev->rphy);
|
||||
|
||||
if (dev && dev_is_sata(dev)) {
|
||||
sas_ata_schedule_reset(dev);
|
||||
sas_ata_wait_eh(dev);
|
||||
return 0;
|
||||
}
|
||||
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
/**
|
||||
* transport_sas_phy_reset - reset a phy and permit libata to manage the link
|
||||
*
|
||||
* phy reset request via sysfs in host workqueue context so we know we
|
||||
* can block on eh and safely traverse the domain_device topology
|
||||
*/
|
||||
static int transport_sas_phy_reset(struct sas_phy *phy, int hard_reset)
|
||||
{
|
||||
enum phy_func reset_type;
|
||||
|
||||
if (hard_reset)
|
||||
reset_type = PHY_FUNC_HARD_RESET;
|
||||
else
|
||||
reset_type = PHY_FUNC_LINK_RESET;
|
||||
|
||||
if (scsi_is_sas_phy_local(phy)) {
|
||||
struct Scsi_Host *shost = dev_to_shost(phy->dev.parent);
|
||||
struct sas_ha_struct *sas_ha = SHOST_TO_SAS_HA(shost);
|
||||
struct asd_sas_phy *asd_phy = sas_ha->sas_phy[phy->number];
|
||||
struct sas_internal *i =
|
||||
to_sas_internal(sas_ha->core.shost->transportt);
|
||||
|
||||
if (!hard_reset && sas_try_ata_reset(asd_phy) == 0)
|
||||
return 0;
|
||||
return i->dft->lldd_control_phy(asd_phy, reset_type, NULL);
|
||||
} else {
|
||||
struct sas_rphy *rphy = dev_to_rphy(phy->dev.parent);
|
||||
struct domain_device *ddev = sas_find_dev_by_rphy(rphy);
|
||||
struct domain_device *ata_dev = sas_ex_to_ata(ddev, phy->number);
|
||||
|
||||
if (ata_dev && !hard_reset) {
|
||||
sas_ata_schedule_reset(ata_dev);
|
||||
sas_ata_wait_eh(ata_dev);
|
||||
return 0;
|
||||
} else
|
||||
return sas_smp_phy_control(ddev, phy->number, reset_type, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
static int sas_phy_enable(struct sas_phy *phy, int enable)
|
||||
{
|
||||
int ret;
|
||||
enum phy_func cmd;
|
||||
|
||||
if (enable)
|
||||
cmd = PHY_FUNC_LINK_RESET;
|
||||
else
|
||||
cmd = PHY_FUNC_DISABLE;
|
||||
|
||||
if (scsi_is_sas_phy_local(phy)) {
|
||||
struct Scsi_Host *shost = dev_to_shost(phy->dev.parent);
|
||||
struct sas_ha_struct *sas_ha = SHOST_TO_SAS_HA(shost);
|
||||
struct asd_sas_phy *asd_phy = sas_ha->sas_phy[phy->number];
|
||||
struct sas_internal *i =
|
||||
to_sas_internal(sas_ha->core.shost->transportt);
|
||||
|
||||
if (enable)
|
||||
ret = transport_sas_phy_reset(phy, 0);
|
||||
else
|
||||
ret = i->dft->lldd_control_phy(asd_phy, cmd, NULL);
|
||||
} else {
|
||||
struct sas_rphy *rphy = dev_to_rphy(phy->dev.parent);
|
||||
struct domain_device *ddev = sas_find_dev_by_rphy(rphy);
|
||||
|
||||
if (enable)
|
||||
ret = transport_sas_phy_reset(phy, 0);
|
||||
else
|
||||
ret = sas_smp_phy_control(ddev, phy->number, cmd, NULL);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
int sas_phy_reset(struct sas_phy *phy, int hard_reset)
|
||||
{
|
||||
int ret;
|
||||
enum phy_func reset_type;
|
||||
|
||||
if (!phy->enabled)
|
||||
return -ENODEV;
|
||||
|
||||
if (hard_reset)
|
||||
reset_type = PHY_FUNC_HARD_RESET;
|
||||
else
|
||||
reset_type = PHY_FUNC_LINK_RESET;
|
||||
|
||||
if (scsi_is_sas_phy_local(phy)) {
|
||||
struct Scsi_Host *shost = dev_to_shost(phy->dev.parent);
|
||||
struct sas_ha_struct *sas_ha = SHOST_TO_SAS_HA(shost);
|
||||
struct asd_sas_phy *asd_phy = sas_ha->sas_phy[phy->number];
|
||||
struct sas_internal *i =
|
||||
to_sas_internal(sas_ha->core.shost->transportt);
|
||||
|
||||
ret = i->dft->lldd_control_phy(asd_phy, reset_type, NULL);
|
||||
} else {
|
||||
struct sas_rphy *rphy = dev_to_rphy(phy->dev.parent);
|
||||
struct domain_device *ddev = sas_find_dev_by_rphy(rphy);
|
||||
ret = sas_smp_phy_control(ddev, phy->number, reset_type, NULL);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
int sas_set_phy_speed(struct sas_phy *phy,
|
||||
struct sas_phy_linkrates *rates)
|
||||
{
|
||||
int ret;
|
||||
|
||||
if ((rates->minimum_linkrate &&
|
||||
rates->minimum_linkrate > phy->maximum_linkrate) ||
|
||||
(rates->maximum_linkrate &&
|
||||
rates->maximum_linkrate < phy->minimum_linkrate))
|
||||
return -EINVAL;
|
||||
|
||||
if (rates->minimum_linkrate &&
|
||||
rates->minimum_linkrate < phy->minimum_linkrate_hw)
|
||||
rates->minimum_linkrate = phy->minimum_linkrate_hw;
|
||||
|
||||
if (rates->maximum_linkrate &&
|
||||
rates->maximum_linkrate > phy->maximum_linkrate_hw)
|
||||
rates->maximum_linkrate = phy->maximum_linkrate_hw;
|
||||
|
||||
if (scsi_is_sas_phy_local(phy)) {
|
||||
struct Scsi_Host *shost = dev_to_shost(phy->dev.parent);
|
||||
struct sas_ha_struct *sas_ha = SHOST_TO_SAS_HA(shost);
|
||||
struct asd_sas_phy *asd_phy = sas_ha->sas_phy[phy->number];
|
||||
struct sas_internal *i =
|
||||
to_sas_internal(sas_ha->core.shost->transportt);
|
||||
|
||||
ret = i->dft->lldd_control_phy(asd_phy, PHY_FUNC_SET_LINK_RATE,
|
||||
rates);
|
||||
} else {
|
||||
struct sas_rphy *rphy = dev_to_rphy(phy->dev.parent);
|
||||
struct domain_device *ddev = sas_find_dev_by_rphy(rphy);
|
||||
ret = sas_smp_phy_control(ddev, phy->number,
|
||||
PHY_FUNC_LINK_RESET, rates);
|
||||
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void sas_prep_resume_ha(struct sas_ha_struct *ha)
|
||||
{
|
||||
int i;
|
||||
|
||||
set_bit(SAS_HA_REGISTERED, &ha->state);
|
||||
|
||||
/* clear out any stale link events/data from the suspension path */
|
||||
for (i = 0; i < ha->num_phys; i++) {
|
||||
struct asd_sas_phy *phy = ha->sas_phy[i];
|
||||
|
||||
memset(phy->attached_sas_addr, 0, SAS_ADDR_SIZE);
|
||||
phy->port_events_pending = 0;
|
||||
phy->phy_events_pending = 0;
|
||||
phy->frame_rcvd_size = 0;
|
||||
}
|
||||
}
|
||||
EXPORT_SYMBOL(sas_prep_resume_ha);
|
||||
|
||||
static int phys_suspended(struct sas_ha_struct *ha)
|
||||
{
|
||||
int i, rc = 0;
|
||||
|
||||
for (i = 0; i < ha->num_phys; i++) {
|
||||
struct asd_sas_phy *phy = ha->sas_phy[i];
|
||||
|
||||
if (phy->suspended)
|
||||
rc++;
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
void sas_resume_ha(struct sas_ha_struct *ha)
|
||||
{
|
||||
const unsigned long tmo = msecs_to_jiffies(25000);
|
||||
int i;
|
||||
|
||||
/* deform ports on phys that did not resume
|
||||
* at this point we may be racing the phy coming back (as posted
|
||||
* by the lldd). So we post the event and once we are in the
|
||||
* libsas context check that the phy remains suspended before
|
||||
* tearing it down.
|
||||
*/
|
||||
i = phys_suspended(ha);
|
||||
if (i)
|
||||
dev_info(ha->dev, "waiting up to 25 seconds for %d phy%s to resume\n",
|
||||
i, i > 1 ? "s" : "");
|
||||
wait_event_timeout(ha->eh_wait_q, phys_suspended(ha) == 0, tmo);
|
||||
for (i = 0; i < ha->num_phys; i++) {
|
||||
struct asd_sas_phy *phy = ha->sas_phy[i];
|
||||
|
||||
if (phy->suspended) {
|
||||
dev_warn(&phy->phy->dev, "resume timeout\n");
|
||||
sas_notify_phy_event(phy, PHYE_RESUME_TIMEOUT);
|
||||
}
|
||||
}
|
||||
|
||||
/* all phys are back up or timed out, turn on i/o so we can
|
||||
* flush out disks that did not return
|
||||
*/
|
||||
scsi_unblock_requests(ha->core.shost);
|
||||
sas_drain_work(ha);
|
||||
}
|
||||
EXPORT_SYMBOL(sas_resume_ha);
|
||||
|
||||
void sas_suspend_ha(struct sas_ha_struct *ha)
|
||||
{
|
||||
int i;
|
||||
|
||||
sas_disable_events(ha);
|
||||
scsi_block_requests(ha->core.shost);
|
||||
for (i = 0; i < ha->num_phys; i++) {
|
||||
struct asd_sas_port *port = ha->sas_port[i];
|
||||
|
||||
sas_discover_event(port, DISCE_SUSPEND);
|
||||
}
|
||||
|
||||
/* flush suspend events while unregistered */
|
||||
mutex_lock(&ha->drain_mutex);
|
||||
__sas_drain_work(ha);
|
||||
mutex_unlock(&ha->drain_mutex);
|
||||
}
|
||||
EXPORT_SYMBOL(sas_suspend_ha);
|
||||
|
||||
static void sas_phy_release(struct sas_phy *phy)
|
||||
{
|
||||
kfree(phy->hostdata);
|
||||
phy->hostdata = NULL;
|
||||
}
|
||||
|
||||
static void phy_reset_work(struct work_struct *work)
|
||||
{
|
||||
struct sas_phy_data *d = container_of(work, typeof(*d), reset_work.work);
|
||||
|
||||
d->reset_result = transport_sas_phy_reset(d->phy, d->hard_reset);
|
||||
}
|
||||
|
||||
static void phy_enable_work(struct work_struct *work)
|
||||
{
|
||||
struct sas_phy_data *d = container_of(work, typeof(*d), enable_work.work);
|
||||
|
||||
d->enable_result = sas_phy_enable(d->phy, d->enable);
|
||||
}
|
||||
|
||||
static int sas_phy_setup(struct sas_phy *phy)
|
||||
{
|
||||
struct sas_phy_data *d = kzalloc(sizeof(*d), GFP_KERNEL);
|
||||
|
||||
if (!d)
|
||||
return -ENOMEM;
|
||||
|
||||
mutex_init(&d->event_lock);
|
||||
INIT_SAS_WORK(&d->reset_work, phy_reset_work);
|
||||
INIT_SAS_WORK(&d->enable_work, phy_enable_work);
|
||||
d->phy = phy;
|
||||
phy->hostdata = d;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int queue_phy_reset(struct sas_phy *phy, int hard_reset)
|
||||
{
|
||||
struct Scsi_Host *shost = dev_to_shost(phy->dev.parent);
|
||||
struct sas_ha_struct *ha = SHOST_TO_SAS_HA(shost);
|
||||
struct sas_phy_data *d = phy->hostdata;
|
||||
int rc;
|
||||
|
||||
if (!d)
|
||||
return -ENOMEM;
|
||||
|
||||
/* libsas workqueue coordinates ata-eh reset with discovery */
|
||||
mutex_lock(&d->event_lock);
|
||||
d->reset_result = 0;
|
||||
d->hard_reset = hard_reset;
|
||||
|
||||
spin_lock_irq(&ha->lock);
|
||||
sas_queue_work(ha, &d->reset_work);
|
||||
spin_unlock_irq(&ha->lock);
|
||||
|
||||
rc = sas_drain_work(ha);
|
||||
if (rc == 0)
|
||||
rc = d->reset_result;
|
||||
mutex_unlock(&d->event_lock);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int queue_phy_enable(struct sas_phy *phy, int enable)
|
||||
{
|
||||
struct Scsi_Host *shost = dev_to_shost(phy->dev.parent);
|
||||
struct sas_ha_struct *ha = SHOST_TO_SAS_HA(shost);
|
||||
struct sas_phy_data *d = phy->hostdata;
|
||||
int rc;
|
||||
|
||||
if (!d)
|
||||
return -ENOMEM;
|
||||
|
||||
/* libsas workqueue coordinates ata-eh reset with discovery */
|
||||
mutex_lock(&d->event_lock);
|
||||
d->enable_result = 0;
|
||||
d->enable = enable;
|
||||
|
||||
spin_lock_irq(&ha->lock);
|
||||
sas_queue_work(ha, &d->enable_work);
|
||||
spin_unlock_irq(&ha->lock);
|
||||
|
||||
rc = sas_drain_work(ha);
|
||||
if (rc == 0)
|
||||
rc = d->enable_result;
|
||||
mutex_unlock(&d->event_lock);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
static struct sas_function_template sft = {
|
||||
.phy_enable = queue_phy_enable,
|
||||
.phy_reset = queue_phy_reset,
|
||||
.phy_setup = sas_phy_setup,
|
||||
.phy_release = sas_phy_release,
|
||||
.set_phy_speed = sas_set_phy_speed,
|
||||
.get_linkerrors = sas_get_linkerrors,
|
||||
.smp_handler = sas_smp_handler,
|
||||
};
|
||||
|
||||
struct scsi_transport_template *
|
||||
sas_domain_attach_transport(struct sas_domain_function_template *dft)
|
||||
{
|
||||
struct scsi_transport_template *stt = sas_attach_transport(&sft);
|
||||
struct sas_internal *i;
|
||||
|
||||
if (!stt)
|
||||
return stt;
|
||||
|
||||
i = to_sas_internal(stt);
|
||||
i->dft = dft;
|
||||
stt->create_work_queue = 1;
|
||||
stt->eh_timed_out = sas_scsi_timed_out;
|
||||
stt->eh_strategy_handler = sas_scsi_recover_host;
|
||||
|
||||
return stt;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(sas_domain_attach_transport);
|
||||
|
||||
|
||||
void sas_domain_release_transport(struct scsi_transport_template *stt)
|
||||
{
|
||||
sas_release_transport(stt);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(sas_domain_release_transport);
|
||||
|
||||
/* ---------- SAS Class register/unregister ---------- */
|
||||
|
||||
static int __init sas_class_init(void)
|
||||
{
|
||||
sas_task_cache = KMEM_CACHE(sas_task, SLAB_HWCACHE_ALIGN);
|
||||
if (!sas_task_cache)
|
||||
return -ENOMEM;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void __exit sas_class_exit(void)
|
||||
{
|
||||
kmem_cache_destroy(sas_task_cache);
|
||||
}
|
||||
|
||||
MODULE_AUTHOR("Luben Tuikov <luben_tuikov@adaptec.com>");
|
||||
MODULE_DESCRIPTION("SAS Transport Layer");
|
||||
MODULE_LICENSE("GPL v2");
|
||||
|
||||
module_init(sas_class_init);
|
||||
module_exit(sas_class_exit);
|
||||
|
||||
EXPORT_SYMBOL_GPL(sas_register_ha);
|
||||
EXPORT_SYMBOL_GPL(sas_unregister_ha);
|
202
drivers/scsi/libsas/sas_internal.h
Normal file
202
drivers/scsi/libsas/sas_internal.h
Normal file
|
@ -0,0 +1,202 @@
|
|||
/*
|
||||
* Serial Attached SCSI (SAS) class internal header file
|
||||
*
|
||||
* Copyright (C) 2005 Adaptec, Inc. All rights reserved.
|
||||
* Copyright (C) 2005 Luben Tuikov <luben_tuikov@adaptec.com>
|
||||
*
|
||||
* This file is licensed under GPLv2.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation; either version 2 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
|
||||
* USA
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef _SAS_INTERNAL_H_
|
||||
#define _SAS_INTERNAL_H_
|
||||
|
||||
#include <scsi/scsi.h>
|
||||
#include <scsi/scsi_host.h>
|
||||
#include <scsi/scsi_transport_sas.h>
|
||||
#include <scsi/libsas.h>
|
||||
#include <scsi/sas_ata.h>
|
||||
|
||||
#define sas_printk(fmt, ...) printk(KERN_NOTICE "sas: " fmt, ## __VA_ARGS__)
|
||||
|
||||
#define SAS_DPRINTK(fmt, ...) printk(KERN_DEBUG "sas: " fmt, ## __VA_ARGS__)
|
||||
|
||||
#define TO_SAS_TASK(_scsi_cmd) ((void *)(_scsi_cmd)->host_scribble)
|
||||
#define ASSIGN_SAS_TASK(_sc, _t) do { (_sc)->host_scribble = (void *) _t; } while (0)
|
||||
|
||||
struct sas_phy_data {
|
||||
/* let reset be performed in sas_queue_work() context */
|
||||
struct sas_phy *phy;
|
||||
struct mutex event_lock;
|
||||
int hard_reset;
|
||||
int reset_result;
|
||||
struct sas_work reset_work;
|
||||
int enable;
|
||||
int enable_result;
|
||||
struct sas_work enable_work;
|
||||
};
|
||||
|
||||
void sas_scsi_recover_host(struct Scsi_Host *shost);
|
||||
|
||||
int sas_show_class(enum sas_class class, char *buf);
|
||||
int sas_show_proto(enum sas_protocol proto, char *buf);
|
||||
int sas_show_linkrate(enum sas_linkrate linkrate, char *buf);
|
||||
int sas_show_oob_mode(enum sas_oob_mode oob_mode, char *buf);
|
||||
|
||||
int sas_register_phys(struct sas_ha_struct *sas_ha);
|
||||
void sas_unregister_phys(struct sas_ha_struct *sas_ha);
|
||||
|
||||
int sas_register_ports(struct sas_ha_struct *sas_ha);
|
||||
void sas_unregister_ports(struct sas_ha_struct *sas_ha);
|
||||
|
||||
enum blk_eh_timer_return sas_scsi_timed_out(struct scsi_cmnd *);
|
||||
|
||||
int sas_init_queue(struct sas_ha_struct *sas_ha);
|
||||
int sas_init_events(struct sas_ha_struct *sas_ha);
|
||||
void sas_shutdown_queue(struct sas_ha_struct *sas_ha);
|
||||
void sas_disable_revalidation(struct sas_ha_struct *ha);
|
||||
void sas_enable_revalidation(struct sas_ha_struct *ha);
|
||||
void __sas_drain_work(struct sas_ha_struct *ha);
|
||||
|
||||
void sas_deform_port(struct asd_sas_phy *phy, int gone);
|
||||
|
||||
void sas_porte_bytes_dmaed(struct work_struct *work);
|
||||
void sas_porte_broadcast_rcvd(struct work_struct *work);
|
||||
void sas_porte_link_reset_err(struct work_struct *work);
|
||||
void sas_porte_timer_event(struct work_struct *work);
|
||||
void sas_porte_hard_reset(struct work_struct *work);
|
||||
void sas_queue_work(struct sas_ha_struct *ha, struct sas_work *sw);
|
||||
|
||||
int sas_notify_lldd_dev_found(struct domain_device *);
|
||||
void sas_notify_lldd_dev_gone(struct domain_device *);
|
||||
|
||||
int sas_smp_phy_control(struct domain_device *dev, int phy_id,
|
||||
enum phy_func phy_func, struct sas_phy_linkrates *);
|
||||
int sas_smp_get_phy_events(struct sas_phy *phy);
|
||||
|
||||
void sas_notify_phy_event(struct asd_sas_phy *phy, enum phy_event event);
|
||||
void sas_device_set_phy(struct domain_device *dev, struct sas_port *port);
|
||||
struct domain_device *sas_find_dev_by_rphy(struct sas_rphy *rphy);
|
||||
struct domain_device *sas_ex_to_ata(struct domain_device *ex_dev, int phy_id);
|
||||
int sas_ex_phy_discover(struct domain_device *dev, int single);
|
||||
int sas_get_report_phy_sata(struct domain_device *dev, int phy_id,
|
||||
struct smp_resp *rps_resp);
|
||||
int sas_try_ata_reset(struct asd_sas_phy *phy);
|
||||
void sas_hae_reset(struct work_struct *work);
|
||||
|
||||
void sas_free_device(struct kref *kref);
|
||||
|
||||
#ifdef CONFIG_SCSI_SAS_HOST_SMP
|
||||
extern int sas_smp_host_handler(struct Scsi_Host *shost, struct request *req,
|
||||
struct request *rsp);
|
||||
#else
|
||||
static inline int sas_smp_host_handler(struct Scsi_Host *shost,
|
||||
struct request *req,
|
||||
struct request *rsp)
|
||||
{
|
||||
shost_printk(KERN_ERR, shost,
|
||||
"Cannot send SMP to a sas host (not enabled in CONFIG)\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
#endif
|
||||
|
||||
static inline void sas_fail_probe(struct domain_device *dev, const char *func, int err)
|
||||
{
|
||||
SAS_DPRINTK("%s: for %s device %16llx returned %d\n",
|
||||
func, dev->parent ? "exp-attached" :
|
||||
"direct-attached",
|
||||
SAS_ADDR(dev->sas_addr), err);
|
||||
sas_unregister_dev(dev->port, dev);
|
||||
}
|
||||
|
||||
static inline void sas_fill_in_rphy(struct domain_device *dev,
|
||||
struct sas_rphy *rphy)
|
||||
{
|
||||
rphy->identify.sas_address = SAS_ADDR(dev->sas_addr);
|
||||
rphy->identify.initiator_port_protocols = dev->iproto;
|
||||
rphy->identify.target_port_protocols = dev->tproto;
|
||||
switch (dev->dev_type) {
|
||||
case SAS_SATA_DEV:
|
||||
/* FIXME: need sata device type */
|
||||
case SAS_END_DEVICE:
|
||||
case SAS_SATA_PENDING:
|
||||
rphy->identify.device_type = SAS_END_DEVICE;
|
||||
break;
|
||||
case SAS_EDGE_EXPANDER_DEVICE:
|
||||
rphy->identify.device_type = SAS_EDGE_EXPANDER_DEVICE;
|
||||
break;
|
||||
case SAS_FANOUT_EXPANDER_DEVICE:
|
||||
rphy->identify.device_type = SAS_FANOUT_EXPANDER_DEVICE;
|
||||
break;
|
||||
default:
|
||||
rphy->identify.device_type = SAS_PHY_UNUSED;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static inline void sas_phy_set_target(struct asd_sas_phy *p, struct domain_device *dev)
|
||||
{
|
||||
struct sas_phy *phy = p->phy;
|
||||
|
||||
if (dev) {
|
||||
if (dev_is_sata(dev))
|
||||
phy->identify.device_type = SAS_END_DEVICE;
|
||||
else
|
||||
phy->identify.device_type = dev->dev_type;
|
||||
phy->identify.target_port_protocols = dev->tproto;
|
||||
} else {
|
||||
phy->identify.device_type = SAS_PHY_UNUSED;
|
||||
phy->identify.target_port_protocols = 0;
|
||||
}
|
||||
}
|
||||
|
||||
static inline void sas_add_parent_port(struct domain_device *dev, int phy_id)
|
||||
{
|
||||
struct expander_device *ex = &dev->ex_dev;
|
||||
struct ex_phy *ex_phy = &ex->ex_phy[phy_id];
|
||||
|
||||
if (!ex->parent_port) {
|
||||
ex->parent_port = sas_port_alloc(&dev->rphy->dev, phy_id);
|
||||
/* FIXME: error handling */
|
||||
BUG_ON(!ex->parent_port);
|
||||
BUG_ON(sas_port_add(ex->parent_port));
|
||||
sas_port_mark_backlink(ex->parent_port);
|
||||
}
|
||||
sas_port_add_phy(ex->parent_port, ex_phy->phy);
|
||||
}
|
||||
|
||||
static inline struct domain_device *sas_alloc_device(void)
|
||||
{
|
||||
struct domain_device *dev = kzalloc(sizeof(*dev), GFP_KERNEL);
|
||||
|
||||
if (dev) {
|
||||
INIT_LIST_HEAD(&dev->siblings);
|
||||
INIT_LIST_HEAD(&dev->dev_list_node);
|
||||
INIT_LIST_HEAD(&dev->disco_list_node);
|
||||
kref_init(&dev->kref);
|
||||
spin_lock_init(&dev->done_lock);
|
||||
}
|
||||
return dev;
|
||||
}
|
||||
|
||||
static inline void sas_put_device(struct domain_device *dev)
|
||||
{
|
||||
kref_put(&dev->kref, sas_free_device);
|
||||
}
|
||||
|
||||
#endif /* _SAS_INTERNAL_H_ */
|
181
drivers/scsi/libsas/sas_phy.c
Normal file
181
drivers/scsi/libsas/sas_phy.c
Normal file
|
@ -0,0 +1,181 @@
|
|||
/*
|
||||
* Serial Attached SCSI (SAS) Phy class
|
||||
*
|
||||
* Copyright (C) 2005 Adaptec, Inc. All rights reserved.
|
||||
* Copyright (C) 2005 Luben Tuikov <luben_tuikov@adaptec.com>
|
||||
*
|
||||
* This file is licensed under GPLv2.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation; either version 2 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*
|
||||
*/
|
||||
|
||||
#include "sas_internal.h"
|
||||
#include <scsi/scsi_host.h>
|
||||
#include <scsi/scsi_transport.h>
|
||||
#include <scsi/scsi_transport_sas.h>
|
||||
#include "../scsi_sas_internal.h"
|
||||
|
||||
/* ---------- Phy events ---------- */
|
||||
|
||||
static void sas_phye_loss_of_signal(struct work_struct *work)
|
||||
{
|
||||
struct asd_sas_event *ev = to_asd_sas_event(work);
|
||||
struct asd_sas_phy *phy = ev->phy;
|
||||
|
||||
clear_bit(PHYE_LOSS_OF_SIGNAL, &phy->phy_events_pending);
|
||||
phy->error = 0;
|
||||
sas_deform_port(phy, 1);
|
||||
}
|
||||
|
||||
static void sas_phye_oob_done(struct work_struct *work)
|
||||
{
|
||||
struct asd_sas_event *ev = to_asd_sas_event(work);
|
||||
struct asd_sas_phy *phy = ev->phy;
|
||||
|
||||
clear_bit(PHYE_OOB_DONE, &phy->phy_events_pending);
|
||||
phy->error = 0;
|
||||
}
|
||||
|
||||
static void sas_phye_oob_error(struct work_struct *work)
|
||||
{
|
||||
struct asd_sas_event *ev = to_asd_sas_event(work);
|
||||
struct asd_sas_phy *phy = ev->phy;
|
||||
struct sas_ha_struct *sas_ha = phy->ha;
|
||||
struct asd_sas_port *port = phy->port;
|
||||
struct sas_internal *i =
|
||||
to_sas_internal(sas_ha->core.shost->transportt);
|
||||
|
||||
clear_bit(PHYE_OOB_ERROR, &phy->phy_events_pending);
|
||||
|
||||
sas_deform_port(phy, 1);
|
||||
|
||||
if (!port && phy->enabled && i->dft->lldd_control_phy) {
|
||||
phy->error++;
|
||||
switch (phy->error) {
|
||||
case 1:
|
||||
case 2:
|
||||
i->dft->lldd_control_phy(phy, PHY_FUNC_HARD_RESET,
|
||||
NULL);
|
||||
break;
|
||||
case 3:
|
||||
default:
|
||||
phy->error = 0;
|
||||
phy->enabled = 0;
|
||||
i->dft->lldd_control_phy(phy, PHY_FUNC_DISABLE, NULL);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void sas_phye_spinup_hold(struct work_struct *work)
|
||||
{
|
||||
struct asd_sas_event *ev = to_asd_sas_event(work);
|
||||
struct asd_sas_phy *phy = ev->phy;
|
||||
struct sas_ha_struct *sas_ha = phy->ha;
|
||||
struct sas_internal *i =
|
||||
to_sas_internal(sas_ha->core.shost->transportt);
|
||||
|
||||
clear_bit(PHYE_SPINUP_HOLD, &phy->phy_events_pending);
|
||||
|
||||
phy->error = 0;
|
||||
i->dft->lldd_control_phy(phy, PHY_FUNC_RELEASE_SPINUP_HOLD, NULL);
|
||||
}
|
||||
|
||||
static void sas_phye_resume_timeout(struct work_struct *work)
|
||||
{
|
||||
struct asd_sas_event *ev = to_asd_sas_event(work);
|
||||
struct asd_sas_phy *phy = ev->phy;
|
||||
|
||||
clear_bit(PHYE_RESUME_TIMEOUT, &phy->phy_events_pending);
|
||||
|
||||
/* phew, lldd got the phy back in the nick of time */
|
||||
if (!phy->suspended) {
|
||||
dev_info(&phy->phy->dev, "resume timeout cancelled\n");
|
||||
return;
|
||||
}
|
||||
|
||||
phy->error = 0;
|
||||
phy->suspended = 0;
|
||||
sas_deform_port(phy, 1);
|
||||
}
|
||||
|
||||
|
||||
/* ---------- Phy class registration ---------- */
|
||||
|
||||
int sas_register_phys(struct sas_ha_struct *sas_ha)
|
||||
{
|
||||
int i;
|
||||
|
||||
static const work_func_t sas_phy_event_fns[PHY_NUM_EVENTS] = {
|
||||
[PHYE_LOSS_OF_SIGNAL] = sas_phye_loss_of_signal,
|
||||
[PHYE_OOB_DONE] = sas_phye_oob_done,
|
||||
[PHYE_OOB_ERROR] = sas_phye_oob_error,
|
||||
[PHYE_SPINUP_HOLD] = sas_phye_spinup_hold,
|
||||
[PHYE_RESUME_TIMEOUT] = sas_phye_resume_timeout,
|
||||
|
||||
};
|
||||
|
||||
static const work_func_t sas_port_event_fns[PORT_NUM_EVENTS] = {
|
||||
[PORTE_BYTES_DMAED] = sas_porte_bytes_dmaed,
|
||||
[PORTE_BROADCAST_RCVD] = sas_porte_broadcast_rcvd,
|
||||
[PORTE_LINK_RESET_ERR] = sas_porte_link_reset_err,
|
||||
[PORTE_TIMER_EVENT] = sas_porte_timer_event,
|
||||
[PORTE_HARD_RESET] = sas_porte_hard_reset,
|
||||
};
|
||||
|
||||
/* Now register the phys. */
|
||||
for (i = 0; i < sas_ha->num_phys; i++) {
|
||||
int k;
|
||||
struct asd_sas_phy *phy = sas_ha->sas_phy[i];
|
||||
|
||||
phy->error = 0;
|
||||
INIT_LIST_HEAD(&phy->port_phy_el);
|
||||
for (k = 0; k < PORT_NUM_EVENTS; k++) {
|
||||
INIT_SAS_WORK(&phy->port_events[k].work, sas_port_event_fns[k]);
|
||||
phy->port_events[k].phy = phy;
|
||||
}
|
||||
|
||||
for (k = 0; k < PHY_NUM_EVENTS; k++) {
|
||||
INIT_SAS_WORK(&phy->phy_events[k].work, sas_phy_event_fns[k]);
|
||||
phy->phy_events[k].phy = phy;
|
||||
}
|
||||
|
||||
phy->port = NULL;
|
||||
phy->ha = sas_ha;
|
||||
spin_lock_init(&phy->frame_rcvd_lock);
|
||||
spin_lock_init(&phy->sas_prim_lock);
|
||||
phy->frame_rcvd_size = 0;
|
||||
|
||||
phy->phy = sas_phy_alloc(&sas_ha->core.shost->shost_gendev, i);
|
||||
if (!phy->phy)
|
||||
return -ENOMEM;
|
||||
|
||||
phy->phy->identify.initiator_port_protocols =
|
||||
phy->iproto;
|
||||
phy->phy->identify.target_port_protocols = phy->tproto;
|
||||
phy->phy->identify.sas_address = SAS_ADDR(sas_ha->sas_addr);
|
||||
phy->phy->identify.phy_identifier = i;
|
||||
phy->phy->minimum_linkrate_hw = SAS_LINK_RATE_UNKNOWN;
|
||||
phy->phy->maximum_linkrate_hw = SAS_LINK_RATE_UNKNOWN;
|
||||
phy->phy->minimum_linkrate = SAS_LINK_RATE_UNKNOWN;
|
||||
phy->phy->maximum_linkrate = SAS_LINK_RATE_UNKNOWN;
|
||||
phy->phy->negotiated_linkrate = SAS_LINK_RATE_UNKNOWN;
|
||||
|
||||
sas_phy_add(phy->phy);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
355
drivers/scsi/libsas/sas_port.c
Normal file
355
drivers/scsi/libsas/sas_port.c
Normal file
|
@ -0,0 +1,355 @@
|
|||
/*
|
||||
* Serial Attached SCSI (SAS) Port class
|
||||
*
|
||||
* Copyright (C) 2005 Adaptec, Inc. All rights reserved.
|
||||
* Copyright (C) 2005 Luben Tuikov <luben_tuikov@adaptec.com>
|
||||
*
|
||||
* This file is licensed under GPLv2.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation; either version 2 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*
|
||||
*/
|
||||
|
||||
#include "sas_internal.h"
|
||||
|
||||
#include <scsi/scsi_transport.h>
|
||||
#include <scsi/scsi_transport_sas.h>
|
||||
#include "../scsi_sas_internal.h"
|
||||
|
||||
static bool phy_is_wideport_member(struct asd_sas_port *port, struct asd_sas_phy *phy)
|
||||
{
|
||||
struct sas_ha_struct *sas_ha = phy->ha;
|
||||
|
||||
if (memcmp(port->attached_sas_addr, phy->attached_sas_addr,
|
||||
SAS_ADDR_SIZE) != 0 || (sas_ha->strict_wide_ports &&
|
||||
memcmp(port->sas_addr, phy->sas_addr, SAS_ADDR_SIZE) != 0))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
static void sas_resume_port(struct asd_sas_phy *phy)
|
||||
{
|
||||
struct domain_device *dev;
|
||||
struct asd_sas_port *port = phy->port;
|
||||
struct sas_ha_struct *sas_ha = phy->ha;
|
||||
struct sas_internal *si = to_sas_internal(sas_ha->core.shost->transportt);
|
||||
|
||||
if (si->dft->lldd_port_formed)
|
||||
si->dft->lldd_port_formed(phy);
|
||||
|
||||
if (port->suspended)
|
||||
port->suspended = 0;
|
||||
else {
|
||||
/* we only need to handle "link returned" actions once */
|
||||
return;
|
||||
}
|
||||
|
||||
/* if the port came back:
|
||||
* 1/ presume every device came back
|
||||
* 2/ force the next revalidation to check all expander phys
|
||||
*/
|
||||
list_for_each_entry(dev, &port->dev_list, dev_list_node) {
|
||||
int i, rc;
|
||||
|
||||
rc = sas_notify_lldd_dev_found(dev);
|
||||
if (rc) {
|
||||
sas_unregister_dev(port, dev);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (dev->dev_type == SAS_EDGE_EXPANDER_DEVICE || dev->dev_type == SAS_FANOUT_EXPANDER_DEVICE) {
|
||||
dev->ex_dev.ex_change_count = -1;
|
||||
for (i = 0; i < dev->ex_dev.num_phys; i++) {
|
||||
struct ex_phy *phy = &dev->ex_dev.ex_phy[i];
|
||||
|
||||
phy->phy_change_count = -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sas_discover_event(port, DISCE_RESUME);
|
||||
}
|
||||
|
||||
/**
|
||||
* sas_form_port -- add this phy to a port
|
||||
* @phy: the phy of interest
|
||||
*
|
||||
* This function adds this phy to an existing port, thus creating a wide
|
||||
* port, or it creates a port and adds the phy to the port.
|
||||
*/
|
||||
static void sas_form_port(struct asd_sas_phy *phy)
|
||||
{
|
||||
int i;
|
||||
struct sas_ha_struct *sas_ha = phy->ha;
|
||||
struct asd_sas_port *port = phy->port;
|
||||
struct sas_internal *si =
|
||||
to_sas_internal(sas_ha->core.shost->transportt);
|
||||
unsigned long flags;
|
||||
|
||||
if (port) {
|
||||
if (!phy_is_wideport_member(port, phy))
|
||||
sas_deform_port(phy, 0);
|
||||
else if (phy->suspended) {
|
||||
phy->suspended = 0;
|
||||
sas_resume_port(phy);
|
||||
|
||||
/* phy came back, try to cancel the timeout */
|
||||
wake_up(&sas_ha->eh_wait_q);
|
||||
return;
|
||||
} else {
|
||||
SAS_DPRINTK("%s: phy%d belongs to port%d already(%d)!\n",
|
||||
__func__, phy->id, phy->port->id,
|
||||
phy->port->num_phys);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/* see if the phy should be part of a wide port */
|
||||
spin_lock_irqsave(&sas_ha->phy_port_lock, flags);
|
||||
for (i = 0; i < sas_ha->num_phys; i++) {
|
||||
port = sas_ha->sas_port[i];
|
||||
spin_lock(&port->phy_list_lock);
|
||||
if (*(u64 *) port->sas_addr &&
|
||||
phy_is_wideport_member(port, phy) && port->num_phys > 0) {
|
||||
/* wide port */
|
||||
SAS_DPRINTK("phy%d matched wide port%d\n", phy->id,
|
||||
port->id);
|
||||
break;
|
||||
}
|
||||
spin_unlock(&port->phy_list_lock);
|
||||
}
|
||||
/* The phy does not match any existing port, create a new one */
|
||||
if (i == sas_ha->num_phys) {
|
||||
for (i = 0; i < sas_ha->num_phys; i++) {
|
||||
port = sas_ha->sas_port[i];
|
||||
spin_lock(&port->phy_list_lock);
|
||||
if (*(u64 *)port->sas_addr == 0
|
||||
&& port->num_phys == 0) {
|
||||
memcpy(port->sas_addr, phy->sas_addr,
|
||||
SAS_ADDR_SIZE);
|
||||
break;
|
||||
}
|
||||
spin_unlock(&port->phy_list_lock);
|
||||
}
|
||||
}
|
||||
|
||||
if (i >= sas_ha->num_phys) {
|
||||
printk(KERN_NOTICE "%s: couldn't find a free port, bug?\n",
|
||||
__func__);
|
||||
spin_unlock_irqrestore(&sas_ha->phy_port_lock, flags);
|
||||
return;
|
||||
}
|
||||
|
||||
/* add the phy to the port */
|
||||
list_add_tail(&phy->port_phy_el, &port->phy_list);
|
||||
sas_phy_set_target(phy, port->port_dev);
|
||||
phy->port = port;
|
||||
port->num_phys++;
|
||||
port->phy_mask |= (1U << phy->id);
|
||||
|
||||
if (*(u64 *)port->attached_sas_addr == 0) {
|
||||
port->class = phy->class;
|
||||
memcpy(port->attached_sas_addr, phy->attached_sas_addr,
|
||||
SAS_ADDR_SIZE);
|
||||
port->iproto = phy->iproto;
|
||||
port->tproto = phy->tproto;
|
||||
port->oob_mode = phy->oob_mode;
|
||||
port->linkrate = phy->linkrate;
|
||||
} else
|
||||
port->linkrate = max(port->linkrate, phy->linkrate);
|
||||
spin_unlock(&port->phy_list_lock);
|
||||
spin_unlock_irqrestore(&sas_ha->phy_port_lock, flags);
|
||||
|
||||
if (!port->port) {
|
||||
port->port = sas_port_alloc(phy->phy->dev.parent, port->id);
|
||||
BUG_ON(!port->port);
|
||||
sas_port_add(port->port);
|
||||
}
|
||||
sas_port_add_phy(port->port, phy->phy);
|
||||
|
||||
SAS_DPRINTK("%s added to %s, phy_mask:0x%x (%16llx)\n",
|
||||
dev_name(&phy->phy->dev), dev_name(&port->port->dev),
|
||||
port->phy_mask,
|
||||
SAS_ADDR(port->attached_sas_addr));
|
||||
|
||||
if (port->port_dev)
|
||||
port->port_dev->pathways = port->num_phys;
|
||||
|
||||
/* Tell the LLDD about this port formation. */
|
||||
if (si->dft->lldd_port_formed)
|
||||
si->dft->lldd_port_formed(phy);
|
||||
|
||||
sas_discover_event(phy->port, DISCE_DISCOVER_DOMAIN);
|
||||
}
|
||||
|
||||
/**
|
||||
* sas_deform_port -- remove this phy from the port it belongs to
|
||||
* @phy: the phy of interest
|
||||
*
|
||||
* This is called when the physical link to the other phy has been
|
||||
* lost (on this phy), in Event thread context. We cannot delay here.
|
||||
*/
|
||||
void sas_deform_port(struct asd_sas_phy *phy, int gone)
|
||||
{
|
||||
struct sas_ha_struct *sas_ha = phy->ha;
|
||||
struct asd_sas_port *port = phy->port;
|
||||
struct sas_internal *si =
|
||||
to_sas_internal(sas_ha->core.shost->transportt);
|
||||
struct domain_device *dev;
|
||||
unsigned long flags;
|
||||
|
||||
if (!port)
|
||||
return; /* done by a phy event */
|
||||
|
||||
dev = port->port_dev;
|
||||
if (dev)
|
||||
dev->pathways--;
|
||||
|
||||
if (port->num_phys == 1) {
|
||||
sas_unregister_domain_devices(port, gone);
|
||||
sas_port_delete(port->port);
|
||||
port->port = NULL;
|
||||
} else {
|
||||
sas_port_delete_phy(port->port, phy->phy);
|
||||
sas_device_set_phy(dev, port->port);
|
||||
}
|
||||
|
||||
if (si->dft->lldd_port_deformed)
|
||||
si->dft->lldd_port_deformed(phy);
|
||||
|
||||
spin_lock_irqsave(&sas_ha->phy_port_lock, flags);
|
||||
spin_lock(&port->phy_list_lock);
|
||||
|
||||
list_del_init(&phy->port_phy_el);
|
||||
sas_phy_set_target(phy, NULL);
|
||||
phy->port = NULL;
|
||||
port->num_phys--;
|
||||
port->phy_mask &= ~(1U << phy->id);
|
||||
|
||||
if (port->num_phys == 0) {
|
||||
INIT_LIST_HEAD(&port->phy_list);
|
||||
memset(port->sas_addr, 0, SAS_ADDR_SIZE);
|
||||
memset(port->attached_sas_addr, 0, SAS_ADDR_SIZE);
|
||||
port->class = 0;
|
||||
port->iproto = 0;
|
||||
port->tproto = 0;
|
||||
port->oob_mode = 0;
|
||||
port->phy_mask = 0;
|
||||
}
|
||||
spin_unlock(&port->phy_list_lock);
|
||||
spin_unlock_irqrestore(&sas_ha->phy_port_lock, flags);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/* ---------- SAS port events ---------- */
|
||||
|
||||
void sas_porte_bytes_dmaed(struct work_struct *work)
|
||||
{
|
||||
struct asd_sas_event *ev = to_asd_sas_event(work);
|
||||
struct asd_sas_phy *phy = ev->phy;
|
||||
|
||||
clear_bit(PORTE_BYTES_DMAED, &phy->port_events_pending);
|
||||
|
||||
sas_form_port(phy);
|
||||
}
|
||||
|
||||
void sas_porte_broadcast_rcvd(struct work_struct *work)
|
||||
{
|
||||
struct asd_sas_event *ev = to_asd_sas_event(work);
|
||||
struct asd_sas_phy *phy = ev->phy;
|
||||
unsigned long flags;
|
||||
u32 prim;
|
||||
|
||||
clear_bit(PORTE_BROADCAST_RCVD, &phy->port_events_pending);
|
||||
|
||||
spin_lock_irqsave(&phy->sas_prim_lock, flags);
|
||||
prim = phy->sas_prim;
|
||||
spin_unlock_irqrestore(&phy->sas_prim_lock, flags);
|
||||
|
||||
SAS_DPRINTK("broadcast received: %d\n", prim);
|
||||
sas_discover_event(phy->port, DISCE_REVALIDATE_DOMAIN);
|
||||
}
|
||||
|
||||
void sas_porte_link_reset_err(struct work_struct *work)
|
||||
{
|
||||
struct asd_sas_event *ev = to_asd_sas_event(work);
|
||||
struct asd_sas_phy *phy = ev->phy;
|
||||
|
||||
clear_bit(PORTE_LINK_RESET_ERR, &phy->port_events_pending);
|
||||
|
||||
sas_deform_port(phy, 1);
|
||||
}
|
||||
|
||||
void sas_porte_timer_event(struct work_struct *work)
|
||||
{
|
||||
struct asd_sas_event *ev = to_asd_sas_event(work);
|
||||
struct asd_sas_phy *phy = ev->phy;
|
||||
|
||||
clear_bit(PORTE_TIMER_EVENT, &phy->port_events_pending);
|
||||
|
||||
sas_deform_port(phy, 1);
|
||||
}
|
||||
|
||||
void sas_porte_hard_reset(struct work_struct *work)
|
||||
{
|
||||
struct asd_sas_event *ev = to_asd_sas_event(work);
|
||||
struct asd_sas_phy *phy = ev->phy;
|
||||
|
||||
clear_bit(PORTE_HARD_RESET, &phy->port_events_pending);
|
||||
|
||||
sas_deform_port(phy, 1);
|
||||
}
|
||||
|
||||
/* ---------- SAS port registration ---------- */
|
||||
|
||||
static void sas_init_port(struct asd_sas_port *port,
|
||||
struct sas_ha_struct *sas_ha, int i)
|
||||
{
|
||||
memset(port, 0, sizeof(*port));
|
||||
port->id = i;
|
||||
INIT_LIST_HEAD(&port->dev_list);
|
||||
INIT_LIST_HEAD(&port->disco_list);
|
||||
INIT_LIST_HEAD(&port->destroy_list);
|
||||
spin_lock_init(&port->phy_list_lock);
|
||||
INIT_LIST_HEAD(&port->phy_list);
|
||||
port->ha = sas_ha;
|
||||
|
||||
spin_lock_init(&port->dev_list_lock);
|
||||
}
|
||||
|
||||
int sas_register_ports(struct sas_ha_struct *sas_ha)
|
||||
{
|
||||
int i;
|
||||
|
||||
/* initialize the ports and discovery */
|
||||
for (i = 0; i < sas_ha->num_phys; i++) {
|
||||
struct asd_sas_port *port = sas_ha->sas_port[i];
|
||||
|
||||
sas_init_port(port, sas_ha, i);
|
||||
sas_init_disc(&port->disc, port);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void sas_unregister_ports(struct sas_ha_struct *sas_ha)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < sas_ha->num_phys; i++)
|
||||
if (sas_ha->sas_phy[i]->port)
|
||||
sas_deform_port(sas_ha->sas_phy[i], 0);
|
||||
|
||||
}
|
1222
drivers/scsi/libsas/sas_scsi_host.c
Normal file
1222
drivers/scsi/libsas/sas_scsi_host.c
Normal file
File diff suppressed because it is too large
Load diff
37
drivers/scsi/libsas/sas_task.c
Normal file
37
drivers/scsi/libsas/sas_task.c
Normal file
|
@ -0,0 +1,37 @@
|
|||
#include <linux/kernel.h>
|
||||
#include <linux/export.h>
|
||||
#include <scsi/sas.h>
|
||||
#include <scsi/libsas.h>
|
||||
|
||||
/* fill task_status_struct based on SSP response frame */
|
||||
void sas_ssp_task_response(struct device *dev, struct sas_task *task,
|
||||
struct ssp_response_iu *iu)
|
||||
{
|
||||
struct task_status_struct *tstat = &task->task_status;
|
||||
|
||||
tstat->resp = SAS_TASK_COMPLETE;
|
||||
|
||||
if (iu->datapres == 0)
|
||||
tstat->stat = iu->status;
|
||||
else if (iu->datapres == 1)
|
||||
tstat->stat = iu->resp_data[3];
|
||||
else if (iu->datapres == 2) {
|
||||
tstat->stat = SAM_STAT_CHECK_CONDITION;
|
||||
tstat->buf_valid_size =
|
||||
min_t(int, SAS_STATUS_BUF_SIZE,
|
||||
be32_to_cpu(iu->sense_data_len));
|
||||
memcpy(tstat->buf, iu->sense_data, tstat->buf_valid_size);
|
||||
|
||||
if (iu->status != SAM_STAT_CHECK_CONDITION)
|
||||
dev_printk(KERN_WARNING, dev,
|
||||
"dev %llx sent sense data, but "
|
||||
"stat(%x) is not CHECK CONDITION\n",
|
||||
SAS_ADDR(task->dev->sas_addr),
|
||||
iu->status);
|
||||
}
|
||||
else
|
||||
/* when datapres contains corrupt/unknown value... */
|
||||
tstat->stat = SAM_STAT_CHECK_CONDITION;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(sas_ssp_task_response);
|
||||
|
Loading…
Add table
Add a link
Reference in a new issue