diff options
author | Navdeep Parhar <np@FreeBSD.org> | 2011-12-16 02:09:51 +0000 |
---|---|---|
committer | Navdeep Parhar <np@FreeBSD.org> | 2011-12-16 02:09:51 +0000 |
commit | 733b92779e406798c19bf5122279f5c3ecbbd64d (patch) | |
tree | 3f54f68c887ea6a17b5427746efe66dfa931b56d /sys/dev/cxgbe/t4_l2t.c | |
parent | 22ea9f58f0c0b19ed8fdf8042f08aa3a243408e0 (diff) |
Many updates to cxgbe(4)
- Device configuration via plain text config file. Also able to operate
when not attached to the chip as the master driver.
- Generic "work request" queue that serves as the base for both ctrl and
ofld tx queues.
- Generic interrupt handler routine that can process any event on any
kind of ingress queue (via a dispatch table).
- A couple of new driver ioctls. cxgbetool can now install a firmware
to the card ("loadfw" command) and can read the card's memory
("memdump" and "tcb" commands).
- Lots of assorted information within dev.t4nex.X.misc.* This is
primarily for debugging and won't show up in sysctl -a.
- Code to manage the L2 tables on the chip.
- Updates to cxgbe(4) man page to go with the tunables that have changed.
- Updates to the shared code in common/
- Updates to the driver-firmware interface (now at fw 1.4.16.0)
MFC after: 1 month
Notes
Notes:
svn path=/head/; revision=228561
Diffstat (limited to 'sys/dev/cxgbe/t4_l2t.c')
-rw-r--r-- | sys/dev/cxgbe/t4_l2t.c | 675 |
1 files changed, 549 insertions, 126 deletions
diff --git a/sys/dev/cxgbe/t4_l2t.c b/sys/dev/cxgbe/t4_l2t.c index 31197b8654f7..be206c1fe892 100644 --- a/sys/dev/cxgbe/t4_l2t.c +++ b/sys/dev/cxgbe/t4_l2t.c @@ -37,7 +37,9 @@ __FBSDID("$FreeBSD$"); #include <sys/mutex.h> #include <sys/rwlock.h> #include <sys/socket.h> +#include <sys/sbuf.h> #include <net/if.h> +#include <net/if_types.h> #include <net/ethernet.h> #include <net/if_vlan_var.h> #include <net/if_dl.h> @@ -50,9 +52,26 @@ __FBSDID("$FreeBSD$"); #include "common/common.h" #include "common/jhash.h" #include "common/t4_msg.h" -#include "offload.h" #include "t4_l2t.h" +/* + * Module locking notes: There is a RW lock protecting the L2 table as a + * whole plus a spinlock per L2T entry. Entry lookups and allocations happen + * under the protection of the table lock, individual entry changes happen + * while holding that entry's spinlock. The table lock nests outside the + * entry locks. Allocations of new entries take the table lock as writers so + * no other lookups can happen while allocating new entries. Entry updates + * take the table lock as readers so multiple entries can be updated in + * parallel. An L2T entry can be dropped by decrementing its reference count + * and therefore can happen in parallel with entry allocation but no entry + * can change state or increment its ref count during allocation as both of + * these perform lookups. + * + * Note: We do not take refereces to ifnets in this module because both + * the TOE and the sockets already hold references to the interfaces and the + * lifetime of an L2T entry is fully contained in the lifetime of the TOE. + */ + /* identifies sync vs async L2T_WRITE_REQs */ #define S_SYNC_WR 12 #define V_SYNC_WR(x) ((x) << S_SYNC_WR) @@ -76,34 +95,251 @@ struct l2t_data { struct l2t_entry l2tab[L2T_SIZE]; }; +static int do_l2t_write_rpl(struct sge_iq *, const struct rss_header *, + struct mbuf *); + +#define VLAN_NONE 0xfff +#define SA(x) ((struct sockaddr *)(x)) +#define SIN(x) ((struct sockaddr_in *)(x)) +#define SINADDR(x) (SIN(x)->sin_addr.s_addr) + /* - * Module locking notes: There is a RW lock protecting the L2 table as a - * whole plus a spinlock per L2T entry. Entry lookups and allocations happen - * under the protection of the table lock, individual entry changes happen - * while holding that entry's spinlock. The table lock nests outside the - * entry locks. Allocations of new entries take the table lock as writers so - * no other lookups can happen while allocating new entries. Entry updates - * take the table lock as readers so multiple entries can be updated in - * parallel. An L2T entry can be dropped by decrementing its reference count - * and therefore can happen in parallel with entry allocation but no entry - * can change state or increment its ref count during allocation as both of - * these perform lookups. - * - * Note: We do not take refereces to ifnets in this module because both - * the TOE and the sockets already hold references to the interfaces and the - * lifetime of an L2T entry is fully contained in the lifetime of the TOE. + * Allocate a free L2T entry. Must be called with l2t_data.lock held. */ +static struct l2t_entry * +alloc_l2e(struct l2t_data *d) +{ + struct l2t_entry *end, *e, **p; + + rw_assert(&d->lock, RA_WLOCKED); + + if (!atomic_load_acq_int(&d->nfree)) + return (NULL); + + /* there's definitely a free entry */ + for (e = d->rover, end = &d->l2tab[L2T_SIZE]; e != end; ++e) + if (atomic_load_acq_int(&e->refcnt) == 0) + goto found; + + for (e = d->l2tab; atomic_load_acq_int(&e->refcnt); ++e) ; +found: + d->rover = e + 1; + atomic_subtract_int(&d->nfree, 1); + + /* + * 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; + return (e); +} + +/* + * Write an L2T entry. Must be called with the entry locked. + * The write may be synchronous or asynchronous. + */ +static int +write_l2e(struct adapter *sc, struct l2t_entry *e, int sync) +{ + struct mbuf *m; + struct cpl_l2t_write_req *req; + + mtx_assert(&e->lock, MA_OWNED); + + if ((m = m_gethdr(M_NOWAIT, MT_DATA)) == NULL) + return (ENOMEM); + + req = mtod(m, struct cpl_l2t_write_req *); + m->m_pkthdr.len = m->m_len = sizeof(*req); + + INIT_TP_WR(req, 0); + OPCODE_TID(req) = htonl(MK_OPCODE_TID(CPL_L2T_WRITE_REQ, e->idx | + V_SYNC_WR(sync) | V_TID_QID(sc->sge.fwq.abs_id))); + req->params = htons(V_L2T_W_PORT(e->lport) | V_L2T_W_NOREPLY(!sync)); + req->l2t_idx = htons(e->idx); + req->vlan = htons(e->vlan); + memcpy(req->dst_mac, e->dmac, sizeof(req->dst_mac)); + + t4_mgmt_tx(sc, m); + + if (sync && e->state != L2T_STATE_SWITCHING) + e->state = L2T_STATE_SYNC_WRITE; + + return (0); +} + +/* + * 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 l2t_data *d) +{ + struct l2t_entry *e; + + rw_rlock(&d->lock); + e = alloc_l2e(d); + if (e) { + mtx_lock(&e->lock); /* avoid race with t4_l2t_free */ + e->state = L2T_STATE_SWITCHING; + atomic_store_rel_int(&e->refcnt, 1); + mtx_unlock(&e->lock); + } + rw_runlock(&d->lock); + return e; +} + +/* + * Sets/updates the contents of a switching L2T entry that has been allocated + * with an earlier call to @t4_l2t_alloc_switching. + */ +int +t4_l2t_set_switching(struct adapter *sc, struct l2t_entry *e, uint16_t vlan, + uint8_t port, uint8_t *eth_addr) +{ + int rc; + + e->vlan = vlan; + e->lport = port; + memcpy(e->dmac, eth_addr, ETHER_ADDR_LEN); + mtx_lock(&e->lock); + rc = write_l2e(sc, e, 0); + mtx_unlock(&e->lock); + return (rc); +} + +int +t4_init_l2t(struct adapter *sc, int flags) +{ + int i; + struct l2t_data *d; + + d = malloc(sizeof(*d), M_CXGBE, M_ZERO | flags); + if (!d) + return (ENOMEM); + + d->rover = d->l2tab; + atomic_store_rel_int(&d->nfree, L2T_SIZE); + rw_init(&d->lock, "L2T"); + + for (i = 0; i < L2T_SIZE; i++) { + d->l2tab[i].idx = i; + d->l2tab[i].state = L2T_STATE_UNUSED; + mtx_init(&d->l2tab[i].lock, "L2T_E", NULL, MTX_DEF); + atomic_store_rel_int(&d->l2tab[i].refcnt, 0); + } + + sc->l2t = d; + t4_register_cpl_handler(sc, CPL_L2T_WRITE_RPL, do_l2t_write_rpl); + + return (0); +} + +int +t4_free_l2t(struct l2t_data *d) +{ + int i; + + for (i = 0; i < L2T_SIZE; i++) + mtx_destroy(&d->l2tab[i].lock); + rw_destroy(&d->lock); + free(d, M_CXGBE); + + return (0); +} + static inline unsigned int vlan_prio(const struct l2t_entry *e) { return e->vlan >> 13; } +static char +l2e_state(const struct l2t_entry *e) +{ + switch (e->state) { + case L2T_STATE_VALID: return 'V'; /* valid, fast-path entry */ + case L2T_STATE_STALE: return 'S'; /* needs revalidation, but usable */ + case L2T_STATE_SYNC_WRITE: return 'W'; + case L2T_STATE_RESOLVING: return e->arpq_head ? 'A' : 'R'; + case L2T_STATE_SWITCHING: return 'X'; + default: return 'U'; + } +} + +int +sysctl_l2t(SYSCTL_HANDLER_ARGS) +{ + struct adapter *sc = arg1; + struct l2t_data *l2t = sc->l2t; + struct l2t_entry *e; + struct sbuf *sb; + int rc, i, header = 0; + char ip[60]; + + if (l2t == NULL) + return (ENXIO); + + rc = sysctl_wire_old_buffer(req, 0); + if (rc != 0) + return (rc); + + sb = sbuf_new_for_sysctl(NULL, NULL, 4096, req); + if (sb == NULL) + return (ENOMEM); + + e = &l2t->l2tab[0]; + for (i = 0; i < L2T_SIZE; i++, e++) { + mtx_lock(&e->lock); + if (e->state == L2T_STATE_UNUSED) + goto skip; + + if (header == 0) { + sbuf_printf(sb, " Idx IP address " + "Ethernet address VLAN/P LP State Users Port"); + header = 1; + } + if (e->state == L2T_STATE_SWITCHING || e->v6) + ip[0] = 0; + else + snprintf(ip, sizeof(ip), "%s", + inet_ntoa(*(struct in_addr *)&e->addr[0])); + + /* XXX: accessing lle probably not safe? */ + sbuf_printf(sb, "\n%4u %-15s %02x:%02x:%02x:%02x:%02x:%02x %4d" + " %u %2u %c %5u %s", + e->idx, ip, e->dmac[0], e->dmac[1], e->dmac[2], + e->dmac[3], e->dmac[4], e->dmac[5], + e->vlan & 0xfff, vlan_prio(e), e->lport, + l2e_state(e), atomic_load_acq_int(&e->refcnt), + e->lle ? e->lle->lle_tbl->llt_ifp->if_xname : ""); +skip: + mtx_unlock(&e->lock); + } + + rc = sbuf_finish(sb); + sbuf_delete(sb); + + return (rc); +} + +#ifndef TCP_OFFLOAD_DISABLE static inline void l2t_hold(struct l2t_data *d, struct l2t_entry *e) { if (atomic_fetchadd_int(&e->refcnt, 1) == 0) /* 0 -> 1 transition */ - atomic_add_int(&d->nfree, -1); + atomic_subtract_int(&d->nfree, 1); } /* @@ -154,38 +390,6 @@ addreq(const struct l2t_entry *e, const uint32_t *addr) } /* - * Write an L2T entry. Must be called with the entry locked (XXX: really?). - * The write may be synchronous or asynchronous. - */ -static int -write_l2e(struct adapter *sc, struct l2t_entry *e, int sync) -{ - struct mbuf *m; - struct cpl_l2t_write_req *req; - - if ((m = m_gethdr(M_NOWAIT, MT_DATA)) == NULL) - return (ENOMEM); - - req = mtod(m, struct cpl_l2t_write_req *); - m->m_pkthdr.len = m->m_len = sizeof(*req); - - INIT_TP_WR(req, 0); - OPCODE_TID(req) = htonl(MK_OPCODE_TID(CPL_L2T_WRITE_REQ, e->idx | - V_SYNC_WR(sync) | V_TID_QID(sc->sge.fwq.abs_id))); - req->params = htons(V_L2T_W_PORT(e->lport) | V_L2T_W_NOREPLY(!sync)); - req->l2t_idx = htons(e->idx); - req->vlan = htons(e->vlan); - memcpy(req->dst_mac, e->dmac, sizeof(req->dst_mac)); - - t4_mgmt_tx(sc, m); - - if (sync && e->state != L2T_STATE_SWITCHING) - e->state = L2T_STATE_SYNC_WRITE; - - return (0); -} - -/* * Add a packet to an L2T entry's queue of packets awaiting resolution. * Must be called with the entry's lock held. */ @@ -194,53 +398,133 @@ arpq_enqueue(struct l2t_entry *e, struct mbuf *m) { mtx_assert(&e->lock, MA_OWNED); - m->m_next = NULL; + KASSERT(m->m_nextpkt == NULL, ("%s: m_nextpkt not NULL", __func__)); if (e->arpq_head) - e->arpq_tail->m_next = m; + e->arpq_tail->m_nextpkt = m; else e->arpq_head = m; e->arpq_tail = m; } -/* - * Allocate a free L2T entry. Must be called with l2t_data.lock held. - */ -static struct l2t_entry * -alloc_l2e(struct l2t_data *d) +static inline void +send_pending(struct adapter *sc, struct l2t_entry *e) { - struct l2t_entry *end, *e, **p; + struct mbuf *m, *next; - rw_assert(&d->lock, RA_WLOCKED); + mtx_assert(&e->lock, MA_OWNED); - if (!atomic_load_acq_int(&d->nfree)) - return (NULL); + for (m = e->arpq_head; m; m = next) { + next = m->m_nextpkt; + m->m_nextpkt = NULL; + t4_wrq_tx(sc, MBUF_EQ(m), m); + } + e->arpq_head = e->arpq_tail = NULL; +} - /* there's definitely a free entry */ - for (e = d->rover, end = &d->l2tab[L2T_SIZE]; e != end; ++e) - if (atomic_load_acq_int(&e->refcnt) == 0) - goto found; +#ifdef INET +/* + * Looks up and fills up an l2t_entry's lle. We grab all the locks that we need + * ourself, and update e->state at the end if e->lle was successfully filled. + * + * The lle passed in comes from arpresolve and is ignored as it does not appear + * to be of much use. + */ +static int +l2t_fill_lle(struct adapter *sc, struct l2t_entry *e, struct llentry *unused) +{ + int rc = 0; + struct sockaddr_in sin; + struct ifnet *ifp = e->ifp; + struct llentry *lle; - for (e = d->l2tab; atomic_load_acq_int(&e->refcnt); ++e) ; -found: - d->rover = e + 1; - atomic_add_int(&d->nfree, -1); + bzero(&sin, sizeof(struct sockaddr_in)); + if (e->v6) + panic("%s: IPv6 L2 resolution not supported yet.", __func__); + + sin.sin_family = AF_INET; + sin.sin_len = sizeof(struct sockaddr_in); + memcpy(&sin.sin_addr, e->addr, sizeof(struct sockaddr_in)); + + mtx_assert(&e->lock, MA_NOTOWNED); + KASSERT(e->addr && ifp, ("%s: bad prep before call", __func__)); + + IF_AFDATA_LOCK(ifp); + lle = lla_lookup(LLTABLE(ifp), LLE_EXCLUSIVE, SA(&sin)); + IF_AFDATA_UNLOCK(ifp); + if (!LLE_IS_VALID(lle)) + return (ENOMEM); + if (!(lle->la_flags & LLE_VALID)) { + rc = EINVAL; + goto done; + } + + LLE_ADDREF(lle); + + mtx_lock(&e->lock); + if (e->state == L2T_STATE_RESOLVING) { + KASSERT(e->lle == NULL, ("%s: lle already valid", __func__)); + e->lle = lle; + memcpy(e->dmac, &lle->ll_addr, ETHER_ADDR_LEN); + write_l2e(sc, e, 1); + } else { + KASSERT(e->lle == lle, ("%s: lle changed", __func__)); + LLE_REMREF(lle); + } + mtx_unlock(&e->lock); +done: + LLE_WUNLOCK(lle); + return (rc); +} +#endif - /* - * 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; - } +int +t4_l2t_send(struct adapter *sc, struct mbuf *m, struct l2t_entry *e) +{ +#ifndef INET + return (EINVAL); +#else + struct llentry *lle = NULL; + struct sockaddr_in sin; + struct ifnet *ifp = e->ifp; + + if (e->v6) + panic("%s: IPv6 L2 resolution not supported yet.", __func__); + + bzero(&sin, sizeof(struct sockaddr_in)); + sin.sin_family = AF_INET; + sin.sin_len = sizeof(struct sockaddr_in); + memcpy(&sin.sin_addr, e->addr, sizeof(struct sockaddr_in)); + +again: + switch (e->state) { + case L2T_STATE_STALE: /* entry is stale, kick off revalidation */ + if (arpresolve(ifp, NULL, NULL, SA(&sin), e->dmac, &lle) == 0) + l2t_fill_lle(sc, e, lle); + + /* Fall through */ + + case L2T_STATE_VALID: /* fast-path, send the packet on */ + return t4_wrq_tx(sc, MBUF_EQ(m), m); + + case L2T_STATE_RESOLVING: + case L2T_STATE_SYNC_WRITE: + mtx_lock(&e->lock); + if (e->state != L2T_STATE_SYNC_WRITE && + e->state != L2T_STATE_RESOLVING) { + /* state changed by the time we got here */ + mtx_unlock(&e->lock); + goto again; } + arpq_enqueue(e, m); + mtx_unlock(&e->lock); + + if (e->state == L2T_STATE_RESOLVING && + arpresolve(ifp, NULL, NULL, SA(&sin), e->dmac, &lle) == 0) + l2t_fill_lle(sc, e, lle); } - e->state = L2T_STATE_UNUSED; - return e; + return (0); +#endif } /* @@ -287,75 +571,214 @@ t4_l2t_release(struct l2t_entry *e) t4_l2e_free(e); } +static int +do_l2t_write_rpl(struct sge_iq *iq, const struct rss_header *rss, + struct mbuf *m) +{ + struct adapter *sc = iq->adapter; + const struct cpl_l2t_write_rpl *rpl = (const void *)(rss + 1); + unsigned int tid = GET_TID(rpl); + unsigned int idx = tid & (L2T_SIZE - 1); + + if (__predict_false(rpl->status != CPL_ERR_NONE)) { + log(LOG_ERR, + "Unexpected L2T_WRITE_RPL status %u for entry %u\n", + rpl->status, idx); + return (EINVAL); + } + + if (tid & F_SYNC_WR) { + struct l2t_entry *e = &sc->l2t->l2tab[idx]; + + mtx_lock(&e->lock); + if (e->state != L2T_STATE_SWITCHING) { + send_pending(sc, e); + e->state = L2T_STATE_VALID; + } + mtx_unlock(&e->lock); + } + + return (0); +} + /* - * 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. + * Reuse an L2T entry that was previously used for the same next hop. + */ +static void +reuse_entry(struct l2t_entry *e) +{ + struct llentry *lle; + + mtx_lock(&e->lock); /* avoid race with t4_l2t_free */ + lle = e->lle; + if (lle) { + KASSERT(lle->la_flags & LLE_VALID, + ("%s: invalid lle stored in l2t_entry", __func__)); + + if (lle->la_expire >= time_uptime) + e->state = L2T_STATE_STALE; + else + e->state = L2T_STATE_VALID; + } else + e->state = L2T_STATE_RESOLVING; + mtx_unlock(&e->lock); +} + +/* + * The TOE wants an L2 table entry that it can use to reach the next hop over + * the specified port. Produce such an entry - create one if needed. + * + * Note that the ifnet could be a pseudo-device like if_vlan, if_lagg, etc. on + * top of the real cxgbe interface. */ struct l2t_entry * -t4_l2t_alloc_switching(struct l2t_data *d) +t4_l2t_get(struct port_info *pi, struct ifnet *ifp, struct sockaddr *sa) { struct l2t_entry *e; + struct l2t_data *d = pi->adapter->l2t; + int addr_len; + uint32_t *addr; + int hash; + struct sockaddr_in6 *sin6; + unsigned int smt_idx = pi->port_id; + + if (sa->sa_family == AF_INET) { + addr = (uint32_t *)&SINADDR(sa); + addr_len = sizeof(SINADDR(sa)); + } else if (sa->sa_family == AF_INET6) { + sin6 = (struct sockaddr_in6 *)sa; + addr = (uint32_t *)&sin6->sin6_addr.s6_addr; + addr_len = sizeof(sin6->sin6_addr.s6_addr); + } else + return (NULL); - rw_rlock(&d->lock); + hash = addr_hash(addr, addr_len, ifp->if_index); + + rw_wlock(&d->lock); + for (e = d->l2tab[hash].first; e; e = e->next) { + if (!addreq(e, addr) && e->ifp == ifp && e->smt_idx == smt_idx){ + l2t_hold(d, e); + if (atomic_load_acq_int(&e->refcnt) == 1) + reuse_entry(e); + goto done; + } + } + + /* Need to allocate a new entry */ e = alloc_l2e(d); if (e) { mtx_lock(&e->lock); /* avoid race with t4_l2t_free */ - e->state = L2T_STATE_SWITCHING; + e->state = L2T_STATE_RESOLVING; + memcpy(e->addr, addr, addr_len); + e->ifindex = ifp->if_index; + e->smt_idx = smt_idx; + e->ifp = ifp; + e->hash = hash; + e->lport = pi->lport; + e->v6 = (addr_len == 16); + e->lle = NULL; atomic_store_rel_int(&e->refcnt, 1); + if (ifp->if_type == IFT_L2VLAN) + VLAN_TAG(ifp, &e->vlan); + else + e->vlan = VLAN_NONE; + e->next = d->l2tab[hash].first; + d->l2tab[hash].first = e; mtx_unlock(&e->lock); } - rw_runlock(&d->lock); +done: + rw_wunlock(&d->lock); return e; } /* - * Sets/updates the contents of a switching L2T entry that has been allocated - * with an earlier call to @t4_l2t_alloc_switching. + * Called when the host's neighbor layer makes a change to some entry that is + * loaded into the HW L2 table. */ -int -t4_l2t_set_switching(struct adapter *sc, struct l2t_entry *e, uint16_t vlan, - uint8_t port, uint8_t *eth_addr) +void +t4_l2t_update(struct adapter *sc, struct llentry *lle) { - e->vlan = vlan; - e->lport = port; - memcpy(e->dmac, eth_addr, ETHER_ADDR_LEN); - return write_l2e(sc, e, 0); -} + struct l2t_entry *e; + struct l2t_data *d = sc->l2t; + struct sockaddr *sa = L3_ADDR(lle); + struct llentry *old_lle = NULL; + uint32_t *addr = (uint32_t *)&SINADDR(sa); + struct ifnet *ifp = lle->lle_tbl->llt_ifp; + int hash = addr_hash(addr, sizeof(*addr), ifp->if_index); + + KASSERT(d != NULL, ("%s: no L2 table", __func__)); + LLE_WLOCK_ASSERT(lle); + KASSERT(lle->la_flags & LLE_VALID || lle->la_flags & LLE_DELETED, + ("%s: entry neither valid nor deleted.", __func__)); -struct l2t_data * -t4_init_l2t(int flags) -{ - int i; - struct l2t_data *d; + rw_rlock(&d->lock); + for (e = d->l2tab[hash].first; e; e = e->next) { + if (!addreq(e, addr) && e->ifp == ifp) { + mtx_lock(&e->lock); + if (atomic_load_acq_int(&e->refcnt)) + goto found; + e->state = L2T_STATE_STALE; + mtx_unlock(&e->lock); + break; + } + } + rw_runlock(&d->lock); - d = malloc(sizeof(*d), M_CXGBE, M_ZERO | flags); - if (!d) - return (NULL); + /* The TOE has no interest in this LLE */ + return; - d->rover = d->l2tab; - atomic_store_rel_int(&d->nfree, L2T_SIZE); - rw_init(&d->lock, "L2T"); + found: + rw_runlock(&d->lock); - for (i = 0; i < L2T_SIZE; i++) { - d->l2tab[i].idx = i; - d->l2tab[i].state = L2T_STATE_UNUSED; - mtx_init(&d->l2tab[i].lock, "L2T_E", NULL, MTX_DEF); - atomic_store_rel_int(&d->l2tab[i].refcnt, 0); - } + if (atomic_load_acq_int(&e->refcnt)) { - return (d); -} + /* Entry is referenced by at least 1 offloaded connection. */ -int -t4_free_l2t(struct l2t_data *d) -{ - int i; + /* Handle deletes first */ + if (lle->la_flags & LLE_DELETED) { + if (lle == e->lle) { + e->lle = NULL; + e->state = L2T_STATE_RESOLVING; + LLE_REMREF(lle); + } + goto done; + } - for (i = 0; i < L2T_SIZE; i++) - mtx_destroy(&d->l2tab[i].lock); - rw_destroy(&d->lock); - free(d, M_CXGBE); + if (lle != e->lle) { + old_lle = e->lle; + LLE_ADDREF(lle); + e->lle = lle; + } - return (0); + if (e->state == L2T_STATE_RESOLVING || + memcmp(e->dmac, &lle->ll_addr, ETHER_ADDR_LEN)) { + + /* unresolved -> resolved; or dmac changed */ + + memcpy(e->dmac, &lle->ll_addr, ETHER_ADDR_LEN); + write_l2e(sc, e, 1); + } else { + + /* +ve reinforcement of a valid or stale entry */ + + } + + e->state = L2T_STATE_VALID; + + } else { + /* + * Entry was used previously but is unreferenced right now. + * e->lle has been released and NULL'd out by t4_l2t_free, or + * l2t_release is about to call t4_l2t_free and do that. + * + * Either way this is of no interest to us. + */ + } + +done: + mtx_unlock(&e->lock); + if (old_lle) + LLE_FREE(old_lle); } + +#endif |