mirror of
https://mirrors.bfsu.edu.cn/git/linux.git
synced 2024-11-27 06:04:23 +08:00
[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:
parent
f4ad7b5807
commit
2908d778ab
484
Documentation/scsi/libsas.txt
Normal file
484
Documentation/scsi/libsas.txt
Normal 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.
|
@ -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
|
||||
|
@ -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
|
||||
|
41
drivers/scsi/aic94xx/Kconfig
Normal file
41
drivers/scsi/aic94xx/Kconfig
Normal 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.
|
39
drivers/scsi/aic94xx/Makefile
Normal file
39
drivers/scsi/aic94xx/Makefile
Normal 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
|
114
drivers/scsi/aic94xx/aic94xx.h
Normal file
114
drivers/scsi/aic94xx/aic94xx.h
Normal 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
|
353
drivers/scsi/aic94xx/aic94xx_dev.c
Normal file
353
drivers/scsi/aic94xx/aic94xx_dev.c
Normal 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;
|
||||
}
|
959
drivers/scsi/aic94xx/aic94xx_dump.c
Normal file
959
drivers/scsi/aic94xx/aic94xx_dump.c
Normal 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 */
|
52
drivers/scsi/aic94xx/aic94xx_dump.h
Normal file
52
drivers/scsi/aic94xx/aic94xx_dump.h
Normal 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_ */
|
1376
drivers/scsi/aic94xx/aic94xx_hwi.c
Normal file
1376
drivers/scsi/aic94xx/aic94xx_hwi.c
Normal file
File diff suppressed because it is too large
Load Diff
397
drivers/scsi/aic94xx/aic94xx_hwi.h
Normal file
397
drivers/scsi/aic94xx/aic94xx_hwi.h
Normal 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
|
860
drivers/scsi/aic94xx/aic94xx_init.c
Normal file
860
drivers/scsi/aic94xx/aic94xx_init.c
Normal 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);
|
332
drivers/scsi/aic94xx/aic94xx_reg.c
Normal file
332
drivers/scsi/aic94xx/aic94xx_reg.c
Normal 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);
|
||||
}
|
302
drivers/scsi/aic94xx/aic94xx_reg.h
Normal file
302
drivers/scsi/aic94xx/aic94xx_reg.h
Normal 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
|
2398
drivers/scsi/aic94xx/aic94xx_reg_def.h
Normal file
2398
drivers/scsi/aic94xx/aic94xx_reg_def.h
Normal file
File diff suppressed because it is too large
Load Diff
785
drivers/scsi/aic94xx/aic94xx_sas.h
Normal file
785
drivers/scsi/aic94xx/aic94xx_sas.h
Normal 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
|
732
drivers/scsi/aic94xx/aic94xx_scb.c
Normal file
732
drivers/scsi/aic94xx/aic94xx_scb.c
Normal 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;
|
||||
}
|
1136
drivers/scsi/aic94xx/aic94xx_sds.c
Normal file
1136
drivers/scsi/aic94xx/aic94xx_sds.c
Normal file
File diff suppressed because it is too large
Load Diff
1401
drivers/scsi/aic94xx/aic94xx_seq.c
Normal file
1401
drivers/scsi/aic94xx/aic94xx_seq.c
Normal file
File diff suppressed because it is too large
Load Diff
70
drivers/scsi/aic94xx/aic94xx_seq.h
Normal file
70
drivers/scsi/aic94xx/aic94xx_seq.h
Normal 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
|
642
drivers/scsi/aic94xx/aic94xx_task.c
Normal file
642
drivers/scsi/aic94xx/aic94xx_task.c
Normal file
@ -0,0 +1,642 @@
|
||||
/*
|
||||
* Aic94xx SAS/SATA Tasks
|
||||
*
|
||||
* Copyright (C) 2005 Adaptec, Inc. All rights reserved.
|
||||
* Copyright (C) 2005 Luben Tuikov <luben_tuikov@adaptec.com>
|
||||
*
|
||||
* This file is licensed under GPLv2.
|
||||
*
|
||||
* This file is part of the aic94xx driver.
|
||||
*
|
||||
* The aic94xx driver is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation; version 2 of the
|
||||
* License.
|
||||
*
|
||||
* The aic94xx driver is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with the aic94xx driver; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/spinlock.h>
|
||||
#include "aic94xx.h"
|
||||
#include "aic94xx_sas.h"
|
||||
#include "aic94xx_hwi.h"
|
||||
|
||||
static void asd_unbuild_ata_ascb(struct asd_ascb *a);
|
||||
static void asd_unbuild_smp_ascb(struct asd_ascb *a);
|
||||
static void asd_unbuild_ssp_ascb(struct asd_ascb *a);
|
||||
|
||||
static inline void asd_can_dequeue(struct asd_ha_struct *asd_ha, int num)
|
||||
{
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&asd_ha->seq.pend_q_lock, flags);
|
||||
asd_ha->seq.can_queue += num;
|
||||
spin_unlock_irqrestore(&asd_ha->seq.pend_q_lock, flags);
|
||||
}
|
||||
|
||||
/* PCI_DMA_... to our direction translation.
|
||||
*/
|
||||
static const u8 data_dir_flags[] = {
|
||||
[PCI_DMA_BIDIRECTIONAL] = DATA_DIR_BYRECIPIENT, /* UNSPECIFIED */
|
||||
[PCI_DMA_TODEVICE] = DATA_DIR_OUT, /* OUTBOUND */
|
||||
[PCI_DMA_FROMDEVICE] = DATA_DIR_IN, /* INBOUND */
|
||||
[PCI_DMA_NONE] = DATA_DIR_NONE, /* NO TRANSFER */
|
||||
};
|
||||
|
||||
static inline int asd_map_scatterlist(struct sas_task *task,
|
||||
struct sg_el *sg_arr,
|
||||
unsigned long gfp_flags)
|
||||
{
|
||||
struct asd_ascb *ascb = task->lldd_task;
|
||||
struct asd_ha_struct *asd_ha = ascb->ha;
|
||||
struct scatterlist *sc;
|
||||
int num_sg, res;
|
||||
|
||||
if (task->data_dir == PCI_DMA_NONE)
|
||||
return 0;
|
||||
|
||||
if (task->num_scatter == 0) {
|
||||
void *p = task->scatter;
|
||||
dma_addr_t dma = pci_map_single(asd_ha->pcidev, p,
|
||||
task->total_xfer_len,
|
||||
task->data_dir);
|
||||
sg_arr[0].bus_addr = cpu_to_le64((u64)dma);
|
||||
sg_arr[0].size = cpu_to_le32(task->total_xfer_len);
|
||||
sg_arr[0].flags |= ASD_SG_EL_LIST_EOL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
num_sg = pci_map_sg(asd_ha->pcidev, task->scatter, task->num_scatter,
|
||||
task->data_dir);
|
||||
if (num_sg == 0)
|
||||
return -ENOMEM;
|
||||
|
||||
if (num_sg > 3) {
|
||||
int i;
|
||||
|
||||
ascb->sg_arr = asd_alloc_coherent(asd_ha,
|
||||
num_sg*sizeof(struct sg_el),
|
||||
gfp_flags);
|
||||
if (!ascb->sg_arr) {
|
||||
res = -ENOMEM;
|
||||
goto err_unmap;
|
||||
}
|
||||
for (sc = task->scatter, i = 0; i < num_sg; i++, sc++) {
|
||||
struct sg_el *sg =
|
||||
&((struct sg_el *)ascb->sg_arr->vaddr)[i];
|
||||
sg->bus_addr = cpu_to_le64((u64)sg_dma_address(sc));
|
||||
sg->size = cpu_to_le32((u32)sg_dma_len(sc));
|
||||
if (i == num_sg-1)
|
||||
sg->flags |= ASD_SG_EL_LIST_EOL;
|
||||
}
|
||||
|
||||
for (sc = task->scatter, i = 0; i < 2; i++, sc++) {
|
||||
sg_arr[i].bus_addr =
|
||||
cpu_to_le64((u64)sg_dma_address(sc));
|
||||
sg_arr[i].size = cpu_to_le32((u32)sg_dma_len(sc));
|
||||
}
|
||||
sg_arr[1].next_sg_offs = 2 * sizeof(*sg_arr);
|
||||
sg_arr[1].flags |= ASD_SG_EL_LIST_EOS;
|
||||
|
||||
memset(&sg_arr[2], 0, sizeof(*sg_arr));
|
||||
sg_arr[2].bus_addr=cpu_to_le64((u64)ascb->sg_arr->dma_handle);
|
||||
} else {
|
||||
int i;
|
||||
for (sc = task->scatter, i = 0; i < num_sg; i++, sc++) {
|
||||
sg_arr[i].bus_addr =
|
||||
cpu_to_le64((u64)sg_dma_address(sc));
|
||||
sg_arr[i].size = cpu_to_le32((u32)sg_dma_len(sc));
|
||||
}
|
||||
sg_arr[i-1].flags |= ASD_SG_EL_LIST_EOL;
|
||||
}
|
||||
|
||||
return 0;
|
||||
err_unmap:
|
||||
pci_unmap_sg(asd_ha->pcidev, task->scatter, task->num_scatter,
|
||||
task->data_dir);
|
||||
return res;
|
||||
}
|
||||
|
||||
static inline void asd_unmap_scatterlist(struct asd_ascb *ascb)
|
||||
{
|
||||
struct asd_ha_struct *asd_ha = ascb->ha;
|
||||
struct sas_task *task = ascb->uldd_task;
|
||||
|
||||
if (task->data_dir == PCI_DMA_NONE)
|
||||
return;
|
||||
|
||||
if (task->num_scatter == 0) {
|
||||
dma_addr_t dma = (dma_addr_t)
|
||||
le64_to_cpu(ascb->scb->ssp_task.sg_element[0].bus_addr);
|
||||
pci_unmap_single(ascb->ha->pcidev, dma, task->total_xfer_len,
|
||||
task->data_dir);
|
||||
return;
|
||||
}
|
||||
|
||||
asd_free_coherent(asd_ha, ascb->sg_arr);
|
||||
pci_unmap_sg(asd_ha->pcidev, task->scatter, task->num_scatter,
|
||||
task->data_dir);
|
||||
}
|
||||
|
||||
/* ---------- Task complete tasklet ---------- */
|
||||
|
||||
static void asd_get_response_tasklet(struct asd_ascb *ascb,
|
||||
struct done_list_struct *dl)
|
||||
{
|
||||
struct asd_ha_struct *asd_ha = ascb->ha;
|
||||
struct sas_task *task = ascb->uldd_task;
|
||||
struct task_status_struct *ts = &task->task_status;
|
||||
unsigned long flags;
|
||||
struct tc_resp_sb_struct {
|
||||
__le16 index_escb;
|
||||
u8 len_lsb;
|
||||
u8 flags;
|
||||
} __attribute__ ((packed)) *resp_sb = (void *) dl->status_block;
|
||||
|
||||
/* int size = ((resp_sb->flags & 7) << 8) | resp_sb->len_lsb; */
|
||||
int edb_id = ((resp_sb->flags & 0x70) >> 4)-1;
|
||||
struct asd_ascb *escb;
|
||||
struct asd_dma_tok *edb;
|
||||
void *r;
|
||||
|
||||
spin_lock_irqsave(&asd_ha->seq.tc_index_lock, flags);
|
||||
escb = asd_tc_index_find(&asd_ha->seq,
|
||||
(int)le16_to_cpu(resp_sb->index_escb));
|
||||
spin_unlock_irqrestore(&asd_ha->seq.tc_index_lock, flags);
|
||||
|
||||
if (!escb) {
|
||||
ASD_DPRINTK("Uh-oh! No escb for this dl?!\n");
|
||||
return;
|
||||
}
|
||||
|
||||
ts->buf_valid_size = 0;
|
||||
edb = asd_ha->seq.edb_arr[edb_id + escb->edb_index];
|
||||
r = edb->vaddr;
|
||||
if (task->task_proto == SAS_PROTO_SSP) {
|
||||
struct ssp_response_iu *iu =
|
||||
r + 16 + sizeof(struct ssp_frame_hdr);
|
||||
|
||||
ts->residual = le32_to_cpu(*(__le32 *)r);
|
||||
ts->resp = SAS_TASK_COMPLETE;
|
||||
if (iu->datapres == 0)
|
||||
ts->stat = iu->status;
|
||||
else if (iu->datapres == 1)
|
||||
ts->stat = iu->resp_data[3];
|
||||
else if (iu->datapres == 2) {
|
||||
ts->stat = SAM_CHECK_COND;
|
||||
ts->buf_valid_size = min((u32) SAS_STATUS_BUF_SIZE,
|
||||
be32_to_cpu(iu->sense_data_len));
|
||||
memcpy(ts->buf, iu->sense_data, ts->buf_valid_size);
|
||||
if (iu->status != SAM_CHECK_COND) {
|
||||
ASD_DPRINTK("device %llx sent sense data, but "
|
||||
"stat(0x%x) is not CHECK_CONDITION"
|
||||
"\n",
|
||||
SAS_ADDR(task->dev->sas_addr),
|
||||
ts->stat);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
struct ata_task_resp *resp = (void *) &ts->buf[0];
|
||||
|
||||
ts->residual = le32_to_cpu(*(__le32 *)r);
|
||||
|
||||
if (SAS_STATUS_BUF_SIZE >= sizeof(*resp)) {
|
||||
resp->frame_len = le16_to_cpu(*(__le16 *)(r+6));
|
||||
memcpy(&resp->ending_fis[0], r+16, 24);
|
||||
ts->buf_valid_size = sizeof(*resp);
|
||||
}
|
||||
}
|
||||
|
||||
asd_invalidate_edb(escb, edb_id);
|
||||
}
|
||||
|
||||
static void asd_task_tasklet_complete(struct asd_ascb *ascb,
|
||||
struct done_list_struct *dl)
|
||||
{
|
||||
struct sas_task *task = ascb->uldd_task;
|
||||
struct task_status_struct *ts = &task->task_status;
|
||||
unsigned long flags;
|
||||
u8 opcode = dl->opcode;
|
||||
|
||||
asd_can_dequeue(ascb->ha, 1);
|
||||
|
||||
Again:
|
||||
switch (opcode) {
|
||||
case TC_NO_ERROR:
|
||||
ts->resp = SAS_TASK_COMPLETE;
|
||||
ts->stat = SAM_GOOD;
|
||||
break;
|
||||
case TC_UNDERRUN:
|
||||
ts->resp = SAS_TASK_COMPLETE;
|
||||
ts->stat = SAS_DATA_UNDERRUN;
|
||||
ts->residual = le32_to_cpu(*(__le32 *)dl->status_block);
|
||||
break;
|
||||
case TC_OVERRUN:
|
||||
ts->resp = SAS_TASK_COMPLETE;
|
||||
ts->stat = SAS_DATA_OVERRUN;
|
||||
ts->residual = 0;
|
||||
break;
|
||||
case TC_SSP_RESP:
|
||||
case TC_ATA_RESP:
|
||||
ts->resp = SAS_TASK_COMPLETE;
|
||||
ts->stat = SAS_PROTO_RESPONSE;
|
||||
asd_get_response_tasklet(ascb, dl);
|
||||
break;
|
||||
case TF_OPEN_REJECT:
|
||||
ts->resp = SAS_TASK_UNDELIVERED;
|
||||
ts->stat = SAS_OPEN_REJECT;
|
||||
if (dl->status_block[1] & 2)
|
||||
ts->open_rej_reason = 1 + dl->status_block[2];
|
||||
else if (dl->status_block[1] & 1)
|
||||
ts->open_rej_reason = (dl->status_block[2] >> 4)+10;
|
||||
else
|
||||
ts->open_rej_reason = SAS_OREJ_UNKNOWN;
|
||||
break;
|
||||
case TF_OPEN_TO:
|
||||
ts->resp = SAS_TASK_UNDELIVERED;
|
||||
ts->stat = SAS_OPEN_TO;
|
||||
break;
|
||||
case TF_PHY_DOWN:
|
||||
case TU_PHY_DOWN:
|
||||
ts->resp = SAS_TASK_UNDELIVERED;
|
||||
ts->stat = SAS_PHY_DOWN;
|
||||
break;
|
||||
case TI_PHY_DOWN:
|
||||
ts->resp = SAS_TASK_COMPLETE;
|
||||
ts->stat = SAS_PHY_DOWN;
|
||||
break;
|
||||
case TI_BREAK:
|
||||
case TI_PROTO_ERR:
|
||||
case TI_NAK:
|
||||
case TI_ACK_NAK_TO:
|
||||
case TF_SMP_XMIT_RCV_ERR:
|
||||
case TC_ATA_R_ERR_RECV:
|
||||
ts->resp = SAS_TASK_COMPLETE;
|
||||
ts->stat = SAS_INTERRUPTED;
|
||||
break;
|
||||
case TF_BREAK:
|
||||
case TU_BREAK:
|
||||
case TU_ACK_NAK_TO:
|
||||
case TF_SMPRSP_TO:
|
||||
ts->resp = SAS_TASK_UNDELIVERED;
|
||||
ts->stat = SAS_DEV_NO_RESPONSE;
|
||||
break;
|
||||
case TF_NAK_RECV:
|
||||
ts->resp = SAS_TASK_COMPLETE;
|
||||
ts->stat = SAS_NAK_R_ERR;
|
||||
break;
|
||||
case TA_I_T_NEXUS_LOSS:
|
||||
opcode = dl->status_block[0];
|
||||
goto Again;
|
||||
break;
|
||||
case TF_INV_CONN_HANDLE:
|
||||
ts->resp = SAS_TASK_UNDELIVERED;
|
||||
ts->stat = SAS_DEVICE_UNKNOWN;
|
||||
break;
|
||||
case TF_REQUESTED_N_PENDING:
|
||||
ts->resp = SAS_TASK_UNDELIVERED;
|
||||
ts->stat = SAS_PENDING;
|
||||
break;
|
||||
case TC_TASK_CLEARED:
|
||||
case TA_ON_REQ:
|
||||
ts->resp = SAS_TASK_COMPLETE;
|
||||
ts->stat = SAS_ABORTED_TASK;
|
||||
break;
|
||||
|
||||
case TF_NO_SMP_CONN:
|
||||
case TF_TMF_NO_CTX:
|
||||
case TF_TMF_NO_TAG:
|
||||
case TF_TMF_TAG_FREE:
|
||||
case TF_TMF_TASK_DONE:
|
||||
case TF_TMF_NO_CONN_HANDLE:
|
||||
case TF_IRTT_TO:
|
||||
case TF_IU_SHORT:
|
||||
case TF_DATA_OFFS_ERR:
|
||||
ts->resp = SAS_TASK_UNDELIVERED;
|
||||
ts->stat = SAS_DEV_NO_RESPONSE;
|
||||
break;
|
||||
|
||||
case TC_LINK_ADM_RESP:
|
||||
case TC_CONTROL_PHY:
|
||||
case TC_RESUME:
|
||||
case TC_PARTIAL_SG_LIST:
|
||||
default:
|
||||
ASD_DPRINTK("%s: dl opcode: 0x%x?\n", __FUNCTION__, opcode);
|
||||
break;
|
||||
}
|
||||
|
||||
switch (task->task_proto) {
|
||||
case SATA_PROTO:
|
||||
case SAS_PROTO_STP:
|
||||
asd_unbuild_ata_ascb(ascb);
|
||||
break;
|
||||
case SAS_PROTO_SMP:
|
||||
asd_unbuild_smp_ascb(ascb);
|
||||
break;
|
||||
case SAS_PROTO_SSP:
|
||||
asd_unbuild_ssp_ascb(ascb);
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
spin_lock_irqsave(&task->task_state_lock, flags);
|
||||
task->task_state_flags &= ~SAS_TASK_STATE_PENDING;
|
||||
task->task_state_flags |= SAS_TASK_STATE_DONE;
|
||||
if (unlikely((task->task_state_flags & SAS_TASK_STATE_ABORTED))) {
|
||||
spin_unlock_irqrestore(&task->task_state_lock, flags);
|
||||
ASD_DPRINTK("task 0x%p done with opcode 0x%x resp 0x%x "
|
||||
"stat 0x%x but aborted by upper layer!\n",
|
||||
task, opcode, ts->resp, ts->stat);
|
||||
complete(&ascb->completion);
|
||||
} else {
|
||||
spin_unlock_irqrestore(&task->task_state_lock, flags);
|
||||
task->lldd_task = NULL;
|
||||
asd_ascb_free(ascb);
|
||||
mb();
|
||||
task->task_done(task);
|
||||
}
|
||||
}
|
||||
|
||||
/* ---------- ATA ---------- */
|
||||
|
||||
static int asd_build_ata_ascb(struct asd_ascb *ascb, struct sas_task *task,
|
||||
unsigned long gfp_flags)
|
||||
{
|
||||
struct domain_device *dev = task->dev;
|
||||
struct scb *scb;
|
||||
u8 flags;
|
||||
int res = 0;
|
||||
|
||||
scb = ascb->scb;
|
||||
|
||||
if (unlikely(task->ata_task.device_control_reg_update))
|
||||
scb->header.opcode = CONTROL_ATA_DEV;
|
||||
else if (dev->sata_dev.command_set == ATA_COMMAND_SET)
|
||||
scb->header.opcode = INITIATE_ATA_TASK;
|
||||
else
|
||||
scb->header.opcode = INITIATE_ATAPI_TASK;
|
||||
|
||||
scb->ata_task.proto_conn_rate = (1 << 5); /* STP */
|
||||
if (dev->port->oob_mode == SAS_OOB_MODE)
|
||||
scb->ata_task.proto_conn_rate |= dev->linkrate;
|
||||
|
||||
scb->ata_task.total_xfer_len = cpu_to_le32(task->total_xfer_len);
|
||||
scb->ata_task.fis = task->ata_task.fis;
|
||||
scb->ata_task.fis.fis_type = 0x27;
|
||||
if (likely(!task->ata_task.device_control_reg_update))
|
||||
scb->ata_task.fis.flags |= 0x80; /* C=1: update ATA cmd reg */
|
||||
scb->ata_task.fis.flags &= 0xF0; /* PM_PORT field shall be 0 */
|
||||
if (dev->sata_dev.command_set == ATAPI_COMMAND_SET)
|
||||
memcpy(scb->ata_task.atapi_packet, task->ata_task.atapi_packet,
|
||||
16);
|
||||
scb->ata_task.sister_scb = cpu_to_le16(0xFFFF);
|
||||
scb->ata_task.conn_handle = cpu_to_le16(
|
||||
(u16)(unsigned long)dev->lldd_dev);
|
||||
|
||||
if (likely(!task->ata_task.device_control_reg_update)) {
|
||||
flags = 0;
|
||||
if (task->ata_task.dma_xfer)
|
||||
flags |= DATA_XFER_MODE_DMA;
|
||||
if (task->ata_task.use_ncq &&
|
||||
dev->sata_dev.command_set != ATAPI_COMMAND_SET)
|
||||
flags |= ATA_Q_TYPE_NCQ;
|
||||
flags |= data_dir_flags[task->data_dir];
|
||||
scb->ata_task.ata_flags = flags;
|
||||
|
||||
scb->ata_task.retry_count = task->ata_task.retry_count;
|
||||
|
||||
flags = 0;
|
||||
if (task->ata_task.set_affil_pol)
|
||||
flags |= SET_AFFIL_POLICY;
|
||||
if (task->ata_task.stp_affil_pol)
|
||||
flags |= STP_AFFIL_POLICY;
|
||||
scb->ata_task.flags = flags;
|
||||
}
|
||||
ascb->tasklet_complete = asd_task_tasklet_complete;
|
||||
|
||||
if (likely(!task->ata_task.device_control_reg_update))
|
||||
res = asd_map_scatterlist(task, scb->ata_task.sg_element,
|
||||
gfp_flags);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
static void asd_unbuild_ata_ascb(struct asd_ascb *a)
|
||||
{
|
||||
asd_unmap_scatterlist(a);
|
||||
}
|
||||
|
||||
/* ---------- SMP ---------- */
|
||||
|
||||
static int asd_build_smp_ascb(struct asd_ascb *ascb, struct sas_task *task,
|
||||
unsigned long gfp_flags)
|
||||
{
|
||||
struct asd_ha_struct *asd_ha = ascb->ha;
|
||||
struct domain_device *dev = task->dev;
|
||||
struct scb *scb;
|
||||
|
||||
pci_map_sg(asd_ha->pcidev, &task->smp_task.smp_req, 1,
|
||||
PCI_DMA_FROMDEVICE);
|
||||
pci_map_sg(asd_ha->pcidev, &task->smp_task.smp_resp, 1,
|
||||
PCI_DMA_FROMDEVICE);
|
||||
|
||||
scb = ascb->scb;
|
||||
|
||||
scb->header.opcode = INITIATE_SMP_TASK;
|
||||
|
||||
scb->smp_task.proto_conn_rate = dev->linkrate;
|
||||
|
||||
scb->smp_task.smp_req.bus_addr =
|
||||
cpu_to_le64((u64)sg_dma_address(&task->smp_task.smp_req));
|
||||
scb->smp_task.smp_req.size =
|
||||
cpu_to_le32((u32)sg_dma_len(&task->smp_task.smp_req)-4);
|
||||
|
||||
scb->smp_task.smp_resp.bus_addr =
|
||||
cpu_to_le64((u64)sg_dma_address(&task->smp_task.smp_resp));
|
||||
scb->smp_task.smp_resp.size =
|
||||
cpu_to_le32((u32)sg_dma_len(&task->smp_task.smp_resp)-4);
|
||||
|
||||
scb->smp_task.sister_scb = cpu_to_le16(0xFFFF);
|
||||
scb->smp_task.conn_handle = cpu_to_le16((u16)
|
||||
(unsigned long)dev->lldd_dev);
|
||||
|
||||
ascb->tasklet_complete = asd_task_tasklet_complete;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void asd_unbuild_smp_ascb(struct asd_ascb *a)
|
||||
{
|
||||
struct sas_task *task = a->uldd_task;
|
||||
|
||||
BUG_ON(!task);
|
||||
pci_unmap_sg(a->ha->pcidev, &task->smp_task.smp_req, 1,
|
||||
PCI_DMA_FROMDEVICE);
|
||||
pci_unmap_sg(a->ha->pcidev, &task->smp_task.smp_resp, 1,
|
||||
PCI_DMA_FROMDEVICE);
|
||||
}
|
||||
|
||||
/* ---------- SSP ---------- */
|
||||
|
||||
static int asd_build_ssp_ascb(struct asd_ascb *ascb, struct sas_task *task,
|
||||
unsigned long gfp_flags)
|
||||
{
|
||||
struct domain_device *dev = task->dev;
|
||||
struct scb *scb;
|
||||
int res = 0;
|
||||
|
||||
scb = ascb->scb;
|
||||
|
||||
scb->header.opcode = INITIATE_SSP_TASK;
|
||||
|
||||
scb->ssp_task.proto_conn_rate = (1 << 4); /* SSP */
|
||||
scb->ssp_task.proto_conn_rate |= dev->linkrate;
|
||||
scb->ssp_task.total_xfer_len = cpu_to_le32(task->total_xfer_len);
|
||||
scb->ssp_task.ssp_frame.frame_type = SSP_DATA;
|
||||
memcpy(scb->ssp_task.ssp_frame.hashed_dest_addr, dev->hashed_sas_addr,
|
||||
HASHED_SAS_ADDR_SIZE);
|
||||
memcpy(scb->ssp_task.ssp_frame.hashed_src_addr,
|
||||
dev->port->ha->hashed_sas_addr, HASHED_SAS_ADDR_SIZE);
|
||||
scb->ssp_task.ssp_frame.tptt = cpu_to_be16(0xFFFF);
|
||||
|
||||
memcpy(scb->ssp_task.ssp_cmd.lun, task->ssp_task.LUN, 8);
|
||||
if (task->ssp_task.enable_first_burst)
|
||||
scb->ssp_task.ssp_cmd.efb_prio_attr |= EFB_MASK;
|
||||
scb->ssp_task.ssp_cmd.efb_prio_attr |= (task->ssp_task.task_prio << 3);
|
||||
scb->ssp_task.ssp_cmd.efb_prio_attr |= (task->ssp_task.task_attr & 7);
|
||||
memcpy(scb->ssp_task.ssp_cmd.cdb, task->ssp_task.cdb, 16);
|
||||
|
||||
scb->ssp_task.sister_scb = cpu_to_le16(0xFFFF);
|
||||
scb->ssp_task.conn_handle = cpu_to_le16(
|
||||
(u16)(unsigned long)dev->lldd_dev);
|
||||
scb->ssp_task.data_dir = data_dir_flags[task->data_dir];
|
||||
scb->ssp_task.retry_count = scb->ssp_task.retry_count;
|
||||
|
||||
ascb->tasklet_complete = asd_task_tasklet_complete;
|
||||
|
||||
res = asd_map_scatterlist(task, scb->ssp_task.sg_element, gfp_flags);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
static void asd_unbuild_ssp_ascb(struct asd_ascb *a)
|
||||
{
|
||||
asd_unmap_scatterlist(a);
|
||||
}
|
||||
|
||||
/* ---------- Execute Task ---------- */
|
||||
|
||||
static inline int asd_can_queue(struct asd_ha_struct *asd_ha, int num)
|
||||
{
|
||||
int res = 0;
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&asd_ha->seq.pend_q_lock, flags);
|
||||
if ((asd_ha->seq.can_queue - num) < 0)
|
||||
res = -SAS_QUEUE_FULL;
|
||||
else
|
||||
asd_ha->seq.can_queue -= num;
|
||||
spin_unlock_irqrestore(&asd_ha->seq.pend_q_lock, flags);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
int asd_execute_task(struct sas_task *task, const int num,
|
||||
unsigned long gfp_flags)
|
||||
{
|
||||
int res = 0;
|
||||
LIST_HEAD(alist);
|
||||
struct sas_task *t = task;
|
||||
struct asd_ascb *ascb = NULL, *a;
|
||||
struct asd_ha_struct *asd_ha = task->dev->port->ha->lldd_ha;
|
||||
|
||||
res = asd_can_queue(asd_ha, num);
|
||||
if (res)
|
||||
return res;
|
||||
|
||||
res = num;
|
||||
ascb = asd_ascb_alloc_list(asd_ha, &res, gfp_flags);
|
||||
if (res) {
|
||||
res = -ENOMEM;
|
||||
goto out_err;
|
||||
}
|
||||
|
||||
__list_add(&alist, ascb->list.prev, &ascb->list);
|
||||
list_for_each_entry(a, &alist, list) {
|
||||
a->uldd_task = t;
|
||||
t->lldd_task = a;
|
||||
t = list_entry(t->list.next, struct sas_task, list);
|
||||
}
|
||||
list_for_each_entry(a, &alist, list) {
|
||||
t = a->uldd_task;
|
||||
a->uldd_timer = 1;
|
||||
if (t->task_proto & SAS_PROTO_STP)
|
||||
t->task_proto = SAS_PROTO_STP;
|
||||
switch (t->task_proto) {
|
||||
case SATA_PROTO:
|
||||
case SAS_PROTO_STP:
|
||||
res = asd_build_ata_ascb(a, t, gfp_flags);
|
||||
break;
|
||||
case SAS_PROTO_SMP:
|
||||
res = asd_build_smp_ascb(a, t, gfp_flags);
|
||||
break;
|
||||
case SAS_PROTO_SSP:
|
||||
res = asd_build_ssp_ascb(a, t, gfp_flags);
|
||||
break;
|
||||
default:
|
||||
asd_printk("unknown sas_task proto: 0x%x\n",
|
||||
t->task_proto);
|
||||
res = -ENOMEM;
|
||||
break;
|
||||
}
|
||||
if (res)
|
||||
goto out_err_unmap;
|
||||
}
|
||||
list_del_init(&alist);
|
||||
|
||||
res = asd_post_ascb_list(asd_ha, ascb, num);
|
||||
if (unlikely(res)) {
|
||||
a = NULL;
|
||||
__list_add(&alist, ascb->list.prev, &ascb->list);
|
||||
goto out_err_unmap;
|
||||
}
|
||||
|
||||
return 0;
|
||||
out_err_unmap:
|
||||
{
|
||||
struct asd_ascb *b = a;
|
||||
list_for_each_entry(a, &alist, list) {
|
||||
if (a == b)
|
||||
break;
|
||||
t = a->uldd_task;
|
||||
switch (t->task_proto) {
|
||||
case SATA_PROTO:
|
||||
case SAS_PROTO_STP:
|
||||
asd_unbuild_ata_ascb(a);
|
||||
break;
|
||||
case SAS_PROTO_SMP:
|
||||
asd_unbuild_smp_ascb(a);
|
||||
break;
|
||||
case SAS_PROTO_SSP:
|
||||
asd_unbuild_ssp_ascb(a);
|
||||
default:
|
||||
break;
|
||||
}
|
||||
t->lldd_task = NULL;
|
||||
}
|
||||
}
|
||||
list_del_init(&alist);
|
||||
out_err:
|
||||
if (ascb)
|
||||
asd_ascb_free_list(ascb);
|
||||
asd_can_dequeue(asd_ha, num);
|
||||
return res;
|
||||
}
|
636
drivers/scsi/aic94xx/aic94xx_tmf.c
Normal file
636
drivers/scsi/aic94xx/aic94xx_tmf.c
Normal file
@ -0,0 +1,636 @@
|
||||
/*
|
||||
* Aic94xx Task Management Functions
|
||||
*
|
||||
* Copyright (C) 2005 Adaptec, Inc. All rights reserved.
|
||||
* Copyright (C) 2005 Luben Tuikov <luben_tuikov@adaptec.com>
|
||||
*
|
||||
* This file is licensed under GPLv2.
|
||||
*
|
||||
* This file is part of the aic94xx driver.
|
||||
*
|
||||
* The aic94xx driver is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation; version 2 of the
|
||||
* License.
|
||||
*
|
||||
* The aic94xx driver is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with the aic94xx driver; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/spinlock.h>
|
||||
#include "aic94xx.h"
|
||||
#include "aic94xx_sas.h"
|
||||
#include "aic94xx_hwi.h"
|
||||
|
||||
/* ---------- Internal enqueue ---------- */
|
||||
|
||||
static int asd_enqueue_internal(struct asd_ascb *ascb,
|
||||
void (*tasklet_complete)(struct asd_ascb *,
|
||||
struct done_list_struct *),
|
||||
void (*timed_out)(unsigned long))
|
||||
{
|
||||
int res;
|
||||
|
||||
ascb->tasklet_complete = tasklet_complete;
|
||||
ascb->uldd_timer = 1;
|
||||
|
||||
ascb->timer.data = (unsigned long) ascb;
|
||||
ascb->timer.function = timed_out;
|
||||
ascb->timer.expires = jiffies + AIC94XX_SCB_TIMEOUT;
|
||||
|
||||
add_timer(&ascb->timer);
|
||||
|
||||
res = asd_post_ascb_list(ascb->ha, ascb, 1);
|
||||
if (unlikely(res))
|
||||
del_timer(&ascb->timer);
|
||||
return res;
|
||||
}
|
||||
|
||||
static inline void asd_timedout_common(unsigned long data)
|
||||
{
|
||||
struct asd_ascb *ascb = (void *) data;
|
||||
struct asd_seq_data *seq = &ascb->ha->seq;
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&seq->pend_q_lock, flags);
|
||||
seq->pending--;
|
||||
list_del_init(&ascb->list);
|
||||
spin_unlock_irqrestore(&seq->pend_q_lock, flags);
|
||||
}
|
||||
|
||||
/* ---------- CLEAR NEXUS ---------- */
|
||||
|
||||
static void asd_clear_nexus_tasklet_complete(struct asd_ascb *ascb,
|
||||
struct done_list_struct *dl)
|
||||
{
|
||||
ASD_DPRINTK("%s: here\n", __FUNCTION__);
|
||||
if (!del_timer(&ascb->timer)) {
|
||||
ASD_DPRINTK("%s: couldn't delete timer\n", __FUNCTION__);
|
||||
return;
|
||||
}
|
||||
ASD_DPRINTK("%s: opcode: 0x%x\n", __FUNCTION__, dl->opcode);
|
||||
ascb->uldd_task = (void *) (unsigned long) dl->opcode;
|
||||
complete(&ascb->completion);
|
||||
}
|
||||
|
||||
static void asd_clear_nexus_timedout(unsigned long data)
|
||||
{
|
||||
struct asd_ascb *ascb = (void *) data;
|
||||
|
||||
ASD_DPRINTK("%s: here\n", __FUNCTION__);
|
||||
asd_timedout_common(data);
|
||||
ascb->uldd_task = (void *) TMF_RESP_FUNC_FAILED;
|
||||
complete(&ascb->completion);
|
||||
}
|
||||
|
||||
#define CLEAR_NEXUS_PRE \
|
||||
ASD_DPRINTK("%s: PRE\n", __FUNCTION__); \
|
||||
res = 1; \
|
||||
ascb = asd_ascb_alloc_list(asd_ha, &res, GFP_KERNEL); \
|
||||
if (!ascb) \
|
||||
return -ENOMEM; \
|
||||
\
|
||||
scb = ascb->scb; \
|
||||
scb->header.opcode = CLEAR_NEXUS
|
||||
|
||||
#define CLEAR_NEXUS_POST \
|
||||
ASD_DPRINTK("%s: POST\n", __FUNCTION__); \
|
||||
res = asd_enqueue_internal(ascb, asd_clear_nexus_tasklet_complete, \
|
||||
asd_clear_nexus_timedout); \
|
||||
if (res) \
|
||||
goto out_err; \
|
||||
ASD_DPRINTK("%s: clear nexus posted, waiting...\n", __FUNCTION__); \
|
||||
wait_for_completion(&ascb->completion); \
|
||||
res = (int) (unsigned long) ascb->uldd_task; \
|
||||
if (res == TC_NO_ERROR) \
|
||||
res = TMF_RESP_FUNC_COMPLETE; \
|
||||
out_err: \
|
||||
asd_ascb_free(ascb); \
|
||||
return res
|
||||
|
||||
int asd_clear_nexus_ha(struct sas_ha_struct *sas_ha)
|
||||
{
|
||||
struct asd_ha_struct *asd_ha = sas_ha->lldd_ha;
|
||||
struct asd_ascb *ascb;
|
||||
struct scb *scb;
|
||||
int res;
|
||||
|
||||
CLEAR_NEXUS_PRE;
|
||||
scb->clear_nexus.nexus = NEXUS_ADAPTER;
|
||||
CLEAR_NEXUS_POST;
|
||||
}
|
||||
|
||||
int asd_clear_nexus_port(struct asd_sas_port *port)
|
||||
{
|
||||
struct asd_ha_struct *asd_ha = port->ha->lldd_ha;
|
||||
struct asd_ascb *ascb;
|
||||
struct scb *scb;
|
||||
int res;
|
||||
|
||||
CLEAR_NEXUS_PRE;
|
||||
scb->clear_nexus.nexus = NEXUS_PORT;
|
||||
scb->clear_nexus.conn_mask = port->phy_mask;
|
||||
CLEAR_NEXUS_POST;
|
||||
}
|
||||
|
||||
#if 0
|
||||
static int asd_clear_nexus_I_T(struct domain_device *dev)
|
||||
{
|
||||
struct asd_ha_struct *asd_ha = dev->port->ha->lldd_ha;
|
||||
struct asd_ascb *ascb;
|
||||
struct scb *scb;
|
||||
int res;
|
||||
|
||||
CLEAR_NEXUS_PRE;
|
||||
scb->clear_nexus.nexus = NEXUS_I_T;
|
||||
scb->clear_nexus.flags = SEND_Q | EXEC_Q | NOTINQ;
|
||||
if (dev->tproto)
|
||||
scb->clear_nexus.flags |= SUSPEND_TX;
|
||||
scb->clear_nexus.conn_handle = cpu_to_le16((u16)(unsigned long)
|
||||
dev->lldd_dev);
|
||||
CLEAR_NEXUS_POST;
|
||||
}
|
||||
#endif
|
||||
|
||||
static int asd_clear_nexus_I_T_L(struct domain_device *dev, u8 *lun)
|
||||
{
|
||||
struct asd_ha_struct *asd_ha = dev->port->ha->lldd_ha;
|
||||
struct asd_ascb *ascb;
|
||||
struct scb *scb;
|
||||
int res;
|
||||
|
||||
CLEAR_NEXUS_PRE;
|
||||
scb->clear_nexus.nexus = NEXUS_I_T_L;
|
||||
scb->clear_nexus.flags = SEND_Q | EXEC_Q | NOTINQ;
|
||||
if (dev->tproto)
|
||||
scb->clear_nexus.flags |= SUSPEND_TX;
|
||||
memcpy(scb->clear_nexus.ssp_task.lun, lun, 8);
|
||||
scb->clear_nexus.conn_handle = cpu_to_le16((u16)(unsigned long)
|
||||
dev->lldd_dev);
|
||||
CLEAR_NEXUS_POST;
|
||||
}
|
||||
|
||||
static int asd_clear_nexus_tag(struct sas_task *task)
|
||||
{
|
||||
struct asd_ha_struct *asd_ha = task->dev->port->ha->lldd_ha;
|
||||
struct asd_ascb *tascb = task->lldd_task;
|
||||
struct asd_ascb *ascb;
|
||||
struct scb *scb;
|
||||
int res;
|
||||
|
||||
CLEAR_NEXUS_PRE;
|
||||
scb->clear_nexus.nexus = NEXUS_TAG;
|
||||
memcpy(scb->clear_nexus.ssp_task.lun, task->ssp_task.LUN, 8);
|
||||
scb->clear_nexus.ssp_task.tag = tascb->tag;
|
||||
if (task->dev->tproto)
|
||||
scb->clear_nexus.conn_handle = cpu_to_le16((u16)(unsigned long)
|
||||
task->dev->lldd_dev);
|
||||
CLEAR_NEXUS_POST;
|
||||
}
|
||||
|
||||
static int asd_clear_nexus_index(struct sas_task *task)
|
||||
{
|
||||
struct asd_ha_struct *asd_ha = task->dev->port->ha->lldd_ha;
|
||||
struct asd_ascb *tascb = task->lldd_task;
|
||||
struct asd_ascb *ascb;
|
||||
struct scb *scb;
|
||||
int res;
|
||||
|
||||
CLEAR_NEXUS_PRE;
|
||||
scb->clear_nexus.nexus = NEXUS_TRANS_CX;
|
||||
if (task->dev->tproto)
|
||||
scb->clear_nexus.conn_handle = cpu_to_le16((u16)(unsigned long)
|
||||
task->dev->lldd_dev);
|
||||
scb->clear_nexus.index = cpu_to_le16(tascb->tc_index);
|
||||
CLEAR_NEXUS_POST;
|
||||
}
|
||||
|
||||
/* ---------- TMFs ---------- */
|
||||
|
||||
static void asd_tmf_timedout(unsigned long data)
|
||||
{
|
||||
struct asd_ascb *ascb = (void *) data;
|
||||
|
||||
ASD_DPRINTK("tmf timed out\n");
|
||||
asd_timedout_common(data);
|
||||
ascb->uldd_task = (void *) TMF_RESP_FUNC_FAILED;
|
||||
complete(&ascb->completion);
|
||||
}
|
||||
|
||||
static int asd_get_tmf_resp_tasklet(struct asd_ascb *ascb,
|
||||
struct done_list_struct *dl)
|
||||
{
|
||||
struct asd_ha_struct *asd_ha = ascb->ha;
|
||||
unsigned long flags;
|
||||
struct tc_resp_sb_struct {
|
||||
__le16 index_escb;
|
||||
u8 len_lsb;
|
||||
u8 flags;
|
||||
} __attribute__ ((packed)) *resp_sb = (void *) dl->status_block;
|
||||
|
||||
int edb_id = ((resp_sb->flags & 0x70) >> 4)-1;
|
||||
struct asd_ascb *escb;
|
||||
struct asd_dma_tok *edb;
|
||||
struct ssp_frame_hdr *fh;
|
||||
struct ssp_response_iu *ru;
|
||||
int res = TMF_RESP_FUNC_FAILED;
|
||||
|
||||
ASD_DPRINTK("tmf resp tasklet\n");
|
||||
|
||||
spin_lock_irqsave(&asd_ha->seq.tc_index_lock, flags);
|
||||
escb = asd_tc_index_find(&asd_ha->seq,
|
||||
(int)le16_to_cpu(resp_sb->index_escb));
|
||||
spin_unlock_irqrestore(&asd_ha->seq.tc_index_lock, flags);
|
||||
|
||||
if (!escb) {
|
||||
ASD_DPRINTK("Uh-oh! No escb for this dl?!\n");
|
||||
return res;
|
||||
}
|
||||
|
||||
edb = asd_ha->seq.edb_arr[edb_id + escb->edb_index];
|
||||
ascb->tag = *(__be16 *)(edb->vaddr+4);
|
||||
fh = edb->vaddr + 16;
|
||||
ru = edb->vaddr + 16 + sizeof(*fh);
|
||||
res = ru->status;
|
||||
if (ru->datapres == 1) /* Response data present */
|
||||
res = ru->resp_data[3];
|
||||
#if 0
|
||||
ascb->tag = fh->tag;
|
||||
#endif
|
||||
ascb->tag_valid = 1;
|
||||
|
||||
asd_invalidate_edb(escb, edb_id);
|
||||
return res;
|
||||
}
|
||||
|
||||
static void asd_tmf_tasklet_complete(struct asd_ascb *ascb,
|
||||
struct done_list_struct *dl)
|
||||
{
|
||||
if (!del_timer(&ascb->timer))
|
||||
return;
|
||||
|
||||
ASD_DPRINTK("tmf tasklet complete\n");
|
||||
|
||||
if (dl->opcode == TC_SSP_RESP)
|
||||
ascb->uldd_task = (void *) (unsigned long)
|
||||
asd_get_tmf_resp_tasklet(ascb, dl);
|
||||
else
|
||||
ascb->uldd_task = (void *) 0xFF00 + (unsigned long) dl->opcode;
|
||||
|
||||
complete(&ascb->completion);
|
||||
}
|
||||
|
||||
static inline int asd_clear_nexus(struct sas_task *task)
|
||||
{
|
||||
int res = TMF_RESP_FUNC_FAILED;
|
||||
struct asd_ascb *tascb = task->lldd_task;
|
||||
unsigned long flags;
|
||||
|
||||
ASD_DPRINTK("task not done, clearing nexus\n");
|
||||
if (tascb->tag_valid)
|
||||
res = asd_clear_nexus_tag(task);
|
||||
else
|
||||
res = asd_clear_nexus_index(task);
|
||||
wait_for_completion_timeout(&tascb->completion,
|
||||
AIC94XX_SCB_TIMEOUT);
|
||||
ASD_DPRINTK("came back from clear nexus\n");
|
||||
spin_lock_irqsave(&task->task_state_lock, flags);
|
||||
if (task->task_state_flags & SAS_TASK_STATE_DONE)
|
||||
res = TMF_RESP_FUNC_COMPLETE;
|
||||
spin_unlock_irqrestore(&task->task_state_lock, flags);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
/**
|
||||
* asd_abort_task -- ABORT TASK TMF
|
||||
* @task: the task to be aborted
|
||||
*
|
||||
* Before calling ABORT TASK the task state flags should be ORed with
|
||||
* SAS_TASK_STATE_ABORTED (unless SAS_TASK_STATE_DONE is set) under
|
||||
* the task_state_lock IRQ spinlock, then ABORT TASK *must* be called.
|
||||
*
|
||||
* Implements the ABORT TASK TMF, I_T_L_Q nexus.
|
||||
* Returns: SAS TMF responses (see sas_task.h),
|
||||
* -ENOMEM,
|
||||
* -SAS_QUEUE_FULL.
|
||||
*
|
||||
* When ABORT TASK returns, the caller of ABORT TASK checks first the
|
||||
* task->task_state_flags, and then the return value of ABORT TASK.
|
||||
*
|
||||
* If the task has task state bit SAS_TASK_STATE_DONE set, then the
|
||||
* task was completed successfully prior to it being aborted. The
|
||||
* caller of ABORT TASK has responsibility to call task->task_done()
|
||||
* xor free the task, depending on their framework. The return code
|
||||
* is TMF_RESP_FUNC_FAILED in this case.
|
||||
*
|
||||
* Else the SAS_TASK_STATE_DONE bit is not set,
|
||||
* If the return code is TMF_RESP_FUNC_COMPLETE, then
|
||||
* the task was aborted successfully. The caller of
|
||||
* ABORT TASK has responsibility to call task->task_done()
|
||||
* to finish the task, xor free the task depending on their
|
||||
* framework.
|
||||
* else
|
||||
* the ABORT TASK returned some kind of error. The task
|
||||
* was _not_ cancelled. Nothing can be assumed.
|
||||
* The caller of ABORT TASK may wish to retry.
|
||||
*/
|
||||
int asd_abort_task(struct sas_task *task)
|
||||
{
|
||||
struct asd_ascb *tascb = task->lldd_task;
|
||||
struct asd_ha_struct *asd_ha = tascb->ha;
|
||||
int res = 1;
|
||||
unsigned long flags;
|
||||
struct asd_ascb *ascb = NULL;
|
||||
struct scb *scb;
|
||||
|
||||
spin_lock_irqsave(&task->task_state_lock, flags);
|
||||
if (task->task_state_flags & SAS_TASK_STATE_DONE) {
|
||||
spin_unlock_irqrestore(&task->task_state_lock, flags);
|
||||
res = TMF_RESP_FUNC_COMPLETE;
|
||||
ASD_DPRINTK("%s: task 0x%p done\n", __FUNCTION__, task);
|
||||
goto out_done;
|
||||
}
|
||||
spin_unlock_irqrestore(&task->task_state_lock, flags);
|
||||
|
||||
ascb = asd_ascb_alloc_list(asd_ha, &res, GFP_KERNEL);
|
||||
if (!ascb)
|
||||
return -ENOMEM;
|
||||
scb = ascb->scb;
|
||||
|
||||
scb->header.opcode = ABORT_TASK;
|
||||
|
||||
switch (task->task_proto) {
|
||||
case SATA_PROTO:
|
||||
case SAS_PROTO_STP:
|
||||
scb->abort_task.proto_conn_rate = (1 << 5); /* STP */
|
||||
break;
|
||||
case SAS_PROTO_SSP:
|
||||
scb->abort_task.proto_conn_rate = (1 << 4); /* SSP */
|
||||
scb->abort_task.proto_conn_rate |= task->dev->linkrate;
|
||||
break;
|
||||
case SAS_PROTO_SMP:
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (task->task_proto == SAS_PROTO_SSP) {
|
||||
scb->abort_task.ssp_frame.frame_type = SSP_TASK;
|
||||
memcpy(scb->abort_task.ssp_frame.hashed_dest_addr,
|
||||
task->dev->hashed_sas_addr, HASHED_SAS_ADDR_SIZE);
|
||||
memcpy(scb->abort_task.ssp_frame.hashed_src_addr,
|
||||
task->dev->port->ha->hashed_sas_addr,
|
||||
HASHED_SAS_ADDR_SIZE);
|
||||
scb->abort_task.ssp_frame.tptt = cpu_to_be16(0xFFFF);
|
||||
|
||||
memcpy(scb->abort_task.ssp_task.lun, task->ssp_task.LUN, 8);
|
||||
scb->abort_task.ssp_task.tmf = TMF_ABORT_TASK;
|
||||
scb->abort_task.ssp_task.tag = cpu_to_be16(0xFFFF);
|
||||
}
|
||||
|
||||
scb->abort_task.sister_scb = cpu_to_le16(0xFFFF);
|
||||
scb->abort_task.conn_handle = cpu_to_le16(
|
||||
(u16)(unsigned long)task->dev->lldd_dev);
|
||||
scb->abort_task.retry_count = 1;
|
||||
scb->abort_task.index = cpu_to_le16((u16)tascb->tc_index);
|
||||
scb->abort_task.itnl_to = cpu_to_le16(ITNL_TIMEOUT_CONST);
|
||||
|
||||
res = asd_enqueue_internal(ascb, asd_tmf_tasklet_complete,
|
||||
asd_tmf_timedout);
|
||||
if (res)
|
||||
goto out;
|
||||
wait_for_completion(&ascb->completion);
|
||||
ASD_DPRINTK("tmf came back\n");
|
||||
|
||||
res = (int) (unsigned long) ascb->uldd_task;
|
||||
tascb->tag = ascb->tag;
|
||||
tascb->tag_valid = ascb->tag_valid;
|
||||
|
||||
spin_lock_irqsave(&task->task_state_lock, flags);
|
||||
if (task->task_state_flags & SAS_TASK_STATE_DONE) {
|
||||
spin_unlock_irqrestore(&task->task_state_lock, flags);
|
||||
res = TMF_RESP_FUNC_COMPLETE;
|
||||
ASD_DPRINTK("%s: task 0x%p done\n", __FUNCTION__, task);
|
||||
goto out_done;
|
||||
}
|
||||
spin_unlock_irqrestore(&task->task_state_lock, flags);
|
||||
|
||||
switch (res) {
|
||||
/* The task to be aborted has been sent to the device.
|
||||
* We got a Response IU for the ABORT TASK TMF. */
|
||||
case TC_NO_ERROR + 0xFF00:
|
||||
case TMF_RESP_FUNC_COMPLETE:
|
||||
case TMF_RESP_FUNC_FAILED:
|
||||
res = asd_clear_nexus(task);
|
||||
break;
|
||||
case TMF_RESP_INVALID_FRAME:
|
||||
case TMF_RESP_OVERLAPPED_TAG:
|
||||
case TMF_RESP_FUNC_ESUPP:
|
||||
case TMF_RESP_NO_LUN:
|
||||
goto out_done; break;
|
||||
}
|
||||
/* In the following we assume that the managing layer
|
||||
* will _never_ make a mistake, when issuing ABORT TASK.
|
||||
*/
|
||||
switch (res) {
|
||||
default:
|
||||
res = asd_clear_nexus(task);
|
||||
/* fallthrough */
|
||||
case TC_NO_ERROR + 0xFF00:
|
||||
case TMF_RESP_FUNC_COMPLETE:
|
||||
break;
|
||||
/* The task hasn't been sent to the device xor we never got
|
||||
* a (sane) Response IU for the ABORT TASK TMF.
|
||||
*/
|
||||
case TF_NAK_RECV + 0xFF00:
|
||||
res = TMF_RESP_INVALID_FRAME;
|
||||
break;
|
||||
case TF_TMF_TASK_DONE + 0xFF00: /* done but not reported yet */
|
||||
res = TMF_RESP_FUNC_FAILED;
|
||||
wait_for_completion_timeout(&tascb->completion,
|
||||
AIC94XX_SCB_TIMEOUT);
|
||||
spin_lock_irqsave(&task->task_state_lock, flags);
|
||||
if (task->task_state_flags & SAS_TASK_STATE_DONE)
|
||||
res = TMF_RESP_FUNC_COMPLETE;
|
||||
spin_unlock_irqrestore(&task->task_state_lock, flags);
|
||||
goto out_done;
|
||||
case TF_TMF_NO_TAG + 0xFF00:
|
||||
case TF_TMF_TAG_FREE + 0xFF00: /* the tag is in the free list */
|
||||
case TF_TMF_NO_CONN_HANDLE + 0xFF00: /* no such device */
|
||||
res = TMF_RESP_FUNC_COMPLETE;
|
||||
goto out_done;
|
||||
case TF_TMF_NO_CTX + 0xFF00: /* not in seq, or proto != SSP */
|
||||
res = TMF_RESP_FUNC_ESUPP;
|
||||
goto out;
|
||||
}
|
||||
out_done:
|
||||
if (res == TMF_RESP_FUNC_COMPLETE) {
|
||||
task->lldd_task = NULL;
|
||||
mb();
|
||||
asd_ascb_free(tascb);
|
||||
}
|
||||
out:
|
||||
asd_ascb_free(ascb);
|
||||
ASD_DPRINTK("task 0x%p aborted, res: 0x%x\n", task, res);
|
||||
return res;
|
||||
}
|
||||
|
||||
/**
|
||||
* asd_initiate_ssp_tmf -- send a TMF to an I_T_L or I_T_L_Q nexus
|
||||
* @dev: pointer to struct domain_device of interest
|
||||
* @lun: pointer to u8[8] which is the LUN
|
||||
* @tmf: the TMF to be performed (see sas_task.h or the SAS spec)
|
||||
* @index: the transaction context of the task to be queried if QT TMF
|
||||
*
|
||||
* This function is used to send ABORT TASK SET, CLEAR ACA,
|
||||
* CLEAR TASK SET, LU RESET and QUERY TASK TMFs.
|
||||
*
|
||||
* No SCBs should be queued to the I_T_L nexus when this SCB is
|
||||
* pending.
|
||||
*
|
||||
* Returns: TMF response code (see sas_task.h or the SAS spec)
|
||||
*/
|
||||
static int asd_initiate_ssp_tmf(struct domain_device *dev, u8 *lun,
|
||||
int tmf, int index)
|
||||
{
|
||||
struct asd_ha_struct *asd_ha = dev->port->ha->lldd_ha;
|
||||
struct asd_ascb *ascb;
|
||||
int res = 1;
|
||||
struct scb *scb;
|
||||
|
||||
if (!(dev->tproto & SAS_PROTO_SSP))
|
||||
return TMF_RESP_FUNC_ESUPP;
|
||||
|
||||
ascb = asd_ascb_alloc_list(asd_ha, &res, GFP_KERNEL);
|
||||
if (!ascb)
|
||||
return -ENOMEM;
|
||||
scb = ascb->scb;
|
||||
|
||||
if (tmf == TMF_QUERY_TASK)
|
||||
scb->header.opcode = QUERY_SSP_TASK;
|
||||
else
|
||||
scb->header.opcode = INITIATE_SSP_TMF;
|
||||
|
||||
scb->ssp_tmf.proto_conn_rate = (1 << 4); /* SSP */
|
||||
scb->ssp_tmf.proto_conn_rate |= dev->linkrate;
|
||||
/* SSP frame header */
|
||||
scb->ssp_tmf.ssp_frame.frame_type = SSP_TASK;
|
||||
memcpy(scb->ssp_tmf.ssp_frame.hashed_dest_addr,
|
||||
dev->hashed_sas_addr, HASHED_SAS_ADDR_SIZE);
|
||||
memcpy(scb->ssp_tmf.ssp_frame.hashed_src_addr,
|
||||
dev->port->ha->hashed_sas_addr, HASHED_SAS_ADDR_SIZE);
|
||||
scb->ssp_tmf.ssp_frame.tptt = cpu_to_be16(0xFFFF);
|
||||
/* SSP Task IU */
|
||||
memcpy(scb->ssp_tmf.ssp_task.lun, lun, 8);
|
||||
scb->ssp_tmf.ssp_task.tmf = tmf;
|
||||
|
||||
scb->ssp_tmf.sister_scb = cpu_to_le16(0xFFFF);
|
||||
scb->ssp_tmf.conn_handle= cpu_to_le16((u16)(unsigned long)
|
||||
dev->lldd_dev);
|
||||
scb->ssp_tmf.retry_count = 1;
|
||||
scb->ssp_tmf.itnl_to = cpu_to_le16(ITNL_TIMEOUT_CONST);
|
||||
if (tmf == TMF_QUERY_TASK)
|
||||
scb->ssp_tmf.index = cpu_to_le16(index);
|
||||
|
||||
res = asd_enqueue_internal(ascb, asd_tmf_tasklet_complete,
|
||||
asd_tmf_timedout);
|
||||
if (res)
|
||||
goto out_err;
|
||||
wait_for_completion(&ascb->completion);
|
||||
res = (int) (unsigned long) ascb->uldd_task;
|
||||
|
||||
switch (res) {
|
||||
case TC_NO_ERROR + 0xFF00:
|
||||
res = TMF_RESP_FUNC_COMPLETE;
|
||||
break;
|
||||
case TF_NAK_RECV + 0xFF00:
|
||||
res = TMF_RESP_INVALID_FRAME;
|
||||
break;
|
||||
case TF_TMF_TASK_DONE + 0xFF00:
|
||||
res = TMF_RESP_FUNC_FAILED;
|
||||
break;
|
||||
case TF_TMF_NO_TAG + 0xFF00:
|
||||
case TF_TMF_TAG_FREE + 0xFF00: /* the tag is in the free list */
|
||||
case TF_TMF_NO_CONN_HANDLE + 0xFF00: /* no such device */
|
||||
res = TMF_RESP_FUNC_COMPLETE;
|
||||
break;
|
||||
case TF_TMF_NO_CTX + 0xFF00: /* not in seq, or proto != SSP */
|
||||
res = TMF_RESP_FUNC_ESUPP;
|
||||
break;
|
||||
default:
|
||||
ASD_DPRINTK("%s: converting result 0x%x to TMF_RESP_FUNC_FAILED\n",
|
||||
__FUNCTION__, res);
|
||||
res = TMF_RESP_FUNC_FAILED;
|
||||
break;
|
||||
}
|
||||
out_err:
|
||||
asd_ascb_free(ascb);
|
||||
return res;
|
||||
}
|
||||
|
||||
int asd_abort_task_set(struct domain_device *dev, u8 *lun)
|
||||
{
|
||||
int res = asd_initiate_ssp_tmf(dev, lun, TMF_ABORT_TASK_SET, 0);
|
||||
|
||||
if (res == TMF_RESP_FUNC_COMPLETE)
|
||||
asd_clear_nexus_I_T_L(dev, lun);
|
||||
return res;
|
||||
}
|
||||
|
||||
int asd_clear_aca(struct domain_device *dev, u8 *lun)
|
||||
{
|
||||
int res = asd_initiate_ssp_tmf(dev, lun, TMF_CLEAR_ACA, 0);
|
||||
|
||||
if (res == TMF_RESP_FUNC_COMPLETE)
|
||||
asd_clear_nexus_I_T_L(dev, lun);
|
||||
return res;
|
||||
}
|
||||
|
||||
int asd_clear_task_set(struct domain_device *dev, u8 *lun)
|
||||
{
|
||||
int res = asd_initiate_ssp_tmf(dev, lun, TMF_CLEAR_TASK_SET, 0);
|
||||
|
||||
if (res == TMF_RESP_FUNC_COMPLETE)
|
||||
asd_clear_nexus_I_T_L(dev, lun);
|
||||
return res;
|
||||
}
|
||||
|
||||
int asd_lu_reset(struct domain_device *dev, u8 *lun)
|
||||
{
|
||||
int res = asd_initiate_ssp_tmf(dev, lun, TMF_LU_RESET, 0);
|
||||
|
||||
if (res == TMF_RESP_FUNC_COMPLETE)
|
||||
asd_clear_nexus_I_T_L(dev, lun);
|
||||
return res;
|
||||
}
|
||||
|
||||
/**
|
||||
* asd_query_task -- send a QUERY TASK TMF to an I_T_L_Q nexus
|
||||
* task: pointer to sas_task struct of interest
|
||||
*
|
||||
* Returns: TMF_RESP_FUNC_COMPLETE if the task is not in the task set,
|
||||
* or TMF_RESP_FUNC_SUCC if the task is in the task set.
|
||||
*
|
||||
* Normally the management layer sets the task to aborted state,
|
||||
* and then calls query task and then abort task.
|
||||
*/
|
||||
int asd_query_task(struct sas_task *task)
|
||||
{
|
||||
struct asd_ascb *ascb = task->lldd_task;
|
||||
int index;
|
||||
|
||||
if (ascb) {
|
||||
index = ascb->tc_index;
|
||||
return asd_initiate_ssp_tmf(task->dev, task->ssp_task.LUN,
|
||||
TMF_QUERY_TASK, index);
|
||||
}
|
||||
return TMF_RESP_FUNC_COMPLETE;
|
||||
}
|
39
drivers/scsi/libsas/Kconfig
Normal file
39
drivers/scsi/libsas/Kconfig
Normal file
@ -0,0 +1,39 @@
|
||||
#
|
||||
# Kernel configuration file for the SAS Class
|
||||
#
|
||||
# Copyright (C) 2005 Adaptec, Inc. All rights reserved.
|
||||
# Copyright (C) 2005 Luben Tuikov <luben_tuikov@adaptec.com>
|
||||
#
|
||||
# This file is licensed under GPLv2.
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License as
|
||||
# published by the Free Software Foundation; version 2 of the
|
||||
# License.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
# General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
|
||||
# USA
|
||||
#
|
||||
|
||||
config SCSI_SAS_LIBSAS
|
||||
tristate "SAS Domain Transport Attributes"
|
||||
depends on SCSI
|
||||
select SCSI_SAS_ATTRS
|
||||
help
|
||||
This provides transport specific helpers for SAS drivers which
|
||||
use the domain device construct (like the aic94xxx).
|
||||
|
||||
config SCSI_SAS_LIBSAS_DEBUG
|
||||
bool "Compile the SAS Domain Transport Attributes in debug mode"
|
||||
default y
|
||||
depends on SCSI_SAS_LIBSAS
|
||||
help
|
||||
Compiles the SAS Layer in debug mode. In debug mode, the
|
||||
SAS Layer prints diagnostic and debug messages.
|
36
drivers/scsi/libsas/Makefile
Normal file
36
drivers/scsi/libsas/Makefile
Normal file
@ -0,0 +1,36 @@
|
||||
#
|
||||
# Kernel Makefile for the libsas helpers
|
||||
#
|
||||
# Copyright (C) 2005 Adaptec, Inc. All rights reserved.
|
||||
# Copyright (C) 2005 Luben Tuikov <luben_tuikov@adaptec.com>
|
||||
#
|
||||
# This file is licensed under GPLv2.
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License as
|
||||
# published by the Free Software Foundation; version 2 of the
|
||||
# License.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
# General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
|
||||
# USA
|
||||
|
||||
ifeq ($(CONFIG_SCSI_SAS_LIBSAS_DEBUG),y)
|
||||
EXTRA_CFLAGS += -DSAS_DEBUG
|
||||
endif
|
||||
|
||||
obj-$(CONFIG_SCSI_SAS_LIBSAS) += libsas.o
|
||||
libsas-y += sas_init.o \
|
||||
sas_phy.o \
|
||||
sas_port.o \
|
||||
sas_event.o \
|
||||
sas_dump.o \
|
||||
sas_discover.o \
|
||||
sas_expander.o \
|
||||
sas_scsi_host.o
|
749
drivers/scsi/libsas/sas_discover.c
Normal file
749
drivers/scsi/libsas/sas_discover.c
Normal file
@ -0,0 +1,749 @@
|
||||
/*
|
||||
* Serial Attached SCSI (SAS) Discover process
|
||||
*
|
||||
* Copyright (C) 2005 Adaptec, Inc. All rights reserved.
|
||||
* Copyright (C) 2005 Luben Tuikov <luben_tuikov@adaptec.com>
|
||||
*
|
||||
* This file is licensed under GPLv2.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation; either version 2 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/pci.h>
|
||||
#include <linux/scatterlist.h>
|
||||
#include <scsi/scsi_host.h>
|
||||
#include <scsi/scsi_eh.h>
|
||||
#include "sas_internal.h"
|
||||
|
||||
#include <scsi/scsi_transport.h>
|
||||
#include <scsi/scsi_transport_sas.h>
|
||||
#include "../scsi_sas_internal.h"
|
||||
|
||||
/* ---------- Basic task processing for discovery purposes ---------- */
|
||||
|
||||
void sas_init_dev(struct domain_device *dev)
|
||||
{
|
||||
INIT_LIST_HEAD(&dev->siblings);
|
||||
INIT_LIST_HEAD(&dev->dev_list_node);
|
||||
switch (dev->dev_type) {
|
||||
case SAS_END_DEV:
|
||||
break;
|
||||
case EDGE_DEV:
|
||||
case FANOUT_DEV:
|
||||
INIT_LIST_HEAD(&dev->ex_dev.children);
|
||||
break;
|
||||
case SATA_DEV:
|
||||
case SATA_PM:
|
||||
case SATA_PM_PORT:
|
||||
INIT_LIST_HEAD(&dev->sata_dev.children);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void sas_task_timedout(unsigned long _task)
|
||||
{
|
||||
struct sas_task *task = (void *) _task;
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&task->task_state_lock, flags);
|
||||
if (!(task->task_state_flags & SAS_TASK_STATE_DONE))
|
||||
task->task_state_flags |= SAS_TASK_STATE_ABORTED;
|
||||
spin_unlock_irqrestore(&task->task_state_lock, flags);
|
||||
|
||||
complete(&task->completion);
|
||||
}
|
||||
|
||||
static void sas_disc_task_done(struct sas_task *task)
|
||||
{
|
||||
if (!del_timer(&task->timer))
|
||||
return;
|
||||
complete(&task->completion);
|
||||
}
|
||||
|
||||
#define SAS_DEV_TIMEOUT 10
|
||||
|
||||
/**
|
||||
* sas_execute_task -- Basic task processing for discovery
|
||||
* @task: the task to be executed
|
||||
* @buffer: pointer to buffer to do I/O
|
||||
* @size: size of @buffer
|
||||
* @pci_dma_dir: PCI_DMA_...
|
||||
*/
|
||||
static int sas_execute_task(struct sas_task *task, void *buffer, int size,
|
||||
int pci_dma_dir)
|
||||
{
|
||||
int res = 0;
|
||||
struct scatterlist *scatter = NULL;
|
||||
struct task_status_struct *ts = &task->task_status;
|
||||
int num_scatter = 0;
|
||||
int retries = 0;
|
||||
struct sas_internal *i =
|
||||
to_sas_internal(task->dev->port->ha->core.shost->transportt);
|
||||
|
||||
if (pci_dma_dir != PCI_DMA_NONE) {
|
||||
scatter = kzalloc(sizeof(*scatter), GFP_KERNEL);
|
||||
if (!scatter)
|
||||
goto out;
|
||||
|
||||
sg_init_one(scatter, buffer, size);
|
||||
num_scatter = 1;
|
||||
}
|
||||
|
||||
task->task_proto = task->dev->tproto;
|
||||
task->scatter = scatter;
|
||||
task->num_scatter = num_scatter;
|
||||
task->total_xfer_len = size;
|
||||
task->data_dir = pci_dma_dir;
|
||||
task->task_done = sas_disc_task_done;
|
||||
|
||||
for (retries = 0; retries < 5; retries++) {
|
||||
task->task_state_flags = SAS_TASK_STATE_PENDING;
|
||||
init_completion(&task->completion);
|
||||
|
||||
task->timer.data = (unsigned long) task;
|
||||
task->timer.function = sas_task_timedout;
|
||||
task->timer.expires = jiffies + SAS_DEV_TIMEOUT*HZ;
|
||||
add_timer(&task->timer);
|
||||
|
||||
res = i->dft->lldd_execute_task(task, 1, GFP_KERNEL);
|
||||
if (res) {
|
||||
del_timer(&task->timer);
|
||||
SAS_DPRINTK("executing SAS discovery task failed:%d\n",
|
||||
res);
|
||||
goto ex_err;
|
||||
}
|
||||
wait_for_completion(&task->completion);
|
||||
res = -ETASK;
|
||||
if (task->task_state_flags & SAS_TASK_STATE_ABORTED) {
|
||||
int res2;
|
||||
SAS_DPRINTK("task aborted, flags:0x%x\n",
|
||||
task->task_state_flags);
|
||||
res2 = i->dft->lldd_abort_task(task);
|
||||
SAS_DPRINTK("came back from abort task\n");
|
||||
if (!(task->task_state_flags & SAS_TASK_STATE_DONE)) {
|
||||
if (res2 == TMF_RESP_FUNC_COMPLETE)
|
||||
continue; /* Retry the task */
|
||||
else
|
||||
goto ex_err;
|
||||
}
|
||||
}
|
||||
if (task->task_status.stat == SAM_BUSY ||
|
||||
task->task_status.stat == SAM_TASK_SET_FULL ||
|
||||
task->task_status.stat == SAS_QUEUE_FULL) {
|
||||
SAS_DPRINTK("task: q busy, sleeping...\n");
|
||||
schedule_timeout_interruptible(HZ);
|
||||
} else if (task->task_status.stat == SAM_CHECK_COND) {
|
||||
struct scsi_sense_hdr shdr;
|
||||
|
||||
if (!scsi_normalize_sense(ts->buf, ts->buf_valid_size,
|
||||
&shdr)) {
|
||||
SAS_DPRINTK("couldn't normalize sense\n");
|
||||
continue;
|
||||
}
|
||||
if ((shdr.sense_key == 6 && shdr.asc == 0x29) ||
|
||||
(shdr.sense_key == 2 && shdr.asc == 4 &&
|
||||
shdr.ascq == 1)) {
|
||||
SAS_DPRINTK("device %016llx LUN: %016llx "
|
||||
"powering up or not ready yet, "
|
||||
"sleeping...\n",
|
||||
SAS_ADDR(task->dev->sas_addr),
|
||||
SAS_ADDR(task->ssp_task.LUN));
|
||||
|
||||
schedule_timeout_interruptible(5*HZ);
|
||||
} else if (shdr.sense_key == 1) {
|
||||
res = 0;
|
||||
break;
|
||||
} else if (shdr.sense_key == 5) {
|
||||
break;
|
||||
} else {
|
||||
SAS_DPRINTK("dev %016llx LUN: %016llx "
|
||||
"sense key:0x%x ASC:0x%x ASCQ:0x%x"
|
||||
"\n",
|
||||
SAS_ADDR(task->dev->sas_addr),
|
||||
SAS_ADDR(task->ssp_task.LUN),
|
||||
shdr.sense_key,
|
||||
shdr.asc, shdr.ascq);
|
||||
}
|
||||
} else if (task->task_status.resp != SAS_TASK_COMPLETE ||
|
||||
task->task_status.stat != SAM_GOOD) {
|
||||
SAS_DPRINTK("task finished with resp:0x%x, "
|
||||
"stat:0x%x\n",
|
||||
task->task_status.resp,
|
||||
task->task_status.stat);
|
||||
goto ex_err;
|
||||
} else {
|
||||
res = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
ex_err:
|
||||
if (pci_dma_dir != PCI_DMA_NONE)
|
||||
kfree(scatter);
|
||||
out:
|
||||
return res;
|
||||
}
|
||||
|
||||
/* ---------- Domain device discovery ---------- */
|
||||
|
||||
/**
|
||||
* sas_get_port_device -- Discover devices which caused port creation
|
||||
* @port: pointer to struct sas_port of interest
|
||||
*
|
||||
* Devices directly attached to a HA port, have no parent. This is
|
||||
* how we know they are (domain) "root" devices. All other devices
|
||||
* do, and should have their "parent" pointer set appropriately as
|
||||
* soon as a child device is discovered.
|
||||
*/
|
||||
static int sas_get_port_device(struct asd_sas_port *port)
|
||||
{
|
||||
unsigned long flags;
|
||||
struct asd_sas_phy *phy;
|
||||
struct sas_rphy *rphy;
|
||||
struct domain_device *dev;
|
||||
|
||||
dev = kzalloc(sizeof(*dev), GFP_KERNEL);
|
||||
if (!dev)
|
||||
return -ENOMEM;
|
||||
|
||||
spin_lock_irqsave(&port->phy_list_lock, flags);
|
||||
if (list_empty(&port->phy_list)) {
|
||||
spin_unlock_irqrestore(&port->phy_list_lock, flags);
|
||||
kfree(dev);
|
||||
return -ENODEV;
|
||||
}
|
||||
phy = container_of(port->phy_list.next, struct asd_sas_phy, port_phy_el);
|
||||
spin_lock(&phy->frame_rcvd_lock);
|
||||
memcpy(dev->frame_rcvd, phy->frame_rcvd, min(sizeof(dev->frame_rcvd),
|
||||
(size_t)phy->frame_rcvd_size));
|
||||
spin_unlock(&phy->frame_rcvd_lock);
|
||||
spin_unlock_irqrestore(&port->phy_list_lock, flags);
|
||||
|
||||
if (dev->frame_rcvd[0] == 0x34 && port->oob_mode == SATA_OOB_MODE) {
|
||||
struct dev_to_host_fis *fis =
|
||||
(struct dev_to_host_fis *) dev->frame_rcvd;
|
||||
if (fis->interrupt_reason == 1 && fis->lbal == 1 &&
|
||||
fis->byte_count_low==0x69 && fis->byte_count_high == 0x96
|
||||
&& (fis->device & ~0x10) == 0)
|
||||
dev->dev_type = SATA_PM;
|
||||
else
|
||||
dev->dev_type = SATA_DEV;
|
||||
dev->tproto = SATA_PROTO;
|
||||
} else {
|
||||
struct sas_identify_frame *id =
|
||||
(struct sas_identify_frame *) dev->frame_rcvd;
|
||||
dev->dev_type = id->dev_type;
|
||||
dev->iproto = id->initiator_bits;
|
||||
dev->tproto = id->target_bits;
|
||||
}
|
||||
|
||||
sas_init_dev(dev);
|
||||
|
||||
switch (dev->dev_type) {
|
||||
case SAS_END_DEV:
|
||||
rphy = sas_end_device_alloc(port->port);
|
||||
break;
|
||||
case EDGE_DEV:
|
||||
rphy = sas_expander_alloc(port->port,
|
||||
SAS_EDGE_EXPANDER_DEVICE);
|
||||
break;
|
||||
case FANOUT_DEV:
|
||||
rphy = sas_expander_alloc(port->port,
|
||||
SAS_FANOUT_EXPANDER_DEVICE);
|
||||
break;
|
||||
case SATA_DEV:
|
||||
default:
|
||||
printk("ERROR: Unidentified device type %d\n", dev->dev_type);
|
||||
rphy = NULL;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!rphy) {
|
||||
kfree(dev);
|
||||
return -ENODEV;
|
||||
}
|
||||
rphy->identify.phy_identifier = phy->phy->identify.phy_identifier;
|
||||
memcpy(dev->sas_addr, port->attached_sas_addr, SAS_ADDR_SIZE);
|
||||
sas_fill_in_rphy(dev, rphy);
|
||||
sas_hash_addr(dev->hashed_sas_addr, dev->sas_addr);
|
||||
port->port_dev = dev;
|
||||
dev->port = port;
|
||||
dev->linkrate = port->linkrate;
|
||||
dev->min_linkrate = port->linkrate;
|
||||
dev->max_linkrate = port->linkrate;
|
||||
dev->pathways = port->num_phys;
|
||||
memset(port->disc.fanout_sas_addr, 0, SAS_ADDR_SIZE);
|
||||
memset(port->disc.eeds_a, 0, SAS_ADDR_SIZE);
|
||||
memset(port->disc.eeds_b, 0, SAS_ADDR_SIZE);
|
||||
port->disc.max_level = 0;
|
||||
|
||||
dev->rphy = rphy;
|
||||
spin_lock(&port->dev_list_lock);
|
||||
list_add_tail(&dev->dev_list_node, &port->dev_list);
|
||||
spin_unlock(&port->dev_list_lock);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ---------- Discover and Revalidate ---------- */
|
||||
|
||||
/* ---------- SATA ---------- */
|
||||
|
||||
static void sas_get_ata_command_set(struct domain_device *dev)
|
||||
{
|
||||
struct dev_to_host_fis *fis =
|
||||
(struct dev_to_host_fis *) dev->frame_rcvd;
|
||||
|
||||
if ((fis->sector_count == 1 && /* ATA */
|
||||
fis->lbal == 1 &&
|
||||
fis->lbam == 0 &&
|
||||
fis->lbah == 0 &&
|
||||
fis->device == 0)
|
||||
||
|
||||
(fis->sector_count == 0 && /* CE-ATA (mATA) */
|
||||
fis->lbal == 0 &&
|
||||
fis->lbam == 0xCE &&
|
||||
fis->lbah == 0xAA &&
|
||||
(fis->device & ~0x10) == 0))
|
||||
|
||||
dev->sata_dev.command_set = ATA_COMMAND_SET;
|
||||
|
||||
else if ((fis->interrupt_reason == 1 && /* ATAPI */
|
||||
fis->lbal == 1 &&
|
||||
fis->byte_count_low == 0x14 &&
|
||||
fis->byte_count_high == 0xEB &&
|
||||
(fis->device & ~0x10) == 0))
|
||||
|
||||
dev->sata_dev.command_set = ATAPI_COMMAND_SET;
|
||||
|
||||
else if ((fis->sector_count == 1 && /* SEMB */
|
||||
fis->lbal == 1 &&
|
||||
fis->lbam == 0x3C &&
|
||||
fis->lbah == 0xC3 &&
|
||||
fis->device == 0)
|
||||
||
|
||||
(fis->interrupt_reason == 1 && /* SATA PM */
|
||||
fis->lbal == 1 &&
|
||||
fis->byte_count_low == 0x69 &&
|
||||
fis->byte_count_high == 0x96 &&
|
||||
(fis->device & ~0x10) == 0))
|
||||
|
||||
/* Treat it as a superset? */
|
||||
dev->sata_dev.command_set = ATAPI_COMMAND_SET;
|
||||
}
|
||||
|
||||
/**
|
||||
* sas_issue_ata_cmd -- Basic SATA command processing for discovery
|
||||
* @dev: the device to send the command to
|
||||
* @command: the command register
|
||||
* @features: the features register
|
||||
* @buffer: pointer to buffer to do I/O
|
||||
* @size: size of @buffer
|
||||
* @pci_dma_dir: PCI_DMA_...
|
||||
*/
|
||||
static int sas_issue_ata_cmd(struct domain_device *dev, u8 command,
|
||||
u8 features, void *buffer, int size,
|
||||
int pci_dma_dir)
|
||||
{
|
||||
int res = 0;
|
||||
struct sas_task *task;
|
||||
struct dev_to_host_fis *d2h_fis = (struct dev_to_host_fis *)
|
||||
&dev->frame_rcvd[0];
|
||||
|
||||
res = -ENOMEM;
|
||||
task = sas_alloc_task(GFP_KERNEL);
|
||||
if (!task)
|
||||
goto out;
|
||||
|
||||
task->dev = dev;
|
||||
|
||||
task->ata_task.fis.command = command;
|
||||
task->ata_task.fis.features = features;
|
||||
task->ata_task.fis.device = d2h_fis->device;
|
||||
task->ata_task.retry_count = 1;
|
||||
|
||||
res = sas_execute_task(task, buffer, size, pci_dma_dir);
|
||||
|
||||
sas_free_task(task);
|
||||
out:
|
||||
return res;
|
||||
}
|
||||
|
||||
static void sas_sata_propagate_sas_addr(struct domain_device *dev)
|
||||
{
|
||||
unsigned long flags;
|
||||
struct asd_sas_port *port = dev->port;
|
||||
struct asd_sas_phy *phy;
|
||||
|
||||
BUG_ON(dev->parent);
|
||||
|
||||
memcpy(port->attached_sas_addr, dev->sas_addr, SAS_ADDR_SIZE);
|
||||
spin_lock_irqsave(&port->phy_list_lock, flags);
|
||||
list_for_each_entry(phy, &port->phy_list, port_phy_el)
|
||||
memcpy(phy->attached_sas_addr, dev->sas_addr, SAS_ADDR_SIZE);
|
||||
spin_unlock_irqrestore(&port->phy_list_lock, flags);
|
||||
}
|
||||
|
||||
#define ATA_IDENTIFY_DEV 0xEC
|
||||
#define ATA_IDENTIFY_PACKET_DEV 0xA1
|
||||
#define ATA_SET_FEATURES 0xEF
|
||||
#define ATA_FEATURE_PUP_STBY_SPIN_UP 0x07
|
||||
|
||||
/**
|
||||
* sas_discover_sata_dev -- discover a STP/SATA device (SATA_DEV)
|
||||
* @dev: STP/SATA device of interest (ATA/ATAPI)
|
||||
*
|
||||
* The LLDD has already been notified of this device, so that we can
|
||||
* send FISes to it. Here we try to get IDENTIFY DEVICE or IDENTIFY
|
||||
* PACKET DEVICE, if ATAPI device, so that the LLDD can fine-tune its
|
||||
* performance for this device.
|
||||
*/
|
||||
static int sas_discover_sata_dev(struct domain_device *dev)
|
||||
{
|
||||
int res;
|
||||
__le16 *identify_x;
|
||||
u8 command;
|
||||
|
||||
identify_x = kzalloc(512, GFP_KERNEL);
|
||||
if (!identify_x)
|
||||
return -ENOMEM;
|
||||
|
||||
if (dev->sata_dev.command_set == ATA_COMMAND_SET) {
|
||||
dev->sata_dev.identify_device = identify_x;
|
||||
command = ATA_IDENTIFY_DEV;
|
||||
} else {
|
||||
dev->sata_dev.identify_packet_device = identify_x;
|
||||
command = ATA_IDENTIFY_PACKET_DEV;
|
||||
}
|
||||
|
||||
res = sas_issue_ata_cmd(dev, command, 0, identify_x, 512,
|
||||
PCI_DMA_FROMDEVICE);
|
||||
if (res)
|
||||
goto out_err;
|
||||
|
||||
/* lives on the media? */
|
||||
if (le16_to_cpu(identify_x[0]) & 4) {
|
||||
/* incomplete response */
|
||||
SAS_DPRINTK("sending SET FEATURE/PUP_STBY_SPIN_UP to "
|
||||
"dev %llx\n", SAS_ADDR(dev->sas_addr));
|
||||
if (!le16_to_cpu(identify_x[83] & (1<<6)))
|
||||
goto cont1;
|
||||
res = sas_issue_ata_cmd(dev, ATA_SET_FEATURES,
|
||||
ATA_FEATURE_PUP_STBY_SPIN_UP,
|
||||
NULL, 0, PCI_DMA_NONE);
|
||||
if (res)
|
||||
goto cont1;
|
||||
|
||||
schedule_timeout_interruptible(5*HZ); /* More time? */
|
||||
res = sas_issue_ata_cmd(dev, command, 0, identify_x, 512,
|
||||
PCI_DMA_FROMDEVICE);
|
||||
if (res)
|
||||
goto out_err;
|
||||
}
|
||||
cont1:
|
||||
/* Get WWN */
|
||||
if (dev->port->oob_mode != SATA_OOB_MODE) {
|
||||
memcpy(dev->sas_addr, dev->sata_dev.rps_resp.rps.stp_sas_addr,
|
||||
SAS_ADDR_SIZE);
|
||||
} else if (dev->sata_dev.command_set == ATA_COMMAND_SET &&
|
||||
(le16_to_cpu(dev->sata_dev.identify_device[108]) & 0xF000)
|
||||
== 0x5000) {
|
||||
int i;
|
||||
|
||||
for (i = 0; i < 4; i++) {
|
||||
dev->sas_addr[2*i] =
|
||||
(le16_to_cpu(dev->sata_dev.identify_device[108+i]) & 0xFF00) >> 8;
|
||||
dev->sas_addr[2*i+1] =
|
||||
le16_to_cpu(dev->sata_dev.identify_device[108+i]) & 0x00FF;
|
||||
}
|
||||
}
|
||||
sas_hash_addr(dev->hashed_sas_addr, dev->sas_addr);
|
||||
if (!dev->parent)
|
||||
sas_sata_propagate_sas_addr(dev);
|
||||
|
||||
/* XXX Hint: register this SATA device with SATL.
|
||||
When this returns, dev->sata_dev->lu is alive and
|
||||
present.
|
||||
sas_satl_register_dev(dev);
|
||||
*/
|
||||
return 0;
|
||||
out_err:
|
||||
dev->sata_dev.identify_packet_device = NULL;
|
||||
dev->sata_dev.identify_device = NULL;
|
||||
kfree(identify_x);
|
||||
return res;
|
||||
}
|
||||
|
||||
static int sas_discover_sata_pm(struct domain_device *dev)
|
||||
{
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
int sas_notify_lldd_dev_found(struct domain_device *dev)
|
||||
{
|
||||
int res = 0;
|
||||
struct sas_ha_struct *sas_ha = dev->port->ha;
|
||||
struct Scsi_Host *shost = sas_ha->core.shost;
|
||||
struct sas_internal *i = to_sas_internal(shost->transportt);
|
||||
|
||||
if (i->dft->lldd_dev_found) {
|
||||
res = i->dft->lldd_dev_found(dev);
|
||||
if (res) {
|
||||
printk("sas: driver on pcidev %s cannot handle "
|
||||
"device %llx, error:%d\n",
|
||||
pci_name(sas_ha->pcidev),
|
||||
SAS_ADDR(dev->sas_addr), res);
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
void sas_notify_lldd_dev_gone(struct domain_device *dev)
|
||||
{
|
||||
struct sas_ha_struct *sas_ha = dev->port->ha;
|
||||
struct Scsi_Host *shost = sas_ha->core.shost;
|
||||
struct sas_internal *i = to_sas_internal(shost->transportt);
|
||||
|
||||
if (i->dft->lldd_dev_gone)
|
||||
i->dft->lldd_dev_gone(dev);
|
||||
}
|
||||
|
||||
/* ---------- Common/dispatchers ---------- */
|
||||
|
||||
/**
|
||||
* sas_discover_sata -- discover an STP/SATA domain device
|
||||
* @dev: pointer to struct domain_device of interest
|
||||
*
|
||||
* First we notify the LLDD of this device, so we can send frames to
|
||||
* it. Then depending on the type of device we call the appropriate
|
||||
* discover functions. Once device discover is done, we notify the
|
||||
* LLDD so that it can fine-tune its parameters for the device, by
|
||||
* removing it and then adding it. That is, the second time around,
|
||||
* the driver would have certain fields, that it is looking at, set.
|
||||
* Finally we initialize the kobj so that the device can be added to
|
||||
* the system at registration time. Devices directly attached to a HA
|
||||
* port, have no parents. All other devices do, and should have their
|
||||
* "parent" pointer set appropriately before calling this function.
|
||||
*/
|
||||
int sas_discover_sata(struct domain_device *dev)
|
||||
{
|
||||
int res;
|
||||
|
||||
sas_get_ata_command_set(dev);
|
||||
|
||||
res = sas_notify_lldd_dev_found(dev);
|
||||
if (res)
|
||||
return res;
|
||||
|
||||
switch (dev->dev_type) {
|
||||
case SATA_DEV:
|
||||
res = sas_discover_sata_dev(dev);
|
||||
break;
|
||||
case SATA_PM:
|
||||
res = sas_discover_sata_pm(dev);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
sas_notify_lldd_dev_gone(dev);
|
||||
if (!res) {
|
||||
sas_notify_lldd_dev_found(dev);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
/**
|
||||
* sas_discover_end_dev -- discover an end device (SSP, etc)
|
||||
* @end: pointer to domain device of interest
|
||||
*
|
||||
* See comment in sas_discover_sata().
|
||||
*/
|
||||
int sas_discover_end_dev(struct domain_device *dev)
|
||||
{
|
||||
int res;
|
||||
|
||||
res = sas_notify_lldd_dev_found(dev);
|
||||
if (res)
|
||||
return res;
|
||||
|
||||
res = sas_rphy_add(dev->rphy);
|
||||
if (res)
|
||||
goto out_err;
|
||||
|
||||
/* do this to get the end device port attributes which will have
|
||||
* been scanned in sas_rphy_add */
|
||||
sas_notify_lldd_dev_gone(dev);
|
||||
sas_notify_lldd_dev_found(dev);
|
||||
|
||||
return 0;
|
||||
|
||||
out_err:
|
||||
sas_notify_lldd_dev_gone(dev);
|
||||
return res;
|
||||
}
|
||||
|
||||
/* ---------- Device registration and unregistration ---------- */
|
||||
|
||||
static inline void sas_unregister_common_dev(struct domain_device *dev)
|
||||
{
|
||||
sas_notify_lldd_dev_gone(dev);
|
||||
if (!dev->parent)
|
||||
dev->port->port_dev = NULL;
|
||||
else
|
||||
list_del_init(&dev->siblings);
|
||||
list_del_init(&dev->dev_list_node);
|
||||
}
|
||||
|
||||
void sas_unregister_dev(struct domain_device *dev)
|
||||
{
|
||||
if (dev->rphy) {
|
||||
sas_remove_children(&dev->rphy->dev);
|
||||
sas_rphy_delete(dev->rphy);
|
||||
dev->rphy = NULL;
|
||||
}
|
||||
if (dev->dev_type == EDGE_DEV || dev->dev_type == FANOUT_DEV) {
|
||||
/* remove the phys and ports, everything else should be gone */
|
||||
kfree(dev->ex_dev.ex_phy);
|
||||
dev->ex_dev.ex_phy = NULL;
|
||||
}
|
||||
sas_unregister_common_dev(dev);
|
||||
}
|
||||
|
||||
void sas_unregister_domain_devices(struct asd_sas_port *port)
|
||||
{
|
||||
struct domain_device *dev, *n;
|
||||
|
||||
list_for_each_entry_safe_reverse(dev,n,&port->dev_list,dev_list_node)
|
||||
sas_unregister_dev(dev);
|
||||
|
||||
port->port->rphy = NULL;
|
||||
|
||||
}
|
||||
|
||||
/* ---------- Discovery and Revalidation ---------- */
|
||||
|
||||
/**
|
||||
* sas_discover_domain -- discover the domain
|
||||
* @port: port to the domain of interest
|
||||
*
|
||||
* NOTE: this process _must_ quit (return) as soon as any connection
|
||||
* errors are encountered. Connection recovery is done elsewhere.
|
||||
* Discover process only interrogates devices in order to discover the
|
||||
* domain.
|
||||
*/
|
||||
static void sas_discover_domain(void *data)
|
||||
{
|
||||
int error = 0;
|
||||
struct asd_sas_port *port = data;
|
||||
|
||||
sas_begin_event(DISCE_DISCOVER_DOMAIN, &port->disc.disc_event_lock,
|
||||
&port->disc.pending);
|
||||
|
||||
if (port->port_dev)
|
||||
return ;
|
||||
else {
|
||||
error = sas_get_port_device(port);
|
||||
if (error)
|
||||
return;
|
||||
}
|
||||
|
||||
SAS_DPRINTK("DOING DISCOVERY on port %d, pid:%d\n", port->id,
|
||||
current->pid);
|
||||
|
||||
switch (port->port_dev->dev_type) {
|
||||
case SAS_END_DEV:
|
||||
error = sas_discover_end_dev(port->port_dev);
|
||||
break;
|
||||
case EDGE_DEV:
|
||||
case FANOUT_DEV:
|
||||
error = sas_discover_root_expander(port->port_dev);
|
||||
break;
|
||||
case SATA_DEV:
|
||||
case SATA_PM:
|
||||
error = sas_discover_sata(port->port_dev);
|
||||
break;
|
||||
default:
|
||||
SAS_DPRINTK("unhandled device %d\n", port->port_dev->dev_type);
|
||||
break;
|
||||
}
|
||||
|
||||
if (error) {
|
||||
kfree(port->port_dev); /* not kobject_register-ed yet */
|
||||
port->port_dev = NULL;
|
||||
}
|
||||
|
||||
SAS_DPRINTK("DONE DISCOVERY on port %d, pid:%d, result:%d\n", port->id,
|
||||
current->pid, error);
|
||||
}
|
||||
|
||||
static void sas_revalidate_domain(void *data)
|
||||
{
|
||||
int res = 0;
|
||||
struct asd_sas_port *port = data;
|
||||
|
||||
sas_begin_event(DISCE_REVALIDATE_DOMAIN, &port->disc.disc_event_lock,
|
||||
&port->disc.pending);
|
||||
|
||||
SAS_DPRINTK("REVALIDATING DOMAIN on port %d, pid:%d\n", port->id,
|
||||
current->pid);
|
||||
if (port->port_dev)
|
||||
res = sas_ex_revalidate_domain(port->port_dev);
|
||||
|
||||
SAS_DPRINTK("done REVALIDATING DOMAIN on port %d, pid:%d, res 0x%x\n",
|
||||
port->id, current->pid, res);
|
||||
}
|
||||
|
||||
/* ---------- Events ---------- */
|
||||
|
||||
int sas_discover_event(struct asd_sas_port *port, enum discover_event ev)
|
||||
{
|
||||
struct sas_discovery *disc;
|
||||
|
||||
if (!port)
|
||||
return 0;
|
||||
disc = &port->disc;
|
||||
|
||||
BUG_ON(ev >= DISC_NUM_EVENTS);
|
||||
|
||||
sas_queue_event(ev, &disc->disc_event_lock, &disc->pending,
|
||||
&disc->disc_work[ev], port->ha->core.shost);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* sas_init_disc -- initialize the discovery struct in the port
|
||||
* @port: pointer to struct port
|
||||
*
|
||||
* Called when the ports are being initialized.
|
||||
*/
|
||||
void sas_init_disc(struct sas_discovery *disc, struct asd_sas_port *port)
|
||||
{
|
||||
int i;
|
||||
|
||||
static void (*sas_event_fns[DISC_NUM_EVENTS])(void *) = {
|
||||
[DISCE_DISCOVER_DOMAIN] = sas_discover_domain,
|
||||
[DISCE_REVALIDATE_DOMAIN] = sas_revalidate_domain,
|
||||
};
|
||||
|
||||
spin_lock_init(&disc->disc_event_lock);
|
||||
disc->pending = 0;
|
||||
for (i = 0; i < DISC_NUM_EVENTS; i++)
|
||||
INIT_WORK(&disc->disc_work[i], sas_event_fns[i], port);
|
||||
}
|
76
drivers/scsi/libsas/sas_dump.c
Normal file
76
drivers/scsi/libsas/sas_dump.c
Normal file
@ -0,0 +1,76 @@
|
||||
/*
|
||||
* Serial Attached SCSI (SAS) Dump/Debugging routines
|
||||
*
|
||||
* Copyright (C) 2005 Adaptec, Inc. All rights reserved.
|
||||
* Copyright (C) 2005 Luben Tuikov <luben_tuikov@adaptec.com>
|
||||
*
|
||||
* This file is licensed under GPLv2.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation; either version 2 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*
|
||||
*/
|
||||
|
||||
#include "sas_dump.h"
|
||||
|
||||
#ifdef SAS_DEBUG
|
||||
|
||||
static const char *sas_hae_str[] = {
|
||||
[0] = "HAE_RESET",
|
||||
};
|
||||
|
||||
static const char *sas_porte_str[] = {
|
||||
[0] = "PORTE_BYTES_DMAED",
|
||||
[1] = "PORTE_BROADCAST_RCVD",
|
||||
[2] = "PORTE_LINK_RESET_ERR",
|
||||
[3] = "PORTE_TIMER_EVENT",
|
||||
[4] = "PORTE_HARD_RESET",
|
||||
};
|
||||
|
||||
static const char *sas_phye_str[] = {
|
||||
[0] = "PHYE_LOSS_OF_SIGNAL",
|
||||
[1] = "PHYE_OOB_DONE",
|
||||
[2] = "PHYE_OOB_ERROR",
|
||||
[3] = "PHYE_SPINUP_HOLD",
|
||||
};
|
||||
|
||||
void sas_dprint_porte(int phyid, enum port_event pe)
|
||||
{
|
||||
SAS_DPRINTK("phy%d: port event: %s\n", phyid, sas_porte_str[pe]);
|
||||
}
|
||||
void sas_dprint_phye(int phyid, enum phy_event pe)
|
||||
{
|
||||
SAS_DPRINTK("phy%d: phy event: %s\n", phyid, sas_phye_str[pe]);
|
||||
}
|
||||
|
||||
void sas_dprint_hae(struct sas_ha_struct *sas_ha, enum ha_event he)
|
||||
{
|
||||
SAS_DPRINTK("ha %s: %s event\n", pci_name(sas_ha->pcidev),
|
||||
sas_hae_str[he]);
|
||||
}
|
||||
|
||||
void sas_dump_port(struct asd_sas_port *port)
|
||||
{
|
||||
SAS_DPRINTK("port%d: class:0x%x\n", port->id, port->class);
|
||||
SAS_DPRINTK("port%d: sas_addr:%llx\n", port->id,
|
||||
SAS_ADDR(port->sas_addr));
|
||||
SAS_DPRINTK("port%d: attached_sas_addr:%llx\n", port->id,
|
||||
SAS_ADDR(port->attached_sas_addr));
|
||||
SAS_DPRINTK("port%d: iproto:0x%x\n", port->id, port->iproto);
|
||||
SAS_DPRINTK("port%d: tproto:0x%x\n", port->id, port->tproto);
|
||||
SAS_DPRINTK("port%d: oob_mode:0x%x\n", port->id, port->oob_mode);
|
||||
SAS_DPRINTK("port%d: num_phys:%d\n", port->id, port->num_phys);
|
||||
}
|
||||
|
||||
#endif /* SAS_DEBUG */
|
42
drivers/scsi/libsas/sas_dump.h
Normal file
42
drivers/scsi/libsas/sas_dump.h
Normal file
@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Serial Attached SCSI (SAS) Dump/Debugging routines header file
|
||||
*
|
||||
* Copyright (C) 2005 Adaptec, Inc. All rights reserved.
|
||||
* Copyright (C) 2005 Luben Tuikov <luben_tuikov@adaptec.com>
|
||||
*
|
||||
* This file is licensed under GPLv2.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation; either version 2 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*
|
||||
*/
|
||||
|
||||
#include "sas_internal.h"
|
||||
|
||||
#ifdef SAS_DEBUG
|
||||
|
||||
void sas_dprint_porte(int phyid, enum port_event pe);
|
||||
void sas_dprint_phye(int phyid, enum phy_event pe);
|
||||
void sas_dprint_hae(struct sas_ha_struct *sas_ha, enum ha_event he);
|
||||
void sas_dump_port(struct asd_sas_port *port);
|
||||
|
||||
#else /* SAS_DEBUG */
|
||||
|
||||
static inline void sas_dprint_porte(int phyid, enum port_event pe) { }
|
||||
static inline void sas_dprint_phye(int phyid, enum phy_event pe) { }
|
||||
static inline void sas_dprint_hae(struct sas_ha_struct *sas_ha,
|
||||
enum ha_event he) { }
|
||||
static inline void sas_dump_port(struct asd_sas_port *port) { }
|
||||
|
||||
#endif /* SAS_DEBUG */
|
75
drivers/scsi/libsas/sas_event.c
Normal file
75
drivers/scsi/libsas/sas_event.c
Normal file
@ -0,0 +1,75 @@
|
||||
/*
|
||||
* Serial Attached SCSI (SAS) Event processing
|
||||
*
|
||||
* Copyright (C) 2005 Adaptec, Inc. All rights reserved.
|
||||
* Copyright (C) 2005 Luben Tuikov <luben_tuikov@adaptec.com>
|
||||
*
|
||||
* This file is licensed under GPLv2.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation; either version 2 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*
|
||||
*/
|
||||
|
||||
#include <scsi/scsi_host.h>
|
||||
#include "sas_internal.h"
|
||||
#include "sas_dump.h"
|
||||
|
||||
static void notify_ha_event(struct sas_ha_struct *sas_ha, enum ha_event event)
|
||||
{
|
||||
BUG_ON(event >= HA_NUM_EVENTS);
|
||||
|
||||
sas_queue_event(event, &sas_ha->event_lock, &sas_ha->pending,
|
||||
&sas_ha->ha_events[event], sas_ha->core.shost);
|
||||
}
|
||||
|
||||
static void notify_port_event(struct asd_sas_phy *phy, enum port_event event)
|
||||
{
|
||||
struct sas_ha_struct *ha = phy->ha;
|
||||
|
||||
BUG_ON(event >= PORT_NUM_EVENTS);
|
||||
|
||||
sas_queue_event(event, &ha->event_lock, &phy->port_events_pending,
|
||||
&phy->port_events[event], ha->core.shost);
|
||||
}
|
||||
|
||||
static void notify_phy_event(struct asd_sas_phy *phy, enum phy_event event)
|
||||
{
|
||||
struct sas_ha_struct *ha = phy->ha;
|
||||
|
||||
BUG_ON(event >= PHY_NUM_EVENTS);
|
||||
|
||||
sas_queue_event(event, &ha->event_lock, &phy->phy_events_pending,
|
||||
&phy->phy_events[event], ha->core.shost);
|
||||
}
|
||||
|
||||
int sas_init_events(struct sas_ha_struct *sas_ha)
|
||||
{
|
||||
static void (*sas_ha_event_fns[HA_NUM_EVENTS])(void *) = {
|
||||
[HAE_RESET] = sas_hae_reset,
|
||||
};
|
||||
|
||||
int i;
|
||||
|
||||
spin_lock_init(&sas_ha->event_lock);
|
||||
|
||||
for (i = 0; i < HA_NUM_EVENTS; i++)
|
||||
INIT_WORK(&sas_ha->ha_events[i], sas_ha_event_fns[i], sas_ha);
|
||||
|
||||
sas_ha->notify_ha_event = notify_ha_event;
|
||||
sas_ha->notify_port_event = notify_port_event;
|
||||
sas_ha->notify_phy_event = notify_phy_event;
|
||||
|
||||
return 0;
|
||||
}
|
1862
drivers/scsi/libsas/sas_expander.c
Normal file
1862
drivers/scsi/libsas/sas_expander.c
Normal file
File diff suppressed because it is too large
Load Diff
227
drivers/scsi/libsas/sas_init.c
Normal file
227
drivers/scsi/libsas/sas_init.c
Normal file
@ -0,0 +1,227 @@
|
||||
/*
|
||||
* Serial Attached SCSI (SAS) Transport Layer initialization
|
||||
*
|
||||
* Copyright (C) 2005 Adaptec, Inc. All rights reserved.
|
||||
* Copyright (C) 2005 Luben Tuikov <luben_tuikov@adaptec.com>
|
||||
*
|
||||
* This file is licensed under GPLv2.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation; either version 2 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
|
||||
* USA
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/spinlock.h>
|
||||
#include <scsi/scsi_host.h>
|
||||
#include <scsi/scsi_device.h>
|
||||
#include <scsi/scsi_transport.h>
|
||||
#include <scsi/scsi_transport_sas.h>
|
||||
|
||||
#include "sas_internal.h"
|
||||
|
||||
#include "../scsi_sas_internal.h"
|
||||
|
||||
kmem_cache_t *sas_task_cache;
|
||||
|
||||
/*------------ SAS addr hash -----------*/
|
||||
void sas_hash_addr(u8 *hashed, const u8 *sas_addr)
|
||||
{
|
||||
const u32 poly = 0x00DB2777;
|
||||
u32 r = 0;
|
||||
int i;
|
||||
|
||||
for (i = 0; i < 8; i++) {
|
||||
int b;
|
||||
for (b = 7; b >= 0; b--) {
|
||||
r <<= 1;
|
||||
if ((1 << b) & sas_addr[i]) {
|
||||
if (!(r & 0x01000000))
|
||||
r ^= poly;
|
||||
} else if (r & 0x01000000)
|
||||
r ^= poly;
|
||||
}
|
||||
}
|
||||
|
||||
hashed[0] = (r >> 16) & 0xFF;
|
||||
hashed[1] = (r >> 8) & 0xFF ;
|
||||
hashed[2] = r & 0xFF;
|
||||
}
|
||||
|
||||
|
||||
/* ---------- HA events ---------- */
|
||||
|
||||
void sas_hae_reset(void *data)
|
||||
{
|
||||
struct sas_ha_struct *ha = data;
|
||||
|
||||
sas_begin_event(HAE_RESET, &ha->event_lock,
|
||||
&ha->pending);
|
||||
}
|
||||
|
||||
int sas_register_ha(struct sas_ha_struct *sas_ha)
|
||||
{
|
||||
int error = 0;
|
||||
|
||||
spin_lock_init(&sas_ha->phy_port_lock);
|
||||
sas_hash_addr(sas_ha->hashed_sas_addr, sas_ha->sas_addr);
|
||||
|
||||
if (sas_ha->lldd_queue_size == 0)
|
||||
sas_ha->lldd_queue_size = 1;
|
||||
else if (sas_ha->lldd_queue_size == -1)
|
||||
sas_ha->lldd_queue_size = 128; /* Sanity */
|
||||
|
||||
error = sas_register_phys(sas_ha);
|
||||
if (error) {
|
||||
printk(KERN_NOTICE "couldn't register sas phys:%d\n", error);
|
||||
return error;
|
||||
}
|
||||
|
||||
error = sas_register_ports(sas_ha);
|
||||
if (error) {
|
||||
printk(KERN_NOTICE "couldn't register sas ports:%d\n", error);
|
||||
goto Undo_phys;
|
||||
}
|
||||
|
||||
error = sas_init_events(sas_ha);
|
||||
if (error) {
|
||||
printk(KERN_NOTICE "couldn't start event thread:%d\n", error);
|
||||
goto Undo_ports;
|
||||
}
|
||||
|
||||
if (sas_ha->lldd_max_execute_num > 1) {
|
||||
error = sas_init_queue(sas_ha);
|
||||
if (error) {
|
||||
printk(KERN_NOTICE "couldn't start queue thread:%d, "
|
||||
"running in direct mode\n", error);
|
||||
sas_ha->lldd_max_execute_num = 1;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
Undo_ports:
|
||||
sas_unregister_ports(sas_ha);
|
||||
Undo_phys:
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
int sas_unregister_ha(struct sas_ha_struct *sas_ha)
|
||||
{
|
||||
if (sas_ha->lldd_max_execute_num > 1) {
|
||||
sas_shutdown_queue(sas_ha);
|
||||
}
|
||||
|
||||
sas_unregister_ports(sas_ha);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int sas_get_linkerrors(struct sas_phy *phy)
|
||||
{
|
||||
if (scsi_is_sas_phy_local(phy))
|
||||
/* FIXME: we have no local phy stats
|
||||
* gathering at this time */
|
||||
return -EINVAL;
|
||||
|
||||
return sas_smp_get_phy_events(phy);
|
||||
}
|
||||
|
||||
static int sas_phy_reset(struct sas_phy *phy, int hard_reset)
|
||||
{
|
||||
int ret;
|
||||
enum phy_func reset_type;
|
||||
|
||||
if (hard_reset)
|
||||
reset_type = PHY_FUNC_HARD_RESET;
|
||||
else
|
||||
reset_type = PHY_FUNC_LINK_RESET;
|
||||
|
||||
if (scsi_is_sas_phy_local(phy)) {
|
||||
struct Scsi_Host *shost = dev_to_shost(phy->dev.parent);
|
||||
struct sas_ha_struct *sas_ha = SHOST_TO_SAS_HA(shost);
|
||||
struct asd_sas_phy *asd_phy = sas_ha->sas_phy[phy->number];
|
||||
struct sas_internal *i =
|
||||
to_sas_internal(sas_ha->core.shost->transportt);
|
||||
|
||||
ret = i->dft->lldd_control_phy(asd_phy, reset_type);
|
||||
} else {
|
||||
struct sas_rphy *rphy = dev_to_rphy(phy->dev.parent);
|
||||
struct domain_device *ddev = sas_find_dev_by_rphy(rphy);
|
||||
ret = sas_smp_phy_control(ddev, phy->number, reset_type);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static struct sas_function_template sft = {
|
||||
.phy_reset = sas_phy_reset,
|
||||
.get_linkerrors = sas_get_linkerrors,
|
||||
};
|
||||
|
||||
struct scsi_transport_template *
|
||||
sas_domain_attach_transport(struct sas_domain_function_template *dft)
|
||||
{
|
||||
struct scsi_transport_template *stt = sas_attach_transport(&sft);
|
||||
struct sas_internal *i;
|
||||
|
||||
if (!stt)
|
||||
return stt;
|
||||
|
||||
i = to_sas_internal(stt);
|
||||
i->dft = dft;
|
||||
stt->create_work_queue = 1;
|
||||
stt->eh_timed_out = sas_scsi_timed_out;
|
||||
stt->eh_strategy_handler = sas_scsi_recover_host;
|
||||
|
||||
return stt;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(sas_domain_attach_transport);
|
||||
|
||||
|
||||
void sas_domain_release_transport(struct scsi_transport_template *stt)
|
||||
{
|
||||
sas_release_transport(stt);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(sas_domain_release_transport);
|
||||
|
||||
/* ---------- SAS Class register/unregister ---------- */
|
||||
|
||||
static int __init sas_class_init(void)
|
||||
{
|
||||
sas_task_cache = kmem_cache_create("sas_task", sizeof(struct sas_task),
|
||||
0, SLAB_HWCACHE_ALIGN, NULL, NULL);
|
||||
if (!sas_task_cache)
|
||||
return -ENOMEM;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void __exit sas_class_exit(void)
|
||||
{
|
||||
kmem_cache_destroy(sas_task_cache);
|
||||
}
|
||||
|
||||
MODULE_AUTHOR("Luben Tuikov <luben_tuikov@adaptec.com>");
|
||||
MODULE_DESCRIPTION("SAS Transport Layer");
|
||||
MODULE_LICENSE("GPL v2");
|
||||
|
||||
module_init(sas_class_init);
|
||||
module_exit(sas_class_exit);
|
||||
|
||||
EXPORT_SYMBOL_GPL(sas_register_ha);
|
||||
EXPORT_SYMBOL_GPL(sas_unregister_ha);
|
146
drivers/scsi/libsas/sas_internal.h
Normal file
146
drivers/scsi/libsas/sas_internal.h
Normal file
@ -0,0 +1,146 @@
|
||||
/*
|
||||
* Serial Attached SCSI (SAS) class internal header file
|
||||
*
|
||||
* Copyright (C) 2005 Adaptec, Inc. All rights reserved.
|
||||
* Copyright (C) 2005 Luben Tuikov <luben_tuikov@adaptec.com>
|
||||
*
|
||||
* This file is licensed under GPLv2.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation; either version 2 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
|
||||
* USA
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef _SAS_INTERNAL_H_
|
||||
#define _SAS_INTERNAL_H_
|
||||
|
||||
#include <scsi/scsi.h>
|
||||
#include <scsi/scsi_host.h>
|
||||
#include <scsi/scsi_transport_sas.h>
|
||||
#include <scsi/libsas.h>
|
||||
|
||||
#define sas_printk(fmt, ...) printk(KERN_NOTICE "sas: " fmt, ## __VA_ARGS__)
|
||||
|
||||
#ifdef SAS_DEBUG
|
||||
#define SAS_DPRINTK(fmt, ...) printk(KERN_NOTICE "sas: " fmt, ## __VA_ARGS__)
|
||||
#else
|
||||
#define SAS_DPRINTK(fmt, ...)
|
||||
#endif
|
||||
|
||||
void sas_scsi_recover_host(struct Scsi_Host *shost);
|
||||
|
||||
int sas_show_class(enum sas_class class, char *buf);
|
||||
int sas_show_proto(enum sas_proto proto, char *buf);
|
||||
int sas_show_linkrate(enum sas_phy_linkrate linkrate, char *buf);
|
||||
int sas_show_oob_mode(enum sas_oob_mode oob_mode, char *buf);
|
||||
|
||||
int sas_register_phys(struct sas_ha_struct *sas_ha);
|
||||
void sas_unregister_phys(struct sas_ha_struct *sas_ha);
|
||||
|
||||
int sas_register_ports(struct sas_ha_struct *sas_ha);
|
||||
void sas_unregister_ports(struct sas_ha_struct *sas_ha);
|
||||
|
||||
enum scsi_eh_timer_return sas_scsi_timed_out(struct scsi_cmnd *);
|
||||
|
||||
int sas_init_queue(struct sas_ha_struct *sas_ha);
|
||||
int sas_init_events(struct sas_ha_struct *sas_ha);
|
||||
void sas_shutdown_queue(struct sas_ha_struct *sas_ha);
|
||||
|
||||
void sas_deform_port(struct asd_sas_phy *phy);
|
||||
|
||||
void sas_porte_bytes_dmaed(void *);
|
||||
void sas_porte_broadcast_rcvd(void *);
|
||||
void sas_porte_link_reset_err(void *);
|
||||
void sas_porte_timer_event(void *);
|
||||
void sas_porte_hard_reset(void *);
|
||||
|
||||
int sas_notify_lldd_dev_found(struct domain_device *);
|
||||
void sas_notify_lldd_dev_gone(struct domain_device *);
|
||||
|
||||
int sas_smp_phy_control(struct domain_device *dev, int phy_id,
|
||||
enum phy_func phy_func);
|
||||
int sas_smp_get_phy_events(struct sas_phy *phy);
|
||||
|
||||
struct domain_device *sas_find_dev_by_rphy(struct sas_rphy *rphy);
|
||||
|
||||
void sas_hae_reset(void *);
|
||||
|
||||
static inline void sas_queue_event(int event, spinlock_t *lock,
|
||||
unsigned long *pending,
|
||||
struct work_struct *work,
|
||||
struct Scsi_Host *shost)
|
||||
{
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(lock, flags);
|
||||
if (test_bit(event, pending)) {
|
||||
spin_unlock_irqrestore(lock, flags);
|
||||
return;
|
||||
}
|
||||
__set_bit(event, pending);
|
||||
spin_unlock_irqrestore(lock, flags);
|
||||
scsi_queue_work(shost, work);
|
||||
}
|
||||
|
||||
static inline void sas_begin_event(int event, spinlock_t *lock,
|
||||
unsigned long *pending)
|
||||
{
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(lock, flags);
|
||||
__clear_bit(event, pending);
|
||||
spin_unlock_irqrestore(lock, flags);
|
||||
}
|
||||
|
||||
static inline void sas_fill_in_rphy(struct domain_device *dev,
|
||||
struct sas_rphy *rphy)
|
||||
{
|
||||
rphy->identify.sas_address = SAS_ADDR(dev->sas_addr);
|
||||
rphy->identify.initiator_port_protocols = dev->iproto;
|
||||
rphy->identify.target_port_protocols = dev->tproto;
|
||||
switch (dev->dev_type) {
|
||||
case SATA_DEV:
|
||||
/* FIXME: need sata device type */
|
||||
case SAS_END_DEV:
|
||||
rphy->identify.device_type = SAS_END_DEVICE;
|
||||
break;
|
||||
case EDGE_DEV:
|
||||
rphy->identify.device_type = SAS_EDGE_EXPANDER_DEVICE;
|
||||
break;
|
||||
case FANOUT_DEV:
|
||||
rphy->identify.device_type = SAS_FANOUT_EXPANDER_DEVICE;
|
||||
break;
|
||||
default:
|
||||
rphy->identify.device_type = SAS_PHY_UNUSED;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static inline void sas_add_parent_port(struct domain_device *dev, int phy_id)
|
||||
{
|
||||
struct expander_device *ex = &dev->ex_dev;
|
||||
struct ex_phy *ex_phy = &ex->ex_phy[phy_id];
|
||||
|
||||
if (!ex->parent_port) {
|
||||
ex->parent_port = sas_port_alloc(&dev->rphy->dev, phy_id);
|
||||
/* FIXME: error handling */
|
||||
BUG_ON(!ex->parent_port);
|
||||
BUG_ON(sas_port_add(ex->parent_port));
|
||||
sas_port_mark_backlink(ex->parent_port);
|
||||
}
|
||||
sas_port_add_phy(ex->parent_port, ex_phy->phy);
|
||||
}
|
||||
|
||||
#endif /* _SAS_INTERNAL_H_ */
|
157
drivers/scsi/libsas/sas_phy.c
Normal file
157
drivers/scsi/libsas/sas_phy.c
Normal file
@ -0,0 +1,157 @@
|
||||
/*
|
||||
* Serial Attached SCSI (SAS) Phy class
|
||||
*
|
||||
* Copyright (C) 2005 Adaptec, Inc. All rights reserved.
|
||||
* Copyright (C) 2005 Luben Tuikov <luben_tuikov@adaptec.com>
|
||||
*
|
||||
* This file is licensed under GPLv2.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation; either version 2 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*
|
||||
*/
|
||||
|
||||
#include "sas_internal.h"
|
||||
#include <scsi/scsi_host.h>
|
||||
#include <scsi/scsi_transport.h>
|
||||
#include <scsi/scsi_transport_sas.h>
|
||||
#include "../scsi_sas_internal.h"
|
||||
|
||||
/* ---------- Phy events ---------- */
|
||||
|
||||
static void sas_phye_loss_of_signal(void *data)
|
||||
{
|
||||
struct asd_sas_phy *phy = data;
|
||||
|
||||
sas_begin_event(PHYE_LOSS_OF_SIGNAL, &phy->ha->event_lock,
|
||||
&phy->phy_events_pending);
|
||||
phy->error = 0;
|
||||
sas_deform_port(phy);
|
||||
}
|
||||
|
||||
static void sas_phye_oob_done(void *data)
|
||||
{
|
||||
struct asd_sas_phy *phy = data;
|
||||
|
||||
sas_begin_event(PHYE_OOB_DONE, &phy->ha->event_lock,
|
||||
&phy->phy_events_pending);
|
||||
phy->error = 0;
|
||||
}
|
||||
|
||||
static void sas_phye_oob_error(void *data)
|
||||
{
|
||||
struct asd_sas_phy *phy = data;
|
||||
struct sas_ha_struct *sas_ha = phy->ha;
|
||||
struct asd_sas_port *port = phy->port;
|
||||
struct sas_internal *i =
|
||||
to_sas_internal(sas_ha->core.shost->transportt);
|
||||
|
||||
sas_begin_event(PHYE_OOB_ERROR, &phy->ha->event_lock,
|
||||
&phy->phy_events_pending);
|
||||
|
||||
sas_deform_port(phy);
|
||||
|
||||
if (!port && phy->enabled && i->dft->lldd_control_phy) {
|
||||
phy->error++;
|
||||
switch (phy->error) {
|
||||
case 1:
|
||||
case 2:
|
||||
i->dft->lldd_control_phy(phy, PHY_FUNC_HARD_RESET);
|
||||
break;
|
||||
case 3:
|
||||
default:
|
||||
phy->error = 0;
|
||||
phy->enabled = 0;
|
||||
i->dft->lldd_control_phy(phy, PHY_FUNC_DISABLE);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void sas_phye_spinup_hold(void *data)
|
||||
{
|
||||
struct asd_sas_phy *phy = data;
|
||||
struct sas_ha_struct *sas_ha = phy->ha;
|
||||
struct sas_internal *i =
|
||||
to_sas_internal(sas_ha->core.shost->transportt);
|
||||
|
||||
sas_begin_event(PHYE_SPINUP_HOLD, &phy->ha->event_lock,
|
||||
&phy->phy_events_pending);
|
||||
|
||||
phy->error = 0;
|
||||
i->dft->lldd_control_phy(phy, PHY_FUNC_RELEASE_SPINUP_HOLD);
|
||||
}
|
||||
|
||||
/* ---------- Phy class registration ---------- */
|
||||
|
||||
int sas_register_phys(struct sas_ha_struct *sas_ha)
|
||||
{
|
||||
int i;
|
||||
|
||||
static void (*sas_phy_event_fns[PHY_NUM_EVENTS])(void *) = {
|
||||
[PHYE_LOSS_OF_SIGNAL] = sas_phye_loss_of_signal,
|
||||
[PHYE_OOB_DONE] = sas_phye_oob_done,
|
||||
[PHYE_OOB_ERROR] = sas_phye_oob_error,
|
||||
[PHYE_SPINUP_HOLD] = sas_phye_spinup_hold,
|
||||
};
|
||||
|
||||
static void (*sas_port_event_fns[PORT_NUM_EVENTS])(void *) = {
|
||||
[PORTE_BYTES_DMAED] = sas_porte_bytes_dmaed,
|
||||
[PORTE_BROADCAST_RCVD] = sas_porte_broadcast_rcvd,
|
||||
[PORTE_LINK_RESET_ERR] = sas_porte_link_reset_err,
|
||||
[PORTE_TIMER_EVENT] = sas_porte_timer_event,
|
||||
[PORTE_HARD_RESET] = sas_porte_hard_reset,
|
||||
};
|
||||
|
||||
/* Now register the phys. */
|
||||
for (i = 0; i < sas_ha->num_phys; i++) {
|
||||
int k;
|
||||
struct asd_sas_phy *phy = sas_ha->sas_phy[i];
|
||||
|
||||
phy->error = 0;
|
||||
INIT_LIST_HEAD(&phy->port_phy_el);
|
||||
for (k = 0; k < PORT_NUM_EVENTS; k++)
|
||||
INIT_WORK(&phy->port_events[k], sas_port_event_fns[k],
|
||||
phy);
|
||||
|
||||
for (k = 0; k < PHY_NUM_EVENTS; k++)
|
||||
INIT_WORK(&phy->phy_events[k], sas_phy_event_fns[k],
|
||||
phy);
|
||||
phy->port = NULL;
|
||||
phy->ha = sas_ha;
|
||||
spin_lock_init(&phy->frame_rcvd_lock);
|
||||
spin_lock_init(&phy->sas_prim_lock);
|
||||
phy->frame_rcvd_size = 0;
|
||||
|
||||
phy->phy = sas_phy_alloc(&sas_ha->core.shost->shost_gendev,
|
||||
i);
|
||||
if (!phy->phy)
|
||||
return -ENOMEM;
|
||||
|
||||
phy->phy->identify.initiator_port_protocols =
|
||||
phy->iproto;
|
||||
phy->phy->identify.target_port_protocols = phy->tproto;
|
||||
phy->phy->identify.sas_address = SAS_ADDR(sas_ha->sas_addr);
|
||||
phy->phy->identify.phy_identifier = i;
|
||||
phy->phy->minimum_linkrate_hw = SAS_LINK_RATE_1_5_GBPS;
|
||||
phy->phy->maximum_linkrate_hw = SAS_LINK_RATE_3_0_GBPS;
|
||||
phy->phy->minimum_linkrate = SAS_LINK_RATE_1_5_GBPS;
|
||||
phy->phy->maximum_linkrate = SAS_LINK_RATE_3_0_GBPS;
|
||||
phy->phy->negotiated_linkrate = SAS_LINK_RATE_UNKNOWN;
|
||||
|
||||
sas_phy_add(phy->phy);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
279
drivers/scsi/libsas/sas_port.c
Normal file
279
drivers/scsi/libsas/sas_port.c
Normal file
@ -0,0 +1,279 @@
|
||||
/*
|
||||
* Serial Attached SCSI (SAS) Port class
|
||||
*
|
||||
* Copyright (C) 2005 Adaptec, Inc. All rights reserved.
|
||||
* Copyright (C) 2005 Luben Tuikov <luben_tuikov@adaptec.com>
|
||||
*
|
||||
* This file is licensed under GPLv2.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation; either version 2 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*
|
||||
*/
|
||||
|
||||
#include "sas_internal.h"
|
||||
|
||||
#include <scsi/scsi_transport.h>
|
||||
#include <scsi/scsi_transport_sas.h>
|
||||
#include "../scsi_sas_internal.h"
|
||||
|
||||
/**
|
||||
* sas_form_port -- add this phy to a port
|
||||
* @phy: the phy of interest
|
||||
*
|
||||
* This function adds this phy to an existing port, thus creating a wide
|
||||
* port, or it creates a port and adds the phy to the port.
|
||||
*/
|
||||
static void sas_form_port(struct asd_sas_phy *phy)
|
||||
{
|
||||
int i;
|
||||
struct sas_ha_struct *sas_ha = phy->ha;
|
||||
struct asd_sas_port *port = phy->port;
|
||||
struct sas_internal *si =
|
||||
to_sas_internal(sas_ha->core.shost->transportt);
|
||||
|
||||
if (port) {
|
||||
if (memcmp(port->attached_sas_addr, phy->attached_sas_addr,
|
||||
SAS_ADDR_SIZE) == 0)
|
||||
sas_deform_port(phy);
|
||||
else {
|
||||
SAS_DPRINTK("%s: phy%d belongs to port%d already(%d)!\n",
|
||||
__FUNCTION__, phy->id, phy->port->id,
|
||||
phy->port->num_phys);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/* find a port */
|
||||
spin_lock(&sas_ha->phy_port_lock);
|
||||
for (i = 0; i < sas_ha->num_phys; i++) {
|
||||
port = sas_ha->sas_port[i];
|
||||
spin_lock(&port->phy_list_lock);
|
||||
if (*(u64 *) port->sas_addr &&
|
||||
memcmp(port->attached_sas_addr,
|
||||
phy->attached_sas_addr, SAS_ADDR_SIZE) == 0 &&
|
||||
port->num_phys > 0) {
|
||||
/* wide port */
|
||||
SAS_DPRINTK("phy%d matched wide port%d\n", phy->id,
|
||||
port->id);
|
||||
break;
|
||||
} else if (*(u64 *) port->sas_addr == 0 && port->num_phys==0) {
|
||||
memcpy(port->sas_addr, phy->sas_addr, SAS_ADDR_SIZE);
|
||||
break;
|
||||
}
|
||||
spin_unlock(&port->phy_list_lock);
|
||||
}
|
||||
|
||||
if (i >= sas_ha->num_phys) {
|
||||
printk(KERN_NOTICE "%s: couldn't find a free port, bug?\n",
|
||||
__FUNCTION__);
|
||||
spin_unlock(&sas_ha->phy_port_lock);
|
||||
return;
|
||||
}
|
||||
|
||||
/* add the phy to the port */
|
||||
list_add_tail(&phy->port_phy_el, &port->phy_list);
|
||||
phy->port = port;
|
||||
port->num_phys++;
|
||||
port->phy_mask |= (1U << phy->id);
|
||||
|
||||
if (!port->phy)
|
||||
port->phy = phy->phy;
|
||||
|
||||
SAS_DPRINTK("phy%d added to port%d, phy_mask:0x%x\n", phy->id,
|
||||
port->id, port->phy_mask);
|
||||
|
||||
if (*(u64 *)port->attached_sas_addr == 0) {
|
||||
port->class = phy->class;
|
||||
memcpy(port->attached_sas_addr, phy->attached_sas_addr,
|
||||
SAS_ADDR_SIZE);
|
||||
port->iproto = phy->iproto;
|
||||
port->tproto = phy->tproto;
|
||||
port->oob_mode = phy->oob_mode;
|
||||
port->linkrate = phy->linkrate;
|
||||
} else
|
||||
port->linkrate = max(port->linkrate, phy->linkrate);
|
||||
spin_unlock(&port->phy_list_lock);
|
||||
spin_unlock(&sas_ha->phy_port_lock);
|
||||
|
||||
if (!port->port) {
|
||||
port->port = sas_port_alloc(phy->phy->dev.parent, port->id);
|
||||
BUG_ON(!port->port);
|
||||
sas_port_add(port->port);
|
||||
}
|
||||
sas_port_add_phy(port->port, phy->phy);
|
||||
|
||||
if (port->port_dev)
|
||||
port->port_dev->pathways = port->num_phys;
|
||||
|
||||
/* Tell the LLDD about this port formation. */
|
||||
if (si->dft->lldd_port_formed)
|
||||
si->dft->lldd_port_formed(phy);
|
||||
|
||||
sas_discover_event(phy->port, DISCE_DISCOVER_DOMAIN);
|
||||
}
|
||||
|
||||
/**
|
||||
* sas_deform_port -- remove this phy from the port it belongs to
|
||||
* @phy: the phy of interest
|
||||
*
|
||||
* This is called when the physical link to the other phy has been
|
||||
* lost (on this phy), in Event thread context. We cannot delay here.
|
||||
*/
|
||||
void sas_deform_port(struct asd_sas_phy *phy)
|
||||
{
|
||||
struct sas_ha_struct *sas_ha = phy->ha;
|
||||
struct asd_sas_port *port = phy->port;
|
||||
struct sas_internal *si =
|
||||
to_sas_internal(sas_ha->core.shost->transportt);
|
||||
|
||||
if (!port)
|
||||
return; /* done by a phy event */
|
||||
|
||||
if (port->port_dev)
|
||||
port->port_dev->pathways--;
|
||||
|
||||
if (port->num_phys == 1) {
|
||||
sas_unregister_domain_devices(port);
|
||||
sas_port_delete(port->port);
|
||||
port->port = NULL;
|
||||
} else
|
||||
sas_port_delete_phy(port->port, phy->phy);
|
||||
|
||||
|
||||
if (si->dft->lldd_port_deformed)
|
||||
si->dft->lldd_port_deformed(phy);
|
||||
|
||||
spin_lock(&sas_ha->phy_port_lock);
|
||||
spin_lock(&port->phy_list_lock);
|
||||
|
||||
list_del_init(&phy->port_phy_el);
|
||||
phy->port = NULL;
|
||||
port->num_phys--;
|
||||
port->phy_mask &= ~(1U << phy->id);
|
||||
|
||||
if (port->num_phys == 0) {
|
||||
INIT_LIST_HEAD(&port->phy_list);
|
||||
memset(port->sas_addr, 0, SAS_ADDR_SIZE);
|
||||
memset(port->attached_sas_addr, 0, SAS_ADDR_SIZE);
|
||||
port->class = 0;
|
||||
port->iproto = 0;
|
||||
port->tproto = 0;
|
||||
port->oob_mode = 0;
|
||||
port->phy_mask = 0;
|
||||
}
|
||||
spin_unlock(&port->phy_list_lock);
|
||||
spin_unlock(&sas_ha->phy_port_lock);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/* ---------- SAS port events ---------- */
|
||||
|
||||
void sas_porte_bytes_dmaed(void *data)
|
||||
{
|
||||
struct asd_sas_phy *phy = data;
|
||||
|
||||
sas_begin_event(PORTE_BYTES_DMAED, &phy->ha->event_lock,
|
||||
&phy->port_events_pending);
|
||||
|
||||
sas_form_port(phy);
|
||||
}
|
||||
|
||||
void sas_porte_broadcast_rcvd(void *data)
|
||||
{
|
||||
unsigned long flags;
|
||||
u32 prim;
|
||||
struct asd_sas_phy *phy = data;
|
||||
|
||||
sas_begin_event(PORTE_BROADCAST_RCVD, &phy->ha->event_lock,
|
||||
&phy->port_events_pending);
|
||||
|
||||
spin_lock_irqsave(&phy->sas_prim_lock, flags);
|
||||
prim = phy->sas_prim;
|
||||
spin_unlock_irqrestore(&phy->sas_prim_lock, flags);
|
||||
|
||||
SAS_DPRINTK("broadcast received: %d\n", prim);
|
||||
sas_discover_event(phy->port, DISCE_REVALIDATE_DOMAIN);
|
||||
}
|
||||
|
||||
void sas_porte_link_reset_err(void *data)
|
||||
{
|
||||
struct asd_sas_phy *phy = data;
|
||||
|
||||
sas_begin_event(PORTE_LINK_RESET_ERR, &phy->ha->event_lock,
|
||||
&phy->port_events_pending);
|
||||
|
||||
sas_deform_port(phy);
|
||||
}
|
||||
|
||||
void sas_porte_timer_event(void *data)
|
||||
{
|
||||
struct asd_sas_phy *phy = data;
|
||||
|
||||
sas_begin_event(PORTE_TIMER_EVENT, &phy->ha->event_lock,
|
||||
&phy->port_events_pending);
|
||||
|
||||
sas_deform_port(phy);
|
||||
}
|
||||
|
||||
void sas_porte_hard_reset(void *data)
|
||||
{
|
||||
struct asd_sas_phy *phy = data;
|
||||
|
||||
sas_begin_event(PORTE_HARD_RESET, &phy->ha->event_lock,
|
||||
&phy->port_events_pending);
|
||||
|
||||
sas_deform_port(phy);
|
||||
}
|
||||
|
||||
/* ---------- SAS port registration ---------- */
|
||||
|
||||
static void sas_init_port(struct asd_sas_port *port,
|
||||
struct sas_ha_struct *sas_ha, int i)
|
||||
{
|
||||
port->id = i;
|
||||
INIT_LIST_HEAD(&port->dev_list);
|
||||
spin_lock_init(&port->phy_list_lock);
|
||||
INIT_LIST_HEAD(&port->phy_list);
|
||||
port->num_phys = 0;
|
||||
port->phy_mask = 0;
|
||||
port->ha = sas_ha;
|
||||
|
||||
spin_lock_init(&port->dev_list_lock);
|
||||
}
|
||||
|
||||
int sas_register_ports(struct sas_ha_struct *sas_ha)
|
||||
{
|
||||
int i;
|
||||
|
||||
/* initialize the ports and discovery */
|
||||
for (i = 0; i < sas_ha->num_phys; i++) {
|
||||
struct asd_sas_port *port = sas_ha->sas_port[i];
|
||||
|
||||
sas_init_port(port, sas_ha, i);
|
||||
sas_init_disc(&port->disc, port);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void sas_unregister_ports(struct sas_ha_struct *sas_ha)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < sas_ha->num_phys; i++)
|
||||
if (sas_ha->sas_phy[i]->port)
|
||||
sas_deform_port(sas_ha->sas_phy[i]);
|
||||
|
||||
}
|
786
drivers/scsi/libsas/sas_scsi_host.c
Normal file
786
drivers/scsi/libsas/sas_scsi_host.c
Normal file
@ -0,0 +1,786 @@
|
||||
/*
|
||||
* Serial Attached SCSI (SAS) class SCSI Host glue.
|
||||
*
|
||||
* Copyright (C) 2005 Adaptec, Inc. All rights reserved.
|
||||
* Copyright (C) 2005 Luben Tuikov <luben_tuikov@adaptec.com>
|
||||
*
|
||||
* This file is licensed under GPLv2.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation; either version 2 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
|
||||
* USA
|
||||
*
|
||||
*/
|
||||
|
||||
#include "sas_internal.h"
|
||||
|
||||
#include <scsi/scsi_host.h>
|
||||
#include <scsi/scsi_device.h>
|
||||
#include <scsi/scsi_tcq.h>
|
||||
#include <scsi/scsi.h>
|
||||
#include <scsi/scsi_transport.h>
|
||||
#include <scsi/scsi_transport_sas.h>
|
||||
#include "../scsi_sas_internal.h"
|
||||
|
||||
#include <linux/err.h>
|
||||
#include <linux/blkdev.h>
|
||||
#include <linux/scatterlist.h>
|
||||
|
||||
/* ---------- SCSI Host glue ---------- */
|
||||
|
||||
#define TO_SAS_TASK(_scsi_cmd) ((void *)(_scsi_cmd)->host_scribble)
|
||||
#define ASSIGN_SAS_TASK(_sc, _t) do { (_sc)->host_scribble = (void *) _t; } while (0)
|
||||
|
||||
static void sas_scsi_task_done(struct sas_task *task)
|
||||
{
|
||||
struct task_status_struct *ts = &task->task_status;
|
||||
struct scsi_cmnd *sc = task->uldd_task;
|
||||
unsigned ts_flags = task->task_state_flags;
|
||||
int hs = 0, stat = 0;
|
||||
|
||||
if (unlikely(!sc)) {
|
||||
SAS_DPRINTK("task_done called with non existing SCSI cmnd!\n");
|
||||
list_del_init(&task->list);
|
||||
sas_free_task(task);
|
||||
return;
|
||||
}
|
||||
|
||||
if (ts->resp == SAS_TASK_UNDELIVERED) {
|
||||
/* transport error */
|
||||
hs = DID_NO_CONNECT;
|
||||
} else { /* ts->resp == SAS_TASK_COMPLETE */
|
||||
/* task delivered, what happened afterwards? */
|
||||
switch (ts->stat) {
|
||||
case SAS_DEV_NO_RESPONSE:
|
||||
case SAS_INTERRUPTED:
|
||||
case SAS_PHY_DOWN:
|
||||
case SAS_NAK_R_ERR:
|
||||
case SAS_OPEN_TO:
|
||||
hs = DID_NO_CONNECT;
|
||||
break;
|
||||
case SAS_DATA_UNDERRUN:
|
||||
sc->resid = ts->residual;
|
||||
if (sc->request_bufflen - sc->resid < sc->underflow)
|
||||
hs = DID_ERROR;
|
||||
break;
|
||||
case SAS_DATA_OVERRUN:
|
||||
hs = DID_ERROR;
|
||||
break;
|
||||
case SAS_QUEUE_FULL:
|
||||
hs = DID_SOFT_ERROR; /* retry */
|
||||
break;
|
||||
case SAS_DEVICE_UNKNOWN:
|
||||
hs = DID_BAD_TARGET;
|
||||
break;
|
||||
case SAS_SG_ERR:
|
||||
hs = DID_PARITY;
|
||||
break;
|
||||
case SAS_OPEN_REJECT:
|
||||
if (ts->open_rej_reason == SAS_OREJ_RSVD_RETRY)
|
||||
hs = DID_SOFT_ERROR; /* retry */
|
||||
else
|
||||
hs = DID_ERROR;
|
||||
break;
|
||||
case SAS_PROTO_RESPONSE:
|
||||
SAS_DPRINTK("LLDD:%s sent SAS_PROTO_RESP for an SSP "
|
||||
"task; please report this\n",
|
||||
task->dev->port->ha->sas_ha_name);
|
||||
break;
|
||||
case SAS_ABORTED_TASK:
|
||||
hs = DID_ABORT;
|
||||
break;
|
||||
case SAM_CHECK_COND:
|
||||
memcpy(sc->sense_buffer, ts->buf,
|
||||
max(SCSI_SENSE_BUFFERSIZE, ts->buf_valid_size));
|
||||
stat = SAM_CHECK_COND;
|
||||
break;
|
||||
default:
|
||||
stat = ts->stat;
|
||||
break;
|
||||
}
|
||||
}
|
||||
ASSIGN_SAS_TASK(sc, NULL);
|
||||
sc->result = (hs << 16) | stat;
|
||||
list_del_init(&task->list);
|
||||
sas_free_task(task);
|
||||
/* This is very ugly but this is how SCSI Core works. */
|
||||
if (ts_flags & SAS_TASK_STATE_ABORTED)
|
||||
scsi_finish_command(sc);
|
||||
else
|
||||
sc->scsi_done(sc);
|
||||
}
|
||||
|
||||
static enum task_attribute sas_scsi_get_task_attr(struct scsi_cmnd *cmd)
|
||||
{
|
||||
enum task_attribute ta = TASK_ATTR_SIMPLE;
|
||||
if (cmd->request && blk_rq_tagged(cmd->request)) {
|
||||
if (cmd->device->ordered_tags &&
|
||||
(cmd->request->flags & REQ_HARDBARRIER))
|
||||
ta = TASK_ATTR_HOQ;
|
||||
}
|
||||
return ta;
|
||||
}
|
||||
|
||||
static struct sas_task *sas_create_task(struct scsi_cmnd *cmd,
|
||||
struct domain_device *dev,
|
||||
unsigned long gfp_flags)
|
||||
{
|
||||
struct sas_task *task = sas_alloc_task(gfp_flags);
|
||||
struct scsi_lun lun;
|
||||
|
||||
if (!task)
|
||||
return NULL;
|
||||
|
||||
*(u32 *)cmd->sense_buffer = 0;
|
||||
task->uldd_task = cmd;
|
||||
ASSIGN_SAS_TASK(cmd, task);
|
||||
|
||||
task->dev = dev;
|
||||
task->task_proto = task->dev->tproto; /* BUG_ON(!SSP) */
|
||||
|
||||
task->ssp_task.retry_count = 1;
|
||||
int_to_scsilun(cmd->device->lun, &lun);
|
||||
memcpy(task->ssp_task.LUN, &lun.scsi_lun, 8);
|
||||
task->ssp_task.task_attr = sas_scsi_get_task_attr(cmd);
|
||||
memcpy(task->ssp_task.cdb, cmd->cmnd, 16);
|
||||
|
||||
task->scatter = cmd->request_buffer;
|
||||
task->num_scatter = cmd->use_sg;
|
||||
task->total_xfer_len = cmd->request_bufflen;
|
||||
task->data_dir = cmd->sc_data_direction;
|
||||
|
||||
task->task_done = sas_scsi_task_done;
|
||||
|
||||
return task;
|
||||
}
|
||||
|
||||
static int sas_queue_up(struct sas_task *task)
|
||||
{
|
||||
struct sas_ha_struct *sas_ha = task->dev->port->ha;
|
||||
struct scsi_core *core = &sas_ha->core;
|
||||
unsigned long flags;
|
||||
LIST_HEAD(list);
|
||||
|
||||
spin_lock_irqsave(&core->task_queue_lock, flags);
|
||||
if (sas_ha->lldd_queue_size < core->task_queue_size + 1) {
|
||||
spin_unlock_irqrestore(&core->task_queue_lock, flags);
|
||||
return -SAS_QUEUE_FULL;
|
||||
}
|
||||
list_add_tail(&task->list, &core->task_queue);
|
||||
core->task_queue_size += 1;
|
||||
spin_unlock_irqrestore(&core->task_queue_lock, flags);
|
||||
up(&core->queue_thread_sema);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* sas_queuecommand -- Enqueue a command for processing
|
||||
* @parameters: See SCSI Core documentation
|
||||
*
|
||||
* Note: XXX: Remove the host unlock/lock pair when SCSI Core can
|
||||
* call us without holding an IRQ spinlock...
|
||||
*/
|
||||
int sas_queuecommand(struct scsi_cmnd *cmd,
|
||||
void (*scsi_done)(struct scsi_cmnd *))
|
||||
{
|
||||
int res = 0;
|
||||
struct domain_device *dev = cmd_to_domain_dev(cmd);
|
||||
struct Scsi_Host *host = cmd->device->host;
|
||||
struct sas_internal *i = to_sas_internal(host->transportt);
|
||||
|
||||
spin_unlock_irq(host->host_lock);
|
||||
|
||||
{
|
||||
struct sas_ha_struct *sas_ha = dev->port->ha;
|
||||
struct sas_task *task;
|
||||
|
||||
res = -ENOMEM;
|
||||
task = sas_create_task(cmd, dev, GFP_ATOMIC);
|
||||
if (!task)
|
||||
goto out;
|
||||
|
||||
cmd->scsi_done = scsi_done;
|
||||
/* Queue up, Direct Mode or Task Collector Mode. */
|
||||
if (sas_ha->lldd_max_execute_num < 2)
|
||||
res = i->dft->lldd_execute_task(task, 1, GFP_ATOMIC);
|
||||
else
|
||||
res = sas_queue_up(task);
|
||||
|
||||
/* Examine */
|
||||
if (res) {
|
||||
SAS_DPRINTK("lldd_execute_task returned: %d\n", res);
|
||||
ASSIGN_SAS_TASK(cmd, NULL);
|
||||
sas_free_task(task);
|
||||
if (res == -SAS_QUEUE_FULL) {
|
||||
cmd->result = DID_SOFT_ERROR << 16; /* retry */
|
||||
res = 0;
|
||||
scsi_done(cmd);
|
||||
}
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
out:
|
||||
spin_lock_irq(host->host_lock);
|
||||
return res;
|
||||
}
|
||||
|
||||
static void sas_scsi_clear_queue_lu(struct list_head *error_q, struct scsi_cmnd *my_cmd)
|
||||
{
|
||||
struct scsi_cmnd *cmd, *n;
|
||||
|
||||
list_for_each_entry_safe(cmd, n, error_q, eh_entry) {
|
||||
if (cmd == my_cmd)
|
||||
list_del_init(&cmd->eh_entry);
|
||||
}
|
||||
}
|
||||
|
||||
static void sas_scsi_clear_queue_I_T(struct list_head *error_q,
|
||||
struct domain_device *dev)
|
||||
{
|
||||
struct scsi_cmnd *cmd, *n;
|
||||
|
||||
list_for_each_entry_safe(cmd, n, error_q, eh_entry) {
|
||||
struct domain_device *x = cmd_to_domain_dev(cmd);
|
||||
|
||||
if (x == dev)
|
||||
list_del_init(&cmd->eh_entry);
|
||||
}
|
||||
}
|
||||
|
||||
static void sas_scsi_clear_queue_port(struct list_head *error_q,
|
||||
struct asd_sas_port *port)
|
||||
{
|
||||
struct scsi_cmnd *cmd, *n;
|
||||
|
||||
list_for_each_entry_safe(cmd, n, error_q, eh_entry) {
|
||||
struct domain_device *dev = cmd_to_domain_dev(cmd);
|
||||
struct asd_sas_port *x = dev->port;
|
||||
|
||||
if (x == port)
|
||||
list_del_init(&cmd->eh_entry);
|
||||
}
|
||||
}
|
||||
|
||||
enum task_disposition {
|
||||
TASK_IS_DONE,
|
||||
TASK_IS_ABORTED,
|
||||
TASK_IS_AT_LU,
|
||||
TASK_IS_NOT_AT_LU,
|
||||
};
|
||||
|
||||
static enum task_disposition sas_scsi_find_task(struct sas_task *task)
|
||||
{
|
||||
struct sas_ha_struct *ha = task->dev->port->ha;
|
||||
unsigned long flags;
|
||||
int i, res;
|
||||
struct sas_internal *si =
|
||||
to_sas_internal(task->dev->port->ha->core.shost->transportt);
|
||||
|
||||
if (ha->lldd_max_execute_num > 1) {
|
||||
struct scsi_core *core = &ha->core;
|
||||
struct sas_task *t, *n;
|
||||
|
||||
spin_lock_irqsave(&core->task_queue_lock, flags);
|
||||
list_for_each_entry_safe(t, n, &core->task_queue, list) {
|
||||
if (task == t) {
|
||||
list_del_init(&t->list);
|
||||
spin_unlock_irqrestore(&core->task_queue_lock,
|
||||
flags);
|
||||
SAS_DPRINTK("%s: task 0x%p aborted from "
|
||||
"task_queue\n",
|
||||
__FUNCTION__, task);
|
||||
return TASK_IS_ABORTED;
|
||||
}
|
||||
}
|
||||
spin_unlock_irqrestore(&core->task_queue_lock, flags);
|
||||
}
|
||||
|
||||
for (i = 0; i < 5; i++) {
|
||||
SAS_DPRINTK("%s: aborting task 0x%p\n", __FUNCTION__, task);
|
||||
res = si->dft->lldd_abort_task(task);
|
||||
|
||||
spin_lock_irqsave(&task->task_state_lock, flags);
|
||||
if (task->task_state_flags & SAS_TASK_STATE_DONE) {
|
||||
spin_unlock_irqrestore(&task->task_state_lock, flags);
|
||||
SAS_DPRINTK("%s: task 0x%p is done\n", __FUNCTION__,
|
||||
task);
|
||||
return TASK_IS_DONE;
|
||||
}
|
||||
spin_unlock_irqrestore(&task->task_state_lock, flags);
|
||||
|
||||
if (res == TMF_RESP_FUNC_COMPLETE) {
|
||||
SAS_DPRINTK("%s: task 0x%p is aborted\n",
|
||||
__FUNCTION__, task);
|
||||
return TASK_IS_ABORTED;
|
||||
} else if (si->dft->lldd_query_task) {
|
||||
SAS_DPRINTK("%s: querying task 0x%p\n",
|
||||
__FUNCTION__, task);
|
||||
res = si->dft->lldd_query_task(task);
|
||||
if (res == TMF_RESP_FUNC_SUCC) {
|
||||
SAS_DPRINTK("%s: task 0x%p at LU\n",
|
||||
__FUNCTION__, task);
|
||||
return TASK_IS_AT_LU;
|
||||
} else if (res == TMF_RESP_FUNC_COMPLETE) {
|
||||
SAS_DPRINTK("%s: task 0x%p not at LU\n",
|
||||
__FUNCTION__, task);
|
||||
return TASK_IS_NOT_AT_LU;
|
||||
}
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
static int sas_recover_lu(struct domain_device *dev, struct scsi_cmnd *cmd)
|
||||
{
|
||||
int res = TMF_RESP_FUNC_FAILED;
|
||||
struct scsi_lun lun;
|
||||
struct sas_internal *i =
|
||||
to_sas_internal(dev->port->ha->core.shost->transportt);
|
||||
|
||||
int_to_scsilun(cmd->device->lun, &lun);
|
||||
|
||||
SAS_DPRINTK("eh: device %llx LUN %x has the task\n",
|
||||
SAS_ADDR(dev->sas_addr),
|
||||
cmd->device->lun);
|
||||
|
||||
if (i->dft->lldd_abort_task_set)
|
||||
res = i->dft->lldd_abort_task_set(dev, lun.scsi_lun);
|
||||
|
||||
if (res == TMF_RESP_FUNC_FAILED) {
|
||||
if (i->dft->lldd_clear_task_set)
|
||||
res = i->dft->lldd_clear_task_set(dev, lun.scsi_lun);
|
||||
}
|
||||
|
||||
if (res == TMF_RESP_FUNC_FAILED) {
|
||||
if (i->dft->lldd_lu_reset)
|
||||
res = i->dft->lldd_lu_reset(dev, lun.scsi_lun);
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
static int sas_recover_I_T(struct domain_device *dev)
|
||||
{
|
||||
int res = TMF_RESP_FUNC_FAILED;
|
||||
struct sas_internal *i =
|
||||
to_sas_internal(dev->port->ha->core.shost->transportt);
|
||||
|
||||
SAS_DPRINTK("I_T nexus reset for dev %016llx\n",
|
||||
SAS_ADDR(dev->sas_addr));
|
||||
|
||||
if (i->dft->lldd_I_T_nexus_reset)
|
||||
res = i->dft->lldd_I_T_nexus_reset(dev);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
void sas_scsi_recover_host(struct Scsi_Host *shost)
|
||||
{
|
||||
struct sas_ha_struct *ha = SHOST_TO_SAS_HA(shost);
|
||||
unsigned long flags;
|
||||
LIST_HEAD(error_q);
|
||||
struct scsi_cmnd *cmd, *n;
|
||||
enum task_disposition res = TASK_IS_DONE;
|
||||
int tmf_resp;
|
||||
struct sas_internal *i = to_sas_internal(shost->transportt);
|
||||
|
||||
spin_lock_irqsave(shost->host_lock, flags);
|
||||
list_splice_init(&shost->eh_cmd_q, &error_q);
|
||||
spin_unlock_irqrestore(shost->host_lock, flags);
|
||||
|
||||
SAS_DPRINTK("Enter %s\n", __FUNCTION__);
|
||||
|
||||
/* All tasks on this list were marked SAS_TASK_STATE_ABORTED
|
||||
* by sas_scsi_timed_out() callback.
|
||||
*/
|
||||
Again:
|
||||
SAS_DPRINTK("going over list...\n");
|
||||
list_for_each_entry_safe(cmd, n, &error_q, eh_entry) {
|
||||
struct sas_task *task = TO_SAS_TASK(cmd);
|
||||
|
||||
SAS_DPRINTK("trying to find task 0x%p\n", task);
|
||||
list_del_init(&cmd->eh_entry);
|
||||
res = sas_scsi_find_task(task);
|
||||
|
||||
cmd->eh_eflags = 0;
|
||||
shost->host_failed--;
|
||||
|
||||
switch (res) {
|
||||
case TASK_IS_DONE:
|
||||
SAS_DPRINTK("%s: task 0x%p is done\n", __FUNCTION__,
|
||||
task);
|
||||
task->task_done(task);
|
||||
continue;
|
||||
case TASK_IS_ABORTED:
|
||||
SAS_DPRINTK("%s: task 0x%p is aborted\n",
|
||||
__FUNCTION__, task);
|
||||
task->task_done(task);
|
||||
continue;
|
||||
case TASK_IS_AT_LU:
|
||||
SAS_DPRINTK("task 0x%p is at LU: lu recover\n", task);
|
||||
tmf_resp = sas_recover_lu(task->dev, cmd);
|
||||
if (tmf_resp == TMF_RESP_FUNC_COMPLETE) {
|
||||
SAS_DPRINTK("dev %016llx LU %x is "
|
||||
"recovered\n",
|
||||
SAS_ADDR(task->dev),
|
||||
cmd->device->lun);
|
||||
task->task_done(task);
|
||||
sas_scsi_clear_queue_lu(&error_q, cmd);
|
||||
goto Again;
|
||||
}
|
||||
/* fallthrough */
|
||||
case TASK_IS_NOT_AT_LU:
|
||||
SAS_DPRINTK("task 0x%p is not at LU: I_T recover\n",
|
||||
task);
|
||||
tmf_resp = sas_recover_I_T(task->dev);
|
||||
if (tmf_resp == TMF_RESP_FUNC_COMPLETE) {
|
||||
SAS_DPRINTK("I_T %016llx recovered\n",
|
||||
SAS_ADDR(task->dev->sas_addr));
|
||||
task->task_done(task);
|
||||
sas_scsi_clear_queue_I_T(&error_q, task->dev);
|
||||
goto Again;
|
||||
}
|
||||
/* Hammer time :-) */
|
||||
if (i->dft->lldd_clear_nexus_port) {
|
||||
struct asd_sas_port *port = task->dev->port;
|
||||
SAS_DPRINTK("clearing nexus for port:%d\n",
|
||||
port->id);
|
||||
res = i->dft->lldd_clear_nexus_port(port);
|
||||
if (res == TMF_RESP_FUNC_COMPLETE) {
|
||||
SAS_DPRINTK("clear nexus port:%d "
|
||||
"succeeded\n", port->id);
|
||||
task->task_done(task);
|
||||
sas_scsi_clear_queue_port(&error_q,
|
||||
port);
|
||||
goto Again;
|
||||
}
|
||||
}
|
||||
if (i->dft->lldd_clear_nexus_ha) {
|
||||
SAS_DPRINTK("clear nexus ha\n");
|
||||
res = i->dft->lldd_clear_nexus_ha(ha);
|
||||
if (res == TMF_RESP_FUNC_COMPLETE) {
|
||||
SAS_DPRINTK("clear nexus ha "
|
||||
"succeeded\n");
|
||||
task->task_done(task);
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
/* If we are here -- this means that no amount
|
||||
* of effort could recover from errors. Quite
|
||||
* possibly the HA just disappeared.
|
||||
*/
|
||||
SAS_DPRINTK("error from device %llx, LUN %x "
|
||||
"couldn't be recovered in any way\n",
|
||||
SAS_ADDR(task->dev->sas_addr),
|
||||
cmd->device->lun);
|
||||
|
||||
task->task_done(task);
|
||||
goto clear_q;
|
||||
}
|
||||
}
|
||||
out:
|
||||
SAS_DPRINTK("--- Exit %s\n", __FUNCTION__);
|
||||
return;
|
||||
clear_q:
|
||||
SAS_DPRINTK("--- Exit %s -- clear_q\n", __FUNCTION__);
|
||||
list_for_each_entry_safe(cmd, n, &error_q, eh_entry) {
|
||||
struct sas_task *task = TO_SAS_TASK(cmd);
|
||||
list_del_init(&cmd->eh_entry);
|
||||
task->task_done(task);
|
||||
}
|
||||
}
|
||||
|
||||
enum scsi_eh_timer_return sas_scsi_timed_out(struct scsi_cmnd *cmd)
|
||||
{
|
||||
struct sas_task *task = TO_SAS_TASK(cmd);
|
||||
unsigned long flags;
|
||||
|
||||
if (!task) {
|
||||
SAS_DPRINTK("command 0x%p, task 0x%p, timed out: EH_HANDLED\n",
|
||||
cmd, task);
|
||||
return EH_HANDLED;
|
||||
}
|
||||
|
||||
spin_lock_irqsave(&task->task_state_lock, flags);
|
||||
if (task->task_state_flags & SAS_TASK_STATE_DONE) {
|
||||
spin_unlock_irqrestore(&task->task_state_lock, flags);
|
||||
SAS_DPRINTK("command 0x%p, task 0x%p, timed out: EH_HANDLED\n",
|
||||
cmd, task);
|
||||
return EH_HANDLED;
|
||||
}
|
||||
task->task_state_flags |= SAS_TASK_STATE_ABORTED;
|
||||
spin_unlock_irqrestore(&task->task_state_lock, flags);
|
||||
|
||||
SAS_DPRINTK("command 0x%p, task 0x%p, timed out: EH_NOT_HANDLED\n",
|
||||
cmd, task);
|
||||
|
||||
return EH_NOT_HANDLED;
|
||||
}
|
||||
|
||||
struct domain_device *sas_find_dev_by_rphy(struct sas_rphy *rphy)
|
||||
{
|
||||
struct Scsi_Host *shost = dev_to_shost(rphy->dev.parent);
|
||||
struct sas_ha_struct *ha = SHOST_TO_SAS_HA(shost);
|
||||
struct domain_device *found_dev = NULL;
|
||||
int i;
|
||||
|
||||
spin_lock(&ha->phy_port_lock);
|
||||
for (i = 0; i < ha->num_phys; i++) {
|
||||
struct asd_sas_port *port = ha->sas_port[i];
|
||||
struct domain_device *dev;
|
||||
|
||||
spin_lock(&port->dev_list_lock);
|
||||
list_for_each_entry(dev, &port->dev_list, dev_list_node) {
|
||||
if (rphy == dev->rphy) {
|
||||
found_dev = dev;
|
||||
spin_unlock(&port->dev_list_lock);
|
||||
goto found;
|
||||
}
|
||||
}
|
||||
spin_unlock(&port->dev_list_lock);
|
||||
}
|
||||
found:
|
||||
spin_unlock(&ha->phy_port_lock);
|
||||
|
||||
return found_dev;
|
||||
}
|
||||
|
||||
static inline struct domain_device *sas_find_target(struct scsi_target *starget)
|
||||
{
|
||||
struct sas_rphy *rphy = dev_to_rphy(starget->dev.parent);
|
||||
|
||||
return sas_find_dev_by_rphy(rphy);
|
||||
}
|
||||
|
||||
int sas_target_alloc(struct scsi_target *starget)
|
||||
{
|
||||
struct domain_device *found_dev = sas_find_target(starget);
|
||||
|
||||
if (!found_dev)
|
||||
return -ENODEV;
|
||||
|
||||
starget->hostdata = found_dev;
|
||||
return 0;
|
||||
}
|
||||
|
||||
#define SAS_DEF_QD 32
|
||||
#define SAS_MAX_QD 64
|
||||
|
||||
int sas_slave_configure(struct scsi_device *scsi_dev)
|
||||
{
|
||||
struct domain_device *dev = sdev_to_domain_dev(scsi_dev);
|
||||
struct sas_ha_struct *sas_ha;
|
||||
|
||||
BUG_ON(dev->rphy->identify.device_type != SAS_END_DEVICE);
|
||||
|
||||
sas_ha = dev->port->ha;
|
||||
|
||||
sas_read_port_mode_page(scsi_dev);
|
||||
|
||||
if (scsi_dev->tagged_supported) {
|
||||
scsi_set_tag_type(scsi_dev, MSG_SIMPLE_TAG);
|
||||
scsi_activate_tcq(scsi_dev, SAS_DEF_QD);
|
||||
} else {
|
||||
SAS_DPRINTK("device %llx, LUN %x doesn't support "
|
||||
"TCQ\n", SAS_ADDR(dev->sas_addr),
|
||||
scsi_dev->lun);
|
||||
scsi_dev->tagged_supported = 0;
|
||||
scsi_set_tag_type(scsi_dev, 0);
|
||||
scsi_deactivate_tcq(scsi_dev, 1);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void sas_slave_destroy(struct scsi_device *scsi_dev)
|
||||
{
|
||||
}
|
||||
|
||||
int sas_change_queue_depth(struct scsi_device *scsi_dev, int new_depth)
|
||||
{
|
||||
int res = min(new_depth, SAS_MAX_QD);
|
||||
|
||||
if (scsi_dev->tagged_supported)
|
||||
scsi_adjust_queue_depth(scsi_dev, scsi_get_tag_type(scsi_dev),
|
||||
res);
|
||||
else {
|
||||
struct domain_device *dev = sdev_to_domain_dev(scsi_dev);
|
||||
sas_printk("device %llx LUN %x queue depth changed to 1\n",
|
||||
SAS_ADDR(dev->sas_addr),
|
||||
scsi_dev->lun);
|
||||
scsi_adjust_queue_depth(scsi_dev, 0, 1);
|
||||
res = 1;
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
int sas_change_queue_type(struct scsi_device *scsi_dev, int qt)
|
||||
{
|
||||
if (!scsi_dev->tagged_supported)
|
||||
return 0;
|
||||
|
||||
scsi_deactivate_tcq(scsi_dev, 1);
|
||||
|
||||
scsi_set_tag_type(scsi_dev, qt);
|
||||
scsi_activate_tcq(scsi_dev, scsi_dev->queue_depth);
|
||||
|
||||
return qt;
|
||||
}
|
||||
|
||||
int sas_bios_param(struct scsi_device *scsi_dev,
|
||||
struct block_device *bdev,
|
||||
sector_t capacity, int *hsc)
|
||||
{
|
||||
hsc[0] = 255;
|
||||
hsc[1] = 63;
|
||||
sector_div(capacity, 255*63);
|
||||
hsc[2] = capacity;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ---------- Task Collector Thread implementation ---------- */
|
||||
|
||||
static void sas_queue(struct sas_ha_struct *sas_ha)
|
||||
{
|
||||
struct scsi_core *core = &sas_ha->core;
|
||||
unsigned long flags;
|
||||
LIST_HEAD(q);
|
||||
int can_queue;
|
||||
int res;
|
||||
struct sas_internal *i = to_sas_internal(core->shost->transportt);
|
||||
|
||||
spin_lock_irqsave(&core->task_queue_lock, flags);
|
||||
while (!core->queue_thread_kill &&
|
||||
!list_empty(&core->task_queue)) {
|
||||
|
||||
can_queue = sas_ha->lldd_queue_size - core->task_queue_size;
|
||||
if (can_queue >= 0) {
|
||||
can_queue = core->task_queue_size;
|
||||
list_splice_init(&core->task_queue, &q);
|
||||
} else {
|
||||
struct list_head *a, *n;
|
||||
|
||||
can_queue = sas_ha->lldd_queue_size;
|
||||
list_for_each_safe(a, n, &core->task_queue) {
|
||||
list_move_tail(a, &q);
|
||||
if (--can_queue == 0)
|
||||
break;
|
||||
}
|
||||
can_queue = sas_ha->lldd_queue_size;
|
||||
}
|
||||
core->task_queue_size -= can_queue;
|
||||
spin_unlock_irqrestore(&core->task_queue_lock, flags);
|
||||
{
|
||||
struct sas_task *task = list_entry(q.next,
|
||||
struct sas_task,
|
||||
list);
|
||||
list_del_init(&q);
|
||||
res = i->dft->lldd_execute_task(task, can_queue,
|
||||
GFP_KERNEL);
|
||||
if (unlikely(res))
|
||||
__list_add(&q, task->list.prev, &task->list);
|
||||
}
|
||||
spin_lock_irqsave(&core->task_queue_lock, flags);
|
||||
if (res) {
|
||||
list_splice_init(&q, &core->task_queue); /*at head*/
|
||||
core->task_queue_size += can_queue;
|
||||
}
|
||||
}
|
||||
spin_unlock_irqrestore(&core->task_queue_lock, flags);
|
||||
}
|
||||
|
||||
static DECLARE_COMPLETION(queue_th_comp);
|
||||
|
||||
/**
|
||||
* sas_queue_thread -- The Task Collector thread
|
||||
* @_sas_ha: pointer to struct sas_ha
|
||||
*/
|
||||
static int sas_queue_thread(void *_sas_ha)
|
||||
{
|
||||
struct sas_ha_struct *sas_ha = _sas_ha;
|
||||
struct scsi_core *core = &sas_ha->core;
|
||||
|
||||
daemonize("sas_queue_%d", core->shost->host_no);
|
||||
current->flags |= PF_NOFREEZE;
|
||||
|
||||
complete(&queue_th_comp);
|
||||
|
||||
while (1) {
|
||||
down_interruptible(&core->queue_thread_sema);
|
||||
sas_queue(sas_ha);
|
||||
if (core->queue_thread_kill)
|
||||
break;
|
||||
}
|
||||
|
||||
complete(&queue_th_comp);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int sas_init_queue(struct sas_ha_struct *sas_ha)
|
||||
{
|
||||
int res;
|
||||
struct scsi_core *core = &sas_ha->core;
|
||||
|
||||
spin_lock_init(&core->task_queue_lock);
|
||||
core->task_queue_size = 0;
|
||||
INIT_LIST_HEAD(&core->task_queue);
|
||||
init_MUTEX_LOCKED(&core->queue_thread_sema);
|
||||
|
||||
res = kernel_thread(sas_queue_thread, sas_ha, 0);
|
||||
if (res >= 0)
|
||||
wait_for_completion(&queue_th_comp);
|
||||
|
||||
return res < 0 ? res : 0;
|
||||
}
|
||||
|
||||
void sas_shutdown_queue(struct sas_ha_struct *sas_ha)
|
||||
{
|
||||
unsigned long flags;
|
||||
struct scsi_core *core = &sas_ha->core;
|
||||
struct sas_task *task, *n;
|
||||
|
||||
init_completion(&queue_th_comp);
|
||||
core->queue_thread_kill = 1;
|
||||
up(&core->queue_thread_sema);
|
||||
wait_for_completion(&queue_th_comp);
|
||||
|
||||
if (!list_empty(&core->task_queue))
|
||||
SAS_DPRINTK("HA: %llx: scsi core task queue is NOT empty!?\n",
|
||||
SAS_ADDR(sas_ha->sas_addr));
|
||||
|
||||
spin_lock_irqsave(&core->task_queue_lock, flags);
|
||||
list_for_each_entry_safe(task, n, &core->task_queue, list) {
|
||||
struct scsi_cmnd *cmd = task->uldd_task;
|
||||
|
||||
list_del_init(&task->list);
|
||||
|
||||
ASSIGN_SAS_TASK(cmd, NULL);
|
||||
sas_free_task(task);
|
||||
cmd->result = DID_ABORT << 16;
|
||||
cmd->scsi_done(cmd);
|
||||
}
|
||||
spin_unlock_irqrestore(&core->task_queue_lock, flags);
|
||||
}
|
||||
|
||||
EXPORT_SYMBOL_GPL(sas_queuecommand);
|
||||
EXPORT_SYMBOL_GPL(sas_target_alloc);
|
||||
EXPORT_SYMBOL_GPL(sas_slave_configure);
|
||||
EXPORT_SYMBOL_GPL(sas_slave_destroy);
|
||||
EXPORT_SYMBOL_GPL(sas_change_queue_depth);
|
||||
EXPORT_SYMBOL_GPL(sas_change_queue_type);
|
||||
EXPORT_SYMBOL_GPL(sas_bios_param);
|
627
include/scsi/libsas.h
Normal file
627
include/scsi/libsas.h
Normal file
@ -0,0 +1,627 @@
|
||||
/*
|
||||
* SAS host prototypes and structures header file
|
||||
*
|
||||
* Copyright (C) 2005 Adaptec, Inc. All rights reserved.
|
||||
* Copyright (C) 2005 Luben Tuikov <luben_tuikov@adaptec.com>
|
||||
*
|
||||
* This file is licensed under GPLv2.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation; either version 2 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
|
||||
* USA
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef _LIBSAS_H_
|
||||
#define _LIBSAS_H_
|
||||
|
||||
|
||||
#include <linux/timer.h>
|
||||
#include <linux/pci.h>
|
||||
#include <scsi/sas.h>
|
||||
#include <linux/list.h>
|
||||
#include <asm/semaphore.h>
|
||||
#include <scsi/scsi_device.h>
|
||||
#include <scsi/scsi_cmnd.h>
|
||||
#include <scsi/scsi_transport_sas.h>
|
||||
|
||||
struct block_device;
|
||||
|
||||
enum sas_class {
|
||||
SAS,
|
||||
EXPANDER
|
||||
};
|
||||
|
||||
enum sas_phy_role {
|
||||
PHY_ROLE_NONE = 0,
|
||||
PHY_ROLE_TARGET = 0x40,
|
||||
PHY_ROLE_INITIATOR = 0x80,
|
||||
};
|
||||
|
||||
enum sas_phy_type {
|
||||
PHY_TYPE_PHYSICAL,
|
||||
PHY_TYPE_VIRTUAL
|
||||
};
|
||||
|
||||
/* The events are mnemonically described in sas_dump.c
|
||||
* so when updating/adding events here, please also
|
||||
* update the other file too.
|
||||
*/
|
||||
enum ha_event {
|
||||
HAE_RESET = 0U,
|
||||
HA_NUM_EVENTS = 1,
|
||||
};
|
||||
|
||||
enum port_event {
|
||||
PORTE_BYTES_DMAED = 0U,
|
||||
PORTE_BROADCAST_RCVD = 1,
|
||||
PORTE_LINK_RESET_ERR = 2,
|
||||
PORTE_TIMER_EVENT = 3,
|
||||
PORTE_HARD_RESET = 4,
|
||||
PORT_NUM_EVENTS = 5,
|
||||
};
|
||||
|
||||
enum phy_event {
|
||||
PHYE_LOSS_OF_SIGNAL = 0U,
|
||||
PHYE_OOB_DONE = 1,
|
||||
PHYE_OOB_ERROR = 2,
|
||||
PHYE_SPINUP_HOLD = 3, /* hot plug SATA, no COMWAKE sent */
|
||||
PHY_NUM_EVENTS = 4,
|
||||
};
|
||||
|
||||
enum discover_event {
|
||||
DISCE_DISCOVER_DOMAIN = 0U,
|
||||
DISCE_REVALIDATE_DOMAIN = 1,
|
||||
DISCE_PORT_GONE = 2,
|
||||
DISC_NUM_EVENTS = 3,
|
||||
};
|
||||
|
||||
/* ---------- Expander Devices ---------- */
|
||||
|
||||
#define ETASK 0xFA
|
||||
|
||||
#define to_dom_device(_obj) container_of(_obj, struct domain_device, dev_obj)
|
||||
#define to_dev_attr(_attr) container_of(_attr, struct domain_dev_attribute,\
|
||||
attr)
|
||||
|
||||
enum routing_attribute {
|
||||
DIRECT_ROUTING,
|
||||
SUBTRACTIVE_ROUTING,
|
||||
TABLE_ROUTING,
|
||||
};
|
||||
|
||||
enum ex_phy_state {
|
||||
PHY_EMPTY,
|
||||
PHY_VACANT,
|
||||
PHY_NOT_PRESENT,
|
||||
PHY_DEVICE_DISCOVERED
|
||||
};
|
||||
|
||||
struct ex_phy {
|
||||
int phy_id;
|
||||
|
||||
enum ex_phy_state phy_state;
|
||||
|
||||
enum sas_dev_type attached_dev_type;
|
||||
enum sas_phy_linkrate linkrate;
|
||||
|
||||
u8 attached_sata_host:1;
|
||||
u8 attached_sata_dev:1;
|
||||
u8 attached_sata_ps:1;
|
||||
|
||||
enum sas_proto attached_tproto;
|
||||
enum sas_proto attached_iproto;
|
||||
|
||||
u8 attached_sas_addr[SAS_ADDR_SIZE];
|
||||
u8 attached_phy_id;
|
||||
|
||||
u8 phy_change_count;
|
||||
enum routing_attribute routing_attr;
|
||||
u8 virtual:1;
|
||||
|
||||
int last_da_index;
|
||||
|
||||
struct sas_phy *phy;
|
||||
struct sas_port *port;
|
||||
};
|
||||
|
||||
struct expander_device {
|
||||
struct list_head children;
|
||||
|
||||
u16 ex_change_count;
|
||||
u16 max_route_indexes;
|
||||
u8 num_phys;
|
||||
u8 configuring:1;
|
||||
u8 conf_route_table:1;
|
||||
u8 enclosure_logical_id[8];
|
||||
|
||||
struct ex_phy *ex_phy;
|
||||
struct sas_port *parent_port;
|
||||
};
|
||||
|
||||
/* ---------- SATA device ---------- */
|
||||
enum ata_command_set {
|
||||
ATA_COMMAND_SET = 0,
|
||||
ATAPI_COMMAND_SET = 1,
|
||||
};
|
||||
|
||||
struct sata_device {
|
||||
enum ata_command_set command_set;
|
||||
struct smp_resp rps_resp; /* report_phy_sata_resp */
|
||||
__le16 *identify_device;
|
||||
__le16 *identify_packet_device;
|
||||
|
||||
u8 port_no; /* port number, if this is a PM (Port) */
|
||||
struct list_head children; /* PM Ports if this is a PM */
|
||||
};
|
||||
|
||||
/* ---------- Domain device ---------- */
|
||||
struct domain_device {
|
||||
enum sas_dev_type dev_type;
|
||||
|
||||
enum sas_phy_linkrate linkrate;
|
||||
enum sas_phy_linkrate min_linkrate;
|
||||
enum sas_phy_linkrate max_linkrate;
|
||||
|
||||
int pathways;
|
||||
|
||||
struct domain_device *parent;
|
||||
struct list_head siblings; /* devices on the same level */
|
||||
struct asd_sas_port *port; /* shortcut to root of the tree */
|
||||
|
||||
struct list_head dev_list_node;
|
||||
|
||||
enum sas_proto iproto;
|
||||
enum sas_proto tproto;
|
||||
|
||||
struct sas_rphy *rphy;
|
||||
|
||||
u8 sas_addr[SAS_ADDR_SIZE];
|
||||
u8 hashed_sas_addr[HASHED_SAS_ADDR_SIZE];
|
||||
|
||||
u8 frame_rcvd[32];
|
||||
|
||||
union {
|
||||
struct expander_device ex_dev;
|
||||
struct sata_device sata_dev; /* STP & directly attached */
|
||||
};
|
||||
|
||||
void *lldd_dev;
|
||||
};
|
||||
|
||||
struct sas_discovery {
|
||||
spinlock_t disc_event_lock;
|
||||
struct work_struct disc_work[DISC_NUM_EVENTS];
|
||||
unsigned long pending;
|
||||
u8 fanout_sas_addr[8];
|
||||
u8 eeds_a[8];
|
||||
u8 eeds_b[8];
|
||||
int max_level;
|
||||
};
|
||||
|
||||
|
||||
/* The port struct is Class:RW, driver:RO */
|
||||
struct asd_sas_port {
|
||||
/* private: */
|
||||
struct completion port_gone_completion;
|
||||
|
||||
struct sas_discovery disc;
|
||||
struct domain_device *port_dev;
|
||||
spinlock_t dev_list_lock;
|
||||
struct list_head dev_list;
|
||||
enum sas_phy_linkrate linkrate;
|
||||
|
||||
struct sas_phy *phy;
|
||||
struct work_struct work;
|
||||
|
||||
/* public: */
|
||||
int id;
|
||||
|
||||
enum sas_class class;
|
||||
u8 sas_addr[SAS_ADDR_SIZE];
|
||||
u8 attached_sas_addr[SAS_ADDR_SIZE];
|
||||
enum sas_proto iproto;
|
||||
enum sas_proto tproto;
|
||||
|
||||
enum sas_oob_mode oob_mode;
|
||||
|
||||
spinlock_t phy_list_lock;
|
||||
struct list_head phy_list;
|
||||
int num_phys;
|
||||
u32 phy_mask;
|
||||
|
||||
struct sas_ha_struct *ha;
|
||||
|
||||
struct sas_port *port;
|
||||
|
||||
void *lldd_port; /* not touched by the sas class code */
|
||||
};
|
||||
|
||||
/* The phy pretty much is controlled by the LLDD.
|
||||
* The class only reads those fields.
|
||||
*/
|
||||
struct asd_sas_phy {
|
||||
/* private: */
|
||||
/* protected by ha->event_lock */
|
||||
struct work_struct port_events[PORT_NUM_EVENTS];
|
||||
struct work_struct phy_events[PHY_NUM_EVENTS];
|
||||
|
||||
unsigned long port_events_pending;
|
||||
unsigned long phy_events_pending;
|
||||
|
||||
int error;
|
||||
|
||||
struct sas_phy *phy;
|
||||
|
||||
/* public: */
|
||||
/* The following are class:RO, driver:R/W */
|
||||
int enabled; /* must be set */
|
||||
|
||||
int id; /* must be set */
|
||||
enum sas_class class;
|
||||
enum sas_proto iproto;
|
||||
enum sas_proto tproto;
|
||||
|
||||
enum sas_phy_type type;
|
||||
enum sas_phy_role role;
|
||||
enum sas_oob_mode oob_mode;
|
||||
enum sas_phy_linkrate linkrate;
|
||||
|
||||
u8 *sas_addr; /* must be set */
|
||||
u8 attached_sas_addr[SAS_ADDR_SIZE]; /* class:RO, driver: R/W */
|
||||
|
||||
spinlock_t frame_rcvd_lock;
|
||||
u8 *frame_rcvd; /* must be set */
|
||||
int frame_rcvd_size;
|
||||
|
||||
spinlock_t sas_prim_lock;
|
||||
u32 sas_prim;
|
||||
|
||||
struct list_head port_phy_el; /* driver:RO */
|
||||
struct asd_sas_port *port; /* Class:RW, driver: RO */
|
||||
|
||||
struct sas_ha_struct *ha; /* may be set; the class sets it anyway */
|
||||
|
||||
void *lldd_phy; /* not touched by the sas_class_code */
|
||||
};
|
||||
|
||||
struct scsi_core {
|
||||
struct Scsi_Host *shost;
|
||||
|
||||
spinlock_t task_queue_lock;
|
||||
struct list_head task_queue;
|
||||
int task_queue_size;
|
||||
|
||||
struct semaphore queue_thread_sema;
|
||||
int queue_thread_kill;
|
||||
};
|
||||
|
||||
struct sas_ha_struct {
|
||||
/* private: */
|
||||
spinlock_t event_lock;
|
||||
struct work_struct ha_events[HA_NUM_EVENTS];
|
||||
unsigned long pending;
|
||||
|
||||
struct scsi_core core;
|
||||
|
||||
/* public: */
|
||||
char *sas_ha_name;
|
||||
struct pci_dev *pcidev; /* should be set */
|
||||
struct module *lldd_module; /* should be set */
|
||||
|
||||
u8 *sas_addr; /* must be set */
|
||||
u8 hashed_sas_addr[HASHED_SAS_ADDR_SIZE];
|
||||
|
||||
spinlock_t phy_port_lock;
|
||||
struct asd_sas_phy **sas_phy; /* array of valid pointers, must be set */
|
||||
struct asd_sas_port **sas_port; /* array of valid pointers, must be set */
|
||||
int num_phys; /* must be set, gt 0, static */
|
||||
|
||||
/* The class calls this to send a task for execution. */
|
||||
int lldd_max_execute_num;
|
||||
int lldd_queue_size;
|
||||
|
||||
/* 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 asd_sas_phy *, enum port_event);
|
||||
void (*notify_phy_event)(struct asd_sas_phy *, enum phy_event);
|
||||
|
||||
void *lldd_ha; /* not touched by sas class code */
|
||||
};
|
||||
|
||||
#define SHOST_TO_SAS_HA(_shost) (*(struct sas_ha_struct **)(_shost)->hostdata)
|
||||
|
||||
static inline struct domain_device *
|
||||
starget_to_domain_dev(struct scsi_target *starget) {
|
||||
return starget->hostdata;
|
||||
}
|
||||
|
||||
static inline struct domain_device *
|
||||
sdev_to_domain_dev(struct scsi_device *sdev) {
|
||||
return starget_to_domain_dev(sdev->sdev_target);
|
||||
}
|
||||
|
||||
static inline struct domain_device *
|
||||
cmd_to_domain_dev(struct scsi_cmnd *cmd)
|
||||
{
|
||||
return sdev_to_domain_dev(cmd->device);
|
||||
}
|
||||
|
||||
void sas_hash_addr(u8 *hashed, const u8 *sas_addr);
|
||||
|
||||
/* Before calling a notify event, LLDD should use this function
|
||||
* when the link is severed (possibly from its tasklet).
|
||||
* The idea is that the Class only reads those, while the LLDD,
|
||||
* can R/W these (thus avoiding a race).
|
||||
*/
|
||||
static inline void sas_phy_disconnected(struct asd_sas_phy *phy)
|
||||
{
|
||||
phy->oob_mode = OOB_NOT_CONNECTED;
|
||||
phy->linkrate = PHY_LINKRATE_NONE;
|
||||
}
|
||||
|
||||
/* ---------- Tasks ---------- */
|
||||
/*
|
||||
service_response | SAS_TASK_COMPLETE | SAS_TASK_UNDELIVERED |
|
||||
exec_status | | |
|
||||
---------------------+---------------------+-----------------------+
|
||||
SAM_... | X | |
|
||||
DEV_NO_RESPONSE | X | X |
|
||||
INTERRUPTED | X | |
|
||||
QUEUE_FULL | | X |
|
||||
DEVICE_UNKNOWN | | X |
|
||||
SG_ERR | | X |
|
||||
---------------------+---------------------+-----------------------+
|
||||
*/
|
||||
|
||||
enum service_response {
|
||||
SAS_TASK_COMPLETE,
|
||||
SAS_TASK_UNDELIVERED = -1,
|
||||
};
|
||||
|
||||
enum exec_status {
|
||||
SAM_GOOD = 0,
|
||||
SAM_CHECK_COND = 2,
|
||||
SAM_COND_MET = 4,
|
||||
SAM_BUSY = 8,
|
||||
SAM_INTERMEDIATE = 0x10,
|
||||
SAM_IM_COND_MET = 0x12,
|
||||
SAM_RESV_CONFLICT= 0x14,
|
||||
SAM_TASK_SET_FULL= 0x28,
|
||||
SAM_ACA_ACTIVE = 0x30,
|
||||
SAM_TASK_ABORTED = 0x40,
|
||||
|
||||
SAS_DEV_NO_RESPONSE = 0x80,
|
||||
SAS_DATA_UNDERRUN,
|
||||
SAS_DATA_OVERRUN,
|
||||
SAS_INTERRUPTED,
|
||||
SAS_QUEUE_FULL,
|
||||
SAS_DEVICE_UNKNOWN,
|
||||
SAS_SG_ERR,
|
||||
SAS_OPEN_REJECT,
|
||||
SAS_OPEN_TO,
|
||||
SAS_PROTO_RESPONSE,
|
||||
SAS_PHY_DOWN,
|
||||
SAS_NAK_R_ERR,
|
||||
SAS_PENDING,
|
||||
SAS_ABORTED_TASK,
|
||||
};
|
||||
|
||||
/* When a task finishes with a response, the LLDD examines the
|
||||
* response:
|
||||
* - For an ATA task task_status_struct::stat is set to
|
||||
* SAS_PROTO_RESPONSE, and the task_status_struct::buf is set to the
|
||||
* contents of struct ata_task_resp.
|
||||
* - For SSP tasks, if no data is present or status/TMF response
|
||||
* is valid, task_status_struct::stat is set. If data is present
|
||||
* (SENSE data), the LLDD copies up to SAS_STATUS_BUF_SIZE, sets
|
||||
* task_status_struct::buf_valid_size, and task_status_struct::stat is
|
||||
* set to SAM_CHECK_COND.
|
||||
*
|
||||
* "buf" has format SCSI Sense for SSP task, or struct ata_task_resp
|
||||
* for ATA task.
|
||||
*
|
||||
* "frame_len" is the total frame length, which could be more or less
|
||||
* than actually copied.
|
||||
*
|
||||
* Tasks ending with response, always set the residual field.
|
||||
*/
|
||||
struct ata_task_resp {
|
||||
u16 frame_len;
|
||||
u8 ending_fis[24]; /* dev to host or data-in */
|
||||
u32 sstatus;
|
||||
u32 serror;
|
||||
u32 scontrol;
|
||||
u32 sactive;
|
||||
};
|
||||
|
||||
#define SAS_STATUS_BUF_SIZE 96
|
||||
|
||||
struct task_status_struct {
|
||||
enum service_response resp;
|
||||
enum exec_status stat;
|
||||
int buf_valid_size;
|
||||
|
||||
u8 buf[SAS_STATUS_BUF_SIZE];
|
||||
|
||||
u32 residual;
|
||||
enum sas_open_rej_reason open_rej_reason;
|
||||
};
|
||||
|
||||
/* ATA and ATAPI task queuable to a SAS LLDD.
|
||||
*/
|
||||
struct sas_ata_task {
|
||||
struct host_to_dev_fis fis;
|
||||
u8 atapi_packet[16]; /* 0 if not ATAPI task */
|
||||
|
||||
u8 retry_count; /* hardware retry, should be > 0 */
|
||||
|
||||
u8 dma_xfer:1; /* PIO:0 or DMA:1 */
|
||||
u8 use_ncq:1;
|
||||
u8 set_affil_pol:1;
|
||||
u8 stp_affil_pol:1;
|
||||
|
||||
u8 device_control_reg_update:1;
|
||||
};
|
||||
|
||||
struct sas_smp_task {
|
||||
struct scatterlist smp_req;
|
||||
struct scatterlist smp_resp;
|
||||
};
|
||||
|
||||
enum task_attribute {
|
||||
TASK_ATTR_SIMPLE = 0,
|
||||
TASK_ATTR_HOQ = 1,
|
||||
TASK_ATTR_ORDERED= 2,
|
||||
TASK_ATTR_ACA = 4,
|
||||
};
|
||||
|
||||
struct sas_ssp_task {
|
||||
u8 retry_count; /* hardware retry, should be > 0 */
|
||||
|
||||
u8 LUN[8];
|
||||
u8 enable_first_burst:1;
|
||||
enum task_attribute task_attr;
|
||||
u8 task_prio;
|
||||
u8 cdb[16];
|
||||
};
|
||||
|
||||
struct sas_task {
|
||||
struct domain_device *dev;
|
||||
struct list_head list;
|
||||
|
||||
spinlock_t task_state_lock;
|
||||
unsigned task_state_flags;
|
||||
|
||||
enum sas_proto task_proto;
|
||||
|
||||
/* Used by the discovery code. */
|
||||
struct timer_list timer;
|
||||
struct completion completion;
|
||||
|
||||
union {
|
||||
struct sas_ata_task ata_task;
|
||||
struct sas_smp_task smp_task;
|
||||
struct sas_ssp_task ssp_task;
|
||||
};
|
||||
|
||||
struct scatterlist *scatter;
|
||||
int num_scatter;
|
||||
u32 total_xfer_len;
|
||||
u8 data_dir:2; /* Use PCI_DMA_... */
|
||||
|
||||
struct task_status_struct task_status;
|
||||
void (*task_done)(struct sas_task *);
|
||||
|
||||
void *lldd_task; /* for use by LLDDs */
|
||||
void *uldd_task;
|
||||
};
|
||||
|
||||
|
||||
|
||||
#define SAS_TASK_STATE_PENDING 1
|
||||
#define SAS_TASK_STATE_DONE 2
|
||||
#define SAS_TASK_STATE_ABORTED 4
|
||||
|
||||
static inline struct sas_task *sas_alloc_task(unsigned long flags)
|
||||
{
|
||||
extern kmem_cache_t *sas_task_cache;
|
||||
struct sas_task *task = kmem_cache_alloc(sas_task_cache, flags);
|
||||
|
||||
if (task) {
|
||||
memset(task, 0, sizeof(*task));
|
||||
INIT_LIST_HEAD(&task->list);
|
||||
spin_lock_init(&task->task_state_lock);
|
||||
task->task_state_flags = SAS_TASK_STATE_PENDING;
|
||||
init_timer(&task->timer);
|
||||
init_completion(&task->completion);
|
||||
}
|
||||
|
||||
return task;
|
||||
}
|
||||
|
||||
static inline void sas_free_task(struct sas_task *task)
|
||||
{
|
||||
if (task) {
|
||||
extern kmem_cache_t *sas_task_cache;
|
||||
BUG_ON(!list_empty(&task->list));
|
||||
kmem_cache_free(sas_task_cache, task);
|
||||
}
|
||||
}
|
||||
|
||||
struct sas_domain_function_template {
|
||||
/* The class calls these to notify the LLDD of an event. */
|
||||
void (*lldd_port_formed)(struct asd_sas_phy *);
|
||||
void (*lldd_port_deformed)(struct asd_sas_phy *);
|
||||
|
||||
/* The class calls these when a device is found or gone. */
|
||||
int (*lldd_dev_found)(struct domain_device *);
|
||||
void (*lldd_dev_gone)(struct domain_device *);
|
||||
|
||||
int (*lldd_execute_task)(struct sas_task *, int num,
|
||||
unsigned long gfp_flags);
|
||||
|
||||
/* 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 *);
|
||||
|
||||
/* Port and Adapter management */
|
||||
int (*lldd_clear_nexus_port)(struct asd_sas_port *);
|
||||
int (*lldd_clear_nexus_ha)(struct sas_ha_struct *);
|
||||
|
||||
/* Phy management */
|
||||
int (*lldd_control_phy)(struct asd_sas_phy *, enum phy_func);
|
||||
};
|
||||
|
||||
extern int sas_register_ha(struct sas_ha_struct *);
|
||||
extern int sas_unregister_ha(struct sas_ha_struct *);
|
||||
|
||||
extern int sas_queuecommand(struct scsi_cmnd *,
|
||||
void (*scsi_done)(struct scsi_cmnd *));
|
||||
extern int sas_target_alloc(struct scsi_target *);
|
||||
extern int sas_slave_alloc(struct scsi_device *);
|
||||
extern int sas_slave_configure(struct scsi_device *);
|
||||
extern void sas_slave_destroy(struct scsi_device *);
|
||||
extern int sas_change_queue_depth(struct scsi_device *, int new_depth);
|
||||
extern int sas_change_queue_type(struct scsi_device *, int qt);
|
||||
extern int sas_bios_param(struct scsi_device *,
|
||||
struct block_device *,
|
||||
sector_t capacity, int *hsc);
|
||||
extern struct scsi_transport_template *
|
||||
sas_domain_attach_transport(struct sas_domain_function_template *);
|
||||
extern void sas_domain_release_transport(struct scsi_transport_template *);
|
||||
|
||||
int sas_discover_root_expander(struct domain_device *);
|
||||
|
||||
void sas_init_ex_attr(void);
|
||||
|
||||
int sas_ex_revalidate_domain(struct domain_device *);
|
||||
|
||||
void sas_unregister_domain_devices(struct asd_sas_port *port);
|
||||
void sas_init_disc(struct sas_discovery *disc, struct asd_sas_port *);
|
||||
int sas_discover_event(struct asd_sas_port *, enum discover_event ev);
|
||||
|
||||
int sas_discover_sata(struct domain_device *);
|
||||
int sas_discover_end_dev(struct domain_device *);
|
||||
|
||||
void sas_unregister_dev(struct domain_device *);
|
||||
|
||||
void sas_init_dev(struct domain_device *);
|
||||
|
||||
#endif /* _SASLIB_H_ */
|
644
include/scsi/sas.h
Normal file
644
include/scsi/sas.h
Normal file
@ -0,0 +1,644 @@
|
||||
/*
|
||||
* SAS structures and definitions header file
|
||||
*
|
||||
* Copyright (C) 2005 Adaptec, Inc. All rights reserved.
|
||||
* Copyright (C) 2005 Luben Tuikov <luben_tuikov@adaptec.com>
|
||||
*
|
||||
* This file is licensed under GPLv2.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation; either version 2 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
|
||||
* USA
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef _SAS_H_
|
||||
#define _SAS_H_
|
||||
|
||||
#include <linux/types.h>
|
||||
#include <asm/byteorder.h>
|
||||
|
||||
#define SAS_ADDR_SIZE 8
|
||||
#define HASHED_SAS_ADDR_SIZE 3
|
||||
#define SAS_ADDR(_sa) ((unsigned long long) be64_to_cpu(*(__be64 *)(_sa)))
|
||||
|
||||
#define SMP_REQUEST 0x40
|
||||
#define SMP_RESPONSE 0x41
|
||||
|
||||
#define SSP_DATA 0x01
|
||||
#define SSP_XFER_RDY 0x05
|
||||
#define SSP_COMMAND 0x06
|
||||
#define SSP_RESPONSE 0x07
|
||||
#define SSP_TASK 0x16
|
||||
|
||||
#define SMP_REPORT_GENERAL 0x00
|
||||
#define SMP_REPORT_MANUF_INFO 0x01
|
||||
#define SMP_READ_GPIO_REG 0x02
|
||||
#define SMP_DISCOVER 0x10
|
||||
#define SMP_REPORT_PHY_ERR_LOG 0x11
|
||||
#define SMP_REPORT_PHY_SATA 0x12
|
||||
#define SMP_REPORT_ROUTE_INFO 0x13
|
||||
#define SMP_WRITE_GPIO_REG 0x82
|
||||
#define SMP_CONF_ROUTE_INFO 0x90
|
||||
#define SMP_PHY_CONTROL 0x91
|
||||
#define SMP_PHY_TEST_FUNCTION 0x92
|
||||
|
||||
#define SMP_RESP_FUNC_ACC 0x00
|
||||
#define SMP_RESP_FUNC_UNK 0x01
|
||||
#define SMP_RESP_FUNC_FAILED 0x02
|
||||
#define SMP_RESP_INV_FRM_LEN 0x03
|
||||
#define SMP_RESP_NO_PHY 0x10
|
||||
#define SMP_RESP_NO_INDEX 0x11
|
||||
#define SMP_RESP_PHY_NO_SATA 0x12
|
||||
#define SMP_RESP_PHY_UNK_OP 0x13
|
||||
#define SMP_RESP_PHY_UNK_TESTF 0x14
|
||||
#define SMP_RESP_PHY_TEST_INPROG 0x15
|
||||
#define SMP_RESP_PHY_VACANT 0x16
|
||||
|
||||
/* SAM TMFs */
|
||||
#define TMF_ABORT_TASK 0x01
|
||||
#define TMF_ABORT_TASK_SET 0x02
|
||||
#define TMF_CLEAR_TASK_SET 0x04
|
||||
#define TMF_LU_RESET 0x08
|
||||
#define TMF_CLEAR_ACA 0x40
|
||||
#define TMF_QUERY_TASK 0x80
|
||||
|
||||
/* SAS TMF responses */
|
||||
#define TMF_RESP_FUNC_COMPLETE 0x00
|
||||
#define TMF_RESP_INVALID_FRAME 0x02
|
||||
#define TMF_RESP_FUNC_ESUPP 0x04
|
||||
#define TMF_RESP_FUNC_FAILED 0x05
|
||||
#define TMF_RESP_FUNC_SUCC 0x08
|
||||
#define TMF_RESP_NO_LUN 0x09
|
||||
#define TMF_RESP_OVERLAPPED_TAG 0x0A
|
||||
|
||||
enum sas_oob_mode {
|
||||
OOB_NOT_CONNECTED,
|
||||
SATA_OOB_MODE,
|
||||
SAS_OOB_MODE
|
||||
};
|
||||
|
||||
/* See sas_discover.c if you plan on changing these.
|
||||
*/
|
||||
enum sas_dev_type {
|
||||
NO_DEVICE = 0, /* protocol */
|
||||
SAS_END_DEV = 1, /* protocol */
|
||||
EDGE_DEV = 2, /* protocol */
|
||||
FANOUT_DEV = 3, /* protocol */
|
||||
SAS_HA = 4,
|
||||
SATA_DEV = 5,
|
||||
SATA_PM = 7,
|
||||
SATA_PM_PORT= 8,
|
||||
};
|
||||
|
||||
enum sas_phy_linkrate {
|
||||
PHY_LINKRATE_NONE = 0,
|
||||
PHY_LINKRATE_UNKNOWN = 0,
|
||||
PHY_DISABLED,
|
||||
PHY_RESET_PROBLEM,
|
||||
PHY_SPINUP_HOLD,
|
||||
PHY_PORT_SELECTOR,
|
||||
PHY_LINKRATE_1_5 = 0x08,
|
||||
PHY_LINKRATE_G1 = PHY_LINKRATE_1_5,
|
||||
PHY_LINKRATE_3 = 0x09,
|
||||
PHY_LINKRATE_G2 = PHY_LINKRATE_3,
|
||||
PHY_LINKRATE_6 = 0x0A,
|
||||
};
|
||||
|
||||
/* Partly from IDENTIFY address frame. */
|
||||
enum sas_proto {
|
||||
SATA_PROTO = 1,
|
||||
SAS_PROTO_SMP = 2, /* protocol */
|
||||
SAS_PROTO_STP = 4, /* protocol */
|
||||
SAS_PROTO_SSP = 8, /* protocol */
|
||||
SAS_PROTO_ALL = 0xE,
|
||||
};
|
||||
|
||||
/* From the spec; local phys only */
|
||||
enum phy_func {
|
||||
PHY_FUNC_NOP,
|
||||
PHY_FUNC_LINK_RESET, /* Enables the phy */
|
||||
PHY_FUNC_HARD_RESET,
|
||||
PHY_FUNC_DISABLE,
|
||||
PHY_FUNC_CLEAR_ERROR_LOG = 5,
|
||||
PHY_FUNC_CLEAR_AFFIL,
|
||||
PHY_FUNC_TX_SATA_PS_SIGNAL,
|
||||
PHY_FUNC_RELEASE_SPINUP_HOLD = 0x10, /* LOCAL PORT ONLY! */
|
||||
};
|
||||
|
||||
/* SAS LLDD would need to report only _very_few_ of those, like BROADCAST.
|
||||
* Most of those are here for completeness.
|
||||
*/
|
||||
enum sas_prim {
|
||||
SAS_PRIM_AIP_NORMAL = 1,
|
||||
SAS_PRIM_AIP_R0 = 2,
|
||||
SAS_PRIM_AIP_R1 = 3,
|
||||
SAS_PRIM_AIP_R2 = 4,
|
||||
SAS_PRIM_AIP_WC = 5,
|
||||
SAS_PRIM_AIP_WD = 6,
|
||||
SAS_PRIM_AIP_WP = 7,
|
||||
SAS_PRIM_AIP_RWP = 8,
|
||||
|
||||
SAS_PRIM_BC_CH = 9,
|
||||
SAS_PRIM_BC_RCH0 = 10,
|
||||
SAS_PRIM_BC_RCH1 = 11,
|
||||
SAS_PRIM_BC_R0 = 12,
|
||||
SAS_PRIM_BC_R1 = 13,
|
||||
SAS_PRIM_BC_R2 = 14,
|
||||
SAS_PRIM_BC_R3 = 15,
|
||||
SAS_PRIM_BC_R4 = 16,
|
||||
|
||||
SAS_PRIM_NOTIFY_ENSP= 17,
|
||||
SAS_PRIM_NOTIFY_R0 = 18,
|
||||
SAS_PRIM_NOTIFY_R1 = 19,
|
||||
SAS_PRIM_NOTIFY_R2 = 20,
|
||||
|
||||
SAS_PRIM_CLOSE_CLAF = 21,
|
||||
SAS_PRIM_CLOSE_NORM = 22,
|
||||
SAS_PRIM_CLOSE_R0 = 23,
|
||||
SAS_PRIM_CLOSE_R1 = 24,
|
||||
|
||||
SAS_PRIM_OPEN_RTRY = 25,
|
||||
SAS_PRIM_OPEN_RJCT = 26,
|
||||
SAS_PRIM_OPEN_ACPT = 27,
|
||||
|
||||
SAS_PRIM_DONE = 28,
|
||||
SAS_PRIM_BREAK = 29,
|
||||
|
||||
SATA_PRIM_DMAT = 33,
|
||||
SATA_PRIM_PMNAK = 34,
|
||||
SATA_PRIM_PMACK = 35,
|
||||
SATA_PRIM_PMREQ_S = 36,
|
||||
SATA_PRIM_PMREQ_P = 37,
|
||||
SATA_SATA_R_ERR = 38,
|
||||
};
|
||||
|
||||
enum sas_open_rej_reason {
|
||||
/* Abandon open */
|
||||
SAS_OREJ_UNKNOWN = 0,
|
||||
SAS_OREJ_BAD_DEST = 1,
|
||||
SAS_OREJ_CONN_RATE = 2,
|
||||
SAS_OREJ_EPROTO = 3,
|
||||
SAS_OREJ_RESV_AB0 = 4,
|
||||
SAS_OREJ_RESV_AB1 = 5,
|
||||
SAS_OREJ_RESV_AB2 = 6,
|
||||
SAS_OREJ_RESV_AB3 = 7,
|
||||
SAS_OREJ_WRONG_DEST= 8,
|
||||
SAS_OREJ_STP_NORES = 9,
|
||||
|
||||
/* Retry open */
|
||||
SAS_OREJ_NO_DEST = 10,
|
||||
SAS_OREJ_PATH_BLOCKED = 11,
|
||||
SAS_OREJ_RSVD_CONT0 = 12,
|
||||
SAS_OREJ_RSVD_CONT1 = 13,
|
||||
SAS_OREJ_RSVD_INIT0 = 14,
|
||||
SAS_OREJ_RSVD_INIT1 = 15,
|
||||
SAS_OREJ_RSVD_STOP0 = 16,
|
||||
SAS_OREJ_RSVD_STOP1 = 17,
|
||||
SAS_OREJ_RSVD_RETRY = 18,
|
||||
};
|
||||
|
||||
struct dev_to_host_fis {
|
||||
u8 fis_type; /* 0x34 */
|
||||
u8 flags;
|
||||
u8 status;
|
||||
u8 error;
|
||||
|
||||
u8 lbal;
|
||||
union { u8 lbam; u8 byte_count_low; };
|
||||
union { u8 lbah; u8 byte_count_high; };
|
||||
u8 device;
|
||||
|
||||
u8 lbal_exp;
|
||||
u8 lbam_exp;
|
||||
u8 lbah_exp;
|
||||
u8 _r_a;
|
||||
|
||||
union { u8 sector_count; u8 interrupt_reason; };
|
||||
u8 sector_count_exp;
|
||||
u8 _r_b;
|
||||
u8 _r_c;
|
||||
|
||||
u32 _r_d;
|
||||
} __attribute__ ((packed));
|
||||
|
||||
struct host_to_dev_fis {
|
||||
u8 fis_type; /* 0x27 */
|
||||
u8 flags;
|
||||
u8 command;
|
||||
u8 features;
|
||||
|
||||
u8 lbal;
|
||||
union { u8 lbam; u8 byte_count_low; };
|
||||
union { u8 lbah; u8 byte_count_high; };
|
||||
u8 device;
|
||||
|
||||
u8 lbal_exp;
|
||||
u8 lbam_exp;
|
||||
u8 lbah_exp;
|
||||
u8 features_exp;
|
||||
|
||||
union { u8 sector_count; u8 interrupt_reason; };
|
||||
u8 sector_count_exp;
|
||||
u8 _r_a;
|
||||
u8 control;
|
||||
|
||||
u32 _r_b;
|
||||
} __attribute__ ((packed));
|
||||
|
||||
/* Prefer to have code clarity over header file clarity.
|
||||
*/
|
||||
#ifdef __LITTLE_ENDIAN_BITFIELD
|
||||
struct sas_identify_frame {
|
||||
/* Byte 0 */
|
||||
u8 frame_type:4;
|
||||
u8 dev_type:3;
|
||||
u8 _un0:1;
|
||||
|
||||
/* Byte 1 */
|
||||
u8 _un1;
|
||||
|
||||
/* Byte 2 */
|
||||
union {
|
||||
struct {
|
||||
u8 _un20:1;
|
||||
u8 smp_iport:1;
|
||||
u8 stp_iport:1;
|
||||
u8 ssp_iport:1;
|
||||
u8 _un247:4;
|
||||
};
|
||||
u8 initiator_bits;
|
||||
};
|
||||
|
||||
/* Byte 3 */
|
||||
union {
|
||||
struct {
|
||||
u8 _un30:1;
|
||||
u8 smp_tport:1;
|
||||
u8 stp_tport:1;
|
||||
u8 ssp_tport:1;
|
||||
u8 _un347:4;
|
||||
};
|
||||
u8 target_bits;
|
||||
};
|
||||
|
||||
/* Byte 4 - 11 */
|
||||
u8 _un4_11[8];
|
||||
|
||||
/* Byte 12 - 19 */
|
||||
u8 sas_addr[SAS_ADDR_SIZE];
|
||||
|
||||
/* Byte 20 */
|
||||
u8 phy_id;
|
||||
|
||||
u8 _un21_27[7];
|
||||
|
||||
__be32 crc;
|
||||
} __attribute__ ((packed));
|
||||
|
||||
struct ssp_frame_hdr {
|
||||
u8 frame_type;
|
||||
u8 hashed_dest_addr[HASHED_SAS_ADDR_SIZE];
|
||||
u8 _r_a;
|
||||
u8 hashed_src_addr[HASHED_SAS_ADDR_SIZE];
|
||||
__be16 _r_b;
|
||||
|
||||
u8 changing_data_ptr:1;
|
||||
u8 retransmit:1;
|
||||
u8 retry_data_frames:1;
|
||||
u8 _r_c:5;
|
||||
|
||||
u8 num_fill_bytes:2;
|
||||
u8 _r_d:6;
|
||||
|
||||
u32 _r_e;
|
||||
__be16 tag;
|
||||
__be16 tptt;
|
||||
__be32 data_offs;
|
||||
} __attribute__ ((packed));
|
||||
|
||||
struct ssp_response_iu {
|
||||
u8 _r_a[10];
|
||||
|
||||
u8 datapres:2;
|
||||
u8 _r_b:6;
|
||||
|
||||
u8 status;
|
||||
|
||||
u32 _r_c;
|
||||
|
||||
__be32 sense_data_len;
|
||||
__be32 response_data_len;
|
||||
|
||||
u8 resp_data[0];
|
||||
u8 sense_data[0];
|
||||
} __attribute__ ((packed));
|
||||
|
||||
/* ---------- SMP ---------- */
|
||||
|
||||
struct report_general_resp {
|
||||
__be16 change_count;
|
||||
__be16 route_indexes;
|
||||
u8 _r_a;
|
||||
u8 num_phys;
|
||||
|
||||
u8 conf_route_table:1;
|
||||
u8 configuring:1;
|
||||
u8 _r_b:6;
|
||||
|
||||
u8 _r_c;
|
||||
|
||||
u8 enclosure_logical_id[8];
|
||||
|
||||
u8 _r_d[12];
|
||||
} __attribute__ ((packed));
|
||||
|
||||
struct discover_resp {
|
||||
u8 _r_a[5];
|
||||
|
||||
u8 phy_id;
|
||||
__be16 _r_b;
|
||||
|
||||
u8 _r_c:4;
|
||||
u8 attached_dev_type:3;
|
||||
u8 _r_d:1;
|
||||
|
||||
u8 linkrate:4;
|
||||
u8 _r_e:4;
|
||||
|
||||
u8 attached_sata_host:1;
|
||||
u8 iproto:3;
|
||||
u8 _r_f:4;
|
||||
|
||||
u8 attached_sata_dev:1;
|
||||
u8 tproto:3;
|
||||
u8 _r_g:3;
|
||||
u8 attached_sata_ps:1;
|
||||
|
||||
u8 sas_addr[8];
|
||||
u8 attached_sas_addr[8];
|
||||
u8 attached_phy_id;
|
||||
|
||||
u8 _r_h[7];
|
||||
|
||||
u8 hmin_linkrate:4;
|
||||
u8 pmin_linkrate:4;
|
||||
u8 hmax_linkrate:4;
|
||||
u8 pmax_linkrate:4;
|
||||
|
||||
u8 change_count;
|
||||
|
||||
u8 pptv:4;
|
||||
u8 _r_i:3;
|
||||
u8 virtual:1;
|
||||
|
||||
u8 routing_attr:4;
|
||||
u8 _r_j:4;
|
||||
|
||||
u8 conn_type;
|
||||
u8 conn_el_index;
|
||||
u8 conn_phy_link;
|
||||
|
||||
u8 _r_k[8];
|
||||
} __attribute__ ((packed));
|
||||
|
||||
struct report_phy_sata_resp {
|
||||
u8 _r_a[5];
|
||||
|
||||
u8 phy_id;
|
||||
u8 _r_b;
|
||||
|
||||
u8 affil_valid:1;
|
||||
u8 affil_supp:1;
|
||||
u8 _r_c:6;
|
||||
|
||||
u32 _r_d;
|
||||
|
||||
u8 stp_sas_addr[8];
|
||||
|
||||
struct dev_to_host_fis fis;
|
||||
|
||||
u32 _r_e;
|
||||
|
||||
u8 affil_stp_ini_addr[8];
|
||||
|
||||
__be32 crc;
|
||||
} __attribute__ ((packed));
|
||||
|
||||
struct smp_resp {
|
||||
u8 frame_type;
|
||||
u8 function;
|
||||
u8 result;
|
||||
u8 reserved;
|
||||
union {
|
||||
struct report_general_resp rg;
|
||||
struct discover_resp disc;
|
||||
struct report_phy_sata_resp rps;
|
||||
};
|
||||
} __attribute__ ((packed));
|
||||
|
||||
#elif defined(__BIG_ENDIAN_BITFIELD)
|
||||
struct sas_identify_frame {
|
||||
/* Byte 0 */
|
||||
u8 _un0:1;
|
||||
u8 dev_type:3;
|
||||
u8 frame_type:4;
|
||||
|
||||
/* Byte 1 */
|
||||
u8 _un1;
|
||||
|
||||
/* Byte 2 */
|
||||
union {
|
||||
struct {
|
||||
u8 _un247:4;
|
||||
u8 ssp_iport:1;
|
||||
u8 stp_iport:1;
|
||||
u8 smp_iport:1;
|
||||
u8 _un20:1;
|
||||
};
|
||||
u8 initiator_bits;
|
||||
};
|
||||
|
||||
/* Byte 3 */
|
||||
union {
|
||||
struct {
|
||||
u8 _un347:4;
|
||||
u8 ssp_tport:1;
|
||||
u8 stp_tport:1;
|
||||
u8 smp_tport:1;
|
||||
u8 _un30:1;
|
||||
};
|
||||
u8 target_bits;
|
||||
};
|
||||
|
||||
/* Byte 4 - 11 */
|
||||
u8 _un4_11[8];
|
||||
|
||||
/* Byte 12 - 19 */
|
||||
u8 sas_addr[SAS_ADDR_SIZE];
|
||||
|
||||
/* Byte 20 */
|
||||
u8 phy_id;
|
||||
|
||||
u8 _un21_27[7];
|
||||
|
||||
__be32 crc;
|
||||
} __attribute__ ((packed));
|
||||
|
||||
struct ssp_frame_hdr {
|
||||
u8 frame_type;
|
||||
u8 hashed_dest_addr[HASHED_SAS_ADDR_SIZE];
|
||||
u8 _r_a;
|
||||
u8 hashed_src_addr[HASHED_SAS_ADDR_SIZE];
|
||||
__be16 _r_b;
|
||||
|
||||
u8 _r_c:5;
|
||||
u8 retry_data_frames:1;
|
||||
u8 retransmit:1;
|
||||
u8 changing_data_ptr:1;
|
||||
|
||||
u8 _r_d:6;
|
||||
u8 num_fill_bytes:2;
|
||||
|
||||
u32 _r_e;
|
||||
__be16 tag;
|
||||
__be16 tptt;
|
||||
__be32 data_offs;
|
||||
} __attribute__ ((packed));
|
||||
|
||||
struct ssp_response_iu {
|
||||
u8 _r_a[10];
|
||||
|
||||
u8 _r_b:6;
|
||||
u8 datapres:2;
|
||||
|
||||
u8 status;
|
||||
|
||||
u32 _r_c;
|
||||
|
||||
__be32 sense_data_len;
|
||||
__be32 response_data_len;
|
||||
|
||||
u8 resp_data[0];
|
||||
u8 sense_data[0];
|
||||
} __attribute__ ((packed));
|
||||
|
||||
/* ---------- SMP ---------- */
|
||||
|
||||
struct report_general_resp {
|
||||
__be16 change_count;
|
||||
__be16 route_indexes;
|
||||
u8 _r_a;
|
||||
u8 num_phys;
|
||||
|
||||
u8 _r_b:6;
|
||||
u8 configuring:1;
|
||||
u8 conf_route_table:1;
|
||||
|
||||
u8 _r_c;
|
||||
|
||||
u8 enclosure_logical_id[8];
|
||||
|
||||
u8 _r_d[12];
|
||||
} __attribute__ ((packed));
|
||||
|
||||
struct discover_resp {
|
||||
u8 _r_a[5];
|
||||
|
||||
u8 phy_id;
|
||||
__be16 _r_b;
|
||||
|
||||
u8 _r_d:1;
|
||||
u8 attached_dev_type:3;
|
||||
u8 _r_c:4;
|
||||
|
||||
u8 _r_e:4;
|
||||
u8 linkrate:4;
|
||||
|
||||
u8 _r_f:4;
|
||||
u8 iproto:3;
|
||||
u8 attached_sata_host:1;
|
||||
|
||||
u8 attached_sata_ps:1;
|
||||
u8 _r_g:3;
|
||||
u8 tproto:3;
|
||||
u8 attached_sata_dev:1;
|
||||
|
||||
u8 sas_addr[8];
|
||||
u8 attached_sas_addr[8];
|
||||
u8 attached_phy_id;
|
||||
|
||||
u8 _r_h[7];
|
||||
|
||||
u8 pmin_linkrate:4;
|
||||
u8 hmin_linkrate:4;
|
||||
u8 pmax_linkrate:4;
|
||||
u8 hmax_linkrate:4;
|
||||
|
||||
u8 change_count;
|
||||
|
||||
u8 virtual:1;
|
||||
u8 _r_i:3;
|
||||
u8 pptv:4;
|
||||
|
||||
u8 _r_j:4;
|
||||
u8 routing_attr:4;
|
||||
|
||||
u8 conn_type;
|
||||
u8 conn_el_index;
|
||||
u8 conn_phy_link;
|
||||
|
||||
u8 _r_k[8];
|
||||
} __attribute__ ((packed));
|
||||
|
||||
struct report_phy_sata_resp {
|
||||
u8 _r_a[5];
|
||||
|
||||
u8 phy_id;
|
||||
u8 _r_b;
|
||||
|
||||
u8 _r_c:6;
|
||||
u8 affil_supp:1;
|
||||
u8 affil_valid:1;
|
||||
|
||||
u32 _r_d;
|
||||
|
||||
u8 stp_sas_addr[8];
|
||||
|
||||
struct dev_to_host_fis fis;
|
||||
|
||||
u32 _r_e;
|
||||
|
||||
u8 affil_stp_ini_addr[8];
|
||||
|
||||
__be32 crc;
|
||||
} __attribute__ ((packed));
|
||||
|
||||
struct smp_resp {
|
||||
u8 frame_type;
|
||||
u8 function;
|
||||
u8 result;
|
||||
u8 reserved;
|
||||
union {
|
||||
struct report_general_resp rg;
|
||||
struct discover_resp disc;
|
||||
struct report_phy_sata_resp rps;
|
||||
};
|
||||
} __attribute__ ((packed));
|
||||
|
||||
#else
|
||||
#error "Bitfield order not defined!"
|
||||
#endif
|
||||
|
||||
#endif /* _SAS_H_ */
|
@ -429,4 +429,10 @@ struct scsi_lun {
|
||||
/* Used to obtain the PCI location of a device */
|
||||
#define SCSI_IOCTL_GET_PCI 0x5387
|
||||
|
||||
/* Pull a u32 out of a SCSI message (using BE SCSI conventions) */
|
||||
static inline u32 scsi_to_u32(u8 *ptr)
|
||||
{
|
||||
return (ptr[0]<<24) + (ptr[1]<<16) + (ptr[2]<<8) + ptr[3];
|
||||
}
|
||||
|
||||
#endif /* _SCSI_SCSI_H */
|
||||
|
Loading…
Reference in New Issue
Block a user