/************************************************************************** Copyright (c) 2007-2008, Chelsio Inc. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Neither the name of the Chelsio Corporation nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ***************************************************************************/ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define VALIDATE_TID 0 MALLOC_DEFINE(M_CXGB, "cxgb", "Chelsio 10 Gigabit Ethernet and services"); TAILQ_HEAD(, cxgb_client) client_list; TAILQ_HEAD(, t3cdev) ofld_dev_list; static struct mtx cxgb_db_lock; static int inited = 0; static inline int offload_activated(struct t3cdev *tdev) { struct adapter *adapter = tdev2adap(tdev); return (isset(&adapter->open_device_map, OFFLOAD_DEVMAP_BIT)); } static inline void register_tdev(struct t3cdev *tdev) { static int unit; mtx_lock(&cxgb_db_lock); snprintf(tdev->name, sizeof(tdev->name), "ofld_dev%d", unit++); TAILQ_INSERT_TAIL(&ofld_dev_list, tdev, entry); mtx_unlock(&cxgb_db_lock); } static inline void unregister_tdev(struct t3cdev *tdev) { if (!inited) return; mtx_lock(&cxgb_db_lock); TAILQ_REMOVE(&ofld_dev_list, tdev, entry); mtx_unlock(&cxgb_db_lock); } #ifndef TCP_OFFLOAD_DISABLE /** * cxgb_register_client - register an offload client * @client: the client * * Add the client to the client list, * and call backs the client for each activated offload device */ void cxgb_register_client(struct cxgb_client *client) { struct t3cdev *tdev; mtx_lock(&cxgb_db_lock); TAILQ_INSERT_TAIL(&client_list, client, client_entry); if (client->add) { TAILQ_FOREACH(tdev, &ofld_dev_list, entry) { if (offload_activated(tdev)) { client->add(tdev); } else CTR1(KTR_CXGB, "cxgb_register_client: %p not activated", tdev); } } mtx_unlock(&cxgb_db_lock); } /** * cxgb_unregister_client - unregister an offload client * @client: the client * * Remove the client to the client list, * and call backs the client for each activated offload device. */ void cxgb_unregister_client(struct cxgb_client *client) { struct t3cdev *tdev; mtx_lock(&cxgb_db_lock); TAILQ_REMOVE(&client_list, client, client_entry); if (client->remove) { TAILQ_FOREACH(tdev, &ofld_dev_list, entry) { if (offload_activated(tdev)) client->remove(tdev); } } mtx_unlock(&cxgb_db_lock); } /** * cxgb_add_clients - activate register clients for an offload device * @tdev: the offload device * * Call backs all registered clients once a offload device is activated */ void cxgb_add_clients(struct t3cdev *tdev) { struct cxgb_client *client; mtx_lock(&cxgb_db_lock); TAILQ_FOREACH(client, &client_list, client_entry) { if (client->add) client->add(tdev); } mtx_unlock(&cxgb_db_lock); } /** * cxgb_remove_clients - activate register clients for an offload device * @tdev: the offload device * * Call backs all registered clients once a offload device is deactivated */ void cxgb_remove_clients(struct t3cdev *tdev) { struct cxgb_client *client; mtx_lock(&cxgb_db_lock); TAILQ_FOREACH(client, &client_list, client_entry) { if (client->remove) client->remove(tdev); } mtx_unlock(&cxgb_db_lock); } #endif /** * cxgb_ofld_recv - process n received offload packets * @dev: the offload device * @m: an array of offload packets * @n: the number of offload packets * * Process an array of ingress offload packets. Each packet is forwarded * to any active network taps and then passed to the offload device's receive * method. We optimize passing packets to the receive method by passing * it the whole array at once except when there are active taps. */ int cxgb_ofld_recv(struct t3cdev *dev, struct mbuf **m, int n) { return dev->recv(dev, m, n); } /* * Dummy handler for Rx offload packets in case we get an offload packet before * proper processing is setup. This complains and drops the packet as it isn't * normal to get offload packets at this stage. */ static int rx_offload_blackhole(struct t3cdev *dev, struct mbuf **m, int n) { while (n--) m_freem(m[n]); return 0; } static void dummy_neigh_update(struct t3cdev *dev, struct rtentry *neigh, uint8_t *enaddr, struct sockaddr *sa) { } void cxgb_set_dummy_ops(struct t3cdev *dev) { dev->recv = rx_offload_blackhole; dev->arp_update = dummy_neigh_update; } static int do_smt_write_rpl(struct t3cdev *dev, struct mbuf *m) { struct cpl_smt_write_rpl *rpl = cplhdr(m); if (rpl->status != CPL_ERR_NONE) log(LOG_ERR, "Unexpected SMT_WRITE_RPL status %u for entry %u\n", rpl->status, GET_TID(rpl)); return CPL_RET_BUF_DONE; } static int do_l2t_write_rpl(struct t3cdev *dev, struct mbuf *m) { struct cpl_l2t_write_rpl *rpl = cplhdr(m); if (rpl->status != CPL_ERR_NONE) log(LOG_ERR, "Unexpected L2T_WRITE_RPL status %u for entry %u\n", rpl->status, GET_TID(rpl)); return CPL_RET_BUF_DONE; } static int do_rte_write_rpl(struct t3cdev *dev, struct mbuf *m) { struct cpl_rte_write_rpl *rpl = cplhdr(m); if (rpl->status != CPL_ERR_NONE) log(LOG_ERR, "Unexpected L2T_WRITE_RPL status %u for entry %u\n", rpl->status, GET_TID(rpl)); return CPL_RET_BUF_DONE; } static int do_set_tcb_rpl(struct t3cdev *dev, struct mbuf *m) { struct cpl_set_tcb_rpl *rpl = cplhdr(m); if (rpl->status != CPL_ERR_NONE) log(LOG_ERR, "Unexpected SET_TCB_RPL status %u for tid %u\n", rpl->status, GET_TID(rpl)); return CPL_RET_BUF_DONE; } static int do_trace(struct t3cdev *dev, struct mbuf *m) { #if 0 struct cpl_trace_pkt *p = cplhdr(m); skb->protocol = 0xffff; skb->dev = dev->lldev; skb_pull(skb, sizeof(*p)); skb->mac.raw = mtod(m, (char *)); netif_receive_skb(skb); #endif return 0; } /* * Process a received packet with an unknown/unexpected CPL opcode. */ static int do_bad_cpl(struct t3cdev *dev, struct mbuf *m) { log(LOG_ERR, "%s: received bad CPL command 0x%x\n", dev->name, 0xFF & *mtod(m, uint32_t *)); return (CPL_RET_BUF_DONE | CPL_RET_BAD_MSG); } /* * Handlers for each CPL opcode */ static cpl_handler_func cpl_handlers[256]; /* * T3CDEV's receive method. */ int process_rx(struct t3cdev *dev, struct mbuf **m, int n) { while (n--) { struct mbuf *m0 = *m++; unsigned int opcode = G_OPCODE(ntohl(m0->m_pkthdr.csum_data)); int ret; DPRINTF("processing op=0x%x m=%p data=%p\n", opcode, m0, m0->m_data); ret = cpl_handlers[opcode] (dev, m0); #if VALIDATE_TID if (ret & CPL_RET_UNKNOWN_TID) { union opcode_tid *p = cplhdr(m0); log(LOG_ERR, "%s: CPL message (opcode %u) had " "unknown TID %u\n", dev->name, opcode, G_TID(ntohl(p->opcode_tid))); } #endif if (ret & CPL_RET_BUF_DONE) m_freem(m0); } return 0; } /* * Add a new handler to the CPL dispatch table. A NULL handler may be supplied * to unregister an existing handler. */ void t3_register_cpl_handler(unsigned int opcode, cpl_handler_func h) { if (opcode < NUM_CPL_CMDS) cpl_handlers[opcode] = h ? h : do_bad_cpl; else log(LOG_ERR, "T3C: handler registration for " "opcode %x failed\n", opcode); } /* * Allocate a chunk of memory using kmalloc or, if that fails, vmalloc. * The allocated memory is cleared. */ void * cxgb_alloc_mem(unsigned long size) { return malloc(size, M_CXGB, M_ZERO|M_NOWAIT); } /* * Free memory allocated through t3_alloc_mem(). */ void cxgb_free_mem(void *addr) { free(addr, M_CXGB); } static __inline int adap2type(struct adapter *adapter) { int type = 0; switch (adapter->params.rev) { case T3_REV_A: type = T3A; break; case T3_REV_B: case T3_REV_B2: type = T3B; break; case T3_REV_C: type = T3C; break; } return type; } void cxgb_adapter_ofld(struct adapter *adapter) { struct t3cdev *tdev = &adapter->tdev; cxgb_set_dummy_ops(tdev); tdev->type = adap2type(adapter); tdev->adapter = adapter; register_tdev(tdev); } void cxgb_adapter_unofld(struct adapter *adapter) { struct t3cdev *tdev = &adapter->tdev; tdev->recv = NULL; tdev->arp_update = NULL; unregister_tdev(tdev); } void cxgb_offload_init(void) { int i; if (inited++) return; mtx_init(&cxgb_db_lock, "ofld db", NULL, MTX_DEF); TAILQ_INIT(&client_list); TAILQ_INIT(&ofld_dev_list); for (i = 0; i < 0x100; ++i) cpl_handlers[i] = do_bad_cpl; t3_register_cpl_handler(CPL_SMT_WRITE_RPL, do_smt_write_rpl); t3_register_cpl_handler(CPL_RTE_WRITE_RPL, do_rte_write_rpl); t3_register_cpl_handler(CPL_L2T_WRITE_RPL, do_l2t_write_rpl); t3_register_cpl_handler(CPL_SET_TCB_RPL, do_set_tcb_rpl); t3_register_cpl_handler(CPL_TRACE_PKT, do_trace); } void cxgb_offload_exit(void) { if (--inited) return; mtx_destroy(&cxgb_db_lock); } MODULE_VERSION(if_cxgb, 1);