2005-04-17 02:20:36 +04:00
/*
* hosts . c Copyright ( C ) 1992 Drew Eckhardt
* Copyright ( C ) 1993 , 1994 , 1995 Eric Youngdale
* Copyright ( C ) 2002 - 2003 Christoph Hellwig
*
* mid to lowlevel SCSI driver interface
* Initial versions : Drew Eckhardt
* Subsequent revisions : Eric Youngdale
*
* < drew @ colorado . edu >
*
* Jiffies wrap fixes ( host - > resetting ) , 3 Dec 1998 Andrea Arcangeli
* Added QLOGIC QLA1280 SCSI controller kernel host support .
* August 4 , 1999 Fred Lewis , Intel DuPont
*
* Updated to reflect the new initialization scheme for the higher
* level of scsi drivers ( sd / sr / st )
* September 17 , 2000 Torben Mathiasen < tmm @ image . dk >
*
* Restructured scsi_host lists and associated functions .
* September 04 , 2002 Mike Anderson ( andmike @ us . ibm . com )
*/
# include <linux/module.h>
# include <linux/blkdev.h>
# include <linux/kernel.h>
2005-09-06 16:04:26 +04:00
# include <linux/kthread.h>
2005-04-17 02:20:36 +04:00
# include <linux/string.h>
# include <linux/mm.h>
# include <linux/init.h>
# include <linux/completion.h>
# include <linux/transport_class.h>
2005-10-29 22:07:23 +04:00
# include <linux/platform_device.h>
2005-04-17 02:20:36 +04:00
# include <scsi/scsi_device.h>
# include <scsi/scsi_host.h>
# include <scsi/scsi_transport.h>
# include "scsi_priv.h"
# include "scsi_logging.h"
2009-06-16 10:22:14 +04:00
static atomic_t scsi_host_next_hn ; /* host_no for next new host */
2005-04-17 02:20:36 +04:00
2008-02-22 02:13:36 +03:00
static void scsi_host_cls_release ( struct device * dev )
2005-04-17 02:20:36 +04:00
{
2008-02-22 02:13:36 +03:00
put_device ( & class_to_shost ( dev ) - > shost_gendev ) ;
2005-04-17 02:20:36 +04:00
}
static struct class shost_class = {
. name = " scsi_host " ,
2008-02-22 02:13:36 +03:00
. dev_release = scsi_host_cls_release ,
2005-04-17 02:20:36 +04:00
} ;
2005-06-16 22:12:38 +04:00
/**
2007-11-03 21:30:39 +03:00
* scsi_host_set_state - Take the given host through the host state model .
2005-06-16 22:12:38 +04:00
* @ shost : scsi host to change the state of .
* @ state : state to change to .
*
* Returns zero if unsuccessful or an error if the requested
* transition is illegal .
* */
int scsi_host_set_state ( struct Scsi_Host * shost , enum scsi_host_state state )
{
enum scsi_host_state oldstate = shost - > shost_state ;
if ( state = = oldstate )
return 0 ;
switch ( state ) {
case SHOST_CREATED :
/* There are no legal states that come back to
* created . This is the manually initialised start
* state */
goto illegal ;
case SHOST_RUNNING :
switch ( oldstate ) {
case SHOST_CREATED :
case SHOST_RECOVERY :
break ;
default :
goto illegal ;
}
break ;
case SHOST_RECOVERY :
switch ( oldstate ) {
case SHOST_RUNNING :
break ;
default :
goto illegal ;
}
break ;
case SHOST_CANCEL :
switch ( oldstate ) {
case SHOST_CREATED :
case SHOST_RUNNING :
2005-09-19 00:05:20 +04:00
case SHOST_CANCEL_RECOVERY :
2005-06-16 22:12:38 +04:00
break ;
default :
goto illegal ;
}
break ;
case SHOST_DEL :
switch ( oldstate ) {
case SHOST_CANCEL :
2005-09-19 00:05:20 +04:00
case SHOST_DEL_RECOVERY :
2005-06-16 22:12:38 +04:00
break ;
default :
goto illegal ;
}
break ;
2005-09-19 00:05:20 +04:00
case SHOST_CANCEL_RECOVERY :
switch ( oldstate ) {
case SHOST_CANCEL :
case SHOST_RECOVERY :
break ;
default :
goto illegal ;
}
break ;
case SHOST_DEL_RECOVERY :
switch ( oldstate ) {
case SHOST_CANCEL_RECOVERY :
break ;
default :
goto illegal ;
}
break ;
2005-06-16 22:12:38 +04:00
}
shost - > shost_state = state ;
return 0 ;
illegal :
SCSI_LOG_ERROR_RECOVERY ( 1 ,
2005-10-02 20:45:08 +04:00
shost_printk ( KERN_ERR , shost ,
" Illegal host state transition "
" %s->%s \n " ,
scsi_host_state_name ( oldstate ) ,
scsi_host_state_name ( state ) ) ) ;
2005-06-16 22:12:38 +04:00
return - EINVAL ;
}
EXPORT_SYMBOL ( scsi_host_set_state ) ;
2005-04-17 02:20:36 +04:00
/**
* scsi_remove_host - remove a scsi host
* @ shost : a pointer to a scsi host to remove
* */
void scsi_remove_host ( struct Scsi_Host * shost )
{
2005-09-19 00:05:20 +04:00
unsigned long flags ;
2006-01-11 15:16:10 +03:00
mutex_lock ( & shost - > scan_mutex ) ;
2005-09-19 00:05:20 +04:00
spin_lock_irqsave ( shost - > host_lock , flags ) ;
if ( scsi_host_set_state ( shost , SHOST_CANCEL ) )
if ( scsi_host_set_state ( shost , SHOST_CANCEL_RECOVERY ) ) {
spin_unlock_irqrestore ( shost - > host_lock , flags ) ;
2006-01-11 15:16:10 +03:00
mutex_unlock ( & shost - > scan_mutex ) ;
2005-09-19 00:05:20 +04:00
return ;
}
spin_unlock_irqrestore ( shost - > host_lock , flags ) ;
2006-01-11 15:16:10 +03:00
mutex_unlock ( & shost - > scan_mutex ) ;
2005-04-17 02:20:36 +04:00
scsi_forget_host ( shost ) ;
scsi_proc_host_rm ( shost ) ;
2005-09-19 00:05:20 +04:00
spin_lock_irqsave ( shost - > host_lock , flags ) ;
if ( scsi_host_set_state ( shost , SHOST_DEL ) )
BUG_ON ( scsi_host_set_state ( shost , SHOST_DEL_RECOVERY ) ) ;
spin_unlock_irqrestore ( shost - > host_lock , flags ) ;
2005-04-17 02:20:36 +04:00
transport_unregister_device ( & shost - > shost_gendev ) ;
2008-02-22 02:13:36 +03:00
device_unregister ( & shost - > shost_dev ) ;
2005-04-17 02:20:36 +04:00
device_del ( & shost - > shost_gendev ) ;
}
EXPORT_SYMBOL ( scsi_remove_host ) ;
/**
* scsi_add_host - add a scsi host
* @ shost : scsi host pointer to add
* @ dev : a struct device of type scsi class
*
* Return value :
* 0 on success / ! = 0 for error
* */
int scsi_add_host ( struct Scsi_Host * shost , struct device * dev )
{
struct scsi_host_template * sht = shost - > hostt ;
int error = - EINVAL ;
printk ( KERN_INFO " scsi%d : %s \n " , shost - > host_no ,
sht - > info ? sht - > info ( shost ) : sht - > name ) ;
if ( ! shost - > can_queue ) {
printk ( KERN_ERR " %s: can_queue = 0 no longer supported \n " ,
sht - > name ) ;
2008-04-21 19:57:20 +04:00
goto fail ;
2005-04-17 02:20:36 +04:00
}
2008-04-21 19:57:20 +04:00
error = scsi_setup_command_freelist ( shost ) ;
if ( error )
goto fail ;
2005-04-17 02:20:36 +04:00
if ( ! shost - > shost_gendev . parent )
shost - > shost_gendev . parent = dev ? dev : & platform_bus ;
error = device_add ( & shost - > shost_gendev ) ;
if ( error )
goto out ;
2005-06-16 22:12:38 +04:00
scsi_host_set_state ( shost , SHOST_RUNNING ) ;
2005-04-17 02:20:36 +04:00
get_device ( shost - > shost_gendev . parent ) ;
2008-02-22 02:13:36 +03:00
error = device_add ( & shost - > shost_dev ) ;
2005-04-17 02:20:36 +04:00
if ( error )
goto out_del_gendev ;
get_device ( & shost - > shost_gendev ) ;
2008-03-22 00:18:23 +03:00
if ( shost - > transportt - > host_size ) {
shost - > shost_data = kzalloc ( shost - > transportt - > host_size ,
GFP_KERNEL ) ;
if ( shost - > shost_data = = NULL ) {
error = - ENOMEM ;
2008-02-22 02:13:36 +03:00
goto out_del_dev ;
2008-03-22 00:18:23 +03:00
}
}
2005-04-17 02:20:36 +04:00
if ( shost - > transportt - > create_work_queue ) {
2008-05-02 08:02:41 +04:00
snprintf ( shost - > work_q_name , sizeof ( shost - > work_q_name ) ,
" scsi_wq_%d " , shost - > host_no ) ;
2005-04-17 02:20:36 +04:00
shost - > work_q = create_singlethread_workqueue (
shost - > work_q_name ) ;
2008-03-22 00:18:23 +03:00
if ( ! shost - > work_q ) {
error = - EINVAL ;
2005-04-17 02:20:36 +04:00
goto out_free_shost_data ;
2008-03-22 00:18:23 +03:00
}
2005-04-17 02:20:36 +04:00
}
error = scsi_sysfs_add_host ( shost ) ;
if ( error )
goto out_destroy_host ;
scsi_proc_host_add ( shost ) ;
return error ;
out_destroy_host :
if ( shost - > work_q )
destroy_workqueue ( shost - > work_q ) ;
out_free_shost_data :
kfree ( shost - > shost_data ) ;
2008-02-22 02:13:36 +03:00
out_del_dev :
device_del ( & shost - > shost_dev ) ;
2005-04-17 02:20:36 +04:00
out_del_gendev :
device_del ( & shost - > shost_gendev ) ;
out :
2008-04-21 19:57:20 +04:00
scsi_destroy_command_freelist ( shost ) ;
fail :
2005-04-17 02:20:36 +04:00
return error ;
}
EXPORT_SYMBOL ( scsi_add_host ) ;
static void scsi_host_dev_release ( struct device * dev )
{
struct Scsi_Host * shost = dev_to_shost ( dev ) ;
struct device * parent = dev - > parent ;
2009-02-28 00:51:42 +03:00
scsi_proc_hostdir_rm ( shost - > hostt ) ;
2005-09-06 16:04:26 +04:00
if ( shost - > ehandler )
kthread_stop ( shost - > ehandler ) ;
2005-04-17 02:20:36 +04:00
if ( shost - > work_q )
destroy_workqueue ( shost - > work_q ) ;
2006-11-16 13:24:10 +03:00
if ( shost - > uspace_req_q ) {
kfree ( shost - > uspace_req_q - > queuedata ) ;
scsi_free_queue ( shost - > uspace_req_q ) ;
}
2005-04-17 02:20:36 +04:00
scsi_destroy_command_freelist ( shost ) ;
2006-08-30 17:45:51 +04:00
if ( shost - > bqt )
blk_free_tags ( shost - > bqt ) ;
2005-04-17 02:20:36 +04:00
kfree ( shost - > shost_data ) ;
if ( parent )
put_device ( parent ) ;
kfree ( shost ) ;
}
2008-07-04 10:47:33 +04:00
static struct device_type scsi_host_type = {
2008-03-18 16:32:28 +03:00
. name = " scsi_host " ,
. release = scsi_host_dev_release ,
} ;
2005-04-17 02:20:36 +04:00
/**
* scsi_host_alloc - register a scsi host adapter instance .
* @ sht : pointer to scsi host template
* @ privsize : extra bytes to allocate for driver
*
* Note :
* Allocate a new Scsi_Host and perform basic initialization .
* The host is not published to the scsi midlayer until scsi_add_host
* is called .
*
* Return value :
* Pointer to a new Scsi_Host
* */
struct Scsi_Host * scsi_host_alloc ( struct scsi_host_template * sht , int privsize )
{
struct Scsi_Host * shost ;
2005-10-21 11:22:08 +04:00
gfp_t gfp_mask = GFP_KERNEL ;
int rval ;
2005-04-17 02:20:36 +04:00
if ( sht - > unchecked_isa_dma & & privsize )
gfp_mask | = __GFP_DMA ;
2006-01-16 18:31:18 +03:00
shost = kzalloc ( sizeof ( struct Scsi_Host ) + privsize , gfp_mask ) ;
2005-04-17 02:20:36 +04:00
if ( ! shost )
return NULL ;
2006-11-04 22:11:36 +03:00
shost - > host_lock = & shost - > default_lock ;
spin_lock_init ( shost - > host_lock ) ;
2005-06-16 22:12:38 +04:00
shost - > shost_state = SHOST_CREATED ;
2005-04-17 02:20:36 +04:00
INIT_LIST_HEAD ( & shost - > __devices ) ;
INIT_LIST_HEAD ( & shost - > __targets ) ;
INIT_LIST_HEAD ( & shost - > eh_cmd_q ) ;
INIT_LIST_HEAD ( & shost - > starved_list ) ;
init_waitqueue_head ( & shost - > host_wait ) ;
2006-01-11 15:16:10 +03:00
mutex_init ( & shost - > scan_mutex ) ;
2005-04-17 02:20:36 +04:00
2009-06-16 10:22:14 +04:00
/*
* subtract one because we increment first then return , but we need to
* know what the next host number was before increment
*/
shost - > host_no = atomic_inc_return ( & scsi_host_next_hn ) - 1 ;
2005-04-17 02:20:36 +04:00
shost - > dma_channel = 0xff ;
/* These three are default values which can be overridden */
shost - > max_channel = 0 ;
shost - > max_id = 8 ;
shost - > max_lun = 8 ;
/* Give each shost a default transportt */
shost - > transportt = & blank_transport_template ;
/*
* All drivers right now should be able to handle 12 byte
* commands . Every so often there are requests for 16 byte
* commands , but individual low - level drivers need to certify that
* they actually do something sensible with such commands .
*/
shost - > max_cmd_len = 12 ;
shost - > hostt = sht ;
shost - > this_id = sht - > this_id ;
shost - > can_queue = sht - > can_queue ;
shost - > sg_tablesize = sht - > sg_tablesize ;
shost - > cmd_per_lun = sht - > cmd_per_lun ;
shost - > unchecked_isa_dma = sht - > unchecked_isa_dma ;
shost - > use_clustering = sht - > use_clustering ;
shost - > ordered_tag = sht - > ordered_tag ;
2007-09-26 07:45:53 +04:00
if ( sht - > supported_mode = = MODE_UNKNOWN )
/* means we didn't set it ... default to INITIATOR */
shost - > active_mode = MODE_INITIATOR ;
else
shost - > active_mode = sht - > supported_mode ;
2005-04-17 02:20:36 +04:00
if ( sht - > max_host_blocked )
shost - > max_host_blocked = sht - > max_host_blocked ;
else
shost - > max_host_blocked = SCSI_DEFAULT_HOST_BLOCKED ;
/*
* If the driver imposes no hard sector transfer limit , start at
* machine infinity initially .
*/
if ( sht - > max_sectors )
shost - > max_sectors = sht - > max_sectors ;
else
shost - > max_sectors = SCSI_DEFAULT_MAX_SECTORS ;
/*
* assume a 4 GB boundary , if not set
*/
if ( sht - > dma_boundary )
shost - > dma_boundary = sht - > dma_boundary ;
else
shost - > dma_boundary = 0xffffffff ;
device_initialize ( & shost - > shost_gendev ) ;
2008-12-04 00:41:36 +03:00
dev_set_name ( & shost - > shost_gendev , " host%d " , shost - > host_no ) ;
2008-03-18 16:32:28 +03:00
# ifndef CONFIG_SYSFS_DEPRECATED
shost - > shost_gendev . bus = & scsi_bus_type ;
# endif
shost - > shost_gendev . type = & scsi_host_type ;
2005-04-17 02:20:36 +04:00
2008-02-22 02:13:36 +03:00
device_initialize ( & shost - > shost_dev ) ;
shost - > shost_dev . parent = & shost - > shost_gendev ;
shost - > shost_dev . class = & shost_class ;
2008-12-04 00:41:36 +03:00
dev_set_name ( & shost - > shost_dev , " host%d " , shost - > host_no ) ;
2008-03-18 16:32:28 +03:00
shost - > shost_dev . groups = scsi_sysfs_shost_attr_groups ;
2005-04-17 02:20:36 +04:00
2005-09-06 16:04:26 +04:00
shost - > ehandler = kthread_run ( scsi_error_handler , shost ,
" scsi_eh_%d " , shost - > host_no ) ;
if ( IS_ERR ( shost - > ehandler ) ) {
rval = PTR_ERR ( shost - > ehandler ) ;
2008-04-21 19:57:20 +04:00
goto fail_kfree ;
2005-09-06 16:04:26 +04:00
}
2005-04-17 02:20:36 +04:00
scsi_proc_hostdir_add ( shost - > hostt ) ;
return shost ;
fail_kfree :
kfree ( shost ) ;
return NULL ;
}
EXPORT_SYMBOL ( scsi_host_alloc ) ;
struct Scsi_Host * scsi_register ( struct scsi_host_template * sht , int privsize )
{
struct Scsi_Host * shost = scsi_host_alloc ( sht , privsize ) ;
if ( ! sht - > detect ) {
printk ( KERN_WARNING " scsi_register() called on new-style "
" template for driver %s \n " , sht - > name ) ;
dump_stack ( ) ;
}
if ( shost )
list_add_tail ( & shost - > sht_legacy_list , & sht - > legacy_hosts ) ;
return shost ;
}
EXPORT_SYMBOL ( scsi_register ) ;
void scsi_unregister ( struct Scsi_Host * shost )
{
list_del ( & shost - > sht_legacy_list ) ;
scsi_host_put ( shost ) ;
}
EXPORT_SYMBOL ( scsi_unregister ) ;
2008-02-22 02:13:36 +03:00
static int __scsi_host_match ( struct device * dev , void * data )
2008-01-22 09:01:34 +03:00
{
struct Scsi_Host * p ;
unsigned short * hostnum = ( unsigned short * ) data ;
2008-02-22 02:13:36 +03:00
p = class_to_shost ( dev ) ;
2008-01-22 09:01:34 +03:00
return p - > host_no = = * hostnum ;
}
2005-04-17 02:20:36 +04:00
/**
* scsi_host_lookup - get a reference to a Scsi_Host by host no
* @ hostnum : host number to locate
*
* Return value :
* A pointer to located Scsi_Host or NULL .
2008-06-12 04:21:00 +04:00
*
* The caller must do a scsi_host_put ( ) to drop the reference
* that scsi_host_get ( ) took . The put_device ( ) below dropped
* the reference from class_find_device ( ) .
2005-04-17 02:20:36 +04:00
* */
struct Scsi_Host * scsi_host_lookup ( unsigned short hostnum )
{
2008-02-22 02:13:36 +03:00
struct device * cdev ;
2008-08-08 04:49:30 +04:00
struct Scsi_Host * shost = NULL ;
2005-04-17 02:20:36 +04:00
2008-05-23 01:21:08 +04:00
cdev = class_find_device ( & shost_class , NULL , & hostnum ,
__scsi_host_match ) ;
2008-06-12 04:21:00 +04:00
if ( cdev ) {
2008-01-22 09:01:34 +03:00
shost = scsi_host_get ( class_to_shost ( cdev ) ) ;
2008-06-12 04:21:00 +04:00
put_device ( cdev ) ;
}
2005-04-17 02:20:36 +04:00
return shost ;
}
EXPORT_SYMBOL ( scsi_host_lookup ) ;
/**
* scsi_host_get - inc a Scsi_Host ref count
* @ shost : Pointer to Scsi_Host to inc .
* */
struct Scsi_Host * scsi_host_get ( struct Scsi_Host * shost )
{
2005-06-16 22:12:38 +04:00
if ( ( shost - > shost_state = = SHOST_DEL ) | |
2005-04-17 02:20:36 +04:00
! get_device ( & shost - > shost_gendev ) )
return NULL ;
return shost ;
}
EXPORT_SYMBOL ( scsi_host_get ) ;
/**
* scsi_host_put - dec a Scsi_Host ref count
* @ shost : Pointer to Scsi_Host to dec .
* */
void scsi_host_put ( struct Scsi_Host * shost )
{
put_device ( & shost - > shost_gendev ) ;
}
EXPORT_SYMBOL ( scsi_host_put ) ;
int scsi_init_hosts ( void )
{
return class_register ( & shost_class ) ;
}
void scsi_exit_hosts ( void )
{
class_unregister ( & shost_class ) ;
}
int scsi_is_host_device ( const struct device * dev )
{
2008-03-18 16:32:28 +03:00
return dev - > type = = & scsi_host_type ;
2005-04-17 02:20:36 +04:00
}
EXPORT_SYMBOL ( scsi_is_host_device ) ;
/**
* scsi_queue_work - Queue work to the Scsi_Host workqueue .
* @ shost : Pointer to Scsi_Host .
* @ work : Work to queue for execution .
*
* Return value :
2006-08-04 21:09:24 +04:00
* 1 - work queued for execution
* 0 - work is already queued
* - EINVAL - work queue doesn ' t exist
2005-04-17 02:20:36 +04:00
* */
int scsi_queue_work ( struct Scsi_Host * shost , struct work_struct * work )
{
if ( unlikely ( ! shost - > work_q ) ) {
printk ( KERN_ERR
" ERROR: Scsi host '%s' attempted to queue scsi-work, "
" when no workqueue created. \n " , shost - > hostt - > name ) ;
dump_stack ( ) ;
return - EINVAL ;
}
return queue_work ( shost - > work_q , work ) ;
}
EXPORT_SYMBOL_GPL ( scsi_queue_work ) ;
/**
* scsi_flush_work - Flush a Scsi_Host ' s workqueue .
* @ shost : Pointer to Scsi_Host .
* */
void scsi_flush_work ( struct Scsi_Host * shost )
{
if ( ! shost - > work_q ) {
printk ( KERN_ERR
" ERROR: Scsi host '%s' attempted to flush scsi-work, "
" when no workqueue created. \n " , shost - > hostt - > name ) ;
dump_stack ( ) ;
return ;
}
flush_workqueue ( shost - > work_q ) ;
}
EXPORT_SYMBOL_GPL ( scsi_flush_work ) ;