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
*/
2008-12-25 15:39:45 +03:00
# define KMSG_COMPONENT "dcssblk"
# define pr_fmt(fmt) KMSG_COMPONENT ": " fmt
2005-04-17 02:20:36 +04:00
# 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 <linux/completion.h>
# include <linux/interrupt.h>
2009-06-16 12:30:51 +04:00
# include <linux/platform_device.h>
# include <asm/extmem.h>
# include <asm/io.h>
2005-04-17 02:20:36 +04:00
# define DCSSBLK_NAME "dcssblk"
# define DCSSBLK_MINORS_PER_DISK 1
# define DCSSBLK_PARM_LEN 400
2008-12-25 15:38:55 +03:00
# define DCSS_BUS_ID_SIZE 20
2005-04-17 02:20:36 +04:00
2008-03-02 18:37:41 +03:00
static int dcssblk_open ( struct block_device * bdev , fmode_t mode ) ;
2013-05-06 05:52:57 +04:00
static void dcssblk_release ( struct gendisk * disk , fmode_t mode ) ;
2011-09-12 14:12:01 +04:00
static void 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 ,
2008-04-28 13:13:02 +04:00
void * * kaddr , unsigned long * pfn ) ;
2005-04-17 02:20:36 +04:00
static char dcssblk_segments [ DCSSBLK_PARM_LEN ] = " \0 " ;
static int dcssblk_major ;
2009-09-22 04:01:13 +04:00
static const struct block_device_operations dcssblk_devops = {
2005-06-24 09:05:23 +04:00
. owner = THIS_MODULE ,
2008-03-02 18:37:41 +03:00
. open = dcssblk_open ,
. release = dcssblk_release ,
2005-06-24 09:05:23 +04:00
. direct_access = dcssblk_direct_access ,
2005-04-17 02:20:36 +04:00
} ;
2008-10-10 23:33:21 +04:00
struct dcssblk_dev_info {
struct list_head lh ;
struct device dev ;
2008-12-25 15:38:55 +03:00
char segment_name [ DCSS_BUS_ID_SIZE ] ;
2008-10-10 23:33:21 +04:00
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 ;
int num_of_segments ;
struct list_head seg_list ;
} ;
struct segment_info {
struct list_head lh ;
2008-12-25 15:38:55 +03:00
char segment_name [ DCSS_BUS_ID_SIZE ] ;
2008-10-10 23:33:21 +04:00
unsigned long start ;
unsigned long end ;
int segment_type ;
} ;
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 ) ;
static DEVICE_ATTR ( add , S_IWUSR , NULL , dcssblk_add_store ) ;
static DEVICE_ATTR ( remove , S_IWUSR , NULL , dcssblk_remove_store ) ;
static struct device * dcssblk_root_dev ;
2008-01-26 16:11:13 +03:00
static LIST_HEAD ( dcssblk_devices ) ;
2005-04-17 02:20:36 +04:00
static struct rw_semaphore dcssblk_devices_sem ;
/*
* release function for segment device .
*/
static void
dcssblk_release_segment ( struct device * dev )
{
2008-10-10 23:33:21 +04:00
struct dcssblk_dev_info * dev_info ;
struct segment_info * entry , * temp ;
dev_info = container_of ( dev , struct dcssblk_dev_info , dev ) ;
list_for_each_entry_safe ( entry , temp , & dev_info - > seg_list , lh ) {
list_del ( & entry - > lh ) ;
kfree ( entry ) ;
}
kfree ( dev_info ) ;
2005-04-17 02:20:36 +04:00
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 .
*/
2007-02-05 23:18:53 +03:00
static int
2005-04-17 02:20:36 +04:00
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 )
2009-06-12 12:26:35 +04:00
if ( minor = = entry - > gd - > first_minor )
2005-04-17 02:20:36 +04:00
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 ;
}
2008-10-10 23:33:21 +04:00
/*
* get the struct segment_info from seg_list
* for the given name .
* down_read ( & dcssblk_devices_sem ) must be held .
*/
static struct segment_info *
dcssblk_get_segment_by_name ( char * name )
{
struct dcssblk_dev_info * dev_info ;
struct segment_info * entry ;
list_for_each_entry ( dev_info , & dcssblk_devices , lh ) {
list_for_each_entry ( entry , & dev_info - > seg_list , lh ) {
if ( ! strcmp ( name , entry - > segment_name ) )
return entry ;
}
}
return NULL ;
}
/*
* get the highest address of the multi - segment block .
*/
static unsigned long
dcssblk_find_highest_addr ( struct dcssblk_dev_info * dev_info )
{
unsigned long highest_addr ;
struct segment_info * entry ;
highest_addr = 0 ;
list_for_each_entry ( entry , & dev_info - > seg_list , lh ) {
if ( highest_addr < entry - > end )
highest_addr = entry - > end ;
}
return highest_addr ;
}
/*
* get the lowest address of the multi - segment block .
*/
static unsigned long
dcssblk_find_lowest_addr ( struct dcssblk_dev_info * dev_info )
{
int set_first ;
unsigned long lowest_addr ;
struct segment_info * entry ;
set_first = 0 ;
lowest_addr = 0 ;
list_for_each_entry ( entry , & dev_info - > seg_list , lh ) {
if ( set_first = = 0 ) {
lowest_addr = entry - > start ;
set_first = 1 ;
} else {
if ( lowest_addr > entry - > start )
lowest_addr = entry - > start ;
}
}
return lowest_addr ;
}
/*
* Check continuity of segments .
*/
static int
dcssblk_is_continuous ( struct dcssblk_dev_info * dev_info )
{
int i , j , rc ;
struct segment_info * sort_list , * entry , temp ;
if ( dev_info - > num_of_segments < = 1 )
return 0 ;
sort_list = kzalloc (
sizeof ( struct segment_info ) * dev_info - > num_of_segments ,
GFP_KERNEL ) ;
if ( sort_list = = NULL )
return - ENOMEM ;
i = 0 ;
list_for_each_entry ( entry , & dev_info - > seg_list , lh ) {
memcpy ( & sort_list [ i ] , entry , sizeof ( struct segment_info ) ) ;
i + + ;
}
/* sort segments */
for ( i = 0 ; i < dev_info - > num_of_segments ; i + + )
for ( j = 0 ; j < dev_info - > num_of_segments ; j + + )
if ( sort_list [ j ] . start > sort_list [ i ] . start ) {
memcpy ( & temp , & sort_list [ i ] ,
sizeof ( struct segment_info ) ) ;
memcpy ( & sort_list [ i ] , & sort_list [ j ] ,
sizeof ( struct segment_info ) ) ;
memcpy ( & sort_list [ j ] , & temp ,
sizeof ( struct segment_info ) ) ;
}
/* check continuity */
for ( i = 0 ; i < dev_info - > num_of_segments - 1 ; i + + ) {
if ( ( sort_list [ i ] . end + 1 ) ! = sort_list [ i + 1 ] . start ) {
2008-12-25 15:39:45 +03:00
pr_err ( " Adjacent DCSSs %s and %s are not "
" contiguous \n " , sort_list [ i ] . segment_name ,
sort_list [ i + 1 ] . segment_name ) ;
2008-10-10 23:33:21 +04:00
rc = - EINVAL ;
goto out ;
}
/* EN and EW are allowed in a block device */
if ( sort_list [ i ] . segment_type ! = sort_list [ i + 1 ] . segment_type ) {
if ( ! ( sort_list [ i ] . segment_type & SEGMENT_EXCLUSIVE ) | |
( sort_list [ i ] . segment_type = = SEG_TYPE_ER ) | |
! ( sort_list [ i + 1 ] . segment_type &
SEGMENT_EXCLUSIVE ) | |
( sort_list [ i + 1 ] . segment_type = = SEG_TYPE_ER ) ) {
2008-12-25 15:39:45 +03:00
pr_err ( " DCSS %s and DCSS %s have "
" incompatible types \n " ,
sort_list [ i ] . segment_name ,
sort_list [ i + 1 ] . segment_name ) ;
2008-10-10 23:33:21 +04:00
rc = - EINVAL ;
goto out ;
}
}
}
rc = 0 ;
out :
kfree ( sort_list ) ;
return rc ;
}
/*
* Load a segment
*/
static int
dcssblk_load_segment ( char * name , struct segment_info * * seg_info )
{
int rc ;
/* already loaded? */
down_read ( & dcssblk_devices_sem ) ;
* seg_info = dcssblk_get_segment_by_name ( name ) ;
up_read ( & dcssblk_devices_sem ) ;
if ( * seg_info ! = NULL )
return - EEXIST ;
/* get a struct segment_info */
* seg_info = kzalloc ( sizeof ( struct segment_info ) , GFP_KERNEL ) ;
if ( * seg_info = = NULL )
return - ENOMEM ;
strcpy ( ( * seg_info ) - > segment_name , name ) ;
/* load the segment */
rc = segment_load ( name , SEGMENT_SHARED ,
& ( * seg_info ) - > start , & ( * seg_info ) - > end ) ;
if ( rc < 0 ) {
segment_warning ( rc , ( * seg_info ) - > segment_name ) ;
kfree ( * seg_info ) ;
} else {
INIT_LIST_HEAD ( & ( * seg_info ) - > lh ) ;
( * seg_info ) - > segment_type = rc ;
}
return rc ;
}
2005-04-17 02:20:36 +04:00
/*
* 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 ;
2008-10-10 23:33:21 +04:00
struct segment_info * entry , * temp ;
2005-04-17 02:20:36 +04:00
int rc ;
2008-07-14 11:59:39 +04:00
if ( ( count > 1 ) & & ( inbuf [ 1 ] ! = ' \n ' ) & & ( inbuf [ 1 ] ! = ' \0 ' ) )
2005-04-17 02:20:36 +04:00
return - EINVAL ;
down_write ( & dcssblk_devices_sem ) ;
dev_info = container_of ( dev , struct dcssblk_dev_info , dev ) ;
if ( atomic_read ( & dev_info - > use_count ) ) {
rc = - EBUSY ;
goto out ;
}
if ( inbuf [ 0 ] = = ' 1 ' ) {
2008-10-10 23:33:21 +04:00
/* reload segments in shared mode */
list_for_each_entry ( entry , & dev_info - > seg_list , lh ) {
rc = segment_modify_shared ( entry - > segment_name ,
SEGMENT_SHARED ) ;
if ( rc < 0 ) {
BUG_ON ( rc = = - EINVAL ) ;
if ( rc ! = - EAGAIN )
goto removeseg ;
2005-04-17 02:20:36 +04:00
}
}
2008-10-10 23:33:21 +04:00
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 ) ;
}
2005-04-17 02:20:36 +04:00
} else if ( inbuf [ 0 ] = = ' 0 ' ) {
2008-10-10 23:33:21 +04:00
/* reload segments in exclusive mode */
2005-04-17 02:20:36 +04:00
if ( dev_info - > segment_type = = SEG_TYPE_SC ) {
2008-12-25 15:39:45 +03:00
pr_err ( " DCSS %s is of type SC and cannot be "
" loaded as exclusive-writable \n " ,
dev_info - > segment_name ) ;
2005-04-17 02:20:36 +04:00
rc = - EINVAL ;
goto out ;
}
2008-10-10 23:33:21 +04:00
list_for_each_entry ( entry , & dev_info - > seg_list , lh ) {
rc = segment_modify_shared ( entry - > segment_name ,
SEGMENT_EXCLUSIVE ) ;
if ( rc < 0 ) {
BUG_ON ( rc = = - EINVAL ) ;
if ( rc ! = - EAGAIN )
goto removeseg ;
}
2005-04-17 02:20:36 +04:00
}
2008-10-10 23:33:21 +04:00
dev_info - > is_shared = 0 ;
set_disk_ro ( dev_info - > gd , 0 ) ;
2005-04-17 02:20:36 +04:00
} else {
rc = - EINVAL ;
goto out ;
}
rc = count ;
goto out ;
removeseg :
2008-12-25 15:39:45 +03:00
pr_err ( " DCSS device %s is removed after a failed access mode "
" change \n " , dev_info - > segment_name ) ;
2008-10-10 23:33:21 +04:00
temp = entry ;
list_for_each_entry ( entry , & dev_info - > seg_list , lh ) {
if ( entry ! = temp )
segment_unload ( entry - > segment_name ) ;
}
2005-04-17 02:20:36 +04:00
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 ) ;
2014-02-03 23:03:04 +04:00
up_write ( & dcssblk_devices_sem ) ;
if ( device_remove_file_self ( dev , attr ) ) {
device_unregister ( dev ) ;
put_device ( dev ) ;
}
return rc ;
2005-04-17 02:20:36 +04:00
out :
up_write ( & dcssblk_devices_sem ) ;
return rc ;
}
2012-10-01 11:12:01 +04:00
static DEVICE_ATTR ( shared , S_IWUSR | S_IRUSR , dcssblk_shared_show ,
dcssblk_shared_store ) ;
2005-04-17 02:20:36 +04:00
/*
* 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 ;
2008-10-10 23:33:21 +04:00
struct segment_info * entry ;
2005-04-17 02:20:36 +04:00
2008-07-14 11:59:39 +04:00
if ( ( count > 1 ) & & ( inbuf [ 1 ] ! = ' \n ' ) & & ( inbuf [ 1 ] ! = ' \0 ' ) )
2005-04-17 02:20:36 +04:00
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
2008-12-25 15:39:45 +03:00
pr_info ( " All DCSSs that map to device %s are "
" saved \n " , dev_info - > segment_name ) ;
2008-10-10 23:33:21 +04:00
list_for_each_entry ( entry , & dev_info - > seg_list , lh ) {
segment_save ( entry - > segment_name ) ;
}
2005-04-17 02:20:36 +04:00
} else {
// device is busy => we save it when it becomes
// idle in dcssblk_release
2008-12-25 15:39:45 +03:00
pr_info ( " Device %s is in use, its DCSSs will be "
" saved when it becomes idle \n " ,
dev_info - > segment_name ) ;
2005-04-17 02:20:36 +04:00
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 ;
2008-12-25 15:39:45 +03:00
pr_info ( " A pending save request for device %s "
" has been canceled \n " ,
dev_info - > segment_name ) ;
2005-04-17 02:20:36 +04:00
}
} else {
up_write ( & dcssblk_devices_sem ) ;
return - EINVAL ;
}
up_write ( & dcssblk_devices_sem ) ;
return count ;
}
2012-10-01 11:12:01 +04:00
static DEVICE_ATTR ( save , S_IWUSR | S_IRUSR , dcssblk_save_show ,
dcssblk_save_store ) ;
2005-04-17 02:20:36 +04:00
2008-10-10 23:33:21 +04:00
/*
* device attribute for showing all segments in a device
*/
static ssize_t
dcssblk_seglist_show ( struct device * dev , struct device_attribute * attr ,
char * buf )
{
int i ;
struct dcssblk_dev_info * dev_info ;
struct segment_info * entry ;
down_read ( & dcssblk_devices_sem ) ;
dev_info = container_of ( dev , struct dcssblk_dev_info , dev ) ;
i = 0 ;
buf [ 0 ] = ' \0 ' ;
list_for_each_entry ( entry , & dev_info - > seg_list , lh ) {
strcpy ( & buf [ i ] , entry - > segment_name ) ;
i + = strlen ( entry - > segment_name ) ;
buf [ i ] = ' \n ' ;
i + + ;
}
up_read ( & dcssblk_devices_sem ) ;
return i ;
}
2012-10-01 11:12:01 +04:00
static DEVICE_ATTR ( seglist , S_IRUSR , dcssblk_seglist_show , NULL ) ;
static struct attribute * dcssblk_dev_attrs [ ] = {
& dev_attr_shared . attr ,
& dev_attr_save . attr ,
& dev_attr_seglist . attr ,
NULL ,
} ;
static struct attribute_group dcssblk_dev_attr_group = {
. attrs = dcssblk_dev_attrs ,
} ;
static const struct attribute_group * dcssblk_dev_attr_groups [ ] = {
& dcssblk_dev_attr_group ,
NULL ,
} ;
2008-10-10 23:33:21 +04:00
2005-04-17 02:20:36 +04:00
/*
* 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
{
2008-10-10 23:33:21 +04:00
int rc , i , j , num_of_segments ;
2005-04-17 02:20:36 +04:00
struct dcssblk_dev_info * dev_info ;
2008-10-10 23:33:21 +04:00
struct segment_info * seg_info , * temp ;
2005-04-17 02:20:36 +04:00
char * local_buf ;
unsigned long seg_byte_size ;
dev_info = NULL ;
2008-10-10 23:33:21 +04:00
seg_info = NULL ;
2005-04-17 02:20:36 +04:00
if ( dev ! = dcssblk_root_dev ) {
rc = - EINVAL ;
goto out_nobuf ;
}
2008-10-10 23:33:21 +04:00
if ( ( count < 1 ) | | ( buf [ 0 ] = = ' \0 ' ) | | ( buf [ 0 ] = = ' \n ' ) ) {
rc = - ENAMETOOLONG ;
goto out_nobuf ;
}
2005-04-17 02:20:36 +04:00
local_buf = kmalloc ( count + 1 , GFP_KERNEL ) ;
if ( local_buf = = NULL ) {
rc = - ENOMEM ;
goto out_nobuf ;
}
2008-10-10 23:33:21 +04:00
2005-04-17 02:20:36 +04:00
/*
* parse input
*/
2008-10-10 23:33:21 +04:00
num_of_segments = 0 ;
2005-04-17 02:20:36 +04:00
for ( i = 0 ; ( ( buf [ i ] ! = ' \0 ' ) & & ( buf [ i ] ! = ' \n ' ) & & i < count ) ; i + + ) {
2008-10-10 23:33:21 +04:00
for ( j = i ; ( buf [ j ] ! = ' : ' ) & &
( buf [ j ] ! = ' \0 ' ) & &
( buf [ j ] ! = ' \n ' ) & &
j < count ; j + + ) {
local_buf [ j - i ] = toupper ( buf [ j ] ) ;
}
local_buf [ j - i ] = ' \0 ' ;
if ( ( ( j - i ) = = 0 ) | | ( ( j - i ) > 8 ) ) {
rc = - ENAMETOOLONG ;
goto seg_list_del ;
}
rc = dcssblk_load_segment ( local_buf , & seg_info ) ;
if ( rc < 0 )
goto seg_list_del ;
/*
* get a struct dcssblk_dev_info
*/
if ( num_of_segments = = 0 ) {
dev_info = kzalloc ( sizeof ( struct dcssblk_dev_info ) ,
GFP_KERNEL ) ;
if ( dev_info = = NULL ) {
rc = - ENOMEM ;
goto out ;
}
strcpy ( dev_info - > segment_name , local_buf ) ;
dev_info - > segment_type = seg_info - > segment_type ;
INIT_LIST_HEAD ( & dev_info - > seg_list ) ;
}
list_add_tail ( & seg_info - > lh , & dev_info - > seg_list ) ;
num_of_segments + + ;
i = j ;
if ( ( buf [ j ] = = ' \0 ' ) | | ( buf [ j ] = = ' \n ' ) )
break ;
2005-04-17 02:20:36 +04:00
}
2008-10-10 23:33:21 +04:00
/* no trailing colon at the end of the input */
if ( ( i > 0 ) & & ( buf [ i - 1 ] = = ' : ' ) ) {
2005-04-17 02:20:36 +04:00
rc = - ENAMETOOLONG ;
2008-10-10 23:33:21 +04:00
goto seg_list_del ;
2005-04-17 02:20:36 +04:00
}
2008-10-10 23:33:21 +04:00
strlcpy ( local_buf , buf , i + 1 ) ;
dev_info - > num_of_segments = num_of_segments ;
rc = dcssblk_is_continuous ( dev_info ) ;
if ( rc < 0 )
goto seg_list_del ;
dev_info - > start = dcssblk_find_lowest_addr ( dev_info ) ;
dev_info - > end = dcssblk_find_highest_addr ( dev_info ) ;
2005-04-17 02:20:36 +04:00
2014-06-10 21:46:20 +04:00
dev_set_name ( & dev_info - > dev , " %s " , dev_info - > segment_name ) ;
2005-04-17 02:20:36 +04:00
dev_info - > dev . release = dcssblk_release_segment ;
2012-10-01 11:12:01 +04:00
dev_info - > dev . groups = dcssblk_dev_attr_groups ;
2005-04-17 02:20:36 +04:00
INIT_LIST_HEAD ( & dev_info - > lh ) ;
dev_info - > gd = alloc_disk ( DCSSBLK_MINORS_PER_DISK ) ;
if ( dev_info - > gd = = NULL ) {
rc = - ENOMEM ;
2008-10-10 23:33:21 +04:00
goto seg_list_del ;
2005-04-17 02:20:36 +04:00
}
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 ;
2008-02-05 18:50:50 +03:00
blk_queue_make_request ( dev_info - > dcssblk_queue , dcssblk_make_request ) ;
2009-05-23 01:17:49 +04:00
blk_queue_logical_block_size ( dev_info - > dcssblk_queue , 4096 ) ;
2008-10-10 23:33:21 +04:00
2005-04-17 02:20:36 +04:00
seg_byte_size = ( dev_info - > end - dev_info - > start + 1 ) ;
set_capacity ( dev_info - > gd , seg_byte_size > > 9 ) ; // size in sectors
2008-12-25 15:39:45 +03:00
pr_info ( " Loaded %s with total size %lu bytes and capacity %lu "
" sectors \n " , local_buf , seg_byte_size , seg_byte_size > > 9 ) ;
2005-04-17 02:20:36 +04:00
dev_info - > save_pending = 0 ;
dev_info - > is_shared = 1 ;
dev_info - > dev . parent = dcssblk_root_dev ;
/*
2008-10-10 23:33:21 +04:00
* get minor , add to list
2005-04-17 02:20:36 +04:00
*/
down_write ( & dcssblk_devices_sem ) ;
2008-10-10 23:33:21 +04:00
if ( dcssblk_get_segment_by_name ( local_buf ) ) {
2008-08-21 21:46:40 +04:00
rc = - EEXIST ;
2008-10-10 23:33:21 +04:00
goto release_gd ;
2008-08-21 21:46:40 +04:00
}
2005-04-17 02:20:36 +04:00
rc = dcssblk_assign_free_minor ( dev_info ) ;
2008-10-10 23:33:21 +04:00
if ( rc )
goto release_gd ;
2005-04-17 02:20:36 +04:00
sprintf ( dev_info - > gd - > disk_name , " dcssblk%d " ,
2009-06-12 12:26:35 +04:00
dev_info - > gd - > first_minor ) ;
2005-04-17 02:20:36 +04:00
list_add_tail ( & dev_info - > lh , & dcssblk_devices ) ;
if ( ! try_module_get ( THIS_MODULE ) ) {
rc = - ENODEV ;
2008-10-10 23:33:21 +04:00
goto dev_list_del ;
2005-04-17 02:20:36 +04:00
}
/*
* register the device
*/
rc = device_register ( & dev_info - > dev ) ;
2008-10-10 23:33:21 +04:00
if ( rc )
2012-10-01 11:12:01 +04:00
goto put_dev ;
2005-04-17 02:20:36 +04:00
2012-10-01 11:12:01 +04:00
get_device ( & dev_info - > dev ) ;
2007-12-04 18:09:03 +03:00
add_disk ( dev_info - > gd ) ;
2005-04-17 02:20:36 +04:00
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 ;
}
up_write ( & dcssblk_devices_sem ) ;
rc = count ;
goto out ;
2012-10-01 11:12:01 +04:00
put_dev :
2005-04-17 02:20:36 +04:00
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 ) ;
2008-10-10 23:33:21 +04:00
list_for_each_entry ( seg_info , & dev_info - > seg_list , lh ) {
segment_unload ( seg_info - > segment_name ) ;
}
2005-04-17 02:20:36 +04:00
put_device ( & dev_info - > dev ) ;
up_write ( & dcssblk_devices_sem ) ;
goto out ;
2008-10-10 23:33:21 +04:00
dev_list_del :
2005-04-17 02:20:36 +04:00
list_del ( & dev_info - > lh ) ;
2008-10-10 23:33:21 +04:00
release_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 ) ;
2008-10-10 23:33:21 +04:00
up_write ( & dcssblk_devices_sem ) ;
seg_list_del :
if ( dev_info = = NULL )
goto out ;
list_for_each_entry_safe ( seg_info , temp , & dev_info - > seg_list , lh ) {
list_del ( & seg_info - > lh ) ;
segment_unload ( seg_info - > segment_name ) ;
kfree ( seg_info ) ;
}
2005-04-17 02:20:36 +04:00
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 ;
2008-10-10 23:33:21 +04:00
struct segment_info * entry ;
2005-04-17 02:20:36 +04:00
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 ) ;
2008-12-25 15:39:45 +03:00
pr_warning ( " Device %s cannot be removed because it is not a "
" known device \n " , local_buf ) ;
2005-04-17 02:20:36 +04:00
rc = - ENODEV ;
goto out_buf ;
}
if ( atomic_read ( & dev_info - > use_count ) ! = 0 ) {
up_write ( & dcssblk_devices_sem ) ;
2008-12-25 15:39:45 +03:00
pr_warning ( " Device %s cannot be removed while it is in "
" use \n " , local_buf ) ;
2005-04-17 02:20:36 +04:00
rc = - EBUSY ;
goto out_buf ;
}
2008-10-10 23:33:21 +04:00
list_del ( & dev_info - > lh ) ;
2005-04-17 02:20:36 +04:00
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 ) ;
2008-10-10 23:33:21 +04:00
/* unload all related segments */
list_for_each_entry ( entry , & dev_info - > seg_list , lh )
segment_unload ( entry - > segment_name ) ;
2005-04-17 02:20:36 +04:00
put_device ( & dev_info - > dev ) ;
up_write ( & dcssblk_devices_sem ) ;
rc = count ;
out_buf :
kfree ( local_buf ) ;
return rc ;
}
static int
2008-03-02 18:37:41 +03:00
dcssblk_open ( struct block_device * bdev , fmode_t mode )
2005-04-17 02:20:36 +04:00
{
struct dcssblk_dev_info * dev_info ;
int rc ;
2008-03-02 18:37:41 +03:00
dev_info = bdev - > bd_disk - > private_data ;
2005-04-17 02:20:36 +04:00
if ( NULL = = dev_info ) {
rc = - ENODEV ;
goto out ;
}
atomic_inc ( & dev_info - > use_count ) ;
2008-03-02 18:37:41 +03:00
bdev - > bd_block_size = 4096 ;
2005-04-17 02:20:36 +04:00
rc = 0 ;
out :
return rc ;
}
2013-05-06 05:52:57 +04:00
static void
2008-03-02 18:37:41 +03:00
dcssblk_release ( struct gendisk * disk , fmode_t mode )
2005-04-17 02:20:36 +04:00
{
2008-03-02 18:37:41 +03:00
struct dcssblk_dev_info * dev_info = disk - > private_data ;
2008-10-10 23:33:21 +04:00
struct segment_info * entry ;
2005-04-17 02:20:36 +04:00
2008-03-02 18:37:41 +03:00
if ( ! dev_info ) {
2013-05-06 05:52:57 +04:00
WARN_ON ( 1 ) ;
return ;
2005-04-17 02:20:36 +04:00
}
down_write ( & dcssblk_devices_sem ) ;
if ( atomic_dec_and_test ( & dev_info - > use_count )
& & ( dev_info - > save_pending ) ) {
2008-12-25 15:39:45 +03:00
pr_info ( " Device %s has become idle and is being saved "
" now \n " , dev_info - > segment_name ) ;
2008-10-10 23:33:21 +04:00
list_for_each_entry ( entry , & dev_info - > seg_list , lh ) {
segment_save ( entry - > segment_name ) ;
}
2005-04-17 02:20:36 +04:00
dev_info - > save_pending = 0 ;
}
up_write ( & dcssblk_devices_sem ) ;
}
2011-09-12 14:12:01 +04:00
static void
2007-07-24 11:28:11 +04:00
dcssblk_make_request ( struct request_queue * q , struct bio * bio )
2005-04-17 02:20:36 +04:00
{
struct dcssblk_dev_info * dev_info ;
2013-11-24 05:19:00 +04:00
struct bio_vec bvec ;
struct bvec_iter iter ;
2005-04-17 02:20:36 +04:00
unsigned long index ;
unsigned long page_addr ;
unsigned long source_addr ;
unsigned long bytes_done ;
bytes_done = 0 ;
dev_info = bio - > bi_bdev - > bd_disk - > private_data ;
if ( dev_info = = NULL )
goto fail ;
2013-10-12 02:44:27 +04:00
if ( ( bio - > bi_iter . bi_sector & 7 ) ! = 0 | |
( bio - > bi_iter . bi_size & 4095 ) ! = 0 )
2005-04-17 02:20:36 +04:00
/* Request is not page-aligned. */
goto fail ;
2012-09-26 02:05:12 +04:00
if ( bio_end_sector ( bio ) > get_capacity ( bio - > bi_bdev - > bd_disk ) ) {
2005-04-17 02:20:36 +04:00
/* 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 ) {
2008-12-25 15:39:45 +03:00
pr_warning ( " Writing to %s failed because it "
" is a read-only device \n " ,
2008-10-10 23:33:09 +04:00
dev_name ( & dev_info - > dev ) ) ;
2005-06-24 09:05:23 +04:00
goto fail ;
}
}
}
2013-10-12 02:44:27 +04:00
index = ( bio - > bi_iter . bi_sector > > 3 ) ;
2013-11-24 05:19:00 +04:00
bio_for_each_segment ( bvec , bio , iter ) {
2005-04-17 02:20:36 +04:00
page_addr = ( unsigned long )
2013-11-24 05:19:00 +04:00
page_address ( bvec . bv_page ) + bvec . bv_offset ;
2005-04-17 02:20:36 +04:00
source_addr = dev_info - > start + ( index < < 12 ) + bytes_done ;
2013-11-24 05:19:00 +04:00
if ( unlikely ( ( page_addr & 4095 ) ! = 0 ) | | ( bvec . bv_len & 4095 ) ! = 0 )
2005-04-17 02:20:36 +04:00
// More paranoia.
goto fail ;
if ( bio_data_dir ( bio ) = = READ ) {
memcpy ( ( void * ) page_addr , ( void * ) source_addr ,
2013-11-24 05:19:00 +04:00
bvec . bv_len ) ;
2005-04-17 02:20:36 +04:00
} else {
memcpy ( ( void * ) source_addr , ( void * ) page_addr ,
2013-11-24 05:19:00 +04:00
bvec . bv_len ) ;
2005-04-17 02:20:36 +04:00
}
2013-11-24 05:19:00 +04:00
bytes_done + = bvec . bv_len ;
2005-04-17 02:20:36 +04:00
}
2007-09-27 14:47:43 +04:00
bio_endio ( bio , 0 ) ;
2011-09-12 14:12:01 +04:00
return ;
2005-04-17 02:20:36 +04:00
fail :
2007-09-27 14:47:43 +04:00
bio_io_error ( bio ) ;
2005-06-24 09:05:23 +04:00
}
static int
dcssblk_direct_access ( struct block_device * bdev , sector_t secnum ,
2008-04-28 13:13:02 +04:00
void * * kaddr , unsigned long * pfn )
2005-06-24 09:05:23 +04:00
{
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 ;
2008-04-28 13:13:02 +04:00
* kaddr = ( void * ) ( dev_info - > start + pgoff * PAGE_SIZE ) ;
* pfn = virt_to_phys ( * kaddr ) > > PAGE_SHIFT ;
2005-04-17 02:20:36 +04:00
return 0 ;
}
static void
dcssblk_check_params ( void )
{
int rc , i , j , k ;
2008-10-10 23:33:21 +04:00
char buf [ DCSSBLK_PARM_LEN + 1 ] ;
2005-04-17 02:20:36 +04:00
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 ] ! = ' ( ' ) & &
2008-10-10 23:33:21 +04:00
( j < DCSSBLK_PARM_LEN ) ; j + + )
2005-04-17 02:20:36 +04:00
{
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 ] = = ' ( ' ) ) {
2008-10-10 23:33:21 +04:00
for ( k = 0 ; ( buf [ k ] ! = ' : ' ) & & ( buf [ k ] ! = ' \0 ' ) ; k + + )
2005-04-17 02:20:36 +04:00
buf [ k ] = toupper ( buf [ k ] ) ;
2008-10-10 23:33:21 +04:00
buf [ k ] = ' \0 ' ;
2005-04-17 02:20:36 +04:00
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 ;
}
}
2009-06-16 12:30:51 +04:00
/*
* Suspend / Resume
*/
static int dcssblk_freeze ( struct device * dev )
{
struct dcssblk_dev_info * dev_info ;
int rc = 0 ;
list_for_each_entry ( dev_info , & dcssblk_devices , lh ) {
switch ( dev_info - > segment_type ) {
case SEG_TYPE_SR :
case SEG_TYPE_ER :
case SEG_TYPE_SC :
if ( ! dev_info - > is_shared )
rc = - EINVAL ;
break ;
default :
rc = - EINVAL ;
break ;
}
if ( rc )
break ;
}
if ( rc )
2009-07-07 18:37:11 +04:00
pr_err ( " Suspending the system failed because DCSS device %s "
" is writable \n " ,
2009-06-16 12:30:51 +04:00
dev_info - > segment_name ) ;
return rc ;
}
static int dcssblk_restore ( struct device * dev )
{
struct dcssblk_dev_info * dev_info ;
struct segment_info * entry ;
unsigned long start , end ;
int rc = 0 ;
list_for_each_entry ( dev_info , & dcssblk_devices , lh ) {
list_for_each_entry ( entry , & dev_info - > seg_list , lh ) {
segment_unload ( entry - > segment_name ) ;
rc = segment_load ( entry - > segment_name , SEGMENT_SHARED ,
& start , & end ) ;
if ( rc < 0 ) {
// TODO in_use check ?
segment_warning ( rc , entry - > segment_name ) ;
goto out_panic ;
}
if ( start ! = entry - > start | | end ! = entry - > end ) {
2009-07-07 18:37:11 +04:00
pr_err ( " The address range of DCSS %s changed "
" while the system was suspended \n " ,
2009-06-16 12:30:51 +04:00
entry - > segment_name ) ;
goto out_panic ;
}
}
}
return 0 ;
out_panic :
panic ( " fatal dcssblk resume error \n " ) ;
}
static int dcssblk_thaw ( struct device * dev )
{
return 0 ;
}
2009-12-15 05:00:08 +03:00
static const struct dev_pm_ops dcssblk_pm_ops = {
2009-06-16 12:30:51 +04:00
. freeze = dcssblk_freeze ,
. thaw = dcssblk_thaw ,
. restore = dcssblk_restore ,
} ;
static struct platform_driver dcssblk_pdrv = {
. driver = {
. name = " dcssblk " ,
. pm = & dcssblk_pm_ops ,
} ,
} ;
static struct platform_device * dcssblk_pdev ;
2005-04-17 02:20:36 +04:00
/*
* The init / exit functions .
*/
static void __exit
dcssblk_exit ( void )
{
2009-06-16 12:30:51 +04:00
platform_device_unregister ( dcssblk_pdev ) ;
platform_driver_unregister ( & dcssblk_pdrv ) ;
2008-12-15 15:58:29 +03:00
root_device_unregister ( dcssblk_root_dev ) ;
2007-07-17 15:03:46 +04:00
unregister_blkdev ( dcssblk_major , DCSSBLK_NAME ) ;
2005-04-17 02:20:36 +04:00
}
static int __init
dcssblk_init ( void )
{
int rc ;
2009-06-16 12:30:51 +04:00
rc = platform_driver_register ( & dcssblk_pdrv ) ;
if ( rc )
2005-04-17 02:20:36 +04:00
return rc ;
2009-06-16 12:30:51 +04:00
dcssblk_pdev = platform_device_register_simple ( " dcssblk " , - 1 , NULL ,
0 ) ;
if ( IS_ERR ( dcssblk_pdev ) ) {
rc = PTR_ERR ( dcssblk_pdev ) ;
goto out_pdrv ;
2005-04-17 02:20:36 +04:00
}
2009-06-16 12:30:51 +04:00
dcssblk_root_dev = root_device_register ( " dcssblk " ) ;
if ( IS_ERR ( dcssblk_root_dev ) ) {
rc = PTR_ERR ( dcssblk_root_dev ) ;
goto out_pdev ;
2005-04-17 02:20:36 +04:00
}
2009-06-16 12:30:51 +04:00
rc = device_create_file ( dcssblk_root_dev , & dev_attr_add ) ;
if ( rc )
goto out_root ;
rc = device_create_file ( dcssblk_root_dev , & dev_attr_remove ) ;
if ( rc )
goto out_root ;
2005-04-17 02:20:36 +04:00
rc = register_blkdev ( 0 , DCSSBLK_NAME ) ;
2009-06-16 12:30:51 +04:00
if ( rc < 0 )
goto out_root ;
2005-04-17 02:20:36 +04:00
dcssblk_major = rc ;
init_rwsem ( & dcssblk_devices_sem ) ;
dcssblk_check_params ( ) ;
return 0 ;
2009-06-16 12:30:51 +04:00
out_root :
root_device_unregister ( dcssblk_root_dev ) ;
out_pdev :
platform_device_unregister ( dcssblk_pdev ) ;
out_pdrv :
platform_driver_unregister ( & dcssblk_pdrv ) ;
return rc ;
2005-04-17 02:20:36 +04:00
}
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, "
2008-10-10 23:33:21 +04:00
" comma-separated list, names in each set separated "
" by commas are separated by colons, each set contains "
" names of contiguous segments and each name max. 8 chars. \n "
" Adding \" (local) \" to the end of each set equals echoing 0 "
" to /sys/devices/dcssblk/<device name>/shared after loading "
" the contiguous segments - \n "
" e.g. segments= \" mydcss1,mydcss2:mydcss3,mydcss4(local) \" " ) ;
2005-04-17 02:20:36 +04:00
MODULE_LICENSE ( " GPL " ) ;