add idl4k kernel firmware version 1.13.0.105

This commit is contained in:
Jaroslav Kysela
2015-03-26 17:22:37 +01:00
parent 5194d2792e
commit e9070cdc77
31064 changed files with 12769984 additions and 0 deletions

View File

@@ -0,0 +1,56 @@
#
# 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.
config SCSI_SAS_LIBSAS_DEBUG
bool "Compile the SAS Domain Transport Attributes in debug mode"
default y
depends on SCSI_SAS_LIBSAS
help
Compiles the SAS Layer in debug mode. In debug mode, the
SAS Layer prints diagnostic and debug messages.

View File

@@ -0,0 +1,39 @@
#
# 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
ifeq ($(CONFIG_SCSI_SAS_LIBSAS_DEBUG),y)
EXTRA_CFLAGS += -DSAS_DEBUG
endif
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

View File

@@ -0,0 +1,777 @@
/*
* 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 <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 SAM_CHECK_COND:
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 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;
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;
enum ata_completion_errors ac;
unsigned long flags;
if (!qc)
goto qc_already_gone;
dev = qc->ap->private_data;
sas_ha = dev->port->ha;
spin_lock_irqsave(dev->sata_dev.ap->lock, flags);
if (stat->stat == SAS_PROTO_RESPONSE || stat->stat == SAM_GOOD) {
ata_tf_from_fis(resp->ending_fis, &dev->sata_dev.tf);
qc->err_mask |= ac_err_mask(dev->sata_dev.tf.command);
dev->sata_dev.sstatus = resp->sstatus;
dev->sata_dev.serror = resp->serror;
dev->sata_dev.scontrol = resp->scontrol;
} else if (stat->stat != SAM_STAT_GOOD) {
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. */
qc->err_mask = ac;
dev->sata_dev.tf.feature = 0x04; /* status err */
dev->sata_dev.tf.command = ATA_ERR;
}
}
qc->lldd_task = NULL;
if (qc->scsicmd)
ASSIGN_SAS_TASK(qc->scsicmd, NULL);
ata_qc_complete(qc);
spin_unlock_irqrestore(dev->sata_dev.ap->lock, flags);
/*
* If the sas_task has an ata qc, a scsi_cmnd and the aborted
* flag is set, then we must have come in via the libsas EH
* functions. When we exit this function, we need to put the
* scsi_cmnd on the list of finished errors. The ata_qc_complete
* call cleans up the libata side of things but we're protected
* from the scsi_cmnd going away because the scsi_cmnd is owned
* by the EH, making libata's call to scsi_done a NOP.
*/
spin_lock_irqsave(&task->task_state_lock, flags);
if (qc->scsicmd && task->task_state_flags & SAS_TASK_STATE_ABORTED)
scsi_eh_finish_cmd(qc->scsicmd, &sas_ha->eh_done_q);
spin_unlock_irqrestore(&task->task_state_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)
{
int res;
struct sas_task *task;
struct domain_device *dev = qc->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);
struct scatterlist *sg;
unsigned int xfer = 0;
unsigned int si;
task = sas_alloc_task(GFP_ATOMIC);
if (!task)
return AC_ERR_SYSTEM;
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, 1, 0, (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)
res = i->dft->lldd_execute_task(task, 1, GFP_ATOMIC);
else
res = sas_queue_up(task);
/* Examine */
if (res) {
SAS_DPRINTK("lldd_execute_task returned: %d\n", res);
if (qc->scsicmd)
ASSIGN_SAS_TASK(qc->scsicmd, NULL);
sas_free_task(task);
return AC_ERR_SYSTEM;
}
return 0;
}
static bool sas_ata_qc_fill_rtf(struct ata_queued_cmd *qc)
{
struct domain_device *dev = qc->ap->private_data;
memcpy(&qc->result_tf, &dev->sata_dev.tf, sizeof(qc->result_tf));
return true;
}
static void sas_ata_phy_reset(struct ata_port *ap)
{
struct domain_device *dev = ap->private_data;
struct sas_internal *i =
to_sas_internal(dev->port->ha->core.shost->transportt);
int res = TMF_RESP_FUNC_FAILED;
if (i->dft->lldd_I_T_nexus_reset)
res = i->dft->lldd_I_T_nexus_reset(dev);
if (res != TMF_RESP_FUNC_COMPLETE)
SAS_DPRINTK("%s: Unable to reset I T nexus?\n", __func__);
switch (dev->sata_dev.command_set) {
case ATA_COMMAND_SET:
SAS_DPRINTK("%s: Found ATA device.\n", __func__);
ap->link.device[0].class = ATA_DEV_ATA;
break;
case ATAPI_COMMAND_SET:
SAS_DPRINTK("%s: Found ATAPI device.\n", __func__);
ap->link.device[0].class = ATA_DEV_ATAPI;
break;
default:
SAS_DPRINTK("%s: Unknown SATA command set: %d.\n",
__func__,
dev->sata_dev.command_set);
ap->link.device[0].class = ATA_DEV_UNKNOWN;
break;
}
ap->cbl = ATA_CBL_SATA;
}
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, so we needn't
* bother with sas_ata_task_done. But we still
* ought to abort the task.
*/
struct sas_task *task = qc->lldd_task;
unsigned long flags;
qc->lldd_task = NULL;
if (task) {
/* Should this be a AT(API) device reset? */
spin_lock_irqsave(&task->task_state_lock, flags);
task->task_state_flags |= SAS_TASK_NEED_DEV_RESET;
spin_unlock_irqrestore(&task->task_state_lock, flags);
task->uldd_task = NULL;
__sas_task_abort(task);
}
}
}
static int sas_ata_scr_write(struct ata_link *link, unsigned int sc_reg_in,
u32 val)
{
struct domain_device *dev = link->ap->private_data;
SAS_DPRINTK("STUB %s\n", __func__);
switch (sc_reg_in) {
case SCR_STATUS:
dev->sata_dev.sstatus = val;
break;
case SCR_CONTROL:
dev->sata_dev.scontrol = val;
break;
case SCR_ERROR:
dev->sata_dev.serror = val;
break;
case SCR_ACTIVE:
dev->sata_dev.ap->link.sactive = val;
break;
default:
return -EINVAL;
}
return 0;
}
static int sas_ata_scr_read(struct ata_link *link, unsigned int sc_reg_in,
u32 *val)
{
struct domain_device *dev = link->ap->private_data;
SAS_DPRINTK("STUB %s\n", __func__);
switch (sc_reg_in) {
case SCR_STATUS:
*val = dev->sata_dev.sstatus;
return 0;
case SCR_CONTROL:
*val = dev->sata_dev.scontrol;
return 0;
case SCR_ERROR:
*val = dev->sata_dev.serror;
return 0;
case SCR_ACTIVE:
*val = dev->sata_dev.ap->link.sactive;
return 0;
default:
return -EINVAL;
}
}
static struct ata_port_operations sas_sata_ops = {
.phy_reset = sas_ata_phy_reset,
.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,
.scr_read = sas_ata_scr_read,
.scr_write = sas_ata_scr_write
};
static struct ata_port_info sata_port_info = {
.flags = ATA_FLAG_SATA | ATA_FLAG_NO_LEGACY | ATA_FLAG_SATA_RESET |
ATA_FLAG_MMIO | ATA_FLAG_PIO_DMA | ATA_FLAG_NCQ,
.pio_mask = 0x1f, /* PIO0-4 */
.mwdma_mask = 0x07, /* MWDMA0-2 */
.udma_mask = ATA_UDMA6,
.port_ops = &sas_sata_ops
};
int sas_ata_init_host_and_port(struct domain_device *found_dev,
struct scsi_target *starget)
{
struct Scsi_Host *shost = dev_to_shost(&starget->dev);
struct sas_ha_struct *ha = SHOST_TO_SAS_HA(shost);
struct ata_port *ap;
ata_host_init(&found_dev->sata_dev.ata_host,
ha->dev,
sata_port_info.flags,
&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;
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 request_queue *q = qc->scsicmd->device->request_queue;
struct completion *waiting;
unsigned long flags;
/* Bounce SCSI-initiated commands to the SCSI EH */
if (qc->scsicmd) {
spin_lock_irqsave(q->queue_lock, flags);
blk_abort_request(qc->scsicmd->request);
spin_unlock_irqrestore(q->queue_lock, flags);
scsi_schedule_eh(qc->scsicmd->device->host);
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_task_timedout(unsigned long _task)
{
struct sas_task *task = (void *) _task;
unsigned long flags;
spin_lock_irqsave(&task->task_state_lock, flags);
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);
complete(&task->completion);
}
static void sas_disc_task_done(struct sas_task *task)
{
if (!del_timer(&task->timer))
return;
complete(&task->completion);
}
#define SAS_DEV_TIMEOUT 10
/**
* sas_execute_task -- Basic task processing for discovery
* @task: the task to be executed
* @buffer: pointer to buffer to do I/O
* @size: size of @buffer
* @dma_dir: DMA direction. DMA_xxx
*/
static int sas_execute_task(struct sas_task *task, void *buffer, int size,
enum dma_data_direction dma_dir)
{
int res = 0;
struct scatterlist *scatter = NULL;
struct task_status_struct *ts = &task->task_status;
int num_scatter = 0;
int retries = 0;
struct sas_internal *i =
to_sas_internal(task->dev->port->ha->core.shost->transportt);
if (dma_dir != DMA_NONE) {
scatter = kzalloc(sizeof(*scatter), GFP_KERNEL);
if (!scatter)
goto out;
sg_init_one(scatter, buffer, size);
num_scatter = 1;
}
task->task_proto = task->dev->tproto;
task->scatter = scatter;
task->num_scatter = num_scatter;
task->total_xfer_len = size;
task->data_dir = dma_dir;
task->task_done = sas_disc_task_done;
if (dma_dir != DMA_NONE &&
sas_protocol_ata(task->task_proto)) {
task->num_scatter = dma_map_sg(task->dev->port->ha->dev,
task->scatter,
task->num_scatter,
task->data_dir);
}
for (retries = 0; retries < 5; retries++) {
task->task_state_flags = SAS_TASK_STATE_PENDING;
init_completion(&task->completion);
task->timer.data = (unsigned long) task;
task->timer.function = sas_task_timedout;
task->timer.expires = jiffies + SAS_DEV_TIMEOUT*HZ;
add_timer(&task->timer);
res = i->dft->lldd_execute_task(task, 1, GFP_KERNEL);
if (res) {
del_timer(&task->timer);
SAS_DPRINTK("executing SAS discovery task failed:%d\n",
res);
goto ex_err;
}
wait_for_completion(&task->completion);
res = -ECOMM;
if (task->task_state_flags & SAS_TASK_STATE_ABORTED) {
int res2;
SAS_DPRINTK("task aborted, flags:0x%x\n",
task->task_state_flags);
res2 = i->dft->lldd_abort_task(task);
SAS_DPRINTK("came back from abort task\n");
if (!(task->task_state_flags & SAS_TASK_STATE_DONE)) {
if (res2 == TMF_RESP_FUNC_COMPLETE)
continue; /* Retry the task */
else
goto ex_err;
}
}
if (task->task_status.stat == SAM_BUSY ||
task->task_status.stat == SAM_TASK_SET_FULL ||
task->task_status.stat == SAS_QUEUE_FULL) {
SAS_DPRINTK("task: q busy, sleeping...\n");
schedule_timeout_interruptible(HZ);
} else if (task->task_status.stat == SAM_CHECK_COND) {
struct scsi_sense_hdr shdr;
if (!scsi_normalize_sense(ts->buf, ts->buf_valid_size,
&shdr)) {
SAS_DPRINTK("couldn't normalize sense\n");
continue;
}
if ((shdr.sense_key == 6 && shdr.asc == 0x29) ||
(shdr.sense_key == 2 && shdr.asc == 4 &&
shdr.ascq == 1)) {
SAS_DPRINTK("device %016llx LUN: %016llx "
"powering up or not ready yet, "
"sleeping...\n",
SAS_ADDR(task->dev->sas_addr),
SAS_ADDR(task->ssp_task.LUN));
schedule_timeout_interruptible(5*HZ);
} else if (shdr.sense_key == 1) {
res = 0;
break;
} else if (shdr.sense_key == 5) {
break;
} else {
SAS_DPRINTK("dev %016llx LUN: %016llx "
"sense key:0x%x ASC:0x%x ASCQ:0x%x"
"\n",
SAS_ADDR(task->dev->sas_addr),
SAS_ADDR(task->ssp_task.LUN),
shdr.sense_key,
shdr.asc, shdr.ascq);
}
} else if (task->task_status.resp != SAS_TASK_COMPLETE ||
task->task_status.stat != SAM_GOOD) {
SAS_DPRINTK("task finished with resp:0x%x, "
"stat:0x%x\n",
task->task_status.resp,
task->task_status.stat);
goto ex_err;
} else {
res = 0;
break;
}
}
ex_err:
if (dma_dir != DMA_NONE) {
if (sas_protocol_ata(task->task_proto))
dma_unmap_sg(task->dev->port->ha->dev,
task->scatter, task->num_scatter,
task->data_dir);
kfree(scatter);
}
out:
return res;
}
/* ---------- SATA ---------- */
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 ((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;
}
/**
* sas_issue_ata_cmd -- Basic SATA command processing for discovery
* @dev: the device to send the command to
* @command: the command register
* @features: the features register
* @buffer: pointer to buffer to do I/O
* @size: size of @buffer
* @dma_dir: DMA direction. DMA_xxx
*/
static int sas_issue_ata_cmd(struct domain_device *dev, u8 command,
u8 features, void *buffer, int size,
enum dma_data_direction dma_dir)
{
int res = 0;
struct sas_task *task;
struct dev_to_host_fis *d2h_fis = (struct dev_to_host_fis *)
&dev->frame_rcvd[0];
res = -ENOMEM;
task = sas_alloc_task(GFP_KERNEL);
if (!task)
goto out;
task->dev = dev;
task->ata_task.fis.fis_type = 0x27;
task->ata_task.fis.command = command;
task->ata_task.fis.features = features;
task->ata_task.fis.device = d2h_fis->device;
task->ata_task.retry_count = 1;
res = sas_execute_task(task, buffer, size, dma_dir);
sas_free_task(task);
out:
return res;
}
#define ATA_IDENTIFY_DEV 0xEC
#define ATA_IDENTIFY_PACKET_DEV 0xA1
#define ATA_SET_FEATURES 0xEF
#define ATA_FEATURE_PUP_STBY_SPIN_UP 0x07
/**
* sas_discover_sata_dev -- discover a STP/SATA device (SATA_DEV)
* @dev: STP/SATA device of interest (ATA/ATAPI)
*
* The LLDD has already been notified of this device, so that we can
* send FISes to it. Here we try to get IDENTIFY DEVICE or IDENTIFY
* PACKET DEVICE, if ATAPI device, so that the LLDD can fine-tune its
* performance for this device.
*/
static int sas_discover_sata_dev(struct domain_device *dev)
{
int res;
__le16 *identify_x;
u8 command;
identify_x = kzalloc(512, GFP_KERNEL);
if (!identify_x)
return -ENOMEM;
if (dev->sata_dev.command_set == ATA_COMMAND_SET) {
dev->sata_dev.identify_device = identify_x;
command = ATA_IDENTIFY_DEV;
} else {
dev->sata_dev.identify_packet_device = identify_x;
command = ATA_IDENTIFY_PACKET_DEV;
}
res = sas_issue_ata_cmd(dev, command, 0, identify_x, 512,
DMA_FROM_DEVICE);
if (res)
goto out_err;
/* lives on the media? */
if (le16_to_cpu(identify_x[0]) & 4) {
/* incomplete response */
SAS_DPRINTK("sending SET FEATURE/PUP_STBY_SPIN_UP to "
"dev %llx\n", SAS_ADDR(dev->sas_addr));
if (!(identify_x[83] & cpu_to_le16(1<<6)))
goto cont1;
res = sas_issue_ata_cmd(dev, ATA_SET_FEATURES,
ATA_FEATURE_PUP_STBY_SPIN_UP,
NULL, 0, DMA_NONE);
if (res)
goto cont1;
schedule_timeout_interruptible(5*HZ); /* More time? */
res = sas_issue_ata_cmd(dev, command, 0, identify_x, 512,
DMA_FROM_DEVICE);
if (res)
goto out_err;
}
cont1:
/* XXX Hint: register this SATA device with SATL.
When this returns, dev->sata_dev->lu is alive and
present.
sas_satl_register_dev(dev);
*/
sas_fill_in_rphy(dev, dev->rphy);
return 0;
out_err:
dev->sata_dev.identify_packet_device = NULL;
dev->sata_dev.identify_device = NULL;
kfree(identify_x);
return res;
}
static int sas_discover_sata_pm(struct domain_device *dev)
{
return -ENODEV;
}
/**
* sas_discover_sata -- discover an STP/SATA domain device
* @dev: pointer to struct domain_device of interest
*
* First we notify the LLDD of this device, so we can send frames to
* it. Then depending on the type of device we call the appropriate
* discover functions. Once device discover is done, we notify the
* LLDD so that it can fine-tune its parameters for the device, by
* removing it and then adding it. That is, the second time around,
* the driver would have certain fields, that it is looking at, set.
* Finally we initialize the kobj so that the device can be added to
* the system at registration time. 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;
sas_get_ata_command_set(dev);
res = sas_notify_lldd_dev_found(dev);
if (res)
return res;
switch (dev->dev_type) {
case SATA_DEV:
res = sas_discover_sata_dev(dev);
break;
case SATA_PM:
res = sas_discover_sata_pm(dev);
break;
default:
break;
}
sas_notify_lldd_dev_gone(dev);
if (!res) {
sas_notify_lldd_dev_found(dev);
res = sas_rphy_add(dev->rphy);
}
return res;
}

View File

@@ -0,0 +1,387 @@
/*
* 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 <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_internal.h"
/* ---------- Basic task processing for discovery purposes ---------- */
void sas_init_dev(struct domain_device *dev)
{
INIT_LIST_HEAD(&dev->siblings);
INIT_LIST_HEAD(&dev->dev_list_node);
switch (dev->dev_type) {
case SAS_END_DEV:
break;
case EDGE_DEV:
case FANOUT_DEV:
INIT_LIST_HEAD(&dev->ex_dev.children);
break;
case SATA_DEV:
case SATA_PM:
case SATA_PM_PORT:
INIT_LIST_HEAD(&dev->sata_dev.children);
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)
{
unsigned long flags;
struct asd_sas_phy *phy;
struct sas_rphy *rphy;
struct domain_device *dev;
dev = kzalloc(sizeof(*dev), GFP_KERNEL);
if (!dev)
return -ENOMEM;
spin_lock_irqsave(&port->phy_list_lock, flags);
if (list_empty(&port->phy_list)) {
spin_unlock_irqrestore(&port->phy_list_lock, flags);
kfree(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_irqrestore(&port->phy_list_lock, flags);
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 = SATA_PM;
else
dev->dev_type = 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);
switch (dev->dev_type) {
case SAS_END_DEV:
case SATA_DEV:
rphy = sas_end_device_alloc(port->port);
break;
case EDGE_DEV:
rphy = sas_expander_alloc(port->port,
SAS_EDGE_EXPANDER_DEVICE);
break;
case FANOUT_DEV:
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) {
kfree(dev);
return -ENODEV;
}
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->port = port;
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;
dev->rphy = rphy;
spin_lock_irq(&port->dev_list_lock);
list_add_tail(&dev->dev_list_node, &port->dev_list);
spin_unlock_irq(&port->dev_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) {
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);
}
}
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)
i->dft->lldd_dev_gone(dev);
}
/* ---------- Common/dispatchers ---------- */
/**
* 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)
goto out_err2;
res = sas_rphy_add(dev->rphy);
if (res)
goto out_err;
return 0;
out_err:
sas_notify_lldd_dev_gone(dev);
out_err2:
return res;
}
/* ---------- Device registration and unregistration ---------- */
static inline void sas_unregister_common_dev(struct domain_device *dev)
{
sas_notify_lldd_dev_gone(dev);
if (!dev->parent)
dev->port->port_dev = NULL;
else
list_del_init(&dev->siblings);
list_del_init(&dev->dev_list_node);
}
void sas_unregister_dev(struct domain_device *dev)
{
if (dev->rphy) {
sas_remove_children(&dev->rphy->dev);
sas_rphy_delete(dev->rphy);
dev->rphy = NULL;
}
if (dev->dev_type == EDGE_DEV || dev->dev_type == FANOUT_DEV) {
/* remove the phys and ports, everything else should be gone */
kfree(dev->ex_dev.ex_phy);
dev->ex_dev.ex_phy = NULL;
}
sas_unregister_common_dev(dev);
}
void sas_unregister_domain_devices(struct asd_sas_port *port)
{
struct domain_device *dev, *n;
list_for_each_entry_safe_reverse(dev,n,&port->dev_list,dev_list_node)
sas_unregister_dev(dev);
port->port->rphy = NULL;
}
/* ---------- 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 =
container_of(work, struct sas_discovery_event, work);
struct asd_sas_port *port = ev->port;
sas_begin_event(DISCE_DISCOVER_DOMAIN, &port->disc.disc_event_lock,
&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_DEV:
error = sas_discover_end_dev(dev);
break;
case EDGE_DEV:
case FANOUT_DEV:
error = sas_discover_root_expander(dev);
break;
case SATA_DEV:
case 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);
dev->rphy = NULL;
spin_lock_irq(&port->dev_list_lock);
list_del_init(&dev->dev_list_node);
spin_unlock_irq(&port->dev_list_lock);
kfree(dev); /* not kobject_register-ed yet */
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 =
container_of(work, struct sas_discovery_event, work);
struct asd_sas_port *port = ev->port;
sas_begin_event(DISCE_REVALIDATE_DOMAIN, &port->disc.disc_event_lock,
&port->disc.pending);
SAS_DPRINTK("REVALIDATING DOMAIN on port %d, pid:%d\n", port->id,
task_pid_nr(current));
if (port->port_dev)
res = sas_ex_revalidate_domain(port->port_dev);
SAS_DPRINTK("done REVALIDATING DOMAIN on port %d, pid:%d, res 0x%x\n",
port->id, task_pid_nr(current), res);
}
/* ---------- Events ---------- */
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_queue_event(ev, &disc->disc_event_lock, &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,
};
spin_lock_init(&disc->disc_event_lock);
disc->pending = 0;
for (i = 0; i < DISC_NUM_EVENTS; i++) {
INIT_WORK(&disc->disc_work[i].work, sas_event_fns[i]);
disc->disc_work[i].port = port;
}
}

