2008-05-02 01:49:59 +04:00
/*
* Basic HP / COMPAQ MSA 1000 support . This is only needed if your HW cannot be
* upgraded .
*
* Copyright ( C ) 2006 Red Hat , Inc . All rights reserved .
* Copyright ( C ) 2006 Mike Christie
2008-07-18 03:53:09 +04:00
* Copyright ( C ) 2008 Hannes Reinecke < hare @ suse . de >
2008-05-02 01:49:59 +04:00
*
* 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 , 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 ; see the file COPYING . If not , write to
* the Free Software Foundation , 675 Mass Ave , Cambridge , MA 0213 9 , USA .
*/
# include <scsi/scsi.h>
# include <scsi/scsi_dbg.h>
# include <scsi/scsi_eh.h>
# include <scsi/scsi_dh.h>
2008-07-18 03:53:09 +04:00
# define HP_SW_NAME "hp_sw"
2008-05-02 01:49:59 +04:00
2008-07-18 03:53:09 +04:00
# define HP_SW_TIMEOUT (60 * HZ)
# define HP_SW_RETRIES 3
# define HP_SW_PATH_UNINITIALIZED -1
# define HP_SW_PATH_ACTIVE 0
# define HP_SW_PATH_PASSIVE 1
2008-05-02 01:49:59 +04:00
struct hp_sw_dh_data {
unsigned char sense [ SCSI_SENSE_BUFFERSIZE ] ;
2008-07-18 03:53:09 +04:00
int path_state ;
2008-05-02 01:49:59 +04:00
int retries ;
} ;
static inline struct hp_sw_dh_data * get_hp_sw_data ( struct scsi_device * sdev )
{
struct scsi_dh_data * scsi_dh_data = sdev - > scsi_dh_data ;
BUG_ON ( scsi_dh_data = = NULL ) ;
return ( ( struct hp_sw_dh_data * ) scsi_dh_data - > buf ) ;
}
2008-07-18 03:53:09 +04:00
/*
* tur_done - Handle TEST UNIT READY return status
* @ sdev : sdev the command has been sent to
* @ errors : blk error code
*
* Returns SCSI_DH_DEV_OFFLINED if the sdev is on the passive path
*/
static int tur_done ( struct scsi_device * sdev , unsigned char * sense )
2008-05-02 01:49:59 +04:00
{
struct scsi_sense_hdr sshdr ;
2008-07-18 03:53:09 +04:00
int ret ;
2008-05-02 01:49:59 +04:00
2008-07-18 03:53:09 +04:00
ret = scsi_normalize_sense ( sense , SCSI_SENSE_BUFFERSIZE , & sshdr ) ;
if ( ! ret ) {
sdev_printk ( KERN_WARNING , sdev ,
" %s: sending tur failed, no sense available \n " ,
HP_SW_NAME ) ;
ret = SCSI_DH_IO ;
2008-05-02 01:49:59 +04:00
goto done ;
2008-07-18 03:53:09 +04:00
}
2008-05-02 01:49:59 +04:00
switch ( sshdr . sense_key ) {
2008-07-18 03:53:09 +04:00
case UNIT_ATTENTION :
ret = SCSI_DH_IMM_RETRY ;
break ;
2008-05-02 01:49:59 +04:00
case NOT_READY :
2008-07-18 03:53:09 +04:00
if ( ( sshdr . asc = = 0x04 ) & & ( sshdr . ascq = = 2 ) ) {
/*
* LUN not ready - Initialization command required
*
* This is the passive path
*/
ret = SCSI_DH_DEV_OFFLINED ;
2008-05-02 01:49:59 +04:00
break ;
}
2008-07-18 03:53:09 +04:00
/* Fallthrough */
2008-05-02 01:49:59 +04:00
default :
2008-07-18 03:53:09 +04:00
sdev_printk ( KERN_WARNING , sdev ,
" %s: sending tur failed, sense %x/%x/%x \n " ,
HP_SW_NAME , sshdr . sense_key , sshdr . asc ,
sshdr . ascq ) ;
break ;
2008-05-02 01:49:59 +04:00
}
done :
2008-07-18 03:53:09 +04:00
return ret ;
}
/*
* hp_sw_tur - Send TEST UNIT READY
* @ sdev : sdev command should be sent to
*
* Use the TEST UNIT READY command to determine
* the path state .
*/
static int hp_sw_tur ( struct scsi_device * sdev , struct hp_sw_dh_data * h )
{
struct request * req ;
int ret ;
2008-12-09 17:52:15 +03:00
retry :
2008-07-18 03:53:09 +04:00
req = blk_get_request ( sdev - > request_queue , WRITE , GFP_NOIO ) ;
if ( ! req )
return SCSI_DH_RES_TEMP_UNAVAIL ;
req - > cmd_type = REQ_TYPE_BLOCK_PC ;
2008-08-20 03:45:30 +04:00
req - > cmd_flags | = REQ_FAILFAST_DEV | REQ_FAILFAST_TRANSPORT |
REQ_FAILFAST_DRIVER ;
2008-07-18 03:53:09 +04:00
req - > cmd_len = COMMAND_SIZE ( TEST_UNIT_READY ) ;
req - > cmd [ 0 ] = TEST_UNIT_READY ;
req - > timeout = HP_SW_TIMEOUT ;
req - > sense = h - > sense ;
memset ( req - > sense , 0 , SCSI_SENSE_BUFFERSIZE ) ;
req - > sense_len = 0 ;
ret = blk_execute_rq ( req - > q , NULL , req , 1 ) ;
if ( ret = = - EIO ) {
if ( req - > sense_len > 0 ) {
ret = tur_done ( sdev , h - > sense ) ;
} else {
sdev_printk ( KERN_WARNING , sdev ,
" %s: sending tur failed with %x \n " ,
HP_SW_NAME , req - > errors ) ;
ret = SCSI_DH_IO ;
}
} else {
h - > path_state = HP_SW_PATH_ACTIVE ;
ret = SCSI_DH_OK ;
}
2008-12-09 17:52:15 +03:00
if ( ret = = SCSI_DH_IMM_RETRY ) {
blk_put_request ( req ) ;
2008-07-18 03:53:09 +04:00
goto retry ;
2008-12-09 17:52:15 +03:00
}
2008-07-18 03:53:09 +04:00
if ( ret = = SCSI_DH_DEV_OFFLINED ) {
h - > path_state = HP_SW_PATH_PASSIVE ;
ret = SCSI_DH_OK ;
}
blk_put_request ( req ) ;
return ret ;
}
/*
* start_done - Handle START STOP UNIT return status
* @ sdev : sdev the command has been sent to
* @ errors : blk error code
*/
static int start_done ( struct scsi_device * sdev , unsigned char * sense )
{
struct scsi_sense_hdr sshdr ;
int rc ;
rc = scsi_normalize_sense ( sense , SCSI_SENSE_BUFFERSIZE , & sshdr ) ;
if ( ! rc ) {
sdev_printk ( KERN_WARNING , sdev ,
" %s: sending start_stop_unit failed, "
" no sense available \n " ,
HP_SW_NAME ) ;
return SCSI_DH_IO ;
}
switch ( sshdr . sense_key ) {
case NOT_READY :
if ( ( sshdr . asc = = 0x04 ) & & ( sshdr . ascq = = 3 ) ) {
/*
* LUN not ready - manual intervention required
*
* Switch - over in progress , retry .
*/
rc = SCSI_DH_RETRY ;
break ;
}
/* fall through */
default :
sdev_printk ( KERN_WARNING , sdev ,
" %s: sending start_stop_unit failed, sense %x/%x/%x \n " ,
HP_SW_NAME , sshdr . sense_key , sshdr . asc ,
sshdr . ascq ) ;
2008-05-02 01:49:59 +04:00
rc = SCSI_DH_IO ;
}
2008-07-18 03:53:09 +04:00
2008-05-02 01:49:59 +04:00
return rc ;
}
2008-07-18 03:53:09 +04:00
/*
* hp_sw_start_stop - Send START STOP UNIT command
* @ sdev : sdev command should be sent to
*
* Sending START STOP UNIT activates the SP .
*/
static int hp_sw_start_stop ( struct scsi_device * sdev , struct hp_sw_dh_data * h )
2008-05-02 01:49:59 +04:00
{
struct request * req ;
2008-07-18 03:53:09 +04:00
int ret , retry ;
2008-05-02 01:49:59 +04:00
2008-12-09 17:52:15 +03:00
retry :
2008-07-18 03:53:09 +04:00
req = blk_get_request ( sdev - > request_queue , WRITE , GFP_NOIO ) ;
2008-05-02 01:49:59 +04:00
if ( ! req )
2008-07-18 03:53:09 +04:00
return SCSI_DH_RES_TEMP_UNAVAIL ;
2008-05-02 01:49:59 +04:00
req - > cmd_type = REQ_TYPE_BLOCK_PC ;
2008-08-20 03:45:30 +04:00
req - > cmd_flags | = REQ_FAILFAST_DEV | REQ_FAILFAST_TRANSPORT |
REQ_FAILFAST_DRIVER ;
2008-05-02 01:49:59 +04:00
req - > cmd_len = COMMAND_SIZE ( START_STOP ) ;
req - > cmd [ 0 ] = START_STOP ;
req - > cmd [ 4 ] = 1 ; /* Start spin cycle */
req - > timeout = HP_SW_TIMEOUT ;
req - > sense = h - > sense ;
memset ( req - > sense , 0 , SCSI_SENSE_BUFFERSIZE ) ;
req - > sense_len = 0 ;
2008-07-18 03:53:09 +04:00
retry = h - > retries ;
2008-05-02 01:49:59 +04:00
ret = blk_execute_rq ( req - > q , NULL , req , 1 ) ;
2008-07-18 03:53:09 +04:00
if ( ret = = - EIO ) {
if ( req - > sense_len > 0 ) {
ret = start_done ( sdev , h - > sense ) ;
} else {
sdev_printk ( KERN_WARNING , sdev ,
" %s: sending start_stop_unit failed with %x \n " ,
HP_SW_NAME , req - > errors ) ;
ret = SCSI_DH_IO ;
}
} else
ret = SCSI_DH_OK ;
if ( ret = = SCSI_DH_RETRY ) {
2008-12-09 17:52:15 +03:00
if ( - - retry ) {
blk_put_request ( req ) ;
2008-07-18 03:53:09 +04:00
goto retry ;
2008-12-09 17:52:15 +03:00
}
2008-05-02 01:49:59 +04:00
ret = SCSI_DH_IO ;
2008-07-18 03:53:09 +04:00
}
blk_put_request ( req ) ;
return ret ;
}
static int hp_sw_prep_fn ( struct scsi_device * sdev , struct request * req )
{
struct hp_sw_dh_data * h = get_hp_sw_data ( sdev ) ;
int ret = BLKPREP_OK ;
if ( h - > path_state ! = HP_SW_PATH_ACTIVE ) {
ret = BLKPREP_KILL ;
req - > cmd_flags | = REQ_QUIET ;
}
return ret ;
}
/*
* hp_sw_activate - Activate a path
* @ sdev : sdev on the path to be activated
*
* The HP Active / Passive firmware is pretty simple ;
* the passive path reports NOT READY with sense codes
* 0x04 / 0x02 ; a START STOP UNIT command will then
* activate the passive path ( and deactivate the
* previously active one ) .
*/
static int hp_sw_activate ( struct scsi_device * sdev )
{
int ret = SCSI_DH_OK ;
struct hp_sw_dh_data * h = get_hp_sw_data ( sdev ) ;
ret = hp_sw_tur ( sdev , h ) ;
if ( ret = = SCSI_DH_OK & & h - > path_state = = HP_SW_PATH_PASSIVE ) {
ret = hp_sw_start_stop ( sdev , h ) ;
if ( ret = = SCSI_DH_OK )
sdev_printk ( KERN_INFO , sdev ,
" %s: activated path \n " ,
HP_SW_NAME ) ;
}
2008-05-02 01:49:59 +04:00
return ret ;
}
2008-08-11 22:59:21 +04:00
static const struct scsi_dh_devlist hp_sw_dh_data_list [ ] = {
2008-07-18 03:53:09 +04:00
{ " COMPAQ " , " MSA1000 VOLUME " } ,
{ " COMPAQ " , " HSV110 " } ,
{ " HP " , " HSV100 " } ,
2008-05-02 01:49:59 +04:00
{ " DEC " , " HSG80 " } ,
{ NULL , NULL } ,
} ;
2008-07-18 03:52:51 +04:00
static int hp_sw_bus_attach ( struct scsi_device * sdev ) ;
static void hp_sw_bus_detach ( struct scsi_device * sdev ) ;
2008-05-02 01:49:59 +04:00
static struct scsi_device_handler hp_sw_dh = {
. name = HP_SW_NAME ,
. module = THIS_MODULE ,
2008-07-18 03:52:51 +04:00
. devlist = hp_sw_dh_data_list ,
. attach = hp_sw_bus_attach ,
. detach = hp_sw_bus_detach ,
2008-05-02 01:49:59 +04:00
. activate = hp_sw_activate ,
2008-07-18 03:53:09 +04:00
. prep_fn = hp_sw_prep_fn ,
2008-05-02 01:49:59 +04:00
} ;
2008-07-18 03:52:51 +04:00
static int hp_sw_bus_attach ( struct scsi_device * sdev )
2008-05-02 01:49:59 +04:00
{
struct scsi_dh_data * scsi_dh_data ;
2008-07-18 03:53:09 +04:00
struct hp_sw_dh_data * h ;
2008-05-02 01:49:59 +04:00
unsigned long flags ;
2008-07-18 03:53:09 +04:00
int ret ;
2008-05-02 01:49:59 +04:00
2008-07-18 03:52:51 +04:00
scsi_dh_data = kzalloc ( sizeof ( struct scsi_device_handler * )
+ sizeof ( struct hp_sw_dh_data ) , GFP_KERNEL ) ;
if ( ! scsi_dh_data ) {
2008-07-18 03:53:09 +04:00
sdev_printk ( KERN_ERR , sdev , " %s: Attach Failed \n " ,
2008-07-18 03:52:51 +04:00
HP_SW_NAME ) ;
2008-07-17 04:35:08 +04:00
return 0 ;
2008-07-18 03:52:51 +04:00
}
2008-07-17 04:35:08 +04:00
2008-07-18 03:52:51 +04:00
scsi_dh_data - > scsi_dh = & hp_sw_dh ;
2008-07-18 03:53:09 +04:00
h = ( struct hp_sw_dh_data * ) scsi_dh_data - > buf ;
h - > path_state = HP_SW_PATH_UNINITIALIZED ;
h - > retries = HP_SW_RETRIES ;
ret = hp_sw_tur ( sdev , h ) ;
if ( ret ! = SCSI_DH_OK | | h - > path_state = = HP_SW_PATH_UNINITIALIZED )
goto failed ;
if ( ! try_module_get ( THIS_MODULE ) )
goto failed ;
2008-07-18 03:52:51 +04:00
spin_lock_irqsave ( sdev - > request_queue - > queue_lock , flags ) ;
sdev - > scsi_dh_data = scsi_dh_data ;
spin_unlock_irqrestore ( sdev - > request_queue - > queue_lock , flags ) ;
2008-05-02 01:49:59 +04:00
2008-07-18 03:53:09 +04:00
sdev_printk ( KERN_INFO , sdev , " %s: attached to %s path \n " ,
HP_SW_NAME , h - > path_state = = HP_SW_PATH_ACTIVE ?
" active " : " passive " ) ;
2008-05-02 01:49:59 +04:00
2008-07-18 03:52:51 +04:00
return 0 ;
2008-07-18 03:53:09 +04:00
failed :
kfree ( scsi_dh_data ) ;
sdev_printk ( KERN_ERR , sdev , " %s: not attached \n " ,
HP_SW_NAME ) ;
return - EINVAL ;
2008-07-18 03:52:51 +04:00
}
2008-05-02 01:49:59 +04:00
2008-07-18 03:52:51 +04:00
static void hp_sw_bus_detach ( struct scsi_device * sdev )
{
struct scsi_dh_data * scsi_dh_data ;
unsigned long flags ;
2008-05-02 01:49:59 +04:00
2008-07-18 03:52:51 +04:00
spin_lock_irqsave ( sdev - > request_queue - > queue_lock , flags ) ;
scsi_dh_data = sdev - > scsi_dh_data ;
sdev - > scsi_dh_data = NULL ;
spin_unlock_irqrestore ( sdev - > request_queue - > queue_lock , flags ) ;
module_put ( THIS_MODULE ) ;
2008-05-02 01:49:59 +04:00
2008-07-18 03:53:09 +04:00
sdev_printk ( KERN_NOTICE , sdev , " %s: Detached \n " , HP_SW_NAME ) ;
2008-05-02 01:49:59 +04:00
2008-07-18 03:52:51 +04:00
kfree ( scsi_dh_data ) ;
2008-05-02 01:49:59 +04:00
}
static int __init hp_sw_init ( void )
{
return scsi_register_device_handler ( & hp_sw_dh ) ;
}
static void __exit hp_sw_exit ( void )
{
scsi_unregister_device_handler ( & hp_sw_dh ) ;
}
module_init ( hp_sw_init ) ;
module_exit ( hp_sw_exit ) ;
2008-07-18 03:53:09 +04:00
MODULE_DESCRIPTION ( " HP Active/Passive driver " ) ;
2008-05-02 01:49:59 +04:00
MODULE_AUTHOR ( " Mike Christie <michaelc@cs.wisc.edu " ) ;
MODULE_LICENSE ( " GPL " ) ;