/* * This file is part of the Chelsio T4 Ethernet driver for Linux. * * Copyright (c) 2003-2014 Chelsio Communications, Inc. All rights reserved. * * This software is available to you under a choice of one of two * licenses. You may choose to be licensed under the terms of the GNU * General Public License (GPL) Version 2, available from the file * COPYING in the main directory of this source tree, or the * OpenIB.org BSD license below: * * Redistribution and use in source and binary forms, with or * without modification, are permitted provided that the following * conditions are met: * * - Redistributions of source code must retain the above * copyright notice, this list of conditions and the following * disclaimer. * * - Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following * disclaimer in the documentation and/or other materials * provided with the distribution. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE.
*/
struct l2t_data { unsignedint l2t_start; /* start index of our piece of the L2T */ unsignedint l2t_size; /* number of entries in l2tab */
rwlock_t lock;
atomic_t nfree; /* number of free entries */ struct l2t_entry *rover; /* starting point for next allocation */ struct l2t_entry l2tab[] __counted_by(l2t_size); /* MUST BE LAST */
};
/* * To avoid having to check address families we do not allow v4 and v6 * neighbors to be on the same hash chain. We keep v4 entries in the first * half of available hash buckets and v6 in the second. We need at least two * entries in our L2T for this scheme to work.
*/ enum {
L2T_MIN_HASH_BUCKETS = 2,
};
/* * Checks if an L2T entry is for the given IP/IPv6 address. It does not check * whether the L2T entry and the address are of the same address family. * Callers ensure an address is only checked against L2T entries of the same * family, something made trivial by the separation of IP and IPv6 hash chains * mentioned above. Returns 0 if there's a match,
*/ staticint addreq(conststruct l2t_entry *e, const u32 *addr)
{ if (e->v6) return (e->addr[0] ^ addr[0]) | (e->addr[1] ^ addr[1]) |
(e->addr[2] ^ addr[2]) | (e->addr[3] ^ addr[3]); return e->addr[0] ^ addr[0];
}
/* * Write an L2T entry. Must be called with the entry locked. * The write may be synchronous or asynchronous.
*/ staticint write_l2e(struct adapter *adap, struct l2t_entry *e, int sync)
{ struct l2t_data *d = adap->l2t; unsignedint l2t_idx = e->idx + d->l2t_start; struct sk_buff *skb; struct cpl_l2t_write_req *req;
skb = alloc_skb(sizeof(*req), GFP_ATOMIC); if (!skb) return -ENOMEM;
/* * Send packets waiting in an L2T entry's ARP queue. Must be called with the * entry locked.
*/ staticvoid send_pending(struct adapter *adap, struct l2t_entry *e)
{ struct sk_buff *skb;
while ((skb = __skb_dequeue(&e->arpq)) != NULL)
t4_ofld_send(adap, skb);
}
/* * Process a CPL_L2T_WRITE_RPL. Wake up the ARP queue if it completes a * synchronous L2T_WRITE. Note that the TID in the reply is really the L2T * index it refers to.
*/ void do_l2t_write_rpl(struct adapter *adap, conststruct cpl_l2t_write_rpl *rpl)
{ struct l2t_data *d = adap->l2t; unsignedint tid = GET_TID(rpl); unsignedint l2t_idx = tid % L2T_SIZE;
if (unlikely(rpl->status != CPL_ERR_NONE)) {
dev_err(adap->pdev_dev, "Unexpected L2T_WRITE_RPL status %u for entry %u\n",
rpl->status, l2t_idx); return;
}
/* * Add a packet to an L2T entry's queue of packets awaiting resolution. * Must be called with the entry's lock held.
*/ staticinlinevoid arpq_enqueue(struct l2t_entry *e, struct sk_buff *skb)
{
__skb_queue_tail(&e->arpq, skb);
}
again: switch (e->state) { case L2T_STATE_STALE: /* entry is stale, kick off revalidation */
neigh_event_send(e->neigh, NULL);
spin_lock_bh(&e->lock); if (e->state == L2T_STATE_STALE)
e->state = L2T_STATE_VALID;
spin_unlock_bh(&e->lock);
fallthrough; case L2T_STATE_VALID: /* fast-path, send the packet on */ return t4_ofld_send(adap, skb); case L2T_STATE_RESOLVING: case L2T_STATE_SYNC_WRITE:
spin_lock_bh(&e->lock); if (e->state != L2T_STATE_SYNC_WRITE &&
e->state != L2T_STATE_RESOLVING) {
spin_unlock_bh(&e->lock); goto again;
}
arpq_enqueue(e, skb);
spin_unlock_bh(&e->lock);
if (e->state == L2T_STATE_RESOLVING &&
!neigh_event_send(e->neigh, NULL)) {
spin_lock_bh(&e->lock); if (e->state == L2T_STATE_RESOLVING &&
!skb_queue_empty(&e->arpq))
write_l2e(adap, e, 1);
spin_unlock_bh(&e->lock);
}
} return 0;
}
EXPORT_SYMBOL(cxgb4_l2t_send);
/* * Allocate a free L2T entry. Must be called with l2t_data.lock held.
*/ staticstruct l2t_entry *alloc_l2e(struct l2t_data *d)
{ struct l2t_entry *end, *e, **p;
if (!atomic_read(&d->nfree)) return NULL;
/* there's definitely a free entry */ for (e = d->rover, end = &d->l2tab[d->l2t_size]; e != end; ++e) if (atomic_read(&e->refcnt) == 0) goto found;
for (e = d->l2tab; atomic_read(&e->refcnt); ++e)
;
found:
d->rover = e + 1;
atomic_dec(&d->nfree);
/* * The entry we found may be an inactive entry that is * presently in the hash table. We need to remove it.
*/ if (e->state < L2T_STATE_SWITCHING) for (p = &d->l2tab[e->hash].first; *p; p = &(*p)->next) if (*p == e) {
*p = e->next;
e->next = NULL; break;
}
for (e = &d->l2tab[0], end = &d->l2tab[d->l2t_size]; e != end; ++e) { if (atomic_read(&e->refcnt) == 0) { if (!first_free)
first_free = e;
} else { if (e->state == L2T_STATE_SWITCHING) { if (ether_addr_equal(e->dmac, dmac) &&
(e->vlan == vlan) && (e->lport == port)) goto exists;
}
}
}
if (first_free) {
e = first_free; goto found;
}
return NULL;
found: /* The entry we found may be an inactive entry that is * presently in the hash table. We need to remove it.
*/ if (e->state < L2T_STATE_SWITCHING) for (p = &d->l2tab[e->hash].first; *p; p = &(*p)->next) if (*p == e) {
*p = e->next;
e->next = NULL; break;
}
e->state = L2T_STATE_UNUSED;
exists: return e;
}
/* Called when an L2T entry has no more users. The entry is left in the hash * table since it is likely to be reused but we also bump nfree to indicate * that the entry can be reallocated for a different neighbor. We also drop * the existing neighbor reference in case the neighbor is going away and is * waiting on our reference. * * Because entries can be reallocated to other neighbors once their ref count * drops to 0 we need to take the entry's lock to avoid races with a new * incarnation.
*/ staticvoid _t4_l2e_free(struct l2t_entry *e)
{ struct l2t_data *d;
if (atomic_read(&e->refcnt) == 0) { /* hasn't been recycled */ if (e->neigh) {
neigh_release(e->neigh);
e->neigh = NULL;
}
__skb_queue_purge(&e->arpq);
}
d = container_of(e, struct l2t_data, l2tab[e->idx]);
atomic_inc(&d->nfree);
}
/* Locked version of _t4_l2e_free */ staticvoid t4_l2e_free(struct l2t_entry *e)
{ struct l2t_data *d;
spin_lock_bh(&e->lock); if (atomic_read(&e->refcnt) == 0) { /* hasn't been recycled */ if (e->neigh) {
neigh_release(e->neigh);
e->neigh = NULL;
}
__skb_queue_purge(&e->arpq);
}
spin_unlock_bh(&e->lock);
d = container_of(e, struct l2t_data, l2tab[e->idx]);
atomic_inc(&d->nfree);
}
void cxgb4_l2t_release(struct l2t_entry *e)
{ if (atomic_dec_and_test(&e->refcnt))
t4_l2e_free(e);
}
EXPORT_SYMBOL(cxgb4_l2t_release);
/* * Update an L2T entry that was previously used for the same next hop as neigh. * Must be called with softirqs disabled.
*/ staticvoid reuse_entry(struct l2t_entry *e, struct neighbour *neigh)
{ unsignedint nud_state;
/* Initialize each of the fields which we care about which are present * in the Compressed Filter Tuple.
*/ if (tp->vlan_shift >= 0 && l2t->vlan != VLAN_NONE)
ntuple |= (u64)(FT_VLAN_VLD_F | l2t->vlan) << tp->vlan_shift;
if (tp->port_shift >= 0)
ntuple |= (u64)l2t->lport << tp->port_shift;
if (tp->protocol_shift >= 0)
ntuple |= (u64)IPPROTO_TCP << tp->protocol_shift;
/* * Called when the host's neighbor layer makes a change to some entry that is * loaded into the HW L2 table.
*/ void t4_l2t_update(struct adapter *adap, struct neighbour *neigh)
{ unsignedint addr_len = neigh->tbl->key_len;
u32 *addr = (u32 *) neigh->primary_key; int hash, ifidx = neigh->dev->ifindex; struct sk_buff_head *arpq = NULL; struct l2t_data *d = adap->l2t; struct l2t_entry *e;
hash = addr_hash(d, addr, addr_len, ifidx);
read_lock_bh(&d->lock); for (e = d->l2tab[hash].first; e; e = e->next) if (!addreq(e, addr) && e->ifindex == ifidx) {
spin_lock(&e->lock); if (atomic_read(&e->refcnt)) goto found;
spin_unlock(&e->lock); break;
}
read_unlock_bh(&d->lock); return;
found:
read_unlock(&d->lock);
if (neigh != e->neigh)
neigh_replace(e, neigh);
if (e->state == L2T_STATE_RESOLVING) { if (neigh->nud_state & NUD_FAILED) {
arpq = &e->arpq;
} elseif ((neigh->nud_state & (NUD_CONNECTED | NUD_STALE)) &&
!skb_queue_empty(&e->arpq)) {
write_l2e(adap, e, 1);
}
} else {
e->state = neigh->nud_state & NUD_CONNECTED ?
L2T_STATE_VALID : L2T_STATE_STALE; if (memcmp(e->dmac, neigh->ha, sizeof(e->dmac)))
write_l2e(adap, e, 0);
}
if (arpq) { struct sk_buff *skb;
/* Called when address resolution fails for an L2T * entry to handle packets on the arpq head. If a * packet specifies a failure handler it is invoked, * otherwise the packet is sent to the device.
*/ while ((skb = __skb_dequeue(&e->arpq)) != NULL) { conststruct l2t_skb_cb *cb = L2T_SKB_CB(skb);
/* Allocate an L2T entry for use by a switching rule. Such need to be * explicitly freed and while busy they are not on any hash chain, so normal * address resolution updates do not see them.
*/ struct l2t_entry *t4_l2t_alloc_switching(struct adapter *adap, u16 vlan,
u8 port, u8 *eth_addr)
{ struct l2t_data *d = adap->l2t; struct l2t_entry *e; int ret;
write_lock_bh(&d->lock);
e = find_or_alloc_l2e(d, vlan, port, eth_addr); if (e) {
spin_lock(&e->lock); /* avoid race with t4_l2t_free */ if (!atomic_read(&e->refcnt)) {
e->state = L2T_STATE_SWITCHING;
e->vlan = vlan;
e->lport = port;
ether_addr_copy(e->dmac, eth_addr);
atomic_set(&e->refcnt, 1);
ret = write_l2e(adap, e, 0); if (ret < 0) {
_t4_l2e_free(e);
spin_unlock(&e->lock);
write_unlock_bh(&d->lock); return NULL;
}
} else {
atomic_inc(&e->refcnt);
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.