2005-04-17 02:20:36 +04:00
/*
* dcssblk . c - - the S / 390 block driver for dcss memory
*
* Authors : Carsten Otte , Stefan Weinhuber , Gerald Schaefer
*/
# include <linux/module.h>
# include <linux/moduleparam.h>
# include <linux/ctype.h>
# include <linux/errno.h>
# include <linux/init.h>
# include <linux/slab.h>
# include <linux/blkdev.h>
# include <asm/extmem.h>
# include <asm/io.h>
# include <linux/completion.h>
# include <linux/interrupt.h>
2006-01-06 11:19:14 +03:00
# include <asm/s390_rdev.h>
2005-04-17 02:20:36 +04:00
//#define DCSSBLK_DEBUG /* Debug messages on/off */
# define DCSSBLK_NAME "dcssblk"
# define DCSSBLK_MINORS_PER_DISK 1
# define DCSSBLK_PARM_LEN 400
# ifdef DCSSBLK_DEBUG
# define PRINT_DEBUG(x...) printk(KERN_DEBUG DCSSBLK_NAME " debug: " x)
# else
# define PRINT_DEBUG(x...) do {} while (0)
# endif
# define PRINT_INFO(x...) printk(KERN_INFO DCSSBLK_NAME " info: " x)
# define PRINT_WARN(x...) printk(KERN_WARNING DCSSBLK_NAME " warning: " x)
# define PRINT_ERR(x...) printk(KERN_ERR DCSSBLK_NAME " error: " x)
static int dcssblk_open ( struct inode * inode , struct file * filp ) ;
static int dcssblk_release ( struct inode * inode , struct file * filp ) ;
static int dcssblk_make_request ( struct request_queue * q , struct bio * bio ) ;
2005-06-24 09:05:23 +04:00
static int dcssblk_direct_access ( struct block_device * bdev , sector_t secnum ,
unsigned long * data ) ;
2005-04-17 02:20:36 +04:00
static char dcssblk_segments [ DCSSBLK_PARM_LEN ] = " \0 " ;
static int dcssblk_major ;
static struct block_device_operations dcssblk_devops = {
2005-06-24 09:05:23 +04:00
. owner = THIS_MODULE ,
. open = dcssblk_open ,
. release = dcssblk_release ,
. direct_access = dcssblk_direct_access ,
2005-04-17 02:20:36 +04:00
} ;
2005-05-17 14:42:58 +04:00
static ssize_t dcssblk_add_store ( struct device * dev , struct device_attribute * attr , const char * buf ,
2005-04-17 02:20:36 +04:00
size_t count ) ;
2005-05-17 14:42:58 +04:00
static ssize_t dcssblk_remove_store ( struct device * dev , struct device_attribute * attr , const char * buf ,
2005-04-17 02:20:36 +04:00
size_t count ) ;
2005-05-17 14:42:58 +04:00
static ssize_t dcssblk_save_store ( struct device * dev , struct device_attribute * attr , const char * buf ,
2005-04-17 02:20:36 +04:00
size_t count ) ;
2005-05-17 14:42:58 +04:00
static ssize_t dcssblk_save_show ( struct device * dev , struct device_attribute * attr , char * buf ) ;
static ssize_t dcssblk_shared_store ( struct device * dev , struct device_attribute * attr , const char * buf ,
2005-04-17 02:20:36 +04:00
size_t count ) ;
2005-05-17 14:42:58 +04:00
static ssize_t dcssblk_shared_show ( struct device * dev , struct device_attribute * attr , char * buf ) ;
2005-04-17 02:20:36 +04:00
static DEVICE_ATTR ( add , S_IWUSR , NULL , dcssblk_add_store ) ;
static DEVICE_ATTR ( remove , S_IWUSR , NULL , dcssblk_remove_store ) ;
static DEVICE_ATTR ( save , S_IWUSR | S_IRUGO , dcssblk_save_show ,
dcssblk_save_store ) ;
static DEVICE_ATTR ( shared , S_IWUSR | S_IRUGO , dcssblk_shared_show ,
dcssblk_shared_store ) ;
static struct device * dcssblk_root_dev ;
struct dcssblk_dev_info {
struct list_head lh ;
struct device dev ;
char segment_name [ BUS_ID_SIZE ] ;
atomic_t use_count ;
struct gendisk * gd ;
unsigned long start ;
unsigned long end ;
int segment_type ;
unsigned char save_pending ;
unsigned char is_shared ;
struct request_queue * dcssblk_queue ;
} ;
static struct list_head dcssblk_devices = LIST_HEAD_INIT ( dcssblk_devices ) ;
static struct rw_semaphore dcssblk_devices_sem ;
/*
* release function for segment device .
*/
static void
dcssblk_release_segment ( struct device * dev )
{
PRINT_DEBUG ( " segment release fn called for %s \n " , dev - > bus_id ) ;
kfree ( container_of ( dev , struct dcssblk_dev_info , dev ) ) ;
module_put ( THIS_MODULE ) ;
}
/*
* get a minor number . needs to be called with
* down_write ( & dcssblk_devices_sem ) and the
* device needs to be enqueued before the semaphore is
* freed .
*/
static inline int
dcssblk_assign_free_minor ( struct dcssblk_dev_info * dev_info )
{
int minor , found ;
struct dcssblk_dev_info * entry ;
if ( dev_info = = NULL )
return - EINVAL ;
for ( minor = 0 ; minor < ( 1 < < MINORBITS ) ; minor + + ) {
found = 0 ;
// test if minor available
list_for_each_entry ( entry , & dcssblk_devices , lh )
if ( minor = = entry - > gd - > first_minor )
found + + ;
if ( ! found ) break ; // got unused minor
}
if ( found )
return - EBUSY ;
dev_info - > gd - > first_minor = minor ;
return 0 ;
}
/*
* get the struct dcssblk_dev_info from dcssblk_devices
* for the given name .
* down_read ( & dcssblk_devices_sem ) must be held .
*/
static struct dcssblk_dev_info *
dcssblk_get_device_by_name ( char * name )
{
struct dcssblk_dev_info * entry ;
list_for_each_entry ( entry , & dcssblk_devices , lh ) {
if ( ! strcmp ( name , entry - > segment_name ) ) {
return entry ;
}
}
return NULL ;
}
/*
* print appropriate error message for segment_load ( ) / segment_type ( )
* return code
*/
static void
dcssblk_segment_warn ( int rc , char * seg_name )
{
switch ( rc ) {
case - ENOENT :
PRINT_WARN ( " cannot load/query segment %s, does not exist \n " ,
seg_name ) ;
break ;
case - ENOSYS :
PRINT_WARN ( " cannot load/query segment %s, not running on VM \n " ,
seg_name ) ;
break ;
case - EIO :
PRINT_WARN ( " cannot load/query segment %s, hardware error \n " ,
seg_name ) ;
break ;
case - ENOTSUPP :
PRINT_WARN ( " cannot load/query segment %s, is a multi-part "
" segment \n " , seg_name ) ;
break ;
case - ENOSPC :
PRINT_WARN ( " cannot load/query segment %s, overlaps with "
" storage \n " , seg_name ) ;
break ;
case - EBUSY :
PRINT_WARN ( " cannot load/query segment %s, overlaps with "
" already loaded dcss \n " , seg_name ) ;
break ;
case - EPERM :
PRINT_WARN ( " cannot load/query segment %s, already loaded in "
" incompatible mode \n " , seg_name ) ;
break ;
case - ENOMEM :
PRINT_WARN ( " cannot load/query segment %s, out of memory \n " ,
seg_name ) ;
break ;
case - ERANGE :
PRINT_WARN ( " cannot load/query segment %s, exceeds kernel "
" mapping range \n " , seg_name ) ;
break ;
default :
PRINT_WARN ( " cannot load/query segment %s, return value %i \n " ,
seg_name , rc ) ;
break ;
}
}
/*
* device attribute for switching shared / nonshared ( exclusive )
* operation ( show + store )
*/
static ssize_t
2005-05-17 14:42:58 +04:00
dcssblk_shared_show ( struct device * dev , struct device_attribute * attr , char * buf )
2005-04-17 02:20:36 +04:00
{
struct dcssblk_dev_info * dev_info ;
dev_info = container_of ( dev , struct dcssblk_dev_info , dev ) ;
return sprintf ( buf , dev_info - > is_shared ? " 1 \n " : " 0 \n " ) ;
}
static ssize_t
2005-05-17 14:42:58 +04:00
dcssblk_shared_store ( struct device * dev , struct device_attribute * attr , const char * inbuf , size_t count )
2005-04-17 02:20:36 +04:00
{
struct dcssblk_dev_info * dev_info ;
int rc ;
if ( ( count > 1 ) & & ( inbuf [ 1 ] ! = ' \n ' ) & & ( inbuf [ 1 ] ! = ' \0 ' ) ) {
PRINT_WARN ( " Invalid value, must be 0 or 1 \n " ) ;
return - EINVAL ;
}
down_write ( & dcssblk_devices_sem ) ;
dev_info = container_of ( dev , struct dcssblk_dev_info , dev ) ;
if ( atomic_read ( & dev_info - > use_count ) ) {
PRINT_ERR ( " share: segment %s is busy! \n " ,
dev_info - > segment_name ) ;
rc = - EBUSY ;
goto out ;
}
if ( inbuf [ 0 ] = = ' 1 ' ) {
// reload segment in shared mode
rc = segment_modify_shared ( dev_info - > segment_name ,
SEGMENT_SHARED ) ;
if ( rc < 0 ) {
BUG_ON ( rc = = - EINVAL ) ;
2007-02-05 23:17:11 +03:00
if ( rc ! = - EAGAIN )
2005-04-17 02:20:36 +04:00
goto removeseg ;
} else {
dev_info - > is_shared = 1 ;
switch ( dev_info - > segment_type ) {
case SEG_TYPE_SR :
case SEG_TYPE_ER :
case SEG_TYPE_SC :
set_disk_ro ( dev_info - > gd , 1 ) ;
}
}
} else if ( inbuf [ 0 ] = = ' 0 ' ) {
// reload segment in exclusive mode
if ( dev_info - > segment_type = = SEG_TYPE_SC ) {
PRINT_ERR ( " Segment type SC (%s) cannot be loaded in "
" non-shared mode \n " , dev_info - > segment_name ) ;
rc = - EINVAL ;
goto out ;
}
rc = segment_modify_shared ( dev_info - > segment_name ,
SEGMENT_EXCLUSIVE ) ;
if ( rc < 0 ) {
BUG_ON ( rc = = - EINVAL ) ;
2007-02-05 23:17:11 +03:00
if ( rc ! = - EAGAIN )
2005-04-17 02:20:36 +04:00
goto removeseg ;
} else {
dev_info - > is_shared = 0 ;
set_disk_ro ( dev_info - > gd , 0 ) ;
}
} else {
PRINT_WARN ( " Invalid value, must be 0 or 1 \n " ) ;
rc = - EINVAL ;
goto out ;
}
rc = count ;
goto out ;
removeseg :
PRINT_ERR ( " Could not reload segment %s, removing it now! \n " ,
dev_info - > segment_name ) ;
list_del ( & dev_info - > lh ) ;
del_gendisk ( dev_info - > gd ) ;
2006-03-12 19:02:03 +03:00
blk_cleanup_queue ( dev_info - > dcssblk_queue ) ;
2005-04-17 02:20:36 +04:00
dev_info - > gd - > queue = NULL ;
put_disk ( dev_info - > gd ) ;
device_unregister ( dev ) ;
put_device ( dev ) ;
out :
up_write ( & dcssblk_devices_sem ) ;
return rc ;
}
/*
* device attribute for save operation on current copy
* of the segment . If the segment is busy , saving will
* become pending until it gets released , which can be
* undone by storing a non - true value to this entry .
* ( show + store )
*/
static ssize_t
2005-05-17 14:42:58 +04:00
dcssblk_save_show ( struct device * dev , struct device_attribute * attr , char * buf )
2005-04-17 02:20:36 +04:00
{
struct dcssblk_dev_info * dev_info ;
dev_info = container_of ( dev , struct dcssblk_dev_info , dev ) ;
return sprintf ( buf , dev_info - > save_pending ? " 1 \n " : " 0 \n " ) ;
}
static ssize_t
2005-05-17 14:42:58 +04:00
dcssblk_save_store ( struct device * dev , struct device_attribute * attr , const char * inbuf , size_t count )
2005-04-17 02:20:36 +04:00
{
struct dcssblk_dev_info * dev_info ;
if ( ( count > 1 ) & & ( inbuf [ 1 ] ! = ' \n ' ) & & ( inbuf [ 1 ] ! = ' \0 ' ) ) {
PRINT_WARN ( " Invalid value, must be 0 or 1 \n " ) ;
return - EINVAL ;
}
dev_info = container_of ( dev , struct dcssblk_dev_info , dev ) ;
down_write ( & dcssblk_devices_sem ) ;
if ( inbuf [ 0 ] = = ' 1 ' ) {
if ( atomic_read ( & dev_info - > use_count ) = = 0 ) {
// device is idle => we save immediately
PRINT_INFO ( " Saving segment %s \n " ,
dev_info - > segment_name ) ;
segment_save ( dev_info - > segment_name ) ;
} else {
// device is busy => we save it when it becomes
// idle in dcssblk_release
PRINT_INFO ( " Segment %s is currently busy, it will "
" be saved when it becomes idle... \n " ,
dev_info - > segment_name ) ;
dev_info - > save_pending = 1 ;
}
} else if ( inbuf [ 0 ] = = ' 0 ' ) {
if ( dev_info - > save_pending ) {
// device is busy & the user wants to undo his save
// request
dev_info - > save_pending = 0 ;
PRINT_INFO ( " Pending save for segment %s deactivated \n " ,
dev_info - > segment_name ) ;
}
} else {
up_write ( & dcssblk_devices_sem ) ;
PRINT_WARN ( " Invalid value, must be 0 or 1 \n " ) ;
return - EINVAL ;
}
up_write ( & dcssblk_devices_sem ) ;
return count ;
}
/*
* device attribute for adding devices
*/
static ssize_t
2005-05-17 14:42:58 +04:00
dcssblk_add_store ( struct device * dev , struct device_attribute * attr , const char * buf , size_t count )
2005-04-17 02:20:36 +04:00
{
int rc , i ;
struct dcssblk_dev_info * dev_info ;
char * local_buf ;
unsigned long seg_byte_size ;
dev_info = NULL ;
if ( dev ! = dcssblk_root_dev ) {
rc = - EINVAL ;
goto out_nobuf ;
}
local_buf = kmalloc ( count + 1 , GFP_KERNEL ) ;
if ( local_buf = = NULL ) {
rc = - ENOMEM ;
goto out_nobuf ;
}
/*
* parse input
*/
for ( i = 0 ; ( ( buf [ i ] ! = ' \0 ' ) & & ( buf [ i ] ! = ' \n ' ) & & i < count ) ; i + + ) {
local_buf [ i ] = toupper ( buf [ i ] ) ;
}
local_buf [ i ] = ' \0 ' ;
if ( ( i = = 0 ) | | ( i > 8 ) ) {
rc = - ENAMETOOLONG ;
goto out ;
}
/*
* already loaded ?
*/
down_read ( & dcssblk_devices_sem ) ;
dev_info = dcssblk_get_device_by_name ( local_buf ) ;
up_read ( & dcssblk_devices_sem ) ;
if ( dev_info ! = NULL ) {
PRINT_WARN ( " Segment %s already loaded! \n " , local_buf ) ;
rc = - EEXIST ;
goto out ;
}
/*
* get a struct dcssblk_dev_info
*/
2006-03-24 14:15:31 +03:00
dev_info = kzalloc ( sizeof ( struct dcssblk_dev_info ) , GFP_KERNEL ) ;
2005-04-17 02:20:36 +04:00
if ( dev_info = = NULL ) {
rc = - ENOMEM ;
goto out ;
}
strcpy ( dev_info - > segment_name , local_buf ) ;
strlcpy ( dev_info - > dev . bus_id , local_buf , BUS_ID_SIZE ) ;
dev_info - > dev . release = dcssblk_release_segment ;
INIT_LIST_HEAD ( & dev_info - > lh ) ;
dev_info - > gd = alloc_disk ( DCSSBLK_MINORS_PER_DISK ) ;
if ( dev_info - > gd = = NULL ) {
rc = - ENOMEM ;
goto free_dev_info ;
}
dev_info - > gd - > major = dcssblk_major ;
dev_info - > gd - > fops = & dcssblk_devops ;
dev_info - > dcssblk_queue = blk_alloc_queue ( GFP_KERNEL ) ;
dev_info - > gd - > queue = dev_info - > dcssblk_queue ;
dev_info - > gd - > private_data = dev_info ;
dev_info - > gd - > driverfs_dev = & dev_info - > dev ;
/*
* load the segment
*/
rc = segment_load ( local_buf , SEGMENT_SHARED ,
& dev_info - > start , & dev_info - > end ) ;
if ( rc < 0 ) {
dcssblk_segment_warn ( rc , dev_info - > segment_name ) ;
goto dealloc_gendisk ;
}
seg_byte_size = ( dev_info - > end - dev_info - > start + 1 ) ;
set_capacity ( dev_info - > gd , seg_byte_size > > 9 ) ; // size in sectors
PRINT_INFO ( " Loaded segment %s, size = %lu Byte, "
" capacity = %lu (512 Byte) sectors \n " , local_buf ,
seg_byte_size , seg_byte_size > > 9 ) ;
dev_info - > segment_type = rc ;
dev_info - > save_pending = 0 ;
dev_info - > is_shared = 1 ;
dev_info - > dev . parent = dcssblk_root_dev ;
/*
* get minor , add to list
*/
down_write ( & dcssblk_devices_sem ) ;
rc = dcssblk_assign_free_minor ( dev_info ) ;
if ( rc ) {
up_write ( & dcssblk_devices_sem ) ;
PRINT_ERR ( " No free minor number available! "
" Unloading segment... \n " ) ;
goto unload_seg ;
}
sprintf ( dev_info - > gd - > disk_name , " dcssblk%d " ,
dev_info - > gd - > first_minor ) ;
list_add_tail ( & dev_info - > lh , & dcssblk_devices ) ;
if ( ! try_module_get ( THIS_MODULE ) ) {
rc = - ENODEV ;
goto list_del ;
}
/*
* register the device
*/
rc = device_register ( & dev_info - > dev ) ;
if ( rc ) {
PRINT_ERR ( " Segment %s could not be registered RC=%d \n " ,
local_buf , rc ) ;
module_put ( THIS_MODULE ) ;
goto list_del ;
}
get_device ( & dev_info - > dev ) ;
rc = device_create_file ( & dev_info - > dev , & dev_attr_shared ) ;
if ( rc )
goto unregister_dev ;
rc = device_create_file ( & dev_info - > dev , & dev_attr_save ) ;
if ( rc )
goto unregister_dev ;
add_disk ( dev_info - > gd ) ;
blk_queue_make_request ( dev_info - > dcssblk_queue , dcssblk_make_request ) ;
blk_queue_hardsect_size ( dev_info - > dcssblk_queue , 4096 ) ;
switch ( dev_info - > segment_type ) {
case SEG_TYPE_SR :
case SEG_TYPE_ER :
case SEG_TYPE_SC :
set_disk_ro ( dev_info - > gd , 1 ) ;
break ;
default :
set_disk_ro ( dev_info - > gd , 0 ) ;
break ;
}
PRINT_DEBUG ( " Segment %s loaded successfully \n " , local_buf ) ;
up_write ( & dcssblk_devices_sem ) ;
rc = count ;
goto out ;
unregister_dev :
PRINT_ERR ( " device_create_file() failed! \n " ) ;
list_del ( & dev_info - > lh ) ;
2006-03-12 19:02:03 +03:00
blk_cleanup_queue ( dev_info - > dcssblk_queue ) ;
2005-04-17 02:20:36 +04:00
dev_info - > gd - > queue = NULL ;
put_disk ( dev_info - > gd ) ;
device_unregister ( & dev_info - > dev ) ;
segment_unload ( dev_info - > segment_name ) ;
put_device ( & dev_info - > dev ) ;
up_write ( & dcssblk_devices_sem ) ;
goto out ;
list_del :
list_del ( & dev_info - > lh ) ;
up_write ( & dcssblk_devices_sem ) ;
unload_seg :
segment_unload ( local_buf ) ;
dealloc_gendisk :
2006-03-12 19:02:03 +03:00
blk_cleanup_queue ( dev_info - > dcssblk_queue ) ;
2005-04-17 02:20:36 +04:00
dev_info - > gd - > queue = NULL ;
put_disk ( dev_info - > gd ) ;
free_dev_info :
kfree ( dev_info ) ;
out :
kfree ( local_buf ) ;
out_nobuf :
return rc ;
}
/*
* device attribute for removing devices
*/
static ssize_t
2005-05-17 14:42:58 +04:00
dcssblk_remove_store ( struct device * dev , struct device_attribute * attr , const char * buf , size_t count )
2005-04-17 02:20:36 +04:00
{
struct dcssblk_dev_info * dev_info ;
int rc , i ;
char * local_buf ;
if ( dev ! = dcssblk_root_dev ) {
return - EINVAL ;
}
local_buf = kmalloc ( count + 1 , GFP_KERNEL ) ;
if ( local_buf = = NULL ) {
return - ENOMEM ;
}
/*
* parse input
*/
for ( i = 0 ; ( ( * ( buf + i ) ! = ' \0 ' ) & & ( * ( buf + i ) ! = ' \n ' ) & & i < count ) ; i + + ) {
local_buf [ i ] = toupper ( buf [ i ] ) ;
}
local_buf [ i ] = ' \0 ' ;
if ( ( i = = 0 ) | | ( i > 8 ) ) {
rc = - ENAMETOOLONG ;
goto out_buf ;
}
down_write ( & dcssblk_devices_sem ) ;
dev_info = dcssblk_get_device_by_name ( local_buf ) ;
if ( dev_info = = NULL ) {
up_write ( & dcssblk_devices_sem ) ;
PRINT_WARN ( " Segment %s is not loaded! \n " , local_buf ) ;
rc = - ENODEV ;
goto out_buf ;
}
if ( atomic_read ( & dev_info - > use_count ) ! = 0 ) {
up_write ( & dcssblk_devices_sem ) ;
PRINT_WARN ( " Segment %s is in use! \n " , local_buf ) ;
rc = - EBUSY ;
goto out_buf ;
}
list_del ( & dev_info - > lh ) ;
del_gendisk ( dev_info - > gd ) ;
2006-03-12 19:02:03 +03:00
blk_cleanup_queue ( dev_info - > dcssblk_queue ) ;
2005-04-17 02:20:36 +04:00
dev_info - > gd - > queue = NULL ;
put_disk ( dev_info - > gd ) ;
device_unregister ( & dev_info - > dev ) ;
segment_unload ( dev_info - > segment_name ) ;
PRINT_DEBUG ( " Segment %s unloaded successfully \n " ,
dev_info - > segment_name ) ;
put_device ( & dev_info - > dev ) ;
up_write ( & dcssblk_devices_sem ) ;
rc = count ;
out_buf :
kfree ( local_buf ) ;
return rc ;
}
static int
dcssblk_open ( struct inode * inode , struct file * filp )
{
struct dcssblk_dev_info * dev_info ;
int rc ;
dev_info = inode - > i_bdev - > bd_disk - > private_data ;
if ( NULL = = dev_info ) {
rc = - ENODEV ;
goto out ;
}
atomic_inc ( & dev_info - > use_count ) ;
inode - > i_bdev - > bd_block_size = 4096 ;
rc = 0 ;
out :
return rc ;
}
static int
dcssblk_release ( struct inode * inode , struct file * filp )
{
struct dcssblk_dev_info * dev_info ;
int rc ;
dev_info = inode - > i_bdev - > bd_disk - > private_data ;
if ( NULL = = dev_info ) {
rc = - ENODEV ;
goto out ;
}
down_write ( & dcssblk_devices_sem ) ;
if ( atomic_dec_and_test ( & dev_info - > use_count )
& & ( dev_info - > save_pending ) ) {
PRINT_INFO ( " Segment %s became idle and is being saved now \n " ,
dev_info - > segment_name ) ;
segment_save ( dev_info - > segment_name ) ;
dev_info - > save_pending = 0 ;
}
up_write ( & dcssblk_devices_sem ) ;
rc = 0 ;
out :
return rc ;
}
static int
dcssblk_make_request ( request_queue_t * q , struct bio * bio )
{
struct dcssblk_dev_info * dev_info ;
struct bio_vec * bvec ;
unsigned long index ;
unsigned long page_addr ;
unsigned long source_addr ;
unsigned long bytes_done ;
int i ;
bytes_done = 0 ;
dev_info = bio - > bi_bdev - > bd_disk - > private_data ;
if ( dev_info = = NULL )
goto fail ;
if ( ( bio - > bi_sector & 7 ) ! = 0 | | ( bio - > bi_size & 4095 ) ! = 0 )
/* Request is not page-aligned. */
goto fail ;
if ( ( ( bio - > bi_size > > 9 ) + bio - > bi_sector )
> get_capacity ( bio - > bi_bdev - > bd_disk ) ) {
/* Request beyond end of DCSS segment. */
goto fail ;
}
2005-06-24 09:05:23 +04:00
/* verify data transfer direction */
if ( dev_info - > is_shared ) {
switch ( dev_info - > segment_type ) {
case SEG_TYPE_SR :
case SEG_TYPE_ER :
case SEG_TYPE_SC :
/* cannot write to these segments */
if ( bio_data_dir ( bio ) = = WRITE ) {
PRINT_WARN ( " rejecting write to ro segment %s \n " , dev_info - > dev . bus_id ) ;
goto fail ;
}
}
}
2005-04-17 02:20:36 +04:00
index = ( bio - > bi_sector > > 3 ) ;
bio_for_each_segment ( bvec , bio , i ) {
page_addr = ( unsigned long )
page_address ( bvec - > bv_page ) + bvec - > bv_offset ;
source_addr = dev_info - > start + ( index < < 12 ) + bytes_done ;
if ( unlikely ( page_addr & 4095 ) ! = 0 | | ( bvec - > bv_len & 4095 ) ! = 0 )
// More paranoia.
goto fail ;
if ( bio_data_dir ( bio ) = = READ ) {
memcpy ( ( void * ) page_addr , ( void * ) source_addr ,
bvec - > bv_len ) ;
} else {
memcpy ( ( void * ) source_addr , ( void * ) page_addr ,
bvec - > bv_len ) ;
}
bytes_done + = bvec - > bv_len ;
}
bio_endio ( bio , bytes_done , 0 ) ;
return 0 ;
fail :
2005-06-24 09:05:23 +04:00
bio_io_error ( bio , bio - > bi_size ) ;
return 0 ;
}
static int
dcssblk_direct_access ( struct block_device * bdev , sector_t secnum ,
unsigned long * data )
{
struct dcssblk_dev_info * dev_info ;
unsigned long pgoff ;
dev_info = bdev - > bd_disk - > private_data ;
if ( ! dev_info )
return - ENODEV ;
if ( secnum % ( PAGE_SIZE / 512 ) )
return - EINVAL ;
pgoff = secnum / ( PAGE_SIZE / 512 ) ;
if ( ( pgoff + 1 ) * PAGE_SIZE - 1 > dev_info - > end - dev_info - > start )
return - ERANGE ;
* data = ( unsigned long ) ( dev_info - > start + pgoff * PAGE_SIZE ) ;
2005-04-17 02:20:36 +04:00
return 0 ;
}
static void
dcssblk_check_params ( void )
{
int rc , i , j , k ;
char buf [ 9 ] ;
struct dcssblk_dev_info * dev_info ;
for ( i = 0 ; ( i < DCSSBLK_PARM_LEN ) & & ( dcssblk_segments [ i ] ! = ' \0 ' ) ;
i + + ) {
for ( j = i ; ( dcssblk_segments [ j ] ! = ' , ' ) & &
( dcssblk_segments [ j ] ! = ' \0 ' ) & &
( dcssblk_segments [ j ] ! = ' ( ' ) & &
( j - i ) < 8 ; j + + )
{
buf [ j - i ] = dcssblk_segments [ j ] ;
}
buf [ j - i ] = ' \0 ' ;
2005-06-26 01:55:29 +04:00
rc = dcssblk_add_store ( dcssblk_root_dev , NULL , buf , j - i ) ;
2005-04-17 02:20:36 +04:00
if ( ( rc > = 0 ) & & ( dcssblk_segments [ j ] = = ' ( ' ) ) {
for ( k = 0 ; buf [ k ] ! = ' \0 ' ; k + + )
buf [ k ] = toupper ( buf [ k ] ) ;
if ( ! strncmp ( & dcssblk_segments [ j ] , " (local) " , 7 ) ) {
down_read ( & dcssblk_devices_sem ) ;
dev_info = dcssblk_get_device_by_name ( buf ) ;
up_read ( & dcssblk_devices_sem ) ;
if ( dev_info )
dcssblk_shared_store ( & dev_info - > dev ,
2005-06-26 01:55:29 +04:00
NULL , " 0 \n " , 2 ) ;
2005-04-17 02:20:36 +04:00
}
}
while ( ( dcssblk_segments [ j ] ! = ' , ' ) & &
( dcssblk_segments [ j ] ! = ' \0 ' ) )
{
j + + ;
}
if ( dcssblk_segments [ j ] = = ' \0 ' )
break ;
i = j ;
}
}
/*
* The init / exit functions .
*/
static void __exit
dcssblk_exit ( void )
{
int rc ;
PRINT_DEBUG ( " DCSSBLOCK EXIT... \n " ) ;
s390_root_dev_unregister ( dcssblk_root_dev ) ;
rc = unregister_blkdev ( dcssblk_major , DCSSBLK_NAME ) ;
if ( rc ) {
PRINT_ERR ( " unregister_blkdev() failed! \n " ) ;
}
PRINT_DEBUG ( " ...finished! \n " ) ;
}
static int __init
dcssblk_init ( void )
{
int rc ;
PRINT_DEBUG ( " DCSSBLOCK INIT... \n " ) ;
dcssblk_root_dev = s390_root_dev_register ( " dcssblk " ) ;
if ( IS_ERR ( dcssblk_root_dev ) ) {
PRINT_ERR ( " device_register() failed! \n " ) ;
return PTR_ERR ( dcssblk_root_dev ) ;
}
rc = device_create_file ( dcssblk_root_dev , & dev_attr_add ) ;
if ( rc ) {
PRINT_ERR ( " device_create_file(add) failed! \n " ) ;
s390_root_dev_unregister ( dcssblk_root_dev ) ;
return rc ;
}
rc = device_create_file ( dcssblk_root_dev , & dev_attr_remove ) ;
if ( rc ) {
PRINT_ERR ( " device_create_file(remove) failed! \n " ) ;
s390_root_dev_unregister ( dcssblk_root_dev ) ;
return rc ;
}
rc = register_blkdev ( 0 , DCSSBLK_NAME ) ;
if ( rc < 0 ) {
PRINT_ERR ( " Can't get dynamic major! \n " ) ;
s390_root_dev_unregister ( dcssblk_root_dev ) ;
return rc ;
}
dcssblk_major = rc ;
init_rwsem ( & dcssblk_devices_sem ) ;
dcssblk_check_params ( ) ;
PRINT_DEBUG ( " ...finished! \n " ) ;
return 0 ;
}
module_init ( dcssblk_init ) ;
module_exit ( dcssblk_exit ) ;
module_param_string ( segments , dcssblk_segments , DCSSBLK_PARM_LEN , 0444 ) ;
MODULE_PARM_DESC ( segments , " Name of DCSS segment(s) to be loaded, "
" comma-separated list, each name max. 8 chars. \n "
" Adding \" (local) \" to segment name equals echoing 0 to "
" /sys/devices/dcssblk/<segment name>/shared after loading "
" the segment - \n "
" e.g. segments= \" mydcss1,mydcss2,mydcss3(local) \" " ) ;
MODULE_LICENSE ( " GPL " ) ;