2005-04-16 15:20:36 -07:00
/*
* Xpram . c - - the S / 390 expanded memory RAM - disk
*
* significant parts of this code are based on
* the sbull device driver presented in
* A . Rubini : Linux Device Drivers
*
* Author of XPRAM specific coding : Reinhard Buendgen
* buendgen @ de . ibm . com
* Rewrite for 2.5 : Martin Schwidefsky < schwidefsky @ de . ibm . com >
*
* External interfaces :
* Interfaces to linux kernel
* xpram_setup : read kernel parameters
* Device specific file operations
* xpram_iotcl
* xpram_open
*
* " ad-hoc " partitioning :
* the expanded memory can be partitioned among several devices
* ( with different minors ) . The partitioning set up can be
* set by kernel or module parameters ( int devs & int sizes [ ] )
*
* Potential future improvements :
* generic hard disk support to replace ad - hoc partitioning
*/
# include <linux/module.h>
# include <linux/moduleparam.h>
# include <linux/ctype.h> /* isdigit, isxdigit */
# include <linux/errno.h>
# include <linux/init.h>
# include <linux/slab.h>
# include <linux/blkdev.h>
# include <linux/blkpg.h>
# include <linux/hdreg.h> /* HDIO_GETGEO */
# include <linux/sysdev.h>
# include <linux/bio.h>
# include <asm/uaccess.h>
# define XPRAM_NAME "xpram"
# define XPRAM_DEVS 1 /* one partition */
# define XPRAM_MAX_DEVS 32 /* maximal number of devices (partitions) */
# define PRINT_DEBUG(x...) printk(KERN_DEBUG XPRAM_NAME " debug:" x)
# define PRINT_INFO(x...) printk(KERN_INFO XPRAM_NAME " info:" x)
# define PRINT_WARN(x...) printk(KERN_WARNING XPRAM_NAME " warning:" x)
# define PRINT_ERR(x...) printk(KERN_ERR XPRAM_NAME " error:" x)
typedef struct {
unsigned int size ; /* size of xpram segment in pages */
unsigned int offset ; /* start page of xpram segment */
} xpram_device_t ;
static xpram_device_t xpram_devices [ XPRAM_MAX_DEVS ] ;
static unsigned int xpram_sizes [ XPRAM_MAX_DEVS ] ;
static struct gendisk * xpram_disks [ XPRAM_MAX_DEVS ] ;
static unsigned int xpram_pages ;
static int xpram_devs ;
/*
* Parameter parsing functions .
*/
2006-07-12 16:40:14 +02:00
static int __initdata devs = XPRAM_DEVS ;
static char __initdata * sizes [ XPRAM_MAX_DEVS ] ;
2005-04-16 15:20:36 -07:00
module_param ( devs , int , 0 ) ;
2006-07-12 16:40:14 +02:00
module_param_array ( sizes , charp , NULL , 0 ) ;
2005-04-16 15:20:36 -07:00
MODULE_PARM_DESC ( devs , " number of devices ( \" partitions \" ), " \
" the default is " __MODULE_STRING ( XPRAM_DEVS ) " \n " ) ;
MODULE_PARM_DESC ( sizes , " list of device (partition) sizes " \
" the defaults are 0s \n " \
" All devices with size 0 equally partition the "
" remaining space on the expanded strorage not "
" claimed by explicit sizes \n " ) ;
MODULE_LICENSE ( " GPL " ) ;
/*
* Copy expanded memory page ( 4 kB ) into main memory
* Arguments
* page_addr : address of target page
* xpage_index : index of expandeded memory page
* Return value
* 0 : if operation succeeds
* - EIO : if pgin failed
* - ENXIO : if xpram has vanished
*/
static int xpram_page_in ( unsigned long page_addr , unsigned int xpage_index )
{
2006-09-28 16:56:43 +02:00
int cc = 2 ; /* return unused cc 2 if pgin traps */
2005-04-16 15:20:36 -07:00
2006-09-28 16:56:43 +02:00
asm volatile (
" .insn rre,0xb22e0000,%1,%2 \n " /* pgin %1,%2 */
" 0: ipm %0 \n "
" srl %0,28 \n "
2005-04-16 15:20:36 -07:00
" 1: \n "
2006-09-28 16:56:43 +02:00
EX_TABLE ( 0 b , 1 b )
: " +d " ( cc ) : " a " ( __pa ( page_addr ) ) , " d " ( xpage_index ) : " cc " ) ;
2005-04-16 15:20:36 -07:00
if ( cc = = 3 )
return - ENXIO ;
if ( cc = = 2 ) {
PRINT_ERR ( " expanded storage lost! \n " ) ;
return - ENXIO ;
}
if ( cc = = 1 ) {
PRINT_ERR ( " page in failed for page index %u. \n " ,
xpage_index ) ;
return - EIO ;
}
return 0 ;
}
/*
* Copy a 4 kB page of main memory to an expanded memory page
* Arguments
* page_addr : address of source page
* xpage_index : index of expandeded memory page
* Return value
* 0 : if operation succeeds
* - EIO : if pgout failed
* - ENXIO : if xpram has vanished
*/
static long xpram_page_out ( unsigned long page_addr , unsigned int xpage_index )
{
2006-09-28 16:56:43 +02:00
int cc = 2 ; /* return unused cc 2 if pgin traps */
2005-04-16 15:20:36 -07:00
2006-09-28 16:56:43 +02:00
asm volatile (
" .insn rre,0xb22f0000,%1,%2 \n " /* pgout %1,%2 */
" 0: ipm %0 \n "
" srl %0,28 \n "
2005-04-16 15:20:36 -07:00
" 1: \n "
2006-09-28 16:56:43 +02:00
EX_TABLE ( 0 b , 1 b )
: " +d " ( cc ) : " a " ( __pa ( page_addr ) ) , " d " ( xpage_index ) : " cc " ) ;
2005-04-16 15:20:36 -07:00
if ( cc = = 3 )
return - ENXIO ;
if ( cc = = 2 ) {
PRINT_ERR ( " expanded storage lost! \n " ) ;
return - ENXIO ;
}
if ( cc = = 1 ) {
PRINT_ERR ( " page out failed for page index %u. \n " ,
xpage_index ) ;
return - EIO ;
}
return 0 ;
}
/*
* Check if xpram is available .
*/
static int __init xpram_present ( void )
{
unsigned long mem_page ;
int rc ;
mem_page = ( unsigned long ) __get_free_page ( GFP_KERNEL ) ;
if ( ! mem_page )
return - ENOMEM ;
rc = xpram_page_in ( mem_page , 0 ) ;
free_page ( mem_page ) ;
return rc ? - ENXIO : 0 ;
}
/*
* Return index of the last available xpram page .
*/
static unsigned long __init xpram_highest_page_index ( void )
{
unsigned int page_index , add_bit ;
unsigned long mem_page ;
mem_page = ( unsigned long ) __get_free_page ( GFP_KERNEL ) ;
if ( ! mem_page )
return 0 ;
page_index = 0 ;
add_bit = 1ULL < < ( sizeof ( unsigned int ) * 8 - 1 ) ;
while ( add_bit > 0 ) {
if ( xpram_page_in ( mem_page , page_index | add_bit ) = = 0 )
page_index | = add_bit ;
add_bit > > = 1 ;
}
free_page ( mem_page ) ;
return page_index ;
}
/*
* Block device make request function .
*/
2007-07-24 09:28:11 +02:00
static int xpram_make_request ( struct request_queue * q , struct bio * bio )
2005-04-16 15:20:36 -07:00
{
xpram_device_t * xdev = bio - > bi_bdev - > bd_disk - > private_data ;
struct bio_vec * bvec ;
unsigned int index ;
unsigned long page_addr ;
unsigned long bytes ;
int i ;
if ( ( bio - > bi_sector & 7 ) ! = 0 | | ( bio - > bi_size & 4095 ) ! = 0 )
/* Request is not page-aligned. */
goto fail ;
if ( ( bio - > bi_size > > 12 ) > xdev - > size )
/* Request size is no page-aligned. */
goto fail ;
if ( ( bio - > bi_sector > > 3 ) > 0xffffffffU - xdev - > offset )
goto fail ;
index = ( bio - > bi_sector > > 3 ) + xdev - > offset ;
bio_for_each_segment ( bvec , bio , i ) {
page_addr = ( unsigned long )
kmap ( bvec - > bv_page ) + bvec - > bv_offset ;
bytes = bvec - > bv_len ;
if ( ( page_addr & 4095 ) ! = 0 | | ( bytes & 4095 ) ! = 0 )
/* More paranoia. */
goto fail ;
while ( bytes > 0 ) {
if ( bio_data_dir ( bio ) = = READ ) {
if ( xpram_page_in ( page_addr , index ) ! = 0 )
goto fail ;
} else {
if ( xpram_page_out ( page_addr , index ) ! = 0 )
goto fail ;
}
page_addr + = 4096 ;
bytes - = 4096 ;
index + + ;
}
}
set_bit ( BIO_UPTODATE , & bio - > bi_flags ) ;
2007-10-12 16:11:53 +02:00
bio_endio ( bio , 0 ) ;
2005-04-16 15:20:36 -07:00
return 0 ;
fail :
2007-09-27 12:47:43 +02:00
bio_io_error ( bio ) ;
2005-04-16 15:20:36 -07:00
return 0 ;
}
2006-01-08 01:02:50 -08:00
static int xpram_getgeo ( struct block_device * bdev , struct hd_geometry * geo )
2005-04-16 15:20:36 -07:00
{
unsigned long size ;
2006-01-08 01:02:50 -08:00
2005-04-16 15:20:36 -07:00
/*
* get geometry : we have to fake one . . . trim the size to a
* multiple of 64 ( 32 k ) : tell we have 16 sectors , 4 heads ,
* whatever cylinders . Tell also that data starts at sector . 4.
*/
size = ( xpram_pages * 8 ) & ~ 0x3f ;
2006-01-08 01:02:50 -08:00
geo - > cylinders = size > > 6 ;
geo - > heads = 4 ;
geo - > sectors = 16 ;
geo - > start = 4 ;
2005-04-16 15:20:36 -07:00
return 0 ;
}
static struct block_device_operations xpram_devops =
{
. owner = THIS_MODULE ,
2006-01-08 01:02:50 -08:00
. getgeo = xpram_getgeo ,
2005-04-16 15:20:36 -07:00
} ;
/*
* Setup xpram_sizes array .
*/
static int __init xpram_setup_sizes ( unsigned long pages )
{
unsigned long mem_needed ;
unsigned long mem_auto ;
2006-07-17 16:09:23 +02:00
unsigned long long size ;
2005-04-16 15:20:36 -07:00
int mem_auto_no ;
int i ;
/* Check number of devices. */
if ( devs < = 0 | | devs > XPRAM_MAX_DEVS ) {
PRINT_ERR ( " invalid number %d of devices \n " , devs ) ;
return - EINVAL ;
}
xpram_devs = devs ;
/*
* Copy sizes array to xpram_sizes and align partition
* sizes to page boundary .
*/
mem_needed = 0 ;
mem_auto_no = 0 ;
for ( i = 0 ; i < xpram_devs ; i + + ) {
2006-07-17 16:09:23 +02:00
if ( sizes [ i ] ) {
size = simple_strtoull ( sizes [ i ] , & sizes [ i ] , 0 ) ;
switch ( sizes [ i ] [ 0 ] ) {
case ' g ' :
case ' G ' :
size < < = 20 ;
break ;
case ' m ' :
case ' M ' :
size < < = 10 ;
}
xpram_sizes [ i ] = ( size + 3 ) & - 4UL ;
}
2005-04-16 15:20:36 -07:00
if ( xpram_sizes [ i ] )
mem_needed + = xpram_sizes [ i ] ;
else
mem_auto_no + + ;
}
PRINT_INFO ( " number of devices (partitions): %d \n " , xpram_devs ) ;
for ( i = 0 ; i < xpram_devs ; i + + ) {
if ( xpram_sizes [ i ] )
PRINT_INFO ( " size of partition %d: %u kB \n " ,
i , xpram_sizes [ i ] ) ;
else
PRINT_INFO ( " size of partition %d to be set "
" automatically \n " , i ) ;
}
PRINT_DEBUG ( " memory needed (for sized partitions): %lu kB \n " ,
mem_needed ) ;
PRINT_DEBUG ( " partitions to be sized automatically: %d \n " ,
mem_auto_no ) ;
if ( mem_needed > pages * 4 ) {
PRINT_ERR ( " Not enough expanded memory available \n " ) ;
return - EINVAL ;
}
/*
* partitioning :
* xpram_sizes [ i ] ! = 0 ; partition i has size xpram_sizes [ i ] kB
* else : ; all partitions with zero xpram_sizes [ i ]
* partition equally the remaining space
*/
if ( mem_auto_no ) {
mem_auto = ( ( pages - mem_needed / 4 ) / mem_auto_no ) * 4 ;
PRINT_INFO ( " automatically determined "
" partition size: %lu kB \n " , mem_auto ) ;
for ( i = 0 ; i < xpram_devs ; i + + )
if ( xpram_sizes [ i ] = = 0 )
xpram_sizes [ i ] = mem_auto ;
}
return 0 ;
}
static struct request_queue * xpram_queue ;
static int __init xpram_setup_blkdev ( void )
{
unsigned long offset ;
int i , rc = - ENOMEM ;
for ( i = 0 ; i < xpram_devs ; i + + ) {
struct gendisk * disk = alloc_disk ( 1 ) ;
if ( ! disk )
goto out ;
xpram_disks [ i ] = disk ;
}
/*
* Register xpram major .
*/
rc = register_blkdev ( XPRAM_MAJOR , XPRAM_NAME ) ;
if ( rc < 0 )
goto out ;
/*
* Assign the other needed values : make request function , sizes and
* hardsect size . All the minor devices feature the same value .
*/
xpram_queue = blk_alloc_queue ( GFP_KERNEL ) ;
if ( ! xpram_queue ) {
rc = - ENOMEM ;
goto out_unreg ;
}
blk_queue_make_request ( xpram_queue , xpram_make_request ) ;
blk_queue_hardsect_size ( xpram_queue , 4096 ) ;
/*
* Setup device structures .
*/
offset = 0 ;
for ( i = 0 ; i < xpram_devs ; i + + ) {
struct gendisk * disk = xpram_disks [ i ] ;
xpram_devices [ i ] . size = xpram_sizes [ i ] / 4 ;
xpram_devices [ i ] . offset = offset ;
offset + = xpram_devices [ i ] . size ;
disk - > major = XPRAM_MAJOR ;
disk - > first_minor = i ;
disk - > fops = & xpram_devops ;
disk - > private_data = & xpram_devices [ i ] ;
disk - > queue = xpram_queue ;
sprintf ( disk - > disk_name , " slram%d " , i ) ;
set_capacity ( disk , xpram_sizes [ i ] < < 1 ) ;
add_disk ( disk ) ;
}
return 0 ;
out_unreg :
unregister_blkdev ( XPRAM_MAJOR , XPRAM_NAME ) ;
out :
while ( i - - )
put_disk ( xpram_disks [ i ] ) ;
return rc ;
}
/*
* Finally , the init / exit functions .
*/
static void __exit xpram_exit ( void )
{
int i ;
for ( i = 0 ; i < xpram_devs ; i + + ) {
del_gendisk ( xpram_disks [ i ] ) ;
put_disk ( xpram_disks [ i ] ) ;
}
unregister_blkdev ( XPRAM_MAJOR , XPRAM_NAME ) ;
blk_cleanup_queue ( xpram_queue ) ;
}
static int __init xpram_init ( void )
{
int rc ;
/* Find out size of expanded memory. */
if ( xpram_present ( ) ! = 0 ) {
PRINT_WARN ( " No expanded memory available \n " ) ;
return - ENODEV ;
}
2006-09-20 15:59:32 +02:00
xpram_pages = xpram_highest_page_index ( ) + 1 ;
2005-04-16 15:20:36 -07:00
PRINT_INFO ( " %u pages expanded memory found (%lu KB). \n " ,
xpram_pages , ( unsigned long ) xpram_pages * 4 ) ;
rc = xpram_setup_sizes ( xpram_pages ) ;
if ( rc )
return rc ;
2006-08-07 18:13:06 +02:00
return xpram_setup_blkdev ( ) ;
2005-04-16 15:20:36 -07:00
}
module_init ( xpram_init ) ;
module_exit ( xpram_exit ) ;