diff options
Diffstat (limited to 'drivers')
-rw-r--r-- | drivers/staging/fsl-dpaa2/ethernet/Makefile | 5 | ||||
-rw-r--r-- | drivers/staging/fsl-dpaa2/ethernet/dpaa2-eth-trace.h | 184 | ||||
-rw-r--r-- | drivers/staging/fsl-dpaa2/ethernet/dpaa2-eth.c | 3121 | ||||
-rw-r--r-- | drivers/staging/fsl-dpaa2/ethernet/dpaa2-eth.h | 459 | ||||
-rw-r--r-- | drivers/staging/fsl-dpaa2/ethernet/dpaa2-ethtool.c | 801 | ||||
-rw-r--r-- | drivers/staging/fsl-dpaa2/ethernet/dpni-cmd.h | 2 |
6 files changed, 4569 insertions, 3 deletions
diff --git a/drivers/staging/fsl-dpaa2/ethernet/Makefile b/drivers/staging/fsl-dpaa2/ethernet/Makefile index 83b6264..77b0b74 100644 --- a/drivers/staging/fsl-dpaa2/ethernet/Makefile +++ b/drivers/staging/fsl-dpaa2/ethernet/Makefile @@ -4,4 +4,7 @@ obj-$(CONFIG_FSL_DPAA2_ETH) += fsl-dpaa2-eth.o -fsl-dpaa2-eth-objs := dpni.o +fsl-dpaa2-eth-objs := dpaa2-eth.o dpaa2-ethtool.o dpni.o + +# Needed by the tracing framework +CFLAGS_dpaa2-eth.o := -I$(src) diff --git a/drivers/staging/fsl-dpaa2/ethernet/dpaa2-eth-trace.h b/drivers/staging/fsl-dpaa2/ethernet/dpaa2-eth-trace.h new file mode 100644 index 0000000..e8e6522 --- /dev/null +++ b/drivers/staging/fsl-dpaa2/ethernet/dpaa2-eth-trace.h @@ -0,0 +1,184 @@ +/* Copyright 2014-2015 Freescale Semiconductor Inc. + * + * 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 Freescale Semiconductor nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * + * ALTERNATIVELY, this software may be distributed under the terms of the + * GNU General Public License ("GPL") as published by the Free Software + * Foundation, either version 2 of that License or (at your option) any + * later version. + * + * THIS SOFTWARE IS PROVIDED BY Freescale Semiconductor ``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 Freescale Semiconductor 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. + */ + +#undef TRACE_SYSTEM +#define TRACE_SYSTEM dpaa2_eth + +#if !defined(_DPAA2_ETH_TRACE_H) || defined(TRACE_HEADER_MULTI_READ) +#define _DPAA2_ETH_TRACE_H + +#include <linux/skbuff.h> +#include <linux/netdevice.h> +#include <linux/tracepoint.h> + +#define TR_FMT "[%s] fd: addr=0x%llx, len=%u, off=%u" +/* trace_printk format for raw buffer event class */ +#define TR_BUF_FMT "[%s] vaddr=%p size=%zu dma_addr=%pad map_size=%zu bpid=%d" + +/* This is used to declare a class of events. + * individual events of this type will be defined below. + */ + +/* Store details about a frame descriptor */ +DECLARE_EVENT_CLASS(dpaa2_eth_fd, + /* Trace function prototype */ + TP_PROTO(struct net_device *netdev, + const struct dpaa2_fd *fd), + + /* Repeat argument list here */ + TP_ARGS(netdev, fd), + + /* A structure containing the relevant information we want + * to record. Declare name and type for each normal element, + * name, type and size for arrays. Use __string for variable + * length strings. + */ + TP_STRUCT__entry( + __field(u64, fd_addr) + __field(u32, fd_len) + __field(u16, fd_offset) + __string(name, netdev->name) + ), + + /* The function that assigns values to the above declared + * fields + */ + TP_fast_assign( + __entry->fd_addr = dpaa2_fd_get_addr(fd); + __entry->fd_len = dpaa2_fd_get_len(fd); + __entry->fd_offset = dpaa2_fd_get_offset(fd); + __assign_str(name, netdev->name); + ), + + /* This is what gets printed when the trace event is + * triggered. + */ + TP_printk(TR_FMT, + __get_str(name), + __entry->fd_addr, + __entry->fd_len, + __entry->fd_offset) +); + +/* Now declare events of the above type. Format is: + * DEFINE_EVENT(class, name, proto, args), with proto and args same as for class + */ + +/* Tx (egress) fd */ +DEFINE_EVENT(dpaa2_eth_fd, dpaa2_tx_fd, + TP_PROTO(struct net_device *netdev, + const struct dpaa2_fd *fd), + + TP_ARGS(netdev, fd) +); + +/* Rx fd */ +DEFINE_EVENT(dpaa2_eth_fd, dpaa2_rx_fd, + TP_PROTO(struct net_device *netdev, + const struct dpaa2_fd *fd), + + TP_ARGS(netdev, fd) +); + +/* Tx confirmation fd */ +DEFINE_EVENT(dpaa2_eth_fd, dpaa2_tx_conf_fd, + TP_PROTO(struct net_device *netdev, + const struct dpaa2_fd *fd), + + TP_ARGS(netdev, fd) +); + +/* Log data about raw buffers. Useful for tracing DPBP content. */ +TRACE_EVENT(dpaa2_eth_buf_seed, + /* Trace function prototype */ + TP_PROTO(struct net_device *netdev, + /* virtual address and size */ + void *vaddr, + size_t size, + /* dma map address and size */ + dma_addr_t dma_addr, + size_t map_size, + /* buffer pool id, if relevant */ + u16 bpid), + + /* Repeat argument list here */ + TP_ARGS(netdev, vaddr, size, dma_addr, map_size, bpid), + + /* A structure containing the relevant information we want + * to record. Declare name and type for each normal element, + * name, type and size for arrays. Use __string for variable + * length strings. + */ + TP_STRUCT__entry( + __field(void *, vaddr) + __field(size_t, size) + __field(dma_addr_t, dma_addr) + __field(size_t, map_size) + __field(u16, bpid) + __string(name, netdev->name) + ), + + /* The function that assigns values to the above declared + * fields + */ + TP_fast_assign( + __entry->vaddr = vaddr; + __entry->size = size; + __entry->dma_addr = dma_addr; + __entry->map_size = map_size; + __entry->bpid = bpid; + __assign_str(name, netdev->name); + ), + + /* This is what gets printed when the trace event is + * triggered. + */ + TP_printk(TR_BUF_FMT, + __get_str(name), + __entry->vaddr, + __entry->size, + &__entry->dma_addr, + __entry->map_size, + __entry->bpid) +); + +/* If only one event of a certain type needs to be declared, use TRACE_EVENT(). + * The syntax is the same as for DECLARE_EVENT_CLASS(). + */ + +#endif /* _DPAA2_ETH_TRACE_H */ + +/* This must be outside ifdef _DPAA2_ETH_TRACE_H */ +#undef TRACE_INCLUDE_PATH +#define TRACE_INCLUDE_PATH . +#undef TRACE_INCLUDE_FILE +#define TRACE_INCLUDE_FILE dpaa2-eth-trace +#include <trace/define_trace.h> diff --git a/drivers/staging/fsl-dpaa2/ethernet/dpaa2-eth.c b/drivers/staging/fsl-dpaa2/ethernet/dpaa2-eth.c new file mode 100644 index 0000000..80b123c --- /dev/null +++ b/drivers/staging/fsl-dpaa2/ethernet/dpaa2-eth.c @@ -0,0 +1,3121 @@ +/* Copyright 2014-2015 Freescale Semiconductor Inc. + * + * 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 Freescale Semiconductor nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * + * ALTERNATIVELY, this software may be distributed under the terms of the + * GNU General Public License ("GPL") as published by the Free Software + * Foundation, either version 2 of that License or (at your option) any + * later version. + * + * THIS SOFTWARE IS PROVIDED BY Freescale Semiconductor ``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 Freescale Semiconductor 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 <linux/init.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/etherdevice.h> +#include <linux/of_net.h> +#include <linux/interrupt.h> +#include <linux/debugfs.h> +#include <linux/kthread.h> +#include <linux/msi.h> +#include <linux/net_tstamp.h> + +#include "../../fsl-mc/include/dpbp.h" +#include "../../fsl-mc/include/dpcon.h" +#include "../../fsl-mc/include/mc.h" +#include "../../fsl-mc/include/mc-sys.h" +#include "dpaa2-eth.h" +#include "dpkg.h" + +/* CREATE_TRACE_POINTS only needs to be defined once. Other dpa files + * using trace events only need to #include <trace/events/sched.h> + */ +#define CREATE_TRACE_POINTS +#include "dpaa2-eth-trace.h" + +MODULE_LICENSE("Dual BSD/GPL"); +MODULE_AUTHOR("Freescale Semiconductor, Inc"); +MODULE_DESCRIPTION("Freescale DPAA2 Ethernet Driver"); + +const char dpaa2_eth_drv_version[] = "0.1"; + +static void validate_rx_csum(struct dpaa2_eth_priv *priv, + u32 fd_status, + struct sk_buff *skb) +{ + skb_checksum_none_assert(skb); + + /* HW checksum validation is disabled, nothing to do here */ + if (!(priv->net_dev->features & NETIF_F_RXCSUM)) + return; + + /* Read checksum validation bits */ + if (!((fd_status & DPAA2_FAS_L3CV) && + (fd_status & DPAA2_FAS_L4CV))) + return; + + /* Inform the stack there's no need to compute L3/L4 csum anymore */ + skb->ip_summed = CHECKSUM_UNNECESSARY; +} + +/* Free a received FD. + * Not to be used for Tx conf FDs or on any other paths. + */ +static void free_rx_fd(struct dpaa2_eth_priv *priv, + const struct dpaa2_fd *fd, + void *vaddr) +{ + struct device *dev = priv->net_dev->dev.parent; + dma_addr_t addr = dpaa2_fd_get_addr(fd); + u8 fd_format = dpaa2_fd_get_format(fd); + struct dpaa2_sg_entry *sgt; + void *sg_vaddr; + int i; + + /* If single buffer frame, just free the data buffer */ + if (fd_format == dpaa2_fd_single) + goto free_buf; + else if (fd_format != dpaa2_fd_sg) + /* we don't support any other format */ + return; + + /* For S/G frames, we first need to free all SG entries */ + sgt = vaddr + dpaa2_fd_get_offset(fd); + for (i = 0; i < DPAA2_ETH_MAX_SG_ENTRIES; i++) { + addr = dpaa2_sg_get_addr(&sgt[i]); + dma_unmap_single(dev, addr, DPAA2_ETH_RX_BUF_SIZE, + DMA_FROM_DEVICE); + + sg_vaddr = phys_to_virt(addr); + put_page(virt_to_head_page(sg_vaddr)); + + if (dpaa2_sg_is_final(&sgt[i])) + break; + } + +free_buf: + put_page(virt_to_head_page(vaddr)); +} + +/* Build a linear skb based on a single-buffer frame descriptor */ +static struct sk_buff *build_linear_skb(struct dpaa2_eth_priv *priv, + struct dpaa2_eth_channel *ch, + const struct dpaa2_fd *fd, + void *fd_vaddr) +{ + struct sk_buff *skb = NULL; + u16 fd_offset = dpaa2_fd_get_offset(fd); + u32 fd_length = dpaa2_fd_get_len(fd); + + skb = build_skb(fd_vaddr, DPAA2_ETH_SKB_SIZE); + if (unlikely(!skb)) + return NULL; + + skb_reserve(skb, fd_offset); + skb_put(skb, fd_length); + + ch->buf_count--; + + return skb; +} + +/* Build a non linear (fragmented) skb based on a S/G table */ +static struct sk_buff *build_frag_skb(struct dpaa2_eth_priv *priv, + struct dpaa2_eth_channel *ch, + struct dpaa2_sg_entry *sgt) +{ + struct sk_buff *skb = NULL; + struct device *dev = priv->net_dev->dev.parent; + void *sg_vaddr; + dma_addr_t sg_addr; + u16 sg_offset; + u32 sg_length; + struct page *page, *head_page; + int page_offset; + int i; + + for (i = 0; i < DPAA2_ETH_MAX_SG_ENTRIES; i++) { + struct dpaa2_sg_entry *sge = &sgt[i]; + + /* NOTE: We only support SG entries in dpaa2_sg_single format, + * but this is the only format we may receive from HW anyway + */ + + /* Get the address and length from the S/G entry */ + sg_addr = dpaa2_sg_get_addr(sge); + dma_unmap_single(dev, sg_addr, DPAA2_ETH_RX_BUF_SIZE, + DMA_FROM_DEVICE); + + sg_vaddr = phys_to_virt(sg_addr); + sg_length = dpaa2_sg_get_len(sge); + + if (i == 0) { + /* We build the skb around the first data buffer */ + skb = build_skb(sg_vaddr, DPAA2_ETH_SKB_SIZE); + if (unlikely(!skb)) + return NULL; + + sg_offset = dpaa2_sg_get_offset(sge); + skb_reserve(skb, sg_offset); + skb_put(skb, sg_length); + } else { + /* Rest of the data buffers are stored as skb frags */ + page = virt_to_page(sg_vaddr); + head_page = virt_to_head_page(sg_vaddr); + + /* Offset in page (which may be compound). + * Data in subsequent SG entries is stored from the + * beginning of the buffer, so we don't need to add the + * sg_offset. + */ + page_offset = ((unsigned long)sg_vaddr & + (PAGE_SIZE - 1)) + + (page_address(page) - page_address(head_page)); + + skb_add_rx_frag(skb, i - 1, head_page, page_offset, + sg_length, DPAA2_ETH_RX_BUF_SIZE); + } + + if (dpaa2_sg_is_final(sge)) + break; + } + + /* Count all data buffers + SG table buffer */ + ch->buf_count -= i + 2; + + return skb; +} + +/* Main Rx frame processing routine */ +static void dpaa2_eth_rx(struct dpaa2_eth_priv *priv, + struct dpaa2_eth_channel *ch, + const struct dpaa2_fd *fd, + struct napi_struct *napi, + u16 queue_id) +{ + dma_addr_t addr = dpaa2_fd_get_addr(fd); + u8 fd_format = dpaa2_fd_get_format(fd); + void *vaddr; + struct sk_buff *skb; + struct rtnl_link_stats64 *percpu_stats; + struct dpaa2_eth_drv_stats *percpu_extras; + struct device *dev = priv->net_dev->dev.parent; + struct dpaa2_fas *fas; + void *buf_data; + u32 status = 0; + + /* Tracing point */ + trace_dpaa2_rx_fd(priv->net_dev, fd); + + dma_unmap_single(dev, addr, DPAA2_ETH_RX_BUF_SIZE, DMA_FROM_DEVICE); + vaddr = phys_to_virt(addr); + + /* HWA - FAS, timestamp */ + fas = dpaa2_eth_get_fas(vaddr); + prefetch(fas); + /* data / SG table */ + buf_data = vaddr + dpaa2_fd_get_offset(fd); + prefetch(buf_data); + + percpu_stats = this_cpu_ptr(priv->percpu_stats); + percpu_extras = this_cpu_ptr(priv->percpu_extras); + + switch (fd_format) { + case dpaa2_fd_single: + skb = build_linear_skb(priv, ch, fd, vaddr); + break; + case dpaa2_fd_sg: + skb = build_frag_skb(priv, ch, buf_data); + + /* prefetch newly built skb data */ + prefetch(skb->data); + + put_page(virt_to_head_page(vaddr)); + percpu_extras->rx_sg_frames++; + percpu_extras->rx_sg_bytes += dpaa2_fd_get_len(fd); + break; + default: + /* We don't support any other format */ + goto err_frame_format; + } + + if (unlikely(!skb)) + goto err_build_skb; + + /* Get the timestamp value */ + if (priv->ts_rx_en) { + struct skb_shared_hwtstamps *shhwtstamps = skb_hwtstamps(skb); + u64 *ns = (u64 *)dpaa2_eth_get_ts(vaddr); + + *ns = DPAA2_PTP_NOMINAL_FREQ_PERIOD_NS * le64_to_cpup(ns); + memset(shhwtstamps, 0, sizeof(*shhwtstamps)); + shhwtstamps->hwtstamp = ns_to_ktime(*ns); + } + + /* Check if we need to validate the L4 csum */ + if (likely(dpaa2_fd_get_frc(fd) & DPAA2_FD_FRC_FASV)) { + status = le32_to_cpu(fas->status); + validate_rx_csum(priv, status, skb); + } + + skb->protocol = eth_type_trans(skb, priv->net_dev); + + /* Record Rx queue - this will be used when picking a Tx queue to + * forward the frames. We're keeping flow affinity through the + * network stack. + */ + skb_record_rx_queue(skb, queue_id); + + percpu_stats->rx_packets++; + percpu_stats->rx_bytes += dpaa2_fd_get_len(fd); + + napi_gro_receive(napi, skb); + + return; + +err_build_skb: + free_rx_fd(priv, fd, vaddr); +err_frame_format: + percpu_stats->rx_dropped++; +} + +#ifdef CONFIG_FSL_DPAA2_ETH_USE_ERR_QUEUE +/* Processing of Rx frames received on the error FQ + * We check and print the error bits and then free the frame + */ +static void dpaa2_eth_rx_err(struct dpaa2_eth_priv *priv, + struct dpaa2_eth_channel *ch, + const struct dpaa2_fd *fd, + struct napi_struct *napi __always_unused, + u16 queue_id __always_unused) +{ + struct device *dev = priv->net_dev->dev.parent; + dma_addr_t addr = dpaa2_fd_get_addr(fd); + void *vaddr; + struct rtnl_link_stats64 *percpu_stats; + struct dpaa2_fas *fas; + u32 status = 0; + bool check_fas_errors = false; + + dma_unmap_single(dev, addr, DPAA2_ETH_RX_BUF_SIZE, DMA_FROM_DEVICE); + vaddr = phys_to_virt(addr); + + /* check frame errors in the FD field */ + if (fd->simple.ctrl & DPAA2_FD_RX_ERR_MASK) { + check_fas_errors = !!(fd->simple.ctrl & DPAA2_FD_CTRL_FAERR) && + !!(dpaa2_fd_get_frc(fd) & DPAA2_FD_FRC_FASV); + if (net_ratelimit()) + netdev_dbg(priv->net_dev, "Rx frame FD err: %x08\n", + fd->simple.ctrl & DPAA2_FD_RX_ERR_MASK); + } + + /* check frame errors in the FAS field */ + if (check_fas_errors) { + fas = dpaa2_eth_get_fas(vaddr); + status = le32_to_cpu(fas->status); + if (net_ratelimit()) + netdev_dbg(priv->net_dev, "Rx frame FAS err: 0x%08x\n", + status & DPAA2_FAS_RX_ERR_MASK); + } + free_rx_fd(priv, fd, vaddr); + + percpu_stats = this_cpu_ptr(priv->percpu_stats); + percpu_stats->rx_errors++; +} +#endif + +/* Consume all frames pull-dequeued into the store. This is the simplest way to + * make sure we don't accidentally issue another volatile dequeue which would + * overwrite (leak) frames already in the store. + * + * The number of frames is returned using the last 2 output arguments, + * separately for Rx and Tx confirmations. + * + * Observance of NAPI budget is not our concern, leaving that to the caller. + */ +static bool consume_frames(struct dpaa2_eth_channel *ch, int *rx_cleaned, + int *tx_conf_cleaned) +{ + struct dpaa2_eth_priv *priv = ch->priv; + struct dpaa2_eth_fq *fq = NULL; + struct dpaa2_dq *dq; + const struct dpaa2_fd *fd; + int cleaned = 0; + int is_last; + + do { + dq = dpaa2_io_store_next(ch->store, &is_last); + if (unlikely(!dq)) { + /* If we're here, we *must* have placed a + * volatile dequeue comnmand, so keep reading through + * the store until we get some sort of valid response + * token (either a valid frame or an "empty dequeue") + */ + continue; + } + + fd = dpaa2_dq_fd(dq); + + /* prefetch the frame descriptor */ + prefetch(fd); + + fq = (struct dpaa2_eth_fq *)dpaa2_dq_fqd_ctx(dq); + fq->consume(priv, ch, fd, &ch->napi, fq->flowid); + cleaned++; + } while (!is_last); + + if (!cleaned) + return false; + + /* All frames brought in store by a volatile dequeue + * come from the same queue + */ + if (fq->type == DPAA2_TX_CONF_FQ) + *tx_conf_cleaned += cleaned; + else + *rx_cleaned += cleaned; + + fq->stats.frames += cleaned; + ch->stats.frames += cleaned; + + return true; +} + +/* Configure the egress frame annotation for timestamp update */ +static void enable_tx_tstamp(struct dpaa2_fd *fd, void *buf_start) +{ + struct dpaa2_faead *faead; + u32 ctrl; + u32 frc; + + /* Mark the egress frame annotation area as valid */ + frc = dpaa2_fd_get_frc(fd); + dpaa2_fd_set_frc(fd, frc | DPAA2_FD_FRC_FAEADV); + + /* enable UPD (update prepanded data) bit in FAEAD field of + * hardware frame annotation area + */ + ctrl = DPAA2_FAEAD_A2V | DPAA2_FAEAD_UPDV | DPAA2_FAEAD_UPD; + faead = dpaa2_eth_get_faead(buf_start); + faead->ctrl = cpu_to_le32(ctrl); +} + +/* Create a frame descriptor based on a fragmented skb */ +static int build_sg_fd(struct dpaa2_eth_priv *priv, + struct sk_buff *skb, + struct dpaa2_fd *fd) +{ + struct device *dev = priv->net_dev->dev.parent; + void *sgt_buf = NULL; + dma_addr_t addr; + int nr_frags = skb_shinfo(skb)->nr_frags; + struct dpaa2_sg_entry *sgt; + int i, err; + int sgt_buf_size; + struct scatterlist *scl, *crt_scl; + int num_sg; + int num_dma_bufs; + struct dpaa2_fas *fas; + struct dpaa2_eth_swa *swa; + + /* Create and map scatterlist. + * We don't advertise NETIF_F_FRAGLIST, so skb_to_sgvec() will not have + * to go beyond nr_frags+1. + * Note: We don't support chained scatterlists + */ + if (unlikely(PAGE_SIZE / sizeof(struct scatterlist) < nr_frags + 1)) + return -EINVAL; + + scl = kcalloc(nr_frags + 1, sizeof(struct scatterlist), GFP_ATOMIC); + if (unlikely(!scl)) + return -ENOMEM; + + sg_init_table(scl, nr_frags + 1); + num_sg = skb_to_sgvec(skb, scl, 0, skb->len); + num_dma_bufs = dma_map_sg(dev, scl, num_sg, DMA_TO_DEVICE); + if (unlikely(!num_dma_bufs)) { + err = -ENOMEM; + goto dma_map_sg_failed; + } + + /* Prepare the HW SGT structure */ + sgt_buf_size = priv->tx_data_offset + + sizeof(struct dpaa2_sg_entry) * (1 + num_dma_bufs); + sgt_buf = kzalloc(sgt_buf_size + DPAA2_ETH_TX_BUF_ALIGN, GFP_ATOMIC); + if (unlikely(!sgt_buf)) { + err = -ENOMEM; + goto sgt_buf_alloc_failed; + } + sgt_buf = PTR_ALIGN(sgt_buf, DPAA2_ETH_TX_BUF_ALIGN); + + /* PTA from egress side is passed as is to the confirmation side so + * we need to clear some fields here in order to find consistent values + * on TX confirmation. We are clearing FAS (Frame Annotation Status) + * field from the hardware annotation area + */ + fas = dpaa2_eth_get_fas(sgt_buf); + memset(fas, 0, DPAA2_FAS_SIZE); + + sgt = (struct dpaa2_sg_entry *)(sgt_buf + priv->tx_data_offset); + + /* Fill in the HW SGT structure. + * + * sgt_buf is zeroed out, so the following fields are implicit + * in all sgt entries: + * - offset is 0 + * - format is 'dpaa2_sg_single' + */ + for_each_sg(scl, crt_scl, num_dma_bufs, i) { + dpaa2_sg_set_addr(&sgt[i], sg_dma_address(crt_scl)); + dpaa2_sg_set_len(&sgt[i], sg_dma_len(crt_scl)); + } + dpaa2_sg_set_final(&sgt[i - 1], true); + + /* Store the skb backpointer in the SGT buffer. + * Fit the scatterlist and the number of buffers alongside the + * skb backpointer in the software annotation area. We'll need + * all of them on Tx Conf. + */ + swa = (struct dpaa2_eth_swa *)sgt_buf; + swa->skb = skb; + swa->scl = scl; + swa->num_sg = num_sg; + swa->num_dma_bufs = num_dma_bufs; + + /* Separately map the SGT buffer */ + addr = dma_map_single(dev, sgt_buf, sgt_buf_size, DMA_TO_DEVICE); + if (unlikely(dma_mapping_error(dev, addr))) { + err = -ENOMEM; + goto dma_map_single_failed; + } + dpaa2_fd_set_offset(fd, priv->tx_data_offset); + dpaa2_fd_set_format(fd, dpaa2_fd_sg); + dpaa2_fd_set_addr(fd, addr); + dpaa2_fd_set_len(fd, skb->len); + + fd->simple.ctrl = DPAA2_FD_CTRL_ASAL | DPAA2_FD_CTRL_PTA | + DPAA2_FD_CTRL_PTV1; + + if (priv->ts_tx_en && skb_shinfo(skb)->tx_flags & SKBTX_HW_TSTAMP) + enable_tx_tstamp(fd, sgt_buf); + + return 0; + +dma_map_single_failed: + kfree(sgt_buf); +sgt_buf_alloc_failed: + dma_unmap_sg(dev, scl, num_sg, DMA_TO_DEVICE); +dma_map_sg_failed: + kfree(scl); + return err; +} + +/* Create a frame descriptor based on a linear skb */ +static int build_single_fd(struct dpaa2_eth_priv *priv, + struct sk_buff *skb, + struct dpaa2_fd *fd) +{ + struct device *dev = priv->net_dev->dev.parent; + u8 *buffer_start; + struct sk_buff **skbh; + dma_addr_t addr; + struct dpaa2_fas *fas; + + buffer_start = PTR_ALIGN(skb->data - priv->tx_data_offset - + DPAA2_ETH_TX_BUF_ALIGN, + DPAA2_ETH_TX_BUF_ALIGN); + + /* PTA from egress side is passed as is to the confirmation side so + * we need to clear some fields here in order to find consistent values + * on TX confirmation. We are clearing FAS (Frame Annotation Status) + * field from the hardware annotation area + */ + fas = dpaa2_eth_get_fas(buffer_start); + memset(fas, 0, DPAA2_FAS_SIZE); + + /* Store a backpointer to the skb at the beginning of the buffer + * (in the private data area) such that we can release it + * on Tx confirm + */ + skbh = (struct sk_buff **)buffer_start; + *skbh = skb; + + addr = dma_map_single(dev, buffer_start, + skb_tail_pointer(skb) - buffer_start, + DMA_TO_DEVICE); + if (unlikely(dma_mapping_error(dev, addr))) + return -ENOMEM; + + dpaa2_fd_set_addr(fd, addr); + dpaa2_fd_set_offset(fd, (u16)(skb->data - buffer_start)); + dpaa2_fd_set_len(fd, skb->len); + dpaa2_fd_set_format(fd, dpaa2_fd_single); + + fd->simple.ctrl = DPAA2_FD_CTRL_ASAL | DPAA2_FD_CTRL_PTA | + DPAA2_FD_CTRL_PTV1; + + if (priv->ts_tx_en && skb_shinfo(skb)->tx_flags & SKBTX_HW_TSTAMP) + enable_tx_tstamp(fd, buffer_start); + + return 0; +} + +/* FD freeing routine on the Tx path + * + * DMA-unmap and free FD and possibly SGT buffer allocated on Tx. The skb + * back-pointed to is also freed. + * This can be called either from dpaa2_eth_tx_conf() or on the error path of + * dpaa2_eth_tx(). + * Optionally, return the frame annotation status word (FAS), which needs + * to be checked if we're on the confirmation path. + */ +static void free_tx_fd(const struct dpaa2_eth_priv *priv, + const struct dpaa2_fd *fd, + u32 *status) +{ + struct device *dev = priv->net_dev->dev.parent; + dma_addr_t fd_addr; + struct sk_buff **skbh, *skb; + unsigned char *buffer_start; + int unmap_size; + struct scatterlist *scl; + int num_sg, num_dma_bufs; + struct dpaa2_eth_swa *swa; + u8 fd_format = dpaa2_fd_get_format(fd); + struct dpaa2_fas *fas; + + fd_addr = dpaa2_fd_get_addr(fd); + skbh = phys_to_virt(fd_addr); + + /* HWA - FAS, timestamp (for Tx confirmation frames) */ + fas = dpaa2_eth_get_fas(skbh); + prefetch(fas); + + switch (fd_format) { + case dpaa2_fd_single: + skb = *skbh; + buffer_start = (unsigned char *)skbh; + /* Accessing the skb buffer is safe before dma unmap, because + * we didn't map the actual skb shell. + */ + dma_unmap_single(dev, fd_addr, + skb_tail_pointer(skb) - buffer_start, + DMA_TO_DEVICE); + break; + case dpaa2_fd_sg: + swa = (struct dpaa2_eth_swa *)skbh; + skb = swa->skb; + scl = swa->scl; + num_sg = swa->num_sg; + num_dma_bufs = swa->num_dma_bufs; + + /* Unmap the scatterlist */ + dma_unmap_sg(dev, scl, num_sg, DMA_TO_DEVICE); + kfree(scl); + + /* Unmap the SGT buffer */ + unmap_size = priv->tx_data_offset + + sizeof(struct dpaa2_sg_entry) * (1 + num_dma_bufs); + dma_unmap_single(dev, fd_addr, unmap_size, DMA_TO_DEVICE); + break; + default: + /* Unsupported format, mark it as errored and give up */ + if (status) + *status = ~0; + return; + } + + /* Get the timestamp value */ + if (priv->ts_tx_en && skb_shinfo(skb)->tx_flags & SKBTX_HW_TSTAMP) { + struct skb_shared_hwtstamps shhwtstamps; + u64 *ns; + + memset(&shhwtstamps, 0, sizeof(shhwtstamps)); + + ns = (u64 *)dpaa2_eth_get_ts(skbh); + *ns = DPAA2_PTP_NOMINAL_FREQ_PERIOD_NS * le64_to_cpup(ns); + shhwtstamps.hwtstamp = ns_to_ktime(*ns); + skb_tstamp_tx(skb, &shhwtstamps); + } + + /* Read the status from the Frame Annotation after we unmap the first + * buffer but before we free it. The caller function is responsible + * for checking the status value. + */ + if (status) + *status = le32_to_cpu(fas->status); + + /* Free SGT buffer kmalloc'ed on tx */ + if (fd_format != dpaa2_fd_single) + kfree(skbh); + + /* Move on with skb release */ + dev_kfree_skb(skb); +} + +static int dpaa2_eth_tx(struct sk_buff *skb, struct net_device *net_dev) +{ + struct dpaa2_eth_priv *priv = netdev_priv(net_dev); + struct dpaa2_fd fd; + struct rtnl_link_stats64 *percpu_stats; + struct dpaa2_eth_drv_stats *percpu_extras; + struct dpaa2_eth_fq *fq; + u16 queue_mapping = skb_get_queue_mapping(skb); + int err, i; + + /* If we're congested, stop this tx queue; transmission of the + * current skb happens regardless of congestion state + */ + fq = &priv->fq[queue_mapping]; + if (unlikely(dpaa2_cscn_state_congested(priv->cscn_mem))) { + netif_stop_subqueue(net_dev, queue_mapping); + fq->stats.congestion_entry++; + } + + percpu_stats = this_cpu_ptr(priv->percpu_stats); + percpu_extras = this_cpu_ptr(priv->percpu_extras); + + if (unlikely(skb_headroom(skb) < DPAA2_ETH_NEEDED_HEADROOM(priv))) { + struct sk_buff *ns; + + ns = skb_realloc_headroom(skb, DPAA2_ETH_NEEDED_HEADROOM(priv)); + if (unlikely(!ns)) { + percpu_stats->tx_dropped++; + goto err_alloc_headroom; + } + dev_kfree_skb(skb); + skb = ns; + } + + /* We'll be holding a back-reference to the skb until Tx Confirmation; + * we don't want that overwritten by a concurrent Tx with a cloned skb. + */ + skb = skb_unshare(skb, GFP_ATOMIC); + if (unlikely(!skb)) { + /* skb_unshare() has already freed the skb */ + percpu_stats->tx_dropped++; + return NETDEV_TX_OK; + } + + /* Setup the FD fields */ + memset(&fd, 0, sizeof(fd)); + + if (skb_is_nonlinear(skb)) { + err = build_sg_fd(priv, skb, &fd); + percpu_extras->tx_sg_frames++; + percpu_extras->tx_sg_bytes += skb->len; + } else { + err = build_single_fd(priv, skb, &fd); + } + + if (unlikely(err)) { + percpu_stats->tx_dropped++; + goto err_build_fd; + } + + /* Tracing point */ + trace_dpaa2_tx_fd(net_dev, &fd); + + for (i = 0; i < DPAA2_ETH_ENQUEUE_RETRIES; i++) { + err = dpaa2_io_service_enqueue_qd(NULL, priv->tx_qdid, 0, + fq->tx_qdbin, &fd); + /* TODO: This doesn't work. Check on simulator. + * err = dpaa2_io_service_enqueue_fq(NULL, + * priv->fq[0].fqid_tx, &fd); + */ + if (err != -EBUSY) + break; + } + percpu_extras->tx_portal_busy += i; + if (unlikely(err < 0)) { + percpu_stats->tx_errors++; + /* Clean up everything, including freeing the skb */ + free_tx_fd(priv, &fd, NULL); + } else { + percpu_stats->tx_packets++; + percpu_stats->tx_bytes += skb->len; + } + + return NETDEV_TX_OK; + +err_build_fd: +err_alloc_headroom: + dev_kfree_skb(skb); + + return NETDEV_TX_OK; +} + +/* Tx confirmation frame processing routine */ +static void dpaa2_eth_tx_conf(struct dpaa2_eth_priv *priv, + struct dpaa2_eth_channel *ch, + const struct dpaa2_fd *fd, + struct napi_struct *napi __always_unused, + u16 queue_id) +{ + struct rtnl_link_stats64 *percpu_stats; + struct dpaa2_eth_drv_stats *percpu_extras; + u32 status = 0; + bool errors = !!(fd->simple.ctrl & DPAA2_FD_TX_ERR_MASK); + bool check_fas_errors = false; + + /* Tracing point */ + trace_dpaa2_tx_conf_fd(priv->net_dev, fd); + + percpu_extras = this_cpu_ptr(priv->percpu_extras); + percpu_extras->tx_conf_frames++; + percpu_extras->tx_conf_bytes += dpaa2_fd_get_len(fd); + + /* Check congestion state and wake all queues if necessary */ + if (unlikely(__netif_subqueue_stopped(priv->net_dev, queue_id) && + !dpaa2_cscn_state_congested(priv->cscn_mem))) + netif_tx_wake_all_queues(priv->net_dev); + + /* check frame errors in the FD field */ + if (unlikely(errors)) { + check_fas_errors = !!(fd->simple.ctrl & DPAA2_FD_CTRL_FAERR) && + !!(dpaa2_fd_get_frc(fd) & DPAA2_FD_FRC_FASV); + if (net_ratelimit()) + netdev_dbg(priv->net_dev, "Tx frame FD err: %x08\n", + fd->simple.ctrl & DPAA2_FD_TX_ERR_MASK); + } + + free_tx_fd(priv, fd, check_fas_errors ? &status : NULL); + + /* if there are no errors, we're done */ + if (likely(!errors)) + return; + + percpu_stats = this_cpu_ptr(priv->percpu_stats); + /* Tx-conf logically pertains to the egress path. */ + percpu_stats->tx_errors++; + + if (net_ratelimit()) + netdev_dbg(priv->net_dev, "Tx frame FAS err: %x08\n", + status & DPAA2_FAS_TX_ERR_MASK); +} + +static int set_rx_csum(struct dpaa2_eth_priv *priv, bool enable) +{ + int err; + + err = dpni_set_offload(priv->mc_io, 0, priv->mc_token, + DPNI_OFF_RX_L3_CSUM, enable); + if (err) { + netdev_err(priv->net_dev, + "dpni_set_offload() DPNI_OFF_RX_L3_CSUM failed\n"); + return err; + } + + err = dpni_set_offload(priv->mc_io, 0, priv->mc_token, + DPNI_OFF_RX_L4_CSUM, enable); + if (err) { + netdev_err(priv->net_dev, + "dpni_set_offload() DPNI_OFF_RX_L4_CSUM failed\n"); + return err; + } + + return 0; +} + +static int set_tx_csum(struct dpaa2_eth_priv *priv, bool enable) +{ + int err; + + err = dpni_set_offload(priv->mc_io, 0, priv->mc_token, + DPNI_OFF_TX_L3_CSUM, enable); + if (err) { + netdev_err(priv->net_dev, + "dpni_set_offload() DPNI_OFF_RX_L3_CSUM failed\n"); + return err; + } + + err = dpni_set_offload(priv->mc_io, 0, priv->mc_token, + DPNI_OFF_TX_L4_CSUM, enable); + if (err) { + netdev_err(priv->net_dev, + "dpni_set_offload() DPNI_OFF_RX_L4_CSUM failed\n"); + return err; + } + + return 0; +} + +/* Perform a single release command to add buffers + * to the specified buffer pool + */ +static int add_bufs(struct dpaa2_eth_priv *priv, u16 bpid) +{ + struct device *dev = priv->net_dev->dev.parent; + u64 buf_array[DPAA2_ETH_BUFS_PER_CMD]; + void *buf; + dma_addr_t addr; + int i; + + for (i = 0; i < DPAA2_ETH_BUFS_PER_CMD; i++) { + /* Allocate buffer visible to WRIOP + skb shared info + + * alignment padding. + */ + buf = napi_alloc_frag(DPAA2_ETH_BUF_RAW_SIZE(priv)); + if (unlikely(!buf)) + goto err_alloc; + + buf = PTR_ALIGN(buf, priv->rx_buf_align); + + addr = dma_map_single(dev, buf, DPAA2_ETH_RX_BUF_SIZE, + DMA_FROM_DEVICE); + if (unlikely(dma_mapping_error(dev, addr))) + goto err_map; + + buf_array[i] = addr; + + /* tracing point */ + trace_dpaa2_eth_buf_seed(priv->net_dev, + buf, DPAA2_ETH_BUF_RAW_SIZE(priv), + addr, DPAA2_ETH_RX_BUF_SIZE, + bpid); + } + +release_bufs: + /* In case the portal is busy, retry until successful. + * The buffer release function would only fail if the QBMan portal + * was busy, which implies portal contention (i.e. more CPUs than + * portals, i.e. GPPs w/o affine DPIOs). For all practical purposes, + * there is little we can realistically do, short of giving up - + * in which case we'd risk depleting the buffer pool and never again + * receiving the Rx interrupt which would kick-start the refill logic. + * So just keep retrying, at the risk of being moved to ksoftirqd. + */ + while (dpaa2_io_service_release(NULL, bpid, buf_array, i)) + cpu_relax(); + return i; + +err_map: + put_page(virt_to_head_page(buf)); +err_alloc: + if (i) + goto release_bufs; + + return 0; +} + +static int seed_pool(struct dpaa2_eth_priv *priv, u16 bpid) +{ + int i, j; + int new_count; + + /* This is the lazy seeding of Rx buffer pools. + * dpaa2_add_bufs() is also used on the Rx hotpath and calls + * napi_alloc_frag(). The trouble with that is that it in turn ends up + * calling this_cpu_ptr(), which mandates execution in atomic context. + * Rather than splitting up the code, do a one-off preempt disable. + */ + preempt_disable(); + for (j = 0; j < priv->num_channels; j++) { + priv->channel[j]->buf_count = 0; + for (i = 0; i < priv->num_bufs; + i += DPAA2_ETH_BUFS_PER_CMD) { + new_count = add_bufs(priv, bpid); + priv->channel[j]->buf_count += new_count; + + if (new_count < DPAA2_ETH_BUFS_PER_CMD) { + preempt_enable(); + return -ENOMEM; + } + } + } + preempt_enable(); + + return 0; +} + +/** + * Drain the specified number of buffers from the DPNI's private buffer pool. + * @count must not exceeed DPAA2_ETH_BUFS_PER_CMD + */ +static void drain_bufs(struct dpaa2_eth_priv *priv, int count) +{ + struct device *dev = priv->net_dev->dev.parent; + u64 buf_array[DPAA2_ETH_BUFS_PER_CMD]; + void *vaddr; + int ret, i; + + do { + ret = dpaa2_io_service_acquire(NULL, priv->bpid, + buf_array, count); + if (ret < 0) { + netdev_err(priv->net_dev, "dpaa2_io_service_acquire() failed\n"); + return; + } + for (i = 0; i < ret; i++) { + /* Same logic as on regular Rx path */ + dma_unmap_single(dev, buf_array[i], + DPAA2_ETH_RX_BUF_SIZE, + DMA_FROM_DEVICE); + vaddr = phys_to_virt(buf_array[i]); + put_page(virt_to_head_page(vaddr)); + } + } while (ret); +} + +static void drain_pool(struct dpaa2_eth_priv *priv) +{ + preempt_disable(); + drain_bufs(priv, DPAA2_ETH_BUFS_PER_CMD); + drain_bufs(priv, 1); + preempt_enable(); +} + +/* Function is called from softirq context only, so we don't need to guard + * the access to percpu count + */ +static int refill_pool(struct dpaa2_eth_priv *priv, + struct dpaa2_eth_channel *ch, + u16 bpid) +{ + int new_count; + + if (likely(ch->buf_count >= priv->refill_thresh)) + return 0; + + do { + new_count = add_bufs(priv, bpid); + if (unlikely(!new_count)) { + /* Out of memory; abort for now, we'll try later on */ + break; + } + ch->buf_count += new_count; + } while (ch->buf_count < priv->num_bufs); + + if (unlikely(ch->buf_count < priv->num_bufs)) + return -ENOMEM; + + return 0; +} + +static int pull_channel(struct dpaa2_eth_channel *ch) +{ + int err; + int dequeues = -1; + + /* Retry while portal is busy */ + do { + err = dpaa2_io_service_pull_channel(NULL, ch->ch_id, ch->store); + dequeues++; + cpu_relax(); + } while (err == -EBUSY); + + ch->stats.dequeue_portal_busy += dequeues; + if (unlikely(err)) + ch->stats.pull_err++; + + return err; +} + +/* NAPI poll routine + * + * Frames are dequeued from the QMan channel associated with this NAPI context. + * Rx and (if configured) Rx error frames count towards the NAPI budget. Tx + * confirmation frames are limited by a threshold per NAPI poll cycle. + */ +static int dpaa2_eth_poll(struct napi_struct *napi, int budget) +{ + struct dpaa2_eth_channel *ch; + int rx_cleaned = 0, tx_conf_cleaned = 0; + bool store_cleaned; + struct dpaa2_eth_priv *priv; + int err; + + ch = container_of(napi, struct dpaa2_eth_channel, napi); + priv = ch->priv; + + do { + err = pull_channel(ch); + if (unlikely(err)) + break; + + /* Refill pool if appropriate */ + refill_pool(priv, ch, priv->bpid); + + store_cleaned = consume_frames(ch, &rx_cleaned, + &tx_conf_cleaned); + + /* If we've either consumed the budget with Rx frames, + * or reached the Tx conf threshold, we're done. + */ + if (rx_cleaned >= budget || + tx_conf_cleaned >= TX_CONF_PER_NAPI_POLL) + return budget; + } while (store_cleaned); + + /* We didn't consume the entire budget, finish napi and + * re-enable data availability notifications. + */ + napi_complete(napi); + do { + err = dpaa2_io_service_rearm(NULL, &ch->nctx); + cpu_relax(); + } while (err == -EBUSY); + + return max(rx_cleaned, 1); +} + +static void enable_ch_napi(struct dpaa2_eth_priv *priv) +{ + struct dpaa2_eth_channel *ch; + int i; + + for (i = 0; i < priv->num_channels; i++) { + ch = priv->channel[i]; + napi_enable(&ch->napi); + } +} + +static void disable_ch_napi(struct dpaa2_eth_priv *priv) +{ + struct dpaa2_eth_channel *ch; + int i; + + for (i = 0; i < priv->num_channels; i++) { + ch = priv->channel[i]; + napi_disable(&ch->napi); + } +} + +static int link_state_update(struct dpaa2_eth_priv *priv) +{ + struct dpni_link_state state; + int err; + + err = dpni_get_link_state(priv->mc_io, 0, priv->mc_token, &state); + if (unlikely(err)) { + netdev_err(priv->net_dev, + "dpni_get_link_state() failed\n"); + return err; + } + + /* Chech link state; speed / duplex changes are not treated yet */ + if (priv->link_state.up == state.up) + return 0; + + priv->link_state = state; + if (state.up) { + netif_carrier_on(priv->net_dev); + netif_tx_start_all_queues(priv->net_dev); + } else { + netif_tx_stop_all_queues(priv->net_dev); + netif_carrier_off(priv->net_dev); + } + + netdev_info(priv->net_dev, "Link Event: state %s", + state.up ? "up" : "down"); + + return 0; +} + +static int dpaa2_eth_open(struct net_device *net_dev) +{ + struct dpaa2_eth_priv *priv = netdev_priv(net_dev); + int err; + + /* We'll only start the txqs when the link is actually ready; make sure + * we don't race against the link up notification, which may come + * immediately after dpni_enable(); + */ + netif_tx_stop_all_queues(net_dev); + + /* Also, explicitly set carrier off, otherwise netif_carrier_ok() will + * return true and cause 'ip link show' to report the LOWER_UP flag, + * even though the link notification wasn't even received. + */ + netif_carrier_off(net_dev); + + err = seed_pool(priv, priv->bpid); + if (err) { + /* Not much to do; the buffer pool, though not filled up, + * may still contain some buffers which would enable us + * to limp on. + */ + netdev_err(net_dev, "Buffer seeding failed for DPBP %d (bpid=%d)\n", + priv->dpbp_dev->obj_desc.id, priv->bpid); + } + + if (priv->tx_pause_frames) + priv->refill_thresh = priv->num_bufs - DPAA2_ETH_BUFS_PER_CMD; + else + priv->refill_thresh = DPAA2_ETH_REFILL_THRESH_TD; + + err = dpni_enable(priv->mc_io, 0, priv->mc_token); + if (err < 0) { + netdev_err(net_dev, "dpni_enable() failed\n"); + goto enable_err; + } + + /* If the DPMAC object has already processed the link up interrupt, + * we have to learn the link state ourselves. + */ + err = link_state_update(priv); + if (err < 0) { + netdev_err(net_dev, "Can't update link state\n"); + goto link_state_err; + } + + return 0; + +link_state_err: +enable_err: + priv->refill_thresh = 0; + drain_pool(priv); + return err; +} + +static int dpaa2_eth_stop(struct net_device *net_dev) +{ + struct dpaa2_eth_priv *priv = netdev_priv(net_dev); + int dpni_enabled; + int retries = 10, i; + + netif_tx_stop_all_queues(net_dev); + netif_carrier_off(net_dev); + + /* Loop while dpni_disable() attempts to drain the egress FQs + * and confirm them back to us. + */ + do { + dpni_disable(priv->mc_io, 0, priv->mc_token); + dpni_is_enabled(priv->mc_io, 0, priv->mc_token, &dpni_enabled); + if (dpni_enabled) + /* Allow the MC some slack */ + msleep(100); + } while (dpni_enabled && --retries); + if (!retries) { + netdev_warn(net_dev, "Retry count exceeded disabling DPNI\n"); + /* Must go on and disable NAPI nonetheless, so we don't crash at + * the next "ifconfig up" + */ + } + + priv->refill_thresh = 0; + + /* Wait for all running napi poll routines to finish, so that no + * new refill operations are started. + */ + for (i = 0; i < priv->num_channels; i++) + napi_synchronize(&priv->channel[i]->napi); + + /* Empty the buffer pool */ + drain_pool(priv); + + return 0; +} + +static int dpaa2_eth_init(struct net_device *net_dev) +{ + u64 supported = 0; + u64 not_supported = 0; + struct dpaa2_eth_priv *priv = netdev_priv(net_dev); + u32 options = priv->dpni_attrs.options; + + /* Capabilities listing */ + supported |= IFF_LIVE_ADDR_CHANGE; + + if (options & DPNI_OPT_NO_MAC_FILTER) + not_supported |= IFF_UNICAST_FLT; + else + supported |= IFF_UNICAST_FLT; + + net_dev->priv_flags |= supported; + net_dev->priv_flags &= ~not_supported; + + /* Features */ + net_dev->features = NETIF_F_RXCSUM | + NETIF_F_IP_CSUM | NETIF_F_IPV6_CSUM | + NETIF_F_SG | NETIF_F_HIGHDMA | + NETIF_F_LLTX; + net_dev->hw_features = net_dev->features; + + return 0; +} + +static int dpaa2_eth_set_addr(struct net_device *net_dev, void *addr) +{ + struct dpaa2_eth_priv *priv = netdev_priv(net_dev); + struct device *dev = net_dev->dev.parent; + int err; + + err = eth_mac_addr(net_dev, addr); + if (err < 0) { + dev_err(dev, "eth_mac_addr() failed (%d)\n", err); + return err; + } + + err = dpni_set_primary_mac_addr(priv->mc_io, 0, priv->mc_token, + net_dev->dev_addr); + if (err) { + dev_err(dev, "dpni_set_primary_mac_addr() failed (%d)\n", err); + return err; + } + + return 0; +} + +/** Fill in counters maintained by the GPP driver. These may be different from + * the hardware counters obtained by ethtool. + */ +static void dpaa2_eth_get_stats(struct net_device *net_dev, + struct rtnl_link_stats64 *stats) +{ + struct dpaa2_eth_priv *priv = netdev_priv(net_dev); + struct rtnl_link_stats64 *percpu_stats; + u64 *cpustats; + u64 *netstats = (u64 *)stats; + int i, j; + int num = sizeof(struct rtnl_link_stats64) / sizeof(u64); + + for_each_possible_cpu(i) { + percpu_stats = per_cpu_ptr(priv->percpu_stats, i); + cpustats = (u64 *)percpu_stats; + for (j = 0; j < num; j++) + netstats[j] += cpustats[j]; + } +} + +static int dpaa2_eth_change_mtu(struct net_device *net_dev, int mtu) +{ + struct dpaa2_eth_priv *priv = netdev_priv(net_dev); + int err; + + /* Set the maximum Rx frame length to match the transmit side; + * account for L2 headers when computing the MFL + */ + err = dpni_set_max_frame_length(priv->mc_io, 0, priv->mc_token, + (u16)DPAA2_ETH_L2_MAX_FRM(mtu)); + if (err) { + netdev_err(net_dev, "dpni_set_max_frame_length() failed\n"); + return err; + } + + net_dev->mtu = mtu; + return 0; +} + +/* Copy mac unicast addresses from @net_dev to @priv. + * Its sole purpose is to make dpaa2_eth_set_rx_mode() more readable. + */ +static void add_uc_hw_addr(const struct net_device *net_dev, + struct dpaa2_eth_priv *priv) +{ + struct netdev_hw_addr *ha; + int err; + + netdev_for_each_uc_addr(ha, net_dev) { + err = dpni_add_mac_addr(priv->mc_io, 0, priv->mc_token, + ha->addr); + if (err) + netdev_warn(priv->net_dev, + "Could not add ucast MAC %pM to the filtering table (err %d)\n", + ha->addr, err); + } +} + +/* Copy mac multicast addresses from @net_dev to @priv + * Its sole purpose is to make dpaa2_eth_set_rx_mode() more readable. + */ +static void add_mc_hw_addr(const struct net_device *net_dev, + struct dpaa2_eth_priv *priv) +{ + struct netdev_hw_addr *ha; + int err; + + netdev_for_each_mc_addr(ha, net_dev) { + err = dpni_add_mac_addr(priv->mc_io, 0, priv->mc_token, + ha->addr); + if (err) + netdev_warn(priv->net_dev, + "Could not add mcast MAC %pM to the filtering table (err %d)\n", + ha->addr, err); + } +} + +static void dpaa2_eth_set_rx_mode(struct net_device *net_dev) +{ + struct dpaa2_eth_priv *priv = netdev_priv(net_dev); + int uc_count = netdev_uc_count(net_dev); + int mc_count = netdev_mc_count(net_dev); + u8 max_mac = priv->dpni_attrs.mac_filter_entries; + u32 options = priv->dpni_attrs.options; + u16 mc_token = priv->mc_token; + struct fsl_mc_io *mc_io = priv->mc_io; + int err; + + /* Basic sanity checks; these probably indicate a misconfiguration */ + if (options & DPNI_OPT_NO_MAC_FILTER && max_mac != 0) + netdev_info(net_dev, + "mac_filter_entries=%d, DPNI_OPT_NO_MAC_FILTER option must be disabled\n", + max_mac); + + /* Force promiscuous if the uc or mc counts exceed our capabilities. */ + if (uc_count > max_mac) { + netdev_info(net_dev, + "Unicast addr count reached %d, max allowed is %d; forcing promisc\n", + uc_count, max_mac); + goto force_promisc; + } + if (mc_count + uc_count > max_mac) { + netdev_info(net_dev, + "Unicast + Multicast addr count reached %d, max allowed is %d; forcing promisc\n", + uc_count + mc_count, max_mac); + goto force_mc_promisc; + } + + /* Adjust promisc settings due to flag combinations */ + if (net_dev->flags & IFF_PROMISC) + goto force_promisc; + if (net_dev->flags & IFF_ALLMULTI) { + /* First, rebuild unicast filtering table. This should be done + * in promisc mode, in order to avoid frame loss while we + * progressively add entries to the table. + * We don't know whether we had been in promisc already, and + * making an MC call to find out is expensive; so set uc promisc + * nonetheless. + */ + err = dpni_set_unicast_promisc(mc_io, 0, mc_token, 1); + if (err) + netdev_warn(net_dev, "Can't set uc promisc\n"); + + /* Actual uc table reconstruction. */ + err = dpni_clear_mac_filters(mc_io, 0, mc_token, 1, 0); + if (err) + netdev_warn(net_dev, "Can't clear uc filters\n"); + add_uc_hw_addr(net_dev, priv); + + /* Finally, clear uc promisc and set mc promisc as requested. */ + err = dpni_set_unicast_promisc(mc_io, 0, mc_token, 0); + if (err) + netdev_warn(net_dev, "Can't clear uc promisc\n"); + goto force_mc_promisc; + } + + /* Neither unicast, nor multicast promisc will be on... eventually. + * For now, rebuild mac filtering tables while forcing both of them on. + */ + err = dpni_set_unicast_promisc(mc_io, 0, mc_token, 1); + if (err) + netdev_warn(net_dev, "Can't set uc promisc (%d)\n", err); + err = dpni_set_multicast_promisc(mc_io, 0, mc_token, 1); + if (err) + netdev_warn(net_dev, "Can't set mc promisc (%d)\n", err); + + /* Actual mac filtering tables reconstruction */ + err = dpni_clear_mac_filters(mc_io, 0, mc_token, 1, 1); + if (err) + netdev_warn(net_dev, "Can't clear mac filters\n"); + add_mc_hw_addr(net_dev, priv); + add_uc_hw_addr(net_dev, priv); + + /* Now we can clear both ucast and mcast promisc, without risking + * to drop legitimate frames anymore. + */ + err = dpni_set_unicast_promisc(mc_io, 0, mc_token, 0); + if (err) + netdev_warn(net_dev, "Can't clear ucast promisc\n"); + err = dpni_set_multicast_promisc(mc_io, 0, mc_token, 0); + if (err) + netdev_warn(net_dev, "Can't clear mcast promisc\n"); + + return; + +force_promisc: + err = dpni_set_unicast_promisc(mc_io, 0, mc_token, 1); + if (err) + netdev_warn(net_dev, "Can't set ucast promisc\n"); +force_mc_promisc: + err = dpni_set_multicast_promisc(mc_io, 0, mc_token, 1); + if (err) + netdev_warn(net_dev, "Can't set mcast promisc\n"); +} + +static int dpaa2_eth_set_features(struct net_device *net_dev, + netdev_features_t features) +{ + struct dpaa2_eth_priv *priv = netdev_priv(net_dev); + netdev_features_t changed = features ^ net_dev->features; + bool enable; + int err; + + if (changed & NETIF_F_RXCSUM) { + enable = !!(features & NETIF_F_RXCSUM); + err = set_rx_csum(priv, enable); + if (err) + return err; + } + + if (changed & (NETIF_F_IP_CSUM | NETIF_F_IPV6_CSUM)) { + enable = !!(features & (NETIF_F_IP_CSUM | NETIF_F_IPV6_CSUM)); + err = set_tx_csum(priv, enable); + if (err) + return err; + } + + return 0; +} + +static int dpaa2_eth_ts_ioctl(struct net_device *dev, struct ifreq *rq, int cmd) +{ + struct dpaa2_eth_priv *priv = netdev_priv(dev); + struct hwtstamp_config config; + + if (copy_from_user(&config, rq->ifr_data, sizeof(config))) + return -EFAULT; + + switch (config.tx_type) { + case HWTSTAMP_TX_OFF: + priv->ts_tx_en = false; + break; + case HWTSTAMP_TX_ON: + priv->ts_tx_en = true; + break; + default: + return -ERANGE; + } + + if (config.rx_filter == HWTSTAMP_FILTER_NONE) { + priv->ts_rx_en = false; + } else { + priv->ts_rx_en = true; + /* TS is set for all frame types, not only those requested */ + config.rx_filter = HWTSTAMP_FILTER_ALL; + } + + return copy_to_user(rq->ifr_data, &config, sizeof(config)) ? + -EFAULT : 0; +} + +static int dpaa2_eth_ioctl(struct net_device *dev, struct ifreq *rq, int cmd) +{ + if (cmd == SIOCSHWTSTAMP) + return dpaa2_eth_ts_ioctl(dev, rq, cmd); + + return -EINVAL; +} + +static const struct net_device_ops dpaa2_eth_ops = { + .ndo_open = dpaa2_eth_open, + .ndo_start_xmit = dpaa2_eth_tx, + .ndo_stop = dpaa2_eth_stop, + .ndo_init = dpaa2_eth_init, + .ndo_set_mac_address = dpaa2_eth_set_addr, + .ndo_get_stats64 = dpaa2_eth_get_stats, + .ndo_change_mtu = dpaa2_eth_change_mtu, + .ndo_set_rx_mode = dpaa2_eth_set_rx_mode, + .ndo_set_features = dpaa2_eth_set_features, + .ndo_do_ioctl = dpaa2_eth_ioctl, +}; + +static void cdan_cb(struct dpaa2_io_notification_ctx *ctx) +{ + struct dpaa2_eth_channel *ch; + + ch = container_of(ctx, struct dpaa2_eth_channel, nctx); + + /* Update NAPI statistics */ + ch->stats.cdan++; + + napi_schedule_irqoff(&ch->napi); +} + +/* Allocate and configure a DPCON object */ +static struct fsl_mc_device *setup_dpcon(struct dpaa2_eth_priv *priv) +{ + struct fsl_mc_device *dpcon; + struct device *dev = priv->net_dev->dev.parent; + struct dpcon_attr attrs; + int err; + + err = fsl_mc_object_allocate(to_fsl_mc_device(dev), + FSL_MC_POOL_DPCON, &dpcon); + if (err) { + dev_info(dev, "Not enough DPCONs, will go on as-is\n"); + return NULL; + } + + err = dpcon_open(priv->mc_io, 0, dpcon->obj_desc.id, &dpcon->mc_handle); + if (err) { + dev_err(dev, "dpcon_open() failed\n"); + goto err_open; + } + + err = dpcon_reset(priv->mc_io, 0, dpcon->mc_handle); + if (err) { + dev_err(dev, "dpcon_reset() failed\n"); + goto err_reset; + } + + err = dpcon_get_attributes(priv->mc_io, 0, dpcon->mc_handle, &attrs); + if (err) { + dev_err(dev, "dpcon_get_attributes() failed\n"); + goto err_get_attr; + } + + err = dpcon_enable(priv->mc_io, 0, dpcon->mc_handle); + if (err) { + dev_err(dev, "dpcon_enable() failed\n"); + goto err_enable; + } + + return dpcon; + +err_enable: +err_get_attr: +err_reset: + dpcon_close(priv->mc_io, 0, dpcon->mc_handle); +err_open: + fsl_mc_object_free(dpcon); + + return NULL; +} + +static void free_dpcon(struct dpaa2_eth_priv *priv, + struct fsl_mc_device *dpcon) +{ + dpcon_disable(priv->mc_io, 0, dpcon->mc_handle); + dpcon_close(priv->mc_io, 0, dpcon->mc_handle); + fsl_mc_object_free(dpcon); +} + +static struct dpaa2_eth_channel * +alloc_channel(struct dpaa2_eth_priv *priv) +{ + struct dpaa2_eth_channel *channel; + struct dpcon_attr attr; + struct device *dev = priv->net_dev->dev.parent; + int err; + + channel = kzalloc(sizeof(*channel), GFP_KERNEL); + if (!channel) + return NULL; + + channel->dpcon = setup_dpcon(priv); + if (!channel->dpcon) + goto err_setup; + + err = dpcon_get_attributes(priv->mc_io, 0, channel->dpcon->mc_handle, + &attr); + if (err) { + dev_err(dev, "dpcon_get_attributes() failed\n"); + goto err_get_attr; + } + + channel->dpcon_id = attr.id; + channel->ch_id = attr.qbman_ch_id; + channel->priv = priv; + + return channel; + +err_get_attr: + free_dpcon(priv, channel->dpcon); +err_setup: + kfree(channel); + return NULL; +} + +static void free_channel(struct dpaa2_eth_priv *priv, + struct dpaa2_eth_channel *channel) +{ + free_dpcon(priv, channel->dpcon); + kfree(channel); +} + +/* DPIO setup: allocate and configure QBMan channels, setup core affinity + * and register data availability notifications + */ +static int setup_dpio(struct dpaa2_eth_priv *priv) +{ + struct dpaa2_io_notification_ctx *nctx; + struct dpaa2_eth_channel *channel; + struct dpcon_notification_cfg dpcon_notif_cfg; + struct device *dev = priv->net_dev->dev.parent; + int i, err; + + /* We want the ability to spread ingress traffic (RX, TX conf) to as + * many cores as possible, so we need one channel for each core + * (unless there's fewer queues than cores, in which case the extra + * channels would be wasted). + * Allocate one channel per core and register it to the core's + * affine DPIO. If not enough channels are available for all cores + * or if some cores don't have an affine DPIO, there will be no + * ingress frame processing on those cores. + */ + cpumask_clear(&priv->dpio_cpumask); + for_each_online_cpu(i) { + /* Try to allocate a channel */ + channel = alloc_channel(priv); + if (!channel) { + dev_info(dev, + "No affine channel for cpu %d and above\n", i); + goto err_alloc_ch; + } + + priv->channel[priv->num_channels] = channel; + + nctx = &channel->nctx; + nctx->is_cdan = 1; + nctx->cb = cdan_cb; + nctx->id = channel->ch_id; + nctx->desired_cpu = i; + + /* Register the new context */ + err = dpaa2_io_service_register(NULL, nctx); + if (err) { + dev_info(dev, "No affine DPIO for cpu %d\n", i); + /* If no affine DPIO for this core, there's probably + * none available for next cores either. + */ + goto err_service_reg; + } + + /* Register DPCON notification with MC */ + dpcon_notif_cfg.dpio_id = nctx->dpio_id; + dpcon_notif_cfg.priority = 0; + dpcon_notif_cfg.user_ctx = nctx->qman64; + err = dpcon_set_notification(priv->mc_io, 0, + channel->dpcon->mc_handle, + &dpcon_notif_cfg); + if (err) { + dev_err(dev, "dpcon_set_notification failed()\n"); + goto err_set_cdan; + } + + /* If we managed to allocate a channel and also found an affine + * DPIO for this core, add it to the final mask + */ + cpumask_set_cpu(i, &priv->dpio_cpumask); + priv->num_channels++; + + /* Stop if we already have enough channels to accommodate all + * RX and TX conf queues + */ + if (priv->num_channels == dpaa2_eth_queue_count(priv)) + break; + } + + /* Tx confirmation queues can only be serviced by cpus + * with an affine DPIO/channel + */ + cpumask_copy(&priv->txconf_cpumask, &priv->dpio_cpumask); + + return 0; + +err_set_cdan: + dpaa2_io_service_deregister(NULL, nctx); +err_service_reg: + free_channel(priv, channel); +err_alloc_ch: + if (cpumask_empty(&priv->dpio_cpumask)) { + dev_err(dev, "No cpu with an affine DPIO/DPCON\n"); + return -ENODEV; + } + cpumask_copy(&priv->txconf_cpumask, &priv->dpio_cpumask); + + dev_info(dev, "Cores %*pbl available for processing ingress traffic\n", + cpumask_pr_args(&priv->dpio_cpumask)); + + return 0; +} + +static void free_dpio(struct dpaa2_eth_priv *priv) +{ + int i; + struct dpaa2_eth_channel *ch; + + /* deregister CDAN notifications and free channels */ + for (i = 0; i < priv->num_channels; i++) { + ch = priv->channel[i]; + dpaa2_io_service_deregister(NULL, &ch->nctx); + free_channel(priv, ch); + } +} + +static struct dpaa2_eth_channel *get_affine_channel(struct dpaa2_eth_priv *priv, + int cpu) +{ + struct device *dev = priv->net_dev->dev.parent; + int i; + + for (i = 0; i < priv->num_channels; i++) + if (priv->channel[i]->nctx.desired_cpu == cpu) + return priv->channel[i]; + + /* We should never get here. Issue a warning and return + * the first channel, because it's still better than nothing + */ + dev_warn(dev, "No affine channel found for cpu %d\n", cpu); + + return priv->channel[0]; +} + +static void set_fq_affinity(struct dpaa2_eth_priv *priv) +{ + struct device *dev = priv->net_dev->dev.parent; + struct cpumask xps_mask = CPU_MASK_NONE; + struct dpaa2_eth_fq *fq; + int rx_cpu, txc_cpu; + int i, err; + + /* For each FQ, pick one channel/CPU to deliver frames to. + * This may well change at runtime, either through irqbalance or + * through direct user intervention. + */ + rx_cpu = cpumask_first(&priv->dpio_cpumask); + txc_cpu = cpumask_first(&priv->txconf_cpumask); + + for (i = 0; i < priv->num_fqs; i++) { + fq = &priv->fq[i]; + switch (fq->type) { + case DPAA2_RX_FQ: + case DPAA2_RX_ERR_FQ: + fq->target_cpu = rx_cpu; + rx_cpu = cpumask_next(rx_cpu, &priv->dpio_cpumask); + if (rx_cpu >= nr_cpu_ids) + rx_cpu = cpumask_first(&priv->dpio_cpumask); + break; + case DPAA2_TX_CONF_FQ: + fq->target_cpu = txc_cpu; + + /* register txc_cpu to XPS */ + cpumask_set_cpu(txc_cpu, &xps_mask); + err = netif_set_xps_queue(priv->net_dev, &xps_mask, + fq->flowid); + if (err) + dev_info_once(dev, + "Tx: error setting XPS queue\n"); + cpumask_clear_cpu(txc_cpu, &xps_mask); + + txc_cpu = cpumask_next(txc_cpu, &priv->txconf_cpumask); + if (txc_cpu >= nr_cpu_ids) + txc_cpu = cpumask_first(&priv->txconf_cpumask); + break; + default: + dev_err(dev, "Unknown FQ type: %d\n", fq->type); + } + fq->channel = get_affine_channel(priv, fq->target_cpu); + } +} + +static void setup_fqs(struct dpaa2_eth_priv *priv) +{ + int i; + + /* We have one TxConf FQ per Tx flow. Tx queues MUST be at the + * beginning of the queue array. + * Number of Rx and Tx queues are the same. + * We only support one traffic class for now. + */ + for (i = 0; i < dpaa2_eth_queue_count(priv); i++) { + priv->fq[priv->num_fqs].type = DPAA2_TX_CONF_FQ; + priv->fq[priv->num_fqs].consume = dpaa2_eth_tx_conf; + priv->fq[priv->num_fqs++].flowid = (u16)i; + } + + for (i = 0; i < dpaa2_eth_queue_count(priv); i++) { + priv->fq[priv->num_fqs].type = DPAA2_RX_FQ; + priv->fq[priv->num_fqs].consume = dpaa2_eth_rx; + priv->fq[priv->num_fqs++].flowid = (u16)i; + } + +#ifdef CONFIG_FSL_DPAA2_ETH_USE_ERR_QUEUE + /* We have exactly one Rx error queue per DPNI */ + priv->fq[priv->num_fqs].type = DPAA2_RX_ERR_FQ; + priv->fq[priv->num_fqs++].consume = dpaa2_eth_rx_err; +#endif + + /* For each FQ, decide on which core to process incoming frames */ + set_fq_affinity(priv); +} + +/* Allocate and configure one buffer pool for each interface */ +static int setup_dpbp(struct dpaa2_eth_priv *priv) +{ + int err; + struct fsl_mc_device *dpbp_dev; + struct dpbp_attr dpbp_attrs; + struct device *dev = priv->net_dev->dev.parent; + + err = fsl_mc_object_allocate(to_fsl_mc_device(dev), FSL_MC_POOL_DPBP, + &dpbp_dev); + if (err) { + dev_err(dev, "DPBP device allocation failed\n"); + return err; + } + + priv->dpbp_dev = dpbp_dev; + + err = dpbp_open(priv->mc_io, 0, priv->dpbp_dev->obj_desc.id, + &dpbp_dev->mc_handle); + if (err) { + dev_err(dev, "dpbp_open() failed\n"); + goto err_open; + } + + err = dpbp_reset(priv->mc_io, 0, dpbp_dev->mc_handle); + if (err) { + dev_err(dev, "dpbp_reset() failed\n"); + goto err_reset; + } + + err = dpbp_enable(priv->mc_io, 0, dpbp_dev->mc_handle); + if (err) { + dev_err(dev, "dpbp_enable() failed\n"); + goto err_enable; + } + + err = dpbp_get_attributes(priv->mc_io, 0, dpbp_dev->mc_handle, + &dpbp_attrs); + if (err) { + dev_err(dev, "dpbp_get_attributes() failed\n"); + goto err_get_attr; + } + + priv->bpid = dpbp_attrs.bpid; + priv->num_bufs = DPAA2_ETH_NUM_BUFS_FC / priv->num_channels; + + return 0; + +err_get_attr: + dpbp_disable(priv->mc_io, 0, dpbp_dev->mc_handle); +err_enable: +err_reset: + dpbp_close(priv->mc_io, 0, dpbp_dev->mc_handle); +err_open: + fsl_mc_object_free(dpbp_dev); + + return err; +} + +static void free_dpbp(struct dpaa2_eth_priv *priv) +{ + drain_pool(priv); + dpbp_disable(priv->mc_io, 0, priv->dpbp_dev->mc_handle); + dpbp_close(priv->mc_io, 0, priv->dpbp_dev->mc_handle); + fsl_mc_object_free(priv->dpbp_dev); +} + +static int setup_tx_congestion(struct dpaa2_eth_priv *priv) +{ + struct dpni_congestion_notification_cfg cong_notif_cfg = { 0 }; + struct device *dev = priv->net_dev->dev.parent; + int err; + + priv->cscn_mem = kzalloc(DPAA2_CSCN_SIZE + DPAA2_CSCN_ALIGN, + GFP_KERNEL); + if (!priv->cscn_mem) + return -ENOMEM; + + priv->cscn_mem = PTR_ALIGN(priv->cscn_mem, DPAA2_CSCN_ALIGN); + priv->cscn_dma = dma_map_single(dev, priv->cscn_mem, DPAA2_CSCN_SIZE, + DMA_FROM_DEVICE); + if (dma_mapping_error(dev, priv->cscn_dma)) { + dev_err(dev, "Error mapping CSCN memory area\n"); + err = -ENOMEM; + goto err_dma_map; + } + + cong_notif_cfg.units = DPNI_CONGESTION_UNIT_BYTES; + cong_notif_cfg.threshold_entry = DPAA2_ETH_TX_CONG_ENTRY_THRESH; + cong_notif_cfg.threshold_exit = DPAA2_ETH_TX_CONG_EXIT_THRESH; + cong_notif_cfg.message_ctx = (u64)priv; + cong_notif_cfg.message_iova = priv->cscn_dma; + cong_notif_cfg.notification_mode = DPNI_CONG_OPT_WRITE_MEM_ON_ENTER | + DPNI_CONG_OPT_WRITE_MEM_ON_EXIT | + DPNI_CONG_OPT_COHERENT_WRITE; + err = dpni_set_congestion_notification(priv->mc_io, 0, priv->mc_token, + DPNI_QUEUE_TX, 0, + &cong_notif_cfg); + if (err) { + dev_err(dev, "dpni_set_congestion_notification failed\n"); + goto err_set_cong; + } + + return 0; + +err_set_cong: + dma_unmap_single(dev, priv->cscn_dma, DPAA2_CSCN_SIZE, DMA_FROM_DEVICE); +err_dma_map: + kfree(priv->cscn_mem); + + return err; +} + +/* Configure the DPNI object this interface is associated with */ +static int setup_dpni(struct fsl_mc_device *ls_dev) +{ + struct device *dev = &ls_dev->dev; + struct dpaa2_eth_priv *priv; + struct net_device *net_dev; + struct dpni_buffer_layout buf_layout; + struct dpni_link_cfg cfg = {0}; + int err; + + net_dev = dev_get_drvdata(dev); + priv = netdev_priv(net_dev); + + priv->dpni_id = ls_dev->obj_desc.id; + + /* get a handle for the DPNI object */ + err = dpni_open(priv->mc_io, 0, priv->dpni_id, &priv->mc_token); + if (err) { + dev_err(dev, "dpni_open() failed\n"); + goto err_open; + } + + ls_dev->mc_io = priv->mc_io; + ls_dev->mc_handle = priv->mc_token; + + err = dpni_reset(priv->mc_io, 0, priv->mc_token); + if (err) { + dev_err(dev, "dpni_reset() failed\n"); + goto err_reset; + } + + err = dpni_get_attributes(priv->mc_io, 0, priv->mc_token, + &priv->dpni_attrs); + + if (err) { + dev_err(dev, "dpni_get_attributes() failed (err=%d)\n", err); + goto err_get_attr; + } + + /* due to a limitation in WRIOP 1.0.0 (ERR009354), the Rx buf + * align value must be a multiple of 256. + */ + priv->rx_buf_align = + priv->dpni_attrs.wriop_version & 0x3ff ? + DPAA2_ETH_RX_BUF_ALIGN : DPAA2_ETH_RX_BUF_ALIGN_V1; + + /* Update number of logical FQs in netdev */ + err = netif_set_real_num_tx_queues(net_dev, + dpaa2_eth_queue_count(priv)); + if (err) { + dev_err(dev, "netif_set_real_num_tx_queues failed (%d)\n", err); + goto err_set_tx_queues; + } + + err = netif_set_real_num_rx_queues(net_dev, + dpaa2_eth_queue_count(priv)); + if (err) { + dev_err(dev, "netif_set_real_num_rx_queues failed (%d)\n", err); + goto err_set_rx_queues; + } + + /* Configure buffer layouts */ + /* rx buffer */ + buf_layout.pass_parser_result = true; + buf_layout.pass_frame_status = true; + buf_layout.private_data_size = DPAA2_ETH_SWA_SIZE; + buf_layout.data_align = priv->rx_buf_align; + buf_layout.data_head_room = DPAA2_ETH_RX_HEAD_ROOM; + buf_layout.options = DPNI_BUF_LAYOUT_OPT_PARSER_RESULT | + DPNI_BUF_LAYOUT_OPT_FRAME_STATUS | + DPNI_BUF_LAYOUT_OPT_PRIVATE_DATA_SIZE | + DPNI_BUF_LAYOUT_OPT_DATA_ALIGN | + DPNI_BUF_LAYOUT_OPT_DATA_HEAD_ROOM; + err = dpni_set_buffer_layout(priv->mc_io, 0, priv->mc_token, + DPNI_QUEUE_RX, &buf_layout); + if (err) { + dev_err(dev, + "dpni_set_buffer_layout(RX) failed\n"); + goto err_buf_layout; + } + + /* tx buffer */ + buf_layout.options = DPNI_BUF_LAYOUT_OPT_FRAME_STATUS | + DPNI_BUF_LAYOUT_OPT_PRIVATE_DATA_SIZE; + err = dpni_set_buffer_layout(priv->mc_io, 0, priv->mc_token, + DPNI_QUEUE_TX, &buf_layout); + if (err) { + dev_err(dev, + "dpni_set_buffer_layout(TX) failed\n"); + goto err_buf_layout; + } + + /* tx-confirm buffer */ + buf_layout.options = DPNI_BUF_LAYOUT_OPT_FRAME_STATUS; + buf_layout.pass_timestamp = 1; + err = dpni_set_buffer_layout(priv->mc_io, 0, priv->mc_token, + DPNI_QUEUE_TX_CONFIRM, &buf_layout); + if (err) { + dev_err(dev, "dpni_set_buffer_layout(TX_CONF) failed\n"); + goto err_buf_layout; + } + + /* Now that we've set our tx buffer layout, retrieve the minimum + * required tx data offset. + */ + err = dpni_get_tx_data_offset(priv->mc_io, 0, priv->mc_token, + &priv->tx_data_offset); + if (err) { + dev_err(dev, "dpni_get_tx_data_offset() failed (%d)\n", err); + goto err_data_offset; + } + + if ((priv->tx_data_offset % 64) != 0) + dev_warn(dev, "Tx data offset (%d) not a multiple of 64B", + priv->tx_data_offset); + + /* Accommodate software annotation space (SWA) */ + priv->tx_data_offset += DPAA2_ETH_SWA_SIZE; + + /* Enable congestion notifications for Tx queues */ + err = setup_tx_congestion(priv); + if (err) + goto err_tx_cong; + + /* allocate classification rule space */ + priv->cls_rule = kzalloc(sizeof(*priv->cls_rule) * + dpaa2_eth_fs_count(priv), GFP_KERNEL); + if (!priv->cls_rule) + goto err_cls_rule; + + /* Enable flow control */ + cfg.options = DPNI_LINK_OPT_AUTONEG | DPNI_LINK_OPT_PAUSE; + priv->tx_pause_frames = 1; + + err = dpni_set_link_cfg(priv->mc_io, 0, priv->mc_token, &cfg); + if (err) { + netdev_err(net_dev, "ERROR %d setting link cfg", err); + goto err_set_link_cfg; + } + + return 0; + +err_set_link_cfg: +err_cls_rule: +err_tx_cong: +err_data_offset: +err_buf_layout: +err_set_rx_queues: +err_set_tx_queues: +err_get_attr: +err_reset: + dpni_close(priv->mc_io, 0, priv->mc_token); +err_open: + return err; +} + +static void free_dpni(struct dpaa2_eth_priv *priv) +{ + struct device *dev = priv->net_dev->dev.parent; + int err; + + err = dpni_reset(priv->mc_io, 0, priv->mc_token); + if (err) + netdev_warn(priv->net_dev, "dpni_reset() failed (err %d)\n", + err); + + dpni_close(priv->mc_io, 0, priv->mc_token); + + kfree(priv->cls_rule); + + dma_unmap_single(dev, priv->cscn_dma, DPAA2_CSCN_SIZE, DMA_FROM_DEVICE); + kfree(priv->cscn_mem); +} + +int setup_fqs_taildrop(struct dpaa2_eth_priv *priv, + bool enable) +{ + struct device *dev = priv->net_dev->dev.parent; + struct dpni_taildrop td; + int err = 0, i; + + td.enable = enable; + td.threshold = DPAA2_ETH_TAILDROP_THRESH; + + if (enable) { + priv->num_bufs = DPAA2_ETH_NUM_BUFS_TD; + priv->refill_thresh = DPAA2_ETH_REFILL_THRESH_TD; + } else { + priv->num_bufs = DPAA2_ETH_NUM_BUFS_FC / + priv->num_channels; + priv->refill_thresh = priv->num_bufs - DPAA2_ETH_BUFS_PER_CMD; + } + + for (i = 0; i < priv->num_fqs; i++) { + if (priv->fq[i].type != DPAA2_RX_FQ) + continue; + + err = dpni_set_taildrop(priv->mc_io, 0, priv->mc_token, + DPNI_CP_QUEUE, DPNI_QUEUE_RX, 0, + priv->fq[i].flowid, &td); + if (err) { + dev_err(dev, "dpni_set_taildrop() failed (%d)\n", err); + break; + } + } + + return err; +} + +static int setup_rx_flow(struct dpaa2_eth_priv *priv, + struct dpaa2_eth_fq *fq) +{ + struct device *dev = priv->net_dev->dev.parent; + struct dpni_queue q = { { 0 } }; + struct dpni_queue_id qid; + u8 q_opt = DPNI_QUEUE_OPT_USER_CTX | DPNI_QUEUE_OPT_DEST; + int err; + + err = dpni_get_queue(priv->mc_io, 0, priv->mc_token, + DPNI_QUEUE_RX, 0, fq->flowid, &q, &qid); + if (err) { + dev_err(dev, "dpni_get_queue() failed (%d)\n", err); + return err; + } + + fq->fqid = qid.fqid; + + q.destination.id = fq->channel->dpcon_id; + q.destination.type = DPNI_DEST_DPCON; + q.destination.priority = 1; + q.user_context = (u64)fq; + err = dpni_set_queue(priv->mc_io, 0, priv->mc_token, + DPNI_QUEUE_RX, 0, fq->flowid, q_opt, &q); + if (err) { + dev_err(dev, "dpni_set_queue() failed (%d)\n", err); + return err; + } + + return 0; +} + +static int setup_tx_flow(struct dpaa2_eth_priv *priv, + struct dpaa2_eth_fq *fq) +{ + struct device *dev = priv->net_dev->dev.parent; + struct dpni_queue q = { { 0 } }; + struct dpni_queue_id qid; + u8 q_opt = DPNI_QUEUE_OPT_USER_CTX | DPNI_QUEUE_OPT_DEST; + int err; + + err = dpni_get_queue(priv->mc_io, 0, priv->mc_token, + DPNI_QUEUE_TX, 0, fq->flowid, &q, &qid); + if (err) { + dev_err(dev, "dpni_get_queue() failed (%d)\n", err); + return err; + } + + fq->tx_qdbin = qid.qdbin; + + err = dpni_get_queue(priv->mc_io, 0, priv->mc_token, + DPNI_QUEUE_TX_CONFIRM, 0, fq->flowid, &q, &qid); + if (err) { + dev_err(dev, "dpni_get_queue() failed (%d)\n", err); + return err; + } + + fq->fqid = qid.fqid; + + q.destination.id = fq->channel->dpcon_id; + q.destination.type = DPNI_DEST_DPCON; + q.destination.priority = 0; + q.user_context = (u64)fq; + err = dpni_set_queue(priv->mc_io, 0, priv->mc_token, + DPNI_QUEUE_TX_CONFIRM, 0, fq->flowid, q_opt, &q); + if (err) { + dev_err(dev, "dpni_get_queue() failed (%d)\n", err); + return err; + } + + return 0; +} + +#ifdef CONFIG_FSL_DPAA2_ETH_USE_ERR_QUEUE +static int setup_rx_err_flow(struct dpaa2_eth_priv *priv, + struct dpaa2_eth_fq *fq) +{ + struct device *dev = priv->net_dev->dev.parent; + struct dpni_queue q = { { 0 } }; + struct dpni_queue_id qid; + u8 q_opt = DPNI_QUEUE_OPT_USER_CTX | DPNI_QUEUE_OPT_DEST; + int err; + + err = dpni_get_queue(priv->mc_io, 0, priv->mc_token, + DPNI_QUEUE_RX_ERR, 0, 0, &q, &qid); + if (err) { + dev_err(dev, "dpni_get_queue() failed (%d)\n", err); + return err; + } + + fq->fqid = qid.fqid; + + q.destination.id = fq->channel->dpcon_id; + q.destination.type = DPNI_DEST_DPCON; + q.destination.priority = 1; + q.user_context = (u64)fq; + err = dpni_set_queue(priv->mc_io, 0, priv->mc_token, + DPNI_QUEUE_RX_ERR, 0, 0, q_opt, &q); + if (err) { + dev_err(dev, "dpni_set_queue() failed (%d)\n", err); + return err; + } + + return 0; +} +#endif + +/* default hash key fields */ +static struct dpaa2_eth_hash_fields default_hash_fields[] = { + { + /* L2 header */ + .rxnfc_field = RXH_L2DA, + .cls_prot = NET_PROT_ETH, + .cls_field = NH_FLD_ETH_DA, + .size = 6, + }, { + .cls_prot = NET_PROT_ETH, + .cls_field = NH_FLD_ETH_SA, + .size = 6, + }, { + /* This is the last ethertype field parsed: + * depending on frame format, it can be the MAC ethertype + * or the VLAN etype. + */ + .cls_prot = NET_PROT_ETH, + .cls_field = NH_FLD_ETH_TYPE, + .size = 2, + }, { + /* VLAN header */ + .rxnfc_field = RXH_VLAN, + .cls_prot = NET_PROT_VLAN, + .cls_field = NH_FLD_VLAN_TCI, + .size = 2, + }, { + /* IP header */ + .rxnfc_field = RXH_IP_SRC, + .cls_prot = NET_PROT_IP, + .cls_field = NH_FLD_IP_SRC, + .size = 4, + }, { + .rxnfc_field = RXH_IP_DST, + .cls_prot = NET_PROT_IP, + .cls_field = NH_FLD_IP_DST, + .size = 4, + }, { + .rxnfc_field = RXH_L3_PROTO, + .cls_prot = NET_PROT_IP, + .cls_field = NH_FLD_IP_PROTO, + .size = 1, + }, { + /* Using UDP ports, this is functionally equivalent to raw + * byte pairs from L4 header. + */ + .rxnfc_field = RXH_L4_B_0_1, + .cls_prot = NET_PROT_UDP, + .cls_field = NH_FLD_UDP_PORT_SRC, + .size = 2, + }, { + .rxnfc_field = RXH_L4_B_2_3, + .cls_prot = NET_PROT_UDP, + .cls_field = NH_FLD_UDP_PORT_DST, + .size = 2, + }, +}; + +/* Set RX hash options */ +static int set_hash(struct dpaa2_eth_priv *priv) +{ + struct device *dev = priv->net_dev->dev.parent; + struct dpkg_profile_cfg cls_cfg; + struct dpni_rx_tc_dist_cfg dist_cfg; + u8 *dma_mem; + int i; + int err = 0; + + memset(&cls_cfg, 0, sizeof(cls_cfg)); + + for (i = 0; i < priv->num_hash_fields; i++) { + struct dpkg_extract *key = + &cls_cfg.extracts[cls_cfg.num_extracts]; + + key->type = DPKG_EXTRACT_FROM_HDR; + key->extract.from_hdr.prot = priv->hash_fields[i].cls_prot; + key->extract.from_hdr.type = DPKG_FULL_FIELD; + key->extract.from_hdr.field = priv->hash_fields[i].cls_field; + cls_cfg.num_extracts++; + + priv->rx_flow_hash |= priv->hash_fields[i].rxnfc_field; + } + + dma_mem = kzalloc(DPAA2_CLASSIFIER_DMA_SIZE, GFP_DMA | GFP_KERNEL); + if (!dma_mem) + return -ENOMEM; + + err = dpni_prepare_key_cfg(&cls_cfg, dma_mem); + if (err) { + dev_err(dev, "dpni_prepare_key_cfg() failed (%d)", err); + goto err_prep_key; + } + + memset(&dist_cfg, 0, sizeof(dist_cfg)); + + /* Prepare for setting the rx dist */ + dist_cfg.key_cfg_iova = dma_map_single(dev, dma_mem, + DPAA2_CLASSIFIER_DMA_SIZE, + DMA_TO_DEVICE); + if (dma_mapping_error(dev, dist_cfg.key_cfg_iova)) { + dev_err(dev, "DMA mapping failed\n"); + err = -ENOMEM; + goto err_dma_map; + } + + dist_cfg.dist_size = dpaa2_eth_queue_count(priv); + if (dpaa2_eth_fs_enabled(priv)) { + dist_cfg.dist_mode = DPNI_DIST_MODE_FS; + dist_cfg.fs_cfg.miss_action = DPNI_FS_MISS_HASH; + } else { + dist_cfg.dist_mode = DPNI_DIST_MODE_HASH; + } + + err = dpni_set_rx_tc_dist(priv->mc_io, 0, priv->mc_token, 0, &dist_cfg); + dma_unmap_single(dev, dist_cfg.key_cfg_iova, + DPAA2_CLASSIFIER_DMA_SIZE, DMA_TO_DEVICE); + if (err) + dev_err(dev, "dpni_set_rx_tc_dist() failed (%d)\n", err); + +err_dma_map: +err_prep_key: + kfree(dma_mem); + return err; +} + +/* Bind the DPNI to its needed objects and resources: buffer pool, DPIOs, + * frame queues and channels + */ +static int bind_dpni(struct dpaa2_eth_priv *priv) +{ + struct net_device *net_dev = priv->net_dev; + struct device *dev = net_dev->dev.parent; + struct dpni_pools_cfg pools_params; + struct dpni_error_cfg err_cfg; + int err = 0; + int i; + + pools_params.num_dpbp = 1; + pools_params.pools[0].dpbp_id = priv->dpbp_dev->obj_desc.id; + pools_params.pools[0].backup_pool = 0; + pools_params.pools[0].buffer_size = DPAA2_ETH_RX_BUF_SIZE; + err = dpni_set_pools(priv->mc_io, 0, priv->mc_token, &pools_params); + if (err) { + dev_err(dev, "dpni_set_pools() failed\n"); + return err; + } + + /* Verify classification options and disable hashing and/or + * flow steering support in case of invalid configuration values + */ + check_cls_support(priv); + + /* have the interface implicitly distribute traffic based on + * a static hash key + */ + if (dpaa2_eth_hash_enabled(priv)) { + priv->hash_fields = default_hash_fields; + priv->num_hash_fields = ARRAY_SIZE(default_hash_fields); + err = set_hash(priv); + if (err) { + dev_err(dev, "Hashing configuration failed\n"); + return err; + } + } + + /* Configure handling of error frames */ + err_cfg.errors = DPAA2_FAS_RX_ERR_MASK; + err_cfg.set_frame_annotation = 1; +#ifdef CONFIG_FSL_DPAA2_ETH_USE_ERR_QUEUE + err_cfg.error_action = DPNI_ERROR_ACTION_SEND_TO_ERROR_QUEUE; +#else + err_cfg.error_action = DPNI_ERROR_ACTION_DISCARD; +#endif + err = dpni_set_errors_behavior(priv->mc_io, 0, priv->mc_token, + &err_cfg); + if (err) { + dev_err(dev, "dpni_set_errors_behavior() failed (%d)\n", err); + return err; + } + + /* Configure Rx and Tx conf queues to generate CDANs */ + for (i = 0; i < priv->num_fqs; i++) { + switch (priv->fq[i].type) { + case DPAA2_RX_FQ: + err = setup_rx_flow(priv, &priv->fq[i]); + break; + case DPAA2_TX_CONF_FQ: + err = setup_tx_flow(priv, &priv->fq[i]); + break; +#ifdef CONFIG_FSL_DPAA2_ETH_USE_ERR_QUEUE + case DPAA2_RX_ERR_FQ: + err = setup_rx_err_flow(priv, &priv->fq[i]); + break; +#endif + default: + dev_err(dev, "Invalid FQ type %d\n", priv->fq[i].type); + return -EINVAL; + } + if (err) + return err; + } + + err = dpni_get_qdid(priv->mc_io, 0, priv->mc_token, DPNI_QUEUE_TX, + &priv->tx_qdid); + if (err) { + dev_err(dev, "dpni_get_qdid() failed\n"); + return err; + } + + return 0; +} + +/* Allocate rings for storing incoming frame descriptors */ +static int alloc_rings(struct dpaa2_eth_priv *priv) +{ + struct net_device *net_dev = priv->net_dev; + struct device *dev = net_dev->dev.parent; + int i; + + for (i = 0; i < priv->num_channels; i++) { + priv->channel[i]->store = + dpaa2_io_store_create(DPAA2_ETH_STORE_SIZE, dev); + if (!priv->channel[i]->store) { + netdev_err(net_dev, "dpaa2_io_store_create() failed\n"); + goto err_ring; + } + } + + return 0; + +err_ring: + for (i = 0; i < priv->num_channels; i++) { + if (!priv->channel[i]->store) + break; + dpaa2_io_store_destroy(priv->channel[i]->store); + } + + return -ENOMEM; +} + +static void free_rings(struct dpaa2_eth_priv *priv) +{ + int i; + + for (i = 0; i < priv->num_channels; i++) + dpaa2_io_store_destroy(priv->channel[i]->store); +} + +static int netdev_init(struct net_device *net_dev) +{ + int err; + struct device *dev = net_dev->dev.parent; + struct dpaa2_eth_priv *priv = netdev_priv(net_dev); + u8 mac_addr[ETH_ALEN], dpni_mac_addr[ETH_ALEN]; + u8 bcast_addr[ETH_ALEN]; + u16 rx_headroom, rx_req_headroom; + + net_dev->netdev_ops = &dpaa2_eth_ops; + + /* Get firmware address, if any */ + err = dpni_get_port_mac_addr(priv->mc_io, 0, priv->mc_token, mac_addr); + if (err) { + dev_err(dev, "dpni_get_port_mac_addr() failed (%d)\n", err); + return err; + } + + /* Get DPNI atttributes address, if any */ + err = dpni_get_primary_mac_addr(priv->mc_io, 0, priv->mc_token, + dpni_mac_addr); + if (err) { + dev_err(dev, "dpni_get_primary_mac_addr() failed (%d)\n", err); + return err; + } + + /* First check if firmware has any address configured by bootloader */ + if (!is_zero_ether_addr(mac_addr)) { + /* If the DPMAC addr != the DPNI addr, update it */ + if (!ether_addr_equal(mac_addr, dpni_mac_addr)) { + err = dpni_set_primary_mac_addr(priv->mc_io, 0, + priv->mc_token, + mac_addr); + if (err) { + dev_err(dev, + "dpni_set_primary_mac_addr() failed (%d)\n", + err); + return err; + } + } + memcpy(net_dev->dev_addr, mac_addr, net_dev->addr_len); + } else if (is_zero_ether_addr(dpni_mac_addr)) { + /* Fills in net_dev->dev_addr, as required by + * register_netdevice() + */ + eth_hw_addr_random(net_dev); + /* Make the user aware, without cluttering the boot log */ + dev_dbg_once(dev, " device(s) have all-zero hwaddr, replaced with random\n"); + err = dpni_set_primary_mac_addr(priv->mc_io, 0, + priv->mc_token, net_dev->dev_addr); + if (err) { + dev_err(dev, + "dpni_set_primary_mac_addr() failed (%d)\n", err); + return err; + } + /* Override NET_ADDR_RANDOM set by eth_hw_addr_random(); for all + * practical purposes, this will be our "permanent" mac address, + * at least until the next reboot. This move will also permit + * register_netdevice() to properly fill up net_dev->perm_addr. + */ + net_dev->addr_assign_type = NET_ADDR_PERM; + /* If DPMAC address is non-zero, use that one */ + } else { + /* NET_ADDR_PERM is default, all we have to do is + * fill in the device addr. + */ + memcpy(net_dev->dev_addr, dpni_mac_addr, net_dev->addr_len); + } + + /* Explicitly add the broadcast address to the MAC filtering table; + * the MC won't do that for us. + */ + eth_broadcast_addr(bcast_addr); + err = dpni_add_mac_addr(priv->mc_io, 0, priv->mc_token, bcast_addr); + if (err) { + dev_warn(dev, "dpni_add_mac_addr() failed (%d)\n", err); + /* Won't return an error; at least, we'd have egress traffic */ + } + + /* Reserve enough space to align buffer as per hardware requirement; + * NOTE: priv->tx_data_offset MUST be initialized at this point. + */ + net_dev->needed_headroom = DPAA2_ETH_NEEDED_HEADROOM(priv); + + /* Set MTU limits */ + net_dev->min_mtu = 68; + net_dev->max_mtu = DPAA2_ETH_MAX_MTU; + + /* Required headroom for Rx skbs, to avoid reallocation on + * forwarding path. + */ + rx_req_headroom = LL_RESERVED_SPACE(net_dev) - ETH_HLEN; + rx_headroom = ALIGN(DPAA2_ETH_RX_HWA_SIZE + DPAA2_ETH_SWA_SIZE + + DPAA2_ETH_RX_HEAD_ROOM, priv->rx_buf_align); + if (rx_req_headroom > rx_headroom) + dev_info_once(dev, + "Required headroom (%d) greater than available (%d).\n" + "This will impact performance due to reallocations.\n", + rx_req_headroom, rx_headroom); + + /* Our .ndo_init will be called herein */ + err = register_netdev(net_dev); + if (err < 0) { + dev_err(dev, "register_netdev() failed (%d)\n", err); + return err; + } + + return 0; +} + +static int poll_link_state(void *arg) +{ + struct dpaa2_eth_priv *priv = (struct dpaa2_eth_priv *)arg; + int err; + + while (!kthread_should_stop()) { + err = link_state_update(priv); + if (unlikely(err)) + return err; + + msleep(DPAA2_ETH_LINK_STATE_REFRESH); + } + + return 0; +} + +static irqreturn_t dpni_irq0_handler(int irq_num, void *arg) +{ + return IRQ_WAKE_THREAD; +} + +static irqreturn_t dpni_irq0_handler_thread(int irq_num, void *arg) +{ + u32 status = 0, clear = 0; + struct device *dev = (struct device *)arg; + struct fsl_mc_device *dpni_dev = to_fsl_mc_device(dev); + struct net_device *net_dev = dev_get_drvdata(dev); + int err; + + err = dpni_get_irq_status(dpni_dev->mc_io, 0, dpni_dev->mc_handle, + DPNI_IRQ_INDEX, &status); + if (unlikely(err)) { + netdev_err(net_dev, "Can't get irq status (err %d)", err); + clear = 0xffffffff; + goto out; + } + + if (status & DPNI_IRQ_EVENT_LINK_CHANGED) { + clear |= DPNI_IRQ_EVENT_LINK_CHANGED; + link_state_update(netdev_priv(net_dev)); + } + +out: + dpni_clear_irq_status(dpni_dev->mc_io, 0, dpni_dev->mc_handle, + DPNI_IRQ_INDEX, clear); + return IRQ_HANDLED; +} + +static int setup_irqs(struct fsl_mc_device *ls_dev) +{ + int err = 0; + struct fsl_mc_device_irq *irq; + + err = fsl_mc_allocate_irqs(ls_dev); + if (err) { + dev_err(&ls_dev->dev, "MC irqs allocation failed\n"); + return err; + } + + irq = ls_dev->irqs[0]; + err = devm_request_threaded_irq(&ls_dev->dev, irq->msi_desc->irq, + dpni_irq0_handler, + dpni_irq0_handler_thread, + IRQF_NO_SUSPEND | IRQF_ONESHOT, + dev_name(&ls_dev->dev), &ls_dev->dev); + if (err < 0) { + dev_err(&ls_dev->dev, "devm_request_threaded_irq(): %d", err); + goto free_mc_irq; + } + + err = dpni_set_irq_mask(ls_dev->mc_io, 0, ls_dev->mc_handle, + DPNI_IRQ_INDEX, DPNI_IRQ_EVENT_LINK_CHANGED); + if (err < 0) { + dev_err(&ls_dev->dev, "dpni_set_irq_mask(): %d", err); + goto free_irq; + } + + err = dpni_set_irq_enable(ls_dev->mc_io, 0, ls_dev->mc_handle, + DPNI_IRQ_INDEX, 1); + if (err < 0) { + dev_err(&ls_dev->dev, "dpni_set_irq_enable(): %d", err); + goto free_irq; + } + + return 0; + +free_irq: + devm_free_irq(&ls_dev->dev, irq->msi_desc->irq, &ls_dev->dev); +free_mc_irq: + fsl_mc_free_irqs(ls_dev); + + return err; +} + +static void add_ch_napi(struct dpaa2_eth_priv *priv) +{ + int i; + struct dpaa2_eth_channel *ch; + + for (i = 0; i < priv->num_channels; i++) { + ch = priv->channel[i]; + /* NAPI weight *MUST* be a multiple of DPAA2_ETH_STORE_SIZE */ + netif_napi_add(priv->net_dev, &ch->napi, dpaa2_eth_poll, + NAPI_POLL_WEIGHT); + } +} + +static void del_ch_napi(struct dpaa2_eth_priv *priv) +{ + int i; + struct dpaa2_eth_channel *ch; + + for (i = 0; i < priv->num_channels; i++) { + ch = priv->channel[i]; + netif_napi_del(&ch->napi); + } +} + +/* SysFS support */ +static ssize_t dpaa2_eth_show_tx_shaping(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct dpaa2_eth_priv *priv = netdev_priv(to_net_dev(dev)); + /* No MC API for getting the shaping config. We're stateful. */ + struct dpni_tx_shaping_cfg *scfg = &priv->shaping_cfg; + + return sprintf(buf, "%u %hu\n", scfg->rate_limit, scfg->max_burst_size); +} + +static ssize_t dpaa2_eth_write_tx_shaping(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t count) +{ + int err, items; + struct dpaa2_eth_priv *priv = netdev_priv(to_net_dev(dev)); + struct dpni_tx_shaping_cfg scfg; + + items = sscanf(buf, "%u %hu", &scfg.rate_limit, &scfg.max_burst_size); + if (items != 2) { + pr_err("Expected format: \"rate_limit(Mbps) max_burst_size(bytes)\"\n"); + return -EINVAL; + } + /* Size restriction as per MC API documentation */ + if (scfg.max_burst_size > 64000) { + pr_err("max_burst_size must be <= 64000, thanks.\n"); + return -EINVAL; + } + + err = dpni_set_tx_shaping(priv->mc_io, 0, priv->mc_token, &scfg); + if (err) { + dev_err(dev, "dpni_set_tx_shaping() failed\n"); + return -EPERM; + } + /* If successful, save the current configuration for future inquiries */ + priv->shaping_cfg = scfg; + + return count; +} + +static ssize_t dpaa2_eth_show_txconf_cpumask(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct dpaa2_eth_priv *priv = netdev_priv(to_net_dev(dev)); + + return cpumap_print_to_pagebuf(1, buf, &priv->txconf_cpumask); +} + +static ssize_t dpaa2_eth_write_txconf_cpumask(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t count) +{ + struct dpaa2_eth_priv *priv = netdev_priv(to_net_dev(dev)); + struct dpaa2_eth_fq *fq; + bool running = netif_running(priv->net_dev); + int i, err; + + err = cpulist_parse(buf, &priv->txconf_cpumask); + if (err) + return err; + + /* Only accept CPUs that have an affine DPIO */ + if (!cpumask_subset(&priv->txconf_cpumask, &priv->dpio_cpumask)) { + netdev_info(priv->net_dev, + "cpumask must be a subset of 0x%lx\n", + *cpumask_bits(&priv->dpio_cpumask)); + cpumask_and(&priv->txconf_cpumask, &priv->dpio_cpumask, + &priv->txconf_cpumask); + } + + /* Rewiring the TxConf FQs requires interface shutdown. + */ + if (running) { + err = dpaa2_eth_stop(priv->net_dev); + if (err) + return -ENODEV; + } + + /* Set the new TxConf FQ affinities */ + set_fq_affinity(priv); + + /* dpaa2_eth_open() below will *stop* the Tx queues until an explicit + * link up notification is received. Give the polling thread enough time + * to detect the link state change, or else we'll end up with the + * transmission side forever shut down. + */ + if (priv->do_link_poll) + msleep(2 * DPAA2_ETH_LINK_STATE_REFRESH); + + for (i = 0; i < priv->num_fqs; i++) { + fq = &priv->fq[i]; + if (fq->type != DPAA2_TX_CONF_FQ) + continue; + setup_tx_flow(priv, fq); + } + + if (running) { + err = dpaa2_eth_open(priv->net_dev); + if (err) + return -ENODEV; + } + + return count; +} + +static struct device_attribute dpaa2_eth_attrs[] = { + __ATTR(txconf_cpumask, + 0600, + dpaa2_eth_show_txconf_cpumask, + dpaa2_eth_write_txconf_cpumask), + + __ATTR(tx_shaping, + 0600, + dpaa2_eth_show_tx_shaping, + dpaa2_eth_write_tx_shaping), +}; + +static void dpaa2_eth_sysfs_init(struct device *dev) +{ + int i, err; + + for (i = 0; i < ARRAY_SIZE(dpaa2_eth_attrs); i++) { + err = device_create_file(dev, &dpaa2_eth_attrs[i]); + if (err) { + dev_err(dev, "ERROR creating sysfs file\n"); + goto undo; + } + } + return; + +undo: + while (i > 0) + device_remove_file(dev, &dpaa2_eth_attrs[--i]); +} + +static void dpaa2_eth_sysfs_remove(struct device *dev) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(dpaa2_eth_attrs); i++) + device_remove_file(dev, &dpaa2_eth_attrs[i]); +} + +static int dpaa2_eth_probe(struct fsl_mc_device *dpni_dev) +{ + struct device *dev; + struct net_device *net_dev = NULL; + struct dpaa2_eth_priv *priv = NULL; + int err = 0; + + dev = &dpni_dev->dev; + + /* Net device */ + net_dev = alloc_etherdev_mq(sizeof(*priv), DPAA2_ETH_MAX_TX_QUEUES); + if (!net_dev) { + dev_err(dev, "alloc_etherdev_mq() failed\n"); + return -ENOMEM; + } + + SET_NETDEV_DEV(net_dev, dev); + dev_set_drvdata(dev, net_dev); + + priv = netdev_priv(net_dev); + priv->net_dev = net_dev; + + /* Obtain a MC portal */ + err = fsl_mc_portal_allocate(dpni_dev, FSL_MC_IO_ATOMIC_CONTEXT_PORTAL, + &priv->mc_io); + if (err) { + dev_err(dev, "MC portal allocation failed\n"); + goto err_portal_alloc; + } + + /* MC objects initialization and configuration */ + err = setup_dpni(dpni_dev); + if (err) + goto err_dpni_setup; + + err = setup_dpio(priv); + if (err) + goto err_dpio_setup; + + setup_fqs(priv); + + err = setup_dpbp(priv); + if (err) + goto err_dpbp_setup; + + err = bind_dpni(priv); + if (err) + goto err_bind; + + /* Add a NAPI context for each channel */ + add_ch_napi(priv); + enable_ch_napi(priv); + + /* Percpu statistics */ + priv->percpu_stats = alloc_percpu(*priv->percpu_stats); + if (!priv->percpu_stats) { + dev_err(dev, "alloc_percpu(percpu_stats) failed\n"); + err = -ENOMEM; + goto err_alloc_percpu_stats; + } + priv->percpu_extras = alloc_percpu(*priv->percpu_extras); + if (!priv->percpu_extras) { + dev_err(dev, "alloc_percpu(percpu_extras) failed\n"); + err = -ENOMEM; + goto err_alloc_percpu_extras; + } + + snprintf(net_dev->name, IFNAMSIZ, "ni%d", dpni_dev->obj_desc.id); + if (!dev_valid_name(net_dev->name)) { + dev_warn(&net_dev->dev, + "netdevice name \"%s\" cannot be used, reverting to default..\n", + net_dev->name); + dev_alloc_name(net_dev, "eth%d"); + dev_warn(&net_dev->dev, "using name \"%s\"\n", net_dev->name); + } + + err = netdev_init(net_dev); + if (err) + goto err_netdev_init; + + /* Configure checksum offload based on current interface flags */ + err = set_rx_csum(priv, !!(net_dev->features & NETIF_F_RXCSUM)); + if (err) + goto err_csum; + + err = set_tx_csum(priv, !!(net_dev->features & + (NETIF_F_IP_CSUM | NETIF_F_IPV6_CSUM))); + if (err) + goto err_csum; + + err = alloc_rings(priv); + if (err) + goto err_alloc_rings; + + net_dev->ethtool_ops = &dpaa2_ethtool_ops; + + err = setup_irqs(dpni_dev); + if (err) { + netdev_warn(net_dev, "Failed to set link interrupt, fall back to polling\n"); + priv->poll_thread = kthread_run(poll_link_state, priv, + "%s_poll_link", net_dev->name); + if (IS_ERR(priv->poll_thread)) { + netdev_err(net_dev, "Error starting polling thread\n"); + goto err_poll_thread; + } + priv->do_link_poll = true; + } + + dpaa2_eth_sysfs_init(&net_dev->dev); + + dev_info(dev, "Probed interface %s\n", net_dev->name); + return 0; + +err_poll_thread: + free_rings(priv); +err_alloc_rings: +err_csum: + unregister_netdev(net_dev); +err_netdev_init: + free_percpu(priv->percpu_extras); +err_alloc_percpu_extras: + free_percpu(priv->percpu_stats); +err_alloc_percpu_stats: + disable_ch_napi(priv); + del_ch_napi(priv); +err_bind: + free_dpbp(priv); +err_dpbp_setup: + free_dpio(priv); +err_dpio_setup: + free_dpni(priv); +err_dpni_setup: + fsl_mc_portal_free(priv->mc_io); +err_portal_alloc: + dev_set_drvdata(dev, NULL); + free_netdev(net_dev); + + return err; +} + +static int dpaa2_eth_remove(struct fsl_mc_device *ls_dev) +{ + struct device *dev; + struct net_device *net_dev; + struct dpaa2_eth_priv *priv; + + dev = &ls_dev->dev; + net_dev = dev_get_drvdata(dev); + priv = netdev_priv(net_dev); + + dpaa2_eth_sysfs_remove(&net_dev->dev); + + unregister_netdev(net_dev); + dev_info(net_dev->dev.parent, "Removed interface %s\n", net_dev->name); + + if (priv->do_link_poll) + kthread_stop(priv->poll_thread); + else + fsl_mc_free_irqs(ls_dev); + + free_rings(priv); + free_percpu(priv->percpu_stats); + free_percpu(priv->percpu_extras); + + disable_ch_napi(priv); + del_ch_napi(priv); + free_dpbp(priv); + free_dpio(priv); + free_dpni(priv); + + fsl_mc_portal_free(priv->mc_io); + + dev_set_drvdata(dev, NULL); + free_netdev(net_dev); + + return 0; +} + +static const struct fsl_mc_device_id dpaa2_eth_match_id_table[] = { + { + .vendor = FSL_MC_VENDOR_FREESCALE, + .obj_type = "dpni", + }, + { .vendor = 0x0 } +}; +MODULE_DEVICE_TABLE(fslmc, dpaa2_eth_match_id_table); + +static struct fsl_mc_driver dpaa2_eth_driver = { + .driver = { + .name = KBUILD_MODNAME, + .owner = THIS_MODULE, + }, + .probe = dpaa2_eth_probe, + .remove = dpaa2_eth_remove, + .match_id_table = dpaa2_eth_match_id_table +}; + +static int __init dpaa2_eth_driver_init(void) +{ + int err; + + err = fsl_mc_driver_register(&dpaa2_eth_driver); + if (err) + return err; + + return 0; +} + +static void __exit dpaa2_eth_driver_exit(void) +{ + fsl_mc_driver_unregister(&dpaa2_eth_driver); +} + +module_init(dpaa2_eth_driver_init); +module_exit(dpaa2_eth_driver_exit); diff --git a/drivers/staging/fsl-dpaa2/ethernet/dpaa2-eth.h b/drivers/staging/fsl-dpaa2/ethernet/dpaa2-eth.h new file mode 100644 index 0000000..8e2f1b9 --- /dev/null +++ b/drivers/staging/fsl-dpaa2/ethernet/dpaa2-eth.h @@ -0,0 +1,459 @@ +/* Copyright 2014-2015 Freescale Semiconductor Inc. + * + * 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 Freescale Semiconductor nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * + * ALTERNATIVELY, this software may be distributed under the terms of the + * GNU General Public License ("GPL") as published by the Free Software + * Foundation, either version 2 of that License or (at your option) any + * later version. + * + * THIS SOFTWARE IS PROVIDED BY Freescale Semiconductor ``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 Freescale Semiconductor 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. + */ + +#ifndef __DPAA2_ETH_H +#define __DPAA2_ETH_H + +#include <linux/atomic.h> +#include <linux/netdevice.h> +#include <linux/if_vlan.h> +#include "../../fsl-mc/include/dpaa2-io.h" +#include "dpni.h" +#include "net.h" + +#define DPAA2_ETH_STORE_SIZE 16 + +/* We set a max threshold for how many Tx confirmations we should process + * on a NAPI poll call, they take less processing time. + */ +#define TX_CONF_PER_NAPI_POLL 256 + +/* Maximum number of scatter-gather entries in an ingress frame, + * considering the maximum receive frame size is 64K + */ +#define DPAA2_ETH_MAX_SG_ENTRIES ((64 * 1024) / DPAA2_ETH_RX_BUF_SIZE) + +/* Maximum acceptable MTU value. It is in direct relation with the hardware + * enforced Max Frame Length (currently 10k). + */ +#define DPAA2_ETH_MFL (10 * 1024) +#define DPAA2_ETH_MAX_MTU (DPAA2_ETH_MFL - VLAN_ETH_HLEN) +/* Convert L3 MTU to L2 MFL */ +#define DPAA2_ETH_L2_MAX_FRM(mtu) ((mtu) + VLAN_ETH_HLEN) + +/* Maximum number of buffers that can be acquired/released through a single + * QBMan command + */ +#define DPAA2_ETH_BUFS_PER_CMD 7 + +/* Set the taildrop threshold (in bytes) to allow the enqueue of several jumbo + * frames in the Rx queues (length of the current frame is not + * taken into account when making the taildrop decision) + */ +#define DPAA2_ETH_TAILDROP_THRESH (64 * 1024) + +/* Buffer quota per queue. Must be large enough such that for minimum sized + * frames taildrop kicks in before the bpool gets depleted, so we compute + * how many 64B frames fit inside the taildrop threshold and add a margin + * to accommodate the buffer refill delay. + */ +#define DPAA2_ETH_MAX_FRAMES_PER_QUEUE (DPAA2_ETH_TAILDROP_THRESH / 64) +#define DPAA2_ETH_NUM_BUFS_TD (DPAA2_ETH_MAX_FRAMES_PER_QUEUE + 256) +#define DPAA2_ETH_REFILL_THRESH_TD \ + (DPAA2_ETH_NUM_BUFS_TD - DPAA2_ETH_BUFS_PER_CMD) + +/* Buffer quota per queue to use when flow control is active. */ +#define DPAA2_ETH_NUM_BUFS_FC 256 + +/* Hardware requires alignment for ingress/egress buffer addresses + * and ingress buffer lengths. + */ +#define DPAA2_ETH_RX_BUF_SIZE 2048 +#define DPAA2_ETH_TX_BUF_ALIGN 64 +#define DPAA2_ETH_RX_BUF_ALIGN 64 +#define DPAA2_ETH_RX_BUF_ALIGN_V1 256 +#define DPAA2_ETH_NEEDED_HEADROOM(p_priv) \ + ((p_priv)->tx_data_offset + DPAA2_ETH_TX_BUF_ALIGN) + +/* rx_extra_head prevents reallocations in L3 processing. */ +#define DPAA2_ETH_SKB_SIZE \ + (DPAA2_ETH_RX_BUF_SIZE + \ + SKB_DATA_ALIGN(sizeof(struct skb_shared_info))) + +/* Hardware only sees DPAA2_ETH_RX_BUF_SIZE, but we need to allocate ingress + * buffers large enough to allow building an skb around them and also account + * for alignment restrictions. + */ +#define DPAA2_ETH_BUF_RAW_SIZE(p_priv) \ + (DPAA2_ETH_SKB_SIZE + \ + (p_priv)->rx_buf_align) + +/* PTP nominal frequency 1GHz */ +#define DPAA2_PTP_NOMINAL_FREQ_PERIOD_NS 1 + +/* Leave enough extra space in the headroom to make sure the skb is + * not realloc'd in forwarding scenarios. + */ +#define DPAA2_ETH_RX_HEAD_ROOM 192 + +/* We are accommodating a skb backpointer and some S/G info + * in the frame's software annotation. The hardware + * options are either 0 or 64, so we choose the latter. + */ +#define DPAA2_ETH_SWA_SIZE 64 + +/* Must keep this struct smaller than DPAA2_ETH_SWA_SIZE */ +struct dpaa2_eth_swa { + struct sk_buff *skb; + struct scatterlist *scl; + int num_sg; + int num_dma_bufs; +}; + +/* Annotation valid bits in FD FRC */ +#define DPAA2_FD_FRC_FASV 0x8000 +#define DPAA2_FD_FRC_FAEADV 0x4000 +#define DPAA2_FD_FRC_FAPRV 0x2000 +#define DPAA2_FD_FRC_FAIADV 0x1000 +#define DPAA2_FD_FRC_FASWOV 0x0800 +#define DPAA2_FD_FRC_FAICFDV 0x0400 + +/* Error bits in FD CTRL */ +#define DPAA2_FD_CTRL_UFD 0x00000004 +#define DPAA2_FD_CTRL_SBE 0x00000008 +#define DPAA2_FD_CTRL_FSE 0x00000010 +#define DPAA2_FD_CTRL_FAERR 0x00000020 + +#define DPAA2_FD_RX_ERR_MASK (DPAA2_FD_CTRL_SBE | \ + DPAA2_FD_CTRL_FAERR) + +#define DPAA2_FD_TX_ERR_MASK (DPAA2_FD_CTRL_UFD | \ + DPAA2_FD_CTRL_SBE | \ + DPAA2_FD_CTRL_FSE | \ + DPAA2_FD_CTRL_FAERR) + +/* Annotation bits in FD CTRL */ +#define DPAA2_FD_CTRL_ASAL 0x00020000 /* ASAL = 128 */ +#define DPAA2_FD_CTRL_PTA 0x00800000 +#define DPAA2_FD_CTRL_PTV1 0x00400000 + +/* Size of hardware annotation area based on the current buffer layout + * configuration + */ +#define DPAA2_ETH_RX_HWA_SIZE 64 +#define DPAA2_ETH_TX_HWA_SIZE 128 + +/* Frame annotation status */ +struct dpaa2_fas { + u8 reserved; + u8 ppid; + __le16 ifpid; + __le32 status; +} __packed; + +/* Frame annotation status word is located in the first 8 bytes + * of the buffer's hardware annotation area + */ +#define DPAA2_FAS_OFFSET 0 +#define DPAA2_FAS_SIZE (sizeof(struct dpaa2_fas)) + +/* Timestamp is located in the next 8 bytes of the buffer's + * hardware annotation area + */ +#define DPAA2_TS_OFFSET 0x8 + +/* Frame annotation egress action descriptor */ +#define DPAA2_FAEAD_OFFSET 0x58 + +struct dpaa2_faead { + __le32 conf_fqid; + __le32 ctrl; +}; + +#define DPAA2_FAEAD_A2V 0x20000000 +#define DPAA2_FAEAD_UPDV 0x00001000 +#define DPAA2_FAEAD_UPD 0x00000010 + +/* accessors for the hardware annotation fields that we use */ +#define dpaa2_eth_get_hwa(buf_addr) \ + ((void *)(buf_addr) + DPAA2_ETH_SWA_SIZE) + +#define dpaa2_eth_get_fas(buf_addr) \ + (struct dpaa2_fas *)(dpaa2_eth_get_hwa(buf_addr) + DPAA2_FAS_OFFSET) + +#define dpaa2_eth_get_ts(buf_addr) \ + (u64 *)(dpaa2_eth_get_hwa(buf_addr) + DPAA2_TS_OFFSET) + +#define dpaa2_eth_get_faead(buf_addr) \ + (struct dpaa2_faead *)(dpaa2_eth_get_hwa(buf_addr) + DPAA2_FAEAD_OFFSET) + +/* Error and status bits in the frame annotation status word */ +/* Debug frame, otherwise supposed to be discarded */ +#define DPAA2_FAS_DISC 0x80000000 +/* MACSEC frame */ +#define DPAA2_FAS_MS 0x40000000 +#define DPAA2_FAS_PTP 0x08000000 +/* Ethernet multicast frame */ +#define DPAA2_FAS_MC 0x04000000 +/* Ethernet broadcast frame */ +#define DPAA2_FAS_BC 0x02000000 +#define DPAA2_FAS_KSE 0x00040000 +#define DPAA2_FAS_EOFHE 0x00020000 +#define DPAA2_FAS_MNLE 0x00010000 +#define DPAA2_FAS_TIDE 0x00008000 +#define DPAA2_FAS_PIEE 0x00004000 +/* Frame length error */ +#define DPAA2_FAS_FLE 0x00002000 +/* Frame physical error */ +#define DPAA2_FAS_FPE 0x00001000 +#define DPAA2_FAS_PTE 0x00000080 +#define DPAA2_FAS_ISP 0x00000040 +#define DPAA2_FAS_PHE 0x00000020 +#define DPAA2_FAS_BLE 0x00000010 +/* L3 csum validation performed */ +#define DPAA2_FAS_L3CV 0x00000008 +/* L3 csum error */ +#define DPAA2_FAS_L3CE 0x00000004 +/* L4 csum validation performed */ +#define DPAA2_FAS_L4CV 0x00000002 +/* L4 csum error */ +#define DPAA2_FAS_L4CE 0x00000001 +/* Possible errors on the ingress path */ +#define DPAA2_FAS_RX_ERR_MASK ((DPAA2_FAS_KSE) | \ + (DPAA2_FAS_EOFHE) | \ + (DPAA2_FAS_MNLE) | \ + (DPAA2_FAS_TIDE) | \ + (DPAA2_FAS_PIEE) | \ + (DPAA2_FAS_FLE) | \ + (DPAA2_FAS_FPE) | \ + (DPAA2_FAS_PTE) | \ + (DPAA2_FAS_ISP) | \ + (DPAA2_FAS_PHE) | \ + (DPAA2_FAS_BLE) | \ + (DPAA2_FAS_L3CE) | \ + (DPAA2_FAS_L4CE)) +/* Tx errors */ +#define DPAA2_FAS_TX_ERR_MASK ((DPAA2_FAS_KSE) | \ + (DPAA2_FAS_EOFHE) | \ + (DPAA2_FAS_MNLE) | \ + (DPAA2_FAS_TIDE)) + +/* Time in milliseconds between link state updates */ +#define DPAA2_ETH_LINK_STATE_REFRESH 1000 + +/* Number of times to retry a frame enqueue before giving up. + * Value determined empirically, in order to minimize the number + * of frames dropped on Tx + */ +#define DPAA2_ETH_ENQUEUE_RETRIES 10 + +/* Tx congestion entry & exit thresholds, in number of bytes. + * We allow a maximum of 512KB worth of frames pending processing on the Tx + * queues of an interface + */ +#define DPAA2_ETH_TX_CONG_ENTRY_THRESH (512 * 1024) +#define DPAA2_ETH_TX_CONG_EXIT_THRESH (DPAA2_ETH_TX_CONG_ENTRY_THRESH * 9/10) + +/* Driver statistics, other than those in struct rtnl_link_stats64. + * These are usually collected per-CPU and aggregated by ethtool. + */ +struct dpaa2_eth_drv_stats { + __u64 tx_conf_frames; + __u64 tx_conf_bytes; + __u64 tx_sg_frames; + __u64 tx_sg_bytes; + __u64 rx_sg_frames; + __u64 rx_sg_bytes; + /* Enqueues retried due to portal busy */ + __u64 tx_portal_busy; +}; + +/* Per-FQ statistics */ +struct dpaa2_eth_fq_stats { + /* Number of frames received on this queue */ + __u64 frames; + /* Number of times this queue entered congestion */ + __u64 congestion_entry; +}; + +/* Per-channel statistics */ +struct dpaa2_eth_ch_stats { + /* Volatile dequeues retried due to portal busy */ + __u64 dequeue_portal_busy; + /* Number of CDANs; useful to estimate avg NAPI len */ + __u64 cdan; + /* Number of frames received on queues from this channel */ + __u64 frames; + /* Pull errors */ + __u64 pull_err; +}; + +/* Maximum number of queues associated with a DPNI */ +#define DPAA2_ETH_MAX_RX_QUEUES 16 +#define DPAA2_ETH_MAX_TX_QUEUES NR_CPUS +#define DPAA2_ETH_MAX_RX_ERR_QUEUES 1 +#define DPAA2_ETH_MAX_QUEUES (DPAA2_ETH_MAX_RX_QUEUES + \ + DPAA2_ETH_MAX_TX_QUEUES + \ + DPAA2_ETH_MAX_RX_ERR_QUEUES) + +#define DPAA2_ETH_MAX_DPCONS NR_CPUS + +enum dpaa2_eth_fq_type { + DPAA2_RX_FQ = 0, + DPAA2_TX_CONF_FQ, + DPAA2_RX_ERR_FQ +}; + +struct dpaa2_eth_priv; + +struct dpaa2_eth_fq { + u32 fqid; + u32 tx_qdbin; + u16 flowid; + int target_cpu; + struct dpaa2_eth_channel *channel; + enum dpaa2_eth_fq_type type; + + void (*consume)(struct dpaa2_eth_priv *, + struct dpaa2_eth_channel *, + const struct dpaa2_fd *, + struct napi_struct *, + u16 queue_id); + struct dpaa2_eth_fq_stats stats; +}; + +struct dpaa2_eth_channel { + struct dpaa2_io_notification_ctx nctx; + struct fsl_mc_device *dpcon; + int dpcon_id; + int ch_id; + int dpio_id; + struct napi_struct napi; + struct dpaa2_io_store *store; + struct dpaa2_eth_priv *priv; + int buf_count; + struct dpaa2_eth_ch_stats stats; +}; + +struct dpaa2_eth_cls_rule { + struct ethtool_rx_flow_spec fs; + bool in_use; +}; + +struct dpaa2_eth_hash_fields { + u64 rxnfc_field; + enum net_prot cls_prot; + int cls_field; + int offset; + int size; +}; + +/* Driver private data */ +struct dpaa2_eth_priv { + struct net_device *net_dev; + + /* Standard statistics */ + struct rtnl_link_stats64 __percpu *percpu_stats; + /* Extra stats, in addition to the ones known by the kernel */ + struct dpaa2_eth_drv_stats __percpu *percpu_extras; + + bool ts_tx_en; /* Tx timestamping enabled */ + bool ts_rx_en; /* Rx timestamping enabled */ + + u16 tx_data_offset; + u16 rx_buf_align; + + u16 bpid; + u16 tx_qdid; + + int tx_pause_frames; + int num_bufs; + int refill_thresh; + + /* Tx congestion notifications are written here */ + void *cscn_mem; + dma_addr_t cscn_dma; + + u8 num_fqs; + /* Tx queues are at the beginning of the array */ + struct dpaa2_eth_fq fq[DPAA2_ETH_MAX_QUEUES]; + + u8 num_channels; + struct dpaa2_eth_channel *channel[DPAA2_ETH_MAX_DPCONS]; + + int dpni_id; + struct dpni_attr dpni_attrs; + struct fsl_mc_device *dpbp_dev; + + struct fsl_mc_io *mc_io; + /* SysFS-controlled affinity mask for TxConf FQs */ + struct cpumask txconf_cpumask; + /* Cores which have an affine DPIO/DPCON. + * This is the cpu set on which Rx frames are processed; + * Tx confirmation frames are processed on a subset of this, + * depending on user settings. + */ + struct cpumask dpio_cpumask; + + u16 mc_token; + + struct dpni_link_state link_state; + bool do_link_poll; + struct task_struct *poll_thread; + + struct dpaa2_eth_hash_fields *hash_fields; + u8 num_hash_fields; + /* enabled ethtool hashing bits */ + u64 rx_flow_hash; + + /* array of classification rules */ + struct dpaa2_eth_cls_rule *cls_rule; + + struct dpni_tx_shaping_cfg shaping_cfg; +}; + +#define dpaa2_eth_hash_enabled(priv) \ + ((priv)->dpni_attrs.num_queues > 1) + +#define dpaa2_eth_fs_enabled(priv) \ + (!((priv)->dpni_attrs.options & DPNI_OPT_NO_FS)) + +#define dpaa2_eth_fs_mask_enabled(priv) \ + ((priv)->dpni_attrs.options & DPNI_OPT_HAS_KEY_MASKING) + +#define dpaa2_eth_fs_count(priv) \ + ((priv)->dpni_attrs.fs_entries) + +/* size of DMA memory used to pass configuration to classifier, in bytes */ +#define DPAA2_CLASSIFIER_DMA_SIZE 256 + +extern const struct ethtool_ops dpaa2_ethtool_ops; +extern const char dpaa2_eth_drv_version[]; + +static inline int dpaa2_eth_queue_count(struct dpaa2_eth_priv *priv) +{ + return priv->dpni_attrs.num_queues; +} + +void check_cls_support(struct dpaa2_eth_priv *priv); + +int setup_fqs_taildrop(struct dpaa2_eth_priv *priv, bool enable); +#endif /* __DPAA2_H */ diff --git a/drivers/staging/fsl-dpaa2/ethernet/dpaa2-ethtool.c b/drivers/staging/fsl-dpaa2/ethernet/dpaa2-ethtool.c new file mode 100644 index 0000000..4ab6169 --- /dev/null +++ b/drivers/staging/fsl-dpaa2/ethernet/dpaa2-ethtool.c @@ -0,0 +1,801 @@ +/* Copyright 2014-2015 Freescale Semiconductor Inc. + * + * 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 Freescale Semiconductor nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * + * ALTERNATIVELY, this software may be distributed under the terms of the + * GNU General Public License ("GPL") as published by the Free Software + * Foundation, either version 2 of that License or (at your option) any + * later version. + * + * THIS SOFTWARE IS PROVIDED BY Freescale Semiconductor ``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 Freescale Semiconductor 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 "dpni.h" /* DPNI_LINK_OPT_* */ +#include "dpaa2-eth.h" + +/* To be kept in sync with dpni_statistics */ +static char dpaa2_ethtool_stats[][ETH_GSTRING_LEN] = { + "rx frames", + "rx bytes", + "rx mcast frames", + "rx mcast bytes", + "rx bcast frames", + "rx bcast bytes", + "tx frames", + "tx bytes", + "tx mcast frames", + "tx mcast bytes", + "tx bcast frames", + "tx bcast bytes", + "rx filtered frames", + "rx discarded frames", + "rx nobuffer discards", + "tx discarded frames", + "tx confirmed frames", +}; + +#define DPAA2_ETH_NUM_STATS ARRAY_SIZE(dpaa2_ethtool_stats) + +/* To be kept in sync with 'struct dpaa2_eth_drv_stats' */ +static char dpaa2_ethtool_extras[][ETH_GSTRING_LEN] = { + /* per-cpu stats */ + + "tx conf frames", + "tx conf bytes", + "tx sg frames", + "tx sg bytes", + "rx sg frames", + "rx sg bytes", + /* how many times we had to retry the enqueue command */ + "enqueue portal busy", + + /* Channel stats */ + /* How many times we had to retry the volatile dequeue command */ + "dequeue portal busy", + "channel pull errors", + /* Number of notifications received */ + "cdan", + "tx congestion state", +}; + +#define DPAA2_ETH_NUM_EXTRA_STATS ARRAY_SIZE(dpaa2_ethtool_extras) + +static void dpaa2_eth_get_drvinfo(struct net_device *net_dev, + struct ethtool_drvinfo *drvinfo) +{ + strlcpy(drvinfo->driver, KBUILD_MODNAME, sizeof(drvinfo->driver)); + strlcpy(drvinfo->version, dpaa2_eth_drv_version, + sizeof(drvinfo->version)); + strlcpy(drvinfo->fw_version, "N/A", sizeof(drvinfo->fw_version)); + strlcpy(drvinfo->bus_info, dev_name(net_dev->dev.parent->parent), + sizeof(drvinfo->bus_info)); +} + +static int dpaa2_eth_get_settings(struct net_device *net_dev, + struct ethtool_cmd *cmd) +{ + struct dpni_link_state state = {0}; + int err = 0; + struct dpaa2_eth_priv *priv = netdev_priv(net_dev); + + err = dpni_get_link_state(priv->mc_io, 0, priv->mc_token, &state); + if (err) { + netdev_err(net_dev, "ERROR %d getting link state", err); + goto out; + } + + /* At the moment, we have no way of interrogating the DPMAC + * from the DPNI side - and for that matter there may exist + * no DPMAC at all. So for now we just don't report anything + * beyond the DPNI attributes. + */ + if (state.options & DPNI_LINK_OPT_AUTONEG) + cmd->autoneg = AUTONEG_ENABLE; + if (!(state.options & DPNI_LINK_OPT_HALF_DUPLEX)) + cmd->duplex = DUPLEX_FULL; + ethtool_cmd_speed_set(cmd, state.rate); + +out: + return err; +} + +static int dpaa2_eth_set_settings(struct net_device *net_dev, + struct ethtool_cmd *cmd) +{ + struct dpaa2_eth_priv *priv = netdev_priv(net_dev); + struct dpni_link_state state = {0}; + struct dpni_link_cfg cfg = {0}; + int err = 0; + + netdev_dbg(net_dev, "Setting link parameters..."); + + /* Need to interrogate on link state to get flow control params */ + err = dpni_get_link_state(priv->mc_io, 0, priv->mc_token, &state); + if (err) { + netdev_err(net_dev, "ERROR %d getting link state", err); + goto out; + } + + cfg.options = state.options; + cfg.rate = ethtool_cmd_speed(cmd); + if (cmd->autoneg == AUTONEG_ENABLE) + cfg.options |= DPNI_LINK_OPT_AUTONEG; + else + cfg.options &= ~DPNI_LINK_OPT_AUTONEG; + if (cmd->duplex == DUPLEX_HALF) + cfg.options |= DPNI_LINK_OPT_HALF_DUPLEX; + else + cfg.options &= ~DPNI_LINK_OPT_HALF_DUPLEX; + + err = dpni_set_link_cfg(priv->mc_io, 0, priv->mc_token, &cfg); + if (err) + /* ethtool will be loud enough if we return an error; no point + * in putting our own error message on the console by default + */ + netdev_dbg(net_dev, "ERROR %d setting link cfg", err); + +out: + return err; +} + +static void dpaa2_eth_get_pauseparam(struct net_device *net_dev, + struct ethtool_pauseparam *pause) +{ + struct dpaa2_eth_priv *priv = netdev_priv(net_dev); + struct dpni_link_state state = {0}; + int err; + + err = dpni_get_link_state(priv->mc_io, 0, priv->mc_token, &state); + if (err) + netdev_dbg(net_dev, "ERROR %d getting link state", err); + + /* for now, pause frames autonegotiation is not separate */ + pause->autoneg = !!(state.options & DPNI_LINK_OPT_AUTONEG); + pause->rx_pause = !!(state.options & DPNI_LINK_OPT_PAUSE); + pause->tx_pause = pause->rx_pause ^ + !!(state.options & DPNI_LINK_OPT_ASYM_PAUSE); +} + +static int dpaa2_eth_set_pauseparam(struct net_device *net_dev, + struct ethtool_pauseparam *pause) +{ + struct dpaa2_eth_priv *priv = netdev_priv(net_dev); + struct dpni_link_state state = {0}; + struct dpni_link_cfg cfg = {0}; + u32 current_tx_pause; + int err = 0; + + err = dpni_get_link_state(priv->mc_io, 0, priv->mc_token, &state); + if (err) { + netdev_dbg(net_dev, "ERROR %d getting link state", err); + goto out; + } + + cfg.rate = state.rate; + cfg.options = state.options; + current_tx_pause = !!(cfg.options & DPNI_LINK_OPT_PAUSE) ^ + !!(cfg.options & DPNI_LINK_OPT_ASYM_PAUSE); + + if (pause->autoneg != !!(state.options & DPNI_LINK_OPT_AUTONEG)) + netdev_warn(net_dev, + "WARN: Can't change pause frames autoneg separately\n"); + + if (pause->rx_pause) + cfg.options |= DPNI_LINK_OPT_PAUSE; + else + cfg.options &= ~DPNI_LINK_OPT_PAUSE; + + if (pause->rx_pause ^ pause->tx_pause) + cfg.options |= DPNI_LINK_OPT_ASYM_PAUSE; + else + cfg.options &= ~DPNI_LINK_OPT_ASYM_PAUSE; + + err = dpni_set_link_cfg(priv->mc_io, 0, priv->mc_token, &cfg); + if (err) { + /* ethtool will be loud enough if we return an error; no point + * in putting our own error message on the console by default + */ + netdev_dbg(net_dev, "ERROR %d setting link cfg", err); + goto out; + } + + /* Enable / disable taildrops if Tx pause frames have changed */ + if (current_tx_pause == pause->tx_pause) + goto out; + + err = setup_fqs_taildrop(priv, !pause->tx_pause); + if (err) + netdev_dbg(net_dev, "ERROR %d configuring taildrop", err); + + priv->tx_pause_frames = pause->tx_pause; +out: + return err; +} + +static void dpaa2_eth_get_strings(struct net_device *netdev, u32 stringset, + u8 *data) +{ + u8 *p = data; + int i; + + switch (stringset) { + case ETH_SS_STATS: + for (i = 0; i < DPAA2_ETH_NUM_STATS; i++) { + strlcpy(p, dpaa2_ethtool_stats[i], ETH_GSTRING_LEN); + p += ETH_GSTRING_LEN; + } + for (i = 0; i < DPAA2_ETH_NUM_EXTRA_STATS; i++) { + strlcpy(p, dpaa2_ethtool_extras[i], ETH_GSTRING_LEN); + p += ETH_GSTRING_LEN; + } + break; + } +} + +static int dpaa2_eth_get_sset_count(struct net_device *net_dev, int sset) +{ + switch (sset) { + case ETH_SS_STATS: /* ethtool_get_stats(), ethtool_get_drvinfo() */ + return DPAA2_ETH_NUM_STATS + DPAA2_ETH_NUM_EXTRA_STATS; + default: + return -EOPNOTSUPP; + } +} + +/** Fill in hardware counters, as returned by MC. + */ +static void dpaa2_eth_get_ethtool_stats(struct net_device *net_dev, + struct ethtool_stats *stats, + u64 *data) +{ + int i = 0; /* Current index in the data array */ + int j = 0, k, err; + union dpni_statistics dpni_stats; + + u64 cdan = 0; + u64 portal_busy = 0, pull_err = 0; + struct dpaa2_eth_priv *priv = netdev_priv(net_dev); + struct dpaa2_eth_drv_stats *extras; + struct dpaa2_eth_ch_stats *ch_stats; + + memset(data, 0, + sizeof(u64) * (DPAA2_ETH_NUM_STATS + DPAA2_ETH_NUM_EXTRA_STATS)); + + /* Print standard counters, from DPNI statistics */ + for (j = 0; j <= 2; j++) { + err = dpni_get_statistics(priv->mc_io, 0, priv->mc_token, + j, &dpni_stats); + if (err != 0) + netdev_warn(net_dev, "Err %d getting DPNI stats page %d", + err, j); + + switch (j) { + case 0: + *(data + i++) = dpni_stats.page_0.ingress_all_frames; + *(data + i++) = dpni_stats.page_0.ingress_all_bytes; + *(data + i++) = dpni_stats.page_0.ingress_multicast_frames; + *(data + i++) = dpni_stats.page_0.ingress_multicast_bytes; + *(data + i++) = dpni_stats.page_0.ingress_broadcast_frames; + *(data + i++) = dpni_stats.page_0.ingress_broadcast_bytes; + break; + case 1: + *(data + i++) = dpni_stats.page_1.egress_all_frames; + *(data + i++) = dpni_stats.page_1.egress_all_bytes; + *(data + i++) = dpni_stats.page_1.egress_multicast_frames; + *(data + i++) = dpni_stats.page_1.egress_multicast_bytes; + *(data + i++) = dpni_stats.page_1.egress_broadcast_frames; + *(data + i++) = dpni_stats.page_1.egress_broadcast_bytes; + break; + case 2: + *(data + i++) = dpni_stats.page_2.ingress_filtered_frames; + *(data + i++) = dpni_stats.page_2.ingress_discarded_frames; + *(data + i++) = dpni_stats.page_2.ingress_nobuffer_discards; + *(data + i++) = dpni_stats.page_2.egress_discarded_frames; + *(data + i++) = dpni_stats.page_2.egress_confirmed_frames; + break; + default: + break; + } + } + + /* Print per-cpu extra stats */ + for_each_online_cpu(k) { + extras = per_cpu_ptr(priv->percpu_extras, k); + for (j = 0; j < sizeof(*extras) / sizeof(__u64); j++) + *((__u64 *)data + i + j) += *((__u64 *)extras + j); + } + + i += j; + + /* We may be using fewer DPIOs than actual CPUs */ + for (j = 0; j < priv->num_channels; j++) { + ch_stats = &priv->channel[j]->stats; + cdan += ch_stats->cdan; + portal_busy += ch_stats->dequeue_portal_busy; + pull_err += ch_stats->pull_err; + } + + *(data + i++) = portal_busy; + *(data + i++) = pull_err; + *(data + i++) = cdan; + + *(data + i++) = dpaa2_cscn_state_congested(priv->cscn_mem); +} + +static int cls_key_off(struct dpaa2_eth_priv *priv, int prot, int field) +{ + int i, off = 0; + + for (i = 0; i < priv->num_hash_fields; i++) { + if (priv->hash_fields[i].cls_prot == prot && + priv->hash_fields[i].cls_field == field) + return off; + off += priv->hash_fields[i].size; + } + + return -1; +} + +static u8 cls_key_size(struct dpaa2_eth_priv *priv) +{ + u8 i, size = 0; + + for (i = 0; i < priv->num_hash_fields; i++) + size += priv->hash_fields[i].size; + + return size; +} + +void check_cls_support(struct dpaa2_eth_priv *priv) +{ + u8 key_size = cls_key_size(priv); + struct device *dev = priv->net_dev->dev.parent; + + if (dpaa2_eth_hash_enabled(priv)) { + if (priv->dpni_attrs.fs_key_size < key_size) { + dev_info(dev, "max_dist_key_size = %d, expected %d. Hashing and steering are disabled\n", + priv->dpni_attrs.fs_key_size, + key_size); + goto disable_fs; + } + if (priv->num_hash_fields > DPKG_MAX_NUM_OF_EXTRACTS) { + dev_info(dev, "Too many key fields (max = %d). Hashing and steering are disabled\n", + DPKG_MAX_NUM_OF_EXTRACTS); + goto disable_fs; + } + } + + if (dpaa2_eth_fs_enabled(priv)) { + if (!dpaa2_eth_hash_enabled(priv)) { + dev_info(dev, "Insufficient queues. Steering is disabled\n"); + goto disable_fs; + } + + if (!dpaa2_eth_fs_mask_enabled(priv)) { + dev_info(dev, "Key masks not supported. Steering is disabled\n"); + goto disable_fs; + } + } + + return; + +disable_fs: + priv->dpni_attrs.options |= DPNI_OPT_NO_FS; + priv->dpni_attrs.options &= ~DPNI_OPT_HAS_KEY_MASKING; +} + +static int prep_l4_rule(struct dpaa2_eth_priv *priv, + struct ethtool_tcpip4_spec *l4_value, + struct ethtool_tcpip4_spec *l4_mask, + void *key, void *mask, u8 l4_proto) +{ + int offset; + + if (l4_mask->tos) { + netdev_err(priv->net_dev, "ToS is not supported for IPv4 L4\n"); + return -EOPNOTSUPP; + } + + if (l4_mask->ip4src) { + offset = cls_key_off(priv, NET_PROT_IP, NH_FLD_IP_SRC); + *(u32 *)(key + offset) = l4_value->ip4src; + *(u32 *)(mask + offset) = l4_mask->ip4src; + } + + if (l4_mask->ip4dst) { + offset = cls_key_off(priv, NET_PROT_IP, NH_FLD_IP_DST); + *(u32 *)(key + offset) = l4_value->ip4dst; + *(u32 *)(mask + offset) = l4_mask->ip4dst; + } + + if (l4_mask->psrc) { + offset = cls_key_off(priv, NET_PROT_UDP, NH_FLD_UDP_PORT_SRC); + *(u32 *)(key + offset) = l4_value->psrc; + *(u32 *)(mask + offset) = l4_mask->psrc; + } + + if (l4_mask->pdst) { + offset = cls_key_off(priv, NET_PROT_UDP, NH_FLD_UDP_PORT_DST); + *(u32 *)(key + offset) = l4_value->pdst; + *(u32 *)(mask + offset) = l4_mask->pdst; + } + + /* Only apply the rule for the user-specified L4 protocol + * and if ethertype matches IPv4 + */ + offset = cls_key_off(priv, NET_PROT_ETH, NH_FLD_ETH_TYPE); + *(u16 *)(key + offset) = htons(ETH_P_IP); + *(u16 *)(mask + offset) = 0xFFFF; + + offset = cls_key_off(priv, NET_PROT_IP, NH_FLD_IP_PROTO); + *(u8 *)(key + offset) = l4_proto; + *(u8 *)(mask + offset) = 0xFF; + + /* TODO: check IP version */ + + return 0; +} + +static int prep_eth_rule(struct dpaa2_eth_priv *priv, + struct ethhdr *eth_value, struct ethhdr *eth_mask, + void *key, void *mask) +{ + int offset; + + if (eth_mask->h_proto) { + netdev_err(priv->net_dev, "Ethertype is not supported!\n"); + return -EOPNOTSUPP; + } + + if (!is_zero_ether_addr(eth_mask->h_source)) { + offset = cls_key_off(priv, NET_PROT_ETH, NH_FLD_ETH_SA); + ether_addr_copy(key + offset, eth_value->h_source); + ether_addr_copy(mask + offset, eth_mask->h_source); + } + + if (!is_zero_ether_addr(eth_mask->h_dest)) { + offset = cls_key_off(priv, NET_PROT_ETH, NH_FLD_ETH_DA); + ether_addr_copy(key + offset, eth_value->h_dest); + ether_addr_copy(mask + offset, eth_mask->h_dest); + } + + return 0; +} + +static int prep_user_ip_rule(struct dpaa2_eth_priv *priv, + struct ethtool_usrip4_spec *uip_value, + struct ethtool_usrip4_spec *uip_mask, + void *key, void *mask) +{ + int offset; + + if (uip_mask->tos) + return -EOPNOTSUPP; + + if (uip_mask->ip4src) { + offset = cls_key_off(priv, NET_PROT_IP, NH_FLD_IP_SRC); + *(u32 *)(key + offset) = uip_value->ip4src; + *(u32 *)(mask + offset) = uip_mask->ip4src; + } + + if (uip_mask->ip4dst) { + offset = cls_key_off(priv, NET_PROT_IP, NH_FLD_IP_DST); + *(u32 *)(key + offset) = uip_value->ip4dst; + *(u32 *)(mask + offset) = uip_mask->ip4dst; + } + + if (uip_mask->proto) { + offset = cls_key_off(priv, NET_PROT_IP, NH_FLD_IP_PROTO); + *(u32 *)(key + offset) = uip_value->proto; + *(u32 *)(mask + offset) = uip_mask->proto; + } + if (uip_mask->l4_4_bytes) { + offset = cls_key_off(priv, NET_PROT_UDP, NH_FLD_UDP_PORT_SRC); + *(u16 *)(key + offset) = uip_value->l4_4_bytes << 16; + *(u16 *)(mask + offset) = uip_mask->l4_4_bytes << 16; + + offset = cls_key_off(priv, NET_PROT_UDP, NH_FLD_UDP_PORT_DST); + *(u16 *)(key + offset) = uip_value->l4_4_bytes & 0xFFFF; + *(u16 *)(mask + offset) = uip_mask->l4_4_bytes & 0xFFFF; + } + + /* Ethertype must be IP */ + offset = cls_key_off(priv, NET_PROT_ETH, NH_FLD_ETH_TYPE); + *(u16 *)(key + offset) = htons(ETH_P_IP); + *(u16 *)(mask + offset) = 0xFFFF; + + return 0; +} + +static int prep_ext_rule(struct dpaa2_eth_priv *priv, + struct ethtool_flow_ext *ext_value, + struct ethtool_flow_ext *ext_mask, + void *key, void *mask) +{ + int offset; + + if (ext_mask->vlan_etype) + return -EOPNOTSUPP; + + if (ext_mask->vlan_tci) { + offset = cls_key_off(priv, NET_PROT_VLAN, NH_FLD_VLAN_TCI); + *(u16 *)(key + offset) = ext_value->vlan_tci; + *(u16 *)(mask + offset) = ext_mask->vlan_tci; + } + + return 0; +} + +static int prep_mac_ext_rule(struct dpaa2_eth_priv *priv, + struct ethtool_flow_ext *ext_value, + struct ethtool_flow_ext *ext_mask, + void *key, void *mask) +{ + int offset; + + if (!is_zero_ether_addr(ext_mask->h_dest)) { + offset = cls_key_off(priv, NET_PROT_ETH, NH_FLD_ETH_DA); + ether_addr_copy(key + offset, ext_value->h_dest); + ether_addr_copy(mask + offset, ext_mask->h_dest); + } + + return 0; +} + +static int prep_cls_rule(struct net_device *net_dev, + struct ethtool_rx_flow_spec *fs, + void *key) +{ + struct dpaa2_eth_priv *priv = netdev_priv(net_dev); + const u8 key_size = cls_key_size(priv); + void *msk = key + key_size; + int err; + + memset(key, 0, key_size * 2); + + switch (fs->flow_type & 0xff) { + case TCP_V4_FLOW: + err = prep_l4_rule(priv, &fs->h_u.tcp_ip4_spec, + &fs->m_u.tcp_ip4_spec, key, msk, + IPPROTO_TCP); + break; + case UDP_V4_FLOW: + err = prep_l4_rule(priv, &fs->h_u.udp_ip4_spec, + &fs->m_u.udp_ip4_spec, key, msk, + IPPROTO_UDP); + break; + case SCTP_V4_FLOW: + err = prep_l4_rule(priv, &fs->h_u.sctp_ip4_spec, + &fs->m_u.sctp_ip4_spec, key, msk, + IPPROTO_SCTP); + break; + case ETHER_FLOW: + err = prep_eth_rule(priv, &fs->h_u.ether_spec, + &fs->m_u.ether_spec, key, msk); + break; + case IP_USER_FLOW: + err = prep_user_ip_rule(priv, &fs->h_u.usr_ip4_spec, + &fs->m_u.usr_ip4_spec, key, msk); + break; + default: + /* TODO: AH, ESP */ + return -EOPNOTSUPP; + } + if (err) + return err; + + if (fs->flow_type & FLOW_EXT) { + err = prep_ext_rule(priv, &fs->h_ext, &fs->m_ext, key, msk); + if (err) + return err; + } + + if (fs->flow_type & FLOW_MAC_EXT) { + err = prep_mac_ext_rule(priv, &fs->h_ext, &fs->m_ext, key, msk); + if (err) + return err; + } + + return 0; +} + +static int do_cls(struct net_device *net_dev, + struct ethtool_rx_flow_spec *fs, + bool add) +{ + struct dpaa2_eth_priv *priv = netdev_priv(net_dev); + struct device *dev = net_dev->dev.parent; + const int rule_cnt = dpaa2_eth_fs_count(priv); + struct dpni_rule_cfg rule_cfg; + struct dpni_fs_action_cfg fs_act = { 0 }; + void *dma_mem; + int err = 0; + + if (!dpaa2_eth_fs_enabled(priv)) { + netdev_err(net_dev, "dev does not support steering!\n"); + /* dev doesn't support steering */ + return -EOPNOTSUPP; + } + + if ((fs->ring_cookie != RX_CLS_FLOW_DISC && + fs->ring_cookie >= dpaa2_eth_queue_count(priv)) || + fs->location >= rule_cnt) + return -EINVAL; + + memset(&rule_cfg, 0, sizeof(rule_cfg)); + rule_cfg.key_size = cls_key_size(priv); + + /* allocate twice the key size, for the actual key and for mask */ + dma_mem = kzalloc(rule_cfg.key_size * 2, GFP_DMA | GFP_KERNEL); + if (!dma_mem) + return -ENOMEM; + + err = prep_cls_rule(net_dev, fs, dma_mem); + if (err) + goto err_free_mem; + + rule_cfg.key_iova = dma_map_single(dev, dma_mem, + rule_cfg.key_size * 2, + DMA_TO_DEVICE); + + rule_cfg.mask_iova = rule_cfg.key_iova + rule_cfg.key_size; + + if (fs->ring_cookie == RX_CLS_FLOW_DISC) + fs_act.options |= DPNI_FS_OPT_DISCARD; + else + fs_act.flow_id = fs->ring_cookie; + + if (add) + err = dpni_add_fs_entry(priv->mc_io, 0, priv->mc_token, + 0, fs->location, &rule_cfg, &fs_act); + else + err = dpni_remove_fs_entry(priv->mc_io, 0, priv->mc_token, + 0, &rule_cfg); + + dma_unmap_single(dev, rule_cfg.key_iova, + rule_cfg.key_size * 2, DMA_TO_DEVICE); + + if (err) + netdev_err(net_dev, "dpaa2_add/remove_cls() error %d\n", err); + +err_free_mem: + kfree(dma_mem); + + return err; +} + +static int add_cls(struct net_device *net_dev, + struct ethtool_rx_flow_spec *fs) +{ + struct dpaa2_eth_priv *priv = netdev_priv(net_dev); + int err; + + err = do_cls(net_dev, fs, true); + if (err) + return err; + + priv->cls_rule[fs->location].in_use = true; + priv->cls_rule[fs->location].fs = *fs; + + return 0; +} + +static int del_cls(struct net_device *net_dev, int location) +{ + struct dpaa2_eth_priv *priv = netdev_priv(net_dev); + int err; + + err = do_cls(net_dev, &priv->cls_rule[location].fs, false); + if (err) + return err; + + priv->cls_rule[location].in_use = false; + + return 0; +} + +static int dpaa2_eth_set_rxnfc(struct net_device *net_dev, + struct ethtool_rxnfc *rxnfc) +{ + int err = 0; + + switch (rxnfc->cmd) { + case ETHTOOL_SRXCLSRLINS: + err = add_cls(net_dev, &rxnfc->fs); + break; + + case ETHTOOL_SRXCLSRLDEL: + err = del_cls(net_dev, rxnfc->fs.location); + break; + + default: + err = -EOPNOTSUPP; + } + + return err; +} + +static int dpaa2_eth_get_rxnfc(struct net_device *net_dev, + struct ethtool_rxnfc *rxnfc, u32 *rule_locs) +{ + struct dpaa2_eth_priv *priv = netdev_priv(net_dev); + const int rule_cnt = dpaa2_eth_fs_count(priv); + int i, j; + + switch (rxnfc->cmd) { + case ETHTOOL_GRXFH: + /* we purposely ignore cmd->flow_type, because the hashing key + * is the same (and fixed) for all protocols + */ + rxnfc->data = priv->rx_flow_hash; + break; + + case ETHTOOL_GRXRINGS: + rxnfc->data = dpaa2_eth_queue_count(priv); + break; + + case ETHTOOL_GRXCLSRLCNT: + for (i = 0, rxnfc->rule_cnt = 0; i < rule_cnt; i++) + if (priv->cls_rule[i].in_use) + rxnfc->rule_cnt++; + rxnfc->data = rule_cnt; + break; + + case ETHTOOL_GRXCLSRULE: + if (!priv->cls_rule[rxnfc->fs.location].in_use) + return -EINVAL; + + rxnfc->fs = priv->cls_rule[rxnfc->fs.location].fs; + break; + + case ETHTOOL_GRXCLSRLALL: + for (i = 0, j = 0; i < rule_cnt; i++) { + if (!priv->cls_rule[i].in_use) + continue; + if (j == rxnfc->rule_cnt) + return -EMSGSIZE; + rule_locs[j++] = i; + } + rxnfc->rule_cnt = j; + rxnfc->data = rule_cnt; + break; + + default: + return -EOPNOTSUPP; + } + + return 0; +} + +const struct ethtool_ops dpaa2_ethtool_ops = { + .get_drvinfo = dpaa2_eth_get_drvinfo, + .get_link = ethtool_op_get_link, + .get_settings = dpaa2_eth_get_settings, + .set_settings = dpaa2_eth_set_settings, + .get_pauseparam = dpaa2_eth_get_pauseparam, + .set_pauseparam = dpaa2_eth_set_pauseparam, + .get_sset_count = dpaa2_eth_get_sset_count, + .get_ethtool_stats = dpaa2_eth_get_ethtool_stats, + .get_strings = dpaa2_eth_get_strings, + .get_rxnfc = dpaa2_eth_get_rxnfc, + .set_rxnfc = dpaa2_eth_set_rxnfc, +}; diff --git a/drivers/staging/fsl-dpaa2/ethernet/dpni-cmd.h b/drivers/staging/fsl-dpaa2/ethernet/dpni-cmd.h index 45794b1..fa353d7 100644 --- a/drivers/staging/fsl-dpaa2/ethernet/dpni-cmd.h +++ b/drivers/staging/fsl-dpaa2/ethernet/dpni-cmd.h @@ -33,8 +33,6 @@ #ifndef _FSL_DPNI_CMD_H #define _FSL_DPNI_CMD_H -#include "dpni.h" - /* DPNI Version */ #define DPNI_VER_MAJOR 7 #define DPNI_VER_MINOR 0 |