View File

@@ -0,0 +1,76 @@
/*
* 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"
#ifdef SAS_DEBUG
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",
};
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);
}
#endif /* SAS_DEBUG */

View File

@@ -0,0 +1,42 @@
/*
* 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"
#ifdef SAS_DEBUG
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);
#else /* SAS_DEBUG */
static inline void sas_dprint_porte(int phyid, enum port_event pe) { }
static inline void sas_dprint_phye(int phyid, enum phy_event pe) { }
static inline void sas_dprint_hae(struct sas_ha_struct *sas_ha,
enum ha_event he) { }
static inline void sas_dump_port(struct asd_sas_port *port) { }
#endif /* SAS_DEBUG */

View File

@@ -0,0 +1,77 @@
/*
* 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 <scsi/scsi_host.h>
#include "sas_internal.h"
#include "sas_dump.h"
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->event_lock, &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, &ha->event_lock, &phy->port_events_pending,
&phy->port_events[event].work, ha);
}
static void 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, &ha->event_lock, &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;
spin_lock_init(&sas_ha->event_lock);
for (i = 0; i < HA_NUM_EVENTS; i++) {
INIT_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 = notify_phy_event;
return 0;
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,273 @@
/*
* 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 "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;
}
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;
if (phy_id >= sas_ha->num_phys) {
resp_data[2] = SMP_RESP_NO_PHY;
return;
}
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;
if (i->dft->lldd_control_phy(sas_ha->sas_phy[phy_id], 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), KM_USER0) + bio_offset(req->bio);
memcpy(req_data, buf, blk_rq_bytes(req));
kunmap_atomic(buf - bio_offset(req->bio), KM_USER0);
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:
/* FIXME: need GPIO support in the transport class */
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), KM_USER0) + bio_offset(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), KM_USER0);
local_irq_enable();
out:
kfree(req_data);
kfree(resp_data);
return error;
}

