1
0

[SCSI] aic94xx: new driver

This is the end point of the separate aic94xx driver based on the
original driver and transport class from Luben Tuikov
<ltuikov@yahoo.com>

The log of the separate development is:

Alexis Bruemmer:
  o aic94xx: fix hotplug/unplug for expanderless systems
  o aic94xx: disable split completion timer/setting by default
  o aic94xx: wide port off expander support
  o aic94xx: remove various inline functions
  o aic94xx: use bitops
  o aic94xx: remove queue comment
  o aic94xx: remove sas_common.c
  o aic94xx: sas remove depot's
  o aic94xx: use available list_for_each_entry_safe_reverse()
  o aic94xx: sas header file merge

James Bottomley:
  o aic94xx: fix TF_TMF_NO_CTX processing
  o aic94xx: convert to request_firmware interface
  o aic94xx: fix hotplug/unplug
  o aic94xx: add link error counts to the expander phys
  o aic94xx: add transport class phy reset capability
  o aic94xx: remove local_attached flag
  o Remove README
  o Fixup Makefile variable for libsas rename
  o Rename sas->libsas
  o aic94xx: correct return code for sas_discover_event
  o aic94xx: use parent backlink port
  o aic94xx: remove channel abstraction
  o aic94xx: fix routing algorithms
  o aic94xx: add backlink port
  o aic94xx: fix cascaded expander properties
  o aic94xx: fix sleep under lock
  o aic94xx: fix panic on module removal in complex topology
  o aic94xx: make use of the new sas_port
  o rename sas_port to asd_sas_port
  o Fix for eh_strategy_handler move
  o aic94xx: move entirely over to correct transport class formulation
  o remove last vestages of sas_rphy_alloc()
  o update for eh_timed_out move
  o Preliminary expander support for aic94xx
  o sas: remove event thread
  o minor warning cleanups
  o remove last vestiges of id mapping arrays
  o Further updates
  o Convert aic94xx over entirely to the transport class end device and
  o update aic94xx/sas to use the new sas transport class end device
  o [PATCH] aic94xx: attaching to the sas transport class
  o Add missing completion removal from prior patch
  o [PATCH] aic94xx: attaching to the sas transport class
  o Build fixes from akpm

Jeff Garzik:
  o [scsi aic94xx] Remove ->owner from PCI info table

Luben Tuikov:
  o initial aic94xx driver

Mike Anderson:
  o aic94xx: fix panic on module insertion
  o aic94xx: stub out SATA_DEV case
  o aic94xx: compile warning cleanups
  o aic94xx: sas_alloc_task
  o aic94xx: ref count update
  o aic94xx nexus loss time value
  o [PATCH] aic94xx: driver assertion in non-x86 BIOS env

Randy Dunlap:
  o libsas: externs not needed

Robert Tarte:
  o aic94xx: sequence patch - fixes SATA support

Signed-off-by: James Bottomley <James.Bottomley@SteelEye.com>
This commit is contained in:
James Bottomley
2006-08-29 09:22:51 -05:00
committed by James Bottomley
parent f4ad7b5807
commit 2908d778ab
37 changed files with 18866 additions and 1 deletions

View File

@@ -0,0 +1,41 @@
#
# Kernel configuration file for aic94xx SAS/SATA driver.
#
# Copyright (c) 2005 Adaptec, Inc. All rights reserved.
# Copyright (c) 2005 Luben Tuikov <luben_tuikov@adaptec.com>
#
# This file is licensed under GPLv2.
#
# This file is part of the aic94xx driver.
#
# The aic94xx driver 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.
#
# The aic94xx driver 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 Aic94xx Driver; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
#
#
config SCSI_AIC94XX
tristate "Adaptec AIC94xx SAS/SATA support"
depends on PCI
select SCSI_SAS_LIBSAS
help
This driver supports Adaptec's SAS/SATA 3Gb/s 64 bit PCI-X
AIC94xx chip based host adapters.
config AIC94XX_DEBUG
bool "Compile in debug mode"
default y
depends on SCSI_AIC94XX
help
Compiles the aic94xx driver in debug mode. In debug mode,
the driver prints some messages to the console.

View File

@@ -0,0 +1,39 @@
#
# Makefile for Adaptec aic94xx SAS/SATA driver.
#
# Copyright (C) 2005 Adaptec, Inc. All rights reserved.
# Copyright (C) 2005 Luben Tuikov <luben_tuikov@adaptec.com>
#
# This file is licensed under GPLv2.
#
# This file is part of the the aic94xx driver.
#
# The aic94xx driver 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.
#
# The aic94xx driver 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 the aic94xx driver; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
ifeq ($(CONFIG_AIC94XX_DEBUG),y)
EXTRA_CFLAGS += -DASD_DEBUG -DASD_ENTER_EXIT
endif
obj-$(CONFIG_SCSI_AIC94XX) += aic94xx.o
aic94xx-y += aic94xx_init.o \
aic94xx_hwi.o \
aic94xx_reg.o \
aic94xx_sds.o \
aic94xx_seq.o \
aic94xx_dump.o \
aic94xx_scb.o \
aic94xx_dev.o \
aic94xx_tmf.o \
aic94xx_task.o

View File

@@ -0,0 +1,114 @@
/*
* Aic94xx SAS/SATA driver 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 file is part of the aic94xx driver.
*
* The aic94xx driver 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.
*
* The aic94xx driver 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 the aic94xx driver; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*
* $Id: //depot/aic94xx/aic94xx.h#31 $
*/
#ifndef _AIC94XX_H_
#define _AIC94XX_H_
#include <linux/slab.h>
#include <linux/ctype.h>
#include <scsi/libsas.h>
#define ASD_DRIVER_NAME "aic94xx"
#define ASD_DRIVER_DESCRIPTION "Adaptec aic94xx SAS/SATA driver"
#define asd_printk(fmt, ...) printk(KERN_NOTICE ASD_DRIVER_NAME ": " fmt, ## __VA_ARGS__)
#ifdef ASD_ENTER_EXIT
#define ENTER printk(KERN_NOTICE "%s: ENTER %s\n", ASD_DRIVER_NAME, \
__FUNCTION__)
#define EXIT printk(KERN_NOTICE "%s: --EXIT %s\n", ASD_DRIVER_NAME, \
__FUNCTION__)
#else
#define ENTER
#define EXIT
#endif
#ifdef ASD_DEBUG
#define ASD_DPRINTK asd_printk
#else
#define ASD_DPRINTK(fmt, ...)
#endif
/* 2*ITNL timeout + 1 second */
#define AIC94XX_SCB_TIMEOUT (5*HZ)
extern kmem_cache_t *asd_dma_token_cache;
extern kmem_cache_t *asd_ascb_cache;
extern char sas_addr_str[2*SAS_ADDR_SIZE + 1];
static inline void asd_stringify_sas_addr(char *p, const u8 *sas_addr)
{
int i;
for (i = 0; i < SAS_ADDR_SIZE; i++, p += 2)
snprintf(p, 3, "%02X", sas_addr[i]);
*p = '\0';
}
static inline void asd_destringify_sas_addr(u8 *sas_addr, const char *p)
{
int i;
for (i = 0; i < SAS_ADDR_SIZE; i++) {
u8 h, l;
if (!*p)
break;
h = isdigit(*p) ? *p-'0' : *p-'A'+10;
p++;
l = isdigit(*p) ? *p-'0' : *p-'A'+10;
p++;
sas_addr[i] = (h<<4) | l;
}
}
struct asd_ha_struct;
struct asd_ascb;
int asd_read_ocm(struct asd_ha_struct *asd_ha);
int asd_read_flash(struct asd_ha_struct *asd_ha);
int asd_dev_found(struct domain_device *dev);
void asd_dev_gone(struct domain_device *dev);
void asd_invalidate_edb(struct asd_ascb *ascb, int edb_id);
int asd_execute_task(struct sas_task *, int num, unsigned long gfp_flags);
/* ---------- TMFs ---------- */
int asd_abort_task(struct sas_task *);
int asd_abort_task_set(struct domain_device *, u8 *lun);
int asd_clear_aca(struct domain_device *, u8 *lun);
int asd_clear_task_set(struct domain_device *, u8 *lun);
int asd_lu_reset(struct domain_device *, u8 *lun);
int asd_query_task(struct sas_task *);
/* ---------- Adapter and Port management ---------- */
int asd_clear_nexus_port(struct asd_sas_port *port);
int asd_clear_nexus_ha(struct sas_ha_struct *sas_ha);
/* ---------- Phy Management ---------- */
int asd_control_phy(struct asd_sas_phy *phy, enum phy_func func);
#endif

View File

@@ -0,0 +1,353 @@
/*
* Aic94xx SAS/SATA DDB management
*
* Copyright (C) 2005 Adaptec, Inc. All rights reserved.
* Copyright (C) 2005 Luben Tuikov <luben_tuikov@adaptec.com>
*
* This file is licensed under GPLv2.
*
* This file is part of the aic94xx driver.
*
* The aic94xx driver 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.
*
* The aic94xx driver 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 the aic94xx driver; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*
* $Id: //depot/aic94xx/aic94xx_dev.c#21 $
*/
#include "aic94xx.h"
#include "aic94xx_hwi.h"
#include "aic94xx_reg.h"
#include "aic94xx_sas.h"
#define FIND_FREE_DDB(_ha) find_first_zero_bit((_ha)->hw_prof.ddb_bitmap, \
(_ha)->hw_prof.max_ddbs)
#define SET_DDB(_ddb, _ha) set_bit(_ddb, (_ha)->hw_prof.ddb_bitmap)
#define CLEAR_DDB(_ddb, _ha) clear_bit(_ddb, (_ha)->hw_prof.ddb_bitmap)
static inline int asd_get_ddb(struct asd_ha_struct *asd_ha)
{
unsigned long flags;
int ddb, i;
spin_lock_irqsave(&asd_ha->hw_prof.ddb_lock, flags);
ddb = FIND_FREE_DDB(asd_ha);
if (ddb >= asd_ha->hw_prof.max_ddbs) {
ddb = -ENOMEM;
spin_unlock_irqrestore(&asd_ha->hw_prof.ddb_lock, flags);
goto out;
}
SET_DDB(ddb, asd_ha);
spin_unlock_irqrestore(&asd_ha->hw_prof.ddb_lock, flags);
for (i = 0; i < sizeof(struct asd_ddb_ssp_smp_target_port); i+= 4)
asd_ddbsite_write_dword(asd_ha, ddb, i, 0);
out:
return ddb;
}
#define INIT_CONN_TAG offsetof(struct asd_ddb_ssp_smp_target_port, init_conn_tag)
#define DEST_SAS_ADDR offsetof(struct asd_ddb_ssp_smp_target_port, dest_sas_addr)
#define SEND_QUEUE_HEAD offsetof(struct asd_ddb_ssp_smp_target_port, send_queue_head)
#define DDB_TYPE offsetof(struct asd_ddb_ssp_smp_target_port, ddb_type)
#define CONN_MASK offsetof(struct asd_ddb_ssp_smp_target_port, conn_mask)
#define DDB_TARG_FLAGS offsetof(struct asd_ddb_ssp_smp_target_port, flags)
#define DDB_TARG_FLAGS2 offsetof(struct asd_ddb_stp_sata_target_port, flags2)
#define EXEC_QUEUE_TAIL offsetof(struct asd_ddb_ssp_smp_target_port, exec_queue_tail)
#define SEND_QUEUE_TAIL offsetof(struct asd_ddb_ssp_smp_target_port, send_queue_tail)
#define SISTER_DDB offsetof(struct asd_ddb_ssp_smp_target_port, sister_ddb)
#define MAX_CCONN offsetof(struct asd_ddb_ssp_smp_target_port, max_concurrent_conn)
#define NUM_CTX offsetof(struct asd_ddb_ssp_smp_target_port, num_contexts)
#define ATA_CMD_SCBPTR offsetof(struct asd_ddb_stp_sata_target_port, ata_cmd_scbptr)
#define SATA_TAG_ALLOC_MASK offsetof(struct asd_ddb_stp_sata_target_port, sata_tag_alloc_mask)
#define NUM_SATA_TAGS offsetof(struct asd_ddb_stp_sata_target_port, num_sata_tags)
#define SATA_STATUS offsetof(struct asd_ddb_stp_sata_target_port, sata_status)
#define NCQ_DATA_SCB_PTR offsetof(struct asd_ddb_stp_sata_target_port, ncq_data_scb_ptr)
#define ITNL_TIMEOUT offsetof(struct asd_ddb_ssp_smp_target_port, itnl_timeout)
static inline void asd_free_ddb(struct asd_ha_struct *asd_ha, int ddb)
{
unsigned long flags;
if (!ddb || ddb >= 0xFFFF)
return;
asd_ddbsite_write_byte(asd_ha, ddb, DDB_TYPE, DDB_TYPE_UNUSED);
spin_lock_irqsave(&asd_ha->hw_prof.ddb_lock, flags);
CLEAR_DDB(ddb, asd_ha);
spin_unlock_irqrestore(&asd_ha->hw_prof.ddb_lock, flags);
}
static inline void asd_set_ddb_type(struct domain_device *dev)
{
struct asd_ha_struct *asd_ha = dev->port->ha->lldd_ha;
int ddb = (int) (unsigned long) dev->lldd_dev;
if (dev->dev_type == SATA_PM_PORT)
asd_ddbsite_write_byte(asd_ha,ddb, DDB_TYPE, DDB_TYPE_PM_PORT);
else if (dev->tproto)
asd_ddbsite_write_byte(asd_ha,ddb, DDB_TYPE, DDB_TYPE_TARGET);
else
asd_ddbsite_write_byte(asd_ha,ddb,DDB_TYPE,DDB_TYPE_INITIATOR);
}
static int asd_init_sata_tag_ddb(struct domain_device *dev)
{
struct asd_ha_struct *asd_ha = dev->port->ha->lldd_ha;
int ddb, i;
ddb = asd_get_ddb(asd_ha);
if (ddb < 0)
return ddb;
for (i = 0; i < sizeof(struct asd_ddb_sata_tag); i += 2)
asd_ddbsite_write_word(asd_ha, ddb, i, 0xFFFF);
asd_ddbsite_write_word(asd_ha, (int) (unsigned long) dev->lldd_dev,
SISTER_DDB, ddb);
return 0;
}
static inline int asd_init_sata(struct domain_device *dev)
{
struct asd_ha_struct *asd_ha = dev->port->ha->lldd_ha;
int ddb = (int) (unsigned long) dev->lldd_dev;
u32 qdepth = 0;
int res = 0;
asd_ddbsite_write_word(asd_ha, ddb, ATA_CMD_SCBPTR, 0xFFFF);
if ((dev->dev_type == SATA_DEV || dev->dev_type == SATA_PM_PORT) &&
dev->sata_dev.identify_device &&
dev->sata_dev.identify_device[10] != 0) {
u16 w75 = le16_to_cpu(dev->sata_dev.identify_device[75]);
u16 w76 = le16_to_cpu(dev->sata_dev.identify_device[76]);
if (w76 & 0x100) /* NCQ? */
qdepth = (w75 & 0x1F) + 1;
asd_ddbsite_write_dword(asd_ha, ddb, SATA_TAG_ALLOC_MASK,
(1<<qdepth)-1);
asd_ddbsite_write_byte(asd_ha, ddb, NUM_SATA_TAGS, qdepth);
}
if (dev->dev_type == SATA_DEV || dev->dev_type == SATA_PM ||
dev->dev_type == SATA_PM_PORT) {
struct dev_to_host_fis *fis = (struct dev_to_host_fis *)
dev->frame_rcvd;
asd_ddbsite_write_byte(asd_ha, ddb, SATA_STATUS, fis->status);
}
asd_ddbsite_write_word(asd_ha, ddb, NCQ_DATA_SCB_PTR, 0xFFFF);
if (qdepth > 0)
res = asd_init_sata_tag_ddb(dev);
return res;
}
static int asd_init_target_ddb(struct domain_device *dev)
{
int ddb, i;
struct asd_ha_struct *asd_ha = dev->port->ha->lldd_ha;
u8 flags = 0;
ddb = asd_get_ddb(asd_ha);
if (ddb < 0)
return ddb;
dev->lldd_dev = (void *) (unsigned long) ddb;
asd_ddbsite_write_byte(asd_ha, ddb, 0, DDB_TP_CONN_TYPE);
asd_ddbsite_write_byte(asd_ha, ddb, 1, 0);
asd_ddbsite_write_word(asd_ha, ddb, INIT_CONN_TAG, 0xFFFF);
for (i = 0; i < SAS_ADDR_SIZE; i++)
asd_ddbsite_write_byte(asd_ha, ddb, DEST_SAS_ADDR+i,
dev->sas_addr[i]);
asd_ddbsite_write_word(asd_ha, ddb, SEND_QUEUE_HEAD, 0xFFFF);
asd_set_ddb_type(dev);
asd_ddbsite_write_byte(asd_ha, ddb, CONN_MASK, dev->port->phy_mask);
if (dev->port->oob_mode != SATA_OOB_MODE) {
flags |= OPEN_REQUIRED;
if ((dev->dev_type == SATA_DEV) ||
(dev->tproto & SAS_PROTO_STP)) {
struct smp_resp *rps_resp = &dev->sata_dev.rps_resp;
if (rps_resp->frame_type == SMP_RESPONSE &&
rps_resp->function == SMP_REPORT_PHY_SATA &&
rps_resp->result == SMP_RESP_FUNC_ACC) {
if (rps_resp->rps.affil_valid)
flags |= STP_AFFIL_POL;
if (rps_resp->rps.affil_supp)
flags |= SUPPORTS_AFFIL;
}
} else {
flags |= CONCURRENT_CONN_SUPP;
if (!dev->parent &&
(dev->dev_type == EDGE_DEV ||
dev->dev_type == FANOUT_DEV))
asd_ddbsite_write_byte(asd_ha, ddb, MAX_CCONN,
4);
else
asd_ddbsite_write_byte(asd_ha, ddb, MAX_CCONN,
dev->pathways);
asd_ddbsite_write_byte(asd_ha, ddb, NUM_CTX, 1);
}
}
if (dev->dev_type == SATA_PM)
flags |= SATA_MULTIPORT;
asd_ddbsite_write_byte(asd_ha, ddb, DDB_TARG_FLAGS, flags);
flags = 0;
if (dev->tproto & SAS_PROTO_STP)
flags |= STP_CL_POL_NO_TX;
asd_ddbsite_write_byte(asd_ha, ddb, DDB_TARG_FLAGS2, flags);
asd_ddbsite_write_word(asd_ha, ddb, EXEC_QUEUE_TAIL, 0xFFFF);
asd_ddbsite_write_word(asd_ha, ddb, SEND_QUEUE_TAIL, 0xFFFF);
asd_ddbsite_write_word(asd_ha, ddb, SISTER_DDB, 0xFFFF);
if (dev->dev_type == SATA_DEV || (dev->tproto & SAS_PROTO_STP)) {
i = asd_init_sata(dev);
if (i < 0) {
asd_free_ddb(asd_ha, ddb);
return i;
}
}
if (dev->dev_type == SAS_END_DEV) {
struct sas_end_device *rdev = rphy_to_end_device(dev->rphy);
if (rdev->I_T_nexus_loss_timeout > 0)
asd_ddbsite_write_word(asd_ha, ddb, ITNL_TIMEOUT,
min(rdev->I_T_nexus_loss_timeout,
(u16)ITNL_TIMEOUT_CONST));
else
asd_ddbsite_write_word(asd_ha, ddb, ITNL_TIMEOUT,
(u16)ITNL_TIMEOUT_CONST);
}
return 0;
}
static int asd_init_sata_pm_table_ddb(struct domain_device *dev)
{
struct asd_ha_struct *asd_ha = dev->port->ha->lldd_ha;
int ddb, i;
ddb = asd_get_ddb(asd_ha);
if (ddb < 0)
return ddb;
for (i = 0; i < 32; i += 2)
asd_ddbsite_write_word(asd_ha, ddb, i, 0xFFFF);
asd_ddbsite_write_word(asd_ha, (int) (unsigned long) dev->lldd_dev,
SISTER_DDB, ddb);
return 0;
}
#define PM_PORT_FLAGS offsetof(struct asd_ddb_sata_pm_port, pm_port_flags)
#define PARENT_DDB offsetof(struct asd_ddb_sata_pm_port, parent_ddb)
/**
* asd_init_sata_pm_port_ddb -- SATA Port Multiplier Port
* dev: pointer to domain device
*
* For SATA Port Multiplier Ports we need to allocate one SATA Port
* Multiplier Port DDB and depending on whether the target on it
* supports SATA II NCQ, one SATA Tag DDB.
*/
static int asd_init_sata_pm_port_ddb(struct domain_device *dev)
{
int ddb, i, parent_ddb, pmtable_ddb;
struct asd_ha_struct *asd_ha = dev->port->ha->lldd_ha;
u8 flags;
ddb = asd_get_ddb(asd_ha);
if (ddb < 0)
return ddb;
asd_set_ddb_type(dev);
flags = (dev->sata_dev.port_no << 4) | PM_PORT_SET;
asd_ddbsite_write_byte(asd_ha, ddb, PM_PORT_FLAGS, flags);
asd_ddbsite_write_word(asd_ha, ddb, SISTER_DDB, 0xFFFF);
asd_ddbsite_write_word(asd_ha, ddb, ATA_CMD_SCBPTR, 0xFFFF);
asd_init_sata(dev);
parent_ddb = (int) (unsigned long) dev->parent->lldd_dev;
asd_ddbsite_write_word(asd_ha, ddb, PARENT_DDB, parent_ddb);
pmtable_ddb = asd_ddbsite_read_word(asd_ha, parent_ddb, SISTER_DDB);
asd_ddbsite_write_word(asd_ha, pmtable_ddb, dev->sata_dev.port_no,ddb);
if (asd_ddbsite_read_byte(asd_ha, ddb, NUM_SATA_TAGS) > 0) {
i = asd_init_sata_tag_ddb(dev);
if (i < 0) {
asd_free_ddb(asd_ha, ddb);
return i;
}
}
return 0;
}
static int asd_init_initiator_ddb(struct domain_device *dev)
{
return -ENODEV;
}
/**
* asd_init_sata_pm_ddb -- SATA Port Multiplier
* dev: pointer to domain device
*
* For STP and direct-attached SATA Port Multipliers we need
* one target port DDB entry and one SATA PM table DDB entry.
*/
static int asd_init_sata_pm_ddb(struct domain_device *dev)
{
int res = 0;
res = asd_init_target_ddb(dev);
if (res)
goto out;
res = asd_init_sata_pm_table_ddb(dev);
if (res)
asd_free_ddb(dev->port->ha->lldd_ha,
(int) (unsigned long) dev->lldd_dev);
out:
return res;
}
int asd_dev_found(struct domain_device *dev)
{
int res = 0;
switch (dev->dev_type) {
case SATA_PM:
res = asd_init_sata_pm_ddb(dev);
break;
case SATA_PM_PORT:
res = asd_init_sata_pm_port_ddb(dev);
break;
default:
if (dev->tproto)
res = asd_init_target_ddb(dev);
else
res = asd_init_initiator_ddb(dev);
}
return res;
}
void asd_dev_gone(struct domain_device *dev)
{
int ddb, sister_ddb;
struct asd_ha_struct *asd_ha = dev->port->ha->lldd_ha;
ddb = (int) (unsigned long) dev->lldd_dev;
sister_ddb = asd_ddbsite_read_word(asd_ha, ddb, SISTER_DDB);
if (sister_ddb != 0xFFFF)
asd_free_ddb(asd_ha, sister_ddb);
asd_free_ddb(asd_ha, ddb);
dev->lldd_dev = NULL;
}

View File

