162306a36Sopenharmony_ci/* 262306a36Sopenharmony_ci * This file is part of the Chelsio T4 Ethernet driver for Linux. 362306a36Sopenharmony_ci * Copyright (C) 2003-2014 Chelsio Communications. All rights reserved. 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Written by Deepak (deepak.s@chelsio.com) 662306a36Sopenharmony_ci * 762306a36Sopenharmony_ci * This program is distributed in the hope that it will be useful, but WITHOUT 862306a36Sopenharmony_ci * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 962306a36Sopenharmony_ci * FITNESS FOR A PARTICULAR PURPOSE. See the LICENSE file included in this 1062306a36Sopenharmony_ci * release for licensing terms and conditions. 1162306a36Sopenharmony_ci */ 1262306a36Sopenharmony_ci 1362306a36Sopenharmony_ci#include <linux/module.h> 1462306a36Sopenharmony_ci#include <linux/netdevice.h> 1562306a36Sopenharmony_ci#include <linux/jhash.h> 1662306a36Sopenharmony_ci#include <linux/if_vlan.h> 1762306a36Sopenharmony_ci#include <net/addrconf.h> 1862306a36Sopenharmony_ci#include "cxgb4.h" 1962306a36Sopenharmony_ci#include "clip_tbl.h" 2062306a36Sopenharmony_ci 2162306a36Sopenharmony_cistatic inline unsigned int ipv4_clip_hash(struct clip_tbl *c, const u32 *key) 2262306a36Sopenharmony_ci{ 2362306a36Sopenharmony_ci unsigned int clipt_size_half = c->clipt_size / 2; 2462306a36Sopenharmony_ci 2562306a36Sopenharmony_ci return jhash_1word(*key, 0) % clipt_size_half; 2662306a36Sopenharmony_ci} 2762306a36Sopenharmony_ci 2862306a36Sopenharmony_cistatic inline unsigned int ipv6_clip_hash(struct clip_tbl *d, const u32 *key) 2962306a36Sopenharmony_ci{ 3062306a36Sopenharmony_ci unsigned int clipt_size_half = d->clipt_size / 2; 3162306a36Sopenharmony_ci u32 xor = key[0] ^ key[1] ^ key[2] ^ key[3]; 3262306a36Sopenharmony_ci 3362306a36Sopenharmony_ci return clipt_size_half + 3462306a36Sopenharmony_ci (jhash_1word(xor, 0) % clipt_size_half); 3562306a36Sopenharmony_ci} 3662306a36Sopenharmony_ci 3762306a36Sopenharmony_cistatic unsigned int clip_addr_hash(struct clip_tbl *ctbl, const u32 *addr, 3862306a36Sopenharmony_ci u8 v6) 3962306a36Sopenharmony_ci{ 4062306a36Sopenharmony_ci return v6 ? ipv6_clip_hash(ctbl, addr) : 4162306a36Sopenharmony_ci ipv4_clip_hash(ctbl, addr); 4262306a36Sopenharmony_ci} 4362306a36Sopenharmony_ci 4462306a36Sopenharmony_cistatic int clip6_get_mbox(const struct net_device *dev, 4562306a36Sopenharmony_ci const struct in6_addr *lip) 4662306a36Sopenharmony_ci{ 4762306a36Sopenharmony_ci struct adapter *adap = netdev2adap(dev); 4862306a36Sopenharmony_ci struct fw_clip_cmd c; 4962306a36Sopenharmony_ci 5062306a36Sopenharmony_ci memset(&c, 0, sizeof(c)); 5162306a36Sopenharmony_ci c.op_to_write = htonl(FW_CMD_OP_V(FW_CLIP_CMD) | 5262306a36Sopenharmony_ci FW_CMD_REQUEST_F | FW_CMD_WRITE_F); 5362306a36Sopenharmony_ci c.alloc_to_len16 = htonl(FW_CLIP_CMD_ALLOC_F | FW_LEN16(c)); 5462306a36Sopenharmony_ci *(__be64 *)&c.ip_hi = *(__be64 *)(lip->s6_addr); 5562306a36Sopenharmony_ci *(__be64 *)&c.ip_lo = *(__be64 *)(lip->s6_addr + 8); 5662306a36Sopenharmony_ci return t4_wr_mbox_meat(adap, adap->mbox, &c, sizeof(c), &c, false); 5762306a36Sopenharmony_ci} 5862306a36Sopenharmony_ci 5962306a36Sopenharmony_cistatic int clip6_release_mbox(const struct net_device *dev, 6062306a36Sopenharmony_ci const struct in6_addr *lip) 6162306a36Sopenharmony_ci{ 6262306a36Sopenharmony_ci struct adapter *adap = netdev2adap(dev); 6362306a36Sopenharmony_ci struct fw_clip_cmd c; 6462306a36Sopenharmony_ci 6562306a36Sopenharmony_ci memset(&c, 0, sizeof(c)); 6662306a36Sopenharmony_ci c.op_to_write = htonl(FW_CMD_OP_V(FW_CLIP_CMD) | 6762306a36Sopenharmony_ci FW_CMD_REQUEST_F | FW_CMD_READ_F); 6862306a36Sopenharmony_ci c.alloc_to_len16 = htonl(FW_CLIP_CMD_FREE_F | FW_LEN16(c)); 6962306a36Sopenharmony_ci *(__be64 *)&c.ip_hi = *(__be64 *)(lip->s6_addr); 7062306a36Sopenharmony_ci *(__be64 *)&c.ip_lo = *(__be64 *)(lip->s6_addr + 8); 7162306a36Sopenharmony_ci return t4_wr_mbox_meat(adap, adap->mbox, &c, sizeof(c), &c, false); 7262306a36Sopenharmony_ci} 7362306a36Sopenharmony_ci 7462306a36Sopenharmony_ciint cxgb4_clip_get(const struct net_device *dev, const u32 *lip, u8 v6) 7562306a36Sopenharmony_ci{ 7662306a36Sopenharmony_ci struct adapter *adap = netdev2adap(dev); 7762306a36Sopenharmony_ci struct clip_tbl *ctbl = adap->clipt; 7862306a36Sopenharmony_ci struct clip_entry *ce, *cte; 7962306a36Sopenharmony_ci u32 *addr = (u32 *)lip; 8062306a36Sopenharmony_ci int hash; 8162306a36Sopenharmony_ci int ret = -1; 8262306a36Sopenharmony_ci 8362306a36Sopenharmony_ci if (!ctbl) 8462306a36Sopenharmony_ci return 0; 8562306a36Sopenharmony_ci 8662306a36Sopenharmony_ci hash = clip_addr_hash(ctbl, addr, v6); 8762306a36Sopenharmony_ci 8862306a36Sopenharmony_ci read_lock_bh(&ctbl->lock); 8962306a36Sopenharmony_ci list_for_each_entry(cte, &ctbl->hash_list[hash], list) { 9062306a36Sopenharmony_ci if (cte->addr6.sin6_family == AF_INET6 && v6) 9162306a36Sopenharmony_ci ret = memcmp(lip, cte->addr6.sin6_addr.s6_addr, 9262306a36Sopenharmony_ci sizeof(struct in6_addr)); 9362306a36Sopenharmony_ci else if (cte->addr.sin_family == AF_INET && !v6) 9462306a36Sopenharmony_ci ret = memcmp(lip, (char *)(&cte->addr.sin_addr), 9562306a36Sopenharmony_ci sizeof(struct in_addr)); 9662306a36Sopenharmony_ci if (!ret) { 9762306a36Sopenharmony_ci ce = cte; 9862306a36Sopenharmony_ci read_unlock_bh(&ctbl->lock); 9962306a36Sopenharmony_ci refcount_inc(&ce->refcnt); 10062306a36Sopenharmony_ci return 0; 10162306a36Sopenharmony_ci } 10262306a36Sopenharmony_ci } 10362306a36Sopenharmony_ci read_unlock_bh(&ctbl->lock); 10462306a36Sopenharmony_ci 10562306a36Sopenharmony_ci write_lock_bh(&ctbl->lock); 10662306a36Sopenharmony_ci if (!list_empty(&ctbl->ce_free_head)) { 10762306a36Sopenharmony_ci ce = list_first_entry(&ctbl->ce_free_head, 10862306a36Sopenharmony_ci struct clip_entry, list); 10962306a36Sopenharmony_ci list_del_init(&ce->list); 11062306a36Sopenharmony_ci spin_lock_init(&ce->lock); 11162306a36Sopenharmony_ci refcount_set(&ce->refcnt, 0); 11262306a36Sopenharmony_ci atomic_dec(&ctbl->nfree); 11362306a36Sopenharmony_ci list_add_tail(&ce->list, &ctbl->hash_list[hash]); 11462306a36Sopenharmony_ci if (v6) { 11562306a36Sopenharmony_ci ce->addr6.sin6_family = AF_INET6; 11662306a36Sopenharmony_ci memcpy(ce->addr6.sin6_addr.s6_addr, 11762306a36Sopenharmony_ci lip, sizeof(struct in6_addr)); 11862306a36Sopenharmony_ci ret = clip6_get_mbox(dev, (const struct in6_addr *)lip); 11962306a36Sopenharmony_ci if (ret) { 12062306a36Sopenharmony_ci write_unlock_bh(&ctbl->lock); 12162306a36Sopenharmony_ci dev_err(adap->pdev_dev, 12262306a36Sopenharmony_ci "CLIP FW cmd failed with error %d, " 12362306a36Sopenharmony_ci "Connections using %pI6c wont be " 12462306a36Sopenharmony_ci "offloaded", 12562306a36Sopenharmony_ci ret, ce->addr6.sin6_addr.s6_addr); 12662306a36Sopenharmony_ci return ret; 12762306a36Sopenharmony_ci } 12862306a36Sopenharmony_ci } else { 12962306a36Sopenharmony_ci ce->addr.sin_family = AF_INET; 13062306a36Sopenharmony_ci memcpy((char *)(&ce->addr.sin_addr), lip, 13162306a36Sopenharmony_ci sizeof(struct in_addr)); 13262306a36Sopenharmony_ci } 13362306a36Sopenharmony_ci } else { 13462306a36Sopenharmony_ci write_unlock_bh(&ctbl->lock); 13562306a36Sopenharmony_ci dev_info(adap->pdev_dev, "CLIP table overflow, " 13662306a36Sopenharmony_ci "Connections using %pI6c wont be offloaded", 13762306a36Sopenharmony_ci (void *)lip); 13862306a36Sopenharmony_ci return -ENOMEM; 13962306a36Sopenharmony_ci } 14062306a36Sopenharmony_ci write_unlock_bh(&ctbl->lock); 14162306a36Sopenharmony_ci refcount_set(&ce->refcnt, 1); 14262306a36Sopenharmony_ci return 0; 14362306a36Sopenharmony_ci} 14462306a36Sopenharmony_ciEXPORT_SYMBOL(cxgb4_clip_get); 14562306a36Sopenharmony_ci 14662306a36Sopenharmony_civoid cxgb4_clip_release(const struct net_device *dev, const u32 *lip, u8 v6) 14762306a36Sopenharmony_ci{ 14862306a36Sopenharmony_ci struct adapter *adap = netdev2adap(dev); 14962306a36Sopenharmony_ci struct clip_tbl *ctbl = adap->clipt; 15062306a36Sopenharmony_ci struct clip_entry *ce, *cte; 15162306a36Sopenharmony_ci u32 *addr = (u32 *)lip; 15262306a36Sopenharmony_ci int hash; 15362306a36Sopenharmony_ci int ret = -1; 15462306a36Sopenharmony_ci 15562306a36Sopenharmony_ci if (!ctbl) 15662306a36Sopenharmony_ci return; 15762306a36Sopenharmony_ci 15862306a36Sopenharmony_ci hash = clip_addr_hash(ctbl, addr, v6); 15962306a36Sopenharmony_ci 16062306a36Sopenharmony_ci read_lock_bh(&ctbl->lock); 16162306a36Sopenharmony_ci list_for_each_entry(cte, &ctbl->hash_list[hash], list) { 16262306a36Sopenharmony_ci if (cte->addr6.sin6_family == AF_INET6 && v6) 16362306a36Sopenharmony_ci ret = memcmp(lip, cte->addr6.sin6_addr.s6_addr, 16462306a36Sopenharmony_ci sizeof(struct in6_addr)); 16562306a36Sopenharmony_ci else if (cte->addr.sin_family == AF_INET && !v6) 16662306a36Sopenharmony_ci ret = memcmp(lip, (char *)(&cte->addr.sin_addr), 16762306a36Sopenharmony_ci sizeof(struct in_addr)); 16862306a36Sopenharmony_ci if (!ret) { 16962306a36Sopenharmony_ci ce = cte; 17062306a36Sopenharmony_ci read_unlock_bh(&ctbl->lock); 17162306a36Sopenharmony_ci goto found; 17262306a36Sopenharmony_ci } 17362306a36Sopenharmony_ci } 17462306a36Sopenharmony_ci read_unlock_bh(&ctbl->lock); 17562306a36Sopenharmony_ci 17662306a36Sopenharmony_ci return; 17762306a36Sopenharmony_cifound: 17862306a36Sopenharmony_ci write_lock_bh(&ctbl->lock); 17962306a36Sopenharmony_ci spin_lock_bh(&ce->lock); 18062306a36Sopenharmony_ci if (refcount_dec_and_test(&ce->refcnt)) { 18162306a36Sopenharmony_ci list_del_init(&ce->list); 18262306a36Sopenharmony_ci list_add_tail(&ce->list, &ctbl->ce_free_head); 18362306a36Sopenharmony_ci atomic_inc(&ctbl->nfree); 18462306a36Sopenharmony_ci if (v6) 18562306a36Sopenharmony_ci clip6_release_mbox(dev, (const struct in6_addr *)lip); 18662306a36Sopenharmony_ci } 18762306a36Sopenharmony_ci spin_unlock_bh(&ce->lock); 18862306a36Sopenharmony_ci write_unlock_bh(&ctbl->lock); 18962306a36Sopenharmony_ci} 19062306a36Sopenharmony_ciEXPORT_SYMBOL(cxgb4_clip_release); 19162306a36Sopenharmony_ci 19262306a36Sopenharmony_ci/* Retrieves IPv6 addresses from a root device (bond, vlan) associated with 19362306a36Sopenharmony_ci * a physical device. 19462306a36Sopenharmony_ci * The physical device reference is needed to send the actul CLIP command. 19562306a36Sopenharmony_ci */ 19662306a36Sopenharmony_cistatic int cxgb4_update_dev_clip(struct net_device *root_dev, 19762306a36Sopenharmony_ci struct net_device *dev) 19862306a36Sopenharmony_ci{ 19962306a36Sopenharmony_ci struct inet6_dev *idev = NULL; 20062306a36Sopenharmony_ci struct inet6_ifaddr *ifa; 20162306a36Sopenharmony_ci int ret = 0; 20262306a36Sopenharmony_ci 20362306a36Sopenharmony_ci idev = __in6_dev_get(root_dev); 20462306a36Sopenharmony_ci if (!idev) 20562306a36Sopenharmony_ci return ret; 20662306a36Sopenharmony_ci 20762306a36Sopenharmony_ci read_lock_bh(&idev->lock); 20862306a36Sopenharmony_ci list_for_each_entry(ifa, &idev->addr_list, if_list) { 20962306a36Sopenharmony_ci ret = cxgb4_clip_get(dev, (const u32 *)ifa->addr.s6_addr, 1); 21062306a36Sopenharmony_ci if (ret < 0) 21162306a36Sopenharmony_ci break; 21262306a36Sopenharmony_ci } 21362306a36Sopenharmony_ci read_unlock_bh(&idev->lock); 21462306a36Sopenharmony_ci 21562306a36Sopenharmony_ci return ret; 21662306a36Sopenharmony_ci} 21762306a36Sopenharmony_ci 21862306a36Sopenharmony_ciint cxgb4_update_root_dev_clip(struct net_device *dev) 21962306a36Sopenharmony_ci{ 22062306a36Sopenharmony_ci struct net_device *root_dev = NULL; 22162306a36Sopenharmony_ci int i, ret = 0; 22262306a36Sopenharmony_ci 22362306a36Sopenharmony_ci /* First populate the real net device's IPv6 addresses */ 22462306a36Sopenharmony_ci ret = cxgb4_update_dev_clip(dev, dev); 22562306a36Sopenharmony_ci if (ret) 22662306a36Sopenharmony_ci return ret; 22762306a36Sopenharmony_ci 22862306a36Sopenharmony_ci /* Parse all bond and vlan devices layered on top of the physical dev */ 22962306a36Sopenharmony_ci root_dev = netdev_master_upper_dev_get_rcu(dev); 23062306a36Sopenharmony_ci if (root_dev) { 23162306a36Sopenharmony_ci ret = cxgb4_update_dev_clip(root_dev, dev); 23262306a36Sopenharmony_ci if (ret) 23362306a36Sopenharmony_ci return ret; 23462306a36Sopenharmony_ci } 23562306a36Sopenharmony_ci 23662306a36Sopenharmony_ci for (i = 0; i < VLAN_N_VID; i++) { 23762306a36Sopenharmony_ci root_dev = __vlan_find_dev_deep_rcu(dev, htons(ETH_P_8021Q), i); 23862306a36Sopenharmony_ci if (!root_dev) 23962306a36Sopenharmony_ci continue; 24062306a36Sopenharmony_ci 24162306a36Sopenharmony_ci ret = cxgb4_update_dev_clip(root_dev, dev); 24262306a36Sopenharmony_ci if (ret) 24362306a36Sopenharmony_ci break; 24462306a36Sopenharmony_ci } 24562306a36Sopenharmony_ci 24662306a36Sopenharmony_ci return ret; 24762306a36Sopenharmony_ci} 24862306a36Sopenharmony_ciEXPORT_SYMBOL(cxgb4_update_root_dev_clip); 24962306a36Sopenharmony_ci 25062306a36Sopenharmony_ciint clip_tbl_show(struct seq_file *seq, void *v) 25162306a36Sopenharmony_ci{ 25262306a36Sopenharmony_ci struct adapter *adapter = seq->private; 25362306a36Sopenharmony_ci struct clip_tbl *ctbl = adapter->clipt; 25462306a36Sopenharmony_ci struct clip_entry *ce; 25562306a36Sopenharmony_ci char ip[60]; 25662306a36Sopenharmony_ci int i; 25762306a36Sopenharmony_ci 25862306a36Sopenharmony_ci read_lock_bh(&ctbl->lock); 25962306a36Sopenharmony_ci 26062306a36Sopenharmony_ci seq_puts(seq, "IP Address Users\n"); 26162306a36Sopenharmony_ci for (i = 0 ; i < ctbl->clipt_size; ++i) { 26262306a36Sopenharmony_ci list_for_each_entry(ce, &ctbl->hash_list[i], list) { 26362306a36Sopenharmony_ci ip[0] = '\0'; 26462306a36Sopenharmony_ci sprintf(ip, "%pISc", &ce->addr); 26562306a36Sopenharmony_ci seq_printf(seq, "%-25s %u\n", ip, 26662306a36Sopenharmony_ci refcount_read(&ce->refcnt)); 26762306a36Sopenharmony_ci } 26862306a36Sopenharmony_ci } 26962306a36Sopenharmony_ci seq_printf(seq, "Free clip entries : %d\n", atomic_read(&ctbl->nfree)); 27062306a36Sopenharmony_ci 27162306a36Sopenharmony_ci read_unlock_bh(&ctbl->lock); 27262306a36Sopenharmony_ci 27362306a36Sopenharmony_ci return 0; 27462306a36Sopenharmony_ci} 27562306a36Sopenharmony_ci 27662306a36Sopenharmony_cistruct clip_tbl *t4_init_clip_tbl(unsigned int clipt_start, 27762306a36Sopenharmony_ci unsigned int clipt_end) 27862306a36Sopenharmony_ci{ 27962306a36Sopenharmony_ci struct clip_entry *cl_list; 28062306a36Sopenharmony_ci struct clip_tbl *ctbl; 28162306a36Sopenharmony_ci unsigned int clipt_size; 28262306a36Sopenharmony_ci int i; 28362306a36Sopenharmony_ci 28462306a36Sopenharmony_ci if (clipt_start >= clipt_end) 28562306a36Sopenharmony_ci return NULL; 28662306a36Sopenharmony_ci clipt_size = clipt_end - clipt_start + 1; 28762306a36Sopenharmony_ci if (clipt_size < CLIPT_MIN_HASH_BUCKETS) 28862306a36Sopenharmony_ci return NULL; 28962306a36Sopenharmony_ci 29062306a36Sopenharmony_ci ctbl = kvzalloc(struct_size(ctbl, hash_list, clipt_size), GFP_KERNEL); 29162306a36Sopenharmony_ci if (!ctbl) 29262306a36Sopenharmony_ci return NULL; 29362306a36Sopenharmony_ci 29462306a36Sopenharmony_ci ctbl->clipt_start = clipt_start; 29562306a36Sopenharmony_ci ctbl->clipt_size = clipt_size; 29662306a36Sopenharmony_ci INIT_LIST_HEAD(&ctbl->ce_free_head); 29762306a36Sopenharmony_ci 29862306a36Sopenharmony_ci atomic_set(&ctbl->nfree, clipt_size); 29962306a36Sopenharmony_ci rwlock_init(&ctbl->lock); 30062306a36Sopenharmony_ci 30162306a36Sopenharmony_ci for (i = 0; i < ctbl->clipt_size; ++i) 30262306a36Sopenharmony_ci INIT_LIST_HEAD(&ctbl->hash_list[i]); 30362306a36Sopenharmony_ci 30462306a36Sopenharmony_ci cl_list = kvcalloc(clipt_size, sizeof(struct clip_entry), GFP_KERNEL); 30562306a36Sopenharmony_ci if (!cl_list) { 30662306a36Sopenharmony_ci kvfree(ctbl); 30762306a36Sopenharmony_ci return NULL; 30862306a36Sopenharmony_ci } 30962306a36Sopenharmony_ci ctbl->cl_list = (void *)cl_list; 31062306a36Sopenharmony_ci 31162306a36Sopenharmony_ci for (i = 0; i < clipt_size; i++) { 31262306a36Sopenharmony_ci INIT_LIST_HEAD(&cl_list[i].list); 31362306a36Sopenharmony_ci list_add_tail(&cl_list[i].list, &ctbl->ce_free_head); 31462306a36Sopenharmony_ci } 31562306a36Sopenharmony_ci 31662306a36Sopenharmony_ci return ctbl; 31762306a36Sopenharmony_ci} 31862306a36Sopenharmony_ci 31962306a36Sopenharmony_civoid t4_cleanup_clip_tbl(struct adapter *adap) 32062306a36Sopenharmony_ci{ 32162306a36Sopenharmony_ci struct clip_tbl *ctbl = adap->clipt; 32262306a36Sopenharmony_ci 32362306a36Sopenharmony_ci if (ctbl) { 32462306a36Sopenharmony_ci kvfree(ctbl->cl_list); 32562306a36Sopenharmony_ci kvfree(ctbl); 32662306a36Sopenharmony_ci } 32762306a36Sopenharmony_ci} 32862306a36Sopenharmony_ciEXPORT_SYMBOL(t4_cleanup_clip_tbl); 329