Back to home page

OSCL-LXR

 
 

    


0001 /*
0002  *  This file is part of the Chelsio T4 Ethernet driver for Linux.
0003  *  Copyright (C) 2003-2014 Chelsio Communications.  All rights reserved.
0004  *
0005  *  Written by Deepak (deepak.s@chelsio.com)
0006  *
0007  *  This program is distributed in the hope that it will be useful, but WITHOUT
0008  *  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
0009  *  FITNESS FOR A PARTICULAR PURPOSE.  See the LICENSE file included in this
0010  *  release for licensing terms and conditions.
0011  */
0012 
0013 #include <linux/module.h>
0014 #include <linux/netdevice.h>
0015 #include <linux/jhash.h>
0016 #include <linux/if_vlan.h>
0017 #include <net/addrconf.h>
0018 #include "cxgb4.h"
0019 #include "clip_tbl.h"
0020 
0021 static inline unsigned int ipv4_clip_hash(struct clip_tbl *c, const u32 *key)
0022 {
0023     unsigned int clipt_size_half = c->clipt_size / 2;
0024 
0025     return jhash_1word(*key, 0) % clipt_size_half;
0026 }
0027 
0028 static inline unsigned int ipv6_clip_hash(struct clip_tbl *d, const u32 *key)
0029 {
0030     unsigned int clipt_size_half = d->clipt_size / 2;
0031     u32 xor = key[0] ^ key[1] ^ key[2] ^ key[3];
0032 
0033     return clipt_size_half +
0034         (jhash_1word(xor, 0) % clipt_size_half);
0035 }
0036 
0037 static unsigned int clip_addr_hash(struct clip_tbl *ctbl, const u32 *addr,
0038                    u8 v6)
0039 {
0040     return v6 ? ipv6_clip_hash(ctbl, addr) :
0041             ipv4_clip_hash(ctbl, addr);
0042 }
0043 
0044 static int clip6_get_mbox(const struct net_device *dev,
0045               const struct in6_addr *lip)
0046 {
0047     struct adapter *adap = netdev2adap(dev);
0048     struct fw_clip_cmd c;
0049 
0050     memset(&c, 0, sizeof(c));
0051     c.op_to_write = htonl(FW_CMD_OP_V(FW_CLIP_CMD) |
0052                   FW_CMD_REQUEST_F | FW_CMD_WRITE_F);
0053     c.alloc_to_len16 = htonl(FW_CLIP_CMD_ALLOC_F | FW_LEN16(c));
0054     *(__be64 *)&c.ip_hi = *(__be64 *)(lip->s6_addr);
0055     *(__be64 *)&c.ip_lo = *(__be64 *)(lip->s6_addr + 8);
0056     return t4_wr_mbox_meat(adap, adap->mbox, &c, sizeof(c), &c, false);
0057 }
0058 
0059 static int clip6_release_mbox(const struct net_device *dev,
0060                   const struct in6_addr *lip)
0061 {
0062     struct adapter *adap = netdev2adap(dev);
0063     struct fw_clip_cmd c;
0064 
0065     memset(&c, 0, sizeof(c));
0066     c.op_to_write = htonl(FW_CMD_OP_V(FW_CLIP_CMD) |
0067                   FW_CMD_REQUEST_F | FW_CMD_READ_F);
0068     c.alloc_to_len16 = htonl(FW_CLIP_CMD_FREE_F | FW_LEN16(c));
0069     *(__be64 *)&c.ip_hi = *(__be64 *)(lip->s6_addr);
0070     *(__be64 *)&c.ip_lo = *(__be64 *)(lip->s6_addr + 8);
0071     return t4_wr_mbox_meat(adap, adap->mbox, &c, sizeof(c), &c, false);
0072 }
0073 
0074 int cxgb4_clip_get(const struct net_device *dev, const u32 *lip, u8 v6)
0075 {
0076     struct adapter *adap = netdev2adap(dev);
0077     struct clip_tbl *ctbl = adap->clipt;
0078     struct clip_entry *ce, *cte;
0079     u32 *addr = (u32 *)lip;
0080     int hash;
0081     int ret = -1;
0082 
0083     if (!ctbl)
0084         return 0;
0085 
0086     hash = clip_addr_hash(ctbl, addr, v6);
0087 
0088     read_lock_bh(&ctbl->lock);
0089     list_for_each_entry(cte, &ctbl->hash_list[hash], list) {
0090         if (cte->addr6.sin6_family == AF_INET6 && v6)
0091             ret = memcmp(lip, cte->addr6.sin6_addr.s6_addr,
0092                      sizeof(struct in6_addr));
0093         else if (cte->addr.sin_family == AF_INET && !v6)
0094             ret = memcmp(lip, (char *)(&cte->addr.sin_addr),
0095                      sizeof(struct in_addr));
0096         if (!ret) {
0097             ce = cte;
0098             read_unlock_bh(&ctbl->lock);
0099             refcount_inc(&ce->refcnt);
0100             return 0;
0101         }
0102     }
0103     read_unlock_bh(&ctbl->lock);
0104 
0105     write_lock_bh(&ctbl->lock);
0106     if (!list_empty(&ctbl->ce_free_head)) {
0107         ce = list_first_entry(&ctbl->ce_free_head,
0108                       struct clip_entry, list);
0109         list_del_init(&ce->list);
0110         spin_lock_init(&ce->lock);
0111         refcount_set(&ce->refcnt, 0);
0112         atomic_dec(&ctbl->nfree);
0113         list_add_tail(&ce->list, &ctbl->hash_list[hash]);
0114         if (v6) {
0115             ce->addr6.sin6_family = AF_INET6;
0116             memcpy(ce->addr6.sin6_addr.s6_addr,
0117                    lip, sizeof(struct in6_addr));
0118             ret = clip6_get_mbox(dev, (const struct in6_addr *)lip);
0119             if (ret) {
0120                 write_unlock_bh(&ctbl->lock);
0121                 dev_err(adap->pdev_dev,
0122                     "CLIP FW cmd failed with error %d, "
0123                     "Connections using %pI6c wont be "
0124                     "offloaded",
0125                     ret, ce->addr6.sin6_addr.s6_addr);
0126                 return ret;
0127             }
0128         } else {
0129             ce->addr.sin_family = AF_INET;
0130             memcpy((char *)(&ce->addr.sin_addr), lip,
0131                    sizeof(struct in_addr));
0132         }
0133     } else {
0134         write_unlock_bh(&ctbl->lock);
0135         dev_info(adap->pdev_dev, "CLIP table overflow, "
0136              "Connections using %pI6c wont be offloaded",
0137              (void *)lip);
0138         return -ENOMEM;
0139     }
0140     write_unlock_bh(&ctbl->lock);
0141     refcount_set(&ce->refcnt, 1);
0142     return 0;
0143 }
0144 EXPORT_SYMBOL(cxgb4_clip_get);
0145 
0146 void cxgb4_clip_release(const struct net_device *dev, const u32 *lip, u8 v6)
0147 {
0148     struct adapter *adap = netdev2adap(dev);
0149     struct clip_tbl *ctbl = adap->clipt;
0150     struct clip_entry *ce, *cte;
0151     u32 *addr = (u32 *)lip;
0152     int hash;
0153     int ret = -1;
0154 
0155     if (!ctbl)
0156         return;
0157 
0158     hash = clip_addr_hash(ctbl, addr, v6);
0159 
0160     read_lock_bh(&ctbl->lock);
0161     list_for_each_entry(cte, &ctbl->hash_list[hash], list) {
0162         if (cte->addr6.sin6_family == AF_INET6 && v6)
0163             ret = memcmp(lip, cte->addr6.sin6_addr.s6_addr,
0164                      sizeof(struct in6_addr));
0165         else if (cte->addr.sin_family == AF_INET && !v6)
0166             ret = memcmp(lip, (char *)(&cte->addr.sin_addr),
0167                      sizeof(struct in_addr));
0168         if (!ret) {
0169             ce = cte;
0170             read_unlock_bh(&ctbl->lock);
0171             goto found;
0172         }
0173     }
0174     read_unlock_bh(&ctbl->lock);
0175 
0176     return;
0177 found:
0178     write_lock_bh(&ctbl->lock);
0179     spin_lock_bh(&ce->lock);
0180     if (refcount_dec_and_test(&ce->refcnt)) {
0181         list_del_init(&ce->list);
0182         list_add_tail(&ce->list, &ctbl->ce_free_head);
0183         atomic_inc(&ctbl->nfree);
0184         if (v6)
0185             clip6_release_mbox(dev, (const struct in6_addr *)lip);
0186     }
0187     spin_unlock_bh(&ce->lock);
0188     write_unlock_bh(&ctbl->lock);
0189 }
0190 EXPORT_SYMBOL(cxgb4_clip_release);
0191 
0192 /* Retrieves IPv6 addresses from a root device (bond, vlan) associated with
0193  * a physical device.
0194  * The physical device reference is needed to send the actul CLIP command.
0195  */
0196 static int cxgb4_update_dev_clip(struct net_device *root_dev,
0197                  struct net_device *dev)
0198 {
0199     struct inet6_dev *idev = NULL;
0200     struct inet6_ifaddr *ifa;
0201     int ret = 0;
0202 
0203     idev = __in6_dev_get(root_dev);
0204     if (!idev)
0205         return ret;
0206 
0207     read_lock_bh(&idev->lock);
0208     list_for_each_entry(ifa, &idev->addr_list, if_list) {
0209         ret = cxgb4_clip_get(dev, (const u32 *)ifa->addr.s6_addr, 1);
0210         if (ret < 0)
0211             break;
0212     }
0213     read_unlock_bh(&idev->lock);
0214 
0215     return ret;
0216 }
0217 
0218 int cxgb4_update_root_dev_clip(struct net_device *dev)
0219 {
0220     struct net_device *root_dev = NULL;
0221     int i, ret = 0;
0222 
0223     /* First populate the real net device's IPv6 addresses */
0224     ret = cxgb4_update_dev_clip(dev, dev);
0225     if (ret)
0226         return ret;
0227 
0228     /* Parse all bond and vlan devices layered on top of the physical dev */
0229     root_dev = netdev_master_upper_dev_get_rcu(dev);
0230     if (root_dev) {
0231         ret = cxgb4_update_dev_clip(root_dev, dev);
0232         if (ret)
0233             return ret;
0234     }
0235 
0236     for (i = 0; i < VLAN_N_VID; i++) {
0237         root_dev = __vlan_find_dev_deep_rcu(dev, htons(ETH_P_8021Q), i);
0238         if (!root_dev)
0239             continue;
0240 
0241         ret = cxgb4_update_dev_clip(root_dev, dev);
0242         if (ret)
0243             break;
0244     }
0245 
0246     return ret;
0247 }
0248 EXPORT_SYMBOL(cxgb4_update_root_dev_clip);
0249 
0250 int clip_tbl_show(struct seq_file *seq, void *v)
0251 {
0252     struct adapter *adapter = seq->private;
0253     struct clip_tbl *ctbl = adapter->clipt;
0254     struct clip_entry *ce;
0255     char ip[60];
0256     int i;
0257 
0258     read_lock_bh(&ctbl->lock);
0259 
0260     seq_puts(seq, "IP Address                  Users\n");
0261     for (i = 0 ; i < ctbl->clipt_size;  ++i) {
0262         list_for_each_entry(ce, &ctbl->hash_list[i], list) {
0263             ip[0] = '\0';
0264             sprintf(ip, "%pISc", &ce->addr);
0265             seq_printf(seq, "%-25s   %u\n", ip,
0266                    refcount_read(&ce->refcnt));
0267         }
0268     }
0269     seq_printf(seq, "Free clip entries : %d\n", atomic_read(&ctbl->nfree));
0270 
0271     read_unlock_bh(&ctbl->lock);
0272 
0273     return 0;
0274 }
0275 
0276 struct clip_tbl *t4_init_clip_tbl(unsigned int clipt_start,
0277                   unsigned int clipt_end)
0278 {
0279     struct clip_entry *cl_list;
0280     struct clip_tbl *ctbl;
0281     unsigned int clipt_size;
0282     int i;
0283 
0284     if (clipt_start >= clipt_end)
0285         return NULL;
0286     clipt_size = clipt_end - clipt_start + 1;
0287     if (clipt_size < CLIPT_MIN_HASH_BUCKETS)
0288         return NULL;
0289 
0290     ctbl = kvzalloc(struct_size(ctbl, hash_list, clipt_size), GFP_KERNEL);
0291     if (!ctbl)
0292         return NULL;
0293 
0294     ctbl->clipt_start = clipt_start;
0295     ctbl->clipt_size = clipt_size;
0296     INIT_LIST_HEAD(&ctbl->ce_free_head);
0297 
0298     atomic_set(&ctbl->nfree, clipt_size);
0299     rwlock_init(&ctbl->lock);
0300 
0301     for (i = 0; i < ctbl->clipt_size; ++i)
0302         INIT_LIST_HEAD(&ctbl->hash_list[i]);
0303 
0304     cl_list = kvcalloc(clipt_size, sizeof(struct clip_entry), GFP_KERNEL);
0305     if (!cl_list) {
0306         kvfree(ctbl);
0307         return NULL;
0308     }
0309     ctbl->cl_list = (void *)cl_list;
0310 
0311     for (i = 0; i < clipt_size; i++) {
0312         INIT_LIST_HEAD(&cl_list[i].list);
0313         list_add_tail(&cl_list[i].list, &ctbl->ce_free_head);
0314     }
0315 
0316     return ctbl;
0317 }
0318 
0319 void t4_cleanup_clip_tbl(struct adapter *adap)
0320 {
0321     struct clip_tbl *ctbl = adap->clipt;
0322 
0323     if (ctbl) {
0324         kvfree(ctbl->cl_list);
0325         kvfree(ctbl);
0326     }
0327 }
0328 EXPORT_SYMBOL(t4_cleanup_clip_tbl);