| /* |
| * 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, |
| u8 v6) |
| { |
| return v6 ? ipv6_clip_hash(ctbl, addr) : |
| ipv4_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 ret = -1; |
| |
| if (!ctbl) |
| return 0; |
| |
| hash = clip_addr_hash(ctbl, addr, v6); |
| |
| read_lock_bh(&ctbl->lock); |
| list_for_each_entry(cte, &ctbl->hash_list[hash], list) { |
| if (cte->addr6.sin6_family == AF_INET6 && v6) |
| ret = memcmp(lip, cte->addr6.sin6_addr.s6_addr, |
| sizeof(struct in6_addr)); |
| else if (cte->addr.sin_family == AF_INET && !v6) |
| ret = memcmp(lip, (char *)(&cte->addr.sin_addr), |
| sizeof(struct in_addr)); |
| if (!ret) { |
| ce = cte; |
| read_unlock_bh(&ctbl->lock); |
| refcount_inc(&ce->refcnt); |
| return 0; |
| } |
| } |
| 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); |
| refcount_set(&ce->refcnt, 0); |
| atomic_dec(&ctbl->nfree); |
| list_add_tail(&ce->list, &ctbl->hash_list[hash]); |
| if (v6) { |
| ce->addr6.sin6_family = AF_INET6; |
| memcpy(ce->addr6.sin6_addr.s6_addr, |
| lip, sizeof(struct in6_addr)); |
| ret = clip6_get_mbox(dev, (const struct in6_addr *)lip); |
| if (ret) { |
| write_unlock_bh(&ctbl->lock); |
| dev_err(adap->pdev_dev, |
| "CLIP FW cmd failed with error %d, " |
| "Connections using %pI6c wont be " |
| "offloaded", |
| ret, ce->addr6.sin6_addr.s6_addr); |
| return ret; |
| } |
| } else { |
| ce->addr.sin_family = AF_INET; |
| memcpy((char *)(&ce->addr.sin_addr), lip, |
| sizeof(struct in_addr)); |
| } |
| } else { |
| write_unlock_bh(&ctbl->lock); |
| dev_info(adap->pdev_dev, "CLIP table overflow, " |
| "Connections using %pI6c wont be offloaded", |
| (void *)lip); |
| return -ENOMEM; |
| } |
| write_unlock_bh(&ctbl->lock); |
| refcount_set(&ce->refcnt, 1); |
| 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 ret = -1; |
| |
| if (!ctbl) |
| return; |
| |
| hash = clip_addr_hash(ctbl, addr, v6); |
| |
| read_lock_bh(&ctbl->lock); |
| list_for_each_entry(cte, &ctbl->hash_list[hash], list) { |
| if (cte->addr6.sin6_family == AF_INET6 && v6) |
| ret = memcmp(lip, cte->addr6.sin6_addr.s6_addr, |
| sizeof(struct in6_addr)); |
| else if (cte->addr.sin_family == AF_INET && !v6) |
| ret = memcmp(lip, (char *)(&cte->addr.sin_addr), |
| sizeof(struct in_addr)); |
| if (!ret) { |
| 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 (refcount_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'; |
| sprintf(ip, "%pISc", &ce->addr); |
| seq_printf(seq, "%-25s %u\n", ip, |
| refcount_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 = kvzalloc(struct_size(ctbl, hash_list, clipt_size), GFP_KERNEL); |
| 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 = kvcalloc(clipt_size, sizeof(struct clip_entry), GFP_KERNEL); |
| if (!cl_list) { |
| kvfree(ctbl); |
| return NULL; |
| } |
| 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) |
| kvfree(ctbl->cl_list); |
| kvfree(ctbl); |
| } |
| } |
| EXPORT_SYMBOL(t4_cleanup_clip_tbl); |