2005-04-17 02:20:36 +04:00
/*
* Copyright ( c ) 2004 Topspin Communications . All rights reserved .
2005-07-27 22:45:20 +04:00
* Copyright ( c ) 2005 Sun Microsystems , Inc . All rights reserved .
2005-04-17 02:20:36 +04:00
*
* This software is available to you under a choice of one of two
* licenses . You may choose to be licensed under the terms of the GNU
* General Public License ( GPL ) Version 2 , available from the file
* COPYING in the main directory of this source tree , or the
* OpenIB . org BSD license below :
*
* Redistribution and use in source and binary forms , with or
* without modification , are permitted provided that the following
* conditions are met :
*
* - Redistributions of source code must retain the above
* copyright notice , this list of conditions and the following
* disclaimer .
*
* - Redistributions in binary form must reproduce the above
* copyright notice , this list of conditions and the following
* disclaimer in the documentation and / or other materials
* provided with the distribution .
*
* THE SOFTWARE IS PROVIDED " AS IS " , WITHOUT WARRANTY OF ANY KIND ,
* EXPRESS OR IMPLIED , INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY , FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT . IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM , DAMAGES OR OTHER LIABILITY , WHETHER IN AN
* ACTION OF CONTRACT , TORT OR OTHERWISE , ARISING FROM , OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE .
*/
# include <linux/errno.h>
# include <linux/spinlock.h>
2011-05-27 23:29:33 +04:00
# include <linux/export.h>
2005-04-17 02:20:36 +04:00
# include <linux/slab.h>
# include <linux/jhash.h>
# include <linux/kthread.h>
2005-08-26 00:40:04 +04:00
# include <rdma/ib_fmr_pool.h>
2005-04-17 02:20:36 +04:00
# include "core_priv.h"
2007-05-07 08:18:11 +04:00
# define PFX "fmr_pool: "
2005-04-17 02:20:36 +04:00
enum {
IB_FMR_MAX_REMAPS = 32 ,
IB_FMR_HASH_BITS = 8 ,
IB_FMR_HASH_SIZE = 1 < < IB_FMR_HASH_BITS ,
IB_FMR_HASH_MASK = IB_FMR_HASH_SIZE - 1
} ;
/*
* If an FMR is not in use , then the list member will point to either
* its pool ' s free_list ( if the FMR can be mapped again ; that is ,
2006-06-18 07:37:37 +04:00
* remap_count < pool - > max_remaps ) or its pool ' s dirty_list ( if the
2005-04-17 02:20:36 +04:00
* FMR needs to be unmapped before being remapped ) . In either of
* these cases it is a bug if the ref_count is not 0. In other words ,
* if ref_count is > 0 , then the list member must not be linked into
* either free_list or dirty_list .
*
* The cache_node member is used to link the FMR into a cache bucket
* ( if caching is enabled ) . This is independent of the reference
* count of the FMR . When a valid FMR is released , its ref_count is
* decremented , and if ref_count reaches 0 , the FMR is placed in
* either free_list or dirty_list as appropriate . However , it is not
* removed from the cache and may be " revived " if a call to
* ib_fmr_register_physical ( ) occurs before the FMR is remapped . In
* this case we just increment the ref_count and remove the FMR from
* free_list / dirty_list .
*
* Before we remap an FMR from free_list , we remove it from the cache
* ( to prevent another user from obtaining a stale FMR ) . When an FMR
* is released , we add it to the tail of the free list , so that our
* cache eviction policy is " least recently used. "
*
* All manipulation of ref_count , list and cache_node is protected by
* pool_lock to maintain consistency .
*/
struct ib_fmr_pool {
spinlock_t pool_lock ;
int pool_size ;
int max_pages ;
2006-06-18 07:37:37 +04:00
int max_remaps ;
2005-04-17 02:20:36 +04:00
int dirty_watermark ;
int dirty_len ;
struct list_head free_list ;
struct list_head dirty_list ;
struct hlist_head * cache_bucket ;
void ( * flush_function ) ( struct ib_fmr_pool * pool ,
void * arg ) ;
void * flush_arg ;
struct task_struct * thread ;
atomic_t req_ser ;
atomic_t flush_ser ;
wait_queue_head_t force_wait ;
} ;
static inline u32 ib_fmr_hash ( u64 first_page )
{
2005-04-17 02:26:10 +04:00
return jhash_2words ( ( u32 ) first_page , ( u32 ) ( first_page > > 32 ) , 0 ) &
( IB_FMR_HASH_SIZE - 1 ) ;
2005-04-17 02:20:36 +04:00
}
/* Caller must hold pool_lock */
static inline struct ib_pool_fmr * ib_fmr_cache_lookup ( struct ib_fmr_pool * pool ,
u64 * page_list ,
int page_list_len ,
u64 io_virtual_address )
{
struct hlist_head * bucket ;
struct ib_pool_fmr * fmr ;
struct hlist_node * pos ;
if ( ! pool - > cache_bucket )
return NULL ;
bucket = pool - > cache_bucket + ib_fmr_hash ( * page_list ) ;
hlist_for_each_entry ( fmr , pos , bucket , cache_node )
if ( io_virtual_address = = fmr - > io_virtual_address & &
page_list_len = = fmr - > page_list_len & &
! memcmp ( page_list , fmr - > page_list ,
page_list_len * sizeof * page_list ) )
return fmr ;
return NULL ;
}
static void ib_fmr_batch_release ( struct ib_fmr_pool * pool )
{
int ret ;
2008-02-26 21:27:31 +03:00
struct ib_pool_fmr * fmr ;
2005-04-17 02:20:36 +04:00
LIST_HEAD ( unmap_list ) ;
LIST_HEAD ( fmr_list ) ;
spin_lock_irq ( & pool - > pool_lock ) ;
list_for_each_entry ( fmr , & pool - > dirty_list , list ) {
hlist_del_init ( & fmr - > cache_node ) ;
fmr - > remap_count = 0 ;
list_add_tail ( & fmr - > fmr - > list , & fmr_list ) ;
# ifdef DEBUG
if ( fmr - > ref_count ! = 0 ) {
2007-08-29 17:36:22 +04:00
printk ( KERN_WARNING PFX " Unmapping FMR 0x%08x with ref count %d \n " ,
2005-04-17 02:20:36 +04:00
fmr , fmr - > ref_count ) ;
}
# endif
}
2008-04-17 08:09:26 +04:00
list_splice_init ( & pool - > dirty_list , & unmap_list ) ;
2005-04-17 02:20:36 +04:00
pool - > dirty_len = 0 ;
spin_unlock_irq ( & pool - > pool_lock ) ;
if ( list_empty ( & unmap_list ) ) {
return ;
}
ret = ib_unmap_fmr ( & fmr_list ) ;
if ( ret )
2007-08-29 17:36:22 +04:00
printk ( KERN_WARNING PFX " ib_unmap_fmr returned %d \n " , ret ) ;
2005-04-17 02:20:36 +04:00
spin_lock_irq ( & pool - > pool_lock ) ;
list_splice ( & unmap_list , & pool - > free_list ) ;
spin_unlock_irq ( & pool - > pool_lock ) ;
}
static int ib_fmr_cleanup_thread ( void * pool_ptr )
{
struct ib_fmr_pool * pool = pool_ptr ;
do {
2008-01-16 20:36:27 +03:00
if ( atomic_read ( & pool - > flush_ser ) - atomic_read ( & pool - > req_ser ) < 0 ) {
2005-04-17 02:20:36 +04:00
ib_fmr_batch_release ( pool ) ;
atomic_inc ( & pool - > flush_ser ) ;
wake_up_interruptible ( & pool - > force_wait ) ;
if ( pool - > flush_function )
pool - > flush_function ( pool , pool - > flush_arg ) ;
}
set_current_state ( TASK_INTERRUPTIBLE ) ;
2008-01-16 20:36:27 +03:00
if ( atomic_read ( & pool - > flush_ser ) - atomic_read ( & pool - > req_ser ) > = 0 & &
2005-04-17 02:20:36 +04:00
! kthread_should_stop ( ) )
schedule ( ) ;
__set_current_state ( TASK_RUNNING ) ;
} while ( ! kthread_should_stop ( ) ) ;
return 0 ;
}
/**
* ib_create_fmr_pool - Create an FMR pool
* @ pd : Protection domain for FMRs
* @ params : FMR pool parameters
*
* Create a pool of FMRs . Return value is pointer to new pool or
* error code if creation failed .
*/
struct ib_fmr_pool * ib_create_fmr_pool ( struct ib_pd * pd ,
struct ib_fmr_pool_param * params )
{
struct ib_device * device ;
struct ib_fmr_pool * pool ;
2006-06-18 07:37:37 +04:00
struct ib_device_attr * attr ;
2005-04-17 02:20:36 +04:00
int i ;
int ret ;
2006-06-18 07:37:37 +04:00
int max_remaps ;
2005-04-17 02:20:36 +04:00
if ( ! params )
return ERR_PTR ( - EINVAL ) ;
device = pd - > device ;
if ( ! device - > alloc_fmr | | ! device - > dealloc_fmr | |
! device - > map_phys_fmr | | ! device - > unmap_fmr ) {
2007-05-07 08:18:11 +04:00
printk ( KERN_INFO PFX " Device %s does not support FMRs \n " ,
2005-04-17 02:20:36 +04:00
device - > name ) ;
return ERR_PTR ( - ENOSYS ) ;
}
2006-06-18 07:37:37 +04:00
attr = kmalloc ( sizeof * attr , GFP_KERNEL ) ;
if ( ! attr ) {
2007-08-29 17:36:22 +04:00
printk ( KERN_WARNING PFX " couldn't allocate device attr struct \n " ) ;
2006-06-18 07:37:37 +04:00
return ERR_PTR ( - ENOMEM ) ;
}
ret = ib_query_device ( device , attr ) ;
if ( ret ) {
2007-08-29 17:36:22 +04:00
printk ( KERN_WARNING PFX " couldn't query device: %d \n " , ret ) ;
2006-06-18 07:37:37 +04:00
kfree ( attr ) ;
return ERR_PTR ( ret ) ;
}
if ( ! attr - > max_map_per_fmr )
max_remaps = IB_FMR_MAX_REMAPS ;
else
max_remaps = attr - > max_map_per_fmr ;
kfree ( attr ) ;
2005-04-17 02:20:36 +04:00
pool = kmalloc ( sizeof * pool , GFP_KERNEL ) ;
if ( ! pool ) {
2007-08-29 17:36:22 +04:00
printk ( KERN_WARNING PFX " couldn't allocate pool struct \n " ) ;
2005-04-17 02:20:36 +04:00
return ERR_PTR ( - ENOMEM ) ;
}
pool - > cache_bucket = NULL ;
pool - > flush_function = params - > flush_function ;
pool - > flush_arg = params - > flush_arg ;
INIT_LIST_HEAD ( & pool - > free_list ) ;
INIT_LIST_HEAD ( & pool - > dirty_list ) ;
if ( params - > cache ) {
pool - > cache_bucket =
kmalloc ( IB_FMR_HASH_SIZE * sizeof * pool - > cache_bucket ,
GFP_KERNEL ) ;
if ( ! pool - > cache_bucket ) {
2007-08-29 17:36:22 +04:00
printk ( KERN_WARNING PFX " Failed to allocate cache in pool \n " ) ;
2005-04-17 02:20:36 +04:00
ret = - ENOMEM ;
goto out_free_pool ;
}
for ( i = 0 ; i < IB_FMR_HASH_SIZE ; + + i )
INIT_HLIST_HEAD ( pool - > cache_bucket + i ) ;
}
pool - > pool_size = 0 ;
pool - > max_pages = params - > max_pages_per_fmr ;
2006-06-18 07:37:37 +04:00
pool - > max_remaps = max_remaps ;
2005-04-17 02:20:36 +04:00
pool - > dirty_watermark = params - > dirty_watermark ;
pool - > dirty_len = 0 ;
spin_lock_init ( & pool - > pool_lock ) ;
atomic_set ( & pool - > req_ser , 0 ) ;
atomic_set ( & pool - > flush_ser , 0 ) ;
init_waitqueue_head ( & pool - > force_wait ) ;
2007-10-31 00:57:43 +03:00
pool - > thread = kthread_run ( ib_fmr_cleanup_thread ,
pool ,
" ib_fmr(%s) " ,
device - > name ) ;
2005-04-17 02:20:36 +04:00
if ( IS_ERR ( pool - > thread ) ) {
2007-08-29 17:36:22 +04:00
printk ( KERN_WARNING PFX " couldn't start cleanup thread \n " ) ;
2005-04-17 02:20:36 +04:00
ret = PTR_ERR ( pool - > thread ) ;
goto out_free_pool ;
}
{
struct ib_pool_fmr * fmr ;
2007-02-17 01:41:14 +03:00
struct ib_fmr_attr fmr_attr = {
2006-02-02 21:43:45 +03:00
. max_pages = params - > max_pages_per_fmr ,
2006-06-18 07:37:37 +04:00
. max_maps = pool - > max_remaps ,
2006-02-02 21:43:45 +03:00
. page_shift = params - > page_shift
2005-04-17 02:20:36 +04:00
} ;
2008-01-29 13:56:18 +03:00
int bytes_per_fmr = sizeof * fmr ;
if ( pool - > cache_bucket )
bytes_per_fmr + = params - > max_pages_per_fmr * sizeof ( u64 ) ;
2005-04-17 02:20:36 +04:00
for ( i = 0 ; i < params - > pool_size ; + + i ) {
2008-01-29 13:56:18 +03:00
fmr = kmalloc ( bytes_per_fmr , GFP_KERNEL ) ;
2005-04-17 02:20:36 +04:00
if ( ! fmr ) {
2007-05-07 08:18:11 +04:00
printk ( KERN_WARNING PFX " failed to allocate fmr "
2007-08-29 17:36:22 +04:00
" struct for FMR %d \n " , i ) ;
2005-04-17 02:20:36 +04:00
goto out_fail ;
}
fmr - > pool = pool ;
fmr - > remap_count = 0 ;
fmr - > ref_count = 0 ;
INIT_HLIST_NODE ( & fmr - > cache_node ) ;
2007-02-17 01:41:14 +03:00
fmr - > fmr = ib_alloc_fmr ( pd , params - > access , & fmr_attr ) ;
2005-04-17 02:20:36 +04:00
if ( IS_ERR ( fmr - > fmr ) ) {
2007-05-07 08:18:11 +04:00
printk ( KERN_WARNING PFX " fmr_create failed "
2007-08-29 17:36:22 +04:00
" for FMR %d \n " , i ) ;
2005-04-17 02:20:36 +04:00
kfree ( fmr ) ;
goto out_fail ;
}
list_add_tail ( & fmr - > list , & pool - > free_list ) ;
+ + pool - > pool_size ;
}
}
return pool ;
out_free_pool :
kfree ( pool - > cache_bucket ) ;
kfree ( pool ) ;
return ERR_PTR ( ret ) ;
out_fail :
ib_destroy_fmr_pool ( pool ) ;
return ERR_PTR ( - ENOMEM ) ;
}
EXPORT_SYMBOL ( ib_create_fmr_pool ) ;
/**
* ib_destroy_fmr_pool - Free FMR pool
* @ pool : FMR pool to free
*
* Destroy an FMR pool and free all associated resources .
*/
2005-07-27 22:45:20 +04:00
void ib_destroy_fmr_pool ( struct ib_fmr_pool * pool )
2005-04-17 02:20:36 +04:00
{
struct ib_pool_fmr * fmr ;
struct ib_pool_fmr * tmp ;
2005-08-15 18:35:16 +04:00
LIST_HEAD ( fmr_list ) ;
2005-04-17 02:20:36 +04:00
int i ;
kthread_stop ( pool - > thread ) ;
ib_fmr_batch_release ( pool ) ;
i = 0 ;
list_for_each_entry_safe ( fmr , tmp , & pool - > free_list , list ) {
2008-02-26 21:27:31 +03:00
if ( fmr - > remap_count ) {
INIT_LIST_HEAD ( & fmr_list ) ;
list_add_tail ( & fmr - > fmr - > list , & fmr_list ) ;
ib_unmap_fmr ( & fmr_list ) ;
}
2005-04-17 02:20:36 +04:00
ib_dealloc_fmr ( fmr - > fmr ) ;
list_del ( & fmr - > list ) ;
kfree ( fmr ) ;
+ + i ;
}
if ( i < pool - > pool_size )
2007-08-29 17:36:22 +04:00
printk ( KERN_WARNING PFX " pool still has %d regions registered \n " ,
2005-04-17 02:20:36 +04:00
pool - > pool_size - i ) ;
kfree ( pool - > cache_bucket ) ;
kfree ( pool ) ;
}
EXPORT_SYMBOL ( ib_destroy_fmr_pool ) ;
/**
* ib_flush_fmr_pool - Invalidate all unmapped FMRs
* @ pool : FMR pool to flush
*
* Ensure that all unmapped FMRs are fully invalidated .
*/
int ib_flush_fmr_pool ( struct ib_fmr_pool * pool )
{
2008-02-26 21:27:53 +03:00
int serial ;
struct ib_pool_fmr * fmr , * next ;
/*
* The free_list holds FMRs that may have been used
* but have not been remapped enough times to be dirty .
* Put them on the dirty list now so that the cleanup
* thread will reap them too .
*/
spin_lock_irq ( & pool - > pool_lock ) ;
list_for_each_entry_safe ( fmr , next , & pool - > free_list , list ) {
if ( fmr - > remap_count > 0 )
list_move ( & fmr - > list , & pool - > dirty_list ) ;
}
spin_unlock_irq ( & pool - > pool_lock ) ;
2005-04-17 02:20:36 +04:00
2008-02-26 21:27:53 +03:00
serial = atomic_inc_return ( & pool - > req_ser ) ;
2005-04-17 02:20:36 +04:00
wake_up_process ( pool - > thread ) ;
if ( wait_event_interruptible ( pool - > force_wait ,
2006-12-12 22:50:19 +03:00
atomic_read ( & pool - > flush_ser ) - serial > = 0 ) )
2005-04-17 02:20:36 +04:00
return - EINTR ;
return 0 ;
}
EXPORT_SYMBOL ( ib_flush_fmr_pool ) ;
/**
* ib_fmr_pool_map_phys -
* @ pool : FMR pool to allocate FMR from
* @ page_list : List of pages to map
* @ list_len : Number of pages in @ page_list
* @ io_virtual_address : I / O virtual address for new FMR
*
* Map an FMR from an FMR pool .
*/
struct ib_pool_fmr * ib_fmr_pool_map_phys ( struct ib_fmr_pool * pool_handle ,
u64 * page_list ,
int list_len ,
2006-07-14 11:23:55 +04:00
u64 io_virtual_address )
2005-04-17 02:20:36 +04:00
{
struct ib_fmr_pool * pool = pool_handle ;
struct ib_pool_fmr * fmr ;
unsigned long flags ;
int result ;
if ( list_len < 1 | | list_len > pool - > max_pages )
return ERR_PTR ( - EINVAL ) ;
spin_lock_irqsave ( & pool - > pool_lock , flags ) ;
fmr = ib_fmr_cache_lookup ( pool ,
page_list ,
list_len ,
2006-07-14 11:23:55 +04:00
io_virtual_address ) ;
2005-04-17 02:20:36 +04:00
if ( fmr ) {
/* found in cache */
+ + fmr - > ref_count ;
if ( fmr - > ref_count = = 1 ) {
list_del ( & fmr - > list ) ;
}
spin_unlock_irqrestore ( & pool - > pool_lock , flags ) ;
return fmr ;
}
if ( list_empty ( & pool - > free_list ) ) {
spin_unlock_irqrestore ( & pool - > pool_lock , flags ) ;
return ERR_PTR ( - EAGAIN ) ;
}
fmr = list_entry ( pool - > free_list . next , struct ib_pool_fmr , list ) ;
list_del ( & fmr - > list ) ;
hlist_del_init ( & fmr - > cache_node ) ;
spin_unlock_irqrestore ( & pool - > pool_lock , flags ) ;
result = ib_map_phys_fmr ( fmr - > fmr , page_list , list_len ,
2006-07-14 11:23:55 +04:00
io_virtual_address ) ;
2005-04-17 02:20:36 +04:00
if ( result ) {
spin_lock_irqsave ( & pool - > pool_lock , flags ) ;
list_add ( & fmr - > list , & pool - > free_list ) ;
spin_unlock_irqrestore ( & pool - > pool_lock , flags ) ;
2007-05-07 08:18:11 +04:00
printk ( KERN_WARNING PFX " fmr_map returns %d \n " , result ) ;
2005-04-17 02:20:36 +04:00
return ERR_PTR ( result ) ;
}
+ + fmr - > remap_count ;
fmr - > ref_count = 1 ;
if ( pool - > cache_bucket ) {
2006-07-14 11:23:55 +04:00
fmr - > io_virtual_address = io_virtual_address ;
2005-04-17 02:20:36 +04:00
fmr - > page_list_len = list_len ;
memcpy ( fmr - > page_list , page_list , list_len * sizeof ( * page_list ) ) ;
spin_lock_irqsave ( & pool - > pool_lock , flags ) ;
hlist_add_head ( & fmr - > cache_node ,
pool - > cache_bucket + ib_fmr_hash ( fmr - > page_list [ 0 ] ) ) ;
spin_unlock_irqrestore ( & pool - > pool_lock , flags ) ;
}
return fmr ;
}
EXPORT_SYMBOL ( ib_fmr_pool_map_phys ) ;
/**
* ib_fmr_pool_unmap - Unmap FMR
* @ fmr : FMR to unmap
*
* Unmap an FMR . The FMR mapping may remain valid until the FMR is
* reused ( or until ib_flush_fmr_pool ( ) is called ) .
*/
int ib_fmr_pool_unmap ( struct ib_pool_fmr * fmr )
{
struct ib_fmr_pool * pool ;
unsigned long flags ;
pool = fmr - > pool ;
spin_lock_irqsave ( & pool - > pool_lock , flags ) ;
- - fmr - > ref_count ;
if ( ! fmr - > ref_count ) {
2006-06-18 07:37:37 +04:00
if ( fmr - > remap_count < pool - > max_remaps ) {
2005-04-17 02:20:36 +04:00
list_add_tail ( & fmr - > list , & pool - > free_list ) ;
} else {
list_add_tail ( & fmr - > list , & pool - > dirty_list ) ;
2008-01-16 20:36:27 +03:00
if ( + + pool - > dirty_len > = pool - > dirty_watermark ) {
atomic_inc ( & pool - > req_ser ) ;
wake_up_process ( pool - > thread ) ;
}
2005-04-17 02:20:36 +04:00
}
}
# ifdef DEBUG
if ( fmr - > ref_count < 0 )
2007-08-29 17:36:22 +04:00
printk ( KERN_WARNING PFX " FMR %p has ref count %d < 0 \n " ,
2005-04-17 02:20:36 +04:00
fmr , fmr - > ref_count ) ;
# endif
spin_unlock_irqrestore ( & pool - > pool_lock , flags ) ;
return 0 ;
}
EXPORT_SYMBOL ( ib_fmr_pool_unmap ) ;