Skip to content

Commit

Permalink
cxgb4 : Update ipv6 address handling api
Browse files Browse the repository at this point in the history
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>
  • Loading branch information
anish authored and davem330 committed Jan 15, 2015
1 parent 5055c37 commit b5a02f5
Show file tree
Hide file tree
Showing 7 changed files with 447 additions and 163 deletions.
2 changes: 1 addition & 1 deletion drivers/net/ethernet/chelsio/cxgb4/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,6 @@

obj-$(CONFIG_CHELSIO_T4) += cxgb4.o

cxgb4-objs := cxgb4_main.o l2t.o t4_hw.o sge.o
cxgb4-objs := cxgb4_main.o l2t.o t4_hw.o sge.o clip_tbl.o
cxgb4-$(CONFIG_CHELSIO_T4_DCB) += cxgb4_dcb.o
cxgb4-$(CONFIG_DEBUG_FS) += cxgb4_debugfs.o
314 changes: 314 additions & 0 deletions drivers/net/ethernet/chelsio/cxgb4/clip_tbl.c
Original file line number Diff line number Diff line change
@@ -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);
41 changes: 41 additions & 0 deletions drivers/net/ethernet/chelsio/cxgb4/clip_tbl.h
Original file line number Diff line number Diff line change
@@ -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);
3 changes: 3 additions & 0 deletions drivers/net/ethernet/chelsio/cxgb4/cxgb4.h
Original file line number Diff line number Diff line change
Expand Up @@ -668,6 +668,9 @@ struct adapter {
unsigned int l2t_start;
unsigned int l2t_end;
struct l2t_data *l2t;
unsigned int clipt_start;
unsigned int clipt_end;
struct clip_tbl *clipt;
void *uld_handle[CXGB4_ULD_MAX];
struct list_head list_node;
struct list_head rcu_node;
Expand Down
Loading

0 comments on commit b5a02f5

Please sign in to comment.