2005-05-10 12:59:13 +04:00
/*
* SCSI Media Changer device driver for Linux 2.6
*
* ( c ) 1996 - 2003 Gerd Knorr < kraxel @ bytesex . org >
*
*/
# define VERSION "0.25"
# include <linux/module.h>
# include <linux/init.h>
# include <linux/fs.h>
# include <linux/kernel.h>
# include <linux/mm.h>
# include <linux/major.h>
# include <linux/string.h>
# include <linux/errno.h>
# include <linux/interrupt.h>
# include <linux/blkdev.h>
# include <linux/completion.h>
# include <linux/compat.h>
# include <linux/chio.h> /* here are all the ioctls */
2006-01-11 15:16:10 +03:00
# include <linux/mutex.h>
2008-01-24 11:24:50 +03:00
# include <linux/idr.h>
2008-05-17 00:12:52 +04:00
# include <linux/smp_lock.h>
2005-05-10 12:59:13 +04:00
# include <scsi/scsi.h>
# include <scsi/scsi_cmnd.h>
# include <scsi/scsi_driver.h>
# include <scsi/scsi_ioctl.h>
# include <scsi/scsi_host.h>
# include <scsi/scsi_device.h>
2005-06-13 07:37:10 +04:00
# include <scsi/scsi_eh.h>
2005-05-10 12:59:13 +04:00
# include <scsi/scsi_dbg.h>
# define CH_DT_MAX 16
# define CH_TYPES 8
2008-01-24 11:24:50 +03:00
# define CH_MAX_DEVS 128
2005-05-10 12:59:13 +04:00
MODULE_DESCRIPTION ( " device driver for scsi media changer devices " ) ;
MODULE_AUTHOR ( " Gerd Knorr <kraxel@bytesex.org> " ) ;
MODULE_LICENSE ( " GPL " ) ;
2006-03-08 11:14:20 +03:00
MODULE_ALIAS_CHARDEV_MAJOR ( SCSI_CHANGER_MAJOR ) ;
2009-03-10 19:19:44 +03:00
MODULE_ALIAS_SCSI_DEVICE ( TYPE_MEDIUM_CHANGER ) ;
2005-05-10 12:59:13 +04:00
static int init = 1 ;
module_param ( init , int , 0444 ) ;
MODULE_PARM_DESC ( init , \
" initialize element status on driver load (default: on) " ) ;
static int timeout_move = 300 ;
module_param ( timeout_move , int , 0644 ) ;
MODULE_PARM_DESC ( timeout_move , " timeout for move commands "
" (default: 300 seconds) " ) ;
static int timeout_init = 3600 ;
module_param ( timeout_init , int , 0644 ) ;
MODULE_PARM_DESC ( timeout_init , " timeout for INITIALIZE ELEMENT STATUS "
" (default: 3600 seconds) " ) ;
static int verbose = 1 ;
module_param ( verbose , int , 0644 ) ;
MODULE_PARM_DESC ( verbose , " be verbose (default: on) " ) ;
static int debug = 0 ;
module_param ( debug , int , 0644 ) ;
MODULE_PARM_DESC ( debug , " enable/disable debug messages, also prints more "
" detailed sense codes on scsi errors (default: off) " ) ;
static int dt_id [ CH_DT_MAX ] = { [ 0 . . . ( CH_DT_MAX - 1 ) ] = - 1 } ;
static int dt_lun [ CH_DT_MAX ] ;
module_param_array ( dt_id , int , NULL , 0444 ) ;
module_param_array ( dt_lun , int , NULL , 0444 ) ;
/* tell the driver about vendor-specific slots */
static int vendor_firsts [ CH_TYPES - 4 ] ;
static int vendor_counts [ CH_TYPES - 4 ] ;
module_param_array ( vendor_firsts , int , NULL , 0444 ) ;
module_param_array ( vendor_counts , int , NULL , 0444 ) ;
2005-11-28 18:22:25 +03:00
static const char * vendor_labels [ CH_TYPES - 4 ] = {
2005-05-10 12:59:13 +04:00
" v0 " , " v1 " , " v2 " , " v3 "
} ;
// module_param_string_array(vendor_labels, NULL, 0444);
# define dprintk(fmt, arg...) if (debug) \
printk ( KERN_DEBUG " %s: " fmt , ch - > name , # # arg )
# define vprintk(fmt, arg...) if (verbose) \
printk ( KERN_INFO " %s: " fmt , ch - > name , # # arg )
/* ------------------------------------------------------------------- */
# define MAX_RETRIES 1
2005-05-12 12:25:26 +04:00
static struct class * ch_sysfs_class ;
2005-05-10 12:59:13 +04:00
typedef struct {
struct list_head list ;
int minor ;
char name [ 8 ] ;
struct scsi_device * device ;
struct scsi_device * * dt ; /* ptrs to data transfer elements */
u_int firsts [ CH_TYPES ] ;
u_int counts [ CH_TYPES ] ;
u_int unit_attention ;
u_int voltags ;
2006-01-11 15:16:10 +03:00
struct mutex lock ;
2005-05-10 12:59:13 +04:00
} scsi_changer ;
2008-01-24 11:24:50 +03:00
static DEFINE_IDR ( ch_index_idr ) ;
static DEFINE_SPINLOCK ( ch_index_lock ) ;
2005-05-10 12:59:13 +04:00
2005-11-28 18:22:25 +03:00
static const struct {
2005-05-10 12:59:13 +04:00
unsigned char sense ;
unsigned char asc ;
unsigned char ascq ;
int errno ;
2008-04-01 09:05:30 +04:00
} ch_err [ ] = {
2005-05-10 12:59:13 +04:00
/* Just filled in what looks right. Hav'nt checked any standard paper for
these errno assignments , so they may be wrong . . . */
{
. sense = ILLEGAL_REQUEST ,
. asc = 0x21 ,
. ascq = 0x01 ,
. errno = EBADSLT , /* Invalid element address */
} , {
. sense = ILLEGAL_REQUEST ,
. asc = 0x28 ,
. ascq = 0x01 ,
. errno = EBADE , /* Import or export element accessed */
} , {
. sense = ILLEGAL_REQUEST ,
. asc = 0x3B ,
. ascq = 0x0D ,
. errno = EXFULL , /* Medium destination element full */
} , {
. sense = ILLEGAL_REQUEST ,
. asc = 0x3B ,
. ascq = 0x0E ,
. errno = EBADE , /* Medium source element empty */
} , {
. sense = ILLEGAL_REQUEST ,
. asc = 0x20 ,
. ascq = 0x00 ,
. errno = EBADRQC , /* Invalid command operation code */
} , {
/* end of list */
}
} ;
/* ------------------------------------------------------------------- */
2005-06-13 07:37:10 +04:00
static int ch_find_errno ( struct scsi_sense_hdr * sshdr )
2005-05-10 12:59:13 +04:00
{
int i , errno = 0 ;
/* Check to see if additional sense information is available */
2005-06-13 07:37:10 +04:00
if ( scsi_sense_valid ( sshdr ) & &
sshdr - > asc ! = 0 ) {
2008-04-01 09:05:30 +04:00
for ( i = 0 ; ch_err [ i ] . errno ! = 0 ; i + + ) {
if ( ch_err [ i ] . sense = = sshdr - > sense_key & &
ch_err [ i ] . asc = = sshdr - > asc & &
ch_err [ i ] . ascq = = sshdr - > ascq ) {
errno = - ch_err [ i ] . errno ;
2005-05-10 12:59:13 +04:00
break ;
}
}
}
if ( errno = = 0 )
errno = - EIO ;
return errno ;
}
static int
ch_do_scsi ( scsi_changer * ch , unsigned char * cmd ,
void * buffer , unsigned buflength ,
enum dma_data_direction direction )
{
2005-06-13 07:37:10 +04:00
int errno , retries = 0 , timeout , result ;
struct scsi_sense_hdr sshdr ;
2008-01-24 11:24:52 +03:00
2005-05-10 12:59:13 +04:00
timeout = ( cmd [ 0 ] = = INITIALIZE_ELEMENT_STATUS )
? timeout_init : timeout_move ;
retry :
errno = 0 ;
if ( debug ) {
dprintk ( " command: " ) ;
__scsi_print_command ( cmd ) ;
}
2005-06-13 07:37:10 +04:00
result = scsi_execute_req ( ch - > device , cmd , direction , buffer ,
buflength , & sshdr , timeout * HZ ,
2008-12-04 08:24:39 +03:00
MAX_RETRIES , NULL ) ;
2005-05-10 12:59:13 +04:00
2005-06-13 07:37:10 +04:00
dprintk ( " result: 0x%x \n " , result ) ;
if ( driver_byte ( result ) & DRIVER_SENSE ) {
2005-05-10 12:59:13 +04:00
if ( debug )
2005-06-13 07:37:10 +04:00
scsi_print_sense_hdr ( ch - > name , & sshdr ) ;
errno = ch_find_errno ( & sshdr ) ;
2005-05-10 12:59:13 +04:00
2005-06-13 07:37:10 +04:00
switch ( sshdr . sense_key ) {
2005-05-10 12:59:13 +04:00
case UNIT_ATTENTION :
ch - > unit_attention = 1 ;
if ( retries + + < 3 )
goto retry ;
break ;
}
}
return errno ;
}
/* ------------------------------------------------------------------------ */
static int
ch_elem_to_typecode ( scsi_changer * ch , u_int elem )
{
int i ;
2008-01-24 11:24:52 +03:00
2005-05-10 12:59:13 +04:00
for ( i = 0 ; i < CH_TYPES ; i + + ) {
if ( elem > = ch - > firsts [ i ] & &
elem < ch - > firsts [ i ] +
ch - > counts [ i ] )
return i + 1 ;
}
return 0 ;
}
static int
ch_read_element_status ( scsi_changer * ch , u_int elem , char * data )
{
u_char cmd [ 12 ] ;
u_char * buffer ;
int result ;
2008-01-24 11:24:52 +03:00
2005-05-10 12:59:13 +04:00
buffer = kmalloc ( 512 , GFP_KERNEL | GFP_DMA ) ;
if ( ! buffer )
return - ENOMEM ;
2008-01-24 11:24:52 +03:00
2005-05-10 12:59:13 +04:00
retry :
memset ( cmd , 0 , sizeof ( cmd ) ) ;
cmd [ 0 ] = READ_ELEMENT_STATUS ;
2008-01-24 11:24:52 +03:00
cmd [ 1 ] = ( ch - > device - > lun < < 5 ) |
2005-05-10 12:59:13 +04:00
( ch - > voltags ? 0x10 : 0 ) |
ch_elem_to_typecode ( ch , elem ) ;
cmd [ 2 ] = ( elem > > 8 ) & 0xff ;
cmd [ 3 ] = elem & 0xff ;
cmd [ 5 ] = 1 ;
cmd [ 9 ] = 255 ;
if ( 0 = = ( result = ch_do_scsi ( ch , cmd , buffer , 256 , DMA_FROM_DEVICE ) ) ) {
if ( ( ( buffer [ 16 ] < < 8 ) | buffer [ 17 ] ) ! = elem ) {
dprintk ( " asked for element 0x%02x, got 0x%02x \n " ,
elem , ( buffer [ 16 ] < < 8 ) | buffer [ 17 ] ) ;
kfree ( buffer ) ;
return - EIO ;
}
memcpy ( data , buffer + 16 , 16 ) ;
} else {
if ( ch - > voltags ) {
ch - > voltags = 0 ;
vprintk ( " device has no volume tag support \n " ) ;
goto retry ;
}
dprintk ( " READ ELEMENT STATUS for element 0x%x failed \n " , elem ) ;
}
kfree ( buffer ) ;
return result ;
}
2008-01-24 11:24:52 +03:00
static int
2005-05-10 12:59:13 +04:00
ch_init_elem ( scsi_changer * ch )
{
int err ;
u_char cmd [ 6 ] ;
vprintk ( " INITIALIZE ELEMENT STATUS, may take some time ... \n " ) ;
memset ( cmd , 0 , sizeof ( cmd ) ) ;
cmd [ 0 ] = INITIALIZE_ELEMENT_STATUS ;
cmd [ 1 ] = ch - > device - > lun < < 5 ;
err = ch_do_scsi ( ch , cmd , NULL , 0 , DMA_NONE ) ;
vprintk ( " ... finished \n " ) ;
return err ;
}
static int
ch_readconfig ( scsi_changer * ch )
{
u_char cmd [ 10 ] , data [ 16 ] ;
u_char * buffer ;
int result , id , lun , i ;
u_int elem ;
2007-04-16 10:05:33 +04:00
buffer = kzalloc ( 512 , GFP_KERNEL | GFP_DMA ) ;
2005-05-10 12:59:13 +04:00
if ( ! buffer )
return - ENOMEM ;
2008-01-24 11:24:52 +03:00
2005-05-10 12:59:13 +04:00
memset ( cmd , 0 , sizeof ( cmd ) ) ;
cmd [ 0 ] = MODE_SENSE ;
cmd [ 1 ] = ch - > device - > lun < < 5 ;
cmd [ 2 ] = 0x1d ;
cmd [ 4 ] = 255 ;
result = ch_do_scsi ( ch , cmd , buffer , 255 , DMA_FROM_DEVICE ) ;
if ( 0 ! = result ) {
cmd [ 1 ] | = ( 1 < < 3 ) ;
result = ch_do_scsi ( ch , cmd , buffer , 255 , DMA_FROM_DEVICE ) ;
}
if ( 0 = = result ) {
ch - > firsts [ CHET_MT ] =
( buffer [ buffer [ 3 ] + 6 ] < < 8 ) | buffer [ buffer [ 3 ] + 7 ] ;
ch - > counts [ CHET_MT ] =
( buffer [ buffer [ 3 ] + 8 ] < < 8 ) | buffer [ buffer [ 3 ] + 9 ] ;
ch - > firsts [ CHET_ST ] =
( buffer [ buffer [ 3 ] + 10 ] < < 8 ) | buffer [ buffer [ 3 ] + 11 ] ;
ch - > counts [ CHET_ST ] =
( buffer [ buffer [ 3 ] + 12 ] < < 8 ) | buffer [ buffer [ 3 ] + 13 ] ;
ch - > firsts [ CHET_IE ] =
( buffer [ buffer [ 3 ] + 14 ] < < 8 ) | buffer [ buffer [ 3 ] + 15 ] ;
ch - > counts [ CHET_IE ] =
( buffer [ buffer [ 3 ] + 16 ] < < 8 ) | buffer [ buffer [ 3 ] + 17 ] ;
ch - > firsts [ CHET_DT ] =
( buffer [ buffer [ 3 ] + 18 ] < < 8 ) | buffer [ buffer [ 3 ] + 19 ] ;
ch - > counts [ CHET_DT ] =
( buffer [ buffer [ 3 ] + 20 ] < < 8 ) | buffer [ buffer [ 3 ] + 21 ] ;
vprintk ( " type #1 (mt): 0x%x+%d [medium transport] \n " ,
ch - > firsts [ CHET_MT ] ,
ch - > counts [ CHET_MT ] ) ;
vprintk ( " type #2 (st): 0x%x+%d [storage] \n " ,
ch - > firsts [ CHET_ST ] ,
ch - > counts [ CHET_ST ] ) ;
vprintk ( " type #3 (ie): 0x%x+%d [import/export] \n " ,
ch - > firsts [ CHET_IE ] ,
ch - > counts [ CHET_IE ] ) ;
vprintk ( " type #4 (dt): 0x%x+%d [data transfer] \n " ,
ch - > firsts [ CHET_DT ] ,
ch - > counts [ CHET_DT ] ) ;
} else {
vprintk ( " reading element address assigment page failed! \n " ) ;
}
2008-01-24 11:24:52 +03:00
2005-05-10 12:59:13 +04:00
/* vendor specific element types */
for ( i = 0 ; i < 4 ; i + + ) {
if ( 0 = = vendor_counts [ i ] )
continue ;
if ( NULL = = vendor_labels [ i ] )
continue ;
ch - > firsts [ CHET_V1 + i ] = vendor_firsts [ i ] ;
ch - > counts [ CHET_V1 + i ] = vendor_counts [ i ] ;
vprintk ( " type #%d (v%d): 0x%x+%d [%s, vendor specific] \n " ,
i + 5 , i + 1 , vendor_firsts [ i ] , vendor_counts [ i ] ,
vendor_labels [ i ] ) ;
}
/* look up the devices of the data transfer elements */
ch - > dt = kmalloc ( ch - > counts [ CHET_DT ] * sizeof ( struct scsi_device ) ,
GFP_KERNEL ) ;
2009-08-08 00:42:21 +04:00
if ( ! ch - > dt ) {
kfree ( buffer ) ;
return - ENOMEM ;
}
2005-05-10 12:59:13 +04:00
for ( elem = 0 ; elem < ch - > counts [ CHET_DT ] ; elem + + ) {
id = - 1 ;
lun = 0 ;
if ( elem < CH_DT_MAX & & - 1 ! = dt_id [ elem ] ) {
id = dt_id [ elem ] ;
lun = dt_lun [ elem ] ;
vprintk ( " dt 0x%x: [insmod option] " ,
elem + ch - > firsts [ CHET_DT ] ) ;
} else if ( 0 ! = ch_read_element_status
( ch , elem + ch - > firsts [ CHET_DT ] , data ) ) {
vprintk ( " dt 0x%x: READ ELEMENT STATUS failed \n " ,
elem + ch - > firsts [ CHET_DT ] ) ;
} else {
vprintk ( " dt 0x%x: " , elem + ch - > firsts [ CHET_DT ] ) ;
if ( data [ 6 ] & 0x80 ) {
if ( verbose )
printk ( " not this SCSI bus \n " ) ;
ch - > dt [ elem ] = NULL ;
} else if ( 0 = = ( data [ 6 ] & 0x30 ) ) {
if ( verbose )
printk ( " ID/LUN unknown \n " ) ;
ch - > dt [ elem ] = NULL ;
} else {
id = ch - > device - > id ;
lun = 0 ;
if ( data [ 6 ] & 0x20 ) id = data [ 7 ] ;
if ( data [ 6 ] & 0x10 ) lun = data [ 6 ] & 7 ;
}
}
if ( - 1 ! = id ) {
if ( verbose )
printk ( " ID %i, LUN %i, " , id , lun ) ;
ch - > dt [ elem ] =
scsi_device_lookup ( ch - > device - > host ,
ch - > device - > channel ,
id , lun ) ;
if ( ! ch - > dt [ elem ] ) {
/* should not happen */
if ( verbose )
printk ( " Huh? device not found! \n " ) ;
} else {
if ( verbose )
printk ( " name: %8.8s %16.16s %4.4s \n " ,
ch - > dt [ elem ] - > vendor ,
ch - > dt [ elem ] - > model ,
ch - > dt [ elem ] - > rev ) ;
}
}
}
ch - > voltags = 1 ;
kfree ( buffer ) ;
return 0 ;
}
/* ------------------------------------------------------------------------ */
static int
ch_position ( scsi_changer * ch , u_int trans , u_int elem , int rotate )
{
u_char cmd [ 10 ] ;
2008-01-24 11:24:52 +03:00
2005-05-10 12:59:13 +04:00
dprintk ( " position: 0x%x \n " , elem ) ;
if ( 0 = = trans )
trans = ch - > firsts [ CHET_MT ] ;
memset ( cmd , 0 , sizeof ( cmd ) ) ;
cmd [ 0 ] = POSITION_TO_ELEMENT ;
cmd [ 1 ] = ch - > device - > lun < < 5 ;
cmd [ 2 ] = ( trans > > 8 ) & 0xff ;
cmd [ 3 ] = trans & 0xff ;
cmd [ 4 ] = ( elem > > 8 ) & 0xff ;
cmd [ 5 ] = elem & 0xff ;
cmd [ 8 ] = rotate ? 1 : 0 ;
return ch_do_scsi ( ch , cmd , NULL , 0 , DMA_NONE ) ;
}
static int
ch_move ( scsi_changer * ch , u_int trans , u_int src , u_int dest , int rotate )
{
u_char cmd [ 12 ] ;
2008-01-24 11:24:52 +03:00
2005-05-10 12:59:13 +04:00
dprintk ( " move: 0x%x => 0x%x \n " , src , dest ) ;
if ( 0 = = trans )
trans = ch - > firsts [ CHET_MT ] ;
memset ( cmd , 0 , sizeof ( cmd ) ) ;
cmd [ 0 ] = MOVE_MEDIUM ;
cmd [ 1 ] = ch - > device - > lun < < 5 ;
cmd [ 2 ] = ( trans > > 8 ) & 0xff ;
cmd [ 3 ] = trans & 0xff ;
cmd [ 4 ] = ( src > > 8 ) & 0xff ;
cmd [ 5 ] = src & 0xff ;
cmd [ 6 ] = ( dest > > 8 ) & 0xff ;
cmd [ 7 ] = dest & 0xff ;
cmd [ 10 ] = rotate ? 1 : 0 ;
return ch_do_scsi ( ch , cmd , NULL , 0 , DMA_NONE ) ;
}
static int
ch_exchange ( scsi_changer * ch , u_int trans , u_int src ,
u_int dest1 , u_int dest2 , int rotate1 , int rotate2 )
{
u_char cmd [ 12 ] ;
2008-01-24 11:24:52 +03:00
2005-05-10 12:59:13 +04:00
dprintk ( " exchange: 0x%x => 0x%x => 0x%x \n " ,
src , dest1 , dest2 ) ;
if ( 0 = = trans )
trans = ch - > firsts [ CHET_MT ] ;
memset ( cmd , 0 , sizeof ( cmd ) ) ;
cmd [ 0 ] = EXCHANGE_MEDIUM ;
cmd [ 1 ] = ch - > device - > lun < < 5 ;
cmd [ 2 ] = ( trans > > 8 ) & 0xff ;
cmd [ 3 ] = trans & 0xff ;
cmd [ 4 ] = ( src > > 8 ) & 0xff ;
cmd [ 5 ] = src & 0xff ;
cmd [ 6 ] = ( dest1 > > 8 ) & 0xff ;
cmd [ 7 ] = dest1 & 0xff ;
cmd [ 8 ] = ( dest2 > > 8 ) & 0xff ;
cmd [ 9 ] = dest2 & 0xff ;
cmd [ 10 ] = ( rotate1 ? 1 : 0 ) | ( rotate2 ? 2 : 0 ) ;
2008-01-24 11:24:52 +03:00
2005-05-10 12:59:13 +04:00
return ch_do_scsi ( ch , cmd , NULL , 0 , DMA_NONE ) ;
}
static void
ch_check_voltag ( char * tag )
{
int i ;
for ( i = 0 ; i < 32 ; i + + ) {
/* restrict to ascii */
if ( tag [ i ] > = 0x7f | | tag [ i ] < 0x20 )
tag [ i ] = ' ' ;
/* don't allow search wildcards */
if ( tag [ i ] = = ' ? ' | |
tag [ i ] = = ' * ' )
tag [ i ] = ' ' ;
}
}
static int
ch_set_voltag ( scsi_changer * ch , u_int elem ,
int alternate , int clear , u_char * tag )
{
u_char cmd [ 12 ] ;
u_char * buffer ;
int result ;
2007-04-16 10:05:33 +04:00
buffer = kzalloc ( 512 , GFP_KERNEL ) ;
2005-05-10 12:59:13 +04:00
if ( ! buffer )
return - ENOMEM ;
dprintk ( " %s %s voltag: 0x%x => \" %s \" \n " ,
clear ? " clear " : " set " ,
alternate ? " alternate " : " primary " ,
elem , tag ) ;
memset ( cmd , 0 , sizeof ( cmd ) ) ;
cmd [ 0 ] = SEND_VOLUME_TAG ;
2008-01-24 11:24:52 +03:00
cmd [ 1 ] = ( ch - > device - > lun < < 5 ) |
2005-05-10 12:59:13 +04:00
ch_elem_to_typecode ( ch , elem ) ;
cmd [ 2 ] = ( elem > > 8 ) & 0xff ;
cmd [ 3 ] = elem & 0xff ;
cmd [ 5 ] = clear
? ( alternate ? 0x0d : 0x0c )
: ( alternate ? 0x0b : 0x0a ) ;
2008-01-24 11:24:52 +03:00
2005-05-10 12:59:13 +04:00
cmd [ 9 ] = 255 ;
memcpy ( buffer , tag , 32 ) ;
ch_check_voltag ( buffer ) ;
result = ch_do_scsi ( ch , cmd , buffer , 256 , DMA_TO_DEVICE ) ;
kfree ( buffer ) ;
return result ;
}
2005-09-10 01:03:44 +04:00
static int ch_gstatus ( scsi_changer * ch , int type , unsigned char __user * dest )
2005-05-10 12:59:13 +04:00
{
int retval = 0 ;
u_char data [ 16 ] ;
unsigned int i ;
2008-01-24 11:24:52 +03:00
2006-01-11 15:16:10 +03:00
mutex_lock ( & ch - > lock ) ;
2005-05-10 12:59:13 +04:00
for ( i = 0 ; i < ch - > counts [ type ] ; i + + ) {
if ( 0 ! = ch_read_element_status
( ch , ch - > firsts [ type ] + i , data ) ) {
retval = - EIO ;
break ;
}
put_user ( data [ 2 ] , dest + i ) ;
if ( data [ 2 ] & CESTATUS_EXCEPT )
vprintk ( " element 0x%x: asc=0x%x, ascq=0x%x \n " ,
ch - > firsts [ type ] + i ,
( int ) data [ 4 ] , ( int ) data [ 5 ] ) ;
retval = ch_read_element_status
( ch , ch - > firsts [ type ] + i , data ) ;
if ( 0 ! = retval )
break ;
}
2006-01-11 15:16:10 +03:00
mutex_unlock ( & ch - > lock ) ;
2005-05-10 12:59:13 +04:00
return retval ;
}
/* ------------------------------------------------------------------------ */
static int
ch_release ( struct inode * inode , struct file * file )
{
scsi_changer * ch = file - > private_data ;
scsi_device_put ( ch - > device ) ;
file - > private_data = NULL ;
return 0 ;
}
static int
ch_open ( struct inode * inode , struct file * file )
{
2008-01-24 11:24:50 +03:00
scsi_changer * ch ;
2005-05-10 12:59:13 +04:00
int minor = iminor ( inode ) ;
2008-05-17 00:12:52 +04:00
lock_kernel ( ) ;
2008-01-24 11:24:50 +03:00
spin_lock ( & ch_index_lock ) ;
ch = idr_find ( & ch_index_idr , minor ) ;
2005-05-10 12:59:13 +04:00
if ( NULL = = ch | | scsi_device_get ( ch - > device ) ) {
2008-01-24 11:24:50 +03:00
spin_unlock ( & ch_index_lock ) ;
2008-05-17 00:12:52 +04:00
unlock_kernel ( ) ;
2005-05-10 12:59:13 +04:00
return - ENXIO ;
}
2008-01-24 11:24:50 +03:00
spin_unlock ( & ch_index_lock ) ;
2005-05-10 12:59:13 +04:00
file - > private_data = ch ;
2008-05-17 00:12:52 +04:00
unlock_kernel ( ) ;
2005-05-10 12:59:13 +04:00
return 0 ;
}
static int
ch_checkrange ( scsi_changer * ch , unsigned int type , unsigned int unit )
{
if ( type > = CH_TYPES | | unit > = ch - > counts [ type ] )
return - 1 ;
return 0 ;
}
2008-01-14 17:43:18 +03:00
static long ch_ioctl ( struct file * file ,
2005-05-10 12:59:13 +04:00
unsigned int cmd , unsigned long arg )
{
scsi_changer * ch = file - > private_data ;
int retval ;
2005-09-10 01:03:44 +04:00
void __user * argp = ( void __user * ) arg ;
2008-01-24 11:24:52 +03:00
2005-05-10 12:59:13 +04:00
switch ( cmd ) {
case CHIOGPARAMS :
{
struct changer_params params ;
2008-01-24 11:24:52 +03:00
2005-05-10 12:59:13 +04:00
params . cp_curpicker = 0 ;
params . cp_npickers = ch - > counts [ CHET_MT ] ;
params . cp_nslots = ch - > counts [ CHET_ST ] ;
params . cp_nportals = ch - > counts [ CHET_IE ] ;
params . cp_ndrives = ch - > counts [ CHET_DT ] ;
2008-01-24 11:24:52 +03:00
2005-09-10 01:03:44 +04:00
if ( copy_to_user ( argp , & params , sizeof ( params ) ) )
2005-05-10 12:59:13 +04:00
return - EFAULT ;
return 0 ;
}
case CHIOGVPARAMS :
{
struct changer_vendor_params vparams ;
memset ( & vparams , 0 , sizeof ( vparams ) ) ;
if ( ch - > counts [ CHET_V1 ] ) {
vparams . cvp_n1 = ch - > counts [ CHET_V1 ] ;
strncpy ( vparams . cvp_label1 , vendor_labels [ 0 ] , 16 ) ;
}
if ( ch - > counts [ CHET_V2 ] ) {
vparams . cvp_n2 = ch - > counts [ CHET_V2 ] ;
strncpy ( vparams . cvp_label2 , vendor_labels [ 1 ] , 16 ) ;
}
if ( ch - > counts [ CHET_V3 ] ) {
vparams . cvp_n3 = ch - > counts [ CHET_V3 ] ;
strncpy ( vparams . cvp_label3 , vendor_labels [ 2 ] , 16 ) ;
}
if ( ch - > counts [ CHET_V4 ] ) {
vparams . cvp_n4 = ch - > counts [ CHET_V4 ] ;
strncpy ( vparams . cvp_label4 , vendor_labels [ 3 ] , 16 ) ;
}
2005-09-10 01:03:44 +04:00
if ( copy_to_user ( argp , & vparams , sizeof ( vparams ) ) )
2005-05-10 12:59:13 +04:00
return - EFAULT ;
return 0 ;
}
2008-01-24 11:24:52 +03:00
2005-05-10 12:59:13 +04:00
case CHIOPOSITION :
{
struct changer_position pos ;
2008-01-24 11:24:52 +03:00
2005-09-10 01:03:44 +04:00
if ( copy_from_user ( & pos , argp , sizeof ( pos ) ) )
2005-05-10 12:59:13 +04:00
return - EFAULT ;
if ( 0 ! = ch_checkrange ( ch , pos . cp_type , pos . cp_unit ) ) {
dprintk ( " CHIOPOSITION: invalid parameter \n " ) ;
return - EBADSLT ;
}
2006-01-11 15:16:10 +03:00
mutex_lock ( & ch - > lock ) ;
2005-05-10 12:59:13 +04:00
retval = ch_position ( ch , 0 ,
ch - > firsts [ pos . cp_type ] + pos . cp_unit ,
pos . cp_flags & CP_INVERT ) ;
2006-01-11 15:16:10 +03:00
mutex_unlock ( & ch - > lock ) ;
2005-05-10 12:59:13 +04:00
return retval ;
}
2008-01-24 11:24:52 +03:00
2005-05-10 12:59:13 +04:00
case CHIOMOVE :
{
struct changer_move mv ;
2005-09-10 01:03:44 +04:00
if ( copy_from_user ( & mv , argp , sizeof ( mv ) ) )
2005-05-10 12:59:13 +04:00
return - EFAULT ;
if ( 0 ! = ch_checkrange ( ch , mv . cm_fromtype , mv . cm_fromunit ) | |
0 ! = ch_checkrange ( ch , mv . cm_totype , mv . cm_tounit ) ) {
dprintk ( " CHIOMOVE: invalid parameter \n " ) ;
return - EBADSLT ;
}
2008-01-24 11:24:52 +03:00
2006-01-11 15:16:10 +03:00
mutex_lock ( & ch - > lock ) ;
2005-05-10 12:59:13 +04:00
retval = ch_move ( ch , 0 ,
ch - > firsts [ mv . cm_fromtype ] + mv . cm_fromunit ,
ch - > firsts [ mv . cm_totype ] + mv . cm_tounit ,
mv . cm_flags & CM_INVERT ) ;
2006-01-11 15:16:10 +03:00
mutex_unlock ( & ch - > lock ) ;
2005-05-10 12:59:13 +04:00
return retval ;
}
case CHIOEXCHANGE :
{
struct changer_exchange mv ;
2008-01-24 11:24:52 +03:00
2005-09-10 01:03:44 +04:00
if ( copy_from_user ( & mv , argp , sizeof ( mv ) ) )
2005-05-10 12:59:13 +04:00
return - EFAULT ;
if ( 0 ! = ch_checkrange ( ch , mv . ce_srctype , mv . ce_srcunit ) | |
0 ! = ch_checkrange ( ch , mv . ce_fdsttype , mv . ce_fdstunit ) | |
0 ! = ch_checkrange ( ch , mv . ce_sdsttype , mv . ce_sdstunit ) ) {
dprintk ( " CHIOEXCHANGE: invalid parameter \n " ) ;
return - EBADSLT ;
}
2008-01-24 11:24:52 +03:00
2006-01-11 15:16:10 +03:00
mutex_lock ( & ch - > lock ) ;
2005-05-10 12:59:13 +04:00
retval = ch_exchange
( ch , 0 ,
ch - > firsts [ mv . ce_srctype ] + mv . ce_srcunit ,
ch - > firsts [ mv . ce_fdsttype ] + mv . ce_fdstunit ,
ch - > firsts [ mv . ce_sdsttype ] + mv . ce_sdstunit ,
mv . ce_flags & CE_INVERT1 , mv . ce_flags & CE_INVERT2 ) ;
2006-01-11 15:16:10 +03:00
mutex_unlock ( & ch - > lock ) ;
2005-05-10 12:59:13 +04:00
return retval ;
}
case CHIOGSTATUS :
{
struct changer_element_status ces ;
2008-01-24 11:24:52 +03:00
2005-09-10 01:03:44 +04:00
if ( copy_from_user ( & ces , argp , sizeof ( ces ) ) )
2005-05-10 12:59:13 +04:00
return - EFAULT ;
if ( ces . ces_type < 0 | | ces . ces_type > = CH_TYPES )
return - EINVAL ;
return ch_gstatus ( ch , ces . ces_type , ces . ces_data ) ;
}
case CHIOGELEM :
{
struct changer_get_element cge ;
2008-04-01 09:05:30 +04:00
u_char ch_cmd [ 12 ] ;
u_char * buffer ;
2005-05-10 12:59:13 +04:00
unsigned int elem ;
int result , i ;
2008-01-24 11:24:52 +03:00
2005-09-10 01:03:44 +04:00
if ( copy_from_user ( & cge , argp , sizeof ( cge ) ) )
2005-05-10 12:59:13 +04:00
return - EFAULT ;
if ( 0 ! = ch_checkrange ( ch , cge . cge_type , cge . cge_unit ) )
return - EINVAL ;
elem = ch - > firsts [ cge . cge_type ] + cge . cge_unit ;
2008-01-24 11:24:52 +03:00
2005-05-10 12:59:13 +04:00
buffer = kmalloc ( 512 , GFP_KERNEL | GFP_DMA ) ;
if ( ! buffer )
return - ENOMEM ;
2006-01-11 15:16:10 +03:00
mutex_lock ( & ch - > lock ) ;
2008-01-24 11:24:52 +03:00
2005-05-10 12:59:13 +04:00
voltag_retry :
2008-04-01 09:05:30 +04:00
memset ( ch_cmd , 0 , sizeof ( ch_cmd ) ) ;
ch_cmd [ 0 ] = READ_ELEMENT_STATUS ;
ch_cmd [ 1 ] = ( ch - > device - > lun < < 5 ) |
2005-05-10 12:59:13 +04:00
( ch - > voltags ? 0x10 : 0 ) |
ch_elem_to_typecode ( ch , elem ) ;
2008-04-01 09:05:30 +04:00
ch_cmd [ 2 ] = ( elem > > 8 ) & 0xff ;
ch_cmd [ 3 ] = elem & 0xff ;
ch_cmd [ 5 ] = 1 ;
ch_cmd [ 9 ] = 255 ;
2008-01-24 11:24:52 +03:00
2008-04-01 09:05:30 +04:00
result = ch_do_scsi ( ch , ch_cmd , buffer , 256 , DMA_FROM_DEVICE ) ;
if ( ! result ) {
2005-05-10 12:59:13 +04:00
cge . cge_status = buffer [ 18 ] ;
cge . cge_flags = 0 ;
if ( buffer [ 18 ] & CESTATUS_EXCEPT ) {
cge . cge_errno = EIO ;
}
if ( buffer [ 25 ] & 0x80 ) {
cge . cge_flags | = CGE_SRC ;
if ( buffer [ 25 ] & 0x40 )
cge . cge_flags | = CGE_INVERT ;
elem = ( buffer [ 26 ] < < 8 ) | buffer [ 27 ] ;
for ( i = 0 ; i < 4 ; i + + ) {
if ( elem > = ch - > firsts [ i ] & &
elem < ch - > firsts [ i ] + ch - > counts [ i ] ) {
cge . cge_srctype = i ;
cge . cge_srcunit = elem - ch - > firsts [ i ] ;
}
}
}
if ( ( buffer [ 22 ] & 0x30 ) = = 0x30 ) {
cge . cge_flags | = CGE_IDLUN ;
cge . cge_id = buffer [ 23 ] ;
cge . cge_lun = buffer [ 22 ] & 7 ;
}
if ( buffer [ 9 ] & 0x80 ) {
cge . cge_flags | = CGE_PVOLTAG ;
memcpy ( cge . cge_pvoltag , buffer + 28 , 36 ) ;
}
if ( buffer [ 9 ] & 0x40 ) {
cge . cge_flags | = CGE_AVOLTAG ;
memcpy ( cge . cge_avoltag , buffer + 64 , 36 ) ;
}
} else if ( ch - > voltags ) {
ch - > voltags = 0 ;
vprintk ( " device has no volume tag support \n " ) ;
goto voltag_retry ;
}
kfree ( buffer ) ;
2006-01-11 15:16:10 +03:00
mutex_unlock ( & ch - > lock ) ;
2008-01-24 11:24:52 +03:00
2005-09-10 01:03:44 +04:00
if ( copy_to_user ( argp , & cge , sizeof ( cge ) ) )
2005-05-10 12:59:13 +04:00
return - EFAULT ;
return result ;
}
case CHIOINITELEM :
{
2006-01-11 15:16:10 +03:00
mutex_lock ( & ch - > lock ) ;
2005-05-10 12:59:13 +04:00
retval = ch_init_elem ( ch ) ;
2006-01-11 15:16:10 +03:00
mutex_unlock ( & ch - > lock ) ;
2005-05-10 12:59:13 +04:00
return retval ;
}
2008-01-24 11:24:52 +03:00
2005-05-10 12:59:13 +04:00
case CHIOSVOLTAG :
{
struct changer_set_voltag csv ;
int elem ;
2005-09-10 01:03:44 +04:00
if ( copy_from_user ( & csv , argp , sizeof ( csv ) ) )
2005-05-10 12:59:13 +04:00
return - EFAULT ;
if ( 0 ! = ch_checkrange ( ch , csv . csv_type , csv . csv_unit ) ) {
dprintk ( " CHIOSVOLTAG: invalid parameter \n " ) ;
return - EBADSLT ;
}
elem = ch - > firsts [ csv . csv_type ] + csv . csv_unit ;
2006-01-11 15:16:10 +03:00
mutex_lock ( & ch - > lock ) ;
2005-05-10 12:59:13 +04:00
retval = ch_set_voltag ( ch , elem ,
csv . csv_flags & CSV_AVOLTAG ,
csv . csv_flags & CSV_CLEARTAG ,
csv . csv_voltag ) ;
2006-01-11 15:16:10 +03:00
mutex_unlock ( & ch - > lock ) ;
2005-05-10 12:59:13 +04:00
return retval ;
}
default :
2005-09-10 01:03:44 +04:00
return scsi_ioctl ( ch - > device , cmd , argp ) ;
2005-05-10 12:59:13 +04:00
}
}
# ifdef CONFIG_COMPAT
struct changer_element_status32 {
int ces_type ;
compat_uptr_t ces_data ;
} ;
# define CHIOGSTATUS32 _IOW('c', 8,struct changer_element_status32)
static long ch_ioctl_compat ( struct file * file ,
unsigned int cmd , unsigned long arg )
{
scsi_changer * ch = file - > private_data ;
2008-01-24 11:24:52 +03:00
2005-05-10 12:59:13 +04:00
switch ( cmd ) {
case CHIOGPARAMS :
case CHIOGVPARAMS :
case CHIOPOSITION :
case CHIOMOVE :
case CHIOEXCHANGE :
case CHIOGELEM :
case CHIOINITELEM :
case CHIOSVOLTAG :
/* compatible */
2008-01-14 17:43:18 +03:00
return ch_ioctl ( file , cmd , arg ) ;
2005-05-10 12:59:13 +04:00
case CHIOGSTATUS32 :
{
struct changer_element_status32 ces32 ;
2005-09-10 01:03:44 +04:00
unsigned char __user * data ;
2008-01-24 11:24:52 +03:00
2005-09-10 01:03:44 +04:00
if ( copy_from_user ( & ces32 , ( void __user * ) arg , sizeof ( ces32 ) ) )
2005-05-10 12:59:13 +04:00
return - EFAULT ;
if ( ces32 . ces_type < 0 | | ces32 . ces_type > = CH_TYPES )
return - EINVAL ;
data = compat_ptr ( ces32 . ces_data ) ;
return ch_gstatus ( ch , ces32 . ces_type , data ) ;
}
default :
// return scsi_ioctl_compat(ch->device, cmd, (void*)arg);
return - ENOIOCTLCMD ;
}
}
# endif
/* ------------------------------------------------------------------------ */
static int ch_probe ( struct device * dev )
{
struct scsi_device * sd = to_scsi_device ( dev ) ;
2008-02-22 02:13:36 +03:00
struct device * class_dev ;
2008-01-24 11:24:50 +03:00
int minor , ret = - ENOMEM ;
2005-05-10 12:59:13 +04:00
scsi_changer * ch ;
2008-01-23 17:34:35 +03:00
2005-05-10 12:59:13 +04:00
if ( sd - > type ! = TYPE_MEDIUM_CHANGER )
return - ENODEV ;
2008-01-23 17:34:35 +03:00
2007-04-16 10:05:33 +04:00
ch = kzalloc ( sizeof ( * ch ) , GFP_KERNEL ) ;
2005-05-10 12:59:13 +04:00
if ( NULL = = ch )
return - ENOMEM ;
2008-01-24 11:24:50 +03:00
if ( ! idr_pre_get ( & ch_index_idr , GFP_KERNEL ) )
goto free_ch ;
spin_lock ( & ch_index_lock ) ;
ret = idr_get_new ( & ch_index_idr , ch , & minor ) ;
spin_unlock ( & ch_index_lock ) ;
if ( ret )
goto free_ch ;
if ( minor > CH_MAX_DEVS ) {
ret = - ENODEV ;
goto remove_idr ;
}
ch - > minor = minor ;
2005-05-10 12:59:13 +04:00
sprintf ( ch - > name , " ch%d " , ch - > minor ) ;
2008-01-23 17:34:35 +03:00
2008-07-22 07:03:34 +04:00
class_dev = device_create ( ch_sysfs_class , dev ,
MKDEV ( SCSI_CHANGER_MAJOR , ch - > minor ) , ch ,
" s%s " , ch - > name ) ;
2008-01-23 17:34:35 +03:00
if ( IS_ERR ( class_dev ) ) {
2008-02-22 02:13:36 +03:00
printk ( KERN_WARNING " ch%d: device_create failed \n " ,
2008-01-23 17:34:35 +03:00
ch - > minor ) ;
2008-01-24 11:24:50 +03:00
ret = PTR_ERR ( class_dev ) ;
goto remove_idr ;
2008-01-23 17:34:35 +03:00
}
2006-01-11 15:16:10 +03:00
mutex_init ( & ch - > lock ) ;
2005-05-10 12:59:13 +04:00
ch - > device = sd ;
ch_readconfig ( ch ) ;
if ( init )
ch_init_elem ( ch ) ;
2008-07-26 18:25:43 +04:00
dev_set_drvdata ( dev , ch ) ;
2005-10-25 02:04:36 +04:00
sdev_printk ( KERN_INFO , sd , " Attached scsi changer %s \n " , ch - > name ) ;
2008-01-23 17:34:35 +03:00
2005-05-10 12:59:13 +04:00
return 0 ;
2008-01-24 11:24:50 +03:00
remove_idr :
idr_remove ( & ch_index_idr , minor ) ;
free_ch :
kfree ( ch ) ;
return ret ;
2005-05-10 12:59:13 +04:00
}
static int ch_remove ( struct device * dev )
{
2008-01-24 11:24:50 +03:00
scsi_changer * ch = dev_get_drvdata ( dev ) ;
2005-05-10 12:59:13 +04:00
2008-01-24 11:24:50 +03:00
spin_lock ( & ch_index_lock ) ;
idr_remove ( & ch_index_idr , ch - > minor ) ;
spin_unlock ( & ch_index_lock ) ;
2005-05-10 12:59:13 +04:00
2008-02-22 02:13:36 +03:00
device_destroy ( ch_sysfs_class , MKDEV ( SCSI_CHANGER_MAJOR , ch - > minor ) ) ;
2005-05-10 12:59:13 +04:00
kfree ( ch - > dt ) ;
kfree ( ch ) ;
return 0 ;
}
2008-01-24 11:24:52 +03:00
static struct scsi_driver ch_template = {
. owner = THIS_MODULE ,
. gendrv = {
. name = " ch " ,
. probe = ch_probe ,
. remove = ch_remove ,
} ,
} ;
static const struct file_operations changer_fops = {
. owner = THIS_MODULE ,
. open = ch_open ,
. release = ch_release ,
. unlocked_ioctl = ch_ioctl ,
# ifdef CONFIG_COMPAT
. compat_ioctl = ch_ioctl_compat ,
# endif
} ;
2005-05-10 12:59:13 +04:00
static int __init init_ch_module ( void )
{
int rc ;
2008-01-24 11:24:52 +03:00
2005-05-10 12:59:13 +04:00
printk ( KERN_INFO " SCSI Media Changer driver v " VERSION " \n " ) ;
2005-05-12 12:25:26 +04:00
ch_sysfs_class = class_create ( THIS_MODULE , " scsi_changer " ) ;
2005-05-10 12:59:13 +04:00
if ( IS_ERR ( ch_sysfs_class ) ) {
rc = PTR_ERR ( ch_sysfs_class ) ;
return rc ;
}
rc = register_chrdev ( SCSI_CHANGER_MAJOR , " ch " , & changer_fops ) ;
if ( rc < 0 ) {
printk ( " Unable to get major %d for SCSI-Changer \n " ,
SCSI_CHANGER_MAJOR ) ;
goto fail1 ;
}
rc = scsi_register_driver ( & ch_template . gendrv ) ;
if ( rc < 0 )
goto fail2 ;
return 0 ;
fail2 :
unregister_chrdev ( SCSI_CHANGER_MAJOR , " ch " ) ;
fail1 :
2005-05-12 12:25:26 +04:00
class_destroy ( ch_sysfs_class ) ;
2005-05-10 12:59:13 +04:00
return rc ;
}
2008-01-24 11:24:52 +03:00
static void __exit exit_ch_module ( void )
2005-05-10 12:59:13 +04:00
{
scsi_unregister_driver ( & ch_template . gendrv ) ;
unregister_chrdev ( SCSI_CHANGER_MAJOR , " ch " ) ;
2005-05-12 12:25:26 +04:00
class_destroy ( ch_sysfs_class ) ;
2008-01-24 11:24:50 +03:00
idr_destroy ( & ch_index_idr ) ;
2005-05-10 12:59:13 +04:00
}
module_init ( init_ch_module ) ;
module_exit ( exit_ch_module ) ;
/*
* Local variables :
* c - basic - offset : 8
* End :
*/