View File

@@ -0,0 +1,316 @@
/*
* 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/init.h>
#include <linux/device.h>
#include <linux/spinlock.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"
struct kmem_cache *sas_task_cache;
/*------------ 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 =
container_of(work, struct sas_ha_event, work);
struct sas_ha_struct *ha = ev->ha;
sas_begin_event(HAE_RESET, &ha->event_lock,
&ha->pending);
}
int sas_register_ha(struct sas_ha_struct *sas_ha)
{
int error = 0;
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 */
sas_ha->state = SAS_HA_REGISTERED;
spin_lock_init(&sas_ha->state_lock);
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);
return 0;
Undo_ports:
sas_unregister_ports(sas_ha);
Undo_phys:
return error;
}
int sas_unregister_ha(struct sas_ha_struct *sas_ha)
{
unsigned long flags;
/* Set the state to unregistered to avoid further
* events to be queued */
spin_lock_irqsave(&sas_ha->state_lock, flags);
sas_ha->state = SAS_HA_UNREGISTERED;
spin_unlock_irqrestore(&sas_ha->state_lock, flags);
scsi_flush_work(sas_ha->core.shost);
sas_unregister_ports(sas_ha);
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))
/* FIXME: we have no local phy stats
* gathering at this time */
return -EINVAL;
return sas_smp_get_phy_events(phy);
}
int sas_phy_enable(struct sas_phy *phy, int enable)
{
int ret;
enum phy_func command;
if (enable)
command = PHY_FUNC_LINK_RESET;
else
command = 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) {
sas_phy_disconnected(asd_phy);
sas_ha->notify_phy_event(asd_phy, PHYE_LOSS_OF_SIGNAL);
}
ret = i->dft->lldd_control_phy(asd_phy, command, 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, command, NULL);
}
return ret;
}
int sas_phy_reset(struct sas_phy *phy, int hard_reset)
{
int ret;
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);
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;
}
static struct sas_function_template sft = {
.phy_enable = sas_phy_enable,
.phy_reset = sas_phy_reset,
.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_create("sas_task", sizeof(struct sas_task),
0, SLAB_HWCACHE_ALIGN, NULL);
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);

