/* * BSD LICENSE * * Copyright(c) 2017 Cavium, Inc.. All rights reserved. * All rights reserved. * * 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. * * Neither the name of Cavium, Inc. 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(S) 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 "lio_bsd.h" #include "lio_common.h" #include "lio_droq.h" #include "lio_iq.h" #include "lio_response_manager.h" #include "lio_device.h" #include "lio_ctrl.h" #include "lio_main.h" #include "lio_network.h" #include "lio_rxtx.h" int lio_xmit(struct lio *lio, struct lio_instr_queue *iq, struct mbuf **m_headp) { struct lio_data_pkt ndata; union lio_cmd_setup cmdsetup; struct lio_mbuf_free_info *finfo = NULL; struct octeon_device *oct = iq->oct_dev; struct lio_iq_stats *stats; struct octeon_instr_irh *irh; struct lio_request_list *tx_buf; union lio_tx_info *tx_info; struct mbuf *m_head; bus_dma_segment_t segs[LIO_MAX_SG]; bus_dmamap_t map; uint64_t dptr = 0; uint32_t tag = 0; int iq_no = 0; int nsegs; int status = 0; iq_no = iq->txpciq.s.q_no; tag = iq_no; stats = &oct->instr_queue[iq_no]->stats; tx_buf = iq->request_list + iq->host_write_index; /* * Check for all conditions in which the current packet cannot be * transmitted. */ if (!(atomic_load_acq_int(&lio->ifstate) & LIO_IFSTATE_RUNNING) || (!lio->linfo.link.s.link_up)) { lio_dev_info(oct, "Transmit failed link_status : %d\n", lio->linfo.link.s.link_up); status = ENETDOWN; goto drop_packet; } if (lio_iq_is_full(oct, iq_no)) { /* Defer sending if queue is full */ lio_dev_dbg(oct, "Transmit failed iq:%d full\n", iq_no); stats->tx_iq_busy++; return (ENOBUFS); } map = tx_buf->map; status = bus_dmamap_load_mbuf_sg(iq->txtag, map, *m_headp, segs, &nsegs, BUS_DMA_NOWAIT); if (status == EFBIG) { struct mbuf *m; m = m_defrag(*m_headp, M_NOWAIT); if (m == NULL) { stats->mbuf_defrag_failed++; goto drop_packet; } *m_headp = m; status = bus_dmamap_load_mbuf_sg(iq->txtag, map, *m_headp, segs, &nsegs, BUS_DMA_NOWAIT); } if (status == ENOMEM) { goto retry; } else if (status) { stats->tx_dmamap_fail++; lio_dev_dbg(oct, "bus_dmamap_load_mbuf_sg failed with error %d. iq:%d", status, iq_no); goto drop_packet; } m_head = *m_headp; /* Info used to unmap and free the buffers. */ finfo = &tx_buf->finfo; finfo->map = map; finfo->mb = m_head; /* Prepare the attributes for the data to be passed to OSI. */ bzero(&ndata, sizeof(struct lio_data_pkt)); ndata.buf = (void *)finfo; ndata.q_no = iq_no; ndata.datasize = m_head->m_pkthdr.len; cmdsetup.cmd_setup64 = 0; cmdsetup.s.iq_no = iq_no; if (m_head->m_pkthdr.csum_flags & CSUM_IP) cmdsetup.s.ip_csum = 1; if ((m_head->m_pkthdr.csum_flags & (CSUM_IP_TCP | CSUM_IP6_TCP)) || (m_head->m_pkthdr.csum_flags & (CSUM_IP_UDP | CSUM_IP6_UDP))) cmdsetup.s.transport_csum = 1; if (nsegs == 1) { cmdsetup.s.u.datasize = segs[0].ds_len; lio_prepare_pci_cmd(oct, &ndata.cmd, &cmdsetup, tag); dptr = segs[0].ds_addr; ndata.cmd.cmd3.dptr = dptr; ndata.reqtype = LIO_REQTYPE_NORESP_NET; } else { struct lio_gather *g; int i; mtx_lock(&lio->glist_lock[iq_no]); g = (struct lio_gather *) lio_delete_first_node(&lio->ghead[iq_no]); mtx_unlock(&lio->glist_lock[iq_no]); if (g == NULL) { lio_dev_err(oct, "Transmit scatter gather: glist null!\n"); goto retry; } cmdsetup.s.gather = 1; cmdsetup.s.u.gatherptrs = nsegs; lio_prepare_pci_cmd(oct, &ndata.cmd, &cmdsetup, tag); bzero(g->sg, g->sg_size); i = 0; while (nsegs--) { g->sg[(i >> 2)].ptr[(i & 3)] = segs[i].ds_addr; lio_add_sg_size(&g->sg[(i >> 2)], segs[i].ds_len, (i & 3)); i++; } dptr = g->sg_dma_ptr; ndata.cmd.cmd3.dptr = dptr; finfo->g = g; ndata.reqtype = LIO_REQTYPE_NORESP_NET_SG; } irh = (struct octeon_instr_irh *)&ndata.cmd.cmd3.irh; tx_info = (union lio_tx_info *)&ndata.cmd.cmd3.ossp[0]; if (m_head->m_pkthdr.csum_flags & (CSUM_IP_TSO | CSUM_IP6_TSO)) { tx_info->s.gso_size = m_head->m_pkthdr.tso_segsz; tx_info->s.gso_segs = howmany(m_head->m_pkthdr.len, m_head->m_pkthdr.tso_segsz); stats->tx_gso++; } /* HW insert VLAN tag */ if (m_head->m_flags & M_VLANTAG) { irh->priority = m_head->m_pkthdr.ether_vtag >> 13; irh->vlan = m_head->m_pkthdr.ether_vtag & 0xfff; } status = lio_send_data_pkt(oct, &ndata); if (status == LIO_IQ_SEND_FAILED) goto retry; if (tx_info->s.gso_segs) stats->tx_done += tx_info->s.gso_segs; else stats->tx_done++; stats->tx_tot_bytes += ndata.datasize; return (0); retry: return (ENOBUFS); drop_packet: stats->tx_dropped++; lio_dev_err(oct, "IQ%d Transmit dropped: %llu\n", iq_no, LIO_CAST64(stats->tx_dropped)); m_freem(*m_headp); *m_headp = NULL; return (status); } int lio_mq_start_locked(struct ifnet *ifp, struct lio_instr_queue *iq) { struct lio *lio = if_getsoftc(ifp); struct mbuf *next; int err = 0; if (((if_getdrvflags(ifp) & IFF_DRV_RUNNING) == 0) || (!lio->linfo.link.s.link_up)) return (-ENETDOWN); /* Process the queue */ while ((next = drbr_peek(ifp, iq->br)) != NULL) { err = lio_xmit(lio, iq, &next); if (err) { if (next == NULL) drbr_advance(ifp, iq->br); else drbr_putback(ifp, iq->br, next); break; } drbr_advance(ifp, iq->br); /* Send a copy of the frame to the BPF listener */ ETHER_BPF_MTAP(ifp, next); if (((if_getdrvflags(ifp) & IFF_DRV_RUNNING) == 0) || (!lio->linfo.link.s.link_up)) break; } return (err); } int lio_mq_start(struct ifnet *ifp, struct mbuf *m) { struct lio *lio = if_getsoftc(ifp); struct octeon_device *oct = lio->oct_dev; struct lio_instr_queue *iq; int err = 0, i; #ifdef RSS uint32_t bucket_id; #endif if (M_HASHTYPE_GET(m) != M_HASHTYPE_NONE) { #ifdef RSS if (rss_hash2bucket(m->m_pkthdr.flowid, M_HASHTYPE_GET(m), &bucket_id) == 0) { i = bucket_id % oct->num_iqs; if (bucket_id > oct->num_iqs) lio_dev_dbg(oct, "bucket_id (%d) > num_iqs (%d)\n", bucket_id, oct->num_iqs); } else #endif i = m->m_pkthdr.flowid % oct->num_iqs; } else i = curcpu % oct->num_iqs; iq = oct->instr_queue[i]; err = drbr_enqueue(ifp, iq->br, m); if (err) return (err); if (mtx_trylock(&iq->enq_lock)) { lio_mq_start_locked(ifp, iq); mtx_unlock(&iq->enq_lock); } return (err); } void lio_qflush(struct ifnet *ifp) { struct lio *lio = if_getsoftc(ifp); struct octeon_device *oct = lio->oct_dev; struct lio_instr_queue *iq; struct mbuf *m; int i; for (i = 0; i < LIO_MAX_INSTR_QUEUES(oct); i++) { if (!(oct->io_qmask.iq & BIT_ULL(i))) continue; iq = oct->instr_queue[i]; mtx_lock(&iq->enq_lock); while ((m = buf_ring_dequeue_sc(iq->br)) != NULL) m_freem(m); mtx_unlock(&iq->enq_lock); } if_qflush(ifp); }