[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
commit 2908d778ab
37 changed files with 18866 additions and 1 deletions

View file

@ -0,0 +1,484 @@
SAS Layer
---------
The SAS Layer is a management infrastructure which manages
SAS LLDDs. It sits between SCSI Core and SAS LLDDs. The
layout is as follows: while SCSI Core is concerned with
SAM/SPC issues, and a SAS LLDD+sequencer is concerned with
phy/OOB/link management, the SAS layer is concerned with:
* SAS Phy/Port/HA event management (LLDD generates,
SAS Layer processes),
* SAS Port management (creation/destruction),
* SAS Domain discovery and revalidation,
* SAS Domain device management,
* SCSI Host registration/unregistration,
* Device registration with SCSI Core (SAS) or libata
(SATA), and
* Expander management and exporting expander control
to user space.
A SAS LLDD is a PCI device driver. It is concerned with
phy/OOB management, and vendor specific tasks and generates
events to the SAS layer.
The SAS Layer does most SAS tasks as outlined in the SAS 1.1
spec.
The sas_ha_struct describes the SAS LLDD to the SAS layer.
Most of it is used by the SAS Layer but a few fields need to
be initialized by the LLDDs.
After initializing your hardware, from the probe() function
you call sas_register_ha(). It will register your LLDD with
the SCSI subsystem, creating a SCSI host and it will
register your SAS driver with the sysfs SAS tree it creates.
It will then return. Then you enable your phys to actually
start OOB (at which point your driver will start calling the
notify_* event callbacks).
Structure descriptions:
struct sas_phy --------------------
Normally this is statically embedded to your driver's
phy structure:
struct my_phy {
blah;
struct sas_phy sas_phy;
bleh;
};
And then all the phys are an array of my_phy in your HA
struct (shown below).
Then as you go along and initialize your phys you also
initialize the sas_phy struct, along with your own
phy structure.
In general, the phys are managed by the LLDD and the ports
are managed by the SAS layer. So the phys are initialized
and updated by the LLDD and the ports are initialized and
updated by the SAS layer.
There is a scheme where the LLDD can RW certain fields,
and the SAS layer can only read such ones, and vice versa.
The idea is to avoid unnecessary locking.
enabled -- must be set (0/1)
id -- must be set [0,MAX_PHYS)
class, proto, type, role, oob_mode, linkrate -- must be set
oob_mode -- you set this when OOB has finished and then notify
the SAS Layer.
sas_addr -- this normally points to an array holding the sas
address of the phy, possibly somewhere in your my_phy
struct.
attached_sas_addr -- set this when you (LLDD) receive an
IDENTIFY frame or a FIS frame, _before_ notifying the SAS
layer. The idea is that sometimes the LLDD may want to fake
or provide a different SAS address on that phy/port and this
allows it to do this. At best you should copy the sas
address from the IDENTIFY frame or maybe generate a SAS
address for SATA directly attached devices. The Discover
process may later change this.
frame_rcvd -- this is where you copy the IDENTIFY/FIS frame
when you get it; you lock, copy, set frame_rcvd_size and
unlock the lock, and then call the event. It is a pointer
since there's no way to know your hw frame size _exactly_,
so you define the actual array in your phy struct and let
this pointer point to it. You copy the frame from your
DMAable memory to that area holding the lock.
sas_prim -- this is where primitives go when they're
received. See sas.h. Grab the lock, set the primitive,
release the lock, notify.
port -- this points to the sas_port if the phy belongs
to a port -- the LLDD only reads this. It points to the
sas_port this phy is part of. Set by the SAS Layer.
ha -- may be set; the SAS layer sets it anyway.
lldd_phy -- you should set this to point to your phy so you
can find your way around faster when the SAS layer calls one
of your callbacks and passes you a phy. If the sas_phy is
embedded you can also use container_of -- whatever you
prefer.
struct sas_port --------------------
The LLDD doesn't set any fields of this struct -- it only
reads them. They should be self explanatory.
phy_mask is 32 bit, this should be enough for now, as I
haven't heard of a HA having more than 8 phys.
lldd_port -- I haven't found use for that -- maybe other
LLDD who wish to have internal port representation can make
use of this.
struct sas_ha_struct --------------------
It normally is statically declared in your own LLDD
structure describing your adapter:
struct my_sas_ha {
blah;
struct sas_ha_struct sas_ha;
struct my_phy phys[MAX_PHYS];
struct sas_port sas_ports[MAX_PHYS]; /* (1) */
bleh;
};
(1) If your LLDD doesn't have its own port representation.
What needs to be initialized (sample function given below).
pcidev
sas_addr -- since the SAS layer doesn't want to mess with
memory allocation, etc, this points to statically
allocated array somewhere (say in your host adapter
structure) and holds the SAS address of the host
adapter as given by you or the manufacturer, etc.
sas_port
sas_phy -- an array of pointers to structures. (see
note above on sas_addr).
These must be set. See more notes below.
num_phys -- the number of phys present in the sas_phy array,
and the number of ports present in the sas_port
array. There can be a maximum num_phys ports (one per
port) so we drop the num_ports, and only use
num_phys.
The event interface:
/* LLDD calls these to notify the class of an event. */
void (*notify_ha_event)(struct sas_ha_struct *, enum ha_event);
void (*notify_port_event)(struct sas_phy *, enum port_event);
void (*notify_phy_event)(struct sas_phy *, enum phy_event);
When sas_register_ha() returns, those are set and can be
called by the LLDD to notify the SAS layer of such events
the SAS layer.
The port notification:
/* The class calls these to notify the LLDD of an event. */
void (*lldd_port_formed)(struct sas_phy *);
void (*lldd_port_deformed)(struct sas_phy *);
If the LLDD wants notification when a port has been formed
or deformed it sets those to a function satisfying the type.
A SAS LLDD should also implement at least one of the Task
Management Functions (TMFs) described in SAM:
/* Task Management Functions. Must be called from process context. */
int (*lldd_abort_task)(struct sas_task *);
int (*lldd_abort_task_set)(struct domain_device *, u8 *lun);
int (*lldd_clear_aca)(struct domain_device *, u8 *lun);
int (*lldd_clear_task_set)(struct domain_device *, u8 *lun);
int (*lldd_I_T_nexus_reset)(struct domain_device *);
int (*lldd_lu_reset)(struct domain_device *, u8 *lun);
int (*lldd_query_task)(struct sas_task *);
For more information please read SAM from T10.org.
Port and Adapter management:
/* Port and Adapter management */
int (*lldd_clear_nexus_port)(struct sas_port *);
int (*lldd_clear_nexus_ha)(struct sas_ha_struct *);
A SAS LLDD should implement at least one of those.
Phy management:
/* Phy management */
int (*lldd_control_phy)(struct sas_phy *, enum phy_func);
lldd_ha -- set this to point to your HA struct. You can also
use container_of if you embedded it as shown above.
A sample initialization and registration function
can look like this (called last thing from probe())
*but* before you enable the phys to do OOB:
static int register_sas_ha(struct my_sas_ha *my_ha)
{
int i;
static struct sas_phy *sas_phys[MAX_PHYS];
static struct sas_port *sas_ports[MAX_PHYS];
my_ha->sas_ha.sas_addr = &my_ha->sas_addr[0];
for (i = 0; i < MAX_PHYS; i++) {
sas_phys[i] = &my_ha->phys[i].sas_phy;
sas_ports[i] = &my_ha->sas_ports[i];
}
my_ha->sas_ha.sas_phy = sas_phys;
my_ha->sas_ha.sas_port = sas_ports;
my_ha->sas_ha.num_phys = MAX_PHYS;
my_ha->sas_ha.lldd_port_formed = my_port_formed;
my_ha->sas_ha.lldd_dev_found = my_dev_found;
my_ha->sas_ha.lldd_dev_gone = my_dev_gone;
my_ha->sas_ha.lldd_max_execute_num = lldd_max_execute_num; (1)
my_ha->sas_ha.lldd_queue_size = ha_can_queue;
my_ha->sas_ha.lldd_execute_task = my_execute_task;
my_ha->sas_ha.lldd_abort_task = my_abort_task;
my_ha->sas_ha.lldd_abort_task_set = my_abort_task_set;
my_ha->sas_ha.lldd_clear_aca = my_clear_aca;
my_ha->sas_ha.lldd_clear_task_set = my_clear_task_set;
my_ha->sas_ha.lldd_I_T_nexus_reset= NULL; (2)
my_ha->sas_ha.lldd_lu_reset = my_lu_reset;
my_ha->sas_ha.lldd_query_task = my_query_task;
my_ha->sas_ha.lldd_clear_nexus_port = my_clear_nexus_port;
my_ha->sas_ha.lldd_clear_nexus_ha = my_clear_nexus_ha;
my_ha->sas_ha.lldd_control_phy = my_control_phy;
return sas_register_ha(&my_ha->sas_ha);
}
(1) This is normally a LLDD parameter, something of the
lines of a task collector. What it tells the SAS Layer is
whether the SAS layer should run in Direct Mode (default:
value 0 or 1) or Task Collector Mode (value greater than 1).
In Direct Mode, the SAS Layer calls Execute Task as soon as
it has a command to send to the SDS, _and_ this is a single
command, i.e. not linked.
Some hardware (e.g. aic94xx) has the capability to DMA more
than one task at a time (interrupt) from host memory. Task
Collector Mode is an optional feature for HAs which support
this in their hardware. (Again, it is completely optional
even if your hardware supports it.)
In Task Collector Mode, the SAS Layer would do _natural_
coalescing of tasks and at the appropriate moment it would
call your driver to DMA more than one task in a single HA
interrupt. DMBS may want to use this by insmod/modprobe
setting the lldd_max_execute_num to something greater than
1.
(2) SAS 1.1 does not define I_T Nexus Reset TMF.
Events
------
Events are _the only way_ a SAS LLDD notifies the SAS layer
of anything. There is no other method or way a LLDD to tell
the SAS layer of anything happening internally or in the SAS
domain.
Phy events:
PHYE_LOSS_OF_SIGNAL, (C)
PHYE_OOB_DONE,
PHYE_OOB_ERROR, (C)
PHYE_SPINUP_HOLD.
Port events, passed on a _phy_:
PORTE_BYTES_DMAED, (M)
PORTE_BROADCAST_RCVD, (E)
PORTE_LINK_RESET_ERR, (C)
PORTE_TIMER_EVENT, (C)
PORTE_HARD_RESET.
Host Adapter event:
HAE_RESET
A SAS LLDD should be able to generate
- at least one event from group C (choice),
- events marked M (mandatory) are mandatory (only one),
- events marked E (expander) if it wants the SAS layer
to handle domain revalidation (only one such).
- Unmarked events are optional.
Meaning:
HAE_RESET -- when your HA got internal error and was reset.
PORTE_BYTES_DMAED -- on receiving an IDENTIFY/FIS frame
PORTE_BROADCAST_RCVD -- on receiving a primitive
PORTE_LINK_RESET_ERR -- timer expired, loss of signal, loss
of DWS, etc. (*)
PORTE_TIMER_EVENT -- DWS reset timeout timer expired (*)
PORTE_HARD_RESET -- Hard Reset primitive received.
PHYE_LOSS_OF_SIGNAL -- the device is gone (*)
PHYE_OOB_DONE -- OOB went fine and oob_mode is valid
PHYE_OOB_ERROR -- Error while doing OOB, the device probably
got disconnected. (*)
PHYE_SPINUP_HOLD -- SATA is present, COMWAKE not sent.
(*) should set/clear the appropriate fields in the phy,
or alternatively call the inlined sas_phy_disconnected()
which is just a helper, from their tasklet.
The Execute Command SCSI RPC:
int (*lldd_execute_task)(struct sas_task *, int num,
unsigned long gfp_flags);
Used to queue a task to the SAS LLDD. @task is the tasks to
be executed. @num should be the number of tasks being
queued at this function call (they are linked listed via
task::list), @gfp_mask should be the gfp_mask defining the
context of the caller.
This function should implement the Execute Command SCSI RPC,
or if you're sending a SCSI Task as linked commands, you
should also use this function.
That is, when lldd_execute_task() is called, the command(s)
go out on the transport *immediately*. There is *no*
queuing of any sort and at any level in a SAS LLDD.
The use of task::list is two-fold, one for linked commands,
the other discussed below.
It is possible to queue up more than one task at a time, by
initializing the list element of struct sas_task, and
passing the number of tasks enlisted in this manner in num.
Returns: -SAS_QUEUE_FULL, -ENOMEM, nothing was queued;
0, the task(s) were queued.
If you want to pass num > 1, then either
A) you're the only caller of this function and keep track
of what you've queued to the LLDD, or
B) you know what you're doing and have a strategy of
retrying.
As opposed to queuing one task at a time (function call),
batch queuing of tasks, by having num > 1, greatly
simplifies LLDD code, sequencer code, and _hardware design_,
and has some performance advantages in certain situations
(DBMS).
The LLDD advertises if it can take more than one command at
a time at lldd_execute_task(), by setting the
lldd_max_execute_num parameter (controlled by "collector"
module parameter in aic94xx SAS LLDD).
You should leave this to the default 1, unless you know what
you're doing.
This is a function of the LLDD, to which the SAS layer can
cater to.
int lldd_queue_size
The host adapter's queue size. This is the maximum
number of commands the lldd can have pending to domain
devices on behalf of all upper layers submitting through
lldd_execute_task().
You really want to set this to something (much) larger than
1.
This _really_ has absolutely nothing to do with queuing.
There is no queuing in SAS LLDDs.
struct sas_task {
dev -- the device this task is destined to
list -- must be initialized (INIT_LIST_HEAD)
task_proto -- _one_ of enum sas_proto
scatter -- pointer to scatter gather list array
num_scatter -- number of elements in scatter
total_xfer_len -- total number of bytes expected to be transfered
data_dir -- PCI_DMA_...
task_done -- callback when the task has finished execution
};
When an external entity, entity other than the LLDD or the
SAS Layer, wants to work with a struct domain_device, it
_must_ call kobject_get() when getting a handle on the
device and kobject_put() when it is done with the device.
This does two things:
A) implements proper kfree() for the device;
B) increments/decrements the kref for all players:
domain_device
all domain_device's ... (if past an expander)
port
host adapter
pci device
and up the ladder, etc.
DISCOVERY
---------
The sysfs tree has the following purposes:
a) It shows you the physical layout of the SAS domain at
the current time, i.e. how the domain looks in the
physical world right now.
b) Shows some device parameters _at_discovery_time_.
This is a link to the tree(1) program, very useful in
viewing the SAS domain:
ftp://mama.indstate.edu/linux/tree/
I expect user space applications to actually create a
graphical interface of this.
That is, the sysfs domain tree doesn't show or keep state if
you e.g., change the meaning of the READY LED MEANING
setting, but it does show you the current connection status
of the domain device.
Keeping internal device state changes is responsibility of
upper layers (Command set drivers) and user space.
When a device or devices are unplugged from the domain, this
is reflected in the sysfs tree immediately, and the device(s)
removed from the system.
The structure domain_device describes any device in the SAS
domain. It is completely managed by the SAS layer. A task
points to a domain device, this is how the SAS LLDD knows
where to send the task(s) to. A SAS LLDD only reads the
contents of the domain_device structure, but it never creates
or destroys one.
Expander management from User Space
-----------------------------------
In each expander directory in sysfs, there is a file called
"smp_portal". It is a binary sysfs attribute file, which
implements an SMP portal (Note: this is *NOT* an SMP port),
to which user space applications can send SMP requests and
receive SMP responses.
Functionality is deceptively simple:
1. Build the SMP frame you want to send. The format and layout
is described in the SAS spec. Leave the CRC field equal 0.
open(2)
2. Open the expander's SMP portal sysfs file in RW mode.
write(2)
3. Write the frame you built in 1.
read(2)
4. Read the amount of data you expect to receive for the frame you built.
If you receive different amount of data you expected to receive,
then there was some kind of error.
close(2)
All this process is shown in detail in the function do_smp_func()
and its callers, in the file "expander_conf.c".
The kernel functionality is implemented in the file
"sas_expander.c".
The program "expander_conf.c" implements this. It takes one
argument, the sysfs file name of the SMP portal to the
expander, and gives expander information, including routing
tables.
The SMP portal gives you complete control of the expander,
so please be careful.