View File

@@ -0,0 +1,168 @@
/*
* 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>
#define sas_printk(fmt, ...) printk(KERN_NOTICE "sas: " fmt, ## __VA_ARGS__)
#ifdef SAS_DEBUG
#define SAS_DPRINTK(fmt, ...) printk(KERN_NOTICE "sas: " fmt, ## __VA_ARGS__)
#else
#define SAS_DPRINTK(fmt, ...)
#endif
#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)
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_deform_port(struct asd_sas_phy *phy);
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);
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);
struct domain_device *sas_find_dev_by_rphy(struct sas_rphy *rphy);
void sas_hae_reset(struct work_struct *work);
#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_queue_event(int event, spinlock_t *lock,
unsigned long *pending,
struct work_struct *work,
struct sas_ha_struct *sas_ha)
{
unsigned long flags;
spin_lock_irqsave(lock, flags);
if (test_bit(event, pending)) {
spin_unlock_irqrestore(lock, flags);
return;
}
__set_bit(event, pending);
spin_unlock_irqrestore(lock, flags);
spin_lock_irqsave(&sas_ha->state_lock, flags);
if (sas_ha->state != SAS_HA_UNREGISTERED) {
scsi_queue_work(sas_ha->core.shost, work);
}
spin_unlock_irqrestore(&sas_ha->state_lock, flags);
}
static inline void sas_begin_event(int event, spinlock_t *lock,
unsigned long *pending)
{
unsigned long flags;
spin_lock_irqsave(lock, flags);
__clear_bit(event, pending);
spin_unlock_irqrestore(lock, flags);
}
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 SATA_DEV:
/* FIXME: need sata device type */
case SAS_END_DEV:
rphy->identify.device_type = SAS_END_DEVICE;
break;
case EDGE_DEV:
rphy->identify.device_type = SAS_EDGE_EXPANDER_DEVICE;
break;
case FANOUT_DEV:
rphy->identify.device_type = SAS_FANOUT_EXPANDER_DEVICE;
break;
default:
rphy->identify.device_type = SAS_PHY_UNUSED;
break;
}
}
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);
}
#endif /* _SAS_INTERNAL_H_ */

