2012-10-12 21:02:13 +01:00
/*
* Copyright ( C ) 2012 Red Hat , Inc .
*
* This file is released under the GPL .
*/
# include "dm.h"
# include "dm-bio-prison.h"
# include <linux/spinlock.h>
# include <linux/mempool.h>
# include <linux/module.h>
# include <linux/slab.h>
/*----------------------------------------------------------------*/
2014-06-05 15:23:09 +01:00
struct bucket {
2012-10-12 21:02:13 +01:00
spinlock_t lock ;
2014-06-05 15:23:09 +01:00
struct hlist_head cells ;
} ;
struct dm_bio_prison {
2012-10-12 21:02:13 +01:00
mempool_t * cell_pool ;
unsigned nr_buckets ;
unsigned hash_mask ;
2014-06-05 15:23:09 +01:00
struct bucket * buckets ;
2012-10-12 21:02:13 +01:00
} ;
/*----------------------------------------------------------------*/
static uint32_t calc_nr_buckets ( unsigned nr_cells )
{
uint32_t n = 128 ;
nr_cells / = 4 ;
nr_cells = min ( nr_cells , 8192u ) ;
while ( n < nr_cells )
n < < = 1 ;
return n ;
}
static struct kmem_cache * _cell_cache ;
2014-06-05 15:23:09 +01:00
static void init_bucket ( struct bucket * b )
{
spin_lock_init ( & b - > lock ) ;
INIT_HLIST_HEAD ( & b - > cells ) ;
}
2012-10-12 21:02:13 +01:00
/*
* @ nr_cells should be the number of cells you want in use _concurrently_ .
* Don ' t confuse it with the number of distinct keys .
*/
struct dm_bio_prison * dm_bio_prison_create ( unsigned nr_cells )
{
unsigned i ;
uint32_t nr_buckets = calc_nr_buckets ( nr_cells ) ;
size_t len = sizeof ( struct dm_bio_prison ) +
2014-06-05 15:23:09 +01:00
( sizeof ( struct bucket ) * nr_buckets ) ;
2012-10-12 21:02:13 +01:00
struct dm_bio_prison * prison = kmalloc ( len , GFP_KERNEL ) ;
if ( ! prison )
return NULL ;
prison - > cell_pool = mempool_create_slab_pool ( nr_cells , _cell_cache ) ;
if ( ! prison - > cell_pool ) {
kfree ( prison ) ;
return NULL ;
}
prison - > nr_buckets = nr_buckets ;
prison - > hash_mask = nr_buckets - 1 ;
2014-06-05 15:23:09 +01:00
prison - > buckets = ( struct bucket * ) ( prison + 1 ) ;
2012-10-12 21:02:13 +01:00
for ( i = 0 ; i < nr_buckets ; i + + )
2014-06-05 15:23:09 +01:00
init_bucket ( prison - > buckets + i ) ;
2012-10-12 21:02:13 +01:00
return prison ;
}
EXPORT_SYMBOL_GPL ( dm_bio_prison_create ) ;
void dm_bio_prison_destroy ( struct dm_bio_prison * prison )
{
mempool_destroy ( prison - > cell_pool ) ;
kfree ( prison ) ;
}
EXPORT_SYMBOL_GPL ( dm_bio_prison_destroy ) ;
2013-03-01 22:45:50 +00:00
struct dm_bio_prison_cell * dm_bio_prison_alloc_cell ( struct dm_bio_prison * prison , gfp_t gfp )
{
return mempool_alloc ( prison - > cell_pool , gfp ) ;
}
EXPORT_SYMBOL_GPL ( dm_bio_prison_alloc_cell ) ;
void dm_bio_prison_free_cell ( struct dm_bio_prison * prison ,
struct dm_bio_prison_cell * cell )
{
mempool_free ( cell , prison - > cell_pool ) ;
}
EXPORT_SYMBOL_GPL ( dm_bio_prison_free_cell ) ;
2012-10-12 21:02:13 +01:00
static uint32_t hash_key ( struct dm_bio_prison * prison , struct dm_cell_key * key )
{
const unsigned long BIG_PRIME = 4294967291UL ;
uint64_t hash = key - > block * BIG_PRIME ;
return ( uint32_t ) ( hash & prison - > hash_mask ) ;
}
static int keys_equal ( struct dm_cell_key * lhs , struct dm_cell_key * rhs )
{
return ( lhs - > virtual = = rhs - > virtual ) & &
( lhs - > dev = = rhs - > dev ) & &
( lhs - > block = = rhs - > block ) ;
}
2014-06-05 15:23:09 +01:00
static struct bucket * get_bucket ( struct dm_bio_prison * prison ,
struct dm_cell_key * key )
{
return prison - > buckets + hash_key ( prison , key ) ;
}
static struct dm_bio_prison_cell * __search_bucket ( struct bucket * b ,
2012-10-12 21:02:13 +01:00
struct dm_cell_key * key )
{
struct dm_bio_prison_cell * cell ;
2014-06-05 15:23:09 +01:00
hlist_for_each_entry ( cell , & b - > cells , list )
2012-10-12 21:02:13 +01:00
if ( keys_equal ( & cell - > key , key ) )
return cell ;
return NULL ;
}
2014-06-05 15:23:09 +01:00
static void __setup_new_cell ( struct bucket * b ,
2013-03-01 22:45:50 +00:00
struct dm_cell_key * key ,
struct bio * holder ,
struct dm_bio_prison_cell * cell )
2012-10-12 21:02:13 +01:00
{
2013-03-01 22:45:50 +00:00
memcpy ( & cell - > key , key , sizeof ( cell - > key ) ) ;
cell - > holder = holder ;
bio_list_init ( & cell - > bios ) ;
2014-06-05 15:23:09 +01:00
hlist_add_head ( & cell - > list , & b - > cells ) ;
2013-03-01 22:45:50 +00:00
}
2012-10-12 21:02:13 +01:00
2014-06-05 15:23:09 +01:00
static int __bio_detain ( struct bucket * b ,
2013-03-01 22:45:50 +00:00
struct dm_cell_key * key ,
struct bio * inmate ,
struct dm_bio_prison_cell * cell_prealloc ,
struct dm_bio_prison_cell * * cell_result )
{
struct dm_bio_prison_cell * cell ;
2012-10-12 21:02:13 +01:00
2014-06-05 15:23:09 +01:00
cell = __search_bucket ( b , key ) ;
2012-10-12 21:02:13 +01:00
if ( cell ) {
2013-03-01 22:45:50 +00:00
if ( inmate )
bio_list_add ( & cell - > bios , inmate ) ;
* cell_result = cell ;
return 1 ;
2012-10-12 21:02:13 +01:00
}
2014-06-05 15:23:09 +01:00
__setup_new_cell ( b , key , inmate , cell_prealloc ) ;
2013-03-01 22:45:50 +00:00
* cell_result = cell_prealloc ;
return 0 ;
}
2012-10-12 21:02:13 +01:00
2013-03-01 22:45:50 +00:00
static int bio_detain ( struct dm_bio_prison * prison ,
struct dm_cell_key * key ,
struct bio * inmate ,
struct dm_bio_prison_cell * cell_prealloc ,
struct dm_bio_prison_cell * * cell_result )
{
int r ;
unsigned long flags ;
2014-06-05 15:23:09 +01:00
struct bucket * b = get_bucket ( prison , key ) ;
2012-10-12 21:02:13 +01:00
2014-06-05 15:23:09 +01:00
spin_lock_irqsave ( & b - > lock , flags ) ;
r = __bio_detain ( b , key , inmate , cell_prealloc , cell_result ) ;
spin_unlock_irqrestore ( & b - > lock , flags ) ;
2012-10-12 21:02:13 +01:00
return r ;
}
2013-03-01 22:45:50 +00:00
int dm_bio_detain ( struct dm_bio_prison * prison ,
struct dm_cell_key * key ,
struct bio * inmate ,
struct dm_bio_prison_cell * cell_prealloc ,
struct dm_bio_prison_cell * * cell_result )
{
return bio_detain ( prison , key , inmate , cell_prealloc , cell_result ) ;
}
2012-10-12 21:02:13 +01:00
EXPORT_SYMBOL_GPL ( dm_bio_detain ) ;
2013-03-01 22:45:51 +00:00
int dm_get_cell ( struct dm_bio_prison * prison ,
struct dm_cell_key * key ,
struct dm_bio_prison_cell * cell_prealloc ,
struct dm_bio_prison_cell * * cell_result )
{
return bio_detain ( prison , key , NULL , cell_prealloc , cell_result ) ;
}
EXPORT_SYMBOL_GPL ( dm_get_cell ) ;
2012-10-12 21:02:13 +01:00
/*
* @ inmates must have been initialised prior to this call
*/
2013-03-01 22:45:50 +00:00
static void __cell_release ( struct dm_bio_prison_cell * cell ,
struct bio_list * inmates )
2012-10-12 21:02:13 +01:00
{
hlist_del ( & cell - > list ) ;
if ( inmates ) {
2013-03-01 22:45:50 +00:00
if ( cell - > holder )
bio_list_add ( inmates , cell - > holder ) ;
2012-10-12 21:02:13 +01:00
bio_list_merge ( inmates , & cell - > bios ) ;
}
}
2013-03-01 22:45:50 +00:00
void dm_cell_release ( struct dm_bio_prison * prison ,
struct dm_bio_prison_cell * cell ,
struct bio_list * bios )
2012-10-12 21:02:13 +01:00
{
unsigned long flags ;
2014-06-05 15:23:09 +01:00
struct bucket * b = get_bucket ( prison , & cell - > key ) ;
2012-10-12 21:02:13 +01:00
2014-06-05 15:23:09 +01:00
spin_lock_irqsave ( & b - > lock , flags ) ;
2012-10-12 21:02:13 +01:00
__cell_release ( cell , bios ) ;
2014-06-05 15:23:09 +01:00
spin_unlock_irqrestore ( & b - > lock , flags ) ;
2012-10-12 21:02:13 +01:00
}
EXPORT_SYMBOL_GPL ( dm_cell_release ) ;
/*
* Sometimes we don ' t want the holder , just the additional bios .
*/
2013-03-01 22:45:50 +00:00
static void __cell_release_no_holder ( struct dm_bio_prison_cell * cell ,
struct bio_list * inmates )
2012-10-12 21:02:13 +01:00
{
hlist_del ( & cell - > list ) ;
bio_list_merge ( inmates , & cell - > bios ) ;
}
2013-03-01 22:45:50 +00:00
void dm_cell_release_no_holder ( struct dm_bio_prison * prison ,
struct dm_bio_prison_cell * cell ,
struct bio_list * inmates )
2012-10-12 21:02:13 +01:00
{
unsigned long flags ;
2014-06-05 15:23:09 +01:00
struct bucket * b = get_bucket ( prison , & cell - > key ) ;
2012-10-12 21:02:13 +01:00
2014-06-05 15:23:09 +01:00
spin_lock_irqsave ( & b - > lock , flags ) ;
2012-10-12 21:02:13 +01:00
__cell_release_no_holder ( cell , inmates ) ;
2014-06-05 15:23:09 +01:00
spin_unlock_irqrestore ( & b - > lock , flags ) ;
2012-10-12 21:02:13 +01:00
}
EXPORT_SYMBOL_GPL ( dm_cell_release_no_holder ) ;
2013-03-01 22:45:50 +00:00
void dm_cell_error ( struct dm_bio_prison * prison ,
2014-05-22 14:32:51 -04:00
struct dm_bio_prison_cell * cell , int error )
2012-10-12 21:02:13 +01:00
{
struct bio_list bios ;
struct bio * bio ;
bio_list_init ( & bios ) ;
2014-06-05 15:23:09 +01:00
dm_cell_release ( prison , cell , & bios ) ;
2012-10-12 21:02:13 +01:00
while ( ( bio = bio_list_pop ( & bios ) ) )
2014-05-22 14:32:51 -04:00
bio_endio ( bio , error ) ;
2012-10-12 21:02:13 +01:00
}
EXPORT_SYMBOL_GPL ( dm_cell_error ) ;
/*----------------------------------------------------------------*/
# define DEFERRED_SET_SIZE 64
struct dm_deferred_entry {
struct dm_deferred_set * ds ;
unsigned count ;
struct list_head work_items ;
} ;
struct dm_deferred_set {
spinlock_t lock ;
unsigned current_entry ;
unsigned sweeper ;
struct dm_deferred_entry entries [ DEFERRED_SET_SIZE ] ;
} ;
struct dm_deferred_set * dm_deferred_set_create ( void )
{
int i ;
struct dm_deferred_set * ds ;
ds = kmalloc ( sizeof ( * ds ) , GFP_KERNEL ) ;
if ( ! ds )
return NULL ;
spin_lock_init ( & ds - > lock ) ;
ds - > current_entry = 0 ;
ds - > sweeper = 0 ;
for ( i = 0 ; i < DEFERRED_SET_SIZE ; i + + ) {
ds - > entries [ i ] . ds = ds ;
ds - > entries [ i ] . count = 0 ;
INIT_LIST_HEAD ( & ds - > entries [ i ] . work_items ) ;
}
return ds ;
}
EXPORT_SYMBOL_GPL ( dm_deferred_set_create ) ;
void dm_deferred_set_destroy ( struct dm_deferred_set * ds )
{
kfree ( ds ) ;
}
EXPORT_SYMBOL_GPL ( dm_deferred_set_destroy ) ;
struct dm_deferred_entry * dm_deferred_entry_inc ( struct dm_deferred_set * ds )
{
unsigned long flags ;
struct dm_deferred_entry * entry ;
spin_lock_irqsave ( & ds - > lock , flags ) ;
entry = ds - > entries + ds - > current_entry ;
entry - > count + + ;
spin_unlock_irqrestore ( & ds - > lock , flags ) ;
return entry ;
}
EXPORT_SYMBOL_GPL ( dm_deferred_entry_inc ) ;
static unsigned ds_next ( unsigned index )
{
return ( index + 1 ) % DEFERRED_SET_SIZE ;
}
static void __sweep ( struct dm_deferred_set * ds , struct list_head * head )
{
while ( ( ds - > sweeper ! = ds - > current_entry ) & &
! ds - > entries [ ds - > sweeper ] . count ) {
list_splice_init ( & ds - > entries [ ds - > sweeper ] . work_items , head ) ;
ds - > sweeper = ds_next ( ds - > sweeper ) ;
}
if ( ( ds - > sweeper = = ds - > current_entry ) & & ! ds - > entries [ ds - > sweeper ] . count )
list_splice_init ( & ds - > entries [ ds - > sweeper ] . work_items , head ) ;
}
void dm_deferred_entry_dec ( struct dm_deferred_entry * entry , struct list_head * head )
{
unsigned long flags ;
spin_lock_irqsave ( & entry - > ds - > lock , flags ) ;
BUG_ON ( ! entry - > count ) ;
- - entry - > count ;
__sweep ( entry - > ds , head ) ;
spin_unlock_irqrestore ( & entry - > ds - > lock , flags ) ;
}
EXPORT_SYMBOL_GPL ( dm_deferred_entry_dec ) ;
/*
* Returns 1 if deferred or 0 if no pending items to delay job .
*/
int dm_deferred_set_add_work ( struct dm_deferred_set * ds , struct list_head * work )
{
int r = 1 ;
unsigned long flags ;
unsigned next_entry ;
spin_lock_irqsave ( & ds - > lock , flags ) ;
if ( ( ds - > sweeper = = ds - > current_entry ) & &
! ds - > entries [ ds - > current_entry ] . count )
r = 0 ;
else {
list_add ( work , & ds - > entries [ ds - > current_entry ] . work_items ) ;
next_entry = ds_next ( ds - > current_entry ) ;
if ( ! ds - > entries [ next_entry ] . count )
ds - > current_entry = next_entry ;
}
spin_unlock_irqrestore ( & ds - > lock , flags ) ;
return r ;
}
EXPORT_SYMBOL_GPL ( dm_deferred_set_add_work ) ;
/*----------------------------------------------------------------*/
static int __init dm_bio_prison_init ( void )
{
_cell_cache = KMEM_CACHE ( dm_bio_prison_cell , 0 ) ;
if ( ! _cell_cache )
return - ENOMEM ;
return 0 ;
}
static void __exit dm_bio_prison_exit ( void )
{
kmem_cache_destroy ( _cell_cache ) ;
_cell_cache = NULL ;
}
/*
* module hooks
*/
module_init ( dm_bio_prison_init ) ;
module_exit ( dm_bio_prison_exit ) ;
MODULE_DESCRIPTION ( DM_NAME " bio prison " ) ;
MODULE_AUTHOR ( " Joe Thornber <dm-devel@redhat.com> " ) ;
MODULE_LICENSE ( " GPL " ) ;