View file

@ -209,7 +209,7 @@ config SCSI_LOGGING
there should be no noticeable performance impact as long as you have
logging turned off.
menu "SCSI Transport Attributes"
menu "SCSI Transports"
depends on SCSI
config SCSI_SPI_ATTRS
@ -242,6 +242,8 @@ config SCSI_SAS_ATTRS
If you wish to export transport-specific information about
each attached SAS device to sysfs, say Y.
source "drivers/scsi/libsas/Kconfig"
endmenu
menu "SCSI low-level drivers"
@ -431,6 +433,7 @@ config SCSI_AIC7XXX_OLD
module will be called aic7xxx_old.
source "drivers/scsi/aic7xxx/Kconfig.aic79xx"
source "drivers/scsi/aic94xx/Kconfig"
# All the I2O code and drivers do not seem to be 64bit safe.
config SCSI_DPT_I2O

View file

@ -32,6 +32,7 @@ obj-$(CONFIG_SCSI_SPI_ATTRS) += scsi_transport_spi.o
obj-$(CONFIG_SCSI_FC_ATTRS) += scsi_transport_fc.o
obj-$(CONFIG_SCSI_ISCSI_ATTRS) += scsi_transport_iscsi.o
obj-$(CONFIG_SCSI_SAS_ATTRS) += scsi_transport_sas.o
obj-$(CONFIG_SCSI_SAS_LIBSAS) += libsas/
obj-$(CONFIG_ISCSI_TCP) += libiscsi.o iscsi_tcp.o
obj-$(CONFIG_INFINIBAND_ISER) += libiscsi.o
@ -68,6 +69,7 @@ obj-$(CONFIG_SCSI_AIC7XXX) += aic7xxx/
obj-$(CONFIG_SCSI_AIC79XX) += aic7xxx/
obj-$(CONFIG_SCSI_AACRAID) += aacraid/
obj-$(CONFIG_SCSI_AIC7XXX_OLD) += aic7xxx_old.o
obj-$(CONFIG_SCSI_AIC94XX) += aic94xx/
obj-$(CONFIG_SCSI_IPS) += ips.o
obj-$(CONFIG_SCSI_FD_MCS) += fd_mcs.o
obj-$(CONFIG_SCSI_FUTURE_DOMAIN)+= fdomain.o

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

Some files were not shown because too many files have changed in this diff Show more