| // SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) |
| |
| #include <linux/bpf_trace.h> |
| #include <linux/dma-mapping.h> |
| #include <linux/etherdevice.h> |
| #include <linux/filter.h> |
| #include <linux/irq.h> |
| #include <linux/pci.h> |
| #include <linux/skbuff.h> |
| #include "funeth_txrx.h" |
| #include "funeth.h" |
| #include "fun_queue.h" |
| |
| #define CREATE_TRACE_POINTS |
| #include "funeth_trace.h" |
| |
| /* Given the device's max supported MTU and pages of at least 4KB a packet can |
| * be scattered into at most 4 buffers. |
| */ |
| #define RX_MAX_FRAGS 4 |
| |
| /* Per packet headroom in non-XDP mode. Present only for 1-frag packets. */ |
| #define FUN_RX_HEADROOM (NET_SKB_PAD + NET_IP_ALIGN) |
| |
| /* We try to reuse pages for our buffers. To avoid frequent page ref writes we |
| * take EXTRA_PAGE_REFS references at once and then hand them out one per packet |
| * occupying the buffer. |
| */ |
| #define EXTRA_PAGE_REFS 1000000 |
| #define MIN_PAGE_REFS 1000 |
| |
| enum { |
| FUN_XDP_FLUSH_REDIR = 1, |
| FUN_XDP_FLUSH_TX = 2, |
| }; |
| |
| /* See if a page is running low on refs we are holding and if so take more. */ |
| static void refresh_refs(struct funeth_rxbuf *buf) |
| { |
| if (unlikely(buf->pg_refs < MIN_PAGE_REFS)) { |
| buf->pg_refs += EXTRA_PAGE_REFS; |
| page_ref_add(buf->page, EXTRA_PAGE_REFS); |
| } |
| } |
| |
| /* Offer a buffer to the Rx buffer cache. The cache will hold the buffer if its |
| * page is worth retaining and there's room for it. Otherwise the page is |
| * unmapped and our references released. |
| */ |
| static void cache_offer(struct funeth_rxq *q, const struct funeth_rxbuf *buf) |
| { |
| struct funeth_rx_cache *c = &q->cache; |
| |
| if (c->prod_cnt - c->cons_cnt <= c->mask && buf->node == numa_mem_id()) { |
| c->bufs[c->prod_cnt & c->mask] = *buf; |
| c->prod_cnt++; |
| } else { |
| dma_unmap_page_attrs(q->dma_dev, buf->dma_addr, PAGE_SIZE, |
| DMA_FROM_DEVICE, DMA_ATTR_SKIP_CPU_SYNC); |
| __page_frag_cache_drain(buf->page, buf->pg_refs); |
| } |
| } |
| |
| /* Get a page from the Rx buffer cache. We only consider the next available |
| * page and return it if we own all its references. |
| */ |
| static bool cache_get(struct funeth_rxq *q, struct funeth_rxbuf *rb) |
| { |
| struct funeth_rx_cache *c = &q->cache; |
| struct funeth_rxbuf *buf; |
| |
| if (c->prod_cnt == c->cons_cnt) |
| return false; /* empty cache */ |
| |
| buf = &c->bufs[c->cons_cnt & c->mask]; |
| if (page_ref_count(buf->page) == buf->pg_refs) { |
| dma_sync_single_for_device(q->dma_dev, buf->dma_addr, |
| PAGE_SIZE, DMA_FROM_DEVICE); |
| *rb = *buf; |
| buf->page = NULL; |
| refresh_refs(rb); |
| c->cons_cnt++; |
| return true; |
| } |
| |
| /* Page can't be reused. If the cache is full drop this page. */ |
| if (c->prod_cnt - c->cons_cnt > c->mask) { |
| dma_unmap_page_attrs(q->dma_dev, buf->dma_addr, PAGE_SIZE, |
| DMA_FROM_DEVICE, DMA_ATTR_SKIP_CPU_SYNC); |
| __page_frag_cache_drain(buf->page, buf->pg_refs); |
| buf->page = NULL; |
| c->cons_cnt++; |
| } |
| return false; |
| } |
| |
| /* Allocate and DMA-map a page for receive. */ |
| static int funeth_alloc_page(struct funeth_rxq *q, struct funeth_rxbuf *rb, |
| int node, gfp_t gfp) |
| { |
| struct page *p; |
| |
| if (cache_get(q, rb)) |
| return 0; |
| |
| p = __alloc_pages_node(node, gfp | __GFP_NOWARN, 0); |
| if (unlikely(!p)) |
| return -ENOMEM; |
| |
| rb->dma_addr = dma_map_page(q->dma_dev, p, 0, PAGE_SIZE, |
| DMA_FROM_DEVICE); |
| if (unlikely(dma_mapping_error(q->dma_dev, rb->dma_addr))) { |
| FUN_QSTAT_INC(q, rx_map_err); |
| __free_page(p); |
| return -ENOMEM; |
| } |
| |
| FUN_QSTAT_INC(q, rx_page_alloc); |
| |
| rb->page = p; |
| rb->pg_refs = 1; |
| refresh_refs(rb); |
| rb->node = page_is_pfmemalloc(p) ? -1 : page_to_nid(p); |
| return 0; |
| } |
| |
| static void funeth_free_page(struct funeth_rxq *q, struct funeth_rxbuf *rb) |
| { |
| if (rb->page) { |
| dma_unmap_page(q->dma_dev, rb->dma_addr, PAGE_SIZE, |
| DMA_FROM_DEVICE); |
| __page_frag_cache_drain(rb->page, rb->pg_refs); |
| rb->page = NULL; |
| } |
| } |
| |
| /* Run the XDP program assigned to an Rx queue. |
| * Return %NULL if the buffer is consumed, or the virtual address of the packet |
| * to turn into an skb. |
| */ |
| static void *fun_run_xdp(struct funeth_rxq *q, skb_frag_t *frags, void *buf_va, |
| int ref_ok, struct funeth_txq *xdp_q) |
| { |
| struct bpf_prog *xdp_prog; |
| struct xdp_frame *xdpf; |
| struct xdp_buff xdp; |
| u32 act; |
| |
| /* VA includes the headroom, frag size includes headroom + tailroom */ |
| xdp_init_buff(&xdp, ALIGN(skb_frag_size(frags), FUN_EPRQ_PKT_ALIGN), |
| &q->xdp_rxq); |
| xdp_prepare_buff(&xdp, buf_va, FUN_XDP_HEADROOM, skb_frag_size(frags) - |
| (FUN_RX_TAILROOM + FUN_XDP_HEADROOM), false); |
| |
| xdp_prog = READ_ONCE(q->xdp_prog); |
| act = bpf_prog_run_xdp(xdp_prog, &xdp); |
| |
| switch (act) { |
| case XDP_PASS: |
| /* remove headroom, which may not be FUN_XDP_HEADROOM now */ |
| skb_frag_size_set(frags, xdp.data_end - xdp.data); |
| skb_frag_off_add(frags, xdp.data - xdp.data_hard_start); |
| goto pass; |
| case XDP_TX: |
| if (unlikely(!ref_ok)) |
| goto pass; |
| |
| xdpf = xdp_convert_buff_to_frame(&xdp); |
| if (!xdpf || !fun_xdp_tx(xdp_q, xdpf)) |
| goto xdp_error; |
| FUN_QSTAT_INC(q, xdp_tx); |
| q->xdp_flush |= FUN_XDP_FLUSH_TX; |
| break; |
| case XDP_REDIRECT: |
| if (unlikely(!ref_ok)) |
| goto pass; |
| if (unlikely(xdp_do_redirect(q->netdev, &xdp, xdp_prog))) |
| goto xdp_error; |
| FUN_QSTAT_INC(q, xdp_redir); |
| q->xdp_flush |= FUN_XDP_FLUSH_REDIR; |
| break; |
| default: |
| bpf_warn_invalid_xdp_action(q->netdev, xdp_prog, act); |
| fallthrough; |
| case XDP_ABORTED: |
| trace_xdp_exception(q->netdev, xdp_prog, act); |
| xdp_error: |
| q->cur_buf->pg_refs++; /* return frags' page reference */ |
| FUN_QSTAT_INC(q, xdp_err); |
| break; |
| case XDP_DROP: |
| q->cur_buf->pg_refs++; |
| FUN_QSTAT_INC(q, xdp_drops); |
| break; |
| } |
| return NULL; |
| |
| pass: |
| return xdp.data; |
| } |
| |
| /* A CQE contains a fixed completion structure along with optional metadata and |
| * even packet data. Given the start address of a CQE return the start of the |
| * contained fixed structure, which lies at the end. |
| */ |
| static const void *cqe_to_info(const void *cqe) |
| { |
| return cqe + FUNETH_CQE_INFO_OFFSET; |
| } |
| |
| /* The inverse of cqe_to_info(). */ |
| static const void *info_to_cqe(const void *cqe_info) |
| { |
| return cqe_info - FUNETH_CQE_INFO_OFFSET; |
| } |
| |
| /* Return the type of hash provided by the device based on the L3 and L4 |
| * protocols it parsed for the packet. |
| */ |
| static enum pkt_hash_types cqe_to_pkt_hash_type(u16 pkt_parse) |
| { |
| static const enum pkt_hash_types htype_map[] = { |
| PKT_HASH_TYPE_NONE, PKT_HASH_TYPE_L3, |
| PKT_HASH_TYPE_NONE, PKT_HASH_TYPE_L4, |
| PKT_HASH_TYPE_NONE, PKT_HASH_TYPE_L3, |
| PKT_HASH_TYPE_NONE, PKT_HASH_TYPE_L3 |
| }; |
| u16 key; |
| |
| /* Build the key from the TCP/UDP and IP/IPv6 bits */ |
| key = ((pkt_parse >> FUN_ETH_RX_CV_OL4_PROT_S) & 6) | |
| ((pkt_parse >> (FUN_ETH_RX_CV_OL3_PROT_S + 1)) & 1); |
| |
| return htype_map[key]; |
| } |
| |
| /* Each received packet can be scattered across several Rx buffers or can |
| * share a buffer with previously received packets depending on the buffer |
| * and packet sizes and the room available in the most recently used buffer. |
| * |
| * The rules are: |
| * - If the buffer at the head of an RQ has not been used it gets (part of) the |
| * next incoming packet. |
| * - Otherwise, if the packet fully fits in the buffer's remaining space the |
| * packet is written there. |
| * - Otherwise, the packet goes into the next Rx buffer. |
| * |
| * This function returns the Rx buffer for a packet or fragment thereof of the |
| * given length. If it isn't @buf it either recycles or frees that buffer |
| * before advancing the queue to the next buffer. |
| * |
| * If called repeatedly with the remaining length of a packet it will walk |
| * through all the buffers containing the packet. |
| */ |
| static struct funeth_rxbuf * |
| get_buf(struct funeth_rxq *q, struct funeth_rxbuf *buf, unsigned int len) |
| { |
| if (q->buf_offset + len <= PAGE_SIZE || !q->buf_offset) |
| return buf; /* @buf holds (part of) the packet */ |
| |
| /* The packet occupies part of the next buffer. Move there after |
| * replenishing the current buffer slot either with the spare page or |
| * by reusing the slot's existing page. Note that if a spare page isn't |
| * available and the current packet occupies @buf it is a multi-frag |
| * packet that will be dropped leaving @buf available for reuse. |
| */ |
| if ((page_ref_count(buf->page) == buf->pg_refs && |
| buf->node == numa_mem_id()) || !q->spare_buf.page) { |
| dma_sync_single_for_device(q->dma_dev, buf->dma_addr, |
| PAGE_SIZE, DMA_FROM_DEVICE); |
| refresh_refs(buf); |
| } else { |
| cache_offer(q, buf); |
| *buf = q->spare_buf; |
| q->spare_buf.page = NULL; |
| q->rqes[q->rq_cons & q->rq_mask] = |
| FUN_EPRQ_RQBUF_INIT(buf->dma_addr); |
| } |
| q->buf_offset = 0; |
| q->rq_cons++; |
| return &q->bufs[q->rq_cons & q->rq_mask]; |
| } |
| |
| /* Gather the page fragments making up the first Rx packet on @q. Its total |
| * length @tot_len includes optional head- and tail-rooms. |
| * |
| * Return 0 if the device retains ownership of at least some of the pages. |
| * In this case the caller may only copy the packet. |
| * |
| * A non-zero return value gives the caller permission to use references to the |
| * pages, e.g., attach them to skbs. Additionally, if the value is <0 at least |
| * one of the pages is PF_MEMALLOC. |
| * |
| * Regardless of outcome the caller is granted a reference to each of the pages. |
| */ |
| static int fun_gather_pkt(struct funeth_rxq *q, unsigned int tot_len, |
| skb_frag_t *frags) |
| { |
| struct funeth_rxbuf *buf = q->cur_buf; |
| unsigned int frag_len; |
| int ref_ok = 1; |
| |
| for (;;) { |
| buf = get_buf(q, buf, tot_len); |
| |
| /* We always keep the RQ full of buffers so before we can give |
| * one of our pages to the stack we require that we can obtain |
| * a replacement page. If we can't the packet will either be |
| * copied or dropped so we can retain ownership of the page and |
| * reuse it. |
| */ |
| if (!q->spare_buf.page && |
| funeth_alloc_page(q, &q->spare_buf, numa_mem_id(), |
| GFP_ATOMIC | __GFP_MEMALLOC)) |
| ref_ok = 0; |
| |
| frag_len = min_t(unsigned int, tot_len, |
| PAGE_SIZE - q->buf_offset); |
| dma_sync_single_for_cpu(q->dma_dev, |
| buf->dma_addr + q->buf_offset, |
| frag_len, DMA_FROM_DEVICE); |
| buf->pg_refs--; |
| if (ref_ok) |
| ref_ok |= buf->node; |
| |
| skb_frag_fill_page_desc(frags++, buf->page, q->buf_offset, |
| frag_len); |
| |
| tot_len -= frag_len; |
| if (!tot_len) |
| break; |
| |
| q->buf_offset = PAGE_SIZE; |
| } |
| q->buf_offset = ALIGN(q->buf_offset + frag_len, FUN_EPRQ_PKT_ALIGN); |
| q->cur_buf = buf; |
| return ref_ok; |
| } |
| |
| static bool rx_hwtstamp_enabled(const struct net_device *dev) |
| { |
| const struct funeth_priv *d = netdev_priv(dev); |
| |
| return d->hwtstamp_cfg.rx_filter == HWTSTAMP_FILTER_ALL; |
| } |
| |
| /* Advance the CQ pointers and phase tag to the next CQE. */ |
| static void advance_cq(struct funeth_rxq *q) |
| { |
| if (unlikely(q->cq_head == q->cq_mask)) { |
| q->cq_head = 0; |
| q->phase ^= 1; |
| q->next_cqe_info = cqe_to_info(q->cqes); |
| } else { |
| q->cq_head++; |
| q->next_cqe_info += FUNETH_CQE_SIZE; |
| } |
| prefetch(q->next_cqe_info); |
| } |
| |
| /* Process the packet represented by the head CQE of @q. Gather the packet's |
| * fragments, run it through the optional XDP program, and if needed construct |
| * an skb and pass it to the stack. |
| */ |
| static void fun_handle_cqe_pkt(struct funeth_rxq *q, struct funeth_txq *xdp_q) |
| { |
| const struct fun_eth_cqe *rxreq = info_to_cqe(q->next_cqe_info); |
| unsigned int i, tot_len, pkt_len = be32_to_cpu(rxreq->pkt_len); |
| struct net_device *ndev = q->netdev; |
| skb_frag_t frags[RX_MAX_FRAGS]; |
| struct skb_shared_info *si; |
| unsigned int headroom; |
| gro_result_t gro_res; |
| struct sk_buff *skb; |
| int ref_ok; |
| void *va; |
| u16 cv; |
| |
| u64_stats_update_begin(&q->syncp); |
| q->stats.rx_pkts++; |
| q->stats.rx_bytes += pkt_len; |
| u64_stats_update_end(&q->syncp); |
| |
| advance_cq(q); |
| |
| /* account for head- and tail-room, present only for 1-buffer packets */ |
| tot_len = pkt_len; |
| headroom = be16_to_cpu(rxreq->headroom); |
| if (likely(headroom)) |
| tot_len += FUN_RX_TAILROOM + headroom; |
| |
| ref_ok = fun_gather_pkt(q, tot_len, frags); |
| va = skb_frag_address(frags); |
| if (xdp_q && headroom == FUN_XDP_HEADROOM) { |
| va = fun_run_xdp(q, frags, va, ref_ok, xdp_q); |
| if (!va) |
| return; |
| headroom = 0; /* XDP_PASS trims it */ |
| } |
| if (unlikely(!ref_ok)) |
| goto no_mem; |
| |
| if (likely(headroom)) { |
| /* headroom is either FUN_RX_HEADROOM or FUN_XDP_HEADROOM */ |
| prefetch(va + headroom); |
| skb = napi_build_skb(va, ALIGN(tot_len, FUN_EPRQ_PKT_ALIGN)); |
| if (unlikely(!skb)) |
| goto no_mem; |
| |
| skb_reserve(skb, headroom); |
| __skb_put(skb, pkt_len); |
| skb->protocol = eth_type_trans(skb, ndev); |
| } else { |
| prefetch(va); |
| skb = napi_get_frags(q->napi); |
| if (unlikely(!skb)) |
| goto no_mem; |
| |
| if (ref_ok < 0) |
| skb->pfmemalloc = 1; |
| |
| si = skb_shinfo(skb); |
| si->nr_frags = rxreq->nsgl; |
| for (i = 0; i < si->nr_frags; i++) |
| si->frags[i] = frags[i]; |
| |
| skb->len = pkt_len; |
| skb->data_len = pkt_len; |
| skb->truesize += round_up(pkt_len, FUN_EPRQ_PKT_ALIGN); |
| } |
| |
| skb_record_rx_queue(skb, q->qidx); |
| cv = be16_to_cpu(rxreq->pkt_cv); |
| if (likely((q->netdev->features & NETIF_F_RXHASH) && rxreq->hash)) |
| skb_set_hash(skb, be32_to_cpu(rxreq->hash), |
| cqe_to_pkt_hash_type(cv)); |
| if (likely((q->netdev->features & NETIF_F_RXCSUM) && rxreq->csum)) { |
| FUN_QSTAT_INC(q, rx_cso); |
| skb->ip_summed = CHECKSUM_UNNECESSARY; |
| skb->csum_level = be16_to_cpu(rxreq->csum) - 1; |
| } |
| if (unlikely(rx_hwtstamp_enabled(q->netdev))) |
| skb_hwtstamps(skb)->hwtstamp = be64_to_cpu(rxreq->timestamp); |
| |
| trace_funeth_rx(q, rxreq->nsgl, pkt_len, skb->hash, cv); |
| |
| gro_res = skb->data_len ? napi_gro_frags(q->napi) : |
| napi_gro_receive(q->napi, skb); |
| if (gro_res == GRO_MERGED || gro_res == GRO_MERGED_FREE) |
| FUN_QSTAT_INC(q, gro_merged); |
| else if (gro_res == GRO_HELD) |
| FUN_QSTAT_INC(q, gro_pkts); |
| return; |
| |
| no_mem: |
| FUN_QSTAT_INC(q, rx_mem_drops); |
| |
| /* Release the references we've been granted for the frag pages. |
| * We return the ref of the last frag and free the rest. |
| */ |
| q->cur_buf->pg_refs++; |
| for (i = 0; i < rxreq->nsgl - 1; i++) |
| __free_page(skb_frag_page(frags + i)); |
| } |
| |
| /* Return 0 if the phase tag of the CQE at the CQ's head matches expectations |
| * indicating the CQE is new. |
| */ |
| static u16 cqe_phase_mismatch(const struct fun_cqe_info *ci, u16 phase) |
| { |
| u16 sf_p = be16_to_cpu(ci->sf_p); |
| |
| return (sf_p & 1) ^ phase; |
| } |
| |
| /* Walk through a CQ identifying and processing fresh CQEs up to the given |
| * budget. Return the remaining budget. |
| */ |
| static int fun_process_cqes(struct funeth_rxq *q, int budget) |
| { |
| struct funeth_priv *fp = netdev_priv(q->netdev); |
| struct funeth_txq **xdpqs, *xdp_q = NULL; |
| |
| xdpqs = rcu_dereference_bh(fp->xdpqs); |
| if (xdpqs) |
| xdp_q = xdpqs[smp_processor_id()]; |
| |
| while (budget && !cqe_phase_mismatch(q->next_cqe_info, q->phase)) { |
| /* access other descriptor fields after the phase check */ |
| dma_rmb(); |
| |
| fun_handle_cqe_pkt(q, xdp_q); |
| budget--; |
| } |
| |
| if (unlikely(q->xdp_flush)) { |
| if (q->xdp_flush & FUN_XDP_FLUSH_TX) |
| fun_txq_wr_db(xdp_q); |
| if (q->xdp_flush & FUN_XDP_FLUSH_REDIR) |
| xdp_do_flush(); |
| q->xdp_flush = 0; |
| } |
| |
| return budget; |
| } |
| |
| /* NAPI handler for Rx queues. Calls the CQE processing loop and writes RQ/CQ |
| * doorbells as needed. |
| */ |
| int fun_rxq_napi_poll(struct napi_struct *napi, int budget) |
| { |
| struct fun_irq *irq = container_of(napi, struct fun_irq, napi); |
| struct funeth_rxq *q = irq->rxq; |
| int work_done = budget - fun_process_cqes(q, budget); |
| u32 cq_db_val = q->cq_head; |
| |
| if (unlikely(work_done >= budget)) |
| FUN_QSTAT_INC(q, rx_budget); |
| else if (napi_complete_done(napi, work_done)) |
| cq_db_val |= q->irq_db_val; |
| |
| /* check whether to post new Rx buffers */ |
| if (q->rq_cons - q->rq_cons_db >= q->rq_db_thres) { |
| u64_stats_update_begin(&q->syncp); |
| q->stats.rx_bufs += q->rq_cons - q->rq_cons_db; |
| u64_stats_update_end(&q->syncp); |
| q->rq_cons_db = q->rq_cons; |
| writel((q->rq_cons - 1) & q->rq_mask, q->rq_db); |
| } |
| |
| writel(cq_db_val, q->cq_db); |
| return work_done; |
| } |
| |
| /* Free the Rx buffers of an Rx queue. */ |
| static void fun_rxq_free_bufs(struct funeth_rxq *q) |
| { |
| struct funeth_rxbuf *b = q->bufs; |
| unsigned int i; |
| |
| for (i = 0; i <= q->rq_mask; i++, b++) |
| funeth_free_page(q, b); |
| |
| funeth_free_page(q, &q->spare_buf); |
| q->cur_buf = NULL; |
| } |
| |
| /* Initially provision an Rx queue with Rx buffers. */ |
| static int fun_rxq_alloc_bufs(struct funeth_rxq *q, int node) |
| { |
| struct funeth_rxbuf *b = q->bufs; |
| unsigned int i; |
| |
| for (i = 0; i <= q->rq_mask; i++, b++) { |
| if (funeth_alloc_page(q, b, node, GFP_KERNEL)) { |
| fun_rxq_free_bufs(q); |
| return -ENOMEM; |
| } |
| q->rqes[i] = FUN_EPRQ_RQBUF_INIT(b->dma_addr); |
| } |
| q->cur_buf = q->bufs; |
| return 0; |
| } |
| |
| /* Initialize a used-buffer cache of the given depth. */ |
| static int fun_rxq_init_cache(struct funeth_rx_cache *c, unsigned int depth, |
| int node) |
| { |
| c->mask = depth - 1; |
| c->bufs = kvzalloc_node(depth * sizeof(*c->bufs), GFP_KERNEL, node); |
| return c->bufs ? 0 : -ENOMEM; |
| } |
| |
| /* Deallocate an Rx queue's used-buffer cache and its contents. */ |
| static void fun_rxq_free_cache(struct funeth_rxq *q) |
| { |
| struct funeth_rxbuf *b = q->cache.bufs; |
| unsigned int i; |
| |
| for (i = 0; i <= q->cache.mask; i++, b++) |
| funeth_free_page(q, b); |
| |
| kvfree(q->cache.bufs); |
| q->cache.bufs = NULL; |
| } |
| |
| int fun_rxq_set_bpf(struct funeth_rxq *q, struct bpf_prog *prog) |
| { |
| struct funeth_priv *fp = netdev_priv(q->netdev); |
| struct fun_admin_epcq_req cmd; |
| u16 headroom; |
| int err; |
| |
| headroom = prog ? FUN_XDP_HEADROOM : FUN_RX_HEADROOM; |
| if (headroom != q->headroom) { |
| cmd.common = FUN_ADMIN_REQ_COMMON_INIT2(FUN_ADMIN_OP_EPCQ, |
| sizeof(cmd)); |
| cmd.u.modify = |
| FUN_ADMIN_EPCQ_MODIFY_REQ_INIT(FUN_ADMIN_SUBOP_MODIFY, |
| 0, q->hw_cqid, headroom); |
| err = fun_submit_admin_sync_cmd(fp->fdev, &cmd.common, NULL, 0, |
| 0); |
| if (err) |
| return err; |
| q->headroom = headroom; |
| } |
| |
| WRITE_ONCE(q->xdp_prog, prog); |
| return 0; |
| } |
| |
| /* Create an Rx queue, allocating the host memory it needs. */ |
| static struct funeth_rxq *fun_rxq_create_sw(struct net_device *dev, |
| unsigned int qidx, |
| unsigned int ncqe, |
| unsigned int nrqe, |
| struct fun_irq *irq) |
| { |
| struct funeth_priv *fp = netdev_priv(dev); |
| struct funeth_rxq *q; |
| int err = -ENOMEM; |
| int numa_node; |
| |
| numa_node = fun_irq_node(irq); |
| q = kzalloc_node(sizeof(*q), GFP_KERNEL, numa_node); |
| if (!q) |
| goto err; |
| |
| q->qidx = qidx; |
| q->netdev = dev; |
| q->cq_mask = ncqe - 1; |
| q->rq_mask = nrqe - 1; |
| q->numa_node = numa_node; |
| q->rq_db_thres = nrqe / 4; |
| u64_stats_init(&q->syncp); |
| q->dma_dev = &fp->pdev->dev; |
| |
| q->rqes = fun_alloc_ring_mem(q->dma_dev, nrqe, sizeof(*q->rqes), |
| sizeof(*q->bufs), false, numa_node, |
| &q->rq_dma_addr, (void **)&q->bufs, NULL); |
| if (!q->rqes) |
| goto free_q; |
| |
| q->cqes = fun_alloc_ring_mem(q->dma_dev, ncqe, FUNETH_CQE_SIZE, 0, |
| false, numa_node, &q->cq_dma_addr, NULL, |
| NULL); |
| if (!q->cqes) |
| goto free_rqes; |
| |
| err = fun_rxq_init_cache(&q->cache, nrqe, numa_node); |
| if (err) |
| goto free_cqes; |
| |
| err = fun_rxq_alloc_bufs(q, numa_node); |
| if (err) |
| goto free_cache; |
| |
| q->stats.rx_bufs = q->rq_mask; |
| q->init_state = FUN_QSTATE_INIT_SW; |
| return q; |
| |
| free_cache: |
| fun_rxq_free_cache(q); |
| free_cqes: |
| dma_free_coherent(q->dma_dev, ncqe * FUNETH_CQE_SIZE, q->cqes, |
| q->cq_dma_addr); |
| free_rqes: |
| fun_free_ring_mem(q->dma_dev, nrqe, sizeof(*q->rqes), false, q->rqes, |
| q->rq_dma_addr, q->bufs); |
| free_q: |
| kfree(q); |
| err: |
| netdev_err(dev, "Unable to allocate memory for Rx queue %u\n", qidx); |
| return ERR_PTR(err); |
| } |
| |
| static void fun_rxq_free_sw(struct funeth_rxq *q) |
| { |
| struct funeth_priv *fp = netdev_priv(q->netdev); |
| |
| fun_rxq_free_cache(q); |
| fun_rxq_free_bufs(q); |
| fun_free_ring_mem(q->dma_dev, q->rq_mask + 1, sizeof(*q->rqes), false, |
| q->rqes, q->rq_dma_addr, q->bufs); |
| dma_free_coherent(q->dma_dev, (q->cq_mask + 1) * FUNETH_CQE_SIZE, |
| q->cqes, q->cq_dma_addr); |
| |
| /* Before freeing the queue transfer key counters to the device. */ |
| fp->rx_packets += q->stats.rx_pkts; |
| fp->rx_bytes += q->stats.rx_bytes; |
| fp->rx_dropped += q->stats.rx_map_err + q->stats.rx_mem_drops; |
| |
| kfree(q); |
| } |
| |
| /* Create an Rx queue's resources on the device. */ |
| int fun_rxq_create_dev(struct funeth_rxq *q, struct fun_irq *irq) |
| { |
| struct funeth_priv *fp = netdev_priv(q->netdev); |
| unsigned int ncqe = q->cq_mask + 1; |
| unsigned int nrqe = q->rq_mask + 1; |
| int err; |
| |
| err = xdp_rxq_info_reg(&q->xdp_rxq, q->netdev, q->qidx, |
| irq->napi.napi_id); |
| if (err) |
| goto out; |
| |
| err = xdp_rxq_info_reg_mem_model(&q->xdp_rxq, MEM_TYPE_PAGE_SHARED, |
| NULL); |
| if (err) |
| goto xdp_unreg; |
| |
| q->phase = 1; |
| q->irq_cnt = 0; |
| q->cq_head = 0; |
| q->rq_cons = 0; |
| q->rq_cons_db = 0; |
| q->buf_offset = 0; |
| q->napi = &irq->napi; |
| q->irq_db_val = fp->cq_irq_db; |
| q->next_cqe_info = cqe_to_info(q->cqes); |
| |
| q->xdp_prog = fp->xdp_prog; |
| q->headroom = fp->xdp_prog ? FUN_XDP_HEADROOM : FUN_RX_HEADROOM; |
| |
| err = fun_sq_create(fp->fdev, FUN_ADMIN_RES_CREATE_FLAG_ALLOCATOR | |
| FUN_ADMIN_EPSQ_CREATE_FLAG_RQ, 0, |
| FUN_HCI_ID_INVALID, 0, nrqe, q->rq_dma_addr, 0, 0, |
| 0, 0, fp->fdev->kern_end_qid, PAGE_SHIFT, |
| &q->hw_sqid, &q->rq_db); |
| if (err) |
| goto xdp_unreg; |
| |
| err = fun_cq_create(fp->fdev, FUN_ADMIN_RES_CREATE_FLAG_ALLOCATOR | |
| FUN_ADMIN_EPCQ_CREATE_FLAG_RQ, 0, |
| q->hw_sqid, ilog2(FUNETH_CQE_SIZE), ncqe, |
| q->cq_dma_addr, q->headroom, FUN_RX_TAILROOM, 0, 0, |
| irq->irq_idx, 0, fp->fdev->kern_end_qid, |
| &q->hw_cqid, &q->cq_db); |
| if (err) |
| goto free_rq; |
| |
| irq->rxq = q; |
| writel(q->rq_mask, q->rq_db); |
| q->init_state = FUN_QSTATE_INIT_FULL; |
| |
| netif_info(fp, ifup, q->netdev, |
| "Rx queue %u, depth %u/%u, HW qid %u/%u, IRQ idx %u, node %d, headroom %u\n", |
| q->qidx, ncqe, nrqe, q->hw_cqid, q->hw_sqid, irq->irq_idx, |
| q->numa_node, q->headroom); |
| return 0; |
| |
| free_rq: |
| fun_destroy_sq(fp->fdev, q->hw_sqid); |
| xdp_unreg: |
| xdp_rxq_info_unreg(&q->xdp_rxq); |
| out: |
| netdev_err(q->netdev, |
| "Failed to create Rx queue %u on device, error %d\n", |
| q->qidx, err); |
| return err; |
| } |
| |
| static void fun_rxq_free_dev(struct funeth_rxq *q) |
| { |
| struct funeth_priv *fp = netdev_priv(q->netdev); |
| struct fun_irq *irq; |
| |
| if (q->init_state < FUN_QSTATE_INIT_FULL) |
| return; |
| |
| irq = container_of(q->napi, struct fun_irq, napi); |
| netif_info(fp, ifdown, q->netdev, |
| "Freeing Rx queue %u (id %u/%u), IRQ %u\n", |
| q->qidx, q->hw_cqid, q->hw_sqid, irq->irq_idx); |
| |
| irq->rxq = NULL; |
| xdp_rxq_info_unreg(&q->xdp_rxq); |
| fun_destroy_sq(fp->fdev, q->hw_sqid); |
| fun_destroy_cq(fp->fdev, q->hw_cqid); |
| q->init_state = FUN_QSTATE_INIT_SW; |
| } |
| |
| /* Create or advance an Rx queue, allocating all the host and device resources |
| * needed to reach the target state. |
| */ |
| int funeth_rxq_create(struct net_device *dev, unsigned int qidx, |
| unsigned int ncqe, unsigned int nrqe, struct fun_irq *irq, |
| int state, struct funeth_rxq **qp) |
| { |
| struct funeth_rxq *q = *qp; |
| int err; |
| |
| if (!q) { |
| q = fun_rxq_create_sw(dev, qidx, ncqe, nrqe, irq); |
| if (IS_ERR(q)) |
| return PTR_ERR(q); |
| } |
| |
| if (q->init_state >= state) |
| goto out; |
| |
| err = fun_rxq_create_dev(q, irq); |
| if (err) { |
| if (!*qp) |
| fun_rxq_free_sw(q); |
| return err; |
| } |
| |
| out: |
| *qp = q; |
| return 0; |
| } |
| |
| /* Free Rx queue resources until it reaches the target state. */ |
| struct funeth_rxq *funeth_rxq_free(struct funeth_rxq *q, int state) |
| { |
| if (state < FUN_QSTATE_INIT_FULL) |
| fun_rxq_free_dev(q); |
| |
| if (state == FUN_QSTATE_DESTROYED) { |
| fun_rxq_free_sw(q); |
| q = NULL; |
| } |
| |
| return q; |
| } |