@@ -0,0 +1,959 @@
/*
* Aic94xx SAS/SATA driver dump interface.
*
* Copyright (C) 2004 Adaptec, Inc. All rights reserved.
* Copyright (C) 2004 David Chaw <david_chaw@adaptec.com>
* Copyright (C) 2005 Luben Tuikov <luben_tuikov@adaptec.com>
*
* This file is licensed under GPLv2.
*
* This file is part of the aic94xx driver.
*
* The aic94xx driver 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.
*
* The aic94xx driver 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 the aic94xx driver; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*
* 2005/07/14/LT Complete overhaul of this file. Update pages, register
* locations, names, etc. Make use of macros. Print more information.
* Print all cseq and lseq mip and mdp.
*
*/
#include "linux/pci.h"
#include "aic94xx.h"
#include "aic94xx_reg.h"
#include "aic94xx_reg_def.h"
#include "aic94xx_sas.h"
#include "aic94xx_dump.h"
#ifdef ASD_DEBUG
#define MD(x) (1 << (x))
#define MODE_COMMON (1 << 31)
#define MODE_0_7 (0xFF)
static const struct lseq_cio_regs {
char *name;
u32 offs;
u8 width;
u32 mode;
} LSEQmCIOREGS[] = {
{"LmMnSCBPTR", 0x20, 16, MD(0)|MD(1)|MD(2)|MD(3)|MD(4) },
{"LmMnDDBPTR", 0x22, 16, MD(0)|MD(1)|MD(2)|MD(3)|MD(4) },
{"LmREQMBX", 0x30, 32, MODE_COMMON },
{"LmRSPMBX", 0x34, 32, MODE_COMMON },
{"LmMnINT", 0x38, 32, MODE_0_7 },
{"LmMnINTEN", 0x3C, 32, MODE_0_7 },
{"LmXMTPRIMD", 0x40, 32, MODE_COMMON },
{"LmXMTPRIMCS", 0x44, 8, MODE_COMMON },
{"LmCONSTAT", 0x45, 8, MODE_COMMON },
{"LmMnDMAERRS", 0x46, 8, MD(0)|MD(1) },
{"LmMnSGDMAERRS", 0x47, 8, MD(0)|MD(1) },
{"LmMnEXPHDRP", 0x48, 8, MD(0) },
{"LmMnSASAALIGN", 0x48, 8, MD(1) },
{"LmMnMSKHDRP", 0x49, 8, MD(0) },
{"LmMnSTPALIGN", 0x49, 8, MD(1) },
{"LmMnRCVHDRP", 0x4A, 8, MD(0) },
{"LmMnXMTHDRP", 0x4A, 8, MD(1) },
{"LmALIGNMODE", 0x4B, 8, MD(1) },
{"LmMnEXPRCVCNT", 0x4C, 32, MD(0) },
{"LmMnXMTCNT", 0x4C, 32, MD(1) },
{"LmMnCURRTAG", 0x54, 16, MD(0) },
{"LmMnPREVTAG", 0x56, 16, MD(0) },
{"LmMnACKOFS", 0x58, 8, MD(1) },
{"LmMnXFRLVL", 0x59, 8, MD(0)|MD(1) },
{"LmMnSGDMACTL", 0x5A, 8, MD(0)|MD(1) },
{"LmMnSGDMASTAT", 0x5B, 8, MD(0)|MD(1) },
{"LmMnDDMACTL", 0x5C, 8, MD(0)|MD(1) },
{"LmMnDDMASTAT", 0x5D, 8, MD(0)|MD(1) },
{"LmMnDDMAMODE", 0x5E, 16, MD(0)|MD(1) },
{"LmMnPIPECTL", 0x61, 8, MD(0)|MD(1) },
{"LmMnACTSCB", 0x62, 16, MD(0)|MD(1) },
{"LmMnSGBHADR", 0x64, 8, MD(0)|MD(1) },
{"LmMnSGBADR", 0x65, 8, MD(0)|MD(1) },
{"LmMnSGDCNT", 0x66, 8, MD(0)|MD(1) },
{"LmMnSGDMADR", 0x68, 32, MD(0)|MD(1) },
{"LmMnSGDMADR", 0x6C, 32, MD(0)|MD(1) },
{"LmMnXFRCNT", 0x70, 32, MD(0)|MD(1) },
{"LmMnXMTCRC", 0x74, 32, MD(1) },
{"LmCURRTAG", 0x74, 16, MD(0) },
{"LmPREVTAG", 0x76, 16, MD(0) },
{"LmMnDPSEL", 0x7B, 8, MD(0)|MD(1) },
{"LmDPTHSTAT", 0x7C, 8, MODE_COMMON },
{"LmMnHOLDLVL", 0x7D, 8, MD(0) },
{"LmMnSATAFS", 0x7E, 8, MD(1) },
{"LmMnCMPLTSTAT", 0x7F, 8, MD(0)|MD(1) },
{"LmPRMSTAT0", 0x80, 32, MODE_COMMON },
{"LmPRMSTAT1", 0x84, 32, MODE_COMMON },
{"LmGPRMINT", 0x88, 8, MODE_COMMON },
{"LmMnCURRSCB", 0x8A, 16, MD(0) },
{"LmPRMICODE", 0x8C, 32, MODE_COMMON },
{"LmMnRCVCNT", 0x90, 16, MD(0) },
{"LmMnBUFSTAT", 0x92, 16, MD(0) },
{"LmMnXMTHDRSIZE",0x92, 8, MD(1) },
{"LmMnXMTSIZE", 0x93, 8, MD(1) },
{"LmMnTGTXFRCNT", 0x94, 32, MD(0) },
{"LmMnEXPROFS", 0x98, 32, MD(0) },
{"LmMnXMTROFS", 0x98, 32, MD(1) },
{"LmMnRCVROFS", 0x9C, 32, MD(0) },
{"LmCONCTL", 0xA0, 16, MODE_COMMON },
{"LmBITLTIMER", 0xA2, 16, MODE_COMMON },
{"LmWWNLOW", 0xA8, 32, MODE_COMMON },
{"LmWWNHIGH", 0xAC, 32, MODE_COMMON },
{"LmMnFRMERR", 0xB0, 32, MD(0) },
{"LmMnFRMERREN", 0xB4, 32, MD(0) },
{"LmAWTIMER", 0xB8, 16, MODE_COMMON },
{"LmAWTCTL", 0xBA, 8, MODE_COMMON },
{"LmMnHDRCMPS", 0xC0, 32, MD(0) },
{"LmMnXMTSTAT", 0xC4, 8, MD(1) },
{"LmHWTSTATEN", 0xC5, 8, MODE_COMMON },
{"LmMnRRDYRC", 0xC6, 8, MD(0) },
{"LmMnRRDYTC", 0xC6, 8, MD(1) },
{"LmHWTSTAT", 0xC7, 8, MODE_COMMON },
{"LmMnDATABUFADR",0xC8, 16, MD(0)|MD(1) },
{"LmDWSSTATUS", 0xCB, 8, MODE_COMMON },
{"LmMnACTSTAT", 0xCE, 16, MD(0)|MD(1) },
{"LmMnREQSCB", 0xD2, 16, MD(0)|MD(1) },
{"LmXXXPRIM", 0xD4, 32, MODE_COMMON },
{"LmRCVASTAT", 0xD9, 8, MODE_COMMON },
{"LmINTDIS1", 0xDA, 8, MODE_COMMON },
{"LmPSTORESEL", 0xDB, 8, MODE_COMMON },
{"LmPSTORE", 0xDC, 32, MODE_COMMON },
{"LmPRIMSTAT0EN", 0xE0, 32, MODE_COMMON },
{"LmPRIMSTAT1EN", 0xE4, 32, MODE_COMMON },
{"LmDONETCTL", 0xF2, 16, MODE_COMMON },
{NULL, 0, 0, 0 }
};
/*
static struct lseq_cio_regs LSEQmOOBREGS[] = {
{"OOB_BFLTR" ,0x100, 8, MD(5)},
{"OOB_INIT_MIN" ,0x102,16, MD(5)},
{"OOB_INIT_MAX" ,0x104,16, MD(5)},
{"OOB_INIT_NEG" ,0x106,16, MD(5)},
{"OOB_SAS_MIN" ,0x108,16, MD(5)},
{"OOB_SAS_MAX" ,0x10A,16, MD(5)},
{"OOB_SAS_NEG" ,0x10C,16, MD(5)},
{"OOB_WAKE_MIN" ,0x10E,16, MD(5)},
{"OOB_WAKE_MAX" ,0x110,16, MD(5)},
{"OOB_WAKE_NEG" ,0x112,16, MD(5)},
{"OOB_IDLE_MAX" ,0x114,16, MD(5)},
{"OOB_BURST_MAX" ,0x116,16, MD(5)},
{"OOB_XMIT_BURST" ,0x118, 8, MD(5)},
{"OOB_SEND_PAIRS" ,0x119, 8, MD(5)},
{"OOB_INIT_IDLE" ,0x11A, 8, MD(5)},
{"OOB_INIT_NEGO" ,0x11C, 8, MD(5)},
{"OOB_SAS_IDLE" ,0x11E, 8, MD(5)},
{"OOB_SAS_NEGO" ,0x120, 8, MD(5)},
{"OOB_WAKE_IDLE" ,0x122, 8, MD(5)},
{"OOB_WAKE_NEGO" ,0x124, 8, MD(5)},
{"OOB_DATA_KBITS" ,0x126, 8, MD(5)},
{"OOB_BURST_DATA" ,0x128,32, MD(5)},
{"OOB_ALIGN_0_DATA" ,0x12C,32, MD(5)},
{"OOB_ALIGN_1_DATA" ,0x130,32, MD(5)},
{"OOB_SYNC_DATA" ,0x134,32, MD(5)},
{"OOB_D10_2_DATA" ,0x138,32, MD(5)},
{"OOB_PHY_RST_CNT" ,0x13C,32, MD(5)},
{"OOB_SIG_GEN" ,0x140, 8, MD(5)},
{"OOB_XMIT" ,0x141, 8, MD(5)},
{"FUNCTION_MAKS" ,0x142, 8, MD(5)},
{"OOB_MODE" ,0x143, 8, MD(5)},
{"CURRENT_STATUS" ,0x144, 8, MD(5)},
{"SPEED_MASK" ,0x145, 8, MD(5)},
{"PRIM_COUNT" ,0x146, 8, MD(5)},
{"OOB_SIGNALS" ,0x148, 8, MD(5)},
{"OOB_DATA_DET" ,0x149, 8, MD(5)},
{"OOB_TIME_OUT" ,0x14C, 8, MD(5)},
{"OOB_TIMER_ENABLE" ,0x14D, 8, MD(5)},
{"OOB_STATUS" ,0x14E, 8, MD(5)},
{"HOT_PLUG_DELAY" ,0x150, 8, MD(5)},
{"RCD_DELAY" ,0x151, 8, MD(5)},
{"COMSAS_TIMER" ,0x152, 8, MD(5)},
{"SNTT_DELAY" ,0x153, 8, MD(5)},
{"SPD_CHNG_DELAY" ,0x154, 8, MD(5)},
{"SNLT_DELAY" ,0x155, 8, MD(5)},
{"SNWT_DELAY" ,0x156, 8, MD(5)},
{"ALIGN_DELAY" ,0x157, 8, MD(5)},
{"INT_ENABLE_0" ,0x158, 8, MD(5)},
{"INT_ENABLE_1" ,0x159, 8, MD(5)},
{"INT_ENABLE_2" ,0x15A, 8, MD(5)},
{"INT_ENABLE_3" ,0x15B, 8, MD(5)},
{"OOB_TEST_REG" ,0x15C, 8, MD(5)},
{"PHY_CONTROL_0" ,0x160, 8, MD(5)},
{"PHY_CONTROL_1" ,0x161, 8, MD(5)},
{"PHY_CONTROL_2" ,0x162, 8, MD(5)},
{"PHY_CONTROL_3" ,0x163, 8, MD(5)},
{"PHY_OOB_CAL_TX" ,0x164, 8, MD(5)},
{"PHY_OOB_CAL_RX" ,0x165, 8, MD(5)},
{"OOB_PHY_CAL_TX" ,0x166, 8, MD(5)},
{"OOB_PHY_CAL_RX" ,0x167, 8, MD(5)},
{"PHY_CONTROL_4" ,0x168, 8, MD(5)},
{"PHY_TEST" ,0x169, 8, MD(5)},
{"PHY_PWR_CTL" ,0x16A, 8, MD(5)},
{"PHY_PWR_DELAY" ,0x16B, 8, MD(5)},
{"OOB_SM_CON" ,0x16C, 8, MD(5)},
{"ADDR_TRAP_1" ,0x16D, 8, MD(5)},
{"ADDR_NEXT_1" ,0x16E, 8, MD(5)},
{"NEXT_ST_1" ,0x16F, 8, MD(5)},
{"OOB_SM_STATE" ,0x170, 8, MD(5)},
{"ADDR_TRAP_2" ,0x171, 8, MD(5)},
{"ADDR_NEXT_2" ,0x172, 8, MD(5)},
{"NEXT_ST_2" ,0x173, 8, MD(5)},
{NULL, 0, 0, 0 }
};
*/
#define STR_8BIT " %30s[0x%04x]:0x%02x\n"
#define STR_16BIT " %30s[0x%04x]:0x%04x\n"
#define STR_32BIT " %30s[0x%04x]:0x%08x\n"
#define STR_64BIT " %30s[0x%04x]:0x%llx\n"
#define PRINT_REG_8bit(_ha, _n, _r) asd_printk(STR_8BIT, #_n, _n, \
asd_read_reg_byte(_ha, _r))
#define PRINT_REG_16bit(_ha, _n, _r) asd_printk(STR_16BIT, #_n, _n, \
asd_read_reg_word(_ha, _r))
#define PRINT_REG_32bit(_ha, _n, _r) asd_printk(STR_32BIT, #_n, _n, \
asd_read_reg_dword(_ha, _r))
#define PRINT_CREG_8bit(_ha, _n) asd_printk(STR_8BIT, #_n, _n, \
asd_read_reg_byte(_ha, C##_n))
#define PRINT_CREG_16bit(_ha, _n) asd_printk(STR_16BIT, #_n, _n, \
asd_read_reg_word(_ha, C##_n))
#define PRINT_CREG_32bit(_ha, _n) asd_printk(STR_32BIT, #_n, _n, \
asd_read_reg_dword(_ha, C##_n))
#define MSTR_8BIT " Mode:%02d %30s[0x%04x]:0x%02x\n"
#define MSTR_16BIT " Mode:%02d %30s[0x%04x]:0x%04x\n"
#define MSTR_32BIT " Mode:%02d %30s[0x%04x]:0x%08x\n"
#define PRINT_MREG_8bit(_ha, _m, _n, _r) asd_printk(MSTR_8BIT, _m, #_n, _n, \
asd_read_reg_byte(_ha, _r))
#define PRINT_MREG_16bit(_ha, _m, _n, _r) asd_printk(MSTR_16BIT, _m, #_n, _n, \
asd_read_reg_word(_ha, _r))
#define PRINT_MREG_32bit(_ha, _m, _n, _r) asd_printk(MSTR_32BIT, _m, #_n, _n, \
asd_read_reg_dword(_ha, _r))
/* can also be used for MD when the register is mode aware already */
#define PRINT_MIS_byte(_ha, _n) asd_printk(STR_8BIT, #_n,CSEQ_##_n-CMAPPEDSCR,\
asd_read_reg_byte(_ha, CSEQ_##_n))
#define PRINT_MIS_word(_ha, _n) asd_printk(STR_16BIT,#_n,CSEQ_##_n-CMAPPEDSCR,\
asd_read_reg_word(_ha, CSEQ_##_n))
#define PRINT_MIS_dword(_ha, _n) \
asd_printk(STR_32BIT,#_n,CSEQ_##_n-CMAPPEDSCR,\
asd_read_reg_dword(_ha, CSEQ_##_n))
#define PRINT_MIS_qword(_ha, _n) \
asd_printk(STR_64BIT, #_n,CSEQ_##_n-CMAPPEDSCR, \
(unsigned long long)(((u64)asd_read_reg_dword(_ha, CSEQ_##_n)) \
| (((u64)asd_read_reg_dword(_ha, (CSEQ_##_n)+4))<<32)))
#define CMDP_REG(_n, _m) (_m*(CSEQ_PAGE_SIZE*2)+CSEQ_##_n)
#define PRINT_CMDP_word(_ha, _n) \
asd_printk("%20s 0x%04x 0x%04x 0x%04x 0x%04x 0x%04x 0x%04x 0x%04x 0x%04x\n", \
#_n, \
asd_read_reg_word(_ha, CMDP_REG(_n, 0)), \
asd_read_reg_word(_ha, CMDP_REG(_n, 1)), \
asd_read_reg_word(_ha, CMDP_REG(_n, 2)), \
asd_read_reg_word(_ha, CMDP_REG(_n, 3)), \
asd_read_reg_word(_ha, CMDP_REG(_n, 4)), \
asd_read_reg_word(_ha, CMDP_REG(_n, 5)), \
asd_read_reg_word(_ha, CMDP_REG(_n, 6)), \
asd_read_reg_word(_ha, CMDP_REG(_n, 7)))
#define PRINT_CMDP_byte(_ha, _n) \
asd_printk("%20s 0x%04x 0x%04x 0x%04x 0x%04x 0x%04x 0x%04x 0x%04x 0x%04x\n", \
#_n, \
asd_read_reg_byte(_ha, CMDP_REG(_n, 0)), \
asd_read_reg_byte(_ha, CMDP_REG(_n, 1)), \
asd_read_reg_byte(_ha, CMDP_REG(_n, 2)), \
asd_read_reg_byte(_ha, CMDP_REG(_n, 3)), \
asd_read_reg_byte(_ha, CMDP_REG(_n, 4)), \
asd_read_reg_byte(_ha, CMDP_REG(_n, 5)), \
asd_read_reg_byte(_ha, CMDP_REG(_n, 6)), \
asd_read_reg_byte(_ha, CMDP_REG(_n, 7)))
static void asd_dump_cseq_state(struct asd_ha_struct *asd_ha)
{
int mode;
asd_printk("CSEQ STATE\n");
asd_printk("ARP2 REGISTERS\n");
PRINT_CREG_32bit(asd_ha, ARP2CTL);
PRINT_CREG_32bit(asd_ha, ARP2INT);
PRINT_CREG_32bit(asd_ha, ARP2INTEN);
PRINT_CREG_8bit(asd_ha, MODEPTR);
PRINT_CREG_8bit(asd_ha, ALTMODE);
PRINT_CREG_8bit(asd_ha, FLAG);
PRINT_CREG_8bit(asd_ha, ARP2INTCTL);
PRINT_CREG_16bit(asd_ha, STACK);
PRINT_CREG_16bit(asd_ha, PRGMCNT);
PRINT_CREG_16bit(asd_ha, ACCUM);
PRINT_CREG_16bit(asd_ha, SINDEX);
PRINT_CREG_16bit(asd_ha, DINDEX);
PRINT_CREG_8bit(asd_ha, SINDIR);
PRINT_CREG_8bit(asd_ha, DINDIR);
PRINT_CREG_8bit(asd_ha, JUMLDIR);
PRINT_CREG_8bit(asd_ha, ARP2HALTCODE);
PRINT_CREG_16bit(asd_ha, CURRADDR);
PRINT_CREG_16bit(asd_ha, LASTADDR);
PRINT_CREG_16bit(asd_ha, NXTLADDR);
asd_printk("IOP REGISTERS\n");
PRINT_REG_32bit(asd_ha, BISTCTL1, CBISTCTL);
PRINT_CREG_32bit(asd_ha, MAPPEDSCR);
asd_printk("CIO REGISTERS\n");
for (mode = 0; mode < 9; mode++)
PRINT_MREG_16bit(asd_ha, mode, MnSCBPTR, CMnSCBPTR(mode));
PRINT_MREG_16bit(asd_ha, 15, MnSCBPTR, CMnSCBPTR(15));
for (mode = 0; mode < 9; mode++)
PRINT_MREG_16bit(asd_ha, mode, MnDDBPTR, CMnDDBPTR(mode));
PRINT_MREG_16bit(asd_ha, 15, MnDDBPTR, CMnDDBPTR(15));
for (mode = 0; mode < 8; mode++)
PRINT_MREG_32bit(asd_ha, mode, MnREQMBX, CMnREQMBX(mode));
for (mode = 0; mode < 8; mode++)
PRINT_MREG_32bit(asd_ha, mode, MnRSPMBX, CMnRSPMBX(mode));
for (mode = 0; mode < 8; mode++)
PRINT_MREG_32bit(asd_ha, mode, MnINT, CMnINT(mode));
for (mode = 0; mode < 8; mode++)
PRINT_MREG_32bit(asd_ha, mode, MnINTEN, CMnINTEN(mode));
PRINT_CREG_8bit(asd_ha, SCRATCHPAGE);
for (mode = 0; mode < 8; mode++)
PRINT_MREG_8bit(asd_ha, mode, MnSCRATCHPAGE,
CMnSCRATCHPAGE(mode));
PRINT_REG_32bit(asd_ha, CLINKCON, CLINKCON);
PRINT_REG_8bit(asd_ha, CCONMSK, CCONMSK);
PRINT_REG_8bit(asd_ha, CCONEXIST, CCONEXIST);
PRINT_REG_16bit(asd_ha, CCONMODE, CCONMODE);
PRINT_REG_32bit(asd_ha, CTIMERCALC, CTIMERCALC);
PRINT_REG_8bit(asd_ha, CINTDIS, CINTDIS);
asd_printk("SCRATCH MEMORY\n");
asd_printk("MIP 4 >>>>>\n");
PRINT_MIS_word(asd_ha, Q_EXE_HEAD);
PRINT_MIS_word(asd_ha, Q_EXE_TAIL);
PRINT_MIS_word(asd_ha, Q_DONE_HEAD);
PRINT_MIS_word(asd_ha, Q_DONE_TAIL);
PRINT_MIS_word(asd_ha, Q_SEND_HEAD);
PRINT_MIS_word(asd_ha, Q_SEND_TAIL);
PRINT_MIS_word(asd_ha, Q_DMA2CHIM_HEAD);
PRINT_MIS_word(asd_ha, Q_DMA2CHIM_TAIL);
PRINT_MIS_word(asd_ha, Q_COPY_HEAD);
PRINT_MIS_word(asd_ha, Q_COPY_TAIL);
PRINT_MIS_word(asd_ha, REG0);
PRINT_MIS_word(asd_ha, REG1);
PRINT_MIS_dword(asd_ha, REG2);
PRINT_MIS_byte(asd_ha, LINK_CTL_Q_MAP);
PRINT_MIS_byte(asd_ha, MAX_CSEQ_MODE);
PRINT_MIS_byte(asd_ha, FREE_LIST_HACK_COUNT);
asd_printk("MIP 5 >>>>\n");
PRINT_MIS_qword(asd_ha, EST_NEXUS_REQ_QUEUE);
PRINT_MIS_qword(asd_ha, EST_NEXUS_REQ_COUNT);
PRINT_MIS_word(asd_ha, Q_EST_NEXUS_HEAD);
PRINT_MIS_word(asd_ha, Q_EST_NEXUS_TAIL);
PRINT_MIS_word(asd_ha, NEED_EST_NEXUS_SCB);
PRINT_MIS_byte(asd_ha, EST_NEXUS_REQ_HEAD);
PRINT_MIS_byte(asd_ha, EST_NEXUS_REQ_TAIL);
PRINT_MIS_byte(asd_ha, EST_NEXUS_SCB_OFFSET);
asd_printk("MIP 6 >>>>\n");
PRINT_MIS_word(asd_ha, INT_ROUT_RET_ADDR0);
PRINT_MIS_word(asd_ha, INT_ROUT_RET_ADDR1);
PRINT_MIS_word(asd_ha, INT_ROUT_SCBPTR);
PRINT_MIS_byte(asd_ha, INT_ROUT_MODE);
PRINT_MIS_byte(asd_ha, ISR_SCRATCH_FLAGS);
PRINT_MIS_word(asd_ha, ISR_SAVE_SINDEX);
PRINT_MIS_word(asd_ha, ISR_SAVE_DINDEX);
PRINT_MIS_word(asd_ha, Q_MONIRTT_HEAD);
PRINT_MIS_word(asd_ha, Q_MONIRTT_TAIL);
PRINT_MIS_byte(asd_ha, FREE_SCB_MASK);
PRINT_MIS_word(asd_ha, BUILTIN_FREE_SCB_HEAD);
PRINT_MIS_word(asd_ha, BUILTIN_FREE_SCB_TAIL);
PRINT_MIS_word(asd_ha, EXTENDED_FREE_SCB_HEAD);
PRINT_MIS_word(asd_ha, EXTENDED_FREE_SCB_TAIL);
asd_printk("MIP 7 >>>>\n");
PRINT_MIS_qword(asd_ha, EMPTY_REQ_QUEUE);
PRINT_MIS_qword(asd_ha, EMPTY_REQ_COUNT);
PRINT_MIS_word(asd_ha, Q_EMPTY_HEAD);
PRINT_MIS_word(asd_ha, Q_EMPTY_TAIL);
PRINT_MIS_word(asd_ha, NEED_EMPTY_SCB);
PRINT_MIS_byte(asd_ha, EMPTY_REQ_HEAD);
PRINT_MIS_byte(asd_ha, EMPTY_REQ_TAIL);
PRINT_MIS_byte(asd_ha, EMPTY_SCB_OFFSET);
PRINT_MIS_word(asd_ha, PRIMITIVE_DATA);
PRINT_MIS_dword(asd_ha, TIMEOUT_CONST);
asd_printk("MDP 0 >>>>\n");
asd_printk("%-20s %6s %6s %6s %6s %6s %6s %6s %6s\n",
"Mode: ", "0", "1", "2", "3", "4", "5", "6", "7");
PRINT_CMDP_word(asd_ha, LRM_SAVE_SINDEX);
PRINT_CMDP_word(asd_ha, LRM_SAVE_SCBPTR);
PRINT_CMDP_word(asd_ha, Q_LINK_HEAD);
PRINT_CMDP_word(asd_ha, Q_LINK_TAIL);
PRINT_CMDP_byte(asd_ha, LRM_SAVE_SCRPAGE);
asd_printk("MDP 0 Mode 8 >>>>\n");
PRINT_MIS_word(asd_ha, RET_ADDR);
PRINT_MIS_word(asd_ha, RET_SCBPTR);
PRINT_MIS_word(asd_ha, SAVE_SCBPTR);
PRINT_MIS_word(asd_ha, EMPTY_TRANS_CTX);
PRINT_MIS_word(asd_ha, RESP_LEN);
PRINT_MIS_word(asd_ha, TMF_SCBPTR);
PRINT_MIS_word(asd_ha, GLOBAL_PREV_SCB);
PRINT_MIS_word(asd_ha, GLOBAL_HEAD);
PRINT_MIS_word(asd_ha, CLEAR_LU_HEAD);
PRINT_MIS_byte(asd_ha, TMF_OPCODE);
PRINT_MIS_byte(asd_ha, SCRATCH_FLAGS);
PRINT_MIS_word(asd_ha, HSB_SITE);
PRINT_MIS_word(asd_ha, FIRST_INV_SCB_SITE);
PRINT_MIS_word(asd_ha, FIRST_INV_DDB_SITE);
asd_printk("MDP 1 Mode 8 >>>>\n");
PRINT_MIS_qword(asd_ha, LUN_TO_CLEAR);
PRINT_MIS_qword(asd_ha, LUN_TO_CHECK);
asd_printk("MDP 2 Mode 8 >>>>\n");
PRINT_MIS_qword(asd_ha, HQ_NEW_POINTER);
PRINT_MIS_qword(asd_ha, HQ_DONE_BASE);
PRINT_MIS_dword(asd_ha, HQ_DONE_POINTER);
PRINT_MIS_byte(asd_ha, HQ_DONE_PASS);
}
#define PRINT_LREG_8bit(_h, _lseq, _n) \
asd_printk(STR_8BIT, #_n, _n, asd_read_reg_byte(_h, Lm##_n(_lseq)))
#define PRINT_LREG_16bit(_h, _lseq, _n) \
asd_printk(STR_16BIT, #_n, _n, asd_read_reg_word(_h, Lm##_n(_lseq)))
#define PRINT_LREG_32bit(_h, _lseq, _n) \
asd_printk(STR_32BIT, #_n, _n, asd_read_reg_dword(_h, Lm##_n(_lseq)))
#define PRINT_LMIP_byte(_h, _lseq, _n) \
asd_printk(STR_8BIT, #_n, LmSEQ_##_n(_lseq)-LmSCRATCH(_lseq), \
asd_read_reg_byte(_h, LmSEQ_##_n(_lseq)))
#define PRINT_LMIP_word(_h, _lseq, _n) \
asd_printk(STR_16BIT, #_n, LmSEQ_##_n(_lseq)-LmSCRATCH(_lseq), \
asd_read_reg_word(_h, LmSEQ_##_n(_lseq)))
#define PRINT_LMIP_dword(_h, _lseq, _n) \
asd_printk(STR_32BIT, #_n, LmSEQ_##_n(_lseq)-LmSCRATCH(_lseq), \
asd_read_reg_dword(_h, LmSEQ_##_n(_lseq)))
#define PRINT_LMIP_qword(_h, _lseq, _n) \
asd_printk(STR_64BIT, #_n, LmSEQ_##_n(_lseq)-LmSCRATCH(_lseq), \
(unsigned long long)(((unsigned long long) \
asd_read_reg_dword(_h, LmSEQ_##_n(_lseq))) \
| (((unsigned long long) \
asd_read_reg_dword(_h, LmSEQ_##_n(_lseq)+4))<<32)))
static void asd_print_lseq_cio_reg(struct asd_ha_struct *asd_ha,
u32 lseq_cio_addr, int i)
{
switch (LSEQmCIOREGS[i].width) {
case 8:
asd_printk("%20s[0x%x]: 0x%02x\n", LSEQmCIOREGS[i].name,
LSEQmCIOREGS[i].offs,
asd_read_reg_byte(asd_ha, lseq_cio_addr +
LSEQmCIOREGS[i].offs));
break;
case 16:
asd_printk("%20s[0x%x]: 0x%04x\n", LSEQmCIOREGS[i].name,
LSEQmCIOREGS[i].offs,
asd_read_reg_word(asd_ha, lseq_cio_addr +
LSEQmCIOREGS[i].offs));
break;
case 32:
asd_printk("%20s[0x%x]: 0x%08x\n", LSEQmCIOREGS[i].name,
LSEQmCIOREGS[i].offs,
asd_read_reg_dword(asd_ha, lseq_cio_addr +
LSEQmCIOREGS[i].offs));
break;
}
}
static void asd_dump_lseq_state(struct asd_ha_struct *asd_ha, int lseq)
{
u32 moffs;
int mode;
asd_printk("LSEQ %d STATE\n", lseq);
asd_printk("LSEQ%d: ARP2 REGISTERS\n", lseq);
PRINT_LREG_32bit(asd_ha, lseq, ARP2CTL);
PRINT_LREG_32bit(asd_ha, lseq, ARP2INT);
PRINT_LREG_32bit(asd_ha, lseq, ARP2INTEN);
PRINT_LREG_8bit(asd_ha, lseq, MODEPTR);
PRINT_LREG_8bit(asd_ha, lseq, ALTMODE);
PRINT_LREG_8bit(asd_ha, lseq, FLAG);
PRINT_LREG_8bit(asd_ha, lseq, ARP2INTCTL);
PRINT_LREG_16bit(asd_ha, lseq, STACK);
PRINT_LREG_16bit(asd_ha, lseq, PRGMCNT);
PRINT_LREG_16bit(asd_ha, lseq, ACCUM);
PRINT_LREG_16bit(asd_ha, lseq, SINDEX);
PRINT_LREG_16bit(asd_ha, lseq, DINDEX);
PRINT_LREG_8bit(asd_ha, lseq, SINDIR);
PRINT_LREG_8bit(asd_ha, lseq, DINDIR);
PRINT_LREG_8bit(asd_ha, lseq, JUMLDIR);
PRINT_LREG_8bit(asd_ha, lseq, ARP2HALTCODE);
PRINT_LREG_16bit(asd_ha, lseq, CURRADDR);
PRINT_LREG_16bit(asd_ha, lseq, LASTADDR);
PRINT_LREG_16bit(asd_ha, lseq, NXTLADDR);
asd_printk("LSEQ%d: IOP REGISTERS\n", lseq);
PRINT_LREG_32bit(asd_ha, lseq, MODECTL);
PRINT_LREG_32bit(asd_ha, lseq, DBGMODE);
PRINT_LREG_32bit(asd_ha, lseq, CONTROL);
PRINT_REG_32bit(asd_ha, BISTCTL0, LmBISTCTL0(lseq));
PRINT_REG_32bit(asd_ha, BISTCTL1, LmBISTCTL1(lseq));
asd_printk("LSEQ%d: CIO REGISTERS\n", lseq);
asd_printk("Mode common:\n");
for (mode = 0; mode < 8; mode++) {
u32 lseq_cio_addr = LmSEQ_PHY_BASE(mode, lseq);
int i;
for (i = 0; LSEQmCIOREGS[i].name; i++)
if (LSEQmCIOREGS[i].mode == MODE_COMMON)
asd_print_lseq_cio_reg(asd_ha,lseq_cio_addr,i);
}
asd_printk("Mode unique:\n");
for (mode = 0; mode < 8; mode++) {
u32 lseq_cio_addr = LmSEQ_PHY_BASE(mode, lseq);
int i;
asd_printk("Mode %d\n", mode);
for (i = 0; LSEQmCIOREGS[i].name; i++) {
if (!(LSEQmCIOREGS[i].mode & (1 << mode)))
continue;
asd_print_lseq_cio_reg(asd_ha, lseq_cio_addr, i);
}
}
asd_printk("SCRATCH MEMORY\n");
asd_printk("LSEQ%d MIP 0 >>>>\n", lseq);
PRINT_LMIP_word(asd_ha, lseq, Q_TGTXFR_HEAD);
PRINT_LMIP_word(asd_ha, lseq, Q_TGTXFR_TAIL);
PRINT_LMIP_byte(asd_ha, lseq, LINK_NUMBER);
PRINT_LMIP_byte(asd_ha, lseq, SCRATCH_FLAGS);
PRINT_LMIP_qword(asd_ha, lseq, CONNECTION_STATE);
PRINT_LMIP_word(asd_ha, lseq, CONCTL);
PRINT_LMIP_byte(asd_ha, lseq, CONSTAT);
PRINT_LMIP_byte(asd_ha, lseq, CONNECTION_MODES);
PRINT_LMIP_word(asd_ha, lseq, REG1_ISR);
PRINT_LMIP_word(asd_ha, lseq, REG2_ISR);
PRINT_LMIP_word(asd_ha, lseq, REG3_ISR);
PRINT_LMIP_qword(asd_ha, lseq,REG0_ISR);
asd_printk("LSEQ%d MIP 1 >>>>\n", lseq);
PRINT_LMIP_word(asd_ha, lseq, EST_NEXUS_SCBPTR0);
PRINT_LMIP_word(asd_ha, lseq, EST_NEXUS_SCBPTR1);
PRINT_LMIP_word(asd_ha, lseq, EST_NEXUS_SCBPTR2);
PRINT_LMIP_word(asd_ha, lseq, EST_NEXUS_SCBPTR3);
PRINT_LMIP_byte(asd_ha, lseq, EST_NEXUS_SCB_OPCODE0);
PRINT_LMIP_byte(asd_ha, lseq, EST_NEXUS_SCB_OPCODE1);
PRINT_LMIP_byte(asd_ha, lseq, EST_NEXUS_SCB_OPCODE2);
PRINT_LMIP_byte(asd_ha, lseq, EST_NEXUS_SCB_OPCODE3);
PRINT_LMIP_byte(asd_ha, lseq, EST_NEXUS_SCB_HEAD);
PRINT_LMIP_byte(asd_ha, lseq, EST_NEXUS_SCB_TAIL);
PRINT_LMIP_byte(asd_ha, lseq, EST_NEXUS_BUF_AVAIL);
PRINT_LMIP_dword(asd_ha, lseq, TIMEOUT_CONST);
PRINT_LMIP_word(asd_ha, lseq, ISR_SAVE_SINDEX);
PRINT_LMIP_word(asd_ha, lseq, ISR_SAVE_DINDEX);
asd_printk("LSEQ%d MIP 2 >>>>\n", lseq);
PRINT_LMIP_word(asd_ha, lseq, EMPTY_SCB_PTR0);
PRINT_LMIP_word(asd_ha, lseq, EMPTY_SCB_PTR1);
PRINT_LMIP_word(asd_ha, lseq, EMPTY_SCB_PTR2);
PRINT_LMIP_word(asd_ha, lseq, EMPTY_SCB_PTR3);
PRINT_LMIP_byte(asd_ha, lseq, EMPTY_SCB_OPCD0);
PRINT_LMIP_byte(asd_ha, lseq, EMPTY_SCB_OPCD1);
PRINT_LMIP_byte(asd_ha, lseq, EMPTY_SCB_OPCD2);
PRINT_LMIP_byte(asd_ha, lseq, EMPTY_SCB_OPCD3);
PRINT_LMIP_byte(asd_ha, lseq, EMPTY_SCB_HEAD);
PRINT_LMIP_byte(asd_ha, lseq, EMPTY_SCB_TAIL);
PRINT_LMIP_byte(asd_ha, lseq, EMPTY_BUFS_AVAIL);
asd_printk("LSEQ%d MIP 3 >>>>\n", lseq);
PRINT_LMIP_dword(asd_ha, lseq, DEV_PRES_TMR_TOUT_CONST);
PRINT_LMIP_dword(asd_ha, lseq, SATA_INTERLOCK_TIMEOUT);
PRINT_LMIP_dword(asd_ha, lseq, SRST_ASSERT_TIMEOUT);
PRINT_LMIP_dword(asd_ha, lseq, RCV_FIS_TIMEOUT);
PRINT_LMIP_dword(asd_ha, lseq, ONE_MILLISEC_TIMEOUT);
PRINT_LMIP_dword(asd_ha, lseq, TEN_MS_COMINIT_TIMEOUT);
PRINT_LMIP_dword(asd_ha, lseq, SMP_RCV_TIMEOUT);
for (mode = 0; mode < 3; mode++) {
asd_printk("LSEQ%d MDP 0 MODE %d >>>>\n", lseq, mode);
moffs = mode * LSEQ_MODE_SCRATCH_SIZE;
asd_printk(STR_16BIT, "RET_ADDR", 0,
asd_read_reg_word(asd_ha, LmSEQ_RET_ADDR(lseq)
+ moffs));
asd_printk(STR_16BIT, "REG0_MODE", 2,
asd_read_reg_word(asd_ha, LmSEQ_REG0_MODE(lseq)
+ moffs));
asd_printk(STR_16BIT, "MODE_FLAGS", 4,
asd_read_reg_word(asd_ha, LmSEQ_MODE_FLAGS(lseq)
+ moffs));
asd_printk(STR_16BIT, "RET_ADDR2", 0x6,
asd_read_reg_word(asd_ha, LmSEQ_RET_ADDR2(lseq)
+ moffs));
asd_printk(STR_16BIT, "RET_ADDR1", 0x8,
asd_read_reg_word(asd_ha, LmSEQ_RET_ADDR1(lseq)
+ moffs));
asd_printk(STR_8BIT, "OPCODE_TO_CSEQ", 0xB,
asd_read_reg_byte(asd_ha, LmSEQ_OPCODE_TO_CSEQ(lseq)
+ moffs));
asd_printk(STR_16BIT, "DATA_TO_CSEQ", 0xC,
asd_read_reg_word(asd_ha, LmSEQ_DATA_TO_CSEQ(lseq)
+ moffs));
}
asd_printk("LSEQ%d MDP 0 MODE 5 >>>>\n", lseq);
moffs = LSEQ_MODE5_PAGE0_OFFSET;
asd_printk(STR_16BIT, "RET_ADDR", 0,
asd_read_reg_word(asd_ha, LmSEQ_RET_ADDR(lseq) + moffs));
asd_printk(STR_16BIT, "REG0_MODE", 2,
asd_read_reg_word(asd_ha, LmSEQ_REG0_MODE(lseq) + moffs));
asd_printk(STR_16BIT, "MODE_FLAGS", 4,
asd_read_reg_word(asd_ha, LmSEQ_MODE_FLAGS(lseq) + moffs));
asd_printk(STR_16BIT, "RET_ADDR2", 0x6,
asd_read_reg_word(asd_ha, LmSEQ_RET_ADDR2(lseq) + moffs));
asd_printk(STR_16BIT, "RET_ADDR1", 0x8,
asd_read_reg_word(asd_ha, LmSEQ_RET_ADDR1(lseq) + moffs));
asd_printk(STR_8BIT, "OPCODE_TO_CSEQ", 0xB,
asd_read_reg_byte(asd_ha, LmSEQ_OPCODE_TO_CSEQ(lseq) + moffs));
asd_printk(STR_16BIT, "DATA_TO_CSEQ", 0xC,
asd_read_reg_word(asd_ha, LmSEQ_DATA_TO_CSEQ(lseq) + moffs));
asd_printk("LSEQ%d MDP 0 MODE 0 >>>>\n", lseq);
PRINT_LMIP_word(asd_ha, lseq, FIRST_INV_DDB_SITE);
PRINT_LMIP_word(asd_ha, lseq, EMPTY_TRANS_CTX);
PRINT_LMIP_word(asd_ha, lseq, RESP_LEN);
PRINT_LMIP_word(asd_ha, lseq, FIRST_INV_SCB_SITE);
PRINT_LMIP_dword(asd_ha, lseq, INTEN_SAVE);
PRINT_LMIP_byte(asd_ha, lseq, LINK_RST_FRM_LEN);
PRINT_LMIP_byte(asd_ha, lseq, LINK_RST_PROTOCOL);
PRINT_LMIP_byte(asd_ha, lseq, RESP_STATUS);
PRINT_LMIP_byte(asd_ha, lseq, LAST_LOADED_SGE);
PRINT_LMIP_byte(asd_ha, lseq, SAVE_SCBPTR);
asd_printk("LSEQ%d MDP 0 MODE 1 >>>>\n", lseq);
PRINT_LMIP_word(asd_ha, lseq, Q_XMIT_HEAD);
PRINT_LMIP_word(asd_ha, lseq, M1_EMPTY_TRANS_CTX);
PRINT_LMIP_word(asd_ha, lseq, INI_CONN_TAG);
PRINT_LMIP_byte(asd_ha, lseq, FAILED_OPEN_STATUS);
PRINT_LMIP_byte(asd_ha, lseq, XMIT_REQUEST_TYPE);
PRINT_LMIP_byte(asd_ha, lseq, M1_RESP_STATUS);
PRINT_LMIP_byte(asd_ha, lseq, M1_LAST_LOADED_SGE);
PRINT_LMIP_word(asd_ha, lseq, M1_SAVE_SCBPTR);
asd_printk("LSEQ%d MDP 0 MODE 2 >>>>\n", lseq);
PRINT_LMIP_word(asd_ha, lseq, PORT_COUNTER);
PRINT_LMIP_word(asd_ha, lseq, PM_TABLE_PTR);
PRINT_LMIP_word(asd_ha, lseq, SATA_INTERLOCK_TMR_SAVE);
PRINT_LMIP_word(asd_ha, lseq, IP_BITL);
PRINT_LMIP_word(asd_ha, lseq, COPY_SMP_CONN_TAG);
PRINT_LMIP_byte(asd_ha, lseq, P0M2_OFFS1AH);
asd_printk("LSEQ%d MDP 0 MODE 4/5 >>>>\n", lseq);
PRINT_LMIP_byte(asd_ha, lseq, SAVED_OOB_STATUS);
PRINT_LMIP_byte(asd_ha, lseq, SAVED_OOB_MODE);
PRINT_LMIP_word(asd_ha, lseq, Q_LINK_HEAD);
PRINT_LMIP_byte(asd_ha, lseq, LINK_RST_ERR);
PRINT_LMIP_byte(asd_ha, lseq, SAVED_OOB_SIGNALS);
PRINT_LMIP_byte(asd_ha, lseq, SAS_RESET_MODE);
PRINT_LMIP_byte(asd_ha, lseq, LINK_RESET_RETRY_COUNT);
PRINT_LMIP_byte(asd_ha, lseq, NUM_LINK_RESET_RETRIES);
PRINT_LMIP_word(asd_ha, lseq, OOB_INT_ENABLES);
PRINT_LMIP_word(asd_ha, lseq, NOTIFY_TIMER_TIMEOUT);
PRINT_LMIP_word(asd_ha, lseq, NOTIFY_TIMER_DOWN_COUNT);
asd_printk("LSEQ%d MDP 1 MODE 0 >>>>\n", lseq);
PRINT_LMIP_qword(asd_ha, lseq, SG_LIST_PTR_ADDR0);
PRINT_LMIP_qword(asd_ha, lseq, SG_LIST_PTR_ADDR1);
asd_printk("LSEQ%d MDP 1 MODE 1 >>>>\n", lseq);
PRINT_LMIP_qword(asd_ha, lseq, M1_SG_LIST_PTR_ADDR0);
PRINT_LMIP_qword(asd_ha, lseq, M1_SG_LIST_PTR_ADDR1);
asd_printk("LSEQ%d MDP 1 MODE 2 >>>>\n", lseq);
PRINT_LMIP_dword(asd_ha, lseq, INVALID_DWORD_COUNT);
PRINT_LMIP_dword(asd_ha, lseq, DISPARITY_ERROR_COUNT);
PRINT_LMIP_dword(asd_ha, lseq, LOSS_OF_SYNC_COUNT);
asd_printk("LSEQ%d MDP 1 MODE 4/5 >>>>\n", lseq);
PRINT_LMIP_dword(asd_ha, lseq, FRAME_TYPE_MASK);
PRINT_LMIP_dword(asd_ha, lseq, HASHED_SRC_ADDR_MASK_PRINT);
PRINT_LMIP_byte(asd_ha, lseq, NUM_FILL_BYTES_MASK);
PRINT_LMIP_word(asd_ha, lseq, TAG_MASK);
PRINT_LMIP_word(asd_ha, lseq, TARGET_PORT_XFER_TAG);
PRINT_LMIP_dword(asd_ha, lseq, DATA_OFFSET);
asd_printk("LSEQ%d MDP 2 MODE 0 >>>>\n", lseq);
PRINT_LMIP_dword(asd_ha, lseq, SMP_RCV_TIMER_TERM_TS);
PRINT_LMIP_byte(asd_ha, lseq, DEVICE_BITS);
PRINT_LMIP_word(asd_ha, lseq, SDB_DDB);
PRINT_LMIP_word(asd_ha, lseq, SDB_NUM_TAGS);
PRINT_LMIP_word(asd_ha, lseq, SDB_CURR_TAG);
asd_printk("LSEQ%d MDP 2 MODE 1 >>>>\n", lseq);
PRINT_LMIP_qword(asd_ha, lseq, TX_ID_ADDR_FRAME);
PRINT_LMIP_dword(asd_ha, lseq, OPEN_TIMER_TERM_TS);
PRINT_LMIP_dword(asd_ha, lseq, SRST_AS_TIMER_TERM_TS);
PRINT_LMIP_dword(asd_ha, lseq, LAST_LOADED_SG_EL);
asd_printk("LSEQ%d MDP 2 MODE 2 >>>>\n", lseq);
PRINT_LMIP_dword(asd_ha, lseq, CLOSE_TIMER_TERM_TS);
PRINT_LMIP_dword(asd_ha, lseq, BREAK_TIMER_TERM_TS);
PRINT_LMIP_dword(asd_ha, lseq, DWS_RESET_TIMER_TERM_TS);
PRINT_LMIP_dword(asd_ha, lseq, SATA_INTERLOCK_TIMER_TERM_TS);
PRINT_LMIP_dword(asd_ha, lseq, MCTL_TIMER_TERM_TS);
asd_printk("LSEQ%d MDP 2 MODE 4/5 >>>>\n", lseq);
PRINT_LMIP_dword(asd_ha, lseq, COMINIT_TIMER_TERM_TS);
PRINT_LMIP_dword(asd_ha, lseq, RCV_ID_TIMER_TERM_TS);
PRINT_LMIP_dword(asd_ha, lseq, RCV_FIS_TIMER_TERM_TS);
PRINT_LMIP_dword(asd_ha, lseq, DEV_PRES_TIMER_TERM_TS);
}
/**
* asd_dump_ddb_site -- dump a CSEQ DDB site
* @asd_ha: pointer to host adapter structure
* @site_no: site number of interest
*/
void asd_dump_target_ddb(struct asd_ha_struct *asd_ha, u16 site_no)
{
if (site_no >= asd_ha->hw_prof.max_ddbs)
return;
#define DDB_FIELDB(__name) \
asd_ddbsite_read_byte(asd_ha, site_no, \
offsetof(struct asd_ddb_ssp_smp_target_port, __name))
#define DDB2_FIELDB(__name) \
asd_ddbsite_read_byte(asd_ha, site_no, \
offsetof(struct asd_ddb_stp_sata_target_port, __name))
#define DDB_FIELDW(__name) \
asd_ddbsite_read_word(asd_ha, site_no, \
offsetof(struct asd_ddb_ssp_smp_target_port, __name))
#define DDB_FIELDD(__name) \
asd_ddbsite_read_dword(asd_ha, site_no, \
offsetof(struct asd_ddb_ssp_smp_target_port, __name))
asd_printk("DDB: 0x%02x\n", site_no);
asd_printk("conn_type: 0x%02x\n", DDB_FIELDB(conn_type));
asd_printk("conn_rate: 0x%02x\n", DDB_FIELDB(conn_rate));
asd_printk("init_conn_tag: 0x%04x\n", be16_to_cpu(DDB_FIELDW(init_conn_tag)));
asd_printk("send_queue_head: 0x%04x\n", be16_to_cpu(DDB_FIELDW(send_queue_head)));
asd_printk("sq_suspended: 0x%02x\n", DDB_FIELDB(sq_suspended));
asd_printk("DDB Type: 0x%02x\n", DDB_FIELDB(ddb_type));
asd_printk("AWT Default: 0x%04x\n", DDB_FIELDW(awt_def));
asd_printk("compat_features: 0x%02x\n", DDB_FIELDB(compat_features));
asd_printk("Pathway Blocked Count: 0x%02x\n",
DDB_FIELDB(pathway_blocked_count));
asd_printk("arb_wait_time: 0x%04x\n", DDB_FIELDW(arb_wait_time));
asd_printk("more_compat_features: 0x%08x\n",
DDB_FIELDD(more_compat_features));
asd_printk("Conn Mask: 0x%02x\n", DDB_FIELDB(conn_mask));
asd_printk("flags: 0x%02x\n", DDB_FIELDB(flags));
asd_printk("flags2: 0x%02x\n", DDB2_FIELDB(flags2));
asd_printk("ExecQ Tail: 0x%04x\n",DDB_FIELDW(exec_queue_tail));
asd_printk("SendQ Tail: 0x%04x\n",DDB_FIELDW(send_queue_tail));
asd_printk("Active Task Count: 0x%04x\n",
DDB_FIELDW(active_task_count));
asd_printk("ITNL Reason: 0x%02x\n", DDB_FIELDB(itnl_reason));
asd_printk("ITNL Timeout Const: 0x%04x\n", DDB_FIELDW(itnl_timeout));
asd_printk("ITNL timestamp: 0x%08x\n", DDB_FIELDD(itnl_timestamp));
}
void asd_dump_ddb_0(struct asd_ha_struct *asd_ha)
{
#define DDB0_FIELDB(__name) \
asd_ddbsite_read_byte(asd_ha, 0, \
offsetof(struct asd_ddb_seq_shared, __name))
#define DDB0_FIELDW(__name) \
asd_ddbsite_read_word(asd_ha, 0, \
offsetof(struct asd_ddb_seq_shared, __name))
#define DDB0_FIELDD(__name) \
asd_ddbsite_read_dword(asd_ha,0 , \
offsetof(struct asd_ddb_seq_shared, __name))
#define DDB0_FIELDA(__name, _o) \
asd_ddbsite_read_byte(asd_ha, 0, \
offsetof(struct asd_ddb_seq_shared, __name)+_o)
asd_printk("DDB: 0\n");
asd_printk("q_free_ddb_head:%04x\n", DDB0_FIELDW(q_free_ddb_head));
asd_printk("q_free_ddb_tail:%04x\n", DDB0_FIELDW(q_free_ddb_tail));
asd_printk("q_free_ddb_cnt:%04x\n", DDB0_FIELDW(q_free_ddb_cnt));
asd_printk("q_used_ddb_head:%04x\n", DDB0_FIELDW(q_used_ddb_head));
asd_printk("q_used_ddb_tail:%04x\n", DDB0_FIELDW(q_used_ddb_tail));
asd_printk("shared_mem_lock:%04x\n", DDB0_FIELDW(shared_mem_lock));
asd_printk("smp_conn_tag:%04x\n", DDB0_FIELDW(smp_conn_tag));
asd_printk("est_nexus_buf_cnt:%04x\n", DDB0_FIELDW(est_nexus_buf_cnt));
asd_printk("est_nexus_buf_thresh:%04x\n",
DDB0_FIELDW(est_nexus_buf_thresh));
asd_printk("conn_not_active:%02x\n", DDB0_FIELDB(conn_not_active));
asd_printk("phy_is_up:%02x\n", DDB0_FIELDB(phy_is_up));
asd_printk("port_map_by_links:%02x %02x %02x %02x "
"%02x %02x %02x %02x\n",
DDB0_FIELDA(port_map_by_links, 0),
DDB0_FIELDA(port_map_by_links, 1),
DDB0_FIELDA(port_map_by_links, 2),
DDB0_FIELDA(port_map_by_links, 3),
DDB0_FIELDA(port_map_by_links, 4),
DDB0_FIELDA(port_map_by_links, 5),
DDB0_FIELDA(port_map_by_links, 6),
DDB0_FIELDA(port_map_by_links, 7));
}
static void asd_dump_scb_site(struct asd_ha_struct *asd_ha, u16 site_no)
{
#define SCB_FIELDB(__name) \
asd_scbsite_read_byte(asd_ha, site_no, sizeof(struct scb_header) \
+ offsetof(struct initiate_ssp_task, __name))
#define SCB_FIELDW(__name) \
asd_scbsite_read_word(asd_ha, site_no, sizeof(struct scb_header) \
+ offsetof(struct initiate_ssp_task, __name))
#define SCB_FIELDD(__name) \
asd_scbsite_read_dword(asd_ha, site_no, sizeof(struct scb_header) \
+ offsetof(struct initiate_ssp_task, __name))
asd_printk("Total Xfer Len: 0x%08x.\n", SCB_FIELDD(total_xfer_len));
asd_printk("Frame Type: 0x%02x.\n", SCB_FIELDB(ssp_frame.frame_type));
asd_printk("Tag: 0x%04x.\n", SCB_FIELDW(ssp_frame.tag));
asd_printk("Target Port Xfer Tag: 0x%04x.\n",
SCB_FIELDW(ssp_frame.tptt));
asd_printk("Data Offset: 0x%08x.\n", SCB_FIELDW(ssp_frame.data_offs));
asd_printk("Retry Count: 0x%02x.\n", SCB_FIELDB(retry_count));
}
/**
* asd_dump_scb_sites -- dump currently used CSEQ SCB sites
* @asd_ha: pointer to host adapter struct
*/
void asd_dump_scb_sites(struct asd_ha_struct *asd_ha)
{
u16 site_no;
for (site_no = 0; site_no < asd_ha->hw_prof.max_scbs; site_no++) {
u8 opcode;
if (!SCB_SITE_VALID(site_no))
continue;
/* We are only interested in SCB sites currently used.
*/
opcode = asd_scbsite_read_byte(asd_ha, site_no,
offsetof(struct scb_header,
opcode));
if (opcode == 0xFF)
continue;
asd_printk("\nSCB: 0x%x\n", site_no);
asd_dump_scb_site(asd_ha, site_no);
}
}
/**
* ads_dump_seq_state -- dump CSEQ and LSEQ states
* @asd_ha: pointer to host adapter structure
* @lseq_mask: mask of LSEQs of interest
*/
void asd_dump_seq_state(struct asd_ha_struct *asd_ha, u8 lseq_mask)
{
int lseq;
asd_dump_cseq_state(asd_ha);
if (lseq_mask != 0)
for_each_sequencer(lseq_mask, lseq_mask, lseq)
asd_dump_lseq_state(asd_ha, lseq);
}
void asd_dump_frame_rcvd(struct asd_phy *phy,
struct done_list_struct *dl)
{
unsigned long flags;
int i;
switch ((dl->status_block[1] & 0x70) >> 3) {
case SAS_PROTO_STP:
ASD_DPRINTK("STP proto device-to-host FIS:\n");
break;
default:
case SAS_PROTO_SSP:
ASD_DPRINTK("SAS proto IDENTIFY:\n");
break;
}
spin_lock_irqsave(&phy->sas_phy.frame_rcvd_lock, flags);
for (i = 0; i < phy->sas_phy.frame_rcvd_size; i+=4)
ASD_DPRINTK("%02x: %02x %02x %02x %02x\n",
i,
phy->frame_rcvd[i],
phy->frame_rcvd[i+1],
phy->frame_rcvd[i+2],
phy->frame_rcvd[i+3]);
spin_unlock_irqrestore(&phy->sas_phy.frame_rcvd_lock, flags);
}
static inline void asd_dump_scb(struct asd_ascb *ascb, int ind)
{
asd_printk("scb%d: vaddr: 0x%p, dma_handle: 0x%llx, next: 0x%llx, "
"index:%d, opcode:0x%02x\n",
ind, ascb->dma_scb.vaddr,
(unsigned long long)ascb->dma_scb.dma_handle,
(unsigned long long)
le64_to_cpu(ascb->scb->header.next_scb),
le16_to_cpu(ascb->scb->header.index),
ascb->scb->header.opcode);
}
void asd_dump_scb_list(struct asd_ascb *ascb, int num)
{
int i = 0;
asd_printk("dumping %d scbs:\n", num);
asd_dump_scb(ascb, i++);
--num;
if (num > 0 && !list_empty(&ascb->list)) {
struct list_head *el;
list_for_each(el, &ascb->list) {
struct asd_ascb *s = list_entry(el, struct asd_ascb,
list);
asd_dump_scb(s, i++);
if (--num <= 0)
break;
}
}
}
#endif /* ASD_DEBUG */

View File

@@ -0,0 +1,52 @@
/*
* Aic94xx SAS/SATA driver dump 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 file is part of the aic94xx driver.
*
* The aic94xx driver 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.
*
* The aic94xx driver 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 the aic94xx driver; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
#ifndef _AIC94XX_DUMP_H_
#define _AIC94XX_DUMP_H_
#ifdef ASD_DEBUG
void asd_dump_ddb_0(struct asd_ha_struct *asd_ha);
void asd_dump_target_ddb(struct asd_ha_struct *asd_ha, u16 site_no);
void asd_dump_scb_sites(struct asd_ha_struct *asd_ha);
void asd_dump_seq_state(struct asd_ha_struct *asd_ha, u8 lseq_mask);
void asd_dump_frame_rcvd(struct asd_phy *phy,
struct done_list_struct *dl);
void asd_dump_scb_list(struct asd_ascb *ascb, int num);
#else /* ASD_DEBUG */
static inline void asd_dump_ddb_0(struct asd_ha_struct *asd_ha) { }
static inline void asd_dump_target_ddb(struct asd_ha_struct *asd_ha,
u16 site_no) { }
static inline void asd_dump_scb_sites(struct asd_ha_struct *asd_ha) { }
static inline void asd_dump_seq_state(struct asd_ha_struct *asd_ha,
u8 lseq_mask) { }
static inline void asd_dump_frame_rcvd(struct asd_phy *phy,
struct done_list_struct *dl) { }
static inline void asd_dump_scb_list(struct asd_ascb *ascb, int num) { }
#endif /* ASD_DEBUG */
#endif /* _AIC94XX_DUMP_H_ */

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,397 @@
/*
* Aic94xx SAS/SATA driver hardware interface 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 file is part of the aic94xx driver.
*
* The aic94xx driver 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.
*
* The aic94xx driver 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 the aic94xx driver; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
#ifndef _AIC94XX_HWI_H_
#define _AIC94XX_HWI_H_
#include <linux/interrupt.h>
#include <linux/pci.h>
#include <linux/dma-mapping.h>
#include <scsi/libsas.h>
#include "aic94xx.h"
#include "aic94xx_sas.h"
/* Define ASD_MAX_PHYS to the maximum phys ever. Currently 8. */
#define ASD_MAX_PHYS 8
#define ASD_PCBA_SN_SIZE 12
/* Those are to be further named properly, the "RAZORx" part, and
* subsequently included in include/linux/pci_ids.h.
*/
#define PCI_DEVICE_ID_ADAPTEC2_RAZOR10 0x410
#define PCI_DEVICE_ID_ADAPTEC2_RAZOR12 0x412
#define PCI_DEVICE_ID_ADAPTEC2_RAZOR1E 0x41E
#define PCI_DEVICE_ID_ADAPTEC2_RAZOR30 0x430
#define PCI_DEVICE_ID_ADAPTEC2_RAZOR32 0x432
#define PCI_DEVICE_ID_ADAPTEC2_RAZOR3E 0x43E
#define PCI_DEVICE_ID_ADAPTEC2_RAZOR3F 0x43F
struct asd_ha_addrspace {
void __iomem *addr;
unsigned long start; /* pci resource start */
unsigned long len; /* pci resource len */
unsigned long flags; /* pci resource flags */
/* addresses internal to the host adapter */
u32 swa_base; /* mmspace 1 (MBAR1) uses this only */
u32 swb_base;
u32 swc_base;
};
struct bios_struct {
int present;
u8 maj;
u8 min;
u32 bld;
};
struct unit_element_struct {
u16 num;
u16 size;
void *area;
};
struct flash_struct {
u32 bar;
int present;
int wide;
u8 manuf;
u8 dev_id;
u8 sec_prot;
u32 dir_offs;
};
struct asd_phy_desc {
/* From CTRL-A settings, then set to what is appropriate */
u8 sas_addr[SAS_ADDR_SIZE];
u8 max_sas_lrate;
u8 min_sas_lrate;
u8 max_sata_lrate;
u8 min_sata_lrate;
u8 flags;
#define ASD_CRC_DIS 1
#define ASD_SATA_SPINUP_HOLD 2
u8 phy_control_0; /* mode 5 reg 0x160 */
u8 phy_control_1; /* mode 5 reg 0x161 */
u8 phy_control_2; /* mode 5 reg 0x162 */
u8 phy_control_3; /* mode 5 reg 0x163 */
};
struct asd_dma_tok {
void *vaddr;
dma_addr_t dma_handle;
size_t size;
};
struct hw_profile {
struct bios_struct bios;
struct unit_element_struct ue;
struct flash_struct flash;
u8 sas_addr[SAS_ADDR_SIZE];
char pcba_sn[ASD_PCBA_SN_SIZE+1];
u8 enabled_phys; /* mask of enabled phys */
struct asd_phy_desc phy_desc[ASD_MAX_PHYS];
u32 max_scbs; /* absolute sequencer scb queue size */
struct asd_dma_tok *scb_ext;
u32 max_ddbs;
struct asd_dma_tok *ddb_ext;
spinlock_t ddb_lock;
void *ddb_bitmap;
int num_phys; /* ENABLEABLE */
int max_phys; /* REPORTED + ENABLEABLE */
unsigned addr_range; /* max # of addrs; max # of possible ports */
unsigned port_name_base;
unsigned dev_name_base;
unsigned sata_name_base;
};
struct asd_ascb {
struct list_head list;
struct asd_ha_struct *ha;
struct scb *scb; /* equals dma_scb->vaddr */
struct asd_dma_tok dma_scb;
struct asd_dma_tok *sg_arr;
void (*tasklet_complete)(struct asd_ascb *, struct done_list_struct *);
u8 uldd_timer:1;
/* internally generated command */
struct timer_list timer;
struct completion completion;
u8 tag_valid:1;
__be16 tag; /* error recovery only */
/* If this is an Empty SCB, index of first edb in seq->edb_arr. */
int edb_index;
/* Used by the timer timeout function. */
int tc_index;
void *uldd_task;
};
#define ASD_DL_SIZE_BITS 0x8
#define ASD_DL_SIZE (1<<(2+ASD_DL_SIZE_BITS))
#define ASD_DEF_DL_TOGGLE 0x01
struct asd_seq_data {
spinlock_t pend_q_lock;
u16 scbpro;
int pending;
struct list_head pend_q;
int can_queue; /* per adapter */
struct asd_dma_tok next_scb; /* next scb to be delivered to CSEQ */
spinlock_t tc_index_lock;
void **tc_index_array;
void *tc_index_bitmap;
int tc_index_bitmap_bits;
struct tasklet_struct dl_tasklet;
struct done_list_struct *dl; /* array of done list entries, equals */
struct asd_dma_tok *actual_dl; /* actual_dl->vaddr */
int dl_toggle;
int dl_next;
int num_edbs;
struct asd_dma_tok **edb_arr;
int num_escbs;
struct asd_ascb **escb_arr; /* array of pointers to escbs */
};
/* This is the Host Adapter structure. It describes the hardware
* SAS adapter.
*/
struct asd_ha_struct {
struct pci_dev *pcidev;
const char *name;
struct sas_ha_struct sas_ha;
u8 revision_id;
int iospace;
spinlock_t iolock;
struct asd_ha_addrspace io_handle[2];
struct hw_profile hw_prof;
struct asd_phy phys[ASD_MAX_PHYS];
struct asd_sas_port ports[ASD_MAX_PHYS];
struct dma_pool *scb_pool;
struct asd_seq_data seq; /* sequencer related */
};
/* ---------- Common macros ---------- */
#define ASD_BUSADDR_LO(__dma_handle) ((u32)(__dma_handle))
#define ASD_BUSADDR_HI(__dma_handle) (((sizeof(dma_addr_t))==8) \
? ((u32)((__dma_handle) >> 32)) \
: ((u32)0))
#define dev_to_asd_ha(__dev) pci_get_drvdata(to_pci_dev(__dev))
#define SCB_SITE_VALID(__site_no) (((__site_no) & 0xF0FF) != 0x00FF \
&& ((__site_no) & 0xF0FF) > 0x001F)
/* For each bit set in __lseq_mask, set __lseq to equal the bit
* position of the set bit and execute the statement following.
* __mc is the temporary mask, used as a mask "counter".
*/
#define for_each_sequencer(__lseq_mask, __mc, __lseq) \
for ((__mc)=(__lseq_mask),(__lseq)=0;(__mc)!=0;(__lseq++),(__mc)>>=1)\
if (((__mc) & 1))
#define for_each_phy(__lseq_mask, __mc, __lseq) \
for ((__mc)=(__lseq_mask),(__lseq)=0;(__mc)!=0;(__lseq++),(__mc)>>=1)\
if (((__mc) & 1))
#define PHY_ENABLED(_HA, _I) ((_HA)->hw_prof.enabled_phys & (1<<(_I)))
/* ---------- DMA allocs ---------- */
static inline struct asd_dma_tok *asd_dmatok_alloc(unsigned int flags)
{
return kmem_cache_alloc(asd_dma_token_cache, flags);
}
static inline void asd_dmatok_free(struct asd_dma_tok *token)
{
kmem_cache_free(asd_dma_token_cache, token);
}
static inline struct asd_dma_tok *asd_alloc_coherent(struct asd_ha_struct *
asd_ha, size_t size,
unsigned int flags)
{
struct asd_dma_tok *token = asd_dmatok_alloc(flags);
if (token) {
token->size = size;
token->vaddr = dma_alloc_coherent(&asd_ha->pcidev->dev,
token->size,
&token->dma_handle,
flags);
if (!token->vaddr) {
asd_dmatok_free(token);
token = NULL;
}
}
return token;
}
static inline void asd_free_coherent(struct asd_ha_struct *asd_ha,
struct asd_dma_tok *token)
{
if (token) {
dma_free_coherent(&asd_ha->pcidev->dev, token->size,
token->vaddr, token->dma_handle);
asd_dmatok_free(token);
}
}
static inline void asd_init_ascb(struct asd_ha_struct *asd_ha,
struct asd_ascb *ascb)
{
INIT_LIST_HEAD(&ascb->list);
ascb->scb = ascb->dma_scb.vaddr;
ascb->ha = asd_ha;
ascb->timer.function = NULL;
init_timer(&ascb->timer);
ascb->tc_index = -1;
init_completion(&ascb->completion);
}
/* Must be called with the tc_index_lock held!
*/
static inline void asd_tc_index_release(struct asd_seq_data *seq, int index)
{
seq->tc_index_array[index] = NULL;
clear_bit(index, seq->tc_index_bitmap);
}
/* Must be called with the tc_index_lock held!
*/
static inline int asd_tc_index_get(struct asd_seq_data *seq, void *ptr)
{
int index;
index = find_first_zero_bit(seq->tc_index_bitmap,
seq->tc_index_bitmap_bits);
if (index == seq->tc_index_bitmap_bits)
return -1;
seq->tc_index_array[index] = ptr;
set_bit(index, seq->tc_index_bitmap);
return index;
}
/* Must be called with the tc_index_lock held!
*/
static inline void *asd_tc_index_find(struct asd_seq_data *seq, int index)
{
return seq->tc_index_array[index];
}
/**
* asd_ascb_free -- free a single aSCB after is has completed
* @ascb: pointer to the aSCB of interest
*
* This frees an aSCB after it has been executed/completed by
* the sequencer.
*/
static inline void asd_ascb_free(struct asd_ascb *ascb)
{
if (ascb) {
struct asd_ha_struct *asd_ha = ascb->ha;
unsigned long flags;
BUG_ON(!list_empty(&ascb->list));
spin_lock_irqsave(&ascb->ha->seq.tc_index_lock, flags);
asd_tc_index_release(&ascb->ha->seq, ascb->tc_index);
spin_unlock_irqrestore(&ascb->ha->seq.tc_index_lock, flags);
dma_pool_free(asd_ha->scb_pool, ascb->dma_scb.vaddr,
ascb->dma_scb.dma_handle);
kmem_cache_free(asd_ascb_cache, ascb);
}
}
/**
* asd_ascb_list_free -- free a list of ascbs
* @ascb_list: a list of ascbs
*
* This function will free a list of ascbs allocated by asd_ascb_alloc_list.
* It is used when say the scb queueing function returned QUEUE_FULL,
* and we do not need the ascbs any more.
*/
static inline void asd_ascb_free_list(struct asd_ascb *ascb_list)
{
LIST_HEAD(list);
struct list_head *n, *pos;
__list_add(&list, ascb_list->list.prev, &ascb_list->list);
list_for_each_safe(pos, n, &list) {
list_del_init(pos);
asd_ascb_free(list_entry(pos, struct asd_ascb, list));
}
}
/* ---------- Function declarations ---------- */
int asd_init_hw(struct asd_ha_struct *asd_ha);
irqreturn_t asd_hw_isr(int irq, void *dev_id, struct pt_regs *regs);
struct asd_ascb *asd_ascb_alloc_list(struct asd_ha_struct
*asd_ha, int *num,
unsigned int gfp_mask);
int asd_post_ascb_list(struct asd_ha_struct *asd_ha, struct asd_ascb *ascb,
int num);
int asd_post_escb_list(struct asd_ha_struct *asd_ha, struct asd_ascb *ascb,
int num);
int asd_init_post_escbs(struct asd_ha_struct *asd_ha);
void asd_build_control_phy(struct asd_ascb *ascb, int phy_id, u8 subfunc);
void asd_control_led(struct asd_ha_struct *asd_ha, int phy_id, int op);
void asd_turn_led(struct asd_ha_struct *asd_ha, int phy_id, int op);
int asd_enable_phys(struct asd_ha_struct *asd_ha, const u8 phy_mask);
void asd_build_initiate_link_adm_task(struct asd_ascb *ascb, int phy_id,
u8 subfunc);
void asd_ascb_timedout(unsigned long data);
int asd_chip_hardrst(struct asd_ha_struct *asd_ha);
#endif

View File

@@ -0,0 +1,860 @@
/*
* Aic94xx SAS/SATA driver 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 file is part of the aic94xx driver.
*
* The aic94xx driver 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.
*
* The aic94xx driver 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 the aic94xx driver; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
#include <linux/config.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/pci.h>
#include <linux/delay.h>
#include <scsi/scsi_host.h>
#include "aic94xx.h"
#include "aic94xx_reg.h"
#include "aic94xx_hwi.h"
#include "aic94xx_seq.h"
/* The format is "version.release.patchlevel" */
#define ASD_DRIVER_VERSION "1.0.2"
static int use_msi = 0;
module_param_named(use_msi, use_msi, int, S_IRUGO);
MODULE_PARM_DESC(use_msi, "\n"
"\tEnable(1) or disable(0) using PCI MSI.\n"
"\tDefault: 0");
static int lldd_max_execute_num = 0;
module_param_named(collector, lldd_max_execute_num, int, S_IRUGO);
MODULE_PARM_DESC(collector, "\n"
"\tIf greater than one, tells the SAS Layer to run in Task Collector\n"
"\tMode. If 1 or 0, tells the SAS Layer to run in Direct Mode.\n"
"\tThe aic94xx SAS LLDD supports both modes.\n"
"\tDefault: 0 (Direct Mode).\n");
char sas_addr_str[2*SAS_ADDR_SIZE + 1] = "";
static struct scsi_transport_template *aic94xx_transport_template;
static struct scsi_host_template aic94xx_sht = {
.module = THIS_MODULE,
/* .name is initialized */
.name = "aic94xx",
.queuecommand = sas_queuecommand,
.target_alloc = sas_target_alloc,
.slave_configure = sas_slave_configure,
.slave_destroy = sas_slave_destroy,
.change_queue_depth = sas_change_queue_depth,
.change_queue_type = sas_change_queue_type,
.bios_param = sas_bios_param,
.can_queue = 1,
.cmd_per_lun = 1,
.this_id = -1,
.sg_tablesize = SG_ALL,
.max_sectors = SCSI_DEFAULT_MAX_SECTORS,
.use_clustering = ENABLE_CLUSTERING,
};
static int __devinit asd_map_memio(struct asd_ha_struct *asd_ha)
{
int err, i;
struct asd_ha_addrspace *io_handle;
asd_ha->iospace = 0;
for (i = 0; i < 3; i += 2) {
io_handle = &asd_ha->io_handle[i==0?0:1];
io_handle->start = pci_resource_start(asd_ha->pcidev, i);
io_handle->len = pci_resource_len(asd_ha->pcidev, i);
io_handle->flags = pci_resource_flags(asd_ha->pcidev, i);
err = -ENODEV;
if (!io_handle->start || !io_handle->len) {
asd_printk("MBAR%d start or length for %s is 0.\n",
i==0?0:1, pci_name(asd_ha->pcidev));
goto Err;
}
err = pci_request_region(asd_ha->pcidev, i, ASD_DRIVER_NAME);
if (err) {
asd_printk("couldn't reserve memory region for %s\n",
pci_name(asd_ha->pcidev));
goto Err;
}
if (io_handle->flags & IORESOURCE_CACHEABLE)
io_handle->addr = ioremap(io_handle->start,
io_handle->len);
else
io_handle->addr = ioremap_nocache(io_handle->start,
io_handle->len);
if (!io_handle->addr) {
asd_printk("couldn't map MBAR%d of %s\n", i==0?0:1,
pci_name(asd_ha->pcidev));
goto Err_unreq;
}
}
return 0;
Err_unreq:
pci_release_region(asd_ha->pcidev, i);
Err:
if (i > 0) {
io_handle = &asd_ha->io_handle[0];
iounmap(io_handle->addr);
pci_release_region(asd_ha->pcidev, 0);
}
return err;
}
static void __devexit asd_unmap_memio(struct asd_ha_struct *asd_ha)
{
struct asd_ha_addrspace *io_handle;
io_handle = &asd_ha->io_handle[1];
iounmap(io_handle->addr);
pci_release_region(asd_ha->pcidev, 2);
io_handle = &asd_ha->io_handle[0];
iounmap(io_handle->addr);
pci_release_region(asd_ha->pcidev, 0);
}
static int __devinit asd_map_ioport(struct asd_ha_struct *asd_ha)
{
int i = PCI_IOBAR_OFFSET, err;
struct asd_ha_addrspace *io_handle = &asd_ha->io_handle[0];
asd_ha->iospace = 1;
io_handle->start = pci_resource_start(asd_ha->pcidev, i);
io_handle->len = pci_resource_len(asd_ha->pcidev, i);
io_handle->flags = pci_resource_flags(asd_ha->pcidev, i);
io_handle->addr = (void __iomem *) io_handle->start;
if (!io_handle->start || !io_handle->len) {
asd_printk("couldn't get IO ports for %s\n",
pci_name(asd_ha->pcidev));
return -ENODEV;
}
err = pci_request_region(asd_ha->pcidev, i, ASD_DRIVER_NAME);
if (err) {
asd_printk("couldn't reserve io space for %s\n",
pci_name(asd_ha->pcidev));
}
return err;
}
static void __devexit asd_unmap_ioport(struct asd_ha_struct *asd_ha)
{
pci_release_region(asd_ha->pcidev, PCI_IOBAR_OFFSET);
}
static int __devinit asd_map_ha(struct asd_ha_struct *asd_ha)
{
int err;
u16 cmd_reg;
err = pci_read_config_word(asd_ha->pcidev, PCI_COMMAND, &cmd_reg);
if (err) {
asd_printk("couldn't read command register of %s\n",
pci_name(asd_ha->pcidev));
goto Err;
}
err = -ENODEV;
if (cmd_reg & PCI_COMMAND_MEMORY) {
if ((err = asd_map_memio(asd_ha)))
goto Err;
} else if (cmd_reg & PCI_COMMAND_IO) {
if ((err = asd_map_ioport(asd_ha)))
goto Err;
asd_printk("%s ioport mapped -- upgrade your hardware\n",
pci_name(asd_ha->pcidev));
} else {
asd_printk("no proper device access to %s\n",
pci_name(asd_ha->pcidev));
goto Err;
}
return 0;
Err:
return err;
}
static void __devexit asd_unmap_ha(struct asd_ha_struct *asd_ha)
{
if (asd_ha->iospace)
asd_unmap_ioport(asd_ha);
else
asd_unmap_memio(asd_ha);
}
static const char *asd_dev_rev[30] = {
[0] = "A0",
[1] = "A1",
[8] = "B0",
};
static int __devinit asd_common_setup(struct asd_ha_struct *asd_ha)
{
int err, i;
err = pci_read_config_byte(asd_ha->pcidev, PCI_REVISION_ID,
&asd_ha->revision_id);
if (err) {
asd_printk("couldn't read REVISION ID register of %s\n",
pci_name(asd_ha->pcidev));
goto Err;
}
err = -ENODEV;
if (asd_ha->revision_id < AIC9410_DEV_REV_B0) {
asd_printk("%s is revision %s (%X), which is not supported\n",
pci_name(asd_ha->pcidev),
asd_dev_rev[asd_ha->revision_id],
asd_ha->revision_id);
goto Err;
}
/* Provide some sane default values. */
asd_ha->hw_prof.max_scbs = 512;
asd_ha->hw_prof.max_ddbs = 128;
asd_ha->hw_prof.num_phys = ASD_MAX_PHYS;
/* All phys are enabled, by default. */
asd_ha->hw_prof.enabled_phys = 0xFF;
for (i = 0; i < ASD_MAX_PHYS; i++) {
asd_ha->hw_prof.phy_desc[i].max_sas_lrate = PHY_LINKRATE_3;
asd_ha->hw_prof.phy_desc[i].min_sas_lrate = PHY_LINKRATE_1_5;
asd_ha->hw_prof.phy_desc[i].max_sata_lrate= PHY_LINKRATE_1_5;
asd_ha->hw_prof.phy_desc[i].min_sata_lrate= PHY_LINKRATE_1_5;
}
return 0;
Err:
return err;
}
static int __devinit asd_aic9410_setup(struct asd_ha_struct *asd_ha)
{
int err = asd_common_setup(asd_ha);
if (err)
return err;
asd_ha->hw_prof.addr_range = 8;
asd_ha->hw_prof.port_name_base = 0;
asd_ha->hw_prof.dev_name_base = 8;
asd_ha->hw_prof.sata_name_base = 16;
return 0;
}
static int __devinit asd_aic9405_setup(struct asd_ha_struct *asd_ha)
{
int err = asd_common_setup(asd_ha);
if (err)
return err;
asd_ha->hw_prof.addr_range = 4;
asd_ha->hw_prof.port_name_base = 0;
asd_ha->hw_prof.dev_name_base = 4;
asd_ha->hw_prof.sata_name_base = 8;
return 0;
}
static ssize_t asd_show_dev_rev(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct asd_ha_struct *asd_ha = dev_to_asd_ha(dev);
return snprintf(buf, PAGE_SIZE, "%s\n",
asd_dev_rev[asd_ha->revision_id]);
}
static DEVICE_ATTR(revision, S_IRUGO, asd_show_dev_rev, NULL);
static ssize_t asd_show_dev_bios_build(struct device *dev,
struct device_attribute *attr,char *buf)
{
struct asd_ha_struct *asd_ha = dev_to_asd_ha(dev);
return snprintf(buf, PAGE_SIZE, "%d\n", asd_ha->hw_prof.bios.bld);
}
static DEVICE_ATTR(bios_build, S_IRUGO, asd_show_dev_bios_build, NULL);
static ssize_t asd_show_dev_pcba_sn(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct asd_ha_struct *asd_ha = dev_to_asd_ha(dev);
return snprintf(buf, PAGE_SIZE, "%s\n", asd_ha->hw_prof.pcba_sn);
}
static DEVICE_ATTR(pcba_sn, S_IRUGO, asd_show_dev_pcba_sn, NULL);
static void asd_create_dev_attrs(struct asd_ha_struct *asd_ha)
{
device_create_file(&asd_ha->pcidev->dev, &dev_attr_revision);
device_create_file(&asd_ha->pcidev->dev, &dev_attr_bios_build);
device_create_file(&asd_ha->pcidev->dev, &dev_attr_pcba_sn);
}
static void asd_remove_dev_attrs(struct asd_ha_struct *asd_ha)
{
device_remove_file(&asd_ha->pcidev->dev, &dev_attr_revision);
device_remove_file(&asd_ha->pcidev->dev, &dev_attr_bios_build);
device_remove_file(&asd_ha->pcidev->dev, &dev_attr_pcba_sn);
}
/* The first entry, 0, is used for dynamic ids, the rest for devices
* we know about.
*/
static struct asd_pcidev_struct {
const char * name;
int (*setup)(struct asd_ha_struct *asd_ha);
} asd_pcidev_data[] = {
/* Id 0 is used for dynamic ids. */
{ .name = "Adaptec AIC-94xx SAS/SATA Host Adapter",
.setup = asd_aic9410_setup
},
{ .name = "Adaptec AIC-9410W SAS/SATA Host Adapter",
.setup = asd_aic9410_setup
},
{ .name = "Adaptec AIC-9405W SAS/SATA Host Adapter",
.setup = asd_aic9405_setup
},
};
static inline int asd_create_ha_caches(struct asd_ha_struct *asd_ha)
{
asd_ha->scb_pool = dma_pool_create(ASD_DRIVER_NAME "_scb_pool",
&asd_ha->pcidev->dev,
sizeof(struct scb),
8, 0);
if (!asd_ha->scb_pool) {
asd_printk("couldn't create scb pool\n");
return -ENOMEM;
}
return 0;
}
/**
* asd_free_edbs -- free empty data buffers
* asd_ha: pointer to host adapter structure
*/
static inline void asd_free_edbs(struct asd_ha_struct *asd_ha)
{
struct asd_seq_data *seq = &asd_ha->seq;
int i;
for (i = 0; i < seq->num_edbs; i++)
asd_free_coherent(asd_ha, seq->edb_arr[i]);
kfree(seq->edb_arr);
seq->edb_arr = NULL;
}
static inline void asd_free_escbs(struct asd_ha_struct *asd_ha)
{
struct asd_seq_data *seq = &asd_ha->seq;
int i;
for (i = 0; i < seq->num_escbs; i++) {
if (!list_empty(&seq->escb_arr[i]->list))
list_del_init(&seq->escb_arr[i]->list);
asd_ascb_free(seq->escb_arr[i]);
}
kfree(seq->escb_arr);
seq->escb_arr = NULL;
}
static inline void asd_destroy_ha_caches(struct asd_ha_struct *asd_ha)
{
int i;
if (asd_ha->hw_prof.ddb_ext)
asd_free_coherent(asd_ha, asd_ha->hw_prof.ddb_ext);
if (asd_ha->hw_prof.scb_ext)
asd_free_coherent(asd_ha, asd_ha->hw_prof.scb_ext);
if (asd_ha->hw_prof.ddb_bitmap)
kfree(asd_ha->hw_prof.ddb_bitmap);
asd_ha->hw_prof.ddb_bitmap = NULL;
for (i = 0; i < ASD_MAX_PHYS; i++) {
struct asd_phy *phy = &asd_ha->phys[i];
asd_free_coherent(asd_ha, phy->id_frm_tok);
}
if (asd_ha->seq.escb_arr)
asd_free_escbs(asd_ha);
if (asd_ha->seq.edb_arr)
asd_free_edbs(asd_ha);
if (asd_ha->hw_prof.ue.area) {
kfree(asd_ha->hw_prof.ue.area);
asd_ha->hw_prof.ue.area = NULL;
}
if (asd_ha->seq.tc_index_array) {
kfree(asd_ha->seq.tc_index_array);
kfree(asd_ha->seq.tc_index_bitmap);
asd_ha->seq.tc_index_array = NULL;
asd_ha->seq.tc_index_bitmap = NULL;
}
if (asd_ha->seq.actual_dl) {
asd_free_coherent(asd_ha, asd_ha->seq.actual_dl);
asd_ha->seq.actual_dl = NULL;
asd_ha->seq.dl = NULL;
}
if (asd_ha->seq.next_scb.vaddr) {
dma_pool_free(asd_ha->scb_pool, asd_ha->seq.next_scb.vaddr,
asd_ha->seq.next_scb.dma_handle);
asd_ha->seq.next_scb.vaddr = NULL;
}
dma_pool_destroy(asd_ha->scb_pool);
asd_ha->scb_pool = NULL;
}
kmem_cache_t *asd_dma_token_cache;
kmem_cache_t *asd_ascb_cache;
static int asd_create_global_caches(void)
{
if (!asd_dma_token_cache) {
asd_dma_token_cache
= kmem_cache_create(ASD_DRIVER_NAME "_dma_token",
sizeof(struct asd_dma_tok),
0,
SLAB_HWCACHE_ALIGN,
NULL, NULL);
if (!asd_dma_token_cache) {
asd_printk("couldn't create dma token cache\n");
return -ENOMEM;
}
}
if (!asd_ascb_cache) {
asd_ascb_cache = kmem_cache_create(ASD_DRIVER_NAME "_ascb",
sizeof(struct asd_ascb),
0,
SLAB_HWCACHE_ALIGN,
NULL, NULL);
if (!asd_ascb_cache) {
asd_printk("couldn't create ascb cache\n");
goto Err;
}
}
return 0;
Err:
kmem_cache_destroy(asd_dma_token_cache);
asd_dma_token_cache = NULL;
return -ENOMEM;
}
static void asd_destroy_global_caches(void)
{
if (asd_dma_token_cache)
kmem_cache_destroy(asd_dma_token_cache);
asd_dma_token_cache = NULL;
if (asd_ascb_cache)
kmem_cache_destroy(asd_ascb_cache);
asd_ascb_cache = NULL;
}
static int asd_register_sas_ha(struct asd_ha_struct *asd_ha)
{
int i;
struct asd_sas_phy **sas_phys =
kmalloc(ASD_MAX_PHYS * sizeof(struct asd_sas_phy), GFP_KERNEL);
struct asd_sas_port **sas_ports =
kmalloc(ASD_MAX_PHYS * sizeof(struct asd_sas_port), GFP_KERNEL);
if (!sas_phys || !sas_ports) {
kfree(sas_phys);
kfree(sas_ports);
return -ENOMEM;
}
asd_ha->sas_ha.sas_ha_name = (char *) asd_ha->name;
asd_ha->sas_ha.lldd_module = THIS_MODULE;
asd_ha->sas_ha.sas_addr = &asd_ha->hw_prof.sas_addr[0];
for (i = 0; i < ASD_MAX_PHYS; i++) {
sas_phys[i] = &asd_ha->phys[i].sas_phy;
sas_ports[i] = &asd_ha->ports[i];
}
asd_ha->sas_ha.sas_phy = sas_phys;
asd_ha->sas_ha.sas_port= sas_ports;
asd_ha->sas_ha.num_phys= ASD_MAX_PHYS;
asd_ha->sas_ha.lldd_queue_size = asd_ha->seq.can_queue;
return sas_register_ha(&asd_ha->sas_ha);
}
static int asd_unregister_sas_ha(struct asd_ha_struct *asd_ha)
{
int err;
err = sas_unregister_ha(&asd_ha->sas_ha);
sas_remove_host(asd_ha->sas_ha.core.shost);
scsi_remove_host(asd_ha->sas_ha.core.shost);
scsi_host_put(asd_ha->sas_ha.core.shost);
kfree(asd_ha->sas_ha.sas_phy);
kfree(asd_ha->sas_ha.sas_port);
return err;
}
static int __devinit asd_pci_probe(struct pci_dev *dev,
const struct pci_device_id *id)
{
struct asd_pcidev_struct *asd_dev;
unsigned asd_id = (unsigned) id->driver_data;
struct asd_ha_struct *asd_ha;
struct Scsi_Host *shost;
int err;
if (asd_id >= ARRAY_SIZE(asd_pcidev_data)) {
asd_printk("wrong driver_data in PCI table\n");
return -ENODEV;
}
if ((err = pci_enable_device(dev))) {
asd_printk("couldn't enable device %s\n", pci_name(dev));
return err;
}
pci_set_master(dev);
err = -ENOMEM;
shost = scsi_host_alloc(&aic94xx_sht, sizeof(void *));
if (!shost)
goto Err;
asd_dev = &asd_pcidev_data[asd_id];
asd_ha = kzalloc(sizeof(*asd_ha), GFP_KERNEL);
if (!asd_ha) {
asd_printk("out of memory\n");
goto Err;
}
asd_ha->pcidev = dev;
asd_ha->sas_ha.pcidev = asd_ha->pcidev;
asd_ha->sas_ha.lldd_ha = asd_ha;
asd_ha->name = asd_dev->name;
asd_printk("found %s, device %s\n", asd_ha->name, pci_name(dev));
SHOST_TO_SAS_HA(shost) = &asd_ha->sas_ha;
asd_ha->sas_ha.core.shost = shost;
shost->transportt = aic94xx_transport_template;
shost->max_id = ~0;
shost->max_lun = ~0;
shost->max_cmd_len = 16;
err = scsi_add_host(shost, &dev->dev);
if (err) {
scsi_host_put(shost);
goto Err_free;
}
err = asd_dev->setup(asd_ha);
if (err)
goto Err_free;
err = -ENODEV;
if (!pci_set_dma_mask(dev, DMA_64BIT_MASK)
&& !pci_set_consistent_dma_mask(dev, DMA_64BIT_MASK))
;
else if (!pci_set_dma_mask(dev, DMA_32BIT_MASK)
&& !pci_set_consistent_dma_mask(dev, DMA_32BIT_MASK))
;
else {
asd_printk("no suitable DMA mask for %s\n", pci_name(dev));
goto Err_free;
}
pci_set_drvdata(dev, asd_ha);
err = asd_map_ha(asd_ha);
if (err)
goto Err_free;
err = asd_create_ha_caches(asd_ha);
if (err)
goto Err_unmap;
err = asd_init_hw(asd_ha);
if (err)
goto Err_free_cache;
asd_printk("device %s: SAS addr %llx, PCBA SN %s, %d phys, %d enabled "
"phys, flash %s, BIOS %s%d\n",
pci_name(dev), SAS_ADDR(asd_ha->hw_prof.sas_addr),
asd_ha->hw_prof.pcba_sn, asd_ha->hw_prof.max_phys,
asd_ha->hw_prof.num_phys,
asd_ha->hw_prof.flash.present ? "present" : "not present",
asd_ha->hw_prof.bios.present ? "build " : "not present",
asd_ha->hw_prof.bios.bld);
if (use_msi)
pci_enable_msi(asd_ha->pcidev);
err = request_irq(asd_ha->pcidev->irq, asd_hw_isr, SA_SHIRQ,
ASD_DRIVER_NAME, asd_ha);
if (err) {
asd_printk("couldn't get irq %d for %s\n",
asd_ha->pcidev->irq, pci_name(asd_ha->pcidev));
goto Err_irq;
}
asd_enable_ints(asd_ha);
err = asd_init_post_escbs(asd_ha);
if (err) {
asd_printk("couldn't post escbs for %s\n",
pci_name(asd_ha->pcidev));
goto Err_escbs;
}
ASD_DPRINTK("escbs posted\n");
asd_create_dev_attrs(asd_ha);
err = asd_register_sas_ha(asd_ha);
if (err)
goto Err_reg_sas;
err = asd_enable_phys(asd_ha, asd_ha->hw_prof.enabled_phys);
if (err) {
asd_printk("coudln't enable phys, err:%d\n", err);
goto Err_en_phys;
}
ASD_DPRINTK("enabled phys\n");
/* give the phy enabling interrupt event time to come in (1s
* is empirically about all it takes) */
ssleep(1);
/* Wait for discovery to finish */
scsi_flush_work(asd_ha->sas_ha.core.shost);
return 0;
Err_en_phys:
asd_unregister_sas_ha(asd_ha);
Err_reg_sas:
asd_remove_dev_attrs(asd_ha);
Err_escbs:
asd_disable_ints(asd_ha);
free_irq(dev->irq, asd_ha);
Err_irq:
if (use_msi)
pci_disable_msi(dev);
asd_chip_hardrst(asd_ha);
Err_free_cache:
asd_destroy_ha_caches(asd_ha);
Err_unmap:
asd_unmap_ha(asd_ha);
Err_free:
kfree(asd_ha);
scsi_remove_host(shost);
Err:
pci_disable_device(dev);
return err;
}
static void asd_free_queues(struct asd_ha_struct *asd_ha)
{
unsigned long flags;
LIST_HEAD(pending);
struct list_head *n, *pos;
spin_lock_irqsave(&asd_ha->seq.pend_q_lock, flags);
asd_ha->seq.pending = 0;
list_splice_init(&asd_ha->seq.pend_q, &pending);
spin_unlock_irqrestore(&asd_ha->seq.pend_q_lock, flags);
if (!list_empty(&pending))
ASD_DPRINTK("Uh-oh! Pending is not empty!\n");
list_for_each_safe(pos, n, &pending) {
struct asd_ascb *ascb = list_entry(pos, struct asd_ascb, list);
list_del_init(pos);
ASD_DPRINTK("freeing from pending\n");
asd_ascb_free(ascb);
}
}
static void asd_turn_off_leds(struct asd_ha_struct *asd_ha)
{
u8 phy_mask = asd_ha->hw_prof.enabled_phys;
u8 i;
for_each_phy(phy_mask, phy_mask, i) {
asd_turn_led(asd_ha, i, 0);
asd_control_led(asd_ha, i, 0);
}
}
static void __devexit asd_pci_remove(struct pci_dev *dev)
{
struct asd_ha_struct *asd_ha = pci_get_drvdata(dev);
if (!asd_ha)
return;
asd_unregister_sas_ha(asd_ha);
asd_disable_ints(asd_ha);
asd_remove_dev_attrs(asd_ha);
/* XXX more here as needed */
free_irq(dev->irq, asd_ha);
if (use_msi)
pci_disable_msi(asd_ha->pcidev);
asd_turn_off_leds(asd_ha);
asd_chip_hardrst(asd_ha);
asd_free_queues(asd_ha);
asd_destroy_ha_caches(asd_ha);
asd_unmap_ha(asd_ha);
kfree(asd_ha);
pci_disable_device(dev);
return;
}
static ssize_t asd_version_show(struct device_driver *driver, char *buf)
{
return snprintf(buf, PAGE_SIZE, "%s\n", ASD_DRIVER_VERSION);
}
static DRIVER_ATTR(version, S_IRUGO, asd_version_show, NULL);
static void asd_create_driver_attrs(struct device_driver *driver)
{
driver_create_file(driver, &driver_attr_version);
}
static void asd_remove_driver_attrs(struct device_driver *driver)
{
driver_remove_file(driver, &driver_attr_version);
}
static struct sas_domain_function_template aic94xx_transport_functions = {
.lldd_port_formed = asd_update_port_links,
.lldd_dev_found = asd_dev_found,
.lldd_dev_gone = asd_dev_gone,
.lldd_execute_task = asd_execute_task,
.lldd_abort_task = asd_abort_task,
.lldd_abort_task_set = asd_abort_task_set,
.lldd_clear_aca = asd_clear_aca,
.lldd_clear_task_set = asd_clear_task_set,
.lldd_I_T_nexus_reset = NULL,
.lldd_lu_reset = asd_lu_reset,
.lldd_query_task = asd_query_task,
.lldd_clear_nexus_port = asd_clear_nexus_port,
.lldd_clear_nexus_ha = asd_clear_nexus_ha,
.lldd_control_phy = asd_control_phy,
};
static const struct pci_device_id aic94xx_pci_table[] __devinitdata = {
{PCI_DEVICE(PCI_VENDOR_ID_ADAPTEC2, PCI_DEVICE_ID_ADAPTEC2_RAZOR10),
0, 0, 1},
{PCI_DEVICE(PCI_VENDOR_ID_ADAPTEC2, PCI_DEVICE_ID_ADAPTEC2_RAZOR12),
0, 0, 1},
{PCI_DEVICE(PCI_VENDOR_ID_ADAPTEC2, PCI_DEVICE_ID_ADAPTEC2_RAZOR1E),
0, 0, 1},
{PCI_DEVICE(PCI_VENDOR_ID_ADAPTEC2, PCI_DEVICE_ID_ADAPTEC2_RAZOR30),
0, 0, 2},
{PCI_DEVICE(PCI_VENDOR_ID_ADAPTEC2, PCI_DEVICE_ID_ADAPTEC2_RAZOR32),
0, 0, 2},
{PCI_DEVICE(PCI_VENDOR_ID_ADAPTEC2, PCI_DEVICE_ID_ADAPTEC2_RAZOR3E),
0, 0, 2},
{PCI_DEVICE(PCI_VENDOR_ID_ADAPTEC2, PCI_DEVICE_ID_ADAPTEC2_RAZOR3F),
0, 0, 2},
{}
};
MODULE_DEVICE_TABLE(pci, aic94xx_pci_table);
static struct pci_driver aic94xx_pci_driver = {
.name = ASD_DRIVER_NAME,
.id_table = aic94xx_pci_table,
.probe = asd_pci_probe,
.remove = __devexit_p(asd_pci_remove),
};
static int __init aic94xx_init(void)
{
int err;
asd_printk("%s version %s loaded\n", ASD_DRIVER_DESCRIPTION,
ASD_DRIVER_VERSION);
err = asd_create_global_caches();
if (err)
return err;
aic94xx_transport_template =
sas_domain_attach_transport(&aic94xx_transport_functions);
if (err)
goto out_destroy_caches;
err = pci_register_driver(&aic94xx_pci_driver);
if (err)
goto out_release_transport;
asd_create_driver_attrs(&aic94xx_pci_driver.driver);
return err;
out_release_transport:
sas_release_transport(aic94xx_transport_template);
out_destroy_caches:
asd_destroy_global_caches();
return err;
}
static void __exit aic94xx_exit(void)
{
asd_remove_driver_attrs(&aic94xx_pci_driver.driver);
pci_unregister_driver(&aic94xx_pci_driver);
sas_release_transport(aic94xx_transport_template);
asd_destroy_global_caches();
asd_printk("%s version %s unloaded\n", ASD_DRIVER_DESCRIPTION,
ASD_DRIVER_VERSION);
}
module_init(aic94xx_init);
module_exit(aic94xx_exit);
MODULE_AUTHOR("Luben Tuikov <luben_tuikov@adaptec.com>");
MODULE_DESCRIPTION(ASD_DRIVER_DESCRIPTION);
MODULE_LICENSE("GPL v2");
MODULE_VERSION(ASD_DRIVER_VERSION);

View File

@@ -0,0 +1,332 @@
/*
* Aic94xx SAS/SATA driver register access.
*
* Copyright (C) 2005 Adaptec, Inc. All rights reserved.
* Copyright (C) 2005 Luben Tuikov <luben_tuikov@adaptec.com>
*
* This file is licensed under GPLv2.
*
* This file is part of the aic94xx driver.
*
* The aic94xx driver 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.
*
* The aic94xx driver 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 the aic94xx driver; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
#include <linux/pci.h>
#include "aic94xx_reg.h"
#include "aic94xx.h"
/* Writing to device address space.
* Offset comes before value to remind that the operation of
* this function is *offs = val.
*/
static inline void asd_write_byte(struct asd_ha_struct *asd_ha,
unsigned long offs, u8 val)
{
if (unlikely(asd_ha->iospace))
outb(val,
(unsigned long)asd_ha->io_handle[0].addr + (offs & 0xFF));
else
writeb(val, asd_ha->io_handle[0].addr + offs);
wmb();
}
static inline void asd_write_word(struct asd_ha_struct *asd_ha,
unsigned long offs, u16 val)
{
if (unlikely(asd_ha->iospace))
outw(val,
(unsigned long)asd_ha->io_handle[0].addr + (offs & 0xFF));
else
writew(val, asd_ha->io_handle[0].addr + offs);
wmb();
}
static inline void asd_write_dword(struct asd_ha_struct *asd_ha,
unsigned long offs, u32 val)
{
if (unlikely(asd_ha->iospace))
outl(val,
(unsigned long)asd_ha->io_handle[0].addr + (offs & 0xFF));
else
writel(val, asd_ha->io_handle[0].addr + offs);
wmb();
}
/* Reading from device address space.
*/
static inline u8 asd_read_byte(struct asd_ha_struct *asd_ha,
unsigned long offs)
{
u8 val;
if (unlikely(asd_ha->iospace))
val = inb((unsigned long) asd_ha->io_handle[0].addr
+ (offs & 0xFF));
else
val = readb(asd_ha->io_handle[0].addr + offs);
rmb();
return val;
}
static inline u16 asd_read_word(struct asd_ha_struct *asd_ha,
unsigned long offs)
{
u16 val;
if (unlikely(asd_ha->iospace))
val = inw((unsigned long)asd_ha->io_handle[0].addr
+ (offs & 0xFF));
else
val = readw(asd_ha->io_handle[0].addr + offs);
rmb();
return val;
}
static inline u32 asd_read_dword(struct asd_ha_struct *asd_ha,
unsigned long offs)
{
u32 val;
if (unlikely(asd_ha->iospace))
val = inl((unsigned long) asd_ha->io_handle[0].addr
+ (offs & 0xFF));
else
val = readl(asd_ha->io_handle[0].addr + offs);
rmb();
return val;
}
static inline u32 asd_mem_offs_swa(void)
{
return 0;
}
static inline u32 asd_mem_offs_swc(void)
{
return asd_mem_offs_swa() + MBAR0_SWA_SIZE;
}
static inline u32 asd_mem_offs_swb(void)
{
return asd_mem_offs_swc() + MBAR0_SWC_SIZE + 0x20;
}
/* We know that the register wanted is in the range
* of the sliding window.
*/
#define ASD_READ_SW(ww, type, ord) \
static inline type asd_read_##ww##_##ord (struct asd_ha_struct *asd_ha,\
u32 reg) \
{ \
struct asd_ha_addrspace *io_handle = &asd_ha->io_handle[0]; \
u32 map_offs=(reg - io_handle-> ww##_base )+asd_mem_offs_##ww ();\
return asd_read_##ord (asd_ha, (unsigned long) map_offs); \
}
#define ASD_WRITE_SW(ww, type, ord) \
static inline void asd_write_##ww##_##ord (struct asd_ha_struct *asd_ha,\
u32 reg, type val) \
{ \
struct asd_ha_addrspace *io_handle = &asd_ha->io_handle[0]; \
u32 map_offs=(reg - io_handle-> ww##_base )+asd_mem_offs_##ww ();\
asd_write_##ord (asd_ha, (unsigned long) map_offs, val); \
}
ASD_READ_SW(swa, u8, byte);
ASD_READ_SW(swa, u16, word);
ASD_READ_SW(swa, u32, dword);
ASD_READ_SW(swb, u8, byte);
ASD_READ_SW(swb, u16, word);
ASD_READ_SW(swb, u32, dword);
ASD_READ_SW(swc, u8, byte);
ASD_READ_SW(swc, u16, word);
ASD_READ_SW(swc, u32, dword);
ASD_WRITE_SW(swa, u8, byte);
ASD_WRITE_SW(swa, u16, word);
ASD_WRITE_SW(swa, u32, dword);
ASD_WRITE_SW(swb, u8, byte);
ASD_WRITE_SW(swb, u16, word);
ASD_WRITE_SW(swb, u32, dword);
ASD_WRITE_SW(swc, u8, byte);
ASD_WRITE_SW(swc, u16, word);
ASD_WRITE_SW(swc, u32, dword);
/*
* A word about sliding windows:
* MBAR0 is divided into sliding windows A, C and B, in that order.
* SWA starts at offset 0 of MBAR0, up to 0x57, with size 0x58 bytes.
* SWC starts at offset 0x58 of MBAR0, up to 0x60, with size 0x8 bytes.
* From 0x60 to 0x7F, we have a copy of PCI config space 0x60-0x7F.
* SWB starts at offset 0x80 of MBAR0 and extends to the end of MBAR0.
* See asd_init_sw() in aic94xx_hwi.c
*
* We map the most common registers we'd access of the internal 4GB
* host adapter memory space. If a register/internal memory location
* is wanted which is not mapped, we slide SWB, by paging it,
* see asd_move_swb() in aic94xx_reg.c.
*/
/**
* asd_move_swb -- move sliding window B
* @asd_ha: pointer to host adapter structure
* @reg: register desired to be within range of the new window
*/
static inline void asd_move_swb(struct asd_ha_struct *asd_ha, u32 reg)
{
u32 base = reg & ~(MBAR0_SWB_SIZE-1);
pci_write_config_dword(asd_ha->pcidev, PCI_CONF_MBAR0_SWB, base);
asd_ha->io_handle[0].swb_base = base;
}
static void __asd_write_reg_byte(struct asd_ha_struct *asd_ha, u32 reg, u8 val)
{
struct asd_ha_addrspace *io_handle=&asd_ha->io_handle[0];
BUG_ON(reg >= 0xC0000000 || reg < ALL_BASE_ADDR);
if (io_handle->swa_base <= reg
&& reg < io_handle->swa_base + MBAR0_SWA_SIZE)
asd_write_swa_byte (asd_ha, reg,val);
else if (io_handle->swb_base <= reg
&& reg < io_handle->swb_base + MBAR0_SWB_SIZE)
asd_write_swb_byte (asd_ha, reg, val);
else if (io_handle->swc_base <= reg
&& reg < io_handle->swc_base + MBAR0_SWC_SIZE)
asd_write_swc_byte (asd_ha, reg, val);
else {
/* Ok, we have to move SWB */
asd_move_swb(asd_ha, reg);
asd_write_swb_byte (asd_ha, reg, val);
}
}
#define ASD_WRITE_REG(type, ord) \
void asd_write_reg_##ord (struct asd_ha_struct *asd_ha, u32 reg, type val)\
{ \
struct asd_ha_addrspace *io_handle=&asd_ha->io_handle[0]; \
unsigned long flags; \
BUG_ON(reg >= 0xC0000000 || reg < ALL_BASE_ADDR); \
spin_lock_irqsave(&asd_ha->iolock, flags); \
if (io_handle->swa_base <= reg \
&& reg < io_handle->swa_base + MBAR0_SWA_SIZE) \
asd_write_swa_##ord (asd_ha, reg,val); \
else if (io_handle->swb_base <= reg \
&& reg < io_handle->swb_base + MBAR0_SWB_SIZE) \
asd_write_swb_##ord (asd_ha, reg, val); \
else if (io_handle->swc_base <= reg \
&& reg < io_handle->swc_base + MBAR0_SWC_SIZE) \
asd_write_swc_##ord (asd_ha, reg, val); \
else { \
/* Ok, we have to move SWB */ \
asd_move_swb(asd_ha, reg); \
asd_write_swb_##ord (asd_ha, reg, val); \
} \
spin_unlock_irqrestore(&asd_ha->iolock, flags); \
}
ASD_WRITE_REG(u8, byte);
ASD_WRITE_REG(u16,word);
ASD_WRITE_REG(u32,dword);
static u8 __asd_read_reg_byte(struct asd_ha_struct *asd_ha, u32 reg)
{
struct asd_ha_addrspace *io_handle=&asd_ha->io_handle[0];
u8 val;
BUG_ON(reg >= 0xC0000000 || reg < ALL_BASE_ADDR);
if (io_handle->swa_base <= reg
&& reg < io_handle->swa_base + MBAR0_SWA_SIZE)
val = asd_read_swa_byte (asd_ha, reg);
else if (io_handle->swb_base <= reg
&& reg < io_handle->swb_base + MBAR0_SWB_SIZE)
val = asd_read_swb_byte (asd_ha, reg);
else if (io_handle->swc_base <= reg
&& reg < io_handle->swc_base + MBAR0_SWC_SIZE)
val = asd_read_swc_byte (asd_ha, reg);
else {
/* Ok, we have to move SWB */
asd_move_swb(asd_ha, reg);
val = asd_read_swb_byte (asd_ha, reg);
}
return val;
}
#define ASD_READ_REG(type, ord) \
type asd_read_reg_##ord (struct asd_ha_struct *asd_ha, u32 reg) \
{ \
struct asd_ha_addrspace *io_handle=&asd_ha->io_handle[0]; \
type val; \
unsigned long flags; \
BUG_ON(reg >= 0xC0000000 || reg < ALL_BASE_ADDR); \
spin_lock_irqsave(&asd_ha->iolock, flags); \
if (io_handle->swa_base <= reg \
&& reg < io_handle->swa_base + MBAR0_SWA_SIZE) \
val = asd_read_swa_##ord (asd_ha, reg); \
else if (io_handle->swb_base <= reg \
&& reg < io_handle->swb_base + MBAR0_SWB_SIZE) \
val = asd_read_swb_##ord (asd_ha, reg); \
else if (io_handle->swc_base <= reg \
&& reg < io_handle->swc_base + MBAR0_SWC_SIZE) \
val = asd_read_swc_##ord (asd_ha, reg); \
else { \
/* Ok, we have to move SWB */ \
asd_move_swb(asd_ha, reg); \
val = asd_read_swb_##ord (asd_ha, reg); \
} \
spin_unlock_irqrestore(&asd_ha->iolock, flags); \
return val; \
}
ASD_READ_REG(u8, byte);
ASD_READ_REG(u16,word);
ASD_READ_REG(u32,dword);
/**
* asd_read_reg_string -- read a string of bytes from io space memory
* @asd_ha: pointer to host adapter structure
* @dst: pointer to a destination buffer where data will be written to
* @offs: start offset (register) to read from
* @count: number of bytes to read
*/
void asd_read_reg_string(struct asd_ha_struct *asd_ha, void *dst,
u32 offs, int count)
{
u8 *p = dst;
unsigned long flags;
spin_lock_irqsave(&asd_ha->iolock, flags);
for ( ; count > 0; count--, offs++, p++)
*p = __asd_read_reg_byte(asd_ha, offs);
spin_unlock_irqrestore(&asd_ha->iolock, flags);
}
/**
* asd_write_reg_string -- write a string of bytes to io space memory
* @asd_ha: pointer to host adapter structure
* @src: pointer to source buffer where data will be read from
* @offs: start offset (register) to write to
* @count: number of bytes to write
*/
void asd_write_reg_string(struct asd_ha_struct *asd_ha, void *src,
u32 offs, int count)
{
u8 *p = src;
unsigned long flags;
spin_lock_irqsave(&asd_ha->iolock, flags);
for ( ; count > 0; count--, offs++, p++)
__asd_write_reg_byte(asd_ha, offs, *p);
spin_unlock_irqrestore(&asd_ha->iolock, flags);
}

View File

@@ -0,0 +1,302 @@
/*
* Aic94xx SAS/SATA driver hardware registers definitions.
*
* Copyright (C) 2005 Adaptec, Inc. All rights reserved.
* Copyright (C) 2005 Luben Tuikov <luben_tuikov@adaptec.com>
*
* This file is licensed under GPLv2.
*
* This file is part of the aic94xx driver.
*
* The aic94xx driver 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.
*
* The aic94xx driver 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 the aic94xx driver; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
#ifndef _AIC94XX_REG_H_
#define _AIC94XX_REG_H_
#include <asm/io.h>
#include "aic94xx_hwi.h"
/* Values */
#define AIC9410_DEV_REV_B0 0x8
/* MBAR0, SWA, SWB, SWC, internal memory space addresses */
#define REG_BASE_ADDR 0xB8000000
#define REG_BASE_ADDR_CSEQCIO 0xB8002000
#define REG_BASE_ADDR_EXSI 0xB8042800
#define MBAR0_SWA_SIZE 0x58
extern u32 MBAR0_SWB_SIZE;
#define MBAR0_SWC_SIZE 0x8
/* MBAR1, points to On Chip Memory */
#define OCM_BASE_ADDR 0xA0000000
#define OCM_MAX_SIZE 0x20000
/* Smallest address possible to reference */
#define ALL_BASE_ADDR OCM_BASE_ADDR
/* PCI configuration space registers */
#define PCI_IOBAR_OFFSET 4
#define PCI_CONF_MBAR1 0x6C
#define PCI_CONF_MBAR0_SWA 0x70
#define PCI_CONF_MBAR0_SWB 0x74
#define PCI_CONF_MBAR0_SWC 0x78
#define PCI_CONF_MBAR_KEY 0x7C
#define PCI_CONF_FLSH_BAR 0xB8
#include "aic94xx_reg_def.h"
u8 asd_read_reg_byte(struct asd_ha_struct *asd_ha, u32 reg);
u16 asd_read_reg_word(struct asd_ha_struct *asd_ha, u32 reg);
u32 asd_read_reg_dword(struct asd_ha_struct *asd_ha, u32 reg);
void asd_write_reg_byte(struct asd_ha_struct *asd_ha, u32 reg, u8 val);
void asd_write_reg_word(struct asd_ha_struct *asd_ha, u32 reg, u16 val);
void asd_write_reg_dword(struct asd_ha_struct *asd_ha, u32 reg, u32 val);
void asd_read_reg_string(struct asd_ha_struct *asd_ha, void *dst,
u32 offs, int count);
void asd_write_reg_string(struct asd_ha_struct *asd_ha, void *src,
u32 offs, int count);
#define ASD_READ_OCM(type, ord, S) \
static inline type asd_read_ocm_##ord (struct asd_ha_struct *asd_ha, \
u32 offs) \
{ \
struct asd_ha_addrspace *io_handle = &asd_ha->io_handle[1]; \
type val = read##S (io_handle->addr + (unsigned long) offs); \
rmb(); \
return val; \
}
ASD_READ_OCM(u8, byte, b);
ASD_READ_OCM(u16,word, w);
ASD_READ_OCM(u32,dword,l);
#define ASD_WRITE_OCM(type, ord, S) \
static inline void asd_write_ocm_##ord (struct asd_ha_struct *asd_ha, \
u32 offs, type val) \
{ \
struct asd_ha_addrspace *io_handle = &asd_ha->io_handle[1]; \
write##S (val, io_handle->addr + (unsigned long) offs); \
return; \
}
ASD_WRITE_OCM(u8, byte, b);
ASD_WRITE_OCM(u16,word, w);
ASD_WRITE_OCM(u32,dword,l);
#define ASD_DDBSITE_READ(type, ord) \
static inline type asd_ddbsite_read_##ord (struct asd_ha_struct *asd_ha, \
u16 ddb_site_no, \
u16 offs) \
{ \
asd_write_reg_word(asd_ha, ALTCIOADR, MnDDB_SITE + offs); \
asd_write_reg_word(asd_ha, ADDBPTR, ddb_site_no); \
return asd_read_reg_##ord (asd_ha, CTXACCESS); \
}
ASD_DDBSITE_READ(u32, dword);
ASD_DDBSITE_READ(u16, word);
static inline u8 asd_ddbsite_read_byte(struct asd_ha_struct *asd_ha,
u16 ddb_site_no,
u16 offs)
{
if (offs & 1)
return asd_ddbsite_read_word(asd_ha, ddb_site_no,
offs & ~1) >> 8;
else
return asd_ddbsite_read_word(asd_ha, ddb_site_no,
offs) & 0xFF;
}
#define ASD_DDBSITE_WRITE(type, ord) \
static inline void asd_ddbsite_write_##ord (struct asd_ha_struct *asd_ha, \
u16 ddb_site_no, \
u16 offs, type val) \
{ \
asd_write_reg_word(asd_ha, ALTCIOADR, MnDDB_SITE + offs); \
asd_write_reg_word(asd_ha, ADDBPTR, ddb_site_no); \
asd_write_reg_##ord (asd_ha, CTXACCESS, val); \
}
ASD_DDBSITE_WRITE(u32, dword);
ASD_DDBSITE_WRITE(u16, word);
static inline void asd_ddbsite_write_byte(struct asd_ha_struct *asd_ha,
u16 ddb_site_no,
u16 offs, u8 val)
{
u16 base = offs & ~1;
u16 rval = asd_ddbsite_read_word(asd_ha, ddb_site_no, base);
if (offs & 1)
rval = (val << 8) | (rval & 0xFF);
else
rval = (rval & 0xFF00) | val;
asd_ddbsite_write_word(asd_ha, ddb_site_no, base, rval);
}
#define ASD_SCBSITE_READ(type, ord) \
static inline type asd_scbsite_read_##ord (struct asd_ha_struct *asd_ha, \
u16 scb_site_no, \
u16 offs) \
{ \
asd_write_reg_word(asd_ha, ALTCIOADR, MnSCB_SITE + offs); \
asd_write_reg_word(asd_ha, ASCBPTR, scb_site_no); \
return asd_read_reg_##ord (asd_ha, CTXACCESS); \
}
ASD_SCBSITE_READ(u32, dword);
ASD_SCBSITE_READ(u16, word);
static inline u8 asd_scbsite_read_byte(struct asd_ha_struct *asd_ha,
u16 scb_site_no,
u16 offs)
{
if (offs & 1)
return asd_scbsite_read_word(asd_ha, scb_site_no,
offs & ~1) >> 8;
else
return asd_scbsite_read_word(asd_ha, scb_site_no,
offs) & 0xFF;
}
#define ASD_SCBSITE_WRITE(type, ord) \
static inline void asd_scbsite_write_##ord (struct asd_ha_struct *asd_ha, \
u16 scb_site_no, \
u16 offs, type val) \
{ \
asd_write_reg_word(asd_ha, ALTCIOADR, MnSCB_SITE + offs); \
asd_write_reg_word(asd_ha, ASCBPTR, scb_site_no); \
asd_write_reg_##ord (asd_ha, CTXACCESS, val); \
}
ASD_SCBSITE_WRITE(u32, dword);
ASD_SCBSITE_WRITE(u16, word);
static inline void asd_scbsite_write_byte(struct asd_ha_struct *asd_ha,
u16 scb_site_no,
u16 offs, u8 val)
{
u16 base = offs & ~1;
u16 rval = asd_scbsite_read_word(asd_ha, scb_site_no, base);
if (offs & 1)
rval = (val << 8) | (rval & 0xFF);
else
rval = (rval & 0xFF00) | val;
asd_scbsite_write_word(asd_ha, scb_site_no, base, rval);
}
/**
* asd_ddbsite_update_word -- atomically update a word in a ddb site
* @asd_ha: pointer to host adapter structure
* @ddb_site_no: the DDB site number
* @offs: the offset into the DDB
* @oldval: old value found in that offset
* @newval: the new value to replace it
*
* This function is used when the sequencers are running and we need to
* update a DDB site atomically without expensive pausing and upausing
* of the sequencers and accessing the DDB site through the CIO bus.
*
* Return 0 on success; -EFAULT on parity error; -EAGAIN if the old value
* is different than the current value at that offset.
*/
static inline int asd_ddbsite_update_word(struct asd_ha_struct *asd_ha,
u16 ddb_site_no, u16 offs,
u16 oldval, u16 newval)
{
u8 done;
u16 oval = asd_ddbsite_read_word(asd_ha, ddb_site_no, offs);
if (oval != oldval)
return -EAGAIN;
asd_write_reg_word(asd_ha, AOLDDATA, oldval);
asd_write_reg_word(asd_ha, ANEWDATA, newval);
do {
done = asd_read_reg_byte(asd_ha, ATOMICSTATCTL);
} while (!(done & ATOMICDONE));
if (done & ATOMICERR)
return -EFAULT; /* parity error */
else if (done & ATOMICWIN)
return 0; /* success */
else
return -EAGAIN; /* oldval different than current value */
}
static inline int asd_ddbsite_update_byte(struct asd_ha_struct *asd_ha,
u16 ddb_site_no, u16 offs,
u8 _oldval, u8 _newval)
{
u16 base = offs & ~1;
u16 oval;
u16 nval = asd_ddbsite_read_word(asd_ha, ddb_site_no, base);
if (offs & 1) {
if ((nval >> 8) != _oldval)
return -EAGAIN;
nval = (_newval << 8) | (nval & 0xFF);
oval = (_oldval << 8) | (nval & 0xFF);
} else {
if ((nval & 0xFF) != _oldval)
return -EAGAIN;
nval = (nval & 0xFF00) | _newval;
oval = (nval & 0xFF00) | _oldval;
}
return asd_ddbsite_update_word(asd_ha, ddb_site_no, base, oval, nval);
}
static inline void asd_write_reg_addr(struct asd_ha_struct *asd_ha, u32 reg,
dma_addr_t dma_handle)
{
asd_write_reg_dword(asd_ha, reg, ASD_BUSADDR_LO(dma_handle));
asd_write_reg_dword(asd_ha, reg+4, ASD_BUSADDR_HI(dma_handle));
}
static inline u32 asd_get_cmdctx_size(struct asd_ha_struct *asd_ha)
{
/* DCHREVISION returns 0, possibly broken */
u32 ctxmemsize = asd_read_reg_dword(asd_ha, LmMnINT(0,0)) & CTXMEMSIZE;
return ctxmemsize ? 65536 : 32768;
}
static inline u32 asd_get_devctx_size(struct asd_ha_struct *asd_ha)
{
u32 ctxmemsize = asd_read_reg_dword(asd_ha, LmMnINT(0,0)) & CTXMEMSIZE;
return ctxmemsize ? 8192 : 4096;
}
static inline void asd_disable_ints(struct asd_ha_struct *asd_ha)
{
asd_write_reg_dword(asd_ha, CHIMINTEN, RST_CHIMINTEN);
}
static inline void asd_enable_ints(struct asd_ha_struct *asd_ha)
{
/* Enable COM SAS interrupt on errors, COMSTAT */
asd_write_reg_dword(asd_ha, COMSTATEN,
EN_CSBUFPERR | EN_CSERR | EN_OVLYERR);
/* Enable DCH SAS CFIFTOERR */
asd_write_reg_dword(asd_ha, DCHSTATUS, EN_CFIFTOERR);
/* Enable Host Device interrupts */
asd_write_reg_dword(asd_ha, CHIMINTEN, SET_CHIMINTEN);
}
#endif

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,785 @@
/*
* Aic94xx SAS/SATA driver SAS definitions and hardware interface 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 file is part of the aic94xx driver.
*
* The aic94xx driver 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.
*
* The aic94xx driver 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 the aic94xx driver; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
#ifndef _AIC94XX_SAS_H_
#define _AIC94XX_SAS_H_
#include <scsi/libsas.h>
/* ---------- DDBs ---------- */
/* DDBs are device descriptor blocks which describe a device in the
* domain that this sequencer can maintain low-level connections for
* us. They are be 64 bytes.
*/
struct asd_ddb_ssp_smp_target_port {
u8 conn_type; /* byte 0 */
#define DDB_TP_CONN_TYPE 0x81 /* Initiator port and addr frame type 0x01 */
u8 conn_rate;
__be16 init_conn_tag;
u8 dest_sas_addr[8]; /* bytes 4-11 */
__le16 send_queue_head;
u8 sq_suspended;
u8 ddb_type; /* DDB_TYPE_TARGET */
#define DDB_TYPE_UNUSED 0xFF
#define DDB_TYPE_TARGET 0xFE
#define DDB_TYPE_INITIATOR 0xFD
#define DDB_TYPE_PM_PORT 0xFC
__le16 _r_a;
__be16 awt_def;
u8 compat_features; /* byte 20 */
u8 pathway_blocked_count;
__be16 arb_wait_time;
__be32 more_compat_features; /* byte 24 */
u8 conn_mask;
u8 flags; /* concurrent conn:2,2 and open:0(1) */
#define CONCURRENT_CONN_SUPP 0x04
#define OPEN_REQUIRED 0x01
u16 _r_b;
__le16 exec_queue_tail;
__le16 send_queue_tail;
__le16 sister_ddb;
__le16 _r_c;
u8 max_concurrent_conn;
u8 num_concurrent_conn;
u8 num_contexts;
u8 _r_d;
__le16 active_task_count;
u8 _r_e[9];
u8 itnl_reason; /* I_T nexus loss reason */
__le16 _r_f;
__le16 itnl_timeout;
#define ITNL_TIMEOUT_CONST 0x7D0 /* 2 seconds */
__le32 itnl_timestamp;
} __attribute__ ((packed));
struct asd_ddb_stp_sata_target_port {
u8 conn_type; /* byte 0 */
u8 conn_rate;
__be16 init_conn_tag;
u8 dest_sas_addr[8]; /* bytes 4-11 */
__le16 send_queue_head;
u8 sq_suspended;
u8 ddb_type; /* DDB_TYPE_TARGET */
__le16 _r_a;
__be16 awt_def;
u8 compat_features; /* byte 20 */
u8 pathway_blocked_count;
__be16 arb_wait_time;
__be32 more_compat_features; /* byte 24 */
u8 conn_mask;
u8 flags; /* concurrent conn:2,2 and open:0(1) */
#define SATA_MULTIPORT 0x80
#define SUPPORTS_AFFIL 0x40
#define STP_AFFIL_POL 0x20
u8 _r_b;
u8 flags2; /* STP close policy:0 */
#define STP_CL_POL_NO_TX 0x00
#define STP_CL_POL_BTW_CMDS 0x01
__le16 exec_queue_tail;
__le16 send_queue_tail;
__le16 sister_ddb;
__le16 ata_cmd_scbptr;
__le32 sata_tag_alloc_mask;
__le16 active_task_count;
__le16 _r_c;
__le32 sata_sactive;
u8 num_sata_tags;
u8 sata_status;
u8 sata_ending_status;
u8 itnl_reason; /* I_T nexus loss reason */
__le16 ncq_data_scb_ptr;
__le16 itnl_timeout;
__le32 itnl_timestamp;
} __attribute__ ((packed));
/* This struct asd_ddb_init_port, describes the device descriptor block
* of an initiator port (when the sequencer is operating in target mode).
* Bytes [0,11] and [20,27] are from the OPEN address frame.
* The sequencer allocates an initiator port DDB entry.
*/
struct asd_ddb_init_port {
u8 conn_type; /* byte 0 */
u8 conn_rate;
__be16 init_conn_tag; /* BE */
u8 dest_sas_addr[8];
__le16 send_queue_head; /* LE, byte 12 */
u8 sq_suspended;
u8 ddb_type; /* DDB_TYPE_INITIATOR */
__le16 _r_a;
__be16 awt_def; /* BE */
u8 compat_features;
u8 pathway_blocked_count;
__be16 arb_wait_time; /* BE */
__be32 more_compat_features; /* BE */
u8 conn_mask;
u8 flags; /* == 5 */
u16 _r_b;
__le16 exec_queue_tail; /* execution queue tail */
__le16 send_queue_tail;
__le16 sister_ddb;
__le16 init_resp_timeout; /* initiator response timeout */
__le32 _r_c;
__le16 active_tasks; /* active task count */
__le16 init_list; /* initiator list link pointer */
__le32 _r_d;
u8 max_conn_to[3]; /* from Conn-Disc mode page, in us, LE */
u8 itnl_reason; /* I_T nexus loss reason */
__le16 bus_inact_to; /* from Conn-Disc mode page, in 100 us, LE */
__le16 itnl_to; /* from the Protocol Specific Port Ctrl MP */
__le32 itnl_timestamp;
} __attribute__ ((packed));
/* This struct asd_ddb_sata_tag, describes a look-up table to be used
* by the sequencers. SATA II, IDENTIFY DEVICE data, word 76, bit 8:
* NCQ support. This table is used by the sequencers to find the
* corresponding SCB, given a SATA II tag value.
*/
struct asd_ddb_sata_tag {
__le16 scb_pointer[32];
} __attribute__ ((packed));
/* This struct asd_ddb_sata_pm_table, describes a port number to
* connection handle look-up table. SATA targets attached to a port
* multiplier require a 4-bit port number value. There is one DDB
* entry of this type for each SATA port multiplier (sister DDB).
* Given a SATA PM port number, this table gives us the SATA PM Port
* DDB of the SATA port multiplier port (i.e. the SATA target
* discovered on the port).
*/
struct asd_ddb_sata_pm_table {
__le16 ddb_pointer[16];
__le16 _r_a[16];
} __attribute__ ((packed));
/* This struct asd_ddb_sata_pm_port, describes the SATA port multiplier
* port format DDB.
*/
struct asd_ddb_sata_pm_port {
u8 _r_a[15];
u8 ddb_type;
u8 _r_b[13];
u8 pm_port_flags;
#define PM_PORT_MASK 0xF0
#define PM_PORT_SET 0x02
u8 _r_c[6];
__le16 sister_ddb;
__le16 ata_cmd_scbptr;
__le32 sata_tag_alloc_mask;
__le16 active_task_count;
__le16 parent_ddb;
__le32 sata_sactive;
u8 num_sata_tags;
u8 sata_status;
u8 sata_ending_status;
u8 _r_d[9];
} __attribute__ ((packed));
/* This struct asd_ddb_seq_shared, describes a DDB shared by the
* central and link sequencers. port_map_by_links is indexed phy
* number [0,7]; each byte is a bit mask of all the phys that are in
* the same port as the indexed phy.
*/
struct asd_ddb_seq_shared {
__le16 q_free_ddb_head;
__le16 q_free_ddb_tail;
__le16 q_free_ddb_cnt;
__le16 q_used_ddb_head;
__le16 q_used_ddb_tail;
__le16 shared_mem_lock;
__le16 smp_conn_tag;
__le16 est_nexus_buf_cnt;
__le16 est_nexus_buf_thresh;
u32 _r_a;
u8 settable_max_contexts;
u8 _r_b[23];
u8 conn_not_active;
u8 phy_is_up;
u8 _r_c[8];
u8 port_map_by_links[8];
} __attribute__ ((packed));
/* ---------- SG Element ---------- */
/* This struct sg_el, describes the hardware scatter gather buffer
* element. All entries are little endian. In an SCB, there are 2 of
* this, plus one more, called a link element of this indicating a
* sublist if needed.
*
* A link element has only the bus address set and the flags (DS) bit
* valid. The bus address points to the start of the sublist.
*
* If a sublist is needed, then that sublist should also include the 2
* sg_el embedded in the SCB, in which case next_sg_offset is 32,
* since sizeof(sg_el) = 16; EOS should be 1 and EOL 0 in this case.
*/
struct sg_el {
__le64 bus_addr;
__le32 size;
__le16 _r;
u8 next_sg_offs;
u8 flags;
#define ASD_SG_EL_DS_MASK 0x30
#define ASD_SG_EL_DS_OCM 0x10
#define ASD_SG_EL_DS_HM 0x00
#define ASD_SG_EL_LIST_MASK 0xC0
#define ASD_SG_EL_LIST_EOL 0x40
#define ASD_SG_EL_LIST_EOS 0x80
} __attribute__ ((packed));
/* ---------- SCBs ---------- */
/* An SCB (sequencer control block) is comprised of a common header
* and a task part, for a total of 128 bytes. All fields are in LE
* order, unless otherwise noted.
*/
/* This struct scb_header, defines the SCB header format.
*/
struct scb_header {
__le64 next_scb;
__le16 index; /* transaction context */
u8 opcode;
} __attribute__ ((packed));
/* SCB opcodes: Execution queue
*/
#define INITIATE_SSP_TASK 0x00
#define INITIATE_LONG_SSP_TASK 0x01
#define INITIATE_BIDIR_SSP_TASK 0x02
#define ABORT_TASK 0x03
#define INITIATE_SSP_TMF 0x04
#define SSP_TARG_GET_DATA 0x05
#define SSP_TARG_GET_DATA_GOOD 0x06
#define SSP_TARG_SEND_RESP 0x07
#define QUERY_SSP_TASK 0x08
#define INITIATE_ATA_TASK 0x09
#define INITIATE_ATAPI_TASK 0x0a
#define CONTROL_ATA_DEV 0x0b
#define INITIATE_SMP_TASK 0x0c
#define SMP_TARG_SEND_RESP 0x0f
/* SCB opcodes: Send Queue
*/
#define SSP_TARG_SEND_DATA 0x40
#define SSP_TARG_SEND_DATA_GOOD 0x41
/* SCB opcodes: Link Queue
*/
#define CONTROL_PHY 0x80
#define SEND_PRIMITIVE 0x81
#define INITIATE_LINK_ADM_TASK 0x82
/* SCB opcodes: other
*/
#define EMPTY_SCB 0xc0
#define INITIATE_SEQ_ADM_TASK 0xc1
#define EST_ICL_TARG_WINDOW 0xc2
#define COPY_MEM 0xc3
#define CLEAR_NEXUS 0xc4
#define INITIATE_DDB_ADM_TASK 0xc6
#define ESTABLISH_NEXUS_ESCB 0xd0
#define LUN_SIZE 8
/* See SAS spec, task IU
*/
struct ssp_task_iu {
u8 lun[LUN_SIZE]; /* BE */
u16 _r_a;
u8 tmf;
u8 _r_b;
__be16 tag; /* BE */
u8 _r_c[14];
} __attribute__ ((packed));
/* See SAS spec, command IU
*/
struct ssp_command_iu {
u8 lun[LUN_SIZE];
u8 _r_a;
u8 efb_prio_attr; /* enable first burst, task prio & attr */
#define EFB_MASK 0x80
#define TASK_PRIO_MASK 0x78
#define TASK_ATTR_MASK 0x07
u8 _r_b;
u8 add_cdb_len; /* in dwords, since bit 0,1 are reserved */
union {
u8 cdb[16];
struct {
__le64 long_cdb_addr; /* bus address, LE */
__le32 long_cdb_size; /* LE */
u8 _r_c[3];
u8 eol_ds; /* eol:6,6, ds:5,4 */
} long_cdb; /* sequencer extension */
};
} __attribute__ ((packed));
struct xfer_rdy_iu {
__be32 requested_offset; /* BE */
__be32 write_data_len; /* BE */
__be32 _r_a;
} __attribute__ ((packed));
/* ---------- SCB tasks ---------- */
/* This is both ssp_task and long_ssp_task
*/
struct initiate_ssp_task {
u8 proto_conn_rate; /* proto:6,4, conn_rate:3,0 */
__le32 total_xfer_len;
struct ssp_frame_hdr ssp_frame;
struct ssp_command_iu ssp_cmd;
__le16 sister_scb; /* 0xFFFF */
__le16 conn_handle; /* index to DDB for the intended target */
u8 data_dir; /* :1,0 */
#define DATA_DIR_NONE 0x00
#define DATA_DIR_IN 0x01
#define DATA_DIR_OUT 0x02
#define DATA_DIR_BYRECIPIENT 0x03
u8 _r_a;
u8 retry_count;
u8 _r_b[5];
struct sg_el sg_element[3]; /* 2 real and 1 link */
} __attribute__ ((packed));
/* This defines both ata_task and atapi_task.
* ata: C bit of FIS should be 1,
* atapi: C bit of FIS should be 1, and command register should be 0xA0,
* to indicate a packet command.
*/
struct initiate_ata_task {
u8 proto_conn_rate;
__le32 total_xfer_len;
struct host_to_dev_fis fis;
__le32 data_offs;
u8 atapi_packet[16];
u8 _r_a[12];
__le16 sister_scb;
__le16 conn_handle;
u8 ata_flags; /* CSMI:6,6, DTM:4,4, QT:3,3, data dir:1,0 */
#define CSMI_TASK 0x40
#define DATA_XFER_MODE_DMA 0x10
#define ATA_Q_TYPE_MASK 0x08
#define ATA_Q_TYPE_UNTAGGED 0x00
#define ATA_Q_TYPE_NCQ 0x08
u8 _r_b;
u8 retry_count;
u8 _r_c;
u8 flags;
#define STP_AFFIL_POLICY 0x20
#define SET_AFFIL_POLICY 0x10
#define RET_PARTIAL_SGLIST 0x02
u8 _r_d[3];
struct sg_el sg_element[3];
} __attribute__ ((packed));
struct initiate_smp_task {
u8 proto_conn_rate;
u8 _r_a[40];
struct sg_el smp_req;
__le16 sister_scb;
__le16 conn_handle;
u8 _r_c[8];
struct sg_el smp_resp;
u8 _r_d[32];
} __attribute__ ((packed));
struct control_phy {
u8 phy_id;
u8 sub_func;
#define DISABLE_PHY 0x00
#define ENABLE_PHY 0x01
#define RELEASE_SPINUP_HOLD 0x02
#define ENABLE_PHY_NO_SAS_OOB 0x03
#define ENABLE_PHY_NO_SATA_OOB 0x04
#define PHY_NO_OP 0x05
#define EXECUTE_HARD_RESET 0x81
u8 func_mask;
u8 speed_mask;
u8 hot_plug_delay;
u8 port_type;
u8 flags;
#define DEV_PRES_TIMER_OVERRIDE_ENABLE 0x01
#define DISABLE_PHY_IF_OOB_FAILS 0x02
__le32 timeout_override;
u8 link_reset_retries;
u8 _r_a[47];
__le16 conn_handle;
u8 _r_b[56];
} __attribute__ ((packed));
struct control_ata_dev {
u8 proto_conn_rate;
__le32 _r_a;
struct host_to_dev_fis fis;
u8 _r_b[32];
__le16 sister_scb;
__le16 conn_handle;
u8 ata_flags; /* 0 */
u8 _r_c[55];
} __attribute__ ((packed));
struct empty_scb {
u8 num_valid;
__le32 _r_a;
#define ASD_EDBS_PER_SCB 7
/* header+data+CRC+DMA suffix data */
#define ASD_EDB_SIZE (24+1024+4+16)
struct sg_el eb[ASD_EDBS_PER_SCB];
#define ELEMENT_NOT_VALID 0xC0
} __attribute__ ((packed));
struct initiate_link_adm {
u8 phy_id;
u8 sub_func;
#define GET_LINK_ERROR_COUNT 0x00
#define RESET_LINK_ERROR_COUNT 0x01
#define ENABLE_NOTIFY_SPINUP_INTS 0x02
u8 _r_a[57];
__le16 conn_handle;
u8 _r_b[56];
} __attribute__ ((packed));
struct copy_memory {
u8 _r_a;
__le16 xfer_len;
__le16 _r_b;
__le64 src_busaddr;
u8 src_ds; /* See definition of sg_el */
u8 _r_c[45];
__le16 conn_handle;
__le64 _r_d;
__le64 dest_busaddr;
u8 dest_ds; /* See definition of sg_el */
u8 _r_e[39];
} __attribute__ ((packed));
struct abort_task {
u8 proto_conn_rate;
__le32 _r_a;
struct ssp_frame_hdr ssp_frame;
struct ssp_task_iu ssp_task;
__le16 sister_scb;
__le16 conn_handle;
u8 flags; /* ovrd_itnl_timer:3,3, suspend_data_trans:2,2 */
#define SUSPEND_DATA_TRANS 0x04
u8 _r_b;
u8 retry_count;
u8 _r_c[5];
__le16 index; /* Transaction context of task to be queried */
__le16 itnl_to;
u8 _r_d[44];
} __attribute__ ((packed));
struct clear_nexus {
u8 nexus;
#define NEXUS_ADAPTER 0x00
#define NEXUS_PORT 0x01
#define NEXUS_I_T 0x02
#define NEXUS_I_T_L 0x03
#define NEXUS_TAG 0x04
#define NEXUS_TRANS_CX 0x05
#define NEXUS_SATA_TAG 0x06
#define NEXUS_T_L 0x07
#define NEXUS_L 0x08
#define NEXUS_T_TAG 0x09
__le32 _r_a;
u8 flags;
#define SUSPEND_TX 0x80
#define RESUME_TX 0x40
#define SEND_Q 0x04
#define EXEC_Q 0x02
#define NOTINQ 0x01
u8 _r_b[3];
u8 conn_mask;
u8 _r_c[19];
struct ssp_task_iu ssp_task; /* LUN and TAG */
__le16 _r_d;
__le16 conn_handle;
__le64 _r_e;
__le16 index; /* Transaction context of task to be cleared */
__le16 context; /* Clear nexus context */
u8 _r_f[44];
} __attribute__ ((packed));
struct initiate_ssp_tmf {
u8 proto_conn_rate;
__le32 _r_a;
struct ssp_frame_hdr ssp_frame;
struct ssp_task_iu ssp_task;
__le16 sister_scb;
__le16 conn_handle;
u8 flags; /* itnl override and suspend data tx */
#define OVERRIDE_ITNL_TIMER 8
u8 _r_b;
u8 retry_count;
u8 _r_c[5];
__le16 index; /* Transaction context of task to be queried */
__le16 itnl_to;
u8 _r_d[44];
} __attribute__ ((packed));
/* Transmits an arbitrary primitive on the link.
* Used for NOTIFY and BROADCAST.
*/
struct send_prim {
u8 phy_id;
u8 wait_transmit; /* :0,0 */
u8 xmit_flags;
#define XMTPSIZE_MASK 0xF0
#define XMTPSIZE_SINGLE 0x10
#define XMTPSIZE_REPEATED 0x20
#define XMTPSIZE_CONT 0x20
#define XMTPSIZE_TRIPLE 0x30
#define XMTPSIZE_REDUNDANT 0x60
#define XMTPSIZE_INF 0
#define XMTCONTEN 0x04
#define XMTPFRM 0x02 /* Transmit at the next frame boundary */
#define XMTPIMM 0x01 /* Transmit immediately */
__le16 _r_a;
u8 prim[4]; /* K, D0, D1, D2 */
u8 _r_b[50];
__le16 conn_handle;
u8 _r_c[56];
} __attribute__ ((packed));
/* This describes both SSP Target Get Data and SSP Target Get Data And
* Send Good Response SCBs. Used when the sequencer is operating in
* target mode...
*/
struct ssp_targ_get_data {
u8 proto_conn_rate;
__le32 total_xfer_len;
struct ssp_frame_hdr ssp_frame;
struct xfer_rdy_iu xfer_rdy;
u8 lun[LUN_SIZE];
__le64 _r_a;
__le16 sister_scb;
__le16 conn_handle;
u8 data_dir; /* 01b */
u8 _r_b;
u8 retry_count;
u8 _r_c[5];
struct sg_el sg_element[3];
} __attribute__ ((packed));
/* ---------- The actual SCB struct ---------- */
struct scb {
struct scb_header header;
union {
struct initiate_ssp_task ssp_task;
struct initiate_ata_task ata_task;
struct initiate_smp_task smp_task;
struct control_phy control_phy;
struct control_ata_dev control_ata_dev;
struct empty_scb escb;
struct initiate_link_adm link_adm;
struct copy_memory cp_mem;
struct abort_task abort_task;
struct clear_nexus clear_nexus;
struct initiate_ssp_tmf ssp_tmf;
};
} __attribute__ ((packed));
/* ---------- Done List ---------- */
/* The done list entry opcode field is defined below.
* The mnemonic encoding and meaning is as follows:
* TC - Task Complete, status was received and acknowledged
* TF - Task Failed, indicates an error prior to receiving acknowledgment
* for the command:
* - no conn,
* - NACK or R_ERR received in response to this command,
* - credit blocked or not available, or in the case of SMP request,
* - no SMP response was received.
* In these four cases it is known that the target didn't receive the
* command.
* TI - Task Interrupted, error after the command was acknowledged. It is
* known that the command was received by the target.
* TU - Task Unacked, command was transmitted but neither ACK (R_OK) nor NAK
* (R_ERR) was received due to loss of signal, broken connection, loss of
* dword sync or other reason. The application client should send the
* appropriate task query.
* TA - Task Aborted, see TF.
* _RESP - The completion includes an empty buffer containing status.
* TO - Timeout.
*/
#define TC_NO_ERROR 0x00
#define TC_UNDERRUN 0x01
#define TC_OVERRUN 0x02
#define TF_OPEN_TO 0x03
#define TF_OPEN_REJECT 0x04
#define TI_BREAK 0x05
#define TI_PROTO_ERR 0x06
#define TC_SSP_RESP 0x07
#define TI_PHY_DOWN 0x08
#define TF_PHY_DOWN 0x09
#define TC_LINK_ADM_RESP 0x0a
#define TC_CSMI 0x0b
#define TC_ATA_RESP 0x0c
#define TU_PHY_DOWN 0x0d
#define TU_BREAK 0x0e
#define TI_SATA_TO 0x0f
#define TI_NAK 0x10
#define TC_CONTROL_PHY 0x11
#define TF_BREAK 0x12
#define TC_RESUME 0x13
#define TI_ACK_NAK_TO 0x14
#define TF_SMPRSP_TO 0x15
#define TF_SMP_XMIT_RCV_ERR 0x16
#define TC_PARTIAL_SG_LIST 0x17
#define TU_ACK_NAK_TO 0x18
#define TU_SATA_TO 0x19
#define TF_NAK_RECV 0x1a
#define TA_I_T_NEXUS_LOSS 0x1b
#define TC_ATA_R_ERR_RECV 0x1c
#define TF_TMF_NO_CTX 0x1d
#define TA_ON_REQ 0x1e
#define TF_TMF_NO_TAG 0x1f
#define TF_TMF_TAG_FREE 0x20
#define TF_TMF_TASK_DONE 0x21
#define TF_TMF_NO_CONN_HANDLE 0x22
#define TC_TASK_CLEARED 0x23
#define TI_SYNCS_RECV 0x24
#define TU_SYNCS_RECV 0x25
#define TF_IRTT_TO 0x26
#define TF_NO_SMP_CONN 0x27
#define TF_IU_SHORT 0x28
#define TF_DATA_OFFS_ERR 0x29
#define TF_INV_CONN_HANDLE 0x2a
#define TF_REQUESTED_N_PENDING 0x2b
/* 0xc1 - 0xc7: empty buffer received,
0xd1 - 0xd7: establish nexus empty buffer received
*/
/* This is the ESCB mask */
#define ESCB_RECVD 0xC0
/* This struct done_list_struct defines the done list entry.
* All fields are LE.
*/
struct done_list_struct {
__le16 index; /* aka transaction context */
u8 opcode;
u8 status_block[4];
u8 toggle; /* bit 0 */
#define DL_TOGGLE_MASK 0x01
} __attribute__ ((packed));
/* ---------- PHYS ---------- */
struct asd_phy {
struct asd_sas_phy sas_phy;
struct asd_phy_desc *phy_desc; /* hw profile */
struct sas_identify_frame *identify_frame;
struct asd_dma_tok *id_frm_tok;
u8 frame_rcvd[ASD_EDB_SIZE];
};
#define ASD_SCB_SIZE sizeof(struct scb)
#define ASD_DDB_SIZE sizeof(struct asd_ddb_ssp_smp_target_port)
/* Define this to 0 if you do not want NOTIFY (ENABLE SPINIP) sent.
* Default: 0x10 (it's a mask)
*/
#define ASD_NOTIFY_ENABLE_SPINUP 0x10
/* If enabled, set this to the interval between transmission
* of NOTIFY (ENABLE SPINUP). In units of 200 us.
*/
#define ASD_NOTIFY_TIMEOUT 2500
/* Initial delay after OOB, before we transmit NOTIFY (ENABLE SPINUP).
* If 0, transmit immediately. In milliseconds.
*/
#define ASD_NOTIFY_DOWN_COUNT 0
/* Device present timer timeout constant, 10 ms. */
#define ASD_DEV_PRESENT_TIMEOUT 0x2710
#define ASD_SATA_INTERLOCK_TIMEOUT 0
/* How long to wait before shutting down an STP connection, unless
* an STP target sent frame(s). 50 usec.
* IGNORED by the sequencer (i.e. value 0 always).
*/
#define ASD_STP_SHUTDOWN_TIMEOUT 0x0
/* ATA soft reset timer timeout. 5 usec. */
#define ASD_SRST_ASSERT_TIMEOUT 0x05
/* 31 sec */
#define ASD_RCV_FIS_TIMEOUT 0x01D905C0
#define ASD_ONE_MILLISEC_TIMEOUT 0x03e8
/* COMINIT timer */
#define ASD_TEN_MILLISEC_TIMEOUT 0x2710
#define ASD_COMINIT_TIMEOUT ASD_TEN_MILLISEC_TIMEOUT
/* 1 sec */
#define ASD_SMP_RCV_TIMEOUT 0x000F4240
#endif

View File

@@ -0,0 +1,732 @@
/*
* Aic94xx SAS/SATA driver SCB management.
*
* Copyright (C) 2005 Adaptec, Inc. All rights reserved.
* Copyright (C) 2005 Luben Tuikov <luben_tuikov@adaptec.com>
*
* This file is licensed under GPLv2.
*
* This file is part of the aic94xx driver.
*
* The aic94xx driver 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.
*
* The aic94xx driver 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 the aic94xx driver; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
#include <linux/pci.h>
#include "aic94xx.h"
#include "aic94xx_reg.h"
#include "aic94xx_hwi.h"
#include "aic94xx_seq.h"
#include "aic94xx_dump.h"
/* ---------- EMPTY SCB ---------- */
#define DL_PHY_MASK 7
#define BYTES_DMAED 0
#define PRIMITIVE_RECVD 0x08
#define PHY_EVENT 0x10
#define LINK_RESET_ERROR 0x18
#define TIMER_EVENT 0x20
#define REQ_TASK_ABORT 0xF0
#define REQ_DEVICE_RESET 0xF1
#define SIGNAL_NCQ_ERROR 0xF2
#define CLEAR_NCQ_ERROR 0xF3
#define PHY_EVENTS_STATUS (CURRENT_LOSS_OF_SIGNAL | CURRENT_OOB_DONE \
| CURRENT_SPINUP_HOLD | CURRENT_GTO_TIMEOUT \
| CURRENT_OOB_ERROR)
static inline void get_lrate_mode(struct asd_phy *phy, u8 oob_mode)
{
switch (oob_mode & 7) {
case PHY_SPEED_60:
/* FIXME: sas transport class doesn't have this */
phy->sas_phy.linkrate = PHY_LINKRATE_6;
phy->sas_phy.phy->negotiated_linkrate = SAS_LINK_RATE_6_0_GBPS;
break;
case PHY_SPEED_30:
phy->sas_phy.linkrate = PHY_LINKRATE_3;
phy->sas_phy.phy->negotiated_linkrate = SAS_LINK_RATE_3_0_GBPS;
break;
case PHY_SPEED_15:
phy->sas_phy.linkrate = PHY_LINKRATE_1_5;
phy->sas_phy.phy->negotiated_linkrate = SAS_LINK_RATE_1_5_GBPS;
break;
}
if (oob_mode & SAS_MODE)
phy->sas_phy.oob_mode = SAS_OOB_MODE;
else if (oob_mode & SATA_MODE)
phy->sas_phy.oob_mode = SATA_OOB_MODE;
}
static inline void asd_phy_event_tasklet(struct asd_ascb *ascb,
struct done_list_struct *dl)
{
struct asd_ha_struct *asd_ha = ascb->ha;
struct sas_ha_struct *sas_ha = &asd_ha->sas_ha;
int phy_id = dl->status_block[0] & DL_PHY_MASK;
struct asd_phy *phy = &asd_ha->phys[phy_id];
u8 oob_status = dl->status_block[1] & PHY_EVENTS_STATUS;
u8 oob_mode = dl->status_block[2];
switch (oob_status) {
case CURRENT_LOSS_OF_SIGNAL:
/* directly attached device was removed */
ASD_DPRINTK("phy%d: device unplugged\n", phy_id);
asd_turn_led(asd_ha, phy_id, 0);
sas_phy_disconnected(&phy->sas_phy);
sas_ha->notify_phy_event(&phy->sas_phy, PHYE_LOSS_OF_SIGNAL);
break;
case CURRENT_OOB_DONE:
/* hot plugged device */
asd_turn_led(asd_ha, phy_id, 1);
get_lrate_mode(phy, oob_mode);
ASD_DPRINTK("phy%d device plugged: lrate:0x%x, proto:0x%x\n",
phy_id, phy->sas_phy.linkrate, phy->sas_phy.iproto);
sas_ha->notify_phy_event(&phy->sas_phy, PHYE_OOB_DONE);
break;
case CURRENT_SPINUP_HOLD:
/* hot plug SATA, no COMWAKE sent */
asd_turn_led(asd_ha, phy_id, 1);
sas_ha->notify_phy_event(&phy->sas_phy, PHYE_SPINUP_HOLD);
break;
case CURRENT_GTO_TIMEOUT:
case CURRENT_OOB_ERROR:
ASD_DPRINTK("phy%d error while OOB: oob status:0x%x\n", phy_id,
dl->status_block[1]);
asd_turn_led(asd_ha, phy_id, 0);
sas_phy_disconnected(&phy->sas_phy);
sas_ha->notify_phy_event(&phy->sas_phy, PHYE_OOB_ERROR);
break;
}
}
/* If phys are enabled sparsely, this will do the right thing. */
static inline unsigned ord_phy(struct asd_ha_struct *asd_ha,
struct asd_phy *phy)
{
u8 enabled_mask = asd_ha->hw_prof.enabled_phys;
int i, k = 0;
for_each_phy(enabled_mask, enabled_mask, i) {
if (&asd_ha->phys[i] == phy)
return k;
k++;
}
return 0;
}
/**
* asd_get_attached_sas_addr -- extract/generate attached SAS address
* phy: pointer to asd_phy
* sas_addr: pointer to buffer where the SAS address is to be written
*
* This function extracts the SAS address from an IDENTIFY frame
* received. If OOB is SATA, then a SAS address is generated from the
* HA tables.
*
* LOCKING: the frame_rcvd_lock needs to be held since this parses the frame
* buffer.
*/
static inline void asd_get_attached_sas_addr(struct asd_phy *phy, u8 *sas_addr)
{
if (phy->sas_phy.frame_rcvd[0] == 0x34
&& phy->sas_phy.oob_mode == SATA_OOB_MODE) {
struct asd_ha_struct *asd_ha = phy->sas_phy.ha->lldd_ha;
/* FIS device-to-host */
u64 addr = be64_to_cpu(*(__be64 *)phy->phy_desc->sas_addr);
addr += asd_ha->hw_prof.sata_name_base + ord_phy(asd_ha, phy);
*(__be64 *)sas_addr = cpu_to_be64(addr);
} else {
struct sas_identify_frame *idframe =
(void *) phy->sas_phy.frame_rcvd;
memcpy(sas_addr, idframe->sas_addr, SAS_ADDR_SIZE);
}
}
static inline void asd_bytes_dmaed_tasklet(struct asd_ascb *ascb,
struct done_list_struct *dl,
int edb_id, int phy_id)
{
unsigned long flags;
int edb_el = edb_id + ascb->edb_index;
struct asd_dma_tok *edb = ascb->ha->seq.edb_arr[edb_el];
struct asd_phy *phy = &ascb->ha->phys[phy_id];
struct sas_ha_struct *sas_ha = phy->sas_phy.ha;
u16 size = ((dl->status_block[3] & 7) << 8) | dl->status_block[2];
size = min(size, (u16) sizeof(phy->frame_rcvd));
spin_lock_irqsave(&phy->sas_phy.frame_rcvd_lock, flags);
memcpy(phy->sas_phy.frame_rcvd, edb->vaddr, size);
phy->sas_phy.frame_rcvd_size = size;
asd_get_attached_sas_addr(phy, phy->sas_phy.attached_sas_addr);
spin_unlock_irqrestore(&phy->sas_phy.frame_rcvd_lock, flags);
asd_dump_frame_rcvd(phy, dl);
sas_ha->notify_port_event(&phy->sas_phy, PORTE_BYTES_DMAED);
}
static inline void asd_link_reset_err_tasklet(struct asd_ascb *ascb,
struct done_list_struct *dl,
int phy_id)
{
struct asd_ha_struct *asd_ha = ascb->ha;
struct sas_ha_struct *sas_ha = &asd_ha->sas_ha;
struct asd_sas_phy *sas_phy = sas_ha->sas_phy[phy_id];
u8 lr_error = dl->status_block[1];
u8 retries_left = dl->status_block[2];
switch (lr_error) {
case 0:
ASD_DPRINTK("phy%d: Receive ID timer expired\n", phy_id);
break;
case 1:
ASD_DPRINTK("phy%d: Loss of signal\n", phy_id);
break;
case 2:
ASD_DPRINTK("phy%d: Loss of dword sync\n", phy_id);
break;
case 3:
ASD_DPRINTK("phy%d: Receive FIS timeout\n", phy_id);
break;
default:
ASD_DPRINTK("phy%d: unknown link reset error code: 0x%x\n",
phy_id, lr_error);
break;
}
asd_turn_led(asd_ha, phy_id, 0);
sas_phy_disconnected(sas_phy);
sas_ha->notify_port_event(sas_phy, PORTE_LINK_RESET_ERR);
if (retries_left == 0) {
int num = 1;
struct asd_ascb *cp = asd_ascb_alloc_list(ascb->ha, &num,
GFP_ATOMIC);
if (!cp) {
asd_printk("%s: out of memory\n", __FUNCTION__);
goto out;
}
ASD_DPRINTK("phy%d: retries:0 performing link reset seq\n",
phy_id);
asd_build_control_phy(cp, phy_id, ENABLE_PHY);
if (asd_post_ascb_list(ascb->ha, cp, 1) != 0)
asd_ascb_free(cp);
}
out:
;
}
static inline void asd_primitive_rcvd_tasklet(struct asd_ascb *ascb,
struct done_list_struct *dl,
int phy_id)
{
unsigned long flags;
struct sas_ha_struct *sas_ha = &ascb->ha->sas_ha;
struct asd_sas_phy *sas_phy = sas_ha->sas_phy[phy_id];
u8 reg = dl->status_block[1];
u32 cont = dl->status_block[2] << ((reg & 3)*8);
reg &= ~3;
switch (reg) {
case LmPRMSTAT0BYTE0:
switch (cont) {
case LmBROADCH:
case LmBROADRVCH0:
case LmBROADRVCH1:
case LmBROADSES:
ASD_DPRINTK("phy%d: BROADCAST change received:%d\n",
phy_id, cont);
spin_lock_irqsave(&sas_phy->sas_prim_lock, flags);
sas_phy->sas_prim = ffs(cont);
spin_unlock_irqrestore(&sas_phy->sas_prim_lock, flags);
sas_ha->notify_port_event(sas_phy,PORTE_BROADCAST_RCVD);
break;
case LmUNKNOWNP:
ASD_DPRINTK("phy%d: unknown BREAK\n", phy_id);
break;
default:
ASD_DPRINTK("phy%d: primitive reg:0x%x, cont:0x%04x\n",
phy_id, reg, cont);
break;
}
break;
case LmPRMSTAT1BYTE0:
switch (cont) {
case LmHARDRST:
ASD_DPRINTK("phy%d: HARD_RESET primitive rcvd\n",
phy_id);
/* The sequencer disables all phys on that port.
* We have to re-enable the phys ourselves. */
sas_ha->notify_port_event(sas_phy, PORTE_HARD_RESET);
break;
default:
ASD_DPRINTK("phy%d: primitive reg:0x%x, cont:0x%04x\n",
phy_id, reg, cont);
break;
}
break;
default:
ASD_DPRINTK("unknown primitive register:0x%x\n",
dl->status_block[1]);
break;
}
}
/**
* asd_invalidate_edb -- invalidate an EDB and if necessary post the ESCB
* @ascb: pointer to Empty SCB
* @edb_id: index [0,6] to the empty data buffer which is to be invalidated
*
* After an EDB has been invalidated, if all EDBs in this ESCB have been
* invalidated, the ESCB is posted back to the sequencer.
* Context is tasklet/IRQ.
*/
void asd_invalidate_edb(struct asd_ascb *ascb, int edb_id)
{
struct asd_seq_data *seq = &ascb->ha->seq;
struct empty_scb *escb = &ascb->scb->escb;
struct sg_el *eb = &escb->eb[edb_id];
struct asd_dma_tok *edb = seq->edb_arr[ascb->edb_index + edb_id];
memset(edb->vaddr, 0, ASD_EDB_SIZE);
eb->flags |= ELEMENT_NOT_VALID;
escb->num_valid--;
if (escb->num_valid == 0) {
int i;
/* ASD_DPRINTK("reposting escb: vaddr: 0x%p, "
"dma_handle: 0x%08llx, next: 0x%08llx, "
"index:%d, opcode:0x%02x\n",
ascb->dma_scb.vaddr,
(u64)ascb->dma_scb.dma_handle,
le64_to_cpu(ascb->scb->header.next_scb),
le16_to_cpu(ascb->scb->header.index),
ascb->scb->header.opcode);
*/
escb->num_valid = ASD_EDBS_PER_SCB;
for (i = 0; i < ASD_EDBS_PER_SCB; i++)
escb->eb[i].flags = 0;
if (!list_empty(&ascb->list))
list_del_init(&ascb->list);
i = asd_post_escb_list(ascb->ha, ascb, 1);
if (i)
asd_printk("couldn't post escb, err:%d\n", i);
}
}
static void escb_tasklet_complete(struct asd_ascb *ascb,
struct done_list_struct *dl)
{
struct asd_ha_struct *asd_ha = ascb->ha;
struct sas_ha_struct *sas_ha = &asd_ha->sas_ha;
int edb = (dl->opcode & DL_PHY_MASK) - 1; /* [0xc1,0xc7] -> [0,6] */
u8 sb_opcode = dl->status_block[0];
int phy_id = sb_opcode & DL_PHY_MASK;
struct asd_sas_phy *sas_phy = sas_ha->sas_phy[phy_id];
if (edb > 6 || edb < 0) {
ASD_DPRINTK("edb is 0x%x! dl->opcode is 0x%x\n",
edb, dl->opcode);
ASD_DPRINTK("sb_opcode : 0x%x, phy_id: 0x%x\n",
sb_opcode, phy_id);
ASD_DPRINTK("escb: vaddr: 0x%p, "
"dma_handle: 0x%llx, next: 0x%llx, "
"index:%d, opcode:0x%02x\n",
ascb->dma_scb.vaddr,
(unsigned long long)ascb->dma_scb.dma_handle,
(unsigned long long)
le64_to_cpu(ascb->scb->header.next_scb),
le16_to_cpu(ascb->scb->header.index),
ascb->scb->header.opcode);
}
sb_opcode &= ~DL_PHY_MASK;
switch (sb_opcode) {
case BYTES_DMAED:
ASD_DPRINTK("%s: phy%d: BYTES_DMAED\n", __FUNCTION__, phy_id);
asd_bytes_dmaed_tasklet(ascb, dl, edb, phy_id);
break;
case PRIMITIVE_RECVD:
ASD_DPRINTK("%s: phy%d: PRIMITIVE_RECVD\n", __FUNCTION__,
phy_id);
asd_primitive_rcvd_tasklet(ascb, dl, phy_id);
break;
case PHY_EVENT:
ASD_DPRINTK("%s: phy%d: PHY_EVENT\n", __FUNCTION__, phy_id);
asd_phy_event_tasklet(ascb, dl);
break;
case LINK_RESET_ERROR:
ASD_DPRINTK("%s: phy%d: LINK_RESET_ERROR\n", __FUNCTION__,
phy_id);
asd_link_reset_err_tasklet(ascb, dl, phy_id);
break;
case TIMER_EVENT:
ASD_DPRINTK("%s: phy%d: TIMER_EVENT, lost dw sync\n",
__FUNCTION__, phy_id);
asd_turn_led(asd_ha, phy_id, 0);
/* the device is gone */
sas_phy_disconnected(sas_phy);
sas_ha->notify_port_event(sas_phy, PORTE_TIMER_EVENT);
break;
case REQ_TASK_ABORT:
ASD_DPRINTK("%s: phy%d: REQ_TASK_ABORT\n", __FUNCTION__,
phy_id);
break;
case REQ_DEVICE_RESET:
ASD_DPRINTK("%s: phy%d: REQ_DEVICE_RESET\n", __FUNCTION__,
phy_id);
break;
case SIGNAL_NCQ_ERROR:
ASD_DPRINTK("%s: phy%d: SIGNAL_NCQ_ERROR\n", __FUNCTION__,
phy_id);
break;
case CLEAR_NCQ_ERROR:
ASD_DPRINTK("%s: phy%d: CLEAR_NCQ_ERROR\n", __FUNCTION__,
phy_id);
break;
default:
ASD_DPRINTK("%s: phy%d: unknown event:0x%x\n", __FUNCTION__,
phy_id, sb_opcode);
ASD_DPRINTK("edb is 0x%x! dl->opcode is 0x%x\n",
edb, dl->opcode);
ASD_DPRINTK("sb_opcode : 0x%x, phy_id: 0x%x\n",
sb_opcode, phy_id);
ASD_DPRINTK("escb: vaddr: 0x%p, "
"dma_handle: 0x%llx, next: 0x%llx, "
"index:%d, opcode:0x%02x\n",
ascb->dma_scb.vaddr,
(unsigned long long)ascb->dma_scb.dma_handle,
(unsigned long long)
le64_to_cpu(ascb->scb->header.next_scb),
le16_to_cpu(ascb->scb->header.index),
ascb->scb->header.opcode);
break;
}
asd_invalidate_edb(ascb, edb);
}
int asd_init_post_escbs(struct asd_ha_struct *asd_ha)
{
struct asd_seq_data *seq = &asd_ha->seq;
int i;
for (i = 0; i < seq->num_escbs; i++)
seq->escb_arr[i]->tasklet_complete = escb_tasklet_complete;
ASD_DPRINTK("posting %d escbs\n", i);
return asd_post_escb_list(asd_ha, seq->escb_arr[0], seq->num_escbs);
}
/* ---------- CONTROL PHY ---------- */
#define CONTROL_PHY_STATUS (CURRENT_DEVICE_PRESENT | CURRENT_OOB_DONE \
| CURRENT_SPINUP_HOLD | CURRENT_GTO_TIMEOUT \
| CURRENT_OOB_ERROR)
/**
* control_phy_tasklet_complete -- tasklet complete for CONTROL PHY ascb
* @ascb: pointer to an ascb
* @dl: pointer to the done list entry
*
* This function completes a CONTROL PHY scb and frees the ascb.
* A note on LEDs:
* - an LED blinks if there is IO though it,
* - if a device is connected to the LED, it is lit,
* - if no device is connected to the LED, is is dimmed (off).
*/
static void control_phy_tasklet_complete(struct asd_ascb *ascb,
struct done_list_struct *dl)
{
struct asd_ha_struct *asd_ha = ascb->ha;
struct scb *scb = ascb->scb;
struct control_phy *control_phy = &scb->control_phy;
u8 phy_id = control_phy->phy_id;
struct asd_phy *phy = &ascb->ha->phys[phy_id];
u8 status = dl->status_block[0];
u8 oob_status = dl->status_block[1];
u8 oob_mode = dl->status_block[2];
/* u8 oob_signals= dl->status_block[3]; */
if (status != 0) {
ASD_DPRINTK("%s: phy%d status block opcode:0x%x\n",
__FUNCTION__, phy_id, status);
goto out;
}
switch (control_phy->sub_func) {
case DISABLE_PHY:
asd_ha->hw_prof.enabled_phys &= ~(1 << phy_id);
asd_turn_led(asd_ha, phy_id, 0);
asd_control_led(asd_ha, phy_id, 0);
ASD_DPRINTK("%s: disable phy%d\n", __FUNCTION__, phy_id);
break;
case ENABLE_PHY:
asd_control_led(asd_ha, phy_id, 1);
if (oob_status & CURRENT_OOB_DONE) {
asd_ha->hw_prof.enabled_phys |= (1 << phy_id);
get_lrate_mode(phy, oob_mode);
asd_turn_led(asd_ha, phy_id, 1);
ASD_DPRINTK("%s: phy%d, lrate:0x%x, proto:0x%x\n",
__FUNCTION__, phy_id,phy->sas_phy.linkrate,
phy->sas_phy.iproto);
} else if (oob_status & CURRENT_SPINUP_HOLD) {
asd_ha->hw_prof.enabled_phys |= (1 << phy_id);
asd_turn_led(asd_ha, phy_id, 1);
ASD_DPRINTK("%s: phy%d, spinup hold\n", __FUNCTION__,
phy_id);
} else if (oob_status & CURRENT_ERR_MASK) {
asd_turn_led(asd_ha, phy_id, 0);
ASD_DPRINTK("%s: phy%d: error: oob status:0x%02x\n",
__FUNCTION__, phy_id, oob_status);
} else if (oob_status & (CURRENT_HOT_PLUG_CNCT
| CURRENT_DEVICE_PRESENT)) {
asd_ha->hw_prof.enabled_phys |= (1 << phy_id);
asd_turn_led(asd_ha, phy_id, 1);
ASD_DPRINTK("%s: phy%d: hot plug or device present\n",
__FUNCTION__, phy_id);
} else {
asd_ha->hw_prof.enabled_phys |= (1 << phy_id);
asd_turn_led(asd_ha, phy_id, 0);
ASD_DPRINTK("%s: phy%d: no device present: "
"oob_status:0x%x\n",
__FUNCTION__, phy_id, oob_status);
}
break;
case RELEASE_SPINUP_HOLD:
case PHY_NO_OP:
case EXECUTE_HARD_RESET:
ASD_DPRINTK("%s: phy%d: sub_func:0x%x\n", __FUNCTION__,
phy_id, control_phy->sub_func);
/* XXX finish */
break;
default:
ASD_DPRINTK("%s: phy%d: sub_func:0x%x?\n", __FUNCTION__,
phy_id, control_phy->sub_func);
break;
}
out:
asd_ascb_free(ascb);
}
static inline void set_speed_mask(u8 *speed_mask, struct asd_phy_desc *pd)
{
/* disable all speeds, then enable defaults */
*speed_mask = SAS_SPEED_60_DIS | SAS_SPEED_30_DIS | SAS_SPEED_15_DIS
| SATA_SPEED_30_DIS | SATA_SPEED_15_DIS;
switch (pd->max_sas_lrate) {
case PHY_LINKRATE_6:
*speed_mask &= ~SAS_SPEED_60_DIS;
default:
case PHY_LINKRATE_3:
*speed_mask &= ~SAS_SPEED_30_DIS;
case PHY_LINKRATE_1_5:
*speed_mask &= ~SAS_SPEED_15_DIS;
}
switch (pd->min_sas_lrate) {
case PHY_LINKRATE_6:
*speed_mask |= SAS_SPEED_30_DIS;
case PHY_LINKRATE_3:
*speed_mask |= SAS_SPEED_15_DIS;
default:
case PHY_LINKRATE_1_5:
/* nothing to do */
;
}
switch (pd->max_sata_lrate) {
case PHY_LINKRATE_3:
*speed_mask &= ~SATA_SPEED_30_DIS;
default:
case PHY_LINKRATE_1_5:
*speed_mask &= ~SATA_SPEED_15_DIS;
}
switch (pd->min_sata_lrate) {
case PHY_LINKRATE_3:
*speed_mask |= SATA_SPEED_15_DIS;
default:
case PHY_LINKRATE_1_5:
/* nothing to do */
;
}
}
/**
* asd_build_control_phy -- build a CONTROL PHY SCB
* @ascb: pointer to an ascb
* @phy_id: phy id to control, integer
* @subfunc: subfunction, what to actually to do the phy
*
* This function builds a CONTROL PHY scb. No allocation of any kind
* is performed. @ascb is allocated with the list function.
* The caller can override the ascb->tasklet_complete to point
* to its own callback function. It must call asd_ascb_free()
* at its tasklet complete function.
* See the default implementation.
*/
void asd_build_control_phy(struct asd_ascb *ascb, int phy_id, u8 subfunc)
{
struct asd_phy *phy = &ascb->ha->phys[phy_id];
struct scb *scb = ascb->scb;
struct control_phy *control_phy = &scb->control_phy;
scb->header.opcode = CONTROL_PHY;
control_phy->phy_id = (u8) phy_id;
control_phy->sub_func = subfunc;
switch (subfunc) {
case EXECUTE_HARD_RESET: /* 0x81 */
case ENABLE_PHY: /* 0x01 */
/* decide hot plug delay */
control_phy->hot_plug_delay = HOTPLUG_DELAY_TIMEOUT;
/* decide speed mask */
set_speed_mask(&control_phy->speed_mask, phy->phy_desc);
/* initiator port settings are in the hi nibble */
if (phy->sas_phy.role == PHY_ROLE_INITIATOR)
control_phy->port_type = SAS_PROTO_ALL << 4;
else if (phy->sas_phy.role == PHY_ROLE_TARGET)
control_phy->port_type = SAS_PROTO_ALL;
else
control_phy->port_type =
(SAS_PROTO_ALL << 4) | SAS_PROTO_ALL;
/* link reset retries, this should be nominal */
control_phy->link_reset_retries = 10;
case RELEASE_SPINUP_HOLD: /* 0x02 */
/* decide the func_mask */
control_phy->func_mask = FUNCTION_MASK_DEFAULT;
if (phy->phy_desc->flags & ASD_SATA_SPINUP_HOLD)
control_phy->func_mask &= ~SPINUP_HOLD_DIS;
else
control_phy->func_mask |= SPINUP_HOLD_DIS;
}
control_phy->conn_handle = cpu_to_le16(0xFFFF);
ascb->tasklet_complete = control_phy_tasklet_complete;
}
/* ---------- INITIATE LINK ADM TASK ---------- */
static void link_adm_tasklet_complete(struct asd_ascb *ascb,
struct done_list_struct *dl)
{
u8 opcode = dl->opcode;
struct initiate_link_adm *link_adm = &ascb->scb->link_adm;
u8 phy_id = link_adm->phy_id;
if (opcode != TC_NO_ERROR) {
asd_printk("phy%d: link adm task 0x%x completed with error "
"0x%x\n", phy_id, link_adm->sub_func, opcode);
}
ASD_DPRINTK("phy%d: link adm task 0x%x: 0x%x\n",
phy_id, link_adm->sub_func, opcode);
asd_ascb_free(ascb);
}
void asd_build_initiate_link_adm_task(struct asd_ascb *ascb, int phy_id,
u8 subfunc)
{
struct scb *scb = ascb->scb;
struct initiate_link_adm *link_adm = &scb->link_adm;
scb->header.opcode = INITIATE_LINK_ADM_TASK;
link_adm->phy_id = phy_id;
link_adm->sub_func = subfunc;
link_adm->conn_handle = cpu_to_le16(0xFFFF);
ascb->tasklet_complete = link_adm_tasklet_complete;
}
/* ---------- SCB timer ---------- */
/**
* asd_ascb_timedout -- called when a pending SCB's timer has expired
* @data: unsigned long, a pointer to the ascb in question
*
* This is the default timeout function which does the most necessary.
* Upper layers can implement their own timeout function, say to free
* resources they have with this SCB, and then call this one at the
* end of their timeout function. To do this, one should initialize
* the ascb->timer.{function, data, expires} prior to calling the post
* funcion. The timer is started by the post function.
*/
void asd_ascb_timedout(unsigned long data)
{
struct asd_ascb *ascb = (void *) data;
struct asd_seq_data *seq = &ascb->ha->seq;
unsigned long flags;
ASD_DPRINTK("scb:0x%x timed out\n", ascb->scb->header.opcode);
spin_lock_irqsave(&seq->pend_q_lock, flags);
seq->pending--;
list_del_init(&ascb->list);
spin_unlock_irqrestore(&seq->pend_q_lock, flags);
asd_ascb_free(ascb);
}
/* ---------- CONTROL PHY ---------- */
/* Given the spec value, return a driver value. */
static const int phy_func_table[] = {
[PHY_FUNC_NOP] = PHY_NO_OP,
[PHY_FUNC_LINK_RESET] = ENABLE_PHY,
[PHY_FUNC_HARD_RESET] = EXECUTE_HARD_RESET,
[PHY_FUNC_DISABLE] = DISABLE_PHY,
[PHY_FUNC_RELEASE_SPINUP_HOLD] = RELEASE_SPINUP_HOLD,
};
int asd_control_phy(struct asd_sas_phy *phy, enum phy_func func)
{
struct asd_ha_struct *asd_ha = phy->ha->lldd_ha;
struct asd_ascb *ascb;
int res = 1;
if (func == PHY_FUNC_CLEAR_ERROR_LOG)
return -ENOSYS;
ascb = asd_ascb_alloc_list(asd_ha, &res, GFP_KERNEL);
if (!ascb)
return -ENOMEM;
asd_build_control_phy(ascb, phy->id, phy_func_table[func]);
res = asd_post_ascb_list(asd_ha, ascb , 1);
if (res)
asd_ascb_free(ascb);
return res;
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,70 @@
/*
* Aic94xx SAS/SATA driver sequencer interface 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 file is part of the aic94xx driver.
*
* The aic94xx driver 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.
*
* The aic94xx driver 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 the aic94xx driver; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
#ifndef _AIC94XX_SEQ_H_
#define _AIC94XX_SEQ_H_
#define CSEQ_NUM_VECS 3
#define LSEQ_NUM_VECS 11
#define SAS_RAZOR_SEQUENCER_FW_FILE "aic94xx-seq.fw"
/* Note: All quantites in the sequencer file are little endian */
struct sequencer_file_header {
/* Checksum of the entire contents of the sequencer excluding
* these four bytes */
u32 csum;
/* numeric major version */
u32 major;
/* numeric minor version */
u32 minor;
/* version string printed by driver */
char version[16];
u32 cseq_table_offset;
u32 cseq_table_size;
u32 lseq_table_offset;
u32 lseq_table_size;
u32 cseq_code_offset;
u32 cseq_code_size;
u32 lseq_code_offset;
u32 lseq_code_size;
u16 mode2_task;
u16 cseq_idle_loop;
u16 lseq_idle_loop;
} __attribute__((packed));
#ifdef __KERNEL__
int asd_pause_cseq(struct asd_ha_struct *asd_ha);
int asd_unpause_cseq(struct asd_ha_struct *asd_ha);
int asd_pause_lseq(struct asd_ha_struct *asd_ha, u8 lseq_mask);
int asd_unpause_lseq(struct asd_ha_struct *asd_ha, u8 lseq_mask);
int asd_init_seqs(struct asd_ha_struct *asd_ha);
int asd_start_seqs(struct asd_ha_struct *asd_ha);
void asd_update_port_links(struct asd_sas_phy *phy);
#endif
#endif

View File

@@ -0,0 +1,642 @@
/*
* Aic94xx SAS/SATA Tasks
*
* Copyright (C) 2005 Adaptec, Inc. All rights reserved.
* Copyright (C) 2005 Luben Tuikov <luben_tuikov@adaptec.com>
*
* This file is licensed under GPLv2.
*
* This file is part of the aic94xx driver.
*
* The aic94xx driver 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.
*
* The aic94xx driver 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 the aic94xx driver; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
#include <linux/spinlock.h>
#include "aic94xx.h"
#include "aic94xx_sas.h"
#include "aic94xx_hwi.h"
static void asd_unbuild_ata_ascb(struct asd_ascb *a);
static void asd_unbuild_smp_ascb(struct asd_ascb *a);
static void asd_unbuild_ssp_ascb(struct asd_ascb *a);
static inline void asd_can_dequeue(struct asd_ha_struct *asd_ha, int num)
{
unsigned long flags;
spin_lock_irqsave(&asd_ha->seq.pend_q_lock, flags);
asd_ha->seq.can_queue += num;
spin_unlock_irqrestore(&asd_ha->seq.pend_q_lock, flags);
}
/* PCI_DMA_... to our direction translation.
*/
static const u8 data_dir_flags[] = {
[PCI_DMA_BIDIRECTIONAL] = DATA_DIR_BYRECIPIENT, /* UNSPECIFIED */
[PCI_DMA_TODEVICE] = DATA_DIR_OUT, /* OUTBOUND */
[PCI_DMA_FROMDEVICE] = DATA_DIR_IN, /* INBOUND */
[PCI_DMA_NONE] = DATA_DIR_NONE, /* NO TRANSFER */
};
static inline int asd_map_scatterlist(struct sas_task *task,
struct sg_el *sg_arr,
unsigned long gfp_flags)
{
struct asd_ascb *ascb = task->lldd_task;
struct asd_ha_struct *asd_ha = ascb->ha;
struct scatterlist *sc;
int num_sg, res;
if (task->data_dir == PCI_DMA_NONE)
return 0;
if (task->num_scatter == 0) {
void *p = task->scatter;
dma_addr_t dma = pci_map_single(asd_ha->pcidev, p,
task->total_xfer_len,
task->data_dir);
sg_arr[0].bus_addr = cpu_to_le64((u64)dma);
sg_arr[0].size = cpu_to_le32(task->total_xfer_len);
sg_arr[0].flags |= ASD_SG_EL_LIST_EOL;
return 0;
}
num_sg = pci_map_sg(asd_ha->pcidev, task->scatter, task->num_scatter,
task->data_dir);
if (num_sg == 0)
return -ENOMEM;
if (num_sg > 3) {
int i;
ascb->sg_arr = asd_alloc_coherent(asd_ha,
num_sg*sizeof(struct sg_el),
gfp_flags);
if (!ascb->sg_arr) {
res = -ENOMEM;
goto err_unmap;
}
for (sc = task->scatter, i = 0; i < num_sg; i++, sc++) {
struct sg_el *sg =
&((struct sg_el *)ascb->sg_arr->vaddr)[i];
sg->bus_addr = cpu_to_le64((u64)sg_dma_address(sc));
sg->size = cpu_to_le32((u32)sg_dma_len(sc));
if (i == num_sg-1)
sg->flags |= ASD_SG_EL_LIST_EOL;
}
for (sc = task->scatter, i = 0; i < 2; i++, sc++) {
sg_arr[i].bus_addr =
cpu_to_le64((u64)sg_dma_address(sc));
sg_arr[i].size = cpu_to_le32((u32)sg_dma_len(sc));
}
sg_arr[1].next_sg_offs = 2 * sizeof(*sg_arr);
sg_arr[1].flags |= ASD_SG_EL_LIST_EOS;
memset(&sg_arr[2], 0, sizeof(*sg_arr));
sg_arr[2].bus_addr=cpu_to_le64((u64)ascb->sg_arr->dma_handle);
} else {
int i;
for (sc = task->scatter, i = 0; i < num_sg; i++, sc++) {
sg_arr[i].bus_addr =
cpu_to_le64((u64)sg_dma_address(sc));
sg_arr[i].size = cpu_to_le32((u32)sg_dma_len(sc));
}
sg_arr[i-1].flags |= ASD_SG_EL_LIST_EOL;
}
return 0;
err_unmap:
pci_unmap_sg(asd_ha->pcidev, task->scatter, task->num_scatter,
task->data_dir);
return res;
}
static inline void asd_unmap_scatterlist(struct asd_ascb *ascb)
{
struct asd_ha_struct *asd_ha = ascb->ha;
struct sas_task *task = ascb->uldd_task;
if (task->data_dir == PCI_DMA_NONE)
return;
if (task->num_scatter == 0) {
dma_addr_t dma = (dma_addr_t)
le64_to_cpu(ascb->scb->ssp_task.sg_element[0].bus_addr);
pci_unmap_single(ascb->ha->pcidev, dma, task->total_xfer_len,
task->data_dir);
return;
}
asd_free_coherent(asd_ha, ascb->sg_arr);
pci_unmap_sg(asd_ha->pcidev, task->scatter, task->num_scatter,
task->data_dir);
}
/* ---------- Task complete tasklet ---------- */
static void asd_get_response_tasklet(struct asd_ascb *ascb,
struct done_list_struct *dl)
{
struct asd_ha_struct *asd_ha = ascb->ha;
struct sas_task *task = ascb->uldd_task;
struct task_status_struct *ts = &task->task_status;
unsigned long flags;
struct tc_resp_sb_struct {
__le16 index_escb;
u8 len_lsb;
u8 flags;
} __attribute__ ((packed)) *resp_sb = (void *) dl->status_block;
/* int size = ((resp_sb->flags & 7) << 8) | resp_sb->len_lsb; */
int edb_id = ((resp_sb->flags & 0x70) >> 4)-1;
struct asd_ascb *escb;
struct asd_dma_tok *edb;
void *r;
spin_lock_irqsave(&asd_ha->seq.tc_index_lock, flags);
escb = asd_tc_index_find(&asd_ha->seq,
(int)le16_to_cpu(resp_sb->index_escb));
spin_unlock_irqrestore(&asd_ha->seq.tc_index_lock, flags);
if (!escb) {
ASD_DPRINTK("Uh-oh! No escb for this dl?!\n");
return;
}
ts->buf_valid_size = 0;
edb = asd_ha->seq.edb_arr[edb_id + escb->edb_index];
r = edb->vaddr;
if (task->task_proto == SAS_PROTO_SSP) {
struct ssp_response_iu *iu =
r + 16 + sizeof(struct ssp_frame_hdr);
ts->residual = le32_to_cpu(*(__le32 *)r);
ts->resp = SAS_TASK_COMPLETE;
if (iu->datapres == 0)
ts->stat = iu->status;
else if (iu->datapres == 1)
ts->stat = iu->resp_data[3];
else if (iu->datapres == 2) {
ts->stat = SAM_CHECK_COND;
ts->buf_valid_size = min((u32) SAS_STATUS_BUF_SIZE,
be32_to_cpu(iu->sense_data_len));
memcpy(ts->buf, iu->sense_data, ts->buf_valid_size);
if (iu->status != SAM_CHECK_COND) {
ASD_DPRINTK("device %llx sent sense data, but "
"stat(0x%x) is not CHECK_CONDITION"
"\n",
SAS_ADDR(task->dev->sas_addr),
ts->stat);
}
}
} else {
struct ata_task_resp *resp = (void *) &ts->buf[0];
ts->residual = le32_to_cpu(*(__le32 *)r);
if (SAS_STATUS_BUF_SIZE >= sizeof(*resp)) {
resp->frame_len = le16_to_cpu(*(__le16 *)(r+6));
memcpy(&resp->ending_fis[0], r+16, 24);
ts->buf_valid_size = sizeof(*resp);
}
}
asd_invalidate_edb(escb, edb_id);
}
static void asd_task_tasklet_complete(struct asd_ascb *ascb,
struct done_list_struct *dl)
{
struct sas_task *task = ascb->uldd_task;
struct task_status_struct *ts = &task->task_status;
unsigned long flags;
u8 opcode = dl->opcode;
asd_can_dequeue(ascb->ha, 1);
Again:
switch (opcode) {
case TC_NO_ERROR:
ts->resp = SAS_TASK_COMPLETE;
ts->stat = SAM_GOOD;
break;
case TC_UNDERRUN:
ts->resp = SAS_TASK_COMPLETE;
ts->stat = SAS_DATA_UNDERRUN;
ts->residual = le32_to_cpu(*(__le32 *)dl->status_block);
break;
case TC_OVERRUN:
ts->resp = SAS_TASK_COMPLETE;
ts->stat = SAS_DATA_OVERRUN;
ts->residual = 0;
break;
case TC_SSP_RESP:
case TC_ATA_RESP:
ts->resp = SAS_TASK_COMPLETE;
ts->stat = SAS_PROTO_RESPONSE;
asd_get_response_tasklet(ascb, dl);
break;
case TF_OPEN_REJECT:
ts->resp = SAS_TASK_UNDELIVERED;
ts->stat = SAS_OPEN_REJECT;
if (dl->status_block[1] & 2)
ts->open_rej_reason = 1 + dl->status_block[2];
else if (dl->status_block[1] & 1)
ts->open_rej_reason = (dl->status_block[2] >> 4)+10;
else
ts->open_rej_reason = SAS_OREJ_UNKNOWN;
break;
case TF_OPEN_TO:
ts->resp = SAS_TASK_UNDELIVERED;
ts->stat = SAS_OPEN_TO;
break;
case TF_PHY_DOWN:
case TU_PHY_DOWN:
ts->resp = SAS_TASK_UNDELIVERED;
ts->stat = SAS_PHY_DOWN;
break;
case TI_PHY_DOWN:
ts->resp = SAS_TASK_COMPLETE;
ts->stat = SAS_PHY_DOWN;
break;
case TI_BREAK:
case TI_PROTO_ERR:
case TI_NAK:
case TI_ACK_NAK_TO:
case TF_SMP_XMIT_RCV_ERR:
case TC_ATA_R_ERR_RECV:
ts->resp = SAS_TASK_COMPLETE;
ts->stat = SAS_INTERRUPTED;
break;
case TF_BREAK:
case TU_BREAK:
case TU_ACK_NAK_TO:
case TF_SMPRSP_TO:
ts->resp = SAS_TASK_UNDELIVERED;
ts->stat = SAS_DEV_NO_RESPONSE;
break;
case TF_NAK_RECV:
ts->resp = SAS_TASK_COMPLETE;
ts->stat = SAS_NAK_R_ERR;
break;
case TA_I_T_NEXUS_LOSS:
opcode = dl->status_block[0];
goto Again;
break;
case TF_INV_CONN_HANDLE:
ts->resp = SAS_TASK_UNDELIVERED;
ts->stat = SAS_DEVICE_UNKNOWN;
break;
case TF_REQUESTED_N_PENDING:
ts->resp = SAS_TASK_UNDELIVERED;
ts->stat = SAS_PENDING;
break;
case TC_TASK_CLEARED:
case TA_ON_REQ:
ts->resp = SAS_TASK_COMPLETE;
ts->stat = SAS_ABORTED_TASK;
break;
case TF_NO_SMP_CONN:
case TF_TMF_NO_CTX:
case TF_TMF_NO_TAG:
case TF_TMF_TAG_FREE:
case TF_TMF_TASK_DONE:
case TF_TMF_NO_CONN_HANDLE:
case TF_IRTT_TO:
case TF_IU_SHORT:
case TF_DATA_OFFS_ERR:
ts->resp = SAS_TASK_UNDELIVERED;
ts->stat = SAS_DEV_NO_RESPONSE;
break;
case TC_LINK_ADM_RESP:
case TC_CONTROL_PHY:
case TC_RESUME:
case TC_PARTIAL_SG_LIST:
default:
ASD_DPRINTK("%s: dl opcode: 0x%x?\n", __FUNCTION__, opcode);
break;
}
switch (task->task_proto) {
case SATA_PROTO:
case SAS_PROTO_STP:
asd_unbuild_ata_ascb(ascb);
break;
case SAS_PROTO_SMP:
asd_unbuild_smp_ascb(ascb);
break;
case SAS_PROTO_SSP:
asd_unbuild_ssp_ascb(ascb);
default:
break;
}
spin_lock_irqsave(&task->task_state_lock, flags);
task->task_state_flags &= ~SAS_TASK_STATE_PENDING;
task->task_state_flags |= SAS_TASK_STATE_DONE;
if (unlikely((task->task_state_flags & SAS_TASK_STATE_ABORTED))) {
spin_unlock_irqrestore(&task->task_state_lock, flags);
ASD_DPRINTK("task 0x%p done with opcode 0x%x resp 0x%x "
"stat 0x%x but aborted by upper layer!\n",
task, opcode, ts->resp, ts->stat);
complete(&ascb->completion);
} else {
spin_unlock_irqrestore(&task->task_state_lock, flags);
task->lldd_task = NULL;
asd_ascb_free(ascb);
mb();
task->task_done(task);
}
}
/* ---------- ATA ---------- */
static int asd_build_ata_ascb(struct asd_ascb *ascb, struct sas_task *task,
unsigned long gfp_flags)
{
struct domain_device *dev = task->dev;
struct scb *scb;
u8 flags;
int res = 0;
scb = ascb->scb;
if (unlikely(task->ata_task.device_control_reg_update))
scb->header.opcode = CONTROL_ATA_DEV;
else if (dev->sata_dev.command_set == ATA_COMMAND_SET)
scb->header.opcode = INITIATE_ATA_TASK;
else
scb->header.opcode = INITIATE_ATAPI_TASK;
scb->ata_task.proto_conn_rate = (1 << 5); /* STP */
if (dev->port->oob_mode == SAS_OOB_MODE)
scb->ata_task.proto_conn_rate |= dev->linkrate;
scb->ata_task.total_xfer_len = cpu_to_le32(task->total_xfer_len);
scb->ata_task.fis = task->ata_task.fis;
scb->ata_task.fis.fis_type = 0x27;
if (likely(!task->ata_task.device_control_reg_update))
scb->ata_task.fis.flags |= 0x80; /* C=1: update ATA cmd reg */
scb->ata_task.fis.flags &= 0xF0; /* PM_PORT field shall be 0 */
if (dev->sata_dev.command_set == ATAPI_COMMAND_SET)
memcpy(scb->ata_task.atapi_packet, task->ata_task.atapi_packet,
16);
scb->ata_task.sister_scb = cpu_to_le16(0xFFFF);
scb->ata_task.conn_handle = cpu_to_le16(
(u16)(unsigned long)dev->lldd_dev);
if (likely(!task->ata_task.device_control_reg_update)) {
flags = 0;
if (task->ata_task.dma_xfer)
flags |= DATA_XFER_MODE_DMA;
if (task->ata_task.use_ncq &&
dev->sata_dev.command_set != ATAPI_COMMAND_SET)
flags |= ATA_Q_TYPE_NCQ;
flags |= data_dir_flags[task->data_dir];
scb->ata_task.ata_flags = flags;
scb->ata_task.retry_count = task->ata_task.retry_count;
flags = 0;
if (task->ata_task.set_affil_pol)
flags |= SET_AFFIL_POLICY;
if (task->ata_task.stp_affil_pol)
flags |= STP_AFFIL_POLICY;
scb->ata_task.flags = flags;
}
ascb->tasklet_complete = asd_task_tasklet_complete;
if (likely(!task->ata_task.device_control_reg_update))
res = asd_map_scatterlist(task, scb->ata_task.sg_element,
gfp_flags);
return res;
}
static void asd_unbuild_ata_ascb(struct asd_ascb *a)
{
asd_unmap_scatterlist(a);
}
/* ---------- SMP ---------- */
static int asd_build_smp_ascb(struct asd_ascb *ascb, struct sas_task *task,
unsigned long gfp_flags)
{
struct asd_ha_struct *asd_ha = ascb->ha;
struct domain_device *dev = task->dev;
struct scb *scb;
pci_map_sg(asd_ha->pcidev, &task->smp_task.smp_req, 1,
PCI_DMA_FROMDEVICE);
pci_map_sg(asd_ha->pcidev, &task->smp_task.smp_resp, 1,
PCI_DMA_FROMDEVICE);
scb = ascb->scb;
scb->header.opcode = INITIATE_SMP_TASK;
scb->smp_task.proto_conn_rate = dev->linkrate;
scb->smp_task.smp_req.bus_addr =
cpu_to_le64((u64)sg_dma_address(&task->smp_task.smp_req));
scb->smp_task.smp_req.size =
cpu_to_le32((u32)sg_dma_len(&task->smp_task.smp_req)-4);
scb->smp_task.smp_resp.bus_addr =
cpu_to_le64((u64)sg_dma_address(&task->smp_task.smp_resp));
scb->smp_task.smp_resp.size =
cpu_to_le32((u32)sg_dma_len(&task->smp_task.smp_resp)-4);
scb->smp_task.sister_scb = cpu_to_le16(0xFFFF);
scb->smp_task.conn_handle = cpu_to_le16((u16)
(unsigned long)dev->lldd_dev);
ascb->tasklet_complete = asd_task_tasklet_complete;
return 0;
}
static void asd_unbuild_smp_ascb(struct asd_ascb *a)
{
struct sas_task *task = a->uldd_task;
BUG_ON(!task);
pci_unmap_sg(a->ha->pcidev, &task->smp_task.smp_req, 1,
PCI_DMA_FROMDEVICE);
pci_unmap_sg(a->ha->pcidev, &task->smp_task.smp_resp, 1,
PCI_DMA_FROMDEVICE);
}
/* ---------- SSP ---------- */
static int asd_build_ssp_ascb(struct asd_ascb *ascb, struct sas_task *task,
unsigned long gfp_flags)
{
struct domain_device *dev = task->dev;
struct scb *scb;
int res = 0;
scb = ascb->scb;
scb->header.opcode = INITIATE_SSP_TASK;
scb->ssp_task.proto_conn_rate = (1 << 4); /* SSP */
scb->ssp_task.proto_conn_rate |= dev->linkrate;
scb->ssp_task.total_xfer_len = cpu_to_le32(task->total_xfer_len);
scb->ssp_task.ssp_frame.frame_type = SSP_DATA;
memcpy(scb->ssp_task.ssp_frame.hashed_dest_addr, dev->hashed_sas_addr,
HASHED_SAS_ADDR_SIZE);
memcpy(scb->ssp_task.ssp_frame.hashed_src_addr,
dev->port->ha->hashed_sas_addr, HASHED_SAS_ADDR_SIZE);
scb->ssp_task.ssp_frame.tptt = cpu_to_be16(0xFFFF);
memcpy(scb->ssp_task.ssp_cmd.lun, task->ssp_task.LUN, 8);
if (task->ssp_task.enable_first_burst)
scb->ssp_task.ssp_cmd.efb_prio_attr |= EFB_MASK;
scb->ssp_task.ssp_cmd.efb_prio_attr |= (task->ssp_task.task_prio << 3);
scb->ssp_task.ssp_cmd.efb_prio_attr |= (task->ssp_task.task_attr & 7);
memcpy(scb->ssp_task.ssp_cmd.cdb, task->ssp_task.cdb, 16);
scb->ssp_task.sister_scb = cpu_to_le16(0xFFFF);
scb->ssp_task.conn_handle = cpu_to_le16(
(u16)(unsigned long)dev->lldd_dev);
scb->ssp_task.data_dir = data_dir_flags[task->data_dir];
scb->ssp_task.retry_count = scb->ssp_task.retry_count;
ascb->tasklet_complete = asd_task_tasklet_complete;
res = asd_map_scatterlist(task, scb->ssp_task.sg_element, gfp_flags);
return res;
}
static void asd_unbuild_ssp_ascb(struct asd_ascb *a)
{
asd_unmap_scatterlist(a);
}
/* ---------- Execute Task ---------- */
static inline int asd_can_queue(struct asd_ha_struct *asd_ha, int num)
{
int res = 0;
unsigned long flags;
spin_lock_irqsave(&asd_ha->seq.pend_q_lock, flags);
if ((asd_ha->seq.can_queue - num) < 0)
res = -SAS_QUEUE_FULL;
else
asd_ha->seq.can_queue -= num;
spin_unlock_irqrestore(&asd_ha->seq.pend_q_lock, flags);
return res;
}
int asd_execute_task(struct sas_task *task, const int num,
unsigned long gfp_flags)
{
int res = 0;
LIST_HEAD(alist);
struct sas_task *t = task;
struct asd_ascb *ascb = NULL, *a;
struct asd_ha_struct *asd_ha = task->dev->port->ha->lldd_ha;
res = asd_can_queue(asd_ha, num);
if (res)
return res;
res = num;
ascb = asd_ascb_alloc_list(asd_ha, &res, gfp_flags);
if (res) {
res = -ENOMEM;
goto out_err;
}
__list_add(&alist, ascb->list.prev, &ascb->list);
list_for_each_entry(a, &alist, list) {
a->uldd_task = t;
t->lldd_task = a;
t = list_entry(t->list.next, struct sas_task, list);
}
list_for_each_entry(a, &alist, list) {
t = a->uldd_task;
a->uldd_timer = 1;
if (t->task_proto & SAS_PROTO_STP)
t->task_proto = SAS_PROTO_STP;
switch (t->task_proto) {
case SATA_PROTO:
case SAS_PROTO_STP:
res = asd_build_ata_ascb(a, t, gfp_flags);
break;
case SAS_PROTO_SMP:
res = asd_build_smp_ascb(a, t, gfp_flags);
break;
case SAS_PROTO_SSP:
res = asd_build_ssp_ascb(a, t, gfp_flags);
break;
default:
asd_printk("unknown sas_task proto: 0x%x\n",
t->task_proto);
res = -ENOMEM;
break;
}
if (res)
goto out_err_unmap;
}
list_del_init(&alist);
res = asd_post_ascb_list(asd_ha, ascb, num);
if (unlikely(res)) {
a = NULL;
__list_add(&alist, ascb->list.prev, &ascb->list);
goto out_err_unmap;
}
return 0;
out_err_unmap:
{
struct asd_ascb *b = a;
list_for_each_entry(a, &alist, list) {
if (a == b)
break;
t = a->uldd_task;
switch (t->task_proto) {
case SATA_PROTO:
case SAS_PROTO_STP:
asd_unbuild_ata_ascb(a);
break;
case SAS_PROTO_SMP:
asd_unbuild_smp_ascb(a);
break;
case SAS_PROTO_SSP:
asd_unbuild_ssp_ascb(a);
default:
break;
}
t->lldd_task = NULL;
}
}
list_del_init(&alist);
out_err:
if (ascb)
asd_ascb_free_list(ascb);
asd_can_dequeue(asd_ha, num);
return res;
}

View File

@@ -0,0 +1,636 @@
/*
* Aic94xx Task Management Functions
*
* Copyright (C) 2005 Adaptec, Inc. All rights reserved.
* Copyright (C) 2005 Luben Tuikov <luben_tuikov@adaptec.com>
*
* This file is licensed under GPLv2.
*
* This file is part of the aic94xx driver.
*
* The aic94xx driver 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.
*
* The aic94xx driver 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 the aic94xx driver; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
#include <linux/spinlock.h>
#include "aic94xx.h"
#include "aic94xx_sas.h"
#include "aic94xx_hwi.h"
/* ---------- Internal enqueue ---------- */
static int asd_enqueue_internal(struct asd_ascb *ascb,
void (*tasklet_complete)(struct asd_ascb *,
struct done_list_struct *),
void (*timed_out)(unsigned long))
{
int res;
ascb->tasklet_complete = tasklet_complete;
ascb->uldd_timer = 1;
ascb->timer.data = (unsigned long) ascb;
ascb->timer.function = timed_out;
ascb->timer.expires = jiffies + AIC94XX_SCB_TIMEOUT;
add_timer(&ascb->timer);
res = asd_post_ascb_list(ascb->ha, ascb, 1);
if (unlikely(res))
del_timer(&ascb->timer);
return res;
}
static inline void asd_timedout_common(unsigned long data)
{
struct asd_ascb *ascb = (void *) data;
struct asd_seq_data *seq = &ascb->ha->seq;
unsigned long flags;
spin_lock_irqsave(&seq->pend_q_lock, flags);
seq->pending--;
list_del_init(&ascb->list);
spin_unlock_irqrestore(&seq->pend_q_lock, flags);
}
/* ---------- CLEAR NEXUS ---------- */
static void asd_clear_nexus_tasklet_complete(struct asd_ascb *ascb,
struct done_list_struct *dl)
{
ASD_DPRINTK("%s: here\n", __FUNCTION__);
if (!del_timer(&ascb->timer)) {
ASD_DPRINTK("%s: couldn't delete timer\n", __FUNCTION__);
return;
}
ASD_DPRINTK("%s: opcode: 0x%x\n", __FUNCTION__, dl->opcode);
ascb->uldd_task = (void *) (unsigned long) dl->opcode;
complete(&ascb->completion);
}
static void asd_clear_nexus_timedout(unsigned long data)
{
struct asd_ascb *ascb = (void *) data;
ASD_DPRINTK("%s: here\n", __FUNCTION__);
asd_timedout_common(data);
ascb->uldd_task = (void *) TMF_RESP_FUNC_FAILED;
complete(&ascb->completion);
}
#define CLEAR_NEXUS_PRE \
ASD_DPRINTK("%s: PRE\n", __FUNCTION__); \
res = 1; \
ascb = asd_ascb_alloc_list(asd_ha, &res, GFP_KERNEL); \
if (!ascb) \
return -ENOMEM; \
\
scb = ascb->scb; \
scb->header.opcode = CLEAR_NEXUS
#define CLEAR_NEXUS_POST \
ASD_DPRINTK("%s: POST\n", __FUNCTION__); \
res = asd_enqueue_internal(ascb, asd_clear_nexus_tasklet_complete, \
asd_clear_nexus_timedout); \
if (res) \
goto out_err; \
ASD_DPRINTK("%s: clear nexus posted, waiting...\n", __FUNCTION__); \
wait_for_completion(&ascb->completion); \
res = (int) (unsigned long) ascb->uldd_task; \
if (res == TC_NO_ERROR) \
res = TMF_RESP_FUNC_COMPLETE; \
out_err: \
asd_ascb_free(ascb); \
return res
int asd_clear_nexus_ha(struct sas_ha_struct *sas_ha)
{
struct asd_ha_struct *asd_ha = sas_ha->lldd_ha;
struct asd_ascb *ascb;
struct scb *scb;
int res;
CLEAR_NEXUS_PRE;
scb->clear_nexus.nexus = NEXUS_ADAPTER;
CLEAR_NEXUS_POST;
}
int asd_clear_nexus_port(struct asd_sas_port *port)
{
struct asd_ha_struct *asd_ha = port->ha->lldd_ha;
struct asd_ascb *ascb;
struct scb *scb;
int res;
CLEAR_NEXUS_PRE;
scb->clear_nexus.nexus = NEXUS_PORT;
scb->clear_nexus.conn_mask = port->phy_mask;
CLEAR_NEXUS_POST;
}
#if 0
static int asd_clear_nexus_I_T(struct domain_device *dev)
{
struct asd_ha_struct *asd_ha = dev->port->ha->lldd_ha;
struct asd_ascb *ascb;
struct scb *scb;
int res;
CLEAR_NEXUS_PRE;
scb->clear_nexus.nexus = NEXUS_I_T;
scb->clear_nexus.flags = SEND_Q | EXEC_Q | NOTINQ;
if (dev->tproto)
scb->clear_nexus.flags |= SUSPEND_TX;
scb->clear_nexus.conn_handle = cpu_to_le16((u16)(unsigned long)
dev->lldd_dev);
CLEAR_NEXUS_POST;
}
#endif
static int asd_clear_nexus_I_T_L(struct domain_device *dev, u8 *lun)
{
struct asd_ha_struct *asd_ha = dev->port->ha->lldd_ha;
struct asd_ascb *ascb;
struct scb *scb;
int res;
CLEAR_NEXUS_PRE;
scb->clear_nexus.nexus = NEXUS_I_T_L;
scb->clear_nexus.flags = SEND_Q | EXEC_Q | NOTINQ;
if (dev->tproto)
scb->clear_nexus.flags |= SUSPEND_TX;
memcpy(scb->clear_nexus.ssp_task.lun, lun, 8);
scb->clear_nexus.conn_handle = cpu_to_le16((u16)(unsigned long)
dev->lldd_dev);
CLEAR_NEXUS_POST;
}
static int asd_clear_nexus_tag(struct sas_task *task)
{
struct asd_ha_struct *asd_ha = task->dev->port->ha->lldd_ha;
struct asd_ascb *tascb = task->lldd_task;
struct asd_ascb *ascb;
struct scb *scb;
int res;
CLEAR_NEXUS_PRE;
scb->clear_nexus.nexus = NEXUS_TAG;
memcpy(scb->clear_nexus.ssp_task.lun, task->ssp_task.LUN, 8);
scb->clear_nexus.ssp_task.tag = tascb->tag;
if (task->dev->tproto)
scb->clear_nexus.conn_handle = cpu_to_le16((u16)(unsigned long)
task->dev->lldd_dev);
CLEAR_NEXUS_POST;
}
static int asd_clear_nexus_index(struct sas_task *task)
{
struct asd_ha_struct *asd_ha = task->dev->port->ha->lldd_ha;
struct asd_ascb *tascb = task->lldd_task;
struct asd_ascb *ascb;
struct scb *scb;
int res;
CLEAR_NEXUS_PRE;
scb->clear_nexus.nexus = NEXUS_TRANS_CX;
if (task->dev->tproto)
scb->clear_nexus.conn_handle = cpu_to_le16((u16)(unsigned long)
task->dev->lldd_dev);
scb->clear_nexus.index = cpu_to_le16(tascb->tc_index);
CLEAR_NEXUS_POST;
}
/* ---------- TMFs ---------- */
static void asd_tmf_timedout(unsigned long data)
{
struct asd_ascb *ascb = (void *) data;
ASD_DPRINTK("tmf timed out\n");
asd_timedout_common(data);
ascb->uldd_task = (void *) TMF_RESP_FUNC_FAILED;
complete(&ascb->completion);
}
static int asd_get_tmf_resp_tasklet(struct asd_ascb *ascb,
struct done_list_struct *dl)
{
struct asd_ha_struct *asd_ha = ascb->ha;
unsigned long flags;
struct tc_resp_sb_struct {
__le16 index_escb;
u8 len_lsb;
u8 flags;
} __attribute__ ((packed)) *resp_sb = (void *) dl->status_block;
int edb_id = ((resp_sb->flags & 0x70) >> 4)-1;
struct asd_ascb *escb;
struct asd_dma_tok *edb;
struct ssp_frame_hdr *fh;
struct ssp_response_iu *ru;
int res = TMF_RESP_FUNC_FAILED;
ASD_DPRINTK("tmf resp tasklet\n");
spin_lock_irqsave(&asd_ha->seq.tc_index_lock, flags);
escb = asd_tc_index_find(&asd_ha->seq,
(int)le16_to_cpu(resp_sb->index_escb));
spin_unlock_irqrestore(&asd_ha->seq.tc_index_lock, flags);
if (!escb) {
ASD_DPRINTK("Uh-oh! No escb for this dl?!\n");
return res;
}
edb = asd_ha->seq.edb_arr[edb_id + escb->edb_index];
ascb->tag = *(__be16 *)(edb->vaddr+4);
fh = edb->vaddr + 16;
ru = edb->vaddr + 16 + sizeof(*fh);
res = ru->status;
if (ru->datapres == 1) /* Response data present */
res = ru->resp_data[3];
#if 0
ascb->tag = fh->tag;
#endif
ascb->tag_valid = 1;
asd_invalidate_edb(escb, edb_id);
return res;
}
static void asd_tmf_tasklet_complete(struct asd_ascb *ascb,
struct done_list_struct *dl)
{
if (!del_timer(&ascb->timer))
return;
ASD_DPRINTK("tmf tasklet complete\n");
if (dl->opcode == TC_SSP_RESP)
ascb->uldd_task = (void *) (unsigned long)
asd_get_tmf_resp_tasklet(ascb, dl);
else
ascb->uldd_task = (void *) 0xFF00 + (unsigned long) dl->opcode;
complete(&ascb->completion);
}
static inline int asd_clear_nexus(struct sas_task *task)
{
int res = TMF_RESP_FUNC_FAILED;
struct asd_ascb *tascb = task->lldd_task;
unsigned long flags;
ASD_DPRINTK("task not done, clearing nexus\n");
if (tascb->tag_valid)
res = asd_clear_nexus_tag(task);
else
res = asd_clear_nexus_index(task);
wait_for_completion_timeout(&tascb->completion,
AIC94XX_SCB_TIMEOUT);
ASD_DPRINTK("came back from clear nexus\n");
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);
return res;
}
/**
* asd_abort_task -- ABORT TASK TMF
* @task: the task to be aborted
*
* Before calling ABORT TASK the task state flags should be ORed with
* SAS_TASK_STATE_ABORTED (unless SAS_TASK_STATE_DONE is set) under
* the task_state_lock IRQ spinlock, then ABORT TASK *must* be called.
*
* Implements the ABORT TASK TMF, I_T_L_Q nexus.
* Returns: SAS TMF responses (see sas_task.h),
* -ENOMEM,
* -SAS_QUEUE_FULL.
*
* When ABORT TASK returns, the caller of ABORT TASK checks first the
* task->task_state_flags, and then the return value of ABORT TASK.
*
* If the task has task state bit SAS_TASK_STATE_DONE set, then the
* task was completed successfully prior to it being aborted. The
* caller of ABORT TASK has responsibility to call task->task_done()
* xor free the task, depending on their framework. The return code
* is TMF_RESP_FUNC_FAILED in this case.
*
* Else the SAS_TASK_STATE_DONE bit is not set,
* If the return code is TMF_RESP_FUNC_COMPLETE, then
* the task was aborted successfully. The caller of
* ABORT TASK has responsibility to call task->task_done()
* to finish the task, xor free the task depending on their
* framework.
* else
* the ABORT TASK returned some kind of error. The task
* was _not_ cancelled. Nothing can be assumed.
* The caller of ABORT TASK may wish to retry.
*/
int asd_abort_task(struct sas_task *task)
{
struct asd_ascb *tascb = task->lldd_task;
struct asd_ha_struct *asd_ha = tascb->ha;
int res = 1;
unsigned long flags;
struct asd_ascb *ascb = NULL;
struct scb *scb;
spin_lock_irqsave(&task->task_state_lock, flags);
if (task->task_state_flags & SAS_TASK_STATE_DONE) {
spin_unlock_irqrestore(&task->task_state_lock, flags);
res = TMF_RESP_FUNC_COMPLETE;
ASD_DPRINTK("%s: task 0x%p done\n", __FUNCTION__, task);
goto out_done;
}
spin_unlock_irqrestore(&task->task_state_lock, flags);
ascb = asd_ascb_alloc_list(asd_ha, &res, GFP_KERNEL);
if (!ascb)
return -ENOMEM;
scb = ascb->scb;
scb->header.opcode = ABORT_TASK;
switch (task->task_proto) {
case SATA_PROTO:
case SAS_PROTO_STP:
scb->abort_task.proto_conn_rate = (1 << 5); /* STP */
break;
case SAS_PROTO_SSP:
scb->abort_task.proto_conn_rate = (1 << 4); /* SSP */
scb->abort_task.proto_conn_rate |= task->dev->linkrate;
break;
case SAS_PROTO_SMP:
break;
default:
break;
}
if (task->task_proto == SAS_PROTO_SSP) {
scb->abort_task.ssp_frame.frame_type = SSP_TASK;
memcpy(scb->abort_task.ssp_frame.hashed_dest_addr,
task->dev->hashed_sas_addr, HASHED_SAS_ADDR_SIZE);
memcpy(scb->abort_task.ssp_frame.hashed_src_addr,
task->dev->port->ha->hashed_sas_addr,
HASHED_SAS_ADDR_SIZE);
scb->abort_task.ssp_frame.tptt = cpu_to_be16(0xFFFF);
memcpy(scb->abort_task.ssp_task.lun, task->ssp_task.LUN, 8);
scb->abort_task.ssp_task.tmf = TMF_ABORT_TASK;
scb->abort_task.ssp_task.tag = cpu_to_be16(0xFFFF);
}
scb->abort_task.sister_scb = cpu_to_le16(0xFFFF);
scb->abort_task.conn_handle = cpu_to_le16(
(u16)(unsigned long)task->dev->lldd_dev);
scb->abort_task.retry_count = 1;
scb->abort_task.index = cpu_to_le16((u16)tascb->tc_index);
scb->abort_task.itnl_to = cpu_to_le16(ITNL_TIMEOUT_CONST);
res = asd_enqueue_internal(ascb, asd_tmf_tasklet_complete,
asd_tmf_timedout);
if (res)
goto out;
wait_for_completion(&ascb->completion);
ASD_DPRINTK("tmf came back\n");
res = (int) (unsigned long) ascb->uldd_task;
tascb->tag = ascb->tag;
tascb->tag_valid = ascb->tag_valid;
spin_lock_irqsave(&task->task_state_lock, flags);
if (task->task_state_flags & SAS_TASK_STATE_DONE) {
spin_unlock_irqrestore(&task->task_state_lock, flags);
res = TMF_RESP_FUNC_COMPLETE;
ASD_DPRINTK("%s: task 0x%p done\n", __FUNCTION__, task);
goto out_done;
}
spin_unlock_irqrestore(&task->task_state_lock, flags);
switch (res) {
/* The task to be aborted has been sent to the device.
* We got a Response IU for the ABORT TASK TMF. */
case TC_NO_ERROR + 0xFF00:
case TMF_RESP_FUNC_COMPLETE:
case TMF_RESP_FUNC_FAILED:
res = asd_clear_nexus(task);
break;
case TMF_RESP_INVALID_FRAME:
case TMF_RESP_OVERLAPPED_TAG:
case TMF_RESP_FUNC_ESUPP:
case TMF_RESP_NO_LUN:
goto out_done; break;
}
/* In the following we assume that the managing layer
* will _never_ make a mistake, when issuing ABORT TASK.
*/
switch (res) {
default:
res = asd_clear_nexus(task);
/* fallthrough */
case TC_NO_ERROR + 0xFF00:
case TMF_RESP_FUNC_COMPLETE:
break;
/* The task hasn't been sent to the device xor we never got
* a (sane) Response IU for the ABORT TASK TMF.
*/
case TF_NAK_RECV + 0xFF00:
res = TMF_RESP_INVALID_FRAME;
break;
case TF_TMF_TASK_DONE + 0xFF00: /* done but not reported yet */
res = TMF_RESP_FUNC_FAILED;
wait_for_completion_timeout(&tascb->completion,
AIC94XX_SCB_TIMEOUT);
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_done;
case TF_TMF_NO_TAG + 0xFF00:
case TF_TMF_TAG_FREE + 0xFF00: /* the tag is in the free list */
case TF_TMF_NO_CONN_HANDLE + 0xFF00: /* no such device */
res = TMF_RESP_FUNC_COMPLETE;
goto out_done;
case TF_TMF_NO_CTX + 0xFF00: /* not in seq, or proto != SSP */
res = TMF_RESP_FUNC_ESUPP;
goto out;
}
out_done:
if (res == TMF_RESP_FUNC_COMPLETE) {
task->lldd_task = NULL;
mb();
asd_ascb_free(tascb);
}
out:
asd_ascb_free(ascb);
ASD_DPRINTK("task 0x%p aborted, res: 0x%x\n", task, res);
return res;
}
/**
* asd_initiate_ssp_tmf -- send a TMF to an I_T_L or I_T_L_Q nexus
* @dev: pointer to struct domain_device of interest
* @lun: pointer to u8[8] which is the LUN
* @tmf: the TMF to be performed (see sas_task.h or the SAS spec)
* @index: the transaction context of the task to be queried if QT TMF
*
* This function is used to send ABORT TASK SET, CLEAR ACA,
* CLEAR TASK SET, LU RESET and QUERY TASK TMFs.
*
* No SCBs should be queued to the I_T_L nexus when this SCB is
* pending.
*
* Returns: TMF response code (see sas_task.h or the SAS spec)
*/
static int asd_initiate_ssp_tmf(struct domain_device *dev, u8 *lun,
int tmf, int index)
{
struct asd_ha_struct *asd_ha = dev->port->ha->lldd_ha;
struct asd_ascb *ascb;
int res = 1;
struct scb *scb;
if (!(dev->tproto & SAS_PROTO_SSP))
return TMF_RESP_FUNC_ESUPP;
ascb = asd_ascb_alloc_list(asd_ha, &res, GFP_KERNEL);
if (!ascb)
return -ENOMEM;
scb = ascb->scb;
if (tmf == TMF_QUERY_TASK)
scb->header.opcode = QUERY_SSP_TASK;
else
scb->header.opcode = INITIATE_SSP_TMF;
scb->ssp_tmf.proto_conn_rate = (1 << 4); /* SSP */
scb->ssp_tmf.proto_conn_rate |= dev->linkrate;
/* SSP frame header */
scb->ssp_tmf.ssp_frame.frame_type = SSP_TASK;
memcpy(scb->ssp_tmf.ssp_frame.hashed_dest_addr,
dev->hashed_sas_addr, HASHED_SAS_ADDR_SIZE);
memcpy(scb->ssp_tmf.ssp_frame.hashed_src_addr,
dev->port->ha->hashed_sas_addr, HASHED_SAS_ADDR_SIZE);
scb->ssp_tmf.ssp_frame.tptt = cpu_to_be16(0xFFFF);
/* SSP Task IU */
memcpy(scb->ssp_tmf.ssp_task.lun, lun, 8);
scb->ssp_tmf.ssp_task.tmf = tmf;
scb->ssp_tmf.sister_scb = cpu_to_le16(0xFFFF);
scb->ssp_tmf.conn_handle= cpu_to_le16((u16)(unsigned long)
dev->lldd_dev);
scb->ssp_tmf.retry_count = 1;
scb->ssp_tmf.itnl_to = cpu_to_le16(ITNL_TIMEOUT_CONST);
if (tmf == TMF_QUERY_TASK)
scb->ssp_tmf.index = cpu_to_le16(index);
res = asd_enqueue_internal(ascb, asd_tmf_tasklet_complete,
asd_tmf_timedout);
if (res)
goto out_err;
wait_for_completion(&ascb->completion);
res = (int) (unsigned long) ascb->uldd_task;
switch (res) {
case TC_NO_ERROR + 0xFF00:
res = TMF_RESP_FUNC_COMPLETE;
break;
case TF_NAK_RECV + 0xFF00:
res = TMF_RESP_INVALID_FRAME;
break;
case TF_TMF_TASK_DONE + 0xFF00:
res = TMF_RESP_FUNC_FAILED;
break;
case TF_TMF_NO_TAG + 0xFF00:
case TF_TMF_TAG_FREE + 0xFF00: /* the tag is in the free list */
case TF_TMF_NO_CONN_HANDLE + 0xFF00: /* no such device */
res = TMF_RESP_FUNC_COMPLETE;
break;
case TF_TMF_NO_CTX + 0xFF00: /* not in seq, or proto != SSP */
res = TMF_RESP_FUNC_ESUPP;
break;
default:
ASD_DPRINTK("%s: converting result 0x%x to TMF_RESP_FUNC_FAILED\n",
__FUNCTION__, res);
res = TMF_RESP_FUNC_FAILED;
break;
}
out_err:
asd_ascb_free(ascb);
return res;
}
int asd_abort_task_set(struct domain_device *dev, u8 *lun)
{
int res = asd_initiate_ssp_tmf(dev, lun, TMF_ABORT_TASK_SET, 0);
if (res == TMF_RESP_FUNC_COMPLETE)
asd_clear_nexus_I_T_L(dev, lun);
return res;
}
int asd_clear_aca(struct domain_device *dev, u8 *lun)
{
int res = asd_initiate_ssp_tmf(dev, lun, TMF_CLEAR_ACA, 0);
if (res == TMF_RESP_FUNC_COMPLETE)
asd_clear_nexus_I_T_L(dev, lun);
return res;
}
int asd_clear_task_set(struct domain_device *dev, u8 *lun)
{
int res = asd_initiate_ssp_tmf(dev, lun, TMF_CLEAR_TASK_SET, 0);
if (res == TMF_RESP_FUNC_COMPLETE)
asd_clear_nexus_I_T_L(dev, lun);
return res;
}
int asd_lu_reset(struct domain_device *dev, u8 *lun)
{
int res = asd_initiate_ssp_tmf(dev, lun, TMF_LU_RESET, 0);
if (res == TMF_RESP_FUNC_COMPLETE)
asd_clear_nexus_I_T_L(dev, lun);
return res;
}
/**
* asd_query_task -- send a QUERY TASK TMF to an I_T_L_Q nexus
* task: pointer to sas_task struct of interest
*
* Returns: TMF_RESP_FUNC_COMPLETE if the task is not in the task set,
* or TMF_RESP_FUNC_SUCC if the task is in the task set.
*
* Normally the management layer sets the task to aborted state,
* and then calls query task and then abort task.
*/
int asd_query_task(struct sas_task *task)
{
struct asd_ascb *ascb = task->lldd_task;
int index;
if (ascb) {
index = ascb->tc_index;
return asd_initiate_ssp_tmf(task->dev, task->ssp_task.LUN,
TMF_QUERY_TASK, index);
}
return TMF_RESP_FUNC_COMPLETE;
}