2005-04-17 02:20:36 +04:00
/*
* Parallel SCSI ( SPI ) transport specific attributes exported to sysfs .
*
* Copyright ( c ) 2003 Silicon Graphics , Inc . All rights reserved .
* Copyright ( c ) 2004 , 2005 James Bottomley < James . Bottomley @ SteelEye . com >
*
* 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/ctype.h>
# include <linux/init.h>
# include <linux/module.h>
# include <linux/workqueue.h>
2005-05-02 03:58:15 +04:00
# include <linux/blkdev.h>
2006-01-14 03:05:44 +03:00
# include <linux/mutex.h>
2005-04-17 02:20:36 +04:00
# include <scsi/scsi.h>
# include "scsi_priv.h"
# include <scsi/scsi_device.h>
# include <scsi/scsi_host.h>
2005-08-28 20:31:14 +04:00
# include <scsi/scsi_cmnd.h>
2005-04-17 02:20:36 +04:00
# include <scsi/scsi_eh.h>
# include <scsi/scsi_transport.h>
# include <scsi/scsi_transport_spi.h>
2005-08-04 00:43:52 +04:00
# define SPI_NUM_ATTRS 14 /* increase this if you add attributes */
2005-04-17 02:20:36 +04:00
# define SPI_OTHER_ATTRS 1 / * Increase this if you add "always
* on " attributes */
# define SPI_HOST_ATTRS 1
# define SPI_MAX_ECHO_BUFFER_SIZE 4096
2005-05-02 03:58:15 +04:00
# define DV_LOOPS 3
# define DV_TIMEOUT (10*HZ)
# define DV_RETRIES 3 / * should only need at most
* two cc / ua clears */
2005-04-17 02:20:36 +04:00
/* Private data accessors (keep these out of the header file) */
# define spi_dv_pending(x) (((struct spi_transport_attrs *)&(x)->starget_data)->dv_pending)
2006-09-20 20:00:18 +04:00
# define spi_dv_in_progress(x) (((struct spi_transport_attrs *)&(x)->starget_data)->dv_in_progress)
2006-01-14 03:05:44 +03:00
# define spi_dv_mutex(x) (((struct spi_transport_attrs *)&(x)->starget_data)->dv_mutex)
2005-04-17 02:20:36 +04:00
struct spi_internal {
struct scsi_transport_template t ;
struct spi_function_template * f ;
/* The actual attributes */
struct class_device_attribute private_attrs [ SPI_NUM_ATTRS ] ;
/* The array of null terminated pointers to attributes
* needed by scsi_sysfs . c */
struct class_device_attribute * attrs [ SPI_NUM_ATTRS + SPI_OTHER_ATTRS + 1 ] ;
struct class_device_attribute private_host_attrs [ SPI_HOST_ATTRS ] ;
struct class_device_attribute * host_attrs [ SPI_HOST_ATTRS + 1 ] ;
} ;
# define to_spi_internal(tmpl) container_of(tmpl, struct spi_internal, t)
static const int ppr_to_ps [ ] = {
/* The PPR values 0-6 are reserved, fill them in when
* the committee defines them */
- 1 , /* 0x00 */
- 1 , /* 0x01 */
- 1 , /* 0x02 */
- 1 , /* 0x03 */
- 1 , /* 0x04 */
- 1 , /* 0x05 */
- 1 , /* 0x06 */
3125 , /* 0x07 */
6250 , /* 0x08 */
12500 , /* 0x09 */
25000 , /* 0x0a */
30300 , /* 0x0b */
50000 , /* 0x0c */
} ;
/* The PPR values at which you calculate the period in ns by multiplying
* by 4 */
# define SPI_STATIC_PPR 0x0c
static int sprint_frac ( char * dest , int value , int denom )
{
int frac = value % denom ;
int result = sprintf ( dest , " %d " , value / denom ) ;
if ( frac = = 0 )
return result ;
dest [ result + + ] = ' . ' ;
do {
denom / = 10 ;
sprintf ( dest + result , " %d " , frac / denom ) ;
result + + ;
frac % = denom ;
} while ( frac ) ;
dest [ result + + ] = ' \0 ' ;
return result ;
}
2005-08-28 20:31:14 +04:00
static int spi_execute ( struct scsi_device * sdev , const void * cmd ,
enum dma_data_direction dir ,
void * buffer , unsigned bufflen ,
struct scsi_sense_hdr * sshdr )
2005-05-02 03:58:15 +04:00
{
2005-08-28 20:31:14 +04:00
int i , result ;
unsigned char sense [ SCSI_SENSE_BUFFERSIZE ] ;
2005-05-02 03:58:15 +04:00
for ( i = 0 ; i < DV_RETRIES ; i + + ) {
2005-08-28 20:31:14 +04:00
result = scsi_execute ( sdev , cmd , dir , buffer , bufflen ,
sense , DV_TIMEOUT , /* retries */ 1 ,
REQ_FAILFAST ) ;
if ( result & DRIVER_SENSE ) {
struct scsi_sense_hdr sshdr_tmp ;
if ( ! sshdr )
sshdr = & sshdr_tmp ;
if ( scsi_normalize_sense ( sense , sizeof ( * sense ) ,
sshdr )
& & sshdr - > sense_key = = UNIT_ATTENTION )
2005-05-02 03:58:15 +04:00
continue ;
}
break ;
}
2005-08-28 20:31:14 +04:00
return result ;
2005-05-02 03:58:15 +04:00
}
2005-04-17 02:20:36 +04:00
static struct {
enum spi_signal_type value ;
char * name ;
} signal_types [ ] = {
{ SPI_SIGNAL_UNKNOWN , " unknown " } ,
{ SPI_SIGNAL_SE , " SE " } ,
{ SPI_SIGNAL_LVD , " LVD " } ,
{ SPI_SIGNAL_HVD , " HVD " } ,
} ;
static inline const char * spi_signal_to_string ( enum spi_signal_type type )
{
int i ;
2006-06-09 09:23:48 +04:00
for ( i = 0 ; i < ARRAY_SIZE ( signal_types ) ; i + + ) {
2005-04-17 02:20:36 +04:00
if ( type = = signal_types [ i ] . value )
return signal_types [ i ] . name ;
}
return NULL ;
}
static inline enum spi_signal_type spi_signal_to_value ( const char * name )
{
int i , len ;
2006-06-09 09:23:48 +04:00
for ( i = 0 ; i < ARRAY_SIZE ( signal_types ) ; i + + ) {
2005-04-17 02:20:36 +04:00
len = strlen ( signal_types [ i ] . name ) ;
if ( strncmp ( name , signal_types [ i ] . name , len ) = = 0 & &
( name [ len ] = = ' \n ' | | name [ len ] = = ' \0 ' ) )
return signal_types [ i ] . value ;
}
return SPI_SIGNAL_UNKNOWN ;
}
2005-08-15 02:09:01 +04:00
static int spi_host_setup ( struct transport_container * tc , struct device * dev ,
struct class_device * cdev )
2005-04-17 02:20:36 +04:00
{
struct Scsi_Host * shost = dev_to_shost ( dev ) ;
spi_signalling ( shost ) = SPI_SIGNAL_UNKNOWN ;
return 0 ;
}
static DECLARE_TRANSPORT_CLASS ( spi_host_class ,
" spi_host " ,
spi_host_setup ,
NULL ,
NULL ) ;
static int spi_host_match ( struct attribute_container * cont ,
struct device * dev )
{
struct Scsi_Host * shost ;
struct spi_internal * i ;
if ( ! scsi_is_host_device ( dev ) )
return 0 ;
shost = dev_to_shost ( dev ) ;
if ( ! shost - > transportt | | shost - > transportt - > host_attrs . ac . class
! = & spi_host_class . class )
return 0 ;
i = to_spi_internal ( shost - > transportt ) ;
return & i - > t . host_attrs . ac = = cont ;
}
2005-08-15 02:09:01 +04:00
static int spi_device_configure ( struct transport_container * tc ,
struct device * dev ,
struct class_device * cdev )
2005-04-17 02:20:36 +04:00
{
struct scsi_device * sdev = to_scsi_device ( dev ) ;
struct scsi_target * starget = sdev - > sdev_target ;
/* Populate the target capability fields with the values
* gleaned from the device inquiry */
spi_support_sync ( starget ) = scsi_device_sync ( sdev ) ;
spi_support_wide ( starget ) = scsi_device_wide ( sdev ) ;
spi_support_dt ( starget ) = scsi_device_dt ( sdev ) ;
spi_support_dt_only ( starget ) = scsi_device_dt_only ( sdev ) ;
spi_support_ius ( starget ) = scsi_device_ius ( sdev ) ;
spi_support_qas ( starget ) = scsi_device_qas ( sdev ) ;
return 0 ;
}
2005-08-15 02:09:01 +04:00
static int spi_setup_transport_attrs ( struct transport_container * tc ,
struct device * dev ,
struct class_device * cdev )
2005-04-17 02:20:36 +04:00
{
struct scsi_target * starget = to_scsi_target ( dev ) ;
spi_period ( starget ) = - 1 ; /* illegal value */
2005-05-07 03:05:20 +04:00
spi_min_period ( starget ) = 0 ;
2005-04-17 02:20:36 +04:00
spi_offset ( starget ) = 0 ; /* async */
2005-05-07 03:05:20 +04:00
spi_max_offset ( starget ) = 255 ;
2005-04-17 02:20:36 +04:00
spi_width ( starget ) = 0 ; /* narrow */
2005-05-07 03:05:20 +04:00
spi_max_width ( starget ) = 1 ;
2005-04-17 02:20:36 +04:00
spi_iu ( starget ) = 0 ; /* no IU */
spi_dt ( starget ) = 0 ; /* ST */
spi_qas ( starget ) = 0 ;
spi_wr_flow ( starget ) = 0 ;
spi_rd_strm ( starget ) = 0 ;
spi_rti ( starget ) = 0 ;
spi_pcomp_en ( starget ) = 0 ;
2005-08-04 00:43:52 +04:00
spi_hold_mcs ( starget ) = 0 ;
2005-04-17 02:20:36 +04:00
spi_dv_pending ( starget ) = 0 ;
2006-09-20 20:00:18 +04:00
spi_dv_in_progress ( starget ) = 0 ;
2005-04-17 02:20:36 +04:00
spi_initial_dv ( starget ) = 0 ;
2006-01-14 03:05:44 +03:00
mutex_init ( & spi_dv_mutex ( starget ) ) ;
2005-04-17 02:20:36 +04:00
return 0 ;
}
2005-05-07 03:05:20 +04:00
# define spi_transport_show_simple(field, format_string) \
\
static ssize_t \
show_spi_transport_ # # field ( struct class_device * cdev , char * buf ) \
{ \
struct scsi_target * starget = transport_class_to_starget ( cdev ) ; \
struct spi_transport_attrs * tp ; \
\
tp = ( struct spi_transport_attrs * ) & starget - > starget_data ; \
return snprintf ( buf , 20 , format_string , tp - > field ) ; \
}
# define spi_transport_store_simple(field, format_string) \
\
static ssize_t \
store_spi_transport_ # # field ( struct class_device * cdev , const char * buf , \
size_t count ) \
{ \
int val ; \
struct scsi_target * starget = transport_class_to_starget ( cdev ) ; \
struct spi_transport_attrs * tp ; \
\
tp = ( struct spi_transport_attrs * ) & starget - > starget_data ; \
val = simple_strtoul ( buf , NULL , 0 ) ; \
tp - > field = val ; \
return count ; \
}
2005-04-17 02:20:36 +04:00
# define spi_transport_show_function(field, format_string) \
\
static ssize_t \
show_spi_transport_ # # field ( struct class_device * cdev , char * buf ) \
{ \
struct scsi_target * starget = transport_class_to_starget ( cdev ) ; \
struct Scsi_Host * shost = dev_to_shost ( starget - > dev . parent ) ; \
struct spi_transport_attrs * tp ; \
struct spi_internal * i = to_spi_internal ( shost - > transportt ) ; \
tp = ( struct spi_transport_attrs * ) & starget - > starget_data ; \
if ( i - > f - > get_ # # field ) \
i - > f - > get_ # # field ( starget ) ; \
return snprintf ( buf , 20 , format_string , tp - > field ) ; \
}
# define spi_transport_store_function(field, format_string) \
static ssize_t \
store_spi_transport_ # # field ( struct class_device * cdev , const char * buf , \
size_t count ) \
{ \
int val ; \
struct scsi_target * starget = transport_class_to_starget ( cdev ) ; \
struct Scsi_Host * shost = dev_to_shost ( starget - > dev . parent ) ; \
struct spi_internal * i = to_spi_internal ( shost - > transportt ) ; \
\
val = simple_strtoul ( buf , NULL , 0 ) ; \
2005-05-07 03:05:20 +04:00
i - > f - > set_ # # field ( starget , val ) ; \
return count ; \
}
# define spi_transport_store_max(field, format_string) \
static ssize_t \
store_spi_transport_ # # field ( struct class_device * cdev , const char * buf , \
size_t count ) \
{ \
int val ; \
struct scsi_target * starget = transport_class_to_starget ( cdev ) ; \
struct Scsi_Host * shost = dev_to_shost ( starget - > dev . parent ) ; \
struct spi_internal * i = to_spi_internal ( shost - > transportt ) ; \
struct spi_transport_attrs * tp \
= ( struct spi_transport_attrs * ) & starget - > starget_data ; \
\
val = simple_strtoul ( buf , NULL , 0 ) ; \
if ( val > tp - > max_ # # field ) \
val = tp - > max_ # # field ; \
2005-04-17 02:20:36 +04:00
i - > f - > set_ # # field ( starget , val ) ; \
return count ; \
}
# define spi_transport_rd_attr(field, format_string) \
spi_transport_show_function ( field , format_string ) \
spi_transport_store_function ( field , format_string ) \
static CLASS_DEVICE_ATTR ( field , S_IRUGO | S_IWUSR , \
show_spi_transport_ # # field , \
store_spi_transport_ # # field ) ;
2005-05-07 03:05:20 +04:00
# define spi_transport_simple_attr(field, format_string) \
spi_transport_show_simple ( field , format_string ) \
spi_transport_store_simple ( field , format_string ) \
static CLASS_DEVICE_ATTR ( field , S_IRUGO | S_IWUSR , \
show_spi_transport_ # # field , \
store_spi_transport_ # # field ) ;
# define spi_transport_max_attr(field, format_string) \
spi_transport_show_function ( field , format_string ) \
spi_transport_store_max ( field , format_string ) \
spi_transport_simple_attr ( max_ # # field , format_string ) \
static CLASS_DEVICE_ATTR ( field , S_IRUGO | S_IWUSR , \
show_spi_transport_ # # field , \
store_spi_transport_ # # field ) ;
2005-04-17 02:20:36 +04:00
/* The Parallel SCSI Tranport Attributes: */
2005-05-07 03:05:20 +04:00
spi_transport_max_attr ( offset , " %d \n " ) ;
spi_transport_max_attr ( width , " %d \n " ) ;
2005-04-17 02:20:36 +04:00
spi_transport_rd_attr ( iu , " %d \n " ) ;
spi_transport_rd_attr ( dt , " %d \n " ) ;
spi_transport_rd_attr ( qas , " %d \n " ) ;
spi_transport_rd_attr ( wr_flow , " %d \n " ) ;
spi_transport_rd_attr ( rd_strm , " %d \n " ) ;
spi_transport_rd_attr ( rti , " %d \n " ) ;
spi_transport_rd_attr ( pcomp_en , " %d \n " ) ;
2005-08-04 00:43:52 +04:00
spi_transport_rd_attr ( hold_mcs , " %d \n " ) ;
2005-04-17 02:20:36 +04:00
2005-03-26 02:52:00 +03:00
/* we only care about the first child device so we return 1 */
static int child_iter ( struct device * dev , void * data )
{
struct scsi_device * sdev = to_scsi_device ( dev ) ;
spi_dv_device ( sdev ) ;
return 1 ;
}
2005-04-17 02:20:36 +04:00
static ssize_t
store_spi_revalidate ( struct class_device * cdev , const char * buf , size_t count )
{
struct scsi_target * starget = transport_class_to_starget ( cdev ) ;
2005-03-26 02:52:00 +03:00
device_for_each_child ( & starget - > dev , NULL , child_iter ) ;
2005-04-17 02:20:36 +04:00
return count ;
}
static CLASS_DEVICE_ATTR ( revalidate , S_IWUSR , NULL , store_spi_revalidate ) ;
/* Translate the period into ns according to the current spec
* for SDTR / PPR messages */
2005-12-16 00:22:01 +03:00
static int period_to_str ( char * buf , int period )
2005-04-17 02:20:36 +04:00
{
int len , picosec ;
2005-05-07 03:05:20 +04:00
if ( period < 0 | | period > 0xff ) {
2005-04-17 02:20:36 +04:00
picosec = - 1 ;
2005-05-07 03:05:20 +04:00
} else if ( period < = SPI_STATIC_PPR ) {
picosec = ppr_to_ps [ period ] ;
2005-04-17 02:20:36 +04:00
} else {
2005-05-07 03:05:20 +04:00
picosec = period * 4000 ;
2005-04-17 02:20:36 +04:00
}
if ( picosec = = - 1 ) {
len = sprintf ( buf , " reserved " ) ;
} else {
len = sprint_frac ( buf , picosec , 1000 ) ;
}
2005-12-16 00:22:01 +03:00
return len ;
}
static ssize_t
2006-02-07 18:01:02 +03:00
show_spi_transport_period_helper ( char * buf , int period )
2005-12-16 00:22:01 +03:00
{
int len = period_to_str ( buf , period ) ;
2005-04-17 02:20:36 +04:00
buf [ len + + ] = ' \n ' ;
buf [ len ] = ' \0 ' ;
return len ;
}
static ssize_t
2005-05-07 03:05:20 +04:00
store_spi_transport_period_helper ( struct class_device * cdev , const char * buf ,
size_t count , int * periodp )
2005-04-17 02:20:36 +04:00
{
int j , picosec , period = - 1 ;
char * endp ;
picosec = simple_strtoul ( buf , & endp , 10 ) * 1000 ;
if ( * endp = = ' . ' ) {
int mult = 100 ;
do {
endp + + ;
if ( ! isdigit ( * endp ) )
break ;
picosec + = ( * endp - ' 0 ' ) * mult ;
mult / = 10 ;
} while ( mult > 0 ) ;
}
for ( j = 0 ; j < = SPI_STATIC_PPR ; j + + ) {
if ( ppr_to_ps [ j ] < picosec )
continue ;
period = j ;
break ;
}
if ( period = = - 1 )
period = picosec / 4000 ;
if ( period > 0xff )
period = 0xff ;
2005-05-07 03:05:20 +04:00
* periodp = period ;
2005-04-17 02:20:36 +04:00
return count ;
}
2005-05-07 03:05:20 +04:00
static ssize_t
show_spi_transport_period ( struct class_device * cdev , char * buf )
{
struct scsi_target * starget = transport_class_to_starget ( cdev ) ;
struct Scsi_Host * shost = dev_to_shost ( starget - > dev . parent ) ;
struct spi_internal * i = to_spi_internal ( shost - > transportt ) ;
struct spi_transport_attrs * tp =
( struct spi_transport_attrs * ) & starget - > starget_data ;
if ( i - > f - > get_period )
i - > f - > get_period ( starget ) ;
2006-02-07 18:01:02 +03:00
return show_spi_transport_period_helper ( buf , tp - > period ) ;
2005-05-07 03:05:20 +04:00
}
static ssize_t
store_spi_transport_period ( struct class_device * cdev , const char * buf ,
size_t count )
{
struct scsi_target * starget = transport_class_to_starget ( cdev ) ;
struct Scsi_Host * shost = dev_to_shost ( starget - > dev . parent ) ;
struct spi_internal * i = to_spi_internal ( shost - > transportt ) ;
struct spi_transport_attrs * tp =
( struct spi_transport_attrs * ) & starget - > starget_data ;
int period , retval ;
retval = store_spi_transport_period_helper ( cdev , buf , count , & period ) ;
if ( period < tp - > min_period )
period = tp - > min_period ;
i - > f - > set_period ( starget , period ) ;
return retval ;
}
2005-04-17 02:20:36 +04:00
static CLASS_DEVICE_ATTR ( period , S_IRUGO | S_IWUSR ,
show_spi_transport_period ,
store_spi_transport_period ) ;
2005-05-07 03:05:20 +04:00
static ssize_t
show_spi_transport_min_period ( struct class_device * cdev , char * buf )
{
struct scsi_target * starget = transport_class_to_starget ( cdev ) ;
struct spi_transport_attrs * tp =
( struct spi_transport_attrs * ) & starget - > starget_data ;
2006-02-07 18:01:02 +03:00
return show_spi_transport_period_helper ( buf , tp - > min_period ) ;
2005-05-07 03:05:20 +04:00
}
static ssize_t
store_spi_transport_min_period ( struct class_device * cdev , const char * buf ,
size_t count )
{
struct scsi_target * starget = transport_class_to_starget ( cdev ) ;
struct spi_transport_attrs * tp =
( struct spi_transport_attrs * ) & starget - > starget_data ;
return store_spi_transport_period_helper ( cdev , buf , count ,
& tp - > min_period ) ;
}
static CLASS_DEVICE_ATTR ( min_period , S_IRUGO | S_IWUSR ,
show_spi_transport_min_period ,
store_spi_transport_min_period ) ;
2005-04-17 02:20:36 +04:00
static ssize_t show_spi_host_signalling ( struct class_device * cdev , char * buf )
{
struct Scsi_Host * shost = transport_class_to_shost ( cdev ) ;
struct spi_internal * i = to_spi_internal ( shost - > transportt ) ;
if ( i - > f - > get_signalling )
i - > f - > get_signalling ( shost ) ;
return sprintf ( buf , " %s \n " , spi_signal_to_string ( spi_signalling ( shost ) ) ) ;
}
static ssize_t store_spi_host_signalling ( struct class_device * cdev ,
const char * buf , size_t count )
{
struct Scsi_Host * shost = transport_class_to_shost ( cdev ) ;
struct spi_internal * i = to_spi_internal ( shost - > transportt ) ;
enum spi_signal_type type = spi_signal_to_value ( buf ) ;
if ( type ! = SPI_SIGNAL_UNKNOWN )
i - > f - > set_signalling ( shost , type ) ;
return count ;
}
static CLASS_DEVICE_ATTR ( signalling , S_IRUGO | S_IWUSR ,
show_spi_host_signalling ,
store_spi_host_signalling ) ;
# define DV_SET(x, y) \
if ( i - > f - > set_ # # x ) \
i - > f - > set_ # # x ( sdev - > sdev_target , y )
enum spi_compare_returns {
SPI_COMPARE_SUCCESS ,
SPI_COMPARE_FAILURE ,
SPI_COMPARE_SKIP_TEST ,
} ;
/* This is for read/write Domain Validation: If the device supports
* an echo buffer , we do read / write tests to it */
static enum spi_compare_returns
2005-08-28 20:31:14 +04:00
spi_dv_device_echo_buffer ( struct scsi_device * sdev , u8 * buffer ,
2005-04-17 02:20:36 +04:00
u8 * ptr , const int retries )
{
int len = ptr - buffer ;
2005-08-28 20:31:14 +04:00
int j , k , r , result ;
2005-04-17 02:20:36 +04:00
unsigned int pattern = 0x0000ffff ;
2005-08-28 20:31:14 +04:00
struct scsi_sense_hdr sshdr ;
2005-04-17 02:20:36 +04:00
const char spi_write_buffer [ ] = {
WRITE_BUFFER , 0x0a , 0 , 0 , 0 , 0 , 0 , len > > 8 , len & 0xff , 0
} ;
const char spi_read_buffer [ ] = {
READ_BUFFER , 0x0a , 0 , 0 , 0 , 0 , 0 , len > > 8 , len & 0xff , 0
} ;
/* set up the pattern buffer. Doesn't matter if we spill
* slightly beyond since that ' s where the read buffer is */
for ( j = 0 ; j < len ; ) {
/* fill the buffer with counting (test a) */
for ( ; j < min ( len , 32 ) ; j + + )
buffer [ j ] = j ;
k = j ;
/* fill the buffer with alternating words of 0x0 and
* 0xffff ( test b ) */
for ( ; j < min ( len , k + 32 ) ; j + = 2 ) {
u16 * word = ( u16 * ) & buffer [ j ] ;
* word = ( j & 0x02 ) ? 0x0000 : 0xffff ;
}
k = j ;
/* fill with crosstalk (alternating 0x5555 0xaaa)
* ( test c ) */
for ( ; j < min ( len , k + 32 ) ; j + = 2 ) {
u16 * word = ( u16 * ) & buffer [ j ] ;
* word = ( j & 0x02 ) ? 0x5555 : 0xaaaa ;
}
k = j ;
/* fill with shifting bits (test d) */
for ( ; j < min ( len , k + 32 ) ; j + = 4 ) {
u32 * word = ( unsigned int * ) & buffer [ j ] ;
u32 roll = ( pattern & 0x80000000 ) ? 1 : 0 ;
* word = pattern ;
pattern = ( pattern < < 1 ) | roll ;
}
/* don't bother with random data (test e) */
}
for ( r = 0 ; r < retries ; r + + ) {
2005-08-28 20:31:14 +04:00
result = spi_execute ( sdev , spi_write_buffer , DMA_TO_DEVICE ,
buffer , len , & sshdr ) ;
if ( result | | ! scsi_device_online ( sdev ) ) {
2005-04-17 02:20:36 +04:00
scsi_device_set_state ( sdev , SDEV_QUIESCE ) ;
2005-08-28 20:31:14 +04:00
if ( scsi_sense_valid ( & sshdr )
2005-04-17 02:20:36 +04:00
& & sshdr . sense_key = = ILLEGAL_REQUEST
/* INVALID FIELD IN CDB */
& & sshdr . asc = = 0x24 & & sshdr . ascq = = 0x00 )
/* This would mean that the drive lied
* to us about supporting an echo
* buffer ( unfortunately some Western
* Digital drives do precisely this )
*/
return SPI_COMPARE_SKIP_TEST ;
2005-10-02 20:45:08 +04:00
sdev_printk ( KERN_ERR , sdev , " Write Buffer failure %x \n " , result ) ;
2005-04-17 02:20:36 +04:00
return SPI_COMPARE_FAILURE ;
}
memset ( ptr , 0 , len ) ;
2005-08-28 20:31:14 +04:00
spi_execute ( sdev , spi_read_buffer , DMA_FROM_DEVICE ,
ptr , len , NULL ) ;
2005-04-17 02:20:36 +04:00
scsi_device_set_state ( sdev , SDEV_QUIESCE ) ;
if ( memcmp ( buffer , ptr , len ) ! = 0 )
return SPI_COMPARE_FAILURE ;
}
return SPI_COMPARE_SUCCESS ;
}
/* This is for the simplest form of Domain Validation: a read test
* on the inquiry data from the device */
static enum spi_compare_returns
2005-08-28 20:31:14 +04:00
spi_dv_device_compare_inquiry ( struct scsi_device * sdev , u8 * buffer ,
2005-04-17 02:20:36 +04:00
u8 * ptr , const int retries )
{
2005-08-28 20:31:14 +04:00
int r , result ;
const int len = sdev - > inquiry_len ;
2005-04-17 02:20:36 +04:00
const char spi_inquiry [ ] = {
INQUIRY , 0 , 0 , 0 , len , 0
} ;
for ( r = 0 ; r < retries ; r + + ) {
memset ( ptr , 0 , len ) ;
2005-08-28 20:31:14 +04:00
result = spi_execute ( sdev , spi_inquiry , DMA_FROM_DEVICE ,
ptr , len , NULL ) ;
2005-04-17 02:20:36 +04:00
2005-08-28 20:31:14 +04:00
if ( result | | ! scsi_device_online ( sdev ) ) {
2005-04-17 02:20:36 +04:00
scsi_device_set_state ( sdev , SDEV_QUIESCE ) ;
return SPI_COMPARE_FAILURE ;
}
/* If we don't have the inquiry data already, the
* first read gets it */
if ( ptr = = buffer ) {
ptr + = len ;
- - r ;
continue ;
}
if ( memcmp ( buffer , ptr , len ) ! = 0 )
/* failure */
return SPI_COMPARE_FAILURE ;
}
return SPI_COMPARE_SUCCESS ;
}
static enum spi_compare_returns
2005-08-28 20:31:14 +04:00
spi_dv_retrain ( struct scsi_device * sdev , u8 * buffer , u8 * ptr ,
2005-04-17 02:20:36 +04:00
enum spi_compare_returns
2005-08-28 20:31:14 +04:00
( * compare_fn ) ( struct scsi_device * , u8 * , u8 * , int ) )
2005-04-17 02:20:36 +04:00
{
2005-08-28 20:31:14 +04:00
struct spi_internal * i = to_spi_internal ( sdev - > host - > transportt ) ;
2005-06-01 03:35:39 +04:00
struct scsi_target * starget = sdev - > sdev_target ;
2005-04-17 02:20:36 +04:00
int period = 0 , prevperiod = 0 ;
enum spi_compare_returns retval ;
for ( ; ; ) {
int newperiod ;
2005-08-28 20:31:14 +04:00
retval = compare_fn ( sdev , buffer , ptr , DV_LOOPS ) ;
2005-04-17 02:20:36 +04:00
if ( retval = = SPI_COMPARE_SUCCESS
| | retval = = SPI_COMPARE_SKIP_TEST )
break ;
/* OK, retrain, fallback */
2005-06-01 03:35:39 +04:00
if ( i - > f - > get_iu )
i - > f - > get_iu ( starget ) ;
if ( i - > f - > get_qas )
i - > f - > get_qas ( starget ) ;
2005-04-17 02:20:36 +04:00
if ( i - > f - > get_period )
i - > f - > get_period ( sdev - > sdev_target ) ;
2005-06-01 03:35:39 +04:00
/* Here's the fallback sequence; first try turning off
* IU , then QAS ( if we can control them ) , then finally
* fall down the periods */
if ( i - > f - > set_iu & & spi_iu ( starget ) ) {
2005-10-02 20:45:08 +04:00
starget_printk ( KERN_ERR , starget , " Domain Validation Disabing Information Units \n " ) ;
2005-06-01 03:35:39 +04:00
DV_SET ( iu , 0 ) ;
} else if ( i - > f - > set_qas & & spi_qas ( starget ) ) {
2005-10-02 20:45:08 +04:00
starget_printk ( KERN_ERR , starget , " Domain Validation Disabing Quick Arbitration and Selection \n " ) ;
2005-06-01 03:35:39 +04:00
DV_SET ( qas , 0 ) ;
} else {
newperiod = spi_period ( starget ) ;
period = newperiod > period ? newperiod : period ;
if ( period < 0x0d )
period + + ;
else
period + = period > > 1 ;
if ( unlikely ( period > 0xff | | period = = prevperiod ) ) {
/* Total failure; set to async and return */
2005-10-02 20:45:08 +04:00
starget_printk ( KERN_ERR , starget , " Domain Validation Failure, dropping back to Asynchronous \n " ) ;
2005-06-01 03:35:39 +04:00
DV_SET ( offset , 0 ) ;
return SPI_COMPARE_FAILURE ;
}
2005-10-02 20:45:08 +04:00
starget_printk ( KERN_ERR , starget , " Domain Validation detected failure, dropping back \n " ) ;
2005-06-01 03:35:39 +04:00
DV_SET ( period , period ) ;
prevperiod = period ;
2005-04-17 02:20:36 +04:00
}
}
return retval ;
}
static int
2005-08-28 20:31:14 +04:00
spi_dv_device_get_echo_buffer ( struct scsi_device * sdev , u8 * buffer )
2005-04-17 02:20:36 +04:00
{
2005-08-28 20:31:14 +04:00
int l , result ;
2005-04-17 02:20:36 +04:00
/* first off do a test unit ready. This can error out
* because of reservations or some other reason . If it
* fails , the device won ' t let us write to the echo buffer
* so just return failure */
const char spi_test_unit_ready [ ] = {
TEST_UNIT_READY , 0 , 0 , 0 , 0 , 0
} ;
const char spi_read_buffer_descriptor [ ] = {
READ_BUFFER , 0x0b , 0 , 0 , 0 , 0 , 0 , 0 , 4 , 0
} ;
/* We send a set of three TURs to clear any outstanding
* unit attention conditions if they exist ( Otherwise the
* buffer tests won ' t be happy ) . If the TUR still fails
* ( reservation conflict , device not ready , etc ) just
* skip the write tests */
for ( l = 0 ; ; l + + ) {
2005-08-28 20:31:14 +04:00
result = spi_execute ( sdev , spi_test_unit_ready , DMA_NONE ,
NULL , 0 , NULL ) ;
2005-04-17 02:20:36 +04:00
2005-08-28 20:31:14 +04:00
if ( result ) {
2005-04-17 02:20:36 +04:00
if ( l > = 3 )
return 0 ;
} else {
/* TUR succeeded */
break ;
}
}
2005-08-28 20:31:14 +04:00
result = spi_execute ( sdev , spi_read_buffer_descriptor ,
DMA_FROM_DEVICE , buffer , 4 , NULL ) ;
2005-04-17 02:20:36 +04:00
2005-08-28 20:31:14 +04:00
if ( result )
2005-04-17 02:20:36 +04:00
/* Device has no echo buffer */
return 0 ;
return buffer [ 3 ] + ( ( buffer [ 2 ] & 0x1f ) < < 8 ) ;
}
static void
2005-08-28 20:31:14 +04:00
spi_dv_device_internal ( struct scsi_device * sdev , u8 * buffer )
2005-04-17 02:20:36 +04:00
{
2005-08-28 20:31:14 +04:00
struct spi_internal * i = to_spi_internal ( sdev - > host - > transportt ) ;
2005-05-07 03:05:20 +04:00
struct scsi_target * starget = sdev - > sdev_target ;
2006-06-10 19:51:23 +04:00
struct Scsi_Host * shost = sdev - > host ;
2005-04-17 02:20:36 +04:00
int len = sdev - > inquiry_len ;
/* first set us up for narrow async */
DV_SET ( offset , 0 ) ;
DV_SET ( width , 0 ) ;
2005-08-28 20:31:14 +04:00
if ( spi_dv_device_compare_inquiry ( sdev , buffer , buffer , DV_LOOPS )
2005-04-17 02:20:36 +04:00
! = SPI_COMPARE_SUCCESS ) {
2005-10-02 20:45:08 +04:00
starget_printk ( KERN_ERR , starget , " Domain Validation Initial Inquiry Failed \n " ) ;
2005-04-17 02:20:36 +04:00
/* FIXME: should probably offline the device here? */
return ;
}
/* test width */
2005-07-02 20:22:01 +04:00
if ( i - > f - > set_width & & spi_max_width ( starget ) & &
scsi_device_wide ( sdev ) ) {
2005-06-01 03:35:39 +04:00
i - > f - > set_width ( starget , 1 ) ;
2005-05-07 03:05:20 +04:00
2005-08-28 20:31:14 +04:00
if ( spi_dv_device_compare_inquiry ( sdev , buffer ,
2005-04-17 02:20:36 +04:00
buffer + len ,
DV_LOOPS )
! = SPI_COMPARE_SUCCESS ) {
2005-10-02 20:45:08 +04:00
starget_printk ( KERN_ERR , starget , " Wide Transfers Fail \n " ) ;
2005-06-01 03:35:39 +04:00
i - > f - > set_width ( starget , 0 ) ;
2005-04-17 02:20:36 +04:00
}
}
if ( ! i - > f - > set_period )
return ;
/* device can't handle synchronous */
2005-07-02 20:22:01 +04:00
if ( ! scsi_device_sync ( sdev ) & & ! scsi_device_dt ( sdev ) )
2005-04-17 02:20:36 +04:00
return ;
2005-11-29 00:41:58 +03:00
/* len == -1 is the signal that we need to ascertain the
* presence of an echo buffer before trying to use it . len = =
* 0 means we don ' t have an echo buffer */
len = - 1 ;
2005-04-17 02:20:36 +04:00
retry :
/* now set up to the maximum */
2005-05-07 03:05:20 +04:00
DV_SET ( offset , spi_max_offset ( starget ) ) ;
DV_SET ( period , spi_min_period ( starget ) ) ;
2005-06-01 03:35:39 +04:00
/* try QAS requests; this should be harmless to set if the
* target supports it */
2006-09-20 20:00:18 +04:00
if ( scsi_device_qas ( sdev ) ) {
2005-07-02 20:22:01 +04:00
DV_SET ( qas , 1 ) ;
2006-09-20 20:00:18 +04:00
} else {
DV_SET ( qas , 0 ) ;
}
if ( scsi_device_ius ( sdev ) & & spi_min_period ( starget ) < 9 ) {
/* This u320 (or u640). Set IU transfers */
2005-07-02 20:22:01 +04:00
DV_SET ( iu , 1 ) ;
2006-09-20 20:00:18 +04:00
/* Then set the optional parameters */
2005-06-01 03:35:39 +04:00
DV_SET ( rd_strm , 1 ) ;
DV_SET ( wr_flow , 1 ) ;
DV_SET ( rti , 1 ) ;
if ( spi_min_period ( starget ) = = 8 )
DV_SET ( pcomp_en , 1 ) ;
2006-09-20 20:00:18 +04:00
} else {
DV_SET ( iu , 0 ) ;
2005-06-01 03:35:39 +04:00
}
2006-09-20 20:00:18 +04:00
2006-06-10 19:51:23 +04:00
/* now that we've done all this, actually check the bus
* signal type ( if known ) . Some devices are stupid on
* a SE bus and still claim they can try LVD only settings */
if ( i - > f - > get_signalling )
i - > f - > get_signalling ( shost ) ;
if ( spi_signalling ( shost ) = = SPI_SIGNAL_SE | |
2006-09-20 20:00:18 +04:00
spi_signalling ( shost ) = = SPI_SIGNAL_HVD | |
! scsi_device_dt ( sdev ) ) {
2006-06-10 19:51:23 +04:00
DV_SET ( dt , 0 ) ;
2006-09-20 20:00:18 +04:00
} else {
DV_SET ( dt , 1 ) ;
}
2005-11-29 00:41:58 +03:00
/* Do the read only INQUIRY tests */
spi_dv_retrain ( sdev , buffer , buffer + sdev - > inquiry_len ,
spi_dv_device_compare_inquiry ) ;
/* See if we actually managed to negotiate and sustain DT */
if ( i - > f - > get_dt )
i - > f - > get_dt ( starget ) ;
/* see if the device has an echo buffer. If it does we can do
* the SPI pattern write tests . Because of some broken
* devices , we * only * try this on a device that has actually
* negotiated DT */
if ( len = = - 1 & & spi_dt ( starget ) )
len = spi_dv_device_get_echo_buffer ( sdev , buffer ) ;
2005-04-17 02:20:36 +04:00
2005-11-29 00:41:58 +03:00
if ( len < = 0 ) {
2005-10-02 20:45:08 +04:00
starget_printk ( KERN_INFO , starget , " Domain Validation skipping write tests \n " ) ;
2005-04-17 02:20:36 +04:00
return ;
}
if ( len > SPI_MAX_ECHO_BUFFER_SIZE ) {
2005-10-02 20:45:08 +04:00
starget_printk ( KERN_WARNING , starget , " Echo buffer size %d is too big, trimming to %d \n " , len , SPI_MAX_ECHO_BUFFER_SIZE ) ;
2005-04-17 02:20:36 +04:00
len = SPI_MAX_ECHO_BUFFER_SIZE ;
}
2005-08-28 20:31:14 +04:00
if ( spi_dv_retrain ( sdev , buffer , buffer + len ,
2005-04-17 02:20:36 +04:00
spi_dv_device_echo_buffer )
= = SPI_COMPARE_SKIP_TEST ) {
/* OK, the stupid drive can't do a write echo buffer
* test after all , fall back to the read tests */
len = 0 ;
goto retry ;
}
}
/** spi_dv_device - Do Domain Validation on the device
* @ sdev : scsi device to validate
*
* Performs the domain validation on the given device in the
* current execution thread . Since DV operations may sleep ,
* the current thread must have user context . Also no SCSI
* related locks that would deadlock I / O issued by the DV may
* be held .
*/
void
spi_dv_device ( struct scsi_device * sdev )
{
struct scsi_target * starget = sdev - > sdev_target ;
u8 * buffer ;
const int len = SPI_MAX_ECHO_BUFFER_SIZE * 2 ;
if ( unlikely ( scsi_device_get ( sdev ) ) )
2005-08-28 20:31:14 +04:00
return ;
2005-04-17 02:20:36 +04:00
2006-09-20 20:00:18 +04:00
if ( unlikely ( spi_dv_in_progress ( starget ) ) )
return ;
spi_dv_in_progress ( starget ) = 1 ;
2006-01-16 18:31:18 +03:00
buffer = kzalloc ( len , GFP_KERNEL ) ;
2005-04-17 02:20:36 +04:00
if ( unlikely ( ! buffer ) )
goto out_put ;
/* We need to verify that the actual device will quiesce; the
* later target quiesce is just a nice to have */
if ( unlikely ( scsi_device_quiesce ( sdev ) ) )
goto out_free ;
scsi_target_quiesce ( starget ) ;
spi_dv_pending ( starget ) = 1 ;
2006-01-14 03:05:44 +03:00
mutex_lock ( & spi_dv_mutex ( starget ) ) ;
2005-04-17 02:20:36 +04:00
2005-10-02 20:45:08 +04:00
starget_printk ( KERN_INFO , starget , " Beginning Domain Validation \n " ) ;
2005-04-17 02:20:36 +04:00
2005-08-28 20:31:14 +04:00
spi_dv_device_internal ( sdev , buffer ) ;
2005-04-17 02:20:36 +04:00
2005-10-02 20:45:08 +04:00
starget_printk ( KERN_INFO , starget , " Ending Domain Validation \n " ) ;
2005-04-17 02:20:36 +04:00
2006-01-14 03:05:44 +03:00
mutex_unlock ( & spi_dv_mutex ( starget ) ) ;
2005-04-17 02:20:36 +04:00
spi_dv_pending ( starget ) = 0 ;
scsi_target_resume ( starget ) ;
spi_initial_dv ( starget ) = 1 ;
out_free :
kfree ( buffer ) ;
out_put :
2006-09-20 20:00:18 +04:00
spi_dv_in_progress ( starget ) = 0 ;
2005-04-17 02:20:36 +04:00
scsi_device_put ( sdev ) ;
}
EXPORT_SYMBOL ( spi_dv_device ) ;
struct work_queue_wrapper {
struct work_struct work ;
struct scsi_device * sdev ;
} ;
static void
spi_dv_device_work_wrapper ( void * data )
{
struct work_queue_wrapper * wqw = ( struct work_queue_wrapper * ) data ;
struct scsi_device * sdev = wqw - > sdev ;
kfree ( wqw ) ;
spi_dv_device ( sdev ) ;
spi_dv_pending ( sdev - > sdev_target ) = 0 ;
scsi_device_put ( sdev ) ;
}
/**
* spi_schedule_dv_device - schedule domain validation to occur on the device
* @ sdev : The device to validate
*
* Identical to spi_dv_device ( ) above , except that the DV will be
* scheduled to occur in a workqueue later . All memory allocations
* are atomic , so may be called from any context including those holding
* SCSI locks .
*/
void
spi_schedule_dv_device ( struct scsi_device * sdev )
{
struct work_queue_wrapper * wqw =
kmalloc ( sizeof ( struct work_queue_wrapper ) , GFP_ATOMIC ) ;
if ( unlikely ( ! wqw ) )
return ;
if ( unlikely ( spi_dv_pending ( sdev - > sdev_target ) ) ) {
kfree ( wqw ) ;
return ;
}
/* Set pending early (dv_device doesn't check it, only sets it) */
spi_dv_pending ( sdev - > sdev_target ) = 1 ;
if ( unlikely ( scsi_device_get ( sdev ) ) ) {
kfree ( wqw ) ;
spi_dv_pending ( sdev - > sdev_target ) = 0 ;
return ;
}
INIT_WORK ( & wqw - > work , spi_dv_device_work_wrapper , wqw ) ;
wqw - > sdev = sdev ;
schedule_work ( & wqw - > work ) ;
}
EXPORT_SYMBOL ( spi_schedule_dv_device ) ;
/**
* spi_display_xfer_agreement - Print the current target transfer agreement
* @ starget : The target for which to display the agreement
*
* Each SPI port is required to maintain a transfer agreement for each
* other port on the bus . This function prints a one - line summary of
* the current agreement ; more detailed information is available in sysfs .
*/
void spi_display_xfer_agreement ( struct scsi_target * starget )
{
struct spi_transport_attrs * tp ;
tp = ( struct spi_transport_attrs * ) & starget - > starget_data ;
if ( tp - > offset > 0 & & tp - > period > 0 ) {
unsigned int picosec , kb100 ;
char * scsi = " FAST-? " ;
char tmp [ 8 ] ;
if ( tp - > period < = SPI_STATIC_PPR ) {
picosec = ppr_to_ps [ tp - > period ] ;
switch ( tp - > period ) {
case 7 : scsi = " FAST-320 " ; break ;
case 8 : scsi = " FAST-160 " ; break ;
case 9 : scsi = " FAST-80 " ; break ;
case 10 :
case 11 : scsi = " FAST-40 " ; break ;
case 12 : scsi = " FAST-20 " ; break ;
}
} else {
picosec = tp - > period * 4000 ;
if ( tp - > period < 25 )
scsi = " FAST-20 " ;
else if ( tp - > period < 50 )
scsi = " FAST-10 " ;
else
scsi = " FAST-5 " ;
}
kb100 = ( 10000000 + picosec / 2 ) / picosec ;
if ( tp - > width )
kb100 * = 2 ;
sprint_frac ( tmp , picosec , 1000 ) ;
dev_info ( & starget - > dev ,
2005-08-04 00:43:52 +04:00
" %s %sSCSI %d.%d MB/s %s%s%s%s%s%s%s%s (%s ns, offset %d) \n " ,
scsi , tp - > width ? " WIDE " : " " , kb100 / 10 , kb100 % 10 ,
tp - > dt ? " DT " : " ST " ,
tp - > iu ? " IU " : " " ,
tp - > qas ? " QAS " : " " ,
tp - > rd_strm ? " RDSTRM " : " " ,
tp - > rti ? " RTI " : " " ,
tp - > wr_flow ? " WRFLOW " : " " ,
tp - > pcomp_en ? " PCOMP " : " " ,
tp - > hold_mcs ? " HMCS " : " " ,
tmp , tp - > offset ) ;
2005-04-17 02:20:36 +04:00
} else {
2005-11-17 21:13:43 +03:00
dev_info ( & starget - > dev , " %sasynchronous \n " ,
2005-04-17 02:20:36 +04:00
tp - > width ? " wide " : " " ) ;
}
}
EXPORT_SYMBOL ( spi_display_xfer_agreement ) ;
2006-02-07 17:54:46 +03:00
int spi_populate_width_msg ( unsigned char * msg , int width )
{
msg [ 0 ] = EXTENDED_MESSAGE ;
msg [ 1 ] = 2 ;
msg [ 2 ] = EXTENDED_WDTR ;
msg [ 3 ] = width ;
return 4 ;
}
2006-02-18 01:58:47 +03:00
EXPORT_SYMBOL_GPL ( spi_populate_width_msg ) ;
2006-02-07 17:54:46 +03:00
int spi_populate_sync_msg ( unsigned char * msg , int period , int offset )
{
msg [ 0 ] = EXTENDED_MESSAGE ;
msg [ 1 ] = 3 ;
msg [ 2 ] = EXTENDED_SDTR ;
msg [ 3 ] = period ;
msg [ 4 ] = offset ;
return 5 ;
}
2006-02-18 01:58:47 +03:00
EXPORT_SYMBOL_GPL ( spi_populate_sync_msg ) ;
2006-02-07 17:54:46 +03:00
int spi_populate_ppr_msg ( unsigned char * msg , int period , int offset ,
int width , int options )
{
msg [ 0 ] = EXTENDED_MESSAGE ;
msg [ 1 ] = 6 ;
msg [ 2 ] = EXTENDED_PPR ;
msg [ 3 ] = period ;
msg [ 4 ] = 0 ;
msg [ 5 ] = offset ;
msg [ 6 ] = width ;
msg [ 7 ] = options ;
return 8 ;
}
2006-02-18 01:58:47 +03:00
EXPORT_SYMBOL_GPL ( spi_populate_ppr_msg ) ;
2006-02-07 17:54:46 +03:00
2005-12-16 00:22:01 +03:00
# ifdef CONFIG_SCSI_CONSTANTS
static const char * const one_byte_msgs [ ] = {
2006-03-10 17:18:22 +03:00
/* 0x00 */ " Task Complete " , NULL /* Extended Message */ , " Save Pointers " ,
2005-12-16 00:22:01 +03:00
/* 0x03 */ " Restore Pointers " , " Disconnect " , " Initiator Error " ,
2006-03-10 17:18:22 +03:00
/* 0x06 */ " Abort Task Set " , " Message Reject " , " Nop " , " Message Parity Error " ,
2005-12-16 00:22:01 +03:00
/* 0x0a */ " Linked Command Complete " , " Linked Command Complete w/flag " ,
2006-03-10 17:18:22 +03:00
/* 0x0c */ " Target Reset " , " Abort Task " , " Clear Task Set " ,
/* 0x0f */ " Initiate Recovery " , " Release Recovery " ,
/* 0x11 */ " Terminate Process " , " Continue Task " , " Target Transfer Disable " ,
/* 0x14 */ NULL , NULL , " Clear ACA " , " LUN Reset "
2005-12-16 00:22:01 +03:00
} ;
static const char * const two_byte_msgs [ ] = {
2005-12-16 00:22:01 +03:00
/* 0x20 */ " Simple Queue Tag " , " Head of Queue Tag " , " Ordered Queue Tag " ,
2006-03-10 17:18:22 +03:00
/* 0x23 */ " Ignore Wide Residue " , " ACA "
2005-12-16 00:22:01 +03:00
} ;
static const char * const extended_msgs [ ] = {
/* 0x00 */ " Modify Data Pointer " , " Synchronous Data Transfer Request " ,
2005-12-16 00:22:01 +03:00
/* 0x02 */ " SCSI-I Extended Identify " , " Wide Data Transfer Request " ,
2006-02-19 06:52:31 +03:00
/* 0x04 */ " Parallel Protocol Request " , " Modify Bidirectional Data Pointer "
2005-12-16 00:22:01 +03:00
} ;
2006-01-06 01:39:18 +03:00
static void print_nego ( const unsigned char * msg , int per , int off , int width )
2005-12-16 00:22:01 +03:00
{
if ( per ) {
char buf [ 20 ] ;
period_to_str ( buf , msg [ per ] ) ;
printk ( " period = %s ns " , buf ) ;
}
if ( off )
printk ( " offset = %d " , msg [ off ] ) ;
if ( width )
printk ( " width = %d " , 8 < < msg [ width ] ) ;
}
2005-12-16 00:22:01 +03:00
2006-02-19 06:52:31 +03:00
static void print_ptr ( const unsigned char * msg , int msb , const char * desc )
{
int ptr = ( msg [ msb ] < < 24 ) | ( msg [ msb + 1 ] < < 16 ) | ( msg [ msb + 2 ] < < 8 ) |
msg [ msb + 3 ] ;
printk ( " %s = %d " , desc , ptr ) ;
}
2005-12-16 00:22:01 +03:00
int spi_print_msg ( const unsigned char * msg )
2005-12-16 00:22:01 +03:00
{
2006-03-10 17:18:22 +03:00
int len = 1 , i ;
2005-12-16 00:22:01 +03:00
if ( msg [ 0 ] = = EXTENDED_MESSAGE ) {
2006-02-19 06:52:31 +03:00
len = 2 + msg [ 1 ] ;
if ( len = = 2 )
len + = 256 ;
2005-12-16 00:22:01 +03:00
if ( msg [ 2 ] < ARRAY_SIZE ( extended_msgs ) )
2005-12-16 00:22:01 +03:00
printk ( " %s " , extended_msgs [ msg [ 2 ] ] ) ;
else
printk ( " Extended Message, reserved code (0x%02x) " ,
( int ) msg [ 2 ] ) ;
switch ( msg [ 2 ] ) {
case EXTENDED_MODIFY_DATA_POINTER :
2006-02-19 06:52:31 +03:00
print_ptr ( msg , 3 , " pointer " ) ;
2005-12-16 00:22:01 +03:00
break ;
case EXTENDED_SDTR :
2005-12-16 00:22:01 +03:00
print_nego ( msg , 3 , 4 , 0 ) ;
2005-12-16 00:22:01 +03:00
break ;
case EXTENDED_WDTR :
2005-12-16 00:22:01 +03:00
print_nego ( msg , 0 , 0 , 3 ) ;
break ;
case EXTENDED_PPR :
print_nego ( msg , 3 , 5 , 6 ) ;
2005-12-16 00:22:01 +03:00
break ;
2006-02-19 06:52:31 +03:00
case EXTENDED_MODIFY_BIDI_DATA_PTR :
print_ptr ( msg , 3 , " out " ) ;
print_ptr ( msg , 7 , " in " ) ;
break ;
2005-12-16 00:22:01 +03:00
default :
for ( i = 2 ; i < len ; + + i )
printk ( " %02x " , msg [ i ] ) ;
}
/* Identify */
} else if ( msg [ 0 ] & 0x80 ) {
printk ( " Identify disconnect %sallowed %s %d " ,
( msg [ 0 ] & 0x40 ) ? " " : " not " ,
( msg [ 0 ] & 0x20 ) ? " target routine " : " lun " ,
msg [ 0 ] & 0x7 ) ;
/* Normal One byte */
} else if ( msg [ 0 ] < 0x1f ) {
2006-03-10 17:18:22 +03:00
if ( msg [ 0 ] < ARRAY_SIZE ( one_byte_msgs ) & & one_byte_msgs [ msg [ 0 ] ] )
2006-02-07 18:05:26 +03:00
printk ( " %s " , one_byte_msgs [ msg [ 0 ] ] ) ;
2005-12-16 00:22:01 +03:00
else
printk ( " reserved (%02x) " , msg [ 0 ] ) ;
2006-03-10 17:18:22 +03:00
} else if ( msg [ 0 ] = = 0x55 ) {
printk ( " QAS Request " ) ;
2005-12-16 00:22:01 +03:00
/* Two byte */
} else if ( msg [ 0 ] < = 0x2f ) {
2005-12-16 00:22:01 +03:00
if ( ( msg [ 0 ] - 0x20 ) < ARRAY_SIZE ( two_byte_msgs ) )
2005-12-16 00:22:01 +03:00
printk ( " %s %02x " , two_byte_msgs [ msg [ 0 ] - 0x20 ] ,
msg [ 1 ] ) ;
else
printk ( " reserved two byte (%02x %02x) " ,
msg [ 0 ] , msg [ 1 ] ) ;
len = 2 ;
} else
2006-02-07 18:05:26 +03:00
printk ( " reserved " ) ;
2005-12-16 00:22:01 +03:00
return len ;
}
2005-12-16 00:22:01 +03:00
EXPORT_SYMBOL ( spi_print_msg ) ;
2005-12-16 00:22:01 +03:00
# else /* ifndef CONFIG_SCSI_CONSTANTS */
2005-12-16 00:22:01 +03:00
int spi_print_msg ( const unsigned char * msg )
2005-12-16 00:22:01 +03:00
{
2006-03-10 17:18:22 +03:00
int len = 1 , i ;
2005-12-16 00:22:01 +03:00
if ( msg [ 0 ] = = EXTENDED_MESSAGE ) {
2006-02-19 06:52:31 +03:00
len = 2 + msg [ 1 ] ;
if ( len = = 2 )
len + = 256 ;
2005-12-16 00:22:01 +03:00
for ( i = 0 ; i < len ; + + i )
printk ( " %02x " , msg [ i ] ) ;
/* Identify */
} else if ( msg [ 0 ] & 0x80 ) {
printk ( " %02x " , msg [ 0 ] ) ;
/* Normal One byte */
2006-03-12 18:54:19 +03:00
} else if ( ( msg [ 0 ] < 0x1f ) | | ( msg [ 0 ] = = 0x55 ) ) {
2005-12-16 00:22:01 +03:00
printk ( " %02x " , msg [ 0 ] ) ;
/* Two byte */
} else if ( msg [ 0 ] < = 0x2f ) {
printk ( " %02x %02x " , msg [ 0 ] , msg [ 1 ] ) ;
len = 2 ;
} else
printk ( " %02x " , msg [ 0 ] ) ;
return len ;
}
2005-12-16 00:22:01 +03:00
EXPORT_SYMBOL ( spi_print_msg ) ;
2005-12-16 00:22:01 +03:00
# endif /* ! CONFIG_SCSI_CONSTANTS */
2005-04-17 02:20:36 +04:00
# define SETUP_ATTRIBUTE(field) \
i - > private_attrs [ count ] = class_device_attr_ # # field ; \
if ( ! i - > f - > set_ # # field ) { \
i - > private_attrs [ count ] . attr . mode = S_IRUGO ; \
i - > private_attrs [ count ] . store = NULL ; \
} \
i - > attrs [ count ] = & i - > private_attrs [ count ] ; \
if ( i - > f - > show_ # # field ) \
count + +
2005-05-07 03:05:20 +04:00
# define SETUP_RELATED_ATTRIBUTE(field, rel_field) \
i - > private_attrs [ count ] = class_device_attr_ # # field ; \
if ( ! i - > f - > set_ # # rel_field ) { \
i - > private_attrs [ count ] . attr . mode = S_IRUGO ; \
i - > private_attrs [ count ] . store = NULL ; \
} \
i - > attrs [ count ] = & i - > private_attrs [ count ] ; \
if ( i - > f - > show_ # # rel_field ) \
count + +
2005-04-17 02:20:36 +04:00
# define SETUP_HOST_ATTRIBUTE(field) \
i - > private_host_attrs [ count ] = class_device_attr_ # # field ; \
if ( ! i - > f - > set_ # # field ) { \
i - > private_host_attrs [ count ] . attr . mode = S_IRUGO ; \
i - > private_host_attrs [ count ] . store = NULL ; \
} \
i - > host_attrs [ count ] = & i - > private_host_attrs [ count ] ; \
count + +
static int spi_device_match ( struct attribute_container * cont ,
struct device * dev )
{
struct scsi_device * sdev ;
struct Scsi_Host * shost ;
2005-08-14 23:34:06 +04:00
struct spi_internal * i ;
2005-04-17 02:20:36 +04:00
if ( ! scsi_is_sdev_device ( dev ) )
return 0 ;
sdev = to_scsi_device ( dev ) ;
shost = sdev - > host ;
if ( ! shost - > transportt | | shost - > transportt - > host_attrs . ac . class
! = & spi_host_class . class )
return 0 ;
/* Note: this class has no device attributes, so it has
* no per - HBA allocation and thus we don ' t need to distinguish
* the attribute containers for the device */
2005-08-14 23:34:06 +04:00
i = to_spi_internal ( shost - > transportt ) ;
if ( i - > f - > deny_binding & & i - > f - > deny_binding ( sdev - > sdev_target ) )
return 0 ;
2005-04-17 02:20:36 +04:00
return 1 ;
}
static int spi_target_match ( struct attribute_container * cont ,
struct device * dev )
{
struct Scsi_Host * shost ;
2005-08-14 23:34:06 +04:00
struct scsi_target * starget ;
2005-04-17 02:20:36 +04:00
struct spi_internal * i ;
if ( ! scsi_is_target_device ( dev ) )
return 0 ;
shost = dev_to_shost ( dev - > parent ) ;
if ( ! shost - > transportt | | shost - > transportt - > host_attrs . ac . class
! = & spi_host_class . class )
return 0 ;
i = to_spi_internal ( shost - > transportt ) ;
2005-08-14 23:34:06 +04:00
starget = to_scsi_target ( dev ) ;
if ( i - > f - > deny_binding & & i - > f - > deny_binding ( starget ) )
return 0 ;
2005-04-17 02:20:36 +04:00
return & i - > t . target_attrs . ac = = cont ;
}
static DECLARE_TRANSPORT_CLASS ( spi_transport_class ,
" spi_transport " ,
spi_setup_transport_attrs ,
NULL ,
NULL ) ;
static DECLARE_ANON_TRANSPORT_CLASS ( spi_device_class ,
spi_device_match ,
spi_device_configure ) ;
struct scsi_transport_template *
spi_attach_transport ( struct spi_function_template * ft )
{
int count = 0 ;
2006-01-16 18:31:18 +03:00
struct spi_internal * i = kzalloc ( sizeof ( struct spi_internal ) ,
GFP_KERNEL ) ;
2005-04-17 02:20:36 +04:00
if ( unlikely ( ! i ) )
return NULL ;
i - > t . target_attrs . ac . class = & spi_transport_class . class ;
i - > t . target_attrs . ac . attrs = & i - > attrs [ 0 ] ;
i - > t . target_attrs . ac . match = spi_target_match ;
transport_container_register ( & i - > t . target_attrs ) ;
i - > t . target_size = sizeof ( struct spi_transport_attrs ) ;
i - > t . host_attrs . ac . class = & spi_host_class . class ;
i - > t . host_attrs . ac . attrs = & i - > host_attrs [ 0 ] ;
i - > t . host_attrs . ac . match = spi_host_match ;
transport_container_register ( & i - > t . host_attrs ) ;
i - > t . host_size = sizeof ( struct spi_host_attrs ) ;
i - > f = ft ;
SETUP_ATTRIBUTE ( period ) ;
2005-05-07 03:05:20 +04:00
SETUP_RELATED_ATTRIBUTE ( min_period , period ) ;
2005-04-17 02:20:36 +04:00
SETUP_ATTRIBUTE ( offset ) ;
2005-05-07 03:05:20 +04:00
SETUP_RELATED_ATTRIBUTE ( max_offset , offset ) ;
2005-04-17 02:20:36 +04:00
SETUP_ATTRIBUTE ( width ) ;
2005-05-07 03:05:20 +04:00
SETUP_RELATED_ATTRIBUTE ( max_width , width ) ;
2005-04-17 02:20:36 +04:00
SETUP_ATTRIBUTE ( iu ) ;
SETUP_ATTRIBUTE ( dt ) ;
SETUP_ATTRIBUTE ( qas ) ;
SETUP_ATTRIBUTE ( wr_flow ) ;
SETUP_ATTRIBUTE ( rd_strm ) ;
SETUP_ATTRIBUTE ( rti ) ;
SETUP_ATTRIBUTE ( pcomp_en ) ;
2005-08-04 00:43:52 +04:00
SETUP_ATTRIBUTE ( hold_mcs ) ;
2005-04-17 02:20:36 +04:00
/* if you add an attribute but forget to increase SPI_NUM_ATTRS
* this bug will trigger */
BUG_ON ( count > SPI_NUM_ATTRS ) ;
i - > attrs [ count + + ] = & class_device_attr_revalidate ;
i - > attrs [ count ] = NULL ;
count = 0 ;
SETUP_HOST_ATTRIBUTE ( signalling ) ;
BUG_ON ( count > SPI_HOST_ATTRS ) ;
i - > host_attrs [ count ] = NULL ;
return & i - > t ;
}
EXPORT_SYMBOL ( spi_attach_transport ) ;
void spi_release_transport ( struct scsi_transport_template * t )
{
struct spi_internal * i = to_spi_internal ( t ) ;
transport_container_unregister ( & i - > t . target_attrs ) ;
transport_container_unregister ( & i - > t . host_attrs ) ;
kfree ( i ) ;
}
EXPORT_SYMBOL ( spi_release_transport ) ;
static __init int spi_transport_init ( void )
{
int error = transport_class_register ( & spi_transport_class ) ;
if ( error )
return error ;
error = anon_transport_class_register ( & spi_device_class ) ;
return transport_class_register ( & spi_host_class ) ;
}
static void __exit spi_transport_exit ( void )
{
transport_class_unregister ( & spi_transport_class ) ;
anon_transport_class_unregister ( & spi_device_class ) ;
transport_class_unregister ( & spi_host_class ) ;
}
MODULE_AUTHOR ( " Martin Hicks " ) ;
MODULE_DESCRIPTION ( " SPI Transport Attributes " ) ;
MODULE_LICENSE ( " GPL " ) ;
module_init ( spi_transport_init ) ;
module_exit ( spi_transport_exit ) ;