This patch improves on previously added support for ipv6 addresses. The code is consolidated to a single file and adds an api for use by dependent upper level drivers such as cxgb4i/iw_cxgb4 etc. Signed-off-by: Anish Bhatt <anish@chelsio.com> Signed-off-by: Deepak Singh <deepak.s@chelsio.com> Signed-off-by: David S. Miller <davem@davemloft.net>tirimbino
parent
5055c371bf
commit
b5a02f503c
@ -0,0 +1,314 @@ |
||||
/*
|
||||
* This file is part of the Chelsio T4 Ethernet driver for Linux. |
||||
* Copyright (C) 2003-2014 Chelsio Communications. All rights reserved. |
||||
* |
||||
* Written by Deepak (deepak.s@chelsio.com) |
||||
* |
||||
* This program is distributed in the hope that it will be useful, but WITHOUT |
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the LICENSE file included in this |
||||
* release for licensing terms and conditions. |
||||
*/ |
||||
|
||||
#include <linux/module.h> |
||||
#include <linux/netdevice.h> |
||||
#include <linux/jhash.h> |
||||
#include <linux/if_vlan.h> |
||||
#include <net/addrconf.h> |
||||
#include "cxgb4.h" |
||||
#include "clip_tbl.h" |
||||
|
||||
static inline unsigned int ipv4_clip_hash(struct clip_tbl *c, const u32 *key) |
||||
{ |
||||
unsigned int clipt_size_half = c->clipt_size / 2; |
||||
|
||||
return jhash_1word(*key, 0) % clipt_size_half; |
||||
} |
||||
|
||||
static inline unsigned int ipv6_clip_hash(struct clip_tbl *d, const u32 *key) |
||||
{ |
||||
unsigned int clipt_size_half = d->clipt_size / 2; |
||||
u32 xor = key[0] ^ key[1] ^ key[2] ^ key[3]; |
||||
|
||||
return clipt_size_half + |
||||
(jhash_1word(xor, 0) % clipt_size_half); |
||||
} |
||||
|
||||
static unsigned int clip_addr_hash(struct clip_tbl *ctbl, const u32 *addr, |
||||
int addr_len) |
||||
{ |
||||
return addr_len == 4 ? ipv4_clip_hash(ctbl, addr) : |
||||
ipv6_clip_hash(ctbl, addr); |
||||
} |
||||
|
||||
static int clip6_get_mbox(const struct net_device *dev, |
||||
const struct in6_addr *lip) |
||||
{ |
||||
struct adapter *adap = netdev2adap(dev); |
||||
struct fw_clip_cmd c; |
||||
|
||||
memset(&c, 0, sizeof(c)); |
||||
c.op_to_write = htonl(FW_CMD_OP_V(FW_CLIP_CMD) | |
||||
FW_CMD_REQUEST_F | FW_CMD_WRITE_F); |
||||
c.alloc_to_len16 = htonl(FW_CLIP_CMD_ALLOC_F | FW_LEN16(c)); |
||||
*(__be64 *)&c.ip_hi = *(__be64 *)(lip->s6_addr); |
||||
*(__be64 *)&c.ip_lo = *(__be64 *)(lip->s6_addr + 8); |
||||
return t4_wr_mbox_meat(adap, adap->mbox, &c, sizeof(c), &c, false); |
||||
} |
||||
|
||||
static int clip6_release_mbox(const struct net_device *dev, |
||||
const struct in6_addr *lip) |
||||
{ |
||||
struct adapter *adap = netdev2adap(dev); |
||||
struct fw_clip_cmd c; |
||||
|
||||
memset(&c, 0, sizeof(c)); |
||||
c.op_to_write = htonl(FW_CMD_OP_V(FW_CLIP_CMD) | |
||||
FW_CMD_REQUEST_F | FW_CMD_READ_F); |
||||
c.alloc_to_len16 = htonl(FW_CLIP_CMD_FREE_F | FW_LEN16(c)); |
||||
*(__be64 *)&c.ip_hi = *(__be64 *)(lip->s6_addr); |
||||
*(__be64 *)&c.ip_lo = *(__be64 *)(lip->s6_addr + 8); |
||||
return t4_wr_mbox_meat(adap, adap->mbox, &c, sizeof(c), &c, false); |
||||
} |
||||
|
||||
int cxgb4_clip_get(const struct net_device *dev, const u32 *lip, u8 v6) |
||||
{ |
||||
struct adapter *adap = netdev2adap(dev); |
||||
struct clip_tbl *ctbl = adap->clipt; |
||||
struct clip_entry *ce, *cte; |
||||
u32 *addr = (u32 *)lip; |
||||
int hash; |
||||
int addr_len; |
||||
int ret = 0; |
||||
|
||||
if (v6) |
||||
addr_len = 16; |
||||
else |
||||
addr_len = 4; |
||||
|
||||
hash = clip_addr_hash(ctbl, addr, addr_len); |
||||
|
||||
read_lock_bh(&ctbl->lock); |
||||
list_for_each_entry(cte, &ctbl->hash_list[hash], list) { |
||||
if (addr_len == cte->addr_len && |
||||
memcmp(lip, cte->addr, cte->addr_len) == 0) { |
||||
ce = cte; |
||||
read_unlock_bh(&ctbl->lock); |
||||
goto found; |
||||
} |
||||
} |
||||
read_unlock_bh(&ctbl->lock); |
||||
|
||||
write_lock_bh(&ctbl->lock); |
||||
if (!list_empty(&ctbl->ce_free_head)) { |
||||
ce = list_first_entry(&ctbl->ce_free_head, |
||||
struct clip_entry, list); |
||||
list_del(&ce->list); |
||||
INIT_LIST_HEAD(&ce->list); |
||||
spin_lock_init(&ce->lock); |
||||
atomic_set(&ce->refcnt, 0); |
||||
atomic_dec(&ctbl->nfree); |
||||
ce->addr_len = addr_len; |
||||
memcpy(ce->addr, lip, addr_len); |
||||
list_add_tail(&ce->list, &ctbl->hash_list[hash]); |
||||
if (v6) { |
||||
ret = clip6_get_mbox(dev, (const struct in6_addr *)lip); |
||||
if (ret) { |
||||
write_unlock_bh(&ctbl->lock); |
||||
return ret; |
||||
} |
||||
} |
||||
} else { |
||||
write_unlock_bh(&ctbl->lock); |
||||
return -ENOMEM; |
||||
} |
||||
write_unlock_bh(&ctbl->lock); |
||||
found: |
||||
atomic_inc(&ce->refcnt); |
||||
|
||||
return 0; |
||||
} |
||||
EXPORT_SYMBOL(cxgb4_clip_get); |
||||
|
||||
void cxgb4_clip_release(const struct net_device *dev, const u32 *lip, u8 v6) |
||||
{ |
||||
struct adapter *adap = netdev2adap(dev); |
||||
struct clip_tbl *ctbl = adap->clipt; |
||||
struct clip_entry *ce, *cte; |
||||
u32 *addr = (u32 *)lip; |
||||
int hash; |
||||
int addr_len; |
||||
|
||||
if (v6) |
||||
addr_len = 16; |
||||
else |
||||
addr_len = 4; |
||||
|
||||
hash = clip_addr_hash(ctbl, addr, addr_len); |
||||
|
||||
read_lock_bh(&ctbl->lock); |
||||
list_for_each_entry(cte, &ctbl->hash_list[hash], list) { |
||||
if (addr_len == cte->addr_len && |
||||
memcmp(lip, cte->addr, cte->addr_len) == 0) { |
||||
ce = cte; |
||||
read_unlock_bh(&ctbl->lock); |
||||
goto found; |
||||
} |
||||
} |
||||
read_unlock_bh(&ctbl->lock); |
||||
|
||||
return; |
||||
found: |
||||
write_lock_bh(&ctbl->lock); |
||||
spin_lock_bh(&ce->lock); |
||||
if (atomic_dec_and_test(&ce->refcnt)) { |
||||
list_del(&ce->list); |
||||
INIT_LIST_HEAD(&ce->list); |
||||
list_add_tail(&ce->list, &ctbl->ce_free_head); |
||||
atomic_inc(&ctbl->nfree); |
||||
if (v6) |
||||
clip6_release_mbox(dev, (const struct in6_addr *)lip); |
||||
} |
||||
spin_unlock_bh(&ce->lock); |
||||
write_unlock_bh(&ctbl->lock); |
||||
} |
||||
EXPORT_SYMBOL(cxgb4_clip_release); |
||||
|
||||
/* Retrieves IPv6 addresses from a root device (bond, vlan) associated with
|
||||
* a physical device. |
||||
* The physical device reference is needed to send the actul CLIP command. |
||||
*/ |
||||
static int cxgb4_update_dev_clip(struct net_device *root_dev, |
||||
struct net_device *dev) |
||||
{ |
||||
struct inet6_dev *idev = NULL; |
||||
struct inet6_ifaddr *ifa; |
||||
int ret = 0; |
||||
|
||||
idev = __in6_dev_get(root_dev); |
||||
if (!idev) |
||||
return ret; |
||||
|
||||
read_lock_bh(&idev->lock); |
||||
list_for_each_entry(ifa, &idev->addr_list, if_list) { |
||||
ret = cxgb4_clip_get(dev, (const u32 *)ifa->addr.s6_addr, 1); |
||||
if (ret < 0) |
||||
break; |
||||
} |
||||
read_unlock_bh(&idev->lock); |
||||
|
||||
return ret; |
||||
} |
||||
|
||||
int cxgb4_update_root_dev_clip(struct net_device *dev) |
||||
{ |
||||
struct net_device *root_dev = NULL; |
||||
int i, ret = 0; |
||||
|
||||
/* First populate the real net device's IPv6 addresses */ |
||||
ret = cxgb4_update_dev_clip(dev, dev); |
||||
if (ret) |
||||
return ret; |
||||
|
||||
/* Parse all bond and vlan devices layered on top of the physical dev */ |
||||
root_dev = netdev_master_upper_dev_get_rcu(dev); |
||||
if (root_dev) { |
||||
ret = cxgb4_update_dev_clip(root_dev, dev); |
||||
if (ret) |
||||
return ret; |
||||
} |
||||
|
||||
for (i = 0; i < VLAN_N_VID; i++) { |
||||
root_dev = __vlan_find_dev_deep_rcu(dev, htons(ETH_P_8021Q), i); |
||||
if (!root_dev) |
||||
continue; |
||||
|
||||
ret = cxgb4_update_dev_clip(root_dev, dev); |
||||
if (ret) |
||||
break; |
||||
} |
||||
|
||||
return ret; |
||||
} |
||||
EXPORT_SYMBOL(cxgb4_update_root_dev_clip); |
||||
|
||||
int clip_tbl_show(struct seq_file *seq, void *v) |
||||
{ |
||||
struct adapter *adapter = seq->private; |
||||
struct clip_tbl *ctbl = adapter->clipt; |
||||
struct clip_entry *ce; |
||||
char ip[60]; |
||||
int i; |
||||
|
||||
read_lock_bh(&ctbl->lock); |
||||
|
||||
seq_puts(seq, "IP Address Users\n"); |
||||
for (i = 0 ; i < ctbl->clipt_size; ++i) { |
||||
list_for_each_entry(ce, &ctbl->hash_list[i], list) { |
||||
ip[0] = '\0'; |
||||
if (ce->addr_len == 16) |
||||
sprintf(ip, "%pI6c", ce->addr); |
||||
else |
||||
sprintf(ip, "%pI4c", ce->addr); |
||||
seq_printf(seq, "%-25s %u\n", ip, |
||||
atomic_read(&ce->refcnt)); |
||||
} |
||||
} |
||||
seq_printf(seq, "Free clip entries : %d\n", atomic_read(&ctbl->nfree)); |
||||
|
||||
read_unlock_bh(&ctbl->lock); |
||||
|
||||
return 0; |
||||
} |
||||
|
||||
struct clip_tbl *t4_init_clip_tbl(unsigned int clipt_start, |
||||
unsigned int clipt_end) |
||||
{ |
||||
struct clip_entry *cl_list; |
||||
struct clip_tbl *ctbl; |
||||
unsigned int clipt_size; |
||||
int i; |
||||
|
||||
if (clipt_start >= clipt_end) |
||||
return NULL; |
||||
clipt_size = clipt_end - clipt_start + 1; |
||||
if (clipt_size < CLIPT_MIN_HASH_BUCKETS) |
||||
return NULL; |
||||
|
||||
ctbl = t4_alloc_mem(sizeof(*ctbl) + |
||||
clipt_size*sizeof(struct list_head)); |
||||
if (!ctbl) |
||||
return NULL; |
||||
|
||||
ctbl->clipt_start = clipt_start; |
||||
ctbl->clipt_size = clipt_size; |
||||
INIT_LIST_HEAD(&ctbl->ce_free_head); |
||||
|
||||
atomic_set(&ctbl->nfree, clipt_size); |
||||
rwlock_init(&ctbl->lock); |
||||
|
||||
for (i = 0; i < ctbl->clipt_size; ++i) |
||||
INIT_LIST_HEAD(&ctbl->hash_list[i]); |
||||
|
||||
cl_list = t4_alloc_mem(clipt_size*sizeof(struct clip_entry)); |
||||
ctbl->cl_list = (void *)cl_list; |
||||
|
||||
for (i = 0; i < clipt_size; i++) { |
||||
INIT_LIST_HEAD(&cl_list[i].list); |
||||
list_add_tail(&cl_list[i].list, &ctbl->ce_free_head); |
||||
} |
||||
|
||||
return ctbl; |
||||
} |
||||
|
||||
void t4_cleanup_clip_tbl(struct adapter *adap) |
||||
{ |
||||
struct clip_tbl *ctbl = adap->clipt; |
||||
|
||||
if (ctbl) { |
||||
if (ctbl->cl_list) |
||||
t4_free_mem(ctbl->cl_list); |
||||
t4_free_mem(ctbl); |
||||
} |
||||
} |
||||
EXPORT_SYMBOL(t4_cleanup_clip_tbl); |
@ -0,0 +1,41 @@ |
||||
/*
|
||||
* This file is part of the Chelsio T4 Ethernet driver for Linux. |
||||
* Copyright (C) 2003-2014 Chelsio Communications. All rights reserved. |
||||
* |
||||
* Written by Deepak (deepak.s@chelsio.com) |
||||
* |
||||
* This program is distributed in the hope that it will be useful, but WITHOUT |
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the LICENSE file included in this |
||||
* release for licensing terms and conditions. |
||||
*/ |
||||
|
||||
struct clip_entry { |
||||
spinlock_t lock; /* Hold while modifying clip reference */ |
||||
atomic_t refcnt; |
||||
struct list_head list; |
||||
u32 addr[4]; |
||||
int addr_len; |
||||
}; |
||||
|
||||
struct clip_tbl { |
||||
unsigned int clipt_start; |
||||
unsigned int clipt_size; |
||||
rwlock_t lock; |
||||
atomic_t nfree; |
||||
struct list_head ce_free_head; |
||||
void *cl_list; |
||||
struct list_head hash_list[0]; |
||||
}; |
||||
|
||||
enum { |
||||
CLIPT_MIN_HASH_BUCKETS = 2, |
||||
}; |
||||
|
||||
struct clip_tbl *t4_init_clip_tbl(unsigned int clipt_start, |
||||
unsigned int clipt_end); |
||||
int cxgb4_clip_get(const struct net_device *dev, const u32 *lip, u8 v6); |
||||
void cxgb4_clip_release(const struct net_device *dev, const u32 *lip, u8 v6); |
||||
int clip_tbl_show(struct seq_file *seq, void *v); |
||||
int cxgb4_update_root_dev_clip(struct net_device *dev); |
||||
void t4_cleanup_clip_tbl(struct adapter *adap); |
Loading…
Reference in new issue