2005-04-17 02:20:36 +04:00
/*
linear . c : Multiple Devices driver for Linux
Copyright ( C ) 1994 - 96 Marc ZYNGIER
< zyngier @ ufr - info - p7 . ibp . fr > or
< maz @ gloups . fdn . fr >
Linear mode management functions .
This program is free software ; you can redistribute it and / or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation ; either version 2 , or ( at your option )
any later version .
You should have received a copy of the GNU General Public License
( for example / usr / src / linux / COPYING ) ; if not , write to the Free
Software Foundation , Inc . , 675 Mass Ave , Cambridge , MA 0213 9 , USA .
*/
# include <linux/module.h>
# include <linux/raid/md.h>
# include <linux/slab.h>
# include <linux/raid/linear.h>
# define MAJOR_NR MD_MAJOR
# define MD_DRIVER
# define MD_PERSONALITY
/*
* find which device holds a particular offset
*/
static inline dev_info_t * which_dev ( mddev_t * mddev , sector_t sector )
{
dev_info_t * hash ;
linear_conf_t * conf = mddev_to_conf ( mddev ) ;
sector_t block = sector > > 1 ;
/*
* sector_div ( a , b ) returns the remainer and sets a to a / b
*/
2005-09-10 03:23:47 +04:00
block > > = conf - > preshift ;
( void ) sector_div ( block , conf - > hash_spacing ) ;
2005-04-17 02:20:36 +04:00
hash = conf - > hash_table [ block ] ;
while ( ( sector > > 1 ) > = ( hash - > size + hash - > offset ) )
hash + + ;
return hash ;
}
/**
2005-09-10 03:23:47 +04:00
* linear_mergeable_bvec - - tell bio layer if two requests can be merged
2005-04-17 02:20:36 +04:00
* @ q : request queue
* @ bio : the buffer head that ' s been built up so far
* @ biovec : the request that could be merged to it .
*
* Return amount of bytes we can take at this offset
*/
2007-07-24 11:28:11 +04:00
static int linear_mergeable_bvec ( struct request_queue * q , struct bio * bio , struct bio_vec * biovec )
2005-04-17 02:20:36 +04:00
{
mddev_t * mddev = q - > queuedata ;
dev_info_t * dev0 ;
unsigned long maxsectors , bio_sectors = bio - > bi_size > > 9 ;
sector_t sector = bio - > bi_sector + get_start_sect ( bio - > bi_bdev ) ;
dev0 = which_dev ( mddev , sector ) ;
maxsectors = ( dev0 - > size < < 1 ) - ( sector - ( dev0 - > offset < < 1 ) ) ;
if ( maxsectors < bio_sectors )
maxsectors = 0 ;
else
maxsectors - = bio_sectors ;
if ( maxsectors < = ( PAGE_SIZE > > 9 ) & & bio_sectors = = 0 )
return biovec - > bv_len ;
/* The bytes available at this offset could be really big,
* so we cap at 2 ^ 31 to avoid overflow */
if ( maxsectors > ( 1 < < ( 31 - 9 ) ) )
return 1 < < 31 ;
return maxsectors < < 9 ;
}
2007-07-24 11:28:11 +04:00
static void linear_unplug ( struct request_queue * q )
2005-04-17 02:20:36 +04:00
{
mddev_t * mddev = q - > queuedata ;
linear_conf_t * conf = mddev_to_conf ( mddev ) ;
int i ;
for ( i = 0 ; i < mddev - > raid_disks ; i + + ) {
2007-07-24 11:28:11 +04:00
struct request_queue * r_queue = bdev_get_queue ( conf - > disks [ i ] . rdev - > bdev ) ;
2007-11-07 22:26:56 +03:00
blk_unplug ( r_queue ) ;
2005-04-17 02:20:36 +04:00
}
}
2006-10-03 12:15:53 +04:00
static int linear_congested ( void * data , int bits )
{
mddev_t * mddev = data ;
linear_conf_t * conf = mddev_to_conf ( mddev ) ;
int i , ret = 0 ;
for ( i = 0 ; i < mddev - > raid_disks & & ! ret ; i + + ) {
2007-07-24 11:28:11 +04:00
struct request_queue * q = bdev_get_queue ( conf - > disks [ i ] . rdev - > bdev ) ;
2006-10-03 12:15:53 +04:00
ret | = bdi_congested ( & q - > backing_dev_info , bits ) ;
}
return ret ;
}
2006-06-26 11:27:41 +04:00
static linear_conf_t * linear_conf ( mddev_t * mddev , int raid_disks )
2005-04-17 02:20:36 +04:00
{
linear_conf_t * conf ;
dev_info_t * * table ;
mdk_rdev_t * rdev ;
int i , nb_zone , cnt ;
2005-09-10 03:23:47 +04:00
sector_t min_spacing ;
2005-04-17 02:20:36 +04:00
sector_t curr_offset ;
struct list_head * tmp ;
2006-06-26 11:27:41 +04:00
conf = kzalloc ( sizeof ( * conf ) + raid_disks * sizeof ( dev_info_t ) ,
2005-04-17 02:20:36 +04:00
GFP_KERNEL ) ;
if ( ! conf )
2006-06-26 11:27:41 +04:00
return NULL ;
2005-04-17 02:20:36 +04:00
cnt = 0 ;
2006-06-26 11:27:41 +04:00
conf - > array_size = 0 ;
2005-04-17 02:20:36 +04:00
2008-02-06 12:39:59 +03:00
rdev_for_each ( rdev , tmp , mddev ) {
2005-04-17 02:20:36 +04:00
int j = rdev - > raid_disk ;
dev_info_t * disk = conf - > disks + j ;
2008-06-28 02:31:19 +04:00
if ( j < 0 | | j > = raid_disks | | disk - > rdev ) {
2005-04-17 02:20:36 +04:00
printk ( " linear: disk numbering problem. Aborting! \n " ) ;
goto out ;
}
disk - > rdev = rdev ;
blk_queue_stack_limits ( mddev - > queue ,
rdev - > bdev - > bd_disk - > queue ) ;
/* as we don't honour merge_bvec_fn, we must never risk
* violating it , so limit - > max_sector to one PAGE , as
* a one page request is never in violation .
*/
if ( rdev - > bdev - > bd_disk - > queue - > merge_bvec_fn & &
mddev - > queue - > max_sectors > ( PAGE_SIZE > > 9 ) )
blk_queue_max_sectors ( mddev - > queue , PAGE_SIZE > > 9 ) ;
disk - > size = rdev - > size ;
2006-06-26 11:27:41 +04:00
conf - > array_size + = rdev - > size ;
2005-04-17 02:20:36 +04:00
cnt + + ;
}
2006-06-26 11:27:41 +04:00
if ( cnt ! = raid_disks ) {
2005-04-17 02:20:36 +04:00
printk ( " linear: not enough drives present. Aborting! \n " ) ;
goto out ;
}
2006-08-05 23:14:07 +04:00
min_spacing = conf - > array_size ;
2005-09-10 03:23:47 +04:00
sector_div ( min_spacing , PAGE_SIZE / sizeof ( struct dev_info * ) ) ;
/* min_spacing is the minimum spacing that will fit the hash
* table in one PAGE . This may be much smaller than needed .
* We find the smallest non - terminal set of consecutive devices
* that is larger than min_spacing as use the size of that as
* the actual spacing
*/
2006-08-05 23:14:07 +04:00
conf - > hash_spacing = conf - > array_size ;
2005-09-10 03:23:47 +04:00
for ( i = 0 ; i < cnt - 1 ; i + + ) {
sector_t sz = 0 ;
int j ;
2007-03-17 00:38:04 +03:00
for ( j = i ; j < cnt - 1 & & sz < min_spacing ; j + + )
2005-09-10 03:23:47 +04:00
sz + = conf - > disks [ j ] . size ;
if ( sz > = min_spacing & & sz < conf - > hash_spacing )
conf - > hash_spacing = sz ;
}
/* hash_spacing may be too large for sector_div to work with,
* so we might need to pre - shift
*/
conf - > preshift = 0 ;
if ( sizeof ( sector_t ) > sizeof ( u32 ) ) {
sector_t space = conf - > hash_spacing ;
while ( space > ( sector_t ) ( ~ ( u32 ) 0 ) ) {
space > > = 1 ;
conf - > preshift + + ;
}
}
2005-04-17 02:20:36 +04:00
/*
* This code was restructured to work around a gcc - 2.95 .3 internal
* compiler error . Alter it with care .
*/
{
sector_t sz ;
unsigned round ;
unsigned long base ;
2006-06-26 11:27:41 +04:00
sz = conf - > array_size > > conf - > preshift ;
2005-09-10 03:23:47 +04:00
sz + = 1 ; /* force round-up */
base = conf - > hash_spacing > > conf - > preshift ;
2005-04-17 02:20:36 +04:00
round = sector_div ( sz , base ) ;
2005-09-10 03:23:47 +04:00
nb_zone = sz + ( round ? 1 : 0 ) ;
2005-04-17 02:20:36 +04:00
}
2005-09-10 03:23:47 +04:00
BUG_ON ( nb_zone > PAGE_SIZE / sizeof ( struct dev_info * ) ) ;
conf - > hash_table = kmalloc ( sizeof ( struct dev_info * ) * nb_zone ,
2005-04-17 02:20:36 +04:00
GFP_KERNEL ) ;
if ( ! conf - > hash_table )
goto out ;
/*
* Here we generate the linear hash table
2005-09-10 03:23:47 +04:00
* First calculate the device offsets .
2005-04-17 02:20:36 +04:00
*/
2005-09-10 03:23:47 +04:00
conf - > disks [ 0 ] . offset = 0 ;
2007-05-24 00:58:10 +04:00
for ( i = 1 ; i < raid_disks ; i + + )
2005-09-10 03:23:47 +04:00
conf - > disks [ i ] . offset =
conf - > disks [ i - 1 ] . offset +
conf - > disks [ i - 1 ] . size ;
2005-04-17 02:20:36 +04:00
table = conf - > hash_table ;
curr_offset = 0 ;
2005-09-10 03:23:47 +04:00
i = 0 ;
for ( curr_offset = 0 ;
2006-08-05 23:14:07 +04:00
curr_offset < conf - > array_size ;
2005-09-10 03:23:47 +04:00
curr_offset + = conf - > hash_spacing ) {
2005-04-17 02:20:36 +04:00
2007-05-24 00:58:10 +04:00
while ( i < raid_disks - 1 & &
2005-09-10 03:23:47 +04:00
curr_offset > = conf - > disks [ i + 1 ] . offset )
i + + ;
2005-04-17 02:20:36 +04:00
2005-09-10 03:23:47 +04:00
* table + + = conf - > disks + i ;
}
if ( conf - > preshift ) {
conf - > hash_spacing > > = conf - > preshift ;
/* round hash_spacing up so that when we divide by it,
* we err on the side of " too-low " , which is safest .
2005-04-17 02:20:36 +04:00
*/
2005-09-10 03:23:47 +04:00
conf - > hash_spacing + + ;
2005-04-17 02:20:36 +04:00
}
2005-09-10 03:23:47 +04:00
BUG_ON ( table - conf - > hash_table > nb_zone ) ;
2005-04-17 02:20:36 +04:00
2006-06-26 11:27:41 +04:00
return conf ;
out :
kfree ( conf ) ;
return NULL ;
}
static int linear_run ( mddev_t * mddev )
{
linear_conf_t * conf ;
2008-05-15 03:05:54 +04:00
mddev - > queue - > queue_lock = & mddev - > queue - > __queue_lock ;
2006-06-26 11:27:41 +04:00
conf = linear_conf ( mddev , mddev - > raid_disks ) ;
if ( ! conf )
return 1 ;
mddev - > private = conf ;
mddev - > array_size = conf - > array_size ;
2005-04-17 02:20:36 +04:00
blk_queue_merge_bvec ( mddev - > queue , linear_mergeable_bvec ) ;
mddev - > queue - > unplug_fn = linear_unplug ;
2006-10-03 12:15:53 +04:00
mddev - > queue - > backing_dev_info . congested_fn = linear_congested ;
mddev - > queue - > backing_dev_info . congested_data = mddev ;
2005-04-17 02:20:36 +04:00
return 0 ;
2006-06-26 11:27:41 +04:00
}
2005-04-17 02:20:36 +04:00
2006-06-26 11:27:41 +04:00
static int linear_add ( mddev_t * mddev , mdk_rdev_t * rdev )
{
/* Adding a drive to a linear array allows the array to grow.
* It is permitted if the new drive has a matching superblock
* already on it , with raid_disk equal to raid_disks .
* It is achieved by creating a new linear_private_data structure
* and swapping it in in - place of the current one .
* The current one is never freed until the array is stopped .
* This avoids races .
*/
linear_conf_t * newconf ;
2007-05-24 00:58:10 +04:00
if ( rdev - > saved_raid_disk ! = mddev - > raid_disks )
2006-06-26 11:27:41 +04:00
return - EINVAL ;
2007-05-24 00:58:10 +04:00
rdev - > raid_disk = rdev - > saved_raid_disk ;
2006-06-26 11:27:41 +04:00
newconf = linear_conf ( mddev , mddev - > raid_disks + 1 ) ;
if ( ! newconf )
return - ENOMEM ;
newconf - > prev = mddev_to_conf ( mddev ) ;
mddev - > private = newconf ;
mddev - > raid_disks + + ;
mddev - > array_size = newconf - > array_size ;
set_capacity ( mddev - > gendisk , mddev - > array_size < < 1 ) ;
return 0 ;
2005-04-17 02:20:36 +04:00
}
static int linear_stop ( mddev_t * mddev )
{
linear_conf_t * conf = mddev_to_conf ( mddev ) ;
blk_sync_queue ( mddev - > queue ) ; /* the unplug fn references 'conf'*/
2006-06-26 11:27:41 +04:00
do {
linear_conf_t * t = conf - > prev ;
kfree ( conf - > hash_table ) ;
kfree ( conf ) ;
conf = t ;
} while ( conf ) ;
2005-04-17 02:20:36 +04:00
return 0 ;
}
2007-07-24 11:28:11 +04:00
static int linear_make_request ( struct request_queue * q , struct bio * bio )
2005-04-17 02:20:36 +04:00
{
2005-11-01 11:26:16 +03:00
const int rw = bio_data_dir ( bio ) ;
2005-04-17 02:20:36 +04:00
mddev_t * mddev = q - > queuedata ;
dev_info_t * tmp_dev ;
sector_t block ;
2005-09-10 03:23:41 +04:00
if ( unlikely ( bio_barrier ( bio ) ) ) {
2007-09-27 14:47:43 +04:00
bio_endio ( bio , - EOPNOTSUPP ) ;
2005-09-10 03:23:41 +04:00
return 0 ;
}
2005-11-01 11:26:16 +03:00
disk_stat_inc ( mddev - > gendisk , ios [ rw ] ) ;
disk_stat_add ( mddev - > gendisk , sectors [ rw ] , bio_sectors ( bio ) ) ;
2005-04-17 02:20:36 +04:00
tmp_dev = which_dev ( mddev , bio - > bi_sector ) ;
block = bio - > bi_sector > > 1 ;
if ( unlikely ( block > = ( tmp_dev - > size + tmp_dev - > offset )
| | block < tmp_dev - > offset ) ) {
char b [ BDEVNAME_SIZE ] ;
printk ( " linear_make_request: Block %llu out of bounds on "
" dev %s size %llu offset %llu \n " ,
( unsigned long long ) block ,
bdevname ( tmp_dev - > rdev - > bdev , b ) ,
( unsigned long long ) tmp_dev - > size ,
( unsigned long long ) tmp_dev - > offset ) ;
2007-09-27 14:47:43 +04:00
bio_io_error ( bio ) ;
2005-04-17 02:20:36 +04:00
return 0 ;
}
if ( unlikely ( bio - > bi_sector + ( bio - > bi_size > > 9 ) >
( tmp_dev - > offset + tmp_dev - > size ) < < 1 ) ) {
/* This bio crosses a device boundary, so we have to
* split it .
*/
struct bio_pair * bp ;
2005-05-17 08:53:15 +04:00
bp = bio_split ( bio , bio_split_pool ,
( ( tmp_dev - > offset + tmp_dev - > size ) < < 1 ) - bio - > bi_sector ) ;
2005-04-17 02:20:36 +04:00
if ( linear_make_request ( q , & bp - > bio1 ) )
generic_make_request ( & bp - > bio1 ) ;
if ( linear_make_request ( q , & bp - > bio2 ) )
generic_make_request ( & bp - > bio2 ) ;
bio_pair_release ( bp ) ;
return 0 ;
}
bio - > bi_bdev = tmp_dev - > rdev - > bdev ;
bio - > bi_sector = bio - > bi_sector - ( tmp_dev - > offset < < 1 ) + tmp_dev - > rdev - > data_offset ;
return 1 ;
}
static void linear_status ( struct seq_file * seq , mddev_t * mddev )
{
# undef MD_DEBUG
# ifdef MD_DEBUG
int j ;
linear_conf_t * conf = mddev_to_conf ( mddev ) ;
sector_t s = 0 ;
seq_printf ( seq , " " ) ;
2005-09-10 03:23:47 +04:00
for ( j = 0 ; j < mddev - > raid_disks ; j + + )
2005-04-17 02:20:36 +04:00
{
char b [ BDEVNAME_SIZE ] ;
s + = conf - > smallest_size ;
seq_printf ( seq , " [%s " ,
bdevname ( conf - > hash_table [ j ] [ 0 ] . rdev - > bdev , b ) ) ;
while ( s > conf - > hash_table [ j ] [ 0 ] . offset +
conf - > hash_table [ j ] [ 0 ] . size )
seq_printf ( seq , " /%s] " ,
bdevname ( conf - > hash_table [ j ] [ 1 ] . rdev - > bdev , b ) ) ;
else
seq_printf ( seq , " ] " ) ;
}
seq_printf ( seq , " \n " ) ;
# endif
seq_printf ( seq , " %dk rounding " , mddev - > chunk_size / 1024 ) ;
}
2006-01-06 11:20:36 +03:00
static struct mdk_personality linear_personality =
2005-04-17 02:20:36 +04:00
{
. name = " linear " ,
2006-01-06 11:20:36 +03:00
. level = LEVEL_LINEAR ,
2005-04-17 02:20:36 +04:00
. owner = THIS_MODULE ,
. make_request = linear_make_request ,
. run = linear_run ,
. stop = linear_stop ,
. status = linear_status ,
2006-06-26 11:27:41 +04:00
. hot_add_disk = linear_add ,
2005-04-17 02:20:36 +04:00
} ;
static int __init linear_init ( void )
{
2006-01-06 11:20:36 +03:00
return register_md_personality ( & linear_personality ) ;
2005-04-17 02:20:36 +04:00
}
static void linear_exit ( void )
{
2006-01-06 11:20:36 +03:00
unregister_md_personality ( & linear_personality ) ;
2005-04-17 02:20:36 +04:00
}
module_init ( linear_init ) ;
module_exit ( linear_exit ) ;
MODULE_LICENSE ( " GPL " ) ;
2006-01-06 11:20:51 +03:00
MODULE_ALIAS ( " md-personality-1 " ) ; /* LINEAR - deprecated*/
MODULE_ALIAS ( " md-linear " ) ;
2006-01-06 11:20:36 +03:00
MODULE_ALIAS ( " md-level--1 " ) ;