View File

@@ -0,0 +1,171 @@
/*
* 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 =
container_of(work, struct asd_sas_event, work);
struct asd_sas_phy *phy = ev->phy;
sas_begin_event(PHYE_LOSS_OF_SIGNAL, &phy->ha->event_lock,
&phy->phy_events_pending);
phy->error = 0;
sas_deform_port(phy);
}
static void sas_phye_oob_done(struct work_struct *work)
{
struct asd_sas_event *ev =
container_of(work, struct asd_sas_event, work);
struct asd_sas_phy *phy = ev->phy;
sas_begin_event(PHYE_OOB_DONE, &phy->ha->event_lock,
&phy->phy_events_pending);
phy->error = 0;
}
static void sas_phye_oob_error(struct work_struct *work)
{
struct asd_sas_event *ev =
container_of(work, struct 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);
sas_begin_event(PHYE_OOB_ERROR, &phy->ha->event_lock,
&phy->phy_events_pending);
sas_deform_port(phy);
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 =
container_of(work, struct 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);
sas_begin_event(PHYE_SPINUP_HOLD, &phy->ha->event_lock,
&phy->phy_events_pending);
phy->error = 0;
i->dft->lldd_control_phy(phy, PHY_FUNC_RELEASE_SPINUP_HOLD, NULL);
}
/* ---------- 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,
};
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_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_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;
}

View File

@@ -0,0 +1,303 @@
/*
* 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"
/**
* 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 (memcmp(port->attached_sas_addr, phy->attached_sas_addr,
SAS_ADDR_SIZE) != 0)
sas_deform_port(phy);
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 &&
memcmp(port->attached_sas_addr,
phy->attached_sas_addr, SAS_ADDR_SIZE) == 0 &&
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);
phy->port = port;
port->num_phys++;
port->phy_mask |= (1U << phy->id);
if (!port->phy)
port->phy = phy->phy;
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)
{
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)
return; /* done by a phy event */
if (port->port_dev)
port->port_dev->pathways--;
if (port->num_phys == 1) {
sas_unregister_domain_devices(port);
sas_port_delete(port->port);
port->port = NULL;
} else
sas_port_delete_phy(port->port, phy->phy);
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);
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 =
container_of(work, struct asd_sas_event, work);
struct asd_sas_phy *phy = ev->phy;
sas_begin_event(PORTE_BYTES_DMAED, &phy->ha->event_lock,
&phy->port_events_pending);
sas_form_port(phy);
}
void sas_porte_broadcast_rcvd(struct work_struct *work)
{
struct asd_sas_event *ev =
container_of(work, struct asd_sas_event, work);
struct asd_sas_phy *phy = ev->phy;
unsigned long flags;
u32 prim;
sas_begin_event(PORTE_BROADCAST_RCVD, &phy->ha->event_lock,
&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 =
container_of(work, struct asd_sas_event, work);
struct asd_sas_phy *phy = ev->phy;
sas_begin_event(PORTE_LINK_RESET_ERR, &phy->ha->event_lock,
&phy->port_events_pending);
sas_deform_port(phy);
}
void sas_porte_timer_event(struct work_struct *work)
{
struct asd_sas_event *ev =
container_of(work, struct asd_sas_event, work);
struct asd_sas_phy *phy = ev->phy;
sas_begin_event(PORTE_TIMER_EVENT, &phy->ha->event_lock,
&phy->port_events_pending);
sas_deform_port(phy);
}
void sas_porte_hard_reset(struct work_struct *work)
{
struct asd_sas_event *ev =
container_of(work, struct asd_sas_event, work);
struct asd_sas_phy *phy = ev->phy;
sas_begin_event(PORTE_HARD_RESET, &phy->ha->event_lock,
&phy->port_events_pending);
sas_deform_port(phy);
}
/* ---------- 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);
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]);
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,36 @@
#include <linux/kernel.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_CHECK_COND;
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_CHECK_COND)
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_CHECK_COND;
}
EXPORT_SYMBOL_GPL(sas_ssp_task_response);