329 lines · c
1/*2 * This file is part of the Chelsio T4 Ethernet driver for Linux.3 * Copyright (C) 2003-2014 Chelsio Communications. All rights reserved.4 *5 * Written by Deepak (deepak.s@chelsio.com)6 *7 * This program is distributed in the hope that it will be useful, but WITHOUT8 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or9 * FITNESS FOR A PARTICULAR PURPOSE. See the LICENSE file included in this10 * release for licensing terms and conditions.11 */12 13#include <linux/module.h>14#include <linux/netdevice.h>15#include <linux/jhash.h>16#include <linux/if_vlan.h>17#include <net/addrconf.h>18#include "cxgb4.h"19#include "clip_tbl.h"20 21static inline unsigned int ipv4_clip_hash(struct clip_tbl *c, const u32 *key)22{23 unsigned int clipt_size_half = c->clipt_size / 2;24 25 return jhash_1word(*key, 0) % clipt_size_half;26}27 28static inline unsigned int ipv6_clip_hash(struct clip_tbl *d, const u32 *key)29{30 unsigned int clipt_size_half = d->clipt_size / 2;31 u32 xor = key[0] ^ key[1] ^ key[2] ^ key[3];32 33 return clipt_size_half +34 (jhash_1word(xor, 0) % clipt_size_half);35}36 37static unsigned int clip_addr_hash(struct clip_tbl *ctbl, const u32 *addr,38 u8 v6)39{40 return v6 ? ipv6_clip_hash(ctbl, addr) :41 ipv4_clip_hash(ctbl, addr);42}43 44static int clip6_get_mbox(const struct net_device *dev,45 const struct in6_addr *lip)46{47 struct adapter *adap = netdev2adap(dev);48 struct fw_clip_cmd c;49 50 memset(&c, 0, sizeof(c));51 c.op_to_write = htonl(FW_CMD_OP_V(FW_CLIP_CMD) |52 FW_CMD_REQUEST_F | FW_CMD_WRITE_F);53 c.alloc_to_len16 = htonl(FW_CLIP_CMD_ALLOC_F | FW_LEN16(c));54 *(__be64 *)&c.ip_hi = *(__be64 *)(lip->s6_addr);55 *(__be64 *)&c.ip_lo = *(__be64 *)(lip->s6_addr + 8);56 return t4_wr_mbox_meat(adap, adap->mbox, &c, sizeof(c), &c, false);57}58 59static int clip6_release_mbox(const struct net_device *dev,60 const struct in6_addr *lip)61{62 struct adapter *adap = netdev2adap(dev);63 struct fw_clip_cmd c;64 65 memset(&c, 0, sizeof(c));66 c.op_to_write = htonl(FW_CMD_OP_V(FW_CLIP_CMD) |67 FW_CMD_REQUEST_F | FW_CMD_READ_F);68 c.alloc_to_len16 = htonl(FW_CLIP_CMD_FREE_F | FW_LEN16(c));69 *(__be64 *)&c.ip_hi = *(__be64 *)(lip->s6_addr);70 *(__be64 *)&c.ip_lo = *(__be64 *)(lip->s6_addr + 8);71 return t4_wr_mbox_meat(adap, adap->mbox, &c, sizeof(c), &c, false);72}73 74int cxgb4_clip_get(const struct net_device *dev, const u32 *lip, u8 v6)75{76 struct adapter *adap = netdev2adap(dev);77 struct clip_tbl *ctbl = adap->clipt;78 struct clip_entry *ce, *cte;79 u32 *addr = (u32 *)lip;80 int hash;81 int ret = -1;82 83 if (!ctbl)84 return 0;85 86 hash = clip_addr_hash(ctbl, addr, v6);87 88 read_lock_bh(&ctbl->lock);89 list_for_each_entry(cte, &ctbl->hash_list[hash], list) {90 if (cte->addr6.sin6_family == AF_INET6 && v6)91 ret = memcmp(lip, cte->addr6.sin6_addr.s6_addr,92 sizeof(struct in6_addr));93 else if (cte->addr.sin_family == AF_INET && !v6)94 ret = memcmp(lip, (char *)(&cte->addr.sin_addr),95 sizeof(struct in_addr));96 if (!ret) {97 ce = cte;98 read_unlock_bh(&ctbl->lock);99 refcount_inc(&ce->refcnt);100 return 0;101 }102 }103 read_unlock_bh(&ctbl->lock);104 105 write_lock_bh(&ctbl->lock);106 if (!list_empty(&ctbl->ce_free_head)) {107 ce = list_first_entry(&ctbl->ce_free_head,108 struct clip_entry, list);109 list_del_init(&ce->list);110 spin_lock_init(&ce->lock);111 refcount_set(&ce->refcnt, 0);112 atomic_dec(&ctbl->nfree);113 list_add_tail(&ce->list, &ctbl->hash_list[hash]);114 if (v6) {115 ce->addr6.sin6_family = AF_INET6;116 memcpy(ce->addr6.sin6_addr.s6_addr,117 lip, sizeof(struct in6_addr));118 ret = clip6_get_mbox(dev, (const struct in6_addr *)lip);119 if (ret) {120 write_unlock_bh(&ctbl->lock);121 dev_err(adap->pdev_dev,122 "CLIP FW cmd failed with error %d, "123 "Connections using %pI6c wont be "124 "offloaded",125 ret, ce->addr6.sin6_addr.s6_addr);126 return ret;127 }128 } else {129 ce->addr.sin_family = AF_INET;130 memcpy((char *)(&ce->addr.sin_addr), lip,131 sizeof(struct in_addr));132 }133 } else {134 write_unlock_bh(&ctbl->lock);135 dev_info(adap->pdev_dev, "CLIP table overflow, "136 "Connections using %pI6c wont be offloaded",137 (void *)lip);138 return -ENOMEM;139 }140 write_unlock_bh(&ctbl->lock);141 refcount_set(&ce->refcnt, 1);142 return 0;143}144EXPORT_SYMBOL(cxgb4_clip_get);145 146void cxgb4_clip_release(const struct net_device *dev, const u32 *lip, u8 v6)147{148 struct adapter *adap = netdev2adap(dev);149 struct clip_tbl *ctbl = adap->clipt;150 struct clip_entry *ce, *cte;151 u32 *addr = (u32 *)lip;152 int hash;153 int ret = -1;154 155 if (!ctbl)156 return;157 158 hash = clip_addr_hash(ctbl, addr, v6);159 160 read_lock_bh(&ctbl->lock);161 list_for_each_entry(cte, &ctbl->hash_list[hash], list) {162 if (cte->addr6.sin6_family == AF_INET6 && v6)163 ret = memcmp(lip, cte->addr6.sin6_addr.s6_addr,164 sizeof(struct in6_addr));165 else if (cte->addr.sin_family == AF_INET && !v6)166 ret = memcmp(lip, (char *)(&cte->addr.sin_addr),167 sizeof(struct in_addr));168 if (!ret) {169 ce = cte;170 read_unlock_bh(&ctbl->lock);171 goto found;172 }173 }174 read_unlock_bh(&ctbl->lock);175 176 return;177found:178 write_lock_bh(&ctbl->lock);179 spin_lock_bh(&ce->lock);180 if (refcount_dec_and_test(&ce->refcnt)) {181 list_del_init(&ce->list);182 list_add_tail(&ce->list, &ctbl->ce_free_head);183 atomic_inc(&ctbl->nfree);184 if (v6)185 clip6_release_mbox(dev, (const struct in6_addr *)lip);186 }187 spin_unlock_bh(&ce->lock);188 write_unlock_bh(&ctbl->lock);189}190EXPORT_SYMBOL(cxgb4_clip_release);191 192/* Retrieves IPv6 addresses from a root device (bond, vlan) associated with193 * a physical device.194 * The physical device reference is needed to send the actul CLIP command.195 */196static int cxgb4_update_dev_clip(struct net_device *root_dev,197 struct net_device *dev)198{199 struct inet6_dev *idev = NULL;200 struct inet6_ifaddr *ifa;201 int ret = 0;202 203 idev = __in6_dev_get(root_dev);204 if (!idev)205 return ret;206 207 read_lock_bh(&idev->lock);208 list_for_each_entry(ifa, &idev->addr_list, if_list) {209 ret = cxgb4_clip_get(dev, (const u32 *)ifa->addr.s6_addr, 1);210 if (ret < 0)211 break;212 }213 read_unlock_bh(&idev->lock);214 215 return ret;216}217 218int cxgb4_update_root_dev_clip(struct net_device *dev)219{220 struct net_device *root_dev = NULL;221 int i, ret = 0;222 223 /* First populate the real net device's IPv6 addresses */224 ret = cxgb4_update_dev_clip(dev, dev);225 if (ret)226 return ret;227 228 /* Parse all bond and vlan devices layered on top of the physical dev */229 root_dev = netdev_master_upper_dev_get_rcu(dev);230 if (root_dev) {231 ret = cxgb4_update_dev_clip(root_dev, dev);232 if (ret)233 return ret;234 }235 236 for (i = 0; i < VLAN_N_VID; i++) {237 root_dev = __vlan_find_dev_deep_rcu(dev, htons(ETH_P_8021Q), i);238 if (!root_dev)239 continue;240 241 ret = cxgb4_update_dev_clip(root_dev, dev);242 if (ret)243 break;244 }245 246 return ret;247}248EXPORT_SYMBOL(cxgb4_update_root_dev_clip);249 250int clip_tbl_show(struct seq_file *seq, void *v)251{252 struct adapter *adapter = seq->private;253 struct clip_tbl *ctbl = adapter->clipt;254 struct clip_entry *ce;255 char ip[60];256 int i;257 258 read_lock_bh(&ctbl->lock);259 260 seq_puts(seq, "IP Address Users\n");261 for (i = 0 ; i < ctbl->clipt_size; ++i) {262 list_for_each_entry(ce, &ctbl->hash_list[i], list) {263 ip[0] = '\0';264 sprintf(ip, "%pISc", &ce->addr);265 seq_printf(seq, "%-25s %u\n", ip,266 refcount_read(&ce->refcnt));267 }268 }269 seq_printf(seq, "Free clip entries : %d\n", atomic_read(&ctbl->nfree));270 271 read_unlock_bh(&ctbl->lock);272 273 return 0;274}275 276struct clip_tbl *t4_init_clip_tbl(unsigned int clipt_start,277 unsigned int clipt_end)278{279 struct clip_entry *cl_list;280 struct clip_tbl *ctbl;281 unsigned int clipt_size;282 int i;283 284 if (clipt_start >= clipt_end)285 return NULL;286 clipt_size = clipt_end - clipt_start + 1;287 if (clipt_size < CLIPT_MIN_HASH_BUCKETS)288 return NULL;289 290 ctbl = kvzalloc(struct_size(ctbl, hash_list, clipt_size), GFP_KERNEL);291 if (!ctbl)292 return NULL;293 294 ctbl->clipt_start = clipt_start;295 ctbl->clipt_size = clipt_size;296 INIT_LIST_HEAD(&ctbl->ce_free_head);297 298 atomic_set(&ctbl->nfree, clipt_size);299 rwlock_init(&ctbl->lock);300 301 for (i = 0; i < ctbl->clipt_size; ++i)302 INIT_LIST_HEAD(&ctbl->hash_list[i]);303 304 cl_list = kvcalloc(clipt_size, sizeof(struct clip_entry), GFP_KERNEL);305 if (!cl_list) {306 kvfree(ctbl);307 return NULL;308 }309 ctbl->cl_list = (void *)cl_list;310 311 for (i = 0; i < clipt_size; i++) {312 INIT_LIST_HEAD(&cl_list[i].list);313 list_add_tail(&cl_list[i].list, &ctbl->ce_free_head);314 }315 316 return ctbl;317}318 319void t4_cleanup_clip_tbl(struct adapter *adap)320{321 struct clip_tbl *ctbl = adap->clipt;322 323 if (ctbl) {324 kvfree(ctbl->cl_list);325 kvfree(ctbl);326 }327}328EXPORT_SYMBOL(t4_cleanup_clip_tbl);329