2005-04-17 02:20:36 +04:00
/*
* Copyright ( C ) 2001 - 2003 Sistina Software ( UK ) Limited .
*
* This file is released under the GPL .
*/
2008-10-21 20:44:59 +04:00
# include <linux/device-mapper.h>
2005-04-17 02:20:36 +04:00
# include <linux/module.h>
# include <linux/init.h>
# include <linux/blkdev.h>
# include <linux/bio.h>
# include <linux/slab.h>
2007-10-20 01:38:44 +04:00
# include <linux/log2.h>
2005-04-17 02:20:36 +04:00
2006-06-26 11:27:35 +04:00
# define DM_MSG_PREFIX "striped"
2008-02-08 05:11:22 +03:00
# define DM_IO_ERROR_THRESHOLD 15
2006-06-26 11:27:35 +04:00
2005-04-17 02:20:36 +04:00
struct stripe {
struct dm_dev * dev ;
sector_t physical_start ;
2008-02-08 05:11:22 +03:00
atomic_t error_count ;
2005-04-17 02:20:36 +04:00
} ;
struct stripe_c {
uint32_t stripes ;
/* The size of this target / num. stripes */
sector_t stripe_width ;
/* stripe chunk size */
uint32_t chunk_shift ;
sector_t chunk_mask ;
2008-02-08 05:11:22 +03:00
/* Needed for handling events */
struct dm_target * ti ;
/* Work struct used for triggering events*/
struct work_struct kstriped_ws ;
2005-04-17 02:20:36 +04:00
struct stripe stripe [ 0 ] ;
} ;
2008-02-08 05:11:22 +03:00
static struct workqueue_struct * kstriped ;
/*
* An event is triggered whenever a drive
* drops out of a stripe volume .
*/
static void trigger_event ( struct work_struct * work )
{
struct stripe_c * sc = container_of ( work , struct stripe_c , kstriped_ws ) ;
dm_table_event ( sc - > ti - > table ) ;
}
2005-04-17 02:20:36 +04:00
static inline struct stripe_c * alloc_context ( unsigned int stripes )
{
size_t len ;
2008-10-21 20:44:57 +04:00
if ( dm_array_too_big ( sizeof ( struct stripe_c ) , sizeof ( struct stripe ) ,
stripes ) )
2005-04-17 02:20:36 +04:00
return NULL ;
len = sizeof ( struct stripe_c ) + ( sizeof ( struct stripe ) * stripes ) ;
return kmalloc ( len , GFP_KERNEL ) ;
}
/*
* Parse a single < dev > < sector > pair
*/
static int get_stripe ( struct dm_target * ti , struct stripe_c * sc ,
unsigned int stripe , char * * argv )
{
2006-03-27 13:17:48 +04:00
unsigned long long start ;
2005-04-17 02:20:36 +04:00
2006-03-27 13:17:48 +04:00
if ( sscanf ( argv [ 1 ] , " %llu " , & start ) ! = 1 )
2005-04-17 02:20:36 +04:00
return - EINVAL ;
if ( dm_get_device ( ti , argv [ 0 ] , start , sc - > stripe_width ,
dm_table_get_mode ( ti - > table ) ,
& sc - > stripe [ stripe ] . dev ) )
return - ENXIO ;
sc - > stripe [ stripe ] . physical_start = start ;
2008-02-08 05:11:22 +03:00
2005-04-17 02:20:36 +04:00
return 0 ;
}
/*
* Construct a striped mapping .
* < number of stripes > < chunk size ( 2 ^ ^ n ) > [ < dev_path > < offset > ] +
*/
static int stripe_ctr ( struct dm_target * ti , unsigned int argc , char * * argv )
{
struct stripe_c * sc ;
sector_t width ;
uint32_t stripes ;
uint32_t chunk_size ;
char * end ;
int r ;
unsigned int i ;
if ( argc < 2 ) {
2006-06-26 11:27:35 +04:00
ti - > error = " Not enough arguments " ;
2005-04-17 02:20:36 +04:00
return - EINVAL ;
}
stripes = simple_strtoul ( argv [ 0 ] , & end , 10 ) ;
if ( * end ) {
2006-06-26 11:27:35 +04:00
ti - > error = " Invalid stripe count " ;
2005-04-17 02:20:36 +04:00
return - EINVAL ;
}
chunk_size = simple_strtoul ( argv [ 1 ] , & end , 10 ) ;
if ( * end ) {
2006-06-26 11:27:35 +04:00
ti - > error = " Invalid chunk_size " ;
2005-04-17 02:20:36 +04:00
return - EINVAL ;
}
/*
* chunk_size is a power of two
*/
2007-10-20 01:38:44 +04:00
if ( ! is_power_of_2 ( chunk_size ) | |
2005-04-17 02:20:36 +04:00
( chunk_size < ( PAGE_SIZE > > SECTOR_SHIFT ) ) ) {
2006-06-26 11:27:35 +04:00
ti - > error = " Invalid chunk size " ;
2005-04-17 02:20:36 +04:00
return - EINVAL ;
}
2006-03-27 13:18:01 +04:00
if ( ti - > len & ( chunk_size - 1 ) ) {
2006-06-26 11:27:35 +04:00
ti - > error = " Target length not divisible by "
2006-03-17 10:04:03 +03:00
" chunk size " ;
return - EINVAL ;
}
2005-04-17 02:20:36 +04:00
width = ti - > len ;
if ( sector_div ( width , stripes ) ) {
2006-06-26 11:27:35 +04:00
ti - > error = " Target length not divisible by "
2005-04-17 02:20:36 +04:00
" number of stripes " ;
return - EINVAL ;
}
/*
* Do we have enough arguments for that many stripes ?
*/
if ( argc ! = ( 2 + 2 * stripes ) ) {
2006-06-26 11:27:35 +04:00
ti - > error = " Not enough destinations "
2005-04-17 02:20:36 +04:00
" specified " ;
return - EINVAL ;
}
sc = alloc_context ( stripes ) ;
if ( ! sc ) {
2006-06-26 11:27:35 +04:00
ti - > error = " Memory allocation for striped context "
2005-04-17 02:20:36 +04:00
" failed " ;
return - ENOMEM ;
}
2008-02-08 05:11:22 +03:00
INIT_WORK ( & sc - > kstriped_ws , trigger_event ) ;
/* Set pointer to dm target; used in trigger_event */
sc - > ti = ti ;
2005-04-17 02:20:36 +04:00
sc - > stripes = stripes ;
sc - > stripe_width = width ;
ti - > split_io = chunk_size ;
2009-06-22 13:12:22 +04:00
ti - > num_flush_requests = stripes ;
2005-04-17 02:20:36 +04:00
sc - > chunk_mask = ( ( sector_t ) chunk_size ) - 1 ;
for ( sc - > chunk_shift = 0 ; chunk_size ; sc - > chunk_shift + + )
chunk_size > > = 1 ;
sc - > chunk_shift - - ;
/*
* Get the stripe destinations .
*/
for ( i = 0 ; i < stripes ; i + + ) {
argv + = 2 ;
r = get_stripe ( ti , sc , i , argv ) ;
if ( r < 0 ) {
2006-06-26 11:27:35 +04:00
ti - > error = " Couldn't parse stripe destination " ;
2005-04-17 02:20:36 +04:00
while ( i - - )
dm_put_device ( ti , sc - > stripe [ i ] . dev ) ;
kfree ( sc ) ;
return r ;
}
2008-02-08 05:11:22 +03:00
atomic_set ( & ( sc - > stripe [ i ] . error_count ) , 0 ) ;
2005-04-17 02:20:36 +04:00
}
ti - > private = sc ;
2008-02-08 05:11:22 +03:00
2005-04-17 02:20:36 +04:00
return 0 ;
}
static void stripe_dtr ( struct dm_target * ti )
{
unsigned int i ;
struct stripe_c * sc = ( struct stripe_c * ) ti - > private ;
for ( i = 0 ; i < sc - > stripes ; i + + )
dm_put_device ( ti , sc - > stripe [ i ] . dev ) ;
2008-02-08 05:11:22 +03:00
flush_workqueue ( kstriped ) ;
2005-04-17 02:20:36 +04:00
kfree ( sc ) ;
}
static int stripe_map ( struct dm_target * ti , struct bio * bio ,
union map_info * map_context )
{
struct stripe_c * sc = ( struct stripe_c * ) ti - > private ;
2009-06-22 13:12:22 +04:00
sector_t offset , chunk ;
uint32_t stripe ;
2005-04-17 02:20:36 +04:00
2009-06-22 13:12:22 +04:00
if ( unlikely ( bio_empty_barrier ( bio ) ) ) {
BUG_ON ( map_context - > flush_request > = sc - > stripes ) ;
bio - > bi_bdev = sc - > stripe [ map_context - > flush_request ] . dev - > bdev ;
return DM_MAPIO_REMAPPED ;
}
offset = bio - > bi_sector - ti - > begin ;
chunk = offset > > sc - > chunk_shift ;
stripe = sector_div ( chunk , sc - > stripes ) ;
2005-04-17 02:20:36 +04:00
bio - > bi_bdev = sc - > stripe [ stripe ] . dev - > bdev ;
bio - > bi_sector = sc - > stripe [ stripe ] . physical_start +
( chunk < < sc - > chunk_shift ) + ( offset & sc - > chunk_mask ) ;
2006-12-08 13:41:06 +03:00
return DM_MAPIO_REMAPPED ;
2005-04-17 02:20:36 +04:00
}
2008-02-08 05:11:24 +03:00
/*
* Stripe status :
*
* INFO
* # stripes [ stripe_name < stripe_name > ] [ group word count ]
* [ error count ' A | D ' < error count ' A | D ' > ]
*
* TABLE
* # stripes [ stripe chunk size ]
* [ stripe_name physical_start < stripe_name physical_start > ]
*
*/
2005-04-17 02:20:36 +04:00
static int stripe_status ( struct dm_target * ti ,
status_type_t type , char * result , unsigned int maxlen )
{
struct stripe_c * sc = ( struct stripe_c * ) ti - > private ;
2008-02-08 05:11:24 +03:00
char buffer [ sc - > stripes + 1 ] ;
2005-04-17 02:20:36 +04:00
unsigned int sz = 0 ;
unsigned int i ;
switch ( type ) {
case STATUSTYPE_INFO :
2008-02-08 05:11:24 +03:00
DMEMIT ( " %d " , sc - > stripes ) ;
for ( i = 0 ; i < sc - > stripes ; i + + ) {
DMEMIT ( " %s " , sc - > stripe [ i ] . dev - > name ) ;
buffer [ i ] = atomic_read ( & ( sc - > stripe [ i ] . error_count ) ) ?
' D ' : ' A ' ;
}
buffer [ i ] = ' \0 ' ;
DMEMIT ( " 1 %s " , buffer ) ;
2005-04-17 02:20:36 +04:00
break ;
case STATUSTYPE_TABLE :
2006-03-27 13:17:48 +04:00
DMEMIT ( " %d %llu " , sc - > stripes ,
( unsigned long long ) sc - > chunk_mask + 1 ) ;
2005-04-17 02:20:36 +04:00
for ( i = 0 ; i < sc - > stripes ; i + + )
2006-03-27 13:17:48 +04:00
DMEMIT ( " %s %llu " , sc - > stripe [ i ] . dev - > name ,
( unsigned long long ) sc - > stripe [ i ] . physical_start ) ;
2005-04-17 02:20:36 +04:00
break ;
}
return 0 ;
}
2008-02-08 05:11:22 +03:00
static int stripe_end_io ( struct dm_target * ti , struct bio * bio ,
int error , union map_info * map_context )
{
unsigned i ;
char major_minor [ 16 ] ;
struct stripe_c * sc = ti - > private ;
if ( ! error )
return 0 ; /* I/O complete */
if ( ( error = = - EWOULDBLOCK ) & & bio_rw_ahead ( bio ) )
return error ;
if ( error = = - EOPNOTSUPP )
return error ;
memset ( major_minor , 0 , sizeof ( major_minor ) ) ;
sprintf ( major_minor , " %d:%d " ,
2008-09-03 11:01:48 +04:00
MAJOR ( disk_devt ( bio - > bi_bdev - > bd_disk ) ) ,
MINOR ( disk_devt ( bio - > bi_bdev - > bd_disk ) ) ) ;
2008-02-08 05:11:22 +03:00
/*
* Test to see which stripe drive triggered the event
* and increment error count for all stripes on that device .
* If the error count for a given device exceeds the threshold
* value we will no longer trigger any further events .
*/
for ( i = 0 ; i < sc - > stripes ; i + + )
if ( ! strcmp ( sc - > stripe [ i ] . dev - > name , major_minor ) ) {
atomic_inc ( & ( sc - > stripe [ i ] . error_count ) ) ;
if ( atomic_read ( & ( sc - > stripe [ i ] . error_count ) ) <
DM_IO_ERROR_THRESHOLD )
queue_work ( kstriped , & sc - > kstriped_ws ) ;
}
return error ;
}
2009-06-22 13:12:33 +04:00
static int stripe_iterate_devices ( struct dm_target * ti ,
iterate_devices_callout_fn fn , void * data )
{
struct stripe_c * sc = ti - > private ;
int ret = 0 ;
unsigned i = 0 ;
do
ret = fn ( ti , sc - > stripe [ i ] . dev ,
sc - > stripe [ i ] . physical_start , data ) ;
while ( ! ret & & + + i < sc - > stripes ) ;
return ret ;
}
2005-04-17 02:20:36 +04:00
static struct target_type stripe_target = {
. name = " striped " ,
2009-06-22 13:12:33 +04:00
. version = { 1 , 2 , 0 } ,
2005-04-17 02:20:36 +04:00
. module = THIS_MODULE ,
. ctr = stripe_ctr ,
. dtr = stripe_dtr ,
. map = stripe_map ,
2008-02-08 05:11:22 +03:00
. end_io = stripe_end_io ,
2005-04-17 02:20:36 +04:00
. status = stripe_status ,
2009-06-22 13:12:33 +04:00
. iterate_devices = stripe_iterate_devices ,
2005-04-17 02:20:36 +04:00
} ;
int __init dm_stripe_init ( void )
{
int r ;
r = dm_register_target ( & stripe_target ) ;
2008-11-14 02:38:56 +03:00
if ( r < 0 ) {
2006-06-26 11:27:35 +04:00
DMWARN ( " target registration failed " ) ;
2008-11-14 02:38:56 +03:00
return r ;
}
2005-04-17 02:20:36 +04:00
2008-02-08 05:11:22 +03:00
kstriped = create_singlethread_workqueue ( " kstriped " ) ;
if ( ! kstriped ) {
DMERR ( " failed to create workqueue kstriped " ) ;
dm_unregister_target ( & stripe_target ) ;
return - ENOMEM ;
}
2005-04-17 02:20:36 +04:00
return r ;
}
void dm_stripe_exit ( void )
{
2009-01-06 06:04:58 +03:00
dm_unregister_target ( & stripe_target ) ;
2008-02-08 05:11:22 +03:00
destroy_workqueue ( kstriped ) ;
2005-04-17 02:20:36 +04:00
return ;
}