2006-06-18 07:37:28 +04:00
/*
* Copyright ( c ) 2005 Voltaire Inc . All rights reserved .
* Copyright ( c ) 2002 - 2005 , Network Appliance , Inc . All rights reserved .
* Copyright ( c ) 1999 - 2005 , Mellanox Technologies , Inc . All rights reserved .
* Copyright ( c ) 2005 Intel Corporation . All rights reserved .
*
* This Software is licensed under one of the following licenses :
*
* 1 ) under the terms of the " Common Public License 1.0 " a copy of which is
* available from the Open Source Initiative , see
* http : //www.opensource.org/licenses/cpl.php.
*
* 2 ) under the terms of the " The BSD License " a copy of which is
* available from the Open Source Initiative , see
* http : //www.opensource.org/licenses/bsd-license.php.
*
* 3 ) under the terms of the " GNU General Public License (GPL) Version 2 " a
* copy of which is available from the Open Source Initiative , see
* http : //www.opensource.org/licenses/gpl-license.php.
*
* Licensee has the right to choose one of the above licenses .
*
* Redistributions of source code must retain the above copyright
* notice and one of the license notices .
*
* Redistributions in binary form must reproduce both the above copyright
* notice , one of the license notices in the documentation
* and / or other materials provided with the distribution .
*/
# include <linux/mutex.h>
# include <linux/inetdevice.h>
# include <linux/workqueue.h>
# include <linux/if_arp.h>
# include <net/arp.h>
# include <net/neighbour.h>
# include <net/route.h>
2006-07-31 07:44:19 +04:00
# include <net/netevent.h>
2006-06-18 07:37:28 +04:00
# include <rdma/ib_addr.h>
MODULE_AUTHOR ( " Sean Hefty " ) ;
MODULE_DESCRIPTION ( " IB Address Translation " ) ;
MODULE_LICENSE ( " Dual BSD/GPL " ) ;
struct addr_req {
struct list_head list ;
struct sockaddr src_addr ;
struct sockaddr dst_addr ;
struct rdma_dev_addr * addr ;
2006-10-31 22:12:59 +03:00
struct rdma_addr_client * client ;
2006-06-18 07:37:28 +04:00
void * context ;
void ( * callback ) ( int status , struct sockaddr * src_addr ,
struct rdma_dev_addr * addr , void * context ) ;
unsigned long timeout ;
int status ;
} ;
2006-11-22 17:57:56 +03:00
static void process_req ( struct work_struct * work ) ;
2006-06-18 07:37:28 +04:00
static DEFINE_MUTEX ( lock ) ;
static LIST_HEAD ( req_list ) ;
2006-11-22 17:57:56 +03:00
static DECLARE_DELAYED_WORK ( work , process_req ) ;
2006-06-18 07:37:28 +04:00
static struct workqueue_struct * addr_wq ;
2006-10-31 22:12:59 +03:00
void rdma_addr_register_client ( struct rdma_addr_client * client )
{
atomic_set ( & client - > refcount , 1 ) ;
init_completion ( & client - > comp ) ;
}
EXPORT_SYMBOL ( rdma_addr_register_client ) ;
static inline void put_client ( struct rdma_addr_client * client )
{
if ( atomic_dec_and_test ( & client - > refcount ) )
complete ( & client - > comp ) ;
}
void rdma_addr_unregister_client ( struct rdma_addr_client * client )
{
put_client ( client ) ;
wait_for_completion ( & client - > comp ) ;
}
EXPORT_SYMBOL ( rdma_addr_unregister_client ) ;
2006-08-04 01:02:42 +04:00
int rdma_copy_addr ( struct rdma_dev_addr * dev_addr , struct net_device * dev ,
const unsigned char * dst_dev_addr )
2006-06-18 07:37:28 +04:00
{
switch ( dev - > type ) {
case ARPHRD_INFINIBAND :
2006-08-04 01:02:42 +04:00
dev_addr - > dev_type = RDMA_NODE_IB_CA ;
break ;
case ARPHRD_ETHER :
dev_addr - > dev_type = RDMA_NODE_RNIC ;
2006-06-18 07:37:28 +04:00
break ;
default :
return - EADDRNOTAVAIL ;
}
memcpy ( dev_addr - > src_dev_addr , dev - > dev_addr , MAX_ADDR_LEN ) ;
memcpy ( dev_addr - > broadcast , dev - > broadcast , MAX_ADDR_LEN ) ;
if ( dst_dev_addr )
memcpy ( dev_addr - > dst_dev_addr , dst_dev_addr , MAX_ADDR_LEN ) ;
return 0 ;
}
2006-08-04 01:02:42 +04:00
EXPORT_SYMBOL ( rdma_copy_addr ) ;
2006-06-18 07:37:28 +04:00
int rdma_translate_ip ( struct sockaddr * addr , struct rdma_dev_addr * dev_addr )
{
struct net_device * dev ;
2006-09-27 09:17:09 +04:00
__be32 ip = ( ( struct sockaddr_in * ) addr ) - > sin_addr . s_addr ;
2006-06-18 07:37:28 +04:00
int ret ;
dev = ip_dev_find ( ip ) ;
if ( ! dev )
return - EADDRNOTAVAIL ;
2006-08-04 01:02:42 +04:00
ret = rdma_copy_addr ( dev_addr , dev , NULL ) ;
2006-06-18 07:37:28 +04:00
dev_put ( dev ) ;
return ret ;
}
EXPORT_SYMBOL ( rdma_translate_ip ) ;
static void set_timeout ( unsigned long time )
{
unsigned long delay ;
cancel_delayed_work ( & work ) ;
delay = time - jiffies ;
if ( ( long ) delay < = 0 )
delay = 1 ;
queue_delayed_work ( addr_wq , & work , delay ) ;
}
static void queue_req ( struct addr_req * req )
{
struct addr_req * temp_req ;
mutex_lock ( & lock ) ;
list_for_each_entry_reverse ( temp_req , & req_list , list ) {
2006-10-17 08:39:09 +04:00
if ( time_after_eq ( req - > timeout , temp_req - > timeout ) )
2006-06-18 07:37:28 +04:00
break ;
}
list_add ( & req - > list , & temp_req - > list ) ;
if ( req_list . next = = & req - > list )
set_timeout ( req - > timeout ) ;
mutex_unlock ( & lock ) ;
}
static void addr_send_arp ( struct sockaddr_in * dst_in )
{
struct rtable * rt ;
struct flowi fl ;
u32 dst_ip = dst_in - > sin_addr . s_addr ;
memset ( & fl , 0 , sizeof fl ) ;
fl . nl_u . ip4_u . daddr = dst_ip ;
if ( ip_route_output_key ( & rt , & fl ) )
return ;
arp_send ( ARPOP_REQUEST , ETH_P_ARP , rt - > rt_gateway , rt - > idev - > dev ,
rt - > rt_src , NULL , rt - > idev - > dev - > dev_addr , NULL ) ;
ip_rt_put ( rt ) ;
}
static int addr_resolve_remote ( struct sockaddr_in * src_in ,
struct sockaddr_in * dst_in ,
struct rdma_dev_addr * addr )
{
u32 src_ip = src_in - > sin_addr . s_addr ;
u32 dst_ip = dst_in - > sin_addr . s_addr ;
struct flowi fl ;
struct rtable * rt ;
struct neighbour * neigh ;
int ret ;
memset ( & fl , 0 , sizeof fl ) ;
fl . nl_u . ip4_u . daddr = dst_ip ;
fl . nl_u . ip4_u . saddr = src_ip ;
ret = ip_route_output_key ( & rt , & fl ) ;
if ( ret )
goto out ;
/* If the device does ARP internally, return 'done' */
if ( rt - > idev - > dev - > flags & IFF_NOARP ) {
2006-08-04 01:02:42 +04:00
rdma_copy_addr ( addr , rt - > idev - > dev , NULL ) ;
2006-06-18 07:37:28 +04:00
goto put ;
}
neigh = neigh_lookup ( & arp_tbl , & rt - > rt_gateway , rt - > idev - > dev ) ;
if ( ! neigh ) {
ret = - ENODATA ;
goto put ;
}
if ( ! ( neigh - > nud_state & NUD_VALID ) ) {
ret = - ENODATA ;
goto release ;
}
if ( ! src_ip ) {
src_in - > sin_family = dst_in - > sin_family ;
src_in - > sin_addr . s_addr = rt - > rt_src ;
}
2006-08-04 01:02:42 +04:00
ret = rdma_copy_addr ( addr , neigh - > dev , neigh - > ha ) ;
2006-06-18 07:37:28 +04:00
release :
neigh_release ( neigh ) ;
put :
ip_rt_put ( rt ) ;
out :
return ret ;
}
2006-11-22 17:57:56 +03:00
static void process_req ( struct work_struct * work )
2006-06-18 07:37:28 +04:00
{
struct addr_req * req , * temp_req ;
struct sockaddr_in * src_in , * dst_in ;
struct list_head done_list ;
INIT_LIST_HEAD ( & done_list ) ;
mutex_lock ( & lock ) ;
list_for_each_entry_safe ( req , temp_req , & req_list , list ) {
2006-11-24 13:32:34 +03:00
if ( req - > status = = - ENODATA ) {
2006-06-18 07:37:28 +04:00
src_in = ( struct sockaddr_in * ) & req - > src_addr ;
dst_in = ( struct sockaddr_in * ) & req - > dst_addr ;
req - > status = addr_resolve_remote ( src_in , dst_in ,
req - > addr ) ;
2006-11-24 13:32:34 +03:00
if ( req - > status & & time_after_eq ( jiffies , req - > timeout ) )
req - > status = - ETIMEDOUT ;
else if ( req - > status = = - ENODATA )
continue ;
2006-06-18 07:37:28 +04:00
}
2006-11-30 02:33:09 +03:00
list_move_tail ( & req - > list , & done_list ) ;
2006-06-18 07:37:28 +04:00
}
if ( ! list_empty ( & req_list ) ) {
req = list_entry ( req_list . next , struct addr_req , list ) ;
set_timeout ( req - > timeout ) ;
}
mutex_unlock ( & lock ) ;
list_for_each_entry_safe ( req , temp_req , & done_list , list ) {
list_del ( & req - > list ) ;
req - > callback ( req - > status , & req - > src_addr , req - > addr ,
req - > context ) ;
2006-10-31 22:12:59 +03:00
put_client ( req - > client ) ;
2006-06-18 07:37:28 +04:00
kfree ( req ) ;
}
}
static int addr_resolve_local ( struct sockaddr_in * src_in ,
struct sockaddr_in * dst_in ,
struct rdma_dev_addr * addr )
{
struct net_device * dev ;
u32 src_ip = src_in - > sin_addr . s_addr ;
2006-09-27 09:17:09 +04:00
__be32 dst_ip = dst_in - > sin_addr . s_addr ;
2006-06-18 07:37:28 +04:00
int ret ;
dev = ip_dev_find ( dst_ip ) ;
if ( ! dev )
return - EADDRNOTAVAIL ;
if ( ZERONET ( src_ip ) ) {
src_in - > sin_family = dst_in - > sin_family ;
src_in - > sin_addr . s_addr = dst_ip ;
2006-08-04 01:02:42 +04:00
ret = rdma_copy_addr ( addr , dev , dev - > dev_addr ) ;
2006-06-18 07:37:28 +04:00
} else if ( LOOPBACK ( src_ip ) ) {
ret = rdma_translate_ip ( ( struct sockaddr * ) dst_in , addr ) ;
if ( ! ret )
memcpy ( addr - > dst_dev_addr , dev - > dev_addr , MAX_ADDR_LEN ) ;
} else {
ret = rdma_translate_ip ( ( struct sockaddr * ) src_in , addr ) ;
if ( ! ret )
memcpy ( addr - > dst_dev_addr , dev - > dev_addr , MAX_ADDR_LEN ) ;
}
dev_put ( dev ) ;
return ret ;
}
2006-10-31 22:12:59 +03:00
int rdma_resolve_ip ( struct rdma_addr_client * client ,
struct sockaddr * src_addr , struct sockaddr * dst_addr ,
2006-06-18 07:37:28 +04:00
struct rdma_dev_addr * addr , int timeout_ms ,
void ( * callback ) ( int status , struct sockaddr * src_addr ,
struct rdma_dev_addr * addr , void * context ) ,
void * context )
{
struct sockaddr_in * src_in , * dst_in ;
struct addr_req * req ;
int ret = 0 ;
req = kmalloc ( sizeof * req , GFP_KERNEL ) ;
if ( ! req )
return - ENOMEM ;
memset ( req , 0 , sizeof * req ) ;
if ( src_addr )
memcpy ( & req - > src_addr , src_addr , ip_addr_size ( src_addr ) ) ;
memcpy ( & req - > dst_addr , dst_addr , ip_addr_size ( dst_addr ) ) ;
req - > addr = addr ;
req - > callback = callback ;
req - > context = context ;
2006-10-31 22:12:59 +03:00
req - > client = client ;
atomic_inc ( & client - > refcount ) ;
2006-06-18 07:37:28 +04:00
src_in = ( struct sockaddr_in * ) & req - > src_addr ;
dst_in = ( struct sockaddr_in * ) & req - > dst_addr ;
req - > status = addr_resolve_local ( src_in , dst_in , addr ) ;
if ( req - > status = = - EADDRNOTAVAIL )
req - > status = addr_resolve_remote ( src_in , dst_in , addr ) ;
switch ( req - > status ) {
case 0 :
req - > timeout = jiffies ;
queue_req ( req ) ;
break ;
case - ENODATA :
req - > timeout = msecs_to_jiffies ( timeout_ms ) + jiffies ;
queue_req ( req ) ;
addr_send_arp ( dst_in ) ;
break ;
default :
ret = req - > status ;
2006-10-31 22:12:59 +03:00
atomic_dec ( & client - > refcount ) ;
2006-06-18 07:37:28 +04:00
kfree ( req ) ;
break ;
}
return ret ;
}
EXPORT_SYMBOL ( rdma_resolve_ip ) ;
void rdma_addr_cancel ( struct rdma_dev_addr * addr )
{
struct addr_req * req , * temp_req ;
mutex_lock ( & lock ) ;
list_for_each_entry_safe ( req , temp_req , & req_list , list ) {
if ( req - > addr = = addr ) {
req - > status = - ECANCELED ;
req - > timeout = jiffies ;
2006-11-30 02:33:09 +03:00
list_move ( & req - > list , & req_list ) ;
2006-06-18 07:37:28 +04:00
set_timeout ( req - > timeout ) ;
break ;
}
}
mutex_unlock ( & lock ) ;
}
EXPORT_SYMBOL ( rdma_addr_cancel ) ;
2006-09-23 02:22:46 +04:00
static int netevent_callback ( struct notifier_block * self , unsigned long event ,
2006-07-31 07:44:19 +04:00
void * ctx )
2006-06-18 07:37:28 +04:00
{
2006-09-23 02:22:46 +04:00
if ( event = = NETEVENT_NEIGH_UPDATE ) {
2006-07-31 07:44:19 +04:00
struct neighbour * neigh = ctx ;
2006-06-18 07:37:28 +04:00
2007-01-24 04:03:17 +03:00
if ( neigh - > nud_state & NUD_VALID ) {
2006-07-31 07:44:19 +04:00
set_timeout ( jiffies ) ;
}
}
2006-06-18 07:37:28 +04:00
return 0 ;
}
2006-07-31 07:44:19 +04:00
static struct notifier_block nb = {
. notifier_call = netevent_callback
2006-06-18 07:37:28 +04:00
} ;
static int addr_init ( void )
{
2007-02-01 23:23:37 +03:00
addr_wq = create_singlethread_workqueue ( " ib_addr " ) ;
2006-06-18 07:37:28 +04:00
if ( ! addr_wq )
return - ENOMEM ;
2006-07-31 07:44:19 +04:00
register_netevent_notifier ( & nb ) ;
2006-06-18 07:37:28 +04:00
return 0 ;
}
static void addr_cleanup ( void )
{
2006-07-31 07:44:19 +04:00
unregister_netevent_notifier ( & nb ) ;
2006-06-18 07:37:28 +04:00
destroy_workqueue ( addr_wq ) ;
}
module_init ( addr_init ) ;
module_exit ( addr_cleanup ) ;