| // SPDX-License-Identifier: (GPL-2.0 OR MIT) | |
| /* | |
| * Microsemi SoCs FDMA driver | |
| * | |
| * Copyright (c) 2021 Microchip | |
| * | |
| * Page recycling code is mostly taken from gianfar driver. | |
| */ | |
| #include <linux/align.h> | |
| #include <linux/bitops.h> | |
| #include <linux/dmapool.h> | |
| #include <linux/dsa/ocelot.h> | |
| #include <linux/netdevice.h> | |
| #include <linux/skbuff.h> | |
| #include "ocelot_fdma.h" | |
| #include "ocelot_qs.h" | |
| DEFINE_STATIC_KEY_FALSE(ocelot_fdma_enabled); | |
| static void ocelot_fdma_writel(struct ocelot *ocelot, u32 reg, u32 data) | |
| { | |
| regmap_write(ocelot->targets[FDMA], reg, data); | |
| } | |
| static u32 ocelot_fdma_readl(struct ocelot *ocelot, u32 reg) | |
| { | |
| u32 retval; | |
| regmap_read(ocelot->targets[FDMA], reg, &retval); | |
| return retval; | |
| } | |
| static dma_addr_t ocelot_fdma_idx_dma(dma_addr_t base, u16 idx) | |
| { | |
| return base + idx * sizeof(struct ocelot_fdma_dcb); | |
| } | |
| static u16 ocelot_fdma_dma_idx(dma_addr_t base, dma_addr_t dma) | |
| { | |
| return (dma - base) / sizeof(struct ocelot_fdma_dcb); | |
| } | |
| static u16 ocelot_fdma_idx_next(u16 idx, u16 ring_sz) | |
| { | |
| return unlikely(idx == ring_sz - 1) ? 0 : idx + 1; | |
| } | |
| static u16 ocelot_fdma_idx_prev(u16 idx, u16 ring_sz) | |
| { | |
| return unlikely(idx == 0) ? ring_sz - 1 : idx - 1; | |
| } | |
| static int ocelot_fdma_rx_ring_free(struct ocelot_fdma *fdma) | |
| { | |
| struct ocelot_fdma_rx_ring *rx_ring = &fdma->rx_ring; | |
| if (rx_ring->next_to_use >= rx_ring->next_to_clean) | |
| return OCELOT_FDMA_RX_RING_SIZE - | |
| (rx_ring->next_to_use - rx_ring->next_to_clean) - 1; | |
| else | |
| return rx_ring->next_to_clean - rx_ring->next_to_use - 1; | |
| } | |
| static int ocelot_fdma_tx_ring_free(struct ocelot_fdma *fdma) | |
| { | |
| struct ocelot_fdma_tx_ring *tx_ring = &fdma->tx_ring; | |
| if (tx_ring->next_to_use >= tx_ring->next_to_clean) | |
| return OCELOT_FDMA_TX_RING_SIZE - | |
| (tx_ring->next_to_use - tx_ring->next_to_clean) - 1; | |
| else | |
| return tx_ring->next_to_clean - tx_ring->next_to_use - 1; | |
| } | |
| static bool ocelot_fdma_tx_ring_empty(struct ocelot_fdma *fdma) | |
| { | |
| struct ocelot_fdma_tx_ring *tx_ring = &fdma->tx_ring; | |
| return tx_ring->next_to_clean == tx_ring->next_to_use; | |
| } | |
| static void ocelot_fdma_activate_chan(struct ocelot *ocelot, dma_addr_t dma, | |
| int chan) | |
| { | |
| ocelot_fdma_writel(ocelot, MSCC_FDMA_DCB_LLP(chan), dma); | |
| /* Barrier to force memory writes to DCB to be completed before starting | |
| * the channel. | |
| */ | |
| wmb(); | |
| ocelot_fdma_writel(ocelot, MSCC_FDMA_CH_ACTIVATE, BIT(chan)); | |
| } | |
| static u32 ocelot_fdma_read_ch_safe(struct ocelot *ocelot) | |
| { | |
| return ocelot_fdma_readl(ocelot, MSCC_FDMA_CH_SAFE); | |
| } | |
| static int ocelot_fdma_wait_chan_safe(struct ocelot *ocelot, int chan) | |
| { | |
| u32 safe; | |
| return readx_poll_timeout_atomic(ocelot_fdma_read_ch_safe, ocelot, safe, | |
| safe & BIT(chan), 0, | |
| OCELOT_FDMA_CH_SAFE_TIMEOUT_US); | |
| } | |
| static void ocelot_fdma_dcb_set_data(struct ocelot_fdma_dcb *dcb, | |
| dma_addr_t dma_addr, | |
| size_t size) | |
| { | |
| u32 offset = dma_addr & 0x3; | |
| dcb->llp = 0; | |
| dcb->datap = ALIGN_DOWN(dma_addr, 4); | |
| dcb->datal = ALIGN_DOWN(size, 4); | |
| dcb->stat = MSCC_FDMA_DCB_STAT_BLOCKO(offset); | |
| } | |
| static bool ocelot_fdma_rx_alloc_page(struct ocelot *ocelot, | |
| struct ocelot_fdma_rx_buf *rxb) | |
| { | |
| dma_addr_t mapping; | |
| struct page *page; | |
| page = dev_alloc_page(); | |
| if (unlikely(!page)) | |
| return false; | |
| mapping = dma_map_page(ocelot->dev, page, 0, PAGE_SIZE, | |
| DMA_FROM_DEVICE); | |
| if (unlikely(dma_mapping_error(ocelot->dev, mapping))) { | |
| __free_page(page); | |
| return false; | |
| } | |
| rxb->page = page; | |
| rxb->page_offset = 0; | |
| rxb->dma_addr = mapping; | |
| return true; | |
| } | |
| static int ocelot_fdma_alloc_rx_buffs(struct ocelot *ocelot, u16 alloc_cnt) | |
| { | |
| struct ocelot_fdma *fdma = ocelot->fdma; | |
| struct ocelot_fdma_rx_ring *rx_ring; | |
| struct ocelot_fdma_rx_buf *rxb; | |
| struct ocelot_fdma_dcb *dcb; | |
| dma_addr_t dma_addr; | |
| int ret = 0; | |
| u16 idx; | |
| rx_ring = &fdma->rx_ring; | |
| idx = rx_ring->next_to_use; | |
| while (alloc_cnt--) { | |
| rxb = &rx_ring->bufs[idx]; | |
| /* try reuse page */ | |
| if (unlikely(!rxb->page)) { | |
| if (unlikely(!ocelot_fdma_rx_alloc_page(ocelot, rxb))) { | |
| dev_err_ratelimited(ocelot->dev, | |
| "Failed to allocate rx\n"); | |
| ret = -ENOMEM; | |
| break; | |
| } | |
| } | |
| dcb = &rx_ring->dcbs[idx]; | |
| dma_addr = rxb->dma_addr + rxb->page_offset; | |
| ocelot_fdma_dcb_set_data(dcb, dma_addr, OCELOT_FDMA_RXB_SIZE); | |
| idx = ocelot_fdma_idx_next(idx, OCELOT_FDMA_RX_RING_SIZE); | |
| /* Chain the DCB to the next one */ | |
| dcb->llp = ocelot_fdma_idx_dma(rx_ring->dcbs_dma, idx); | |
| } | |
| rx_ring->next_to_use = idx; | |
| rx_ring->next_to_alloc = idx; | |
| return ret; | |
| } | |
| static bool ocelot_fdma_tx_dcb_set_skb(struct ocelot *ocelot, | |
| struct ocelot_fdma_tx_buf *tx_buf, | |
| struct ocelot_fdma_dcb *dcb, | |
| struct sk_buff *skb) | |
| { | |
| dma_addr_t mapping; | |
| mapping = dma_map_single(ocelot->dev, skb->data, skb->len, | |
| DMA_TO_DEVICE); | |
| if (unlikely(dma_mapping_error(ocelot->dev, mapping))) | |
| return false; | |
| dma_unmap_addr_set(tx_buf, dma_addr, mapping); | |
| ocelot_fdma_dcb_set_data(dcb, mapping, OCELOT_FDMA_RX_SIZE); | |
| tx_buf->skb = skb; | |
| dcb->stat |= MSCC_FDMA_DCB_STAT_BLOCKL(skb->len); | |
| dcb->stat |= MSCC_FDMA_DCB_STAT_SOF | MSCC_FDMA_DCB_STAT_EOF; | |
| return true; | |
| } | |
| static bool ocelot_fdma_check_stop_rx(struct ocelot *ocelot) | |
| { | |
| u32 llp; | |
| /* Check if the FDMA hits the DCB with LLP == NULL */ | |
| llp = ocelot_fdma_readl(ocelot, MSCC_FDMA_DCB_LLP(MSCC_FDMA_XTR_CHAN)); | |
| if (unlikely(llp)) | |
| return false; | |
| ocelot_fdma_writel(ocelot, MSCC_FDMA_CH_DISABLE, | |
| BIT(MSCC_FDMA_XTR_CHAN)); | |
| return true; | |
| } | |
| static void ocelot_fdma_rx_set_llp(struct ocelot_fdma_rx_ring *rx_ring) | |
| { | |
| struct ocelot_fdma_dcb *dcb; | |
| unsigned int idx; | |
| idx = ocelot_fdma_idx_prev(rx_ring->next_to_use, | |
| OCELOT_FDMA_RX_RING_SIZE); | |
| dcb = &rx_ring->dcbs[idx]; | |
| dcb->llp = 0; | |
| } | |
| static void ocelot_fdma_rx_restart(struct ocelot *ocelot) | |
| { | |
| struct ocelot_fdma *fdma = ocelot->fdma; | |
| struct ocelot_fdma_rx_ring *rx_ring; | |
| const u8 chan = MSCC_FDMA_XTR_CHAN; | |
| dma_addr_t new_llp, dma_base; | |
| unsigned int idx; | |
| u32 llp_prev; | |
| int ret; | |
| rx_ring = &fdma->rx_ring; | |
| ret = ocelot_fdma_wait_chan_safe(ocelot, chan); | |
| if (ret) { | |
| dev_err_ratelimited(ocelot->dev, | |
| "Unable to stop RX channel\n"); | |
| return; | |
| } | |
| ocelot_fdma_rx_set_llp(rx_ring); | |
| /* FDMA stopped on the last DCB that contained a NULL LLP, since | |
| * we processed some DCBs in RX, there is free space, and we must set | |
| * DCB_LLP to point to the next DCB | |
| */ | |
| llp_prev = ocelot_fdma_readl(ocelot, MSCC_FDMA_DCB_LLP_PREV(chan)); | |
| dma_base = rx_ring->dcbs_dma; | |
| /* Get the next DMA addr located after LLP == NULL DCB */ | |
| idx = ocelot_fdma_dma_idx(dma_base, llp_prev); | |
| idx = ocelot_fdma_idx_next(idx, OCELOT_FDMA_RX_RING_SIZE); | |
| new_llp = ocelot_fdma_idx_dma(dma_base, idx); | |
| /* Finally reactivate the channel */ | |
| ocelot_fdma_activate_chan(ocelot, new_llp, chan); | |
| } | |
| static bool ocelot_fdma_add_rx_frag(struct ocelot_fdma_rx_buf *rxb, u32 stat, | |
| struct sk_buff *skb, bool first) | |
| { | |
| int size = MSCC_FDMA_DCB_STAT_BLOCKL(stat); | |
| struct page *page = rxb->page; | |
| if (likely(first)) { | |
| skb_put(skb, size); | |
| } else { | |
| skb_add_rx_frag(skb, skb_shinfo(skb)->nr_frags, page, | |
| rxb->page_offset, size, OCELOT_FDMA_RX_SIZE); | |
| } | |
| /* Try to reuse page */ | |
| if (unlikely(page_ref_count(page) != 1 || page_is_pfmemalloc(page))) | |
| return false; | |
| /* Change offset to the other half */ | |
| rxb->page_offset ^= OCELOT_FDMA_RX_SIZE; | |
| page_ref_inc(page); | |
| return true; | |
| } | |
| static void ocelot_fdma_reuse_rx_page(struct ocelot *ocelot, | |
| struct ocelot_fdma_rx_buf *old_rxb) | |
| { | |
| struct ocelot_fdma_rx_ring *rx_ring = &ocelot->fdma->rx_ring; | |
| struct ocelot_fdma_rx_buf *new_rxb; | |
| new_rxb = &rx_ring->bufs[rx_ring->next_to_alloc]; | |
| rx_ring->next_to_alloc = ocelot_fdma_idx_next(rx_ring->next_to_alloc, | |
| OCELOT_FDMA_RX_RING_SIZE); | |
| /* Copy page reference */ | |
| *new_rxb = *old_rxb; | |
| /* Sync for use by the device */ | |
| dma_sync_single_range_for_device(ocelot->dev, old_rxb->dma_addr, | |
| old_rxb->page_offset, | |
| OCELOT_FDMA_RX_SIZE, DMA_FROM_DEVICE); | |
| } | |
| static struct sk_buff *ocelot_fdma_get_skb(struct ocelot *ocelot, u32 stat, | |
| struct ocelot_fdma_rx_buf *rxb, | |
| struct sk_buff *skb) | |
| { | |
| bool first = false; | |
| /* Allocate skb head and data */ | |
| if (likely(!skb)) { | |
| void *buff_addr = page_address(rxb->page) + | |
| rxb->page_offset; | |
| skb = build_skb(buff_addr, OCELOT_FDMA_SKBFRAG_SIZE); | |
| if (unlikely(!skb)) { | |
| dev_err_ratelimited(ocelot->dev, | |
| "build_skb failed !\n"); | |
| return NULL; | |
| } | |
| first = true; | |
| } | |
| dma_sync_single_range_for_cpu(ocelot->dev, rxb->dma_addr, | |
| rxb->page_offset, OCELOT_FDMA_RX_SIZE, | |
| DMA_FROM_DEVICE); | |
| if (ocelot_fdma_add_rx_frag(rxb, stat, skb, first)) { | |
| /* Reuse the free half of the page for the next_to_alloc DCB*/ | |
| ocelot_fdma_reuse_rx_page(ocelot, rxb); | |
| } else { | |
| /* page cannot be reused, unmap it */ | |
| dma_unmap_page(ocelot->dev, rxb->dma_addr, PAGE_SIZE, | |
| DMA_FROM_DEVICE); | |
| } | |
| /* clear rx buff content */ | |
| rxb->page = NULL; | |
| return skb; | |
| } | |
| static bool ocelot_fdma_receive_skb(struct ocelot *ocelot, struct sk_buff *skb) | |
| { | |
| struct net_device *ndev; | |
| void *xfh = skb->data; | |
| u64 timestamp; | |
| u64 src_port; | |
| skb_pull(skb, OCELOT_TAG_LEN); | |
| ocelot_xfh_get_src_port(xfh, &src_port); | |
| if (unlikely(src_port >= ocelot->num_phys_ports)) | |
| return false; | |
| ndev = ocelot_port_to_netdev(ocelot, src_port); | |
| if (unlikely(!ndev)) | |
| return false; | |
| if (pskb_trim(skb, skb->len - ETH_FCS_LEN)) | |
| return false; | |
| skb->dev = ndev; | |
| skb->protocol = eth_type_trans(skb, skb->dev); | |
| skb->dev->stats.rx_bytes += skb->len; | |
| skb->dev->stats.rx_packets++; | |
| if (ocelot->ptp) { | |
| ocelot_xfh_get_rew_val(xfh, ×tamp); | |
| ocelot_ptp_rx_timestamp(ocelot, skb, timestamp); | |
| } | |
| if (likely(!skb_defer_rx_timestamp(skb))) | |
| netif_receive_skb(skb); | |
| return true; | |
| } | |
| static int ocelot_fdma_rx_get(struct ocelot *ocelot, int budget) | |
| { | |
| struct ocelot_fdma *fdma = ocelot->fdma; | |
| struct ocelot_fdma_rx_ring *rx_ring; | |
| struct ocelot_fdma_rx_buf *rxb; | |
| struct ocelot_fdma_dcb *dcb; | |
| struct sk_buff *skb; | |
| int work_done = 0; | |
| int cleaned_cnt; | |
| u32 stat; | |
| u16 idx; | |
| cleaned_cnt = ocelot_fdma_rx_ring_free(fdma); | |
| rx_ring = &fdma->rx_ring; | |
| skb = rx_ring->skb; | |
| while (budget--) { | |
| idx = rx_ring->next_to_clean; | |
| dcb = &rx_ring->dcbs[idx]; | |
| stat = dcb->stat; | |
| if (MSCC_FDMA_DCB_STAT_BLOCKL(stat) == 0) | |
| break; | |
| /* New packet is a start of frame but we already got a skb set, | |
| * we probably lost an EOF packet, free skb | |
| */ | |
| if (unlikely(skb && (stat & MSCC_FDMA_DCB_STAT_SOF))) { | |
| dev_kfree_skb(skb); | |
| skb = NULL; | |
| } | |
| rxb = &rx_ring->bufs[idx]; | |
| /* Fetch next to clean buffer from the rx_ring */ | |
| skb = ocelot_fdma_get_skb(ocelot, stat, rxb, skb); | |
| if (unlikely(!skb)) | |
| break; | |
| work_done++; | |
| cleaned_cnt++; | |
| idx = ocelot_fdma_idx_next(idx, OCELOT_FDMA_RX_RING_SIZE); | |
| rx_ring->next_to_clean = idx; | |
| if (unlikely(stat & MSCC_FDMA_DCB_STAT_ABORT || | |
| stat & MSCC_FDMA_DCB_STAT_PD)) { | |
| dev_err_ratelimited(ocelot->dev, | |
| "DCB aborted or pruned\n"); | |
| dev_kfree_skb(skb); | |
| skb = NULL; | |
| continue; | |
| } | |
| /* We still need to process the other fragment of the packet | |
| * before delivering it to the network stack | |
| */ | |
| if (!(stat & MSCC_FDMA_DCB_STAT_EOF)) | |
| continue; | |
| if (unlikely(!ocelot_fdma_receive_skb(ocelot, skb))) | |
| dev_kfree_skb(skb); | |
| skb = NULL; | |
| } | |
| rx_ring->skb = skb; | |
| if (cleaned_cnt) | |
| ocelot_fdma_alloc_rx_buffs(ocelot, cleaned_cnt); | |
| return work_done; | |
| } | |
| static void ocelot_fdma_wakeup_netdev(struct ocelot *ocelot) | |
| { | |
| struct ocelot_port_private *priv; | |
| struct ocelot_port *ocelot_port; | |
| struct net_device *dev; | |
| int port; | |
| for (port = 0; port < ocelot->num_phys_ports; port++) { | |
| ocelot_port = ocelot->ports[port]; | |
| if (!ocelot_port) | |
| continue; | |
| priv = container_of(ocelot_port, struct ocelot_port_private, | |
| port); | |
| dev = priv->dev; | |
| if (unlikely(netif_queue_stopped(dev))) | |
| netif_wake_queue(dev); | |
| } | |
| } | |
| static void ocelot_fdma_tx_cleanup(struct ocelot *ocelot, int budget) | |
| { | |
| struct ocelot_fdma *fdma = ocelot->fdma; | |
| struct ocelot_fdma_tx_ring *tx_ring; | |
| struct ocelot_fdma_tx_buf *buf; | |
| unsigned int new_null_llp_idx; | |
| struct ocelot_fdma_dcb *dcb; | |
| bool end_of_list = false; | |
| struct sk_buff *skb; | |
| dma_addr_t dma; | |
| u32 dcb_llp; | |
| u16 ntc; | |
| int ret; | |
| tx_ring = &fdma->tx_ring; | |
| /* Purge the TX packets that have been sent up to the NULL llp or the | |
| * end of done list. | |
| */ | |
| while (!ocelot_fdma_tx_ring_empty(fdma)) { | |
| ntc = tx_ring->next_to_clean; | |
| dcb = &tx_ring->dcbs[ntc]; | |
| if (!(dcb->stat & MSCC_FDMA_DCB_STAT_PD)) | |
| break; | |
| buf = &tx_ring->bufs[ntc]; | |
| skb = buf->skb; | |
| dma_unmap_single(ocelot->dev, dma_unmap_addr(buf, dma_addr), | |
| skb->len, DMA_TO_DEVICE); | |
| napi_consume_skb(skb, budget); | |
| dcb_llp = dcb->llp; | |
| /* Only update after accessing all dcb fields */ | |
| tx_ring->next_to_clean = ocelot_fdma_idx_next(ntc, | |
| OCELOT_FDMA_TX_RING_SIZE); | |
| /* If we hit the NULL LLP, stop, we might need to reload FDMA */ | |
| if (dcb_llp == 0) { | |
| end_of_list = true; | |
| break; | |
| } | |
| } | |
| /* No need to try to wake if there were no TX cleaned_cnt up. */ | |
| if (ocelot_fdma_tx_ring_free(fdma)) | |
| ocelot_fdma_wakeup_netdev(ocelot); | |
| /* If there is still some DCBs to be processed by the FDMA or if the | |
| * pending list is empty, there is no need to restart the FDMA. | |
| */ | |
| if (!end_of_list || ocelot_fdma_tx_ring_empty(fdma)) | |
| return; | |
| ret = ocelot_fdma_wait_chan_safe(ocelot, MSCC_FDMA_INJ_CHAN); | |
| if (ret) { | |
| dev_warn(ocelot->dev, | |
| "Failed to wait for TX channel to stop\n"); | |
| return; | |
| } | |
| /* Set NULL LLP to be the last DCB used */ | |
| new_null_llp_idx = ocelot_fdma_idx_prev(tx_ring->next_to_use, | |
| OCELOT_FDMA_TX_RING_SIZE); | |
| dcb = &tx_ring->dcbs[new_null_llp_idx]; | |
| dcb->llp = 0; | |
| dma = ocelot_fdma_idx_dma(tx_ring->dcbs_dma, tx_ring->next_to_clean); | |
| ocelot_fdma_activate_chan(ocelot, dma, MSCC_FDMA_INJ_CHAN); | |
| } | |
| static int ocelot_fdma_napi_poll(struct napi_struct *napi, int budget) | |
| { | |
| struct ocelot_fdma *fdma = container_of(napi, struct ocelot_fdma, napi); | |
| struct ocelot *ocelot = fdma->ocelot; | |
| int work_done = 0; | |
| bool rx_stopped; | |
| ocelot_fdma_tx_cleanup(ocelot, budget); | |
| rx_stopped = ocelot_fdma_check_stop_rx(ocelot); | |
| work_done = ocelot_fdma_rx_get(ocelot, budget); | |
| if (rx_stopped) | |
| ocelot_fdma_rx_restart(ocelot); | |
| if (work_done < budget) { | |
| napi_complete_done(&fdma->napi, work_done); | |
| ocelot_fdma_writel(ocelot, MSCC_FDMA_INTR_ENA, | |
| BIT(MSCC_FDMA_INJ_CHAN) | | |
| BIT(MSCC_FDMA_XTR_CHAN)); | |
| } | |
| return work_done; | |
| } | |
| static irqreturn_t ocelot_fdma_interrupt(int irq, void *dev_id) | |
| { | |
| u32 ident, llp, frm, err, err_code; | |
| struct ocelot *ocelot = dev_id; | |
| ident = ocelot_fdma_readl(ocelot, MSCC_FDMA_INTR_IDENT); | |
| frm = ocelot_fdma_readl(ocelot, MSCC_FDMA_INTR_FRM); | |
| llp = ocelot_fdma_readl(ocelot, MSCC_FDMA_INTR_LLP); | |
| ocelot_fdma_writel(ocelot, MSCC_FDMA_INTR_LLP, llp & ident); | |
| ocelot_fdma_writel(ocelot, MSCC_FDMA_INTR_FRM, frm & ident); | |
| if (frm || llp) { | |
| ocelot_fdma_writel(ocelot, MSCC_FDMA_INTR_ENA, 0); | |
| napi_schedule(&ocelot->fdma->napi); | |
| } | |
| err = ocelot_fdma_readl(ocelot, MSCC_FDMA_EVT_ERR); | |
| if (unlikely(err)) { | |
| err_code = ocelot_fdma_readl(ocelot, MSCC_FDMA_EVT_ERR_CODE); | |
| dev_err_ratelimited(ocelot->dev, | |
| "Error ! chans mask: %#x, code: %#x\n", | |
| err, err_code); | |
| ocelot_fdma_writel(ocelot, MSCC_FDMA_EVT_ERR, err); | |
| ocelot_fdma_writel(ocelot, MSCC_FDMA_EVT_ERR_CODE, err_code); | |
| } | |
| return IRQ_HANDLED; | |
| } | |
| static void ocelot_fdma_send_skb(struct ocelot *ocelot, | |
| struct ocelot_fdma *fdma, struct sk_buff *skb) | |
| { | |
| struct ocelot_fdma_tx_ring *tx_ring = &fdma->tx_ring; | |
| struct ocelot_fdma_tx_buf *tx_buf; | |
| struct ocelot_fdma_dcb *dcb; | |
| dma_addr_t dma; | |
| u16 next_idx; | |
| dcb = &tx_ring->dcbs[tx_ring->next_to_use]; | |
| tx_buf = &tx_ring->bufs[tx_ring->next_to_use]; | |
| if (!ocelot_fdma_tx_dcb_set_skb(ocelot, tx_buf, dcb, skb)) { | |
| dev_kfree_skb_any(skb); | |
| return; | |
| } | |
| next_idx = ocelot_fdma_idx_next(tx_ring->next_to_use, | |
| OCELOT_FDMA_TX_RING_SIZE); | |
| skb_tx_timestamp(skb); | |
| /* If the FDMA TX chan is empty, then enqueue the DCB directly */ | |
| if (ocelot_fdma_tx_ring_empty(fdma)) { | |
| dma = ocelot_fdma_idx_dma(tx_ring->dcbs_dma, | |
| tx_ring->next_to_use); | |
| ocelot_fdma_activate_chan(ocelot, dma, MSCC_FDMA_INJ_CHAN); | |
| } else { | |
| /* Chain the DCBs */ | |
| dcb->llp = ocelot_fdma_idx_dma(tx_ring->dcbs_dma, next_idx); | |
| } | |
| tx_ring->next_to_use = next_idx; | |
| } | |
| static int ocelot_fdma_prepare_skb(struct ocelot *ocelot, int port, u32 rew_op, | |
| struct sk_buff *skb, struct net_device *dev) | |
| { | |
| int needed_headroom = max_t(int, OCELOT_TAG_LEN - skb_headroom(skb), 0); | |
| int needed_tailroom = max_t(int, ETH_FCS_LEN - skb_tailroom(skb), 0); | |
| void *ifh; | |
| int err; | |
| if (unlikely(needed_headroom || needed_tailroom || | |
| skb_header_cloned(skb))) { | |
| err = pskb_expand_head(skb, needed_headroom, needed_tailroom, | |
| GFP_ATOMIC); | |
| if (unlikely(err)) { | |
| dev_kfree_skb_any(skb); | |
| return 1; | |
| } | |
| } | |
| err = skb_linearize(skb); | |
| if (err) { | |
| net_err_ratelimited("%s: skb_linearize error (%d)!\n", | |
| dev->name, err); | |
| dev_kfree_skb_any(skb); | |
| return 1; | |
| } | |
| ifh = skb_push(skb, OCELOT_TAG_LEN); | |
| skb_put(skb, ETH_FCS_LEN); | |
| ocelot_ifh_set_basic(ifh, ocelot, port, rew_op, skb); | |
| return 0; | |
| } | |
| int ocelot_fdma_inject_frame(struct ocelot *ocelot, int port, u32 rew_op, | |
| struct sk_buff *skb, struct net_device *dev) | |
| { | |
| struct ocelot_fdma *fdma = ocelot->fdma; | |
| int ret = NETDEV_TX_OK; | |
| spin_lock(&fdma->tx_ring.xmit_lock); | |
| if (ocelot_fdma_tx_ring_free(fdma) == 0) { | |
| netif_stop_queue(dev); | |
| ret = NETDEV_TX_BUSY; | |
| goto out; | |
| } | |
| if (ocelot_fdma_prepare_skb(ocelot, port, rew_op, skb, dev)) | |
| goto out; | |
| ocelot_fdma_send_skb(ocelot, fdma, skb); | |
| out: | |
| spin_unlock(&fdma->tx_ring.xmit_lock); | |
| return ret; | |
| } | |
| static void ocelot_fdma_free_rx_ring(struct ocelot *ocelot) | |
| { | |
| struct ocelot_fdma *fdma = ocelot->fdma; | |
| struct ocelot_fdma_rx_ring *rx_ring; | |
| struct ocelot_fdma_rx_buf *rxb; | |
| u16 idx; | |
| rx_ring = &fdma->rx_ring; | |
| idx = rx_ring->next_to_clean; | |
| /* Free the pages held in the RX ring */ | |
| while (idx != rx_ring->next_to_use) { | |
| rxb = &rx_ring->bufs[idx]; | |
| dma_unmap_page(ocelot->dev, rxb->dma_addr, PAGE_SIZE, | |
| DMA_FROM_DEVICE); | |
| __free_page(rxb->page); | |
| idx = ocelot_fdma_idx_next(idx, OCELOT_FDMA_RX_RING_SIZE); | |
| } | |
| if (fdma->rx_ring.skb) | |
| dev_kfree_skb_any(fdma->rx_ring.skb); | |
| } | |
| static void ocelot_fdma_free_tx_ring(struct ocelot *ocelot) | |
| { | |
| struct ocelot_fdma *fdma = ocelot->fdma; | |
| struct ocelot_fdma_tx_ring *tx_ring; | |
| struct ocelot_fdma_tx_buf *txb; | |
| struct sk_buff *skb; | |
| u16 idx; | |
| tx_ring = &fdma->tx_ring; | |
| idx = tx_ring->next_to_clean; | |
| while (idx != tx_ring->next_to_use) { | |
| txb = &tx_ring->bufs[idx]; | |
| skb = txb->skb; | |
| dma_unmap_single(ocelot->dev, dma_unmap_addr(txb, dma_addr), | |
| skb->len, DMA_TO_DEVICE); | |
| dev_kfree_skb_any(skb); | |
| idx = ocelot_fdma_idx_next(idx, OCELOT_FDMA_TX_RING_SIZE); | |
| } | |
| } | |
| static int ocelot_fdma_rings_alloc(struct ocelot *ocelot) | |
| { | |
| struct ocelot_fdma *fdma = ocelot->fdma; | |
| struct ocelot_fdma_dcb *dcbs; | |
| unsigned int adjust; | |
| dma_addr_t dcbs_dma; | |
| int ret; | |
| /* Create a pool of consistent memory blocks for hardware descriptors */ | |
| fdma->dcbs_base = dmam_alloc_coherent(ocelot->dev, | |
| OCELOT_DCBS_HW_ALLOC_SIZE, | |
| &fdma->dcbs_dma_base, GFP_KERNEL); | |
| if (!fdma->dcbs_base) | |
| return -ENOMEM; | |
| /* DCBs must be aligned on a 32bit boundary */ | |
| dcbs = fdma->dcbs_base; | |
| dcbs_dma = fdma->dcbs_dma_base; | |
| if (!IS_ALIGNED(dcbs_dma, 4)) { | |
| adjust = dcbs_dma & 0x3; | |
| dcbs_dma = ALIGN(dcbs_dma, 4); | |
| dcbs = (void *)dcbs + adjust; | |
| } | |
| /* TX queue */ | |
| fdma->tx_ring.dcbs = dcbs; | |
| fdma->tx_ring.dcbs_dma = dcbs_dma; | |
| spin_lock_init(&fdma->tx_ring.xmit_lock); | |
| /* RX queue */ | |
| fdma->rx_ring.dcbs = dcbs + OCELOT_FDMA_TX_RING_SIZE; | |
| fdma->rx_ring.dcbs_dma = dcbs_dma + OCELOT_FDMA_TX_DCB_SIZE; | |
| ret = ocelot_fdma_alloc_rx_buffs(ocelot, | |
| ocelot_fdma_tx_ring_free(fdma)); | |
| if (ret) { | |
| ocelot_fdma_free_rx_ring(ocelot); | |
| return ret; | |
| } | |
| /* Set the last DCB LLP as NULL, this is normally done when restarting | |
| * the RX chan, but this is for the first run | |
| */ | |
| ocelot_fdma_rx_set_llp(&fdma->rx_ring); | |
| return 0; | |
| } | |
| void ocelot_fdma_netdev_init(struct ocelot *ocelot, struct net_device *dev) | |
| { | |
| struct ocelot_fdma *fdma = ocelot->fdma; | |
| dev->needed_headroom = OCELOT_TAG_LEN; | |
| dev->needed_tailroom = ETH_FCS_LEN; | |
| if (fdma->ndev) | |
| return; | |
| fdma->ndev = dev; | |
| netif_napi_add_weight(dev, &fdma->napi, ocelot_fdma_napi_poll, | |
| OCELOT_FDMA_WEIGHT); | |
| } | |
| void ocelot_fdma_netdev_deinit(struct ocelot *ocelot, struct net_device *dev) | |
| { | |
| struct ocelot_fdma *fdma = ocelot->fdma; | |
| if (fdma->ndev == dev) { | |
| netif_napi_del(&fdma->napi); | |
| fdma->ndev = NULL; | |
| } | |
| } | |
| void ocelot_fdma_init(struct platform_device *pdev, struct ocelot *ocelot) | |
| { | |
| struct device *dev = ocelot->dev; | |
| struct ocelot_fdma *fdma; | |
| int ret; | |
| fdma = devm_kzalloc(dev, sizeof(*fdma), GFP_KERNEL); | |
| if (!fdma) | |
| return; | |
| ocelot->fdma = fdma; | |
| ocelot->dev->coherent_dma_mask = DMA_BIT_MASK(32); | |
| ocelot_fdma_writel(ocelot, MSCC_FDMA_INTR_ENA, 0); | |
| fdma->ocelot = ocelot; | |
| fdma->irq = platform_get_irq_byname(pdev, "fdma"); | |
| ret = devm_request_irq(dev, fdma->irq, ocelot_fdma_interrupt, 0, | |
| dev_name(dev), ocelot); | |
| if (ret) | |
| goto err_free_fdma; | |
| ret = ocelot_fdma_rings_alloc(ocelot); | |
| if (ret) | |
| goto err_free_irq; | |
| static_branch_enable(&ocelot_fdma_enabled); | |
| return; | |
| err_free_irq: | |
| devm_free_irq(dev, fdma->irq, fdma); | |
| err_free_fdma: | |
| devm_kfree(dev, fdma); | |
| ocelot->fdma = NULL; | |
| } | |
| void ocelot_fdma_start(struct ocelot *ocelot) | |
| { | |
| struct ocelot_fdma *fdma = ocelot->fdma; | |
| /* Reconfigure for extraction and injection using DMA */ | |
| ocelot_write_rix(ocelot, QS_INJ_GRP_CFG_MODE(2), QS_INJ_GRP_CFG, 0); | |
| ocelot_write_rix(ocelot, QS_INJ_CTRL_GAP_SIZE(0), QS_INJ_CTRL, 0); | |
| ocelot_write_rix(ocelot, QS_XTR_GRP_CFG_MODE(2), QS_XTR_GRP_CFG, 0); | |
| ocelot_fdma_writel(ocelot, MSCC_FDMA_INTR_LLP, 0xffffffff); | |
| ocelot_fdma_writel(ocelot, MSCC_FDMA_INTR_FRM, 0xffffffff); | |
| ocelot_fdma_writel(ocelot, MSCC_FDMA_INTR_LLP_ENA, | |
| BIT(MSCC_FDMA_INJ_CHAN) | BIT(MSCC_FDMA_XTR_CHAN)); | |
| ocelot_fdma_writel(ocelot, MSCC_FDMA_INTR_FRM_ENA, | |
| BIT(MSCC_FDMA_XTR_CHAN)); | |
| ocelot_fdma_writel(ocelot, MSCC_FDMA_INTR_ENA, | |
| BIT(MSCC_FDMA_INJ_CHAN) | BIT(MSCC_FDMA_XTR_CHAN)); | |
| napi_enable(&fdma->napi); | |
| ocelot_fdma_activate_chan(ocelot, ocelot->fdma->rx_ring.dcbs_dma, | |
| MSCC_FDMA_XTR_CHAN); | |
| } | |
| void ocelot_fdma_deinit(struct ocelot *ocelot) | |
| { | |
| struct ocelot_fdma *fdma = ocelot->fdma; | |
| ocelot_fdma_writel(ocelot, MSCC_FDMA_INTR_ENA, 0); | |
| ocelot_fdma_writel(ocelot, MSCC_FDMA_CH_FORCEDIS, | |
| BIT(MSCC_FDMA_XTR_CHAN)); | |
| ocelot_fdma_writel(ocelot, MSCC_FDMA_CH_FORCEDIS, | |
| BIT(MSCC_FDMA_INJ_CHAN)); | |
| napi_synchronize(&fdma->napi); | |
| napi_disable(&fdma->napi); | |
| ocelot_fdma_free_rx_ring(ocelot); | |
| ocelot_fdma_free_tx_ring(ocelot); | |
| } |