2006-08-29 18:22:51 +04:00
/*
* 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"
2006-12-07 07:33:20 +03:00
struct kmem_cache * sas_task_cache ;
2006-08-29 18:22:51 +04:00
/*------------ 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 ---------- */
2006-11-22 17:57:56 +03:00
void sas_hae_reset ( struct work_struct * work )
2006-08-29 18:22:51 +04:00
{
2006-11-22 17:57:56 +03:00
struct sas_ha_event * ev =
container_of ( work , struct sas_ha_event , work ) ;
struct sas_ha_struct * ha = ev - > ha ;
2006-08-29 18:22:51 +04:00
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 ;
}
}
2006-10-31 02:18:39 +03:00
INIT_LIST_HEAD ( & sas_ha - > eh_done_q ) ;
2006-08-29 18:22:51 +04:00
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 ) ;
}
2007-01-12 01:14:57 +03:00
int sas_phy_enable ( struct sas_phy * phy , int enable )
{
int ret ;
enum phy_func command ;
if ( enable )
command = PHY_FUNC_LINK_RESET ;
else
command = PHY_FUNC_DISABLE ;
if ( scsi_is_sas_phy_local ( phy ) ) {
struct Scsi_Host * shost = dev_to_shost ( phy - > dev . parent ) ;
struct sas_ha_struct * sas_ha = SHOST_TO_SAS_HA ( shost ) ;
struct asd_sas_phy * asd_phy = sas_ha - > sas_phy [ phy - > number ] ;
struct sas_internal * i =
to_sas_internal ( sas_ha - > core . shost - > transportt ) ;
if ( ! enable ) {
sas_phy_disconnected ( asd_phy ) ;
sas_ha - > notify_phy_event ( asd_phy , PHYE_LOSS_OF_SIGNAL ) ;
}
ret = i - > dft - > lldd_control_phy ( asd_phy , command , NULL ) ;
} else {
struct sas_rphy * rphy = dev_to_rphy ( phy - > dev . parent ) ;
struct domain_device * ddev = sas_find_dev_by_rphy ( rphy ) ;
ret = sas_smp_phy_control ( ddev , phy - > number , command , NULL ) ;
}
return ret ;
}
2006-11-08 04:28:55 +03:00
int sas_phy_reset ( struct sas_phy * phy , int hard_reset )
2006-08-29 18:22:51 +04:00
{
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 ) ;
2006-09-07 04:28:07 +04:00
ret = i - > dft - > lldd_control_phy ( asd_phy , reset_type , NULL ) ;
2006-08-29 18:22:51 +04:00
} else {
struct sas_rphy * rphy = dev_to_rphy ( phy - > dev . parent ) ;
struct domain_device * ddev = sas_find_dev_by_rphy ( rphy ) ;
2006-09-07 04:28:07 +04:00
ret = sas_smp_phy_control ( ddev , phy - > number , reset_type , NULL ) ;
2006-08-29 18:22:51 +04:00
}
return ret ;
}
2007-01-12 01:14:57 +03:00
int sas_set_phy_speed ( struct sas_phy * phy ,
struct sas_phy_linkrates * rates )
2006-09-07 04:28:07 +04:00
{
int ret ;
if ( ( rates - > minimum_linkrate & &
rates - > minimum_linkrate > phy - > maximum_linkrate ) | |
( rates - > maximum_linkrate & &
rates - > maximum_linkrate < phy - > minimum_linkrate ) )
return - EINVAL ;
if ( rates - > minimum_linkrate & &
rates - > minimum_linkrate < phy - > minimum_linkrate_hw )
rates - > minimum_linkrate = phy - > minimum_linkrate_hw ;
if ( rates - > maximum_linkrate & &
rates - > maximum_linkrate > phy - > maximum_linkrate_hw )
rates - > maximum_linkrate = phy - > maximum_linkrate_hw ;
if ( scsi_is_sas_phy_local ( phy ) ) {
struct Scsi_Host * shost = dev_to_shost ( phy - > dev . parent ) ;
struct sas_ha_struct * sas_ha = SHOST_TO_SAS_HA ( shost ) ;
struct asd_sas_phy * asd_phy = sas_ha - > sas_phy [ phy - > number ] ;
struct sas_internal * i =
to_sas_internal ( sas_ha - > core . shost - > transportt ) ;
ret = i - > dft - > lldd_control_phy ( asd_phy , PHY_FUNC_SET_LINK_RATE ,
rates ) ;
} else {
struct sas_rphy * rphy = dev_to_rphy ( phy - > dev . parent ) ;
struct domain_device * ddev = sas_find_dev_by_rphy ( rphy ) ;
ret = sas_smp_phy_control ( ddev , phy - > number ,
PHY_FUNC_LINK_RESET , rates ) ;
}
return ret ;
}
2006-08-29 18:22:51 +04:00
static struct sas_function_template sft = {
2007-01-12 01:14:57 +03:00
. phy_enable = sas_phy_enable ,
2006-08-29 18:22:51 +04:00
. phy_reset = sas_phy_reset ,
2006-09-07 04:28:07 +04:00
. set_phy_speed = sas_set_phy_speed ,
2006-08-29 18:22:51 +04:00
. 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 ) ;