300 lines
8.7 KiB
Diff
300 lines
8.7 KiB
Diff
From fced03d891377fa04153fb0538bc8ca95ba05020 Mon Sep 17 00:00:00 2001
|
|
From: Madalin Bucur <madalin.bucur@nxp.com>
|
|
Date: Tue, 14 Nov 2017 08:12:12 +0200
|
|
Subject: [PATCH] dpaa_eth: workaround for ERR010022
|
|
|
|
On LS1043A SoC there is a known erratum ERR010022 that results in split DMA
|
|
transfers in the FMan under certain conditions. This, combined with a fixed
|
|
size FIFO of ongoing DMA transfers that may overflow when a split occurs,
|
|
results in the FMan stalling DMA transfers under high traffic. To avoid the
|
|
problem, one needs to prevent the DMA transfer splits to occur by preparing
|
|
the buffers as follows.
|
|
|
|
In order to prevent split transactions, all frames need to be aligned to 16
|
|
bytes and not cross 4K address boundaries. To allow Jumbo frames (up to
|
|
9.6K), all data must be aligned to 256 byes. This way, 4K boundary crossings
|
|
will not trigger any transaction splits.
|
|
|
|
The errata is prevented from manifesting by realigning all outgoing frames to
|
|
256 byte boundaries. In the process, all S/G frames are linearized.
|
|
|
|
Signed-off-by: Madalin Bucur <madalin.bucur@nxp.com>
|
|
Signed-off-by: Camelia Groza <camelia.groza@nxp.com>
|
|
[rebase]
|
|
Signed-off-by: Yangbo Lu <yangbo.lu@nxp.com>
|
|
---
|
|
drivers/net/ethernet/freescale/dpaa/dpaa_eth.c | 204 +++++++++++++++++++++++--
|
|
1 file changed, 194 insertions(+), 10 deletions(-)
|
|
|
|
--- a/drivers/net/ethernet/freescale/dpaa/dpaa_eth.c
|
|
+++ b/drivers/net/ethernet/freescale/dpaa/dpaa_eth.c
|
|
@@ -54,6 +54,10 @@
|
|
#include <linux/phy_fixed.h>
|
|
#include <soc/fsl/bman.h>
|
|
#include <soc/fsl/qman.h>
|
|
+#if !defined(CONFIG_PPC) && defined(CONFIG_SOC_BUS)
|
|
+#include <linux/sys_soc.h> /* soc_device_match */
|
|
+#endif
|
|
+
|
|
#include "fman.h"
|
|
#include "fman_port.h"
|
|
#include "mac.h"
|
|
@@ -73,6 +77,10 @@ static u16 tx_timeout = 1000;
|
|
module_param(tx_timeout, ushort, 0444);
|
|
MODULE_PARM_DESC(tx_timeout, "The Tx timeout in ms");
|
|
|
|
+#ifndef CONFIG_PPC
|
|
+bool dpaa_errata_a010022;
|
|
+#endif
|
|
+
|
|
#define FM_FD_STAT_RX_ERRORS \
|
|
(FM_FD_ERR_DMA | FM_FD_ERR_PHYSICAL | \
|
|
FM_FD_ERR_SIZE | FM_FD_ERR_CLS_DISCARD | \
|
|
@@ -1495,7 +1503,19 @@ static int dpaa_bp_add_8_bufs(const stru
|
|
u8 i;
|
|
|
|
for (i = 0; i < 8; i++) {
|
|
+#ifndef CONFIG_PPC
|
|
+ if (dpaa_errata_a010022) {
|
|
+ struct page *page = alloc_page(GFP_KERNEL);
|
|
+
|
|
+ if (unlikely(!page))
|
|
+ goto release_previous_buffs;
|
|
+ new_buf = page_address(page);
|
|
+ } else {
|
|
+ new_buf = netdev_alloc_frag(dpaa_bp->raw_size);
|
|
+ }
|
|
+#else
|
|
new_buf = netdev_alloc_frag(dpaa_bp->raw_size);
|
|
+#endif
|
|
if (unlikely(!new_buf)) {
|
|
dev_err(dev, "netdev_alloc_frag() failed, size %zu\n",
|
|
dpaa_bp->raw_size);
|
|
@@ -1663,9 +1683,15 @@ static struct sk_buff *dpaa_cleanup_tx_f
|
|
}
|
|
}
|
|
|
|
- if (qm_fd_get_format(fd) == qm_fd_sg)
|
|
- /* Free the page frag that we allocated on Tx */
|
|
- skb_free_frag(phys_to_virt(addr));
|
|
+ if (qm_fd_get_format(fd) == qm_fd_sg) {
|
|
+#ifndef CONFIG_PPC
|
|
+ if (dpaa_errata_a010022)
|
|
+ put_page(virt_to_page(sgt));
|
|
+ else
|
|
+#endif
|
|
+ /* Free the page frag that we allocated on Tx */
|
|
+ skb_free_frag(phys_to_virt(addr));
|
|
+ }
|
|
|
|
return skb;
|
|
}
|
|
@@ -1922,14 +1948,26 @@ static int skb_to_sg_fd(struct dpaa_priv
|
|
size_t frag_len;
|
|
void *sgt_buf;
|
|
|
|
- /* get a page frag to store the SGTable */
|
|
- sz = SKB_DATA_ALIGN(priv->tx_headroom + DPAA_SGT_SIZE);
|
|
- sgt_buf = netdev_alloc_frag(sz);
|
|
- if (unlikely(!sgt_buf)) {
|
|
- netdev_err(net_dev, "netdev_alloc_frag() failed for size %d\n",
|
|
- sz);
|
|
- return -ENOMEM;
|
|
+#ifndef CONFIG_PPC
|
|
+ if (unlikely(dpaa_errata_a010022)) {
|
|
+ struct page *page = alloc_page(GFP_ATOMIC);
|
|
+ if (unlikely(!page))
|
|
+ return -ENOMEM;
|
|
+ sgt_buf = page_address(page);
|
|
+ } else {
|
|
+#endif
|
|
+ /* get a page frag to store the SGTable */
|
|
+ sz = SKB_DATA_ALIGN(priv->tx_headroom + DPAA_SGT_SIZE);
|
|
+ sgt_buf = netdev_alloc_frag(sz);
|
|
+ if (unlikely(!sgt_buf)) {
|
|
+ netdev_err(net_dev,
|
|
+ "netdev_alloc_frag() failed for size %d\n",
|
|
+ sz);
|
|
+ return -ENOMEM;
|
|
+ }
|
|
+#ifndef CONFIG_PPC
|
|
}
|
|
+#endif
|
|
|
|
/* Enable L3/L4 hardware checksum computation.
|
|
*
|
|
@@ -2049,6 +2087,122 @@ static inline int dpaa_xmit(struct dpaa_
|
|
return 0;
|
|
}
|
|
|
|
+#ifndef CONFIG_PPC
|
|
+/* On LS1043A SoC there is a known erratum ERR010022 that results in split DMA
|
|
+ * transfers in the FMan under certain conditions. This, combined with a fixed
|
|
+ * size FIFO of ongoing DMA transfers that may overflow when a split occurs,
|
|
+ * results in the FMan stalling DMA transfers under high traffic. To avoid the
|
|
+ * problem, one needs to prevent the DMA transfer splits to occur by preparing
|
|
+ * the buffers
|
|
+ */
|
|
+
|
|
+#define DPAA_A010022_HEADROOM 256
|
|
+#define CROSS_4K_BOUND(start, size) \
|
|
+ (((start) + (size)) > (((start) + 0x1000) & ~0xFFF))
|
|
+
|
|
+static bool dpaa_errata_a010022_has_dma_issue(struct sk_buff *skb,
|
|
+ struct dpaa_priv *priv)
|
|
+{
|
|
+ int nr_frags, i = 0;
|
|
+ skb_frag_t *frag;
|
|
+
|
|
+ /* Transfers that do not start at 16B aligned addresses will be split;
|
|
+ * Transfers that cross a 4K page boundary will also be split
|
|
+ */
|
|
+
|
|
+ /* Check if the frame data is aligned to 16 bytes */
|
|
+ if ((uintptr_t)skb->data % DPAA_FD_DATA_ALIGNMENT)
|
|
+ return true;
|
|
+
|
|
+ /* Check if the headroom crosses a boundary */
|
|
+ if (CROSS_4K_BOUND((uintptr_t)skb->head, skb_headroom(skb)))
|
|
+ return true;
|
|
+
|
|
+ /* Check if the non-paged data crosses a boundary */
|
|
+ if (CROSS_4K_BOUND((uintptr_t)skb->data, skb_headlen(skb)))
|
|
+ return true;
|
|
+
|
|
+ nr_frags = skb_shinfo(skb)->nr_frags;
|
|
+
|
|
+ while (i < nr_frags) {
|
|
+ frag = &skb_shinfo(skb)->frags[i];
|
|
+
|
|
+ /* Check if a paged fragment crosses a boundary from its
|
|
+ * offset to its end.
|
|
+ */
|
|
+ if (CROSS_4K_BOUND((uintptr_t)frag->page_offset, frag->size))
|
|
+ return true;
|
|
+
|
|
+ i++;
|
|
+ }
|
|
+
|
|
+ return false;
|
|
+}
|
|
+
|
|
+static struct sk_buff *dpaa_errata_a010022_prevent(struct sk_buff *skb,
|
|
+ struct dpaa_priv *priv)
|
|
+{
|
|
+ int trans_offset = skb_transport_offset(skb);
|
|
+ int net_offset = skb_network_offset(skb);
|
|
+ int nsize, npage_order, headroom;
|
|
+ struct sk_buff *nskb = NULL;
|
|
+ struct page *npage;
|
|
+ void *npage_addr;
|
|
+
|
|
+ if (!dpaa_errata_a010022_has_dma_issue(skb, priv))
|
|
+ return skb;
|
|
+
|
|
+ /* For the new skb we only need the old one's data (both non-paged and
|
|
+ * paged). We can skip the old tailroom.
|
|
+ *
|
|
+ * The headroom also needs to fit our private info (64 bytes) but we
|
|
+ * reserve 256 bytes instead in order to guarantee that the data is
|
|
+ * aligned to 256.
|
|
+ */
|
|
+ headroom = DPAA_A010022_HEADROOM;
|
|
+ nsize = headroom + skb->len +
|
|
+ SKB_DATA_ALIGN(sizeof(struct skb_shared_info));
|
|
+
|
|
+ /* Reserve enough memory to accommodate Jumbo frames */
|
|
+ npage_order = (nsize - 1) / PAGE_SIZE;
|
|
+ npage = alloc_pages(GFP_ATOMIC | __GFP_COMP, npage_order);
|
|
+ if (unlikely(!npage)) {
|
|
+ WARN_ONCE(1, "Memory allocation failure\n");
|
|
+ return NULL;
|
|
+ }
|
|
+ npage_addr = page_address(npage);
|
|
+
|
|
+ nskb = build_skb(npage_addr, nsize);
|
|
+ if (unlikely(!nskb))
|
|
+ goto err;
|
|
+
|
|
+ /* Code borrowed and adapted from skb_copy() */
|
|
+ skb_reserve(nskb, headroom);
|
|
+ skb_put(nskb, skb->len);
|
|
+ if (skb_copy_bits(skb, 0, nskb->data, skb->len)) {
|
|
+ WARN_ONCE(1, "skb parsing failure\n");
|
|
+ goto err;
|
|
+ }
|
|
+ copy_skb_header(nskb, skb);
|
|
+
|
|
+ /* We move the headroom when we align it so we have to reset the
|
|
+ * network and transport header offsets relative to the new data
|
|
+ * pointer. The checksum offload relies on these offsets.
|
|
+ */
|
|
+ skb_set_network_header(nskb, net_offset);
|
|
+ skb_set_transport_header(nskb, trans_offset);
|
|
+
|
|
+ dev_kfree_skb(skb);
|
|
+ return nskb;
|
|
+
|
|
+err:
|
|
+ if (nskb)
|
|
+ dev_kfree_skb(nskb);
|
|
+ put_page(npage);
|
|
+ return NULL;
|
|
+}
|
|
+#endif
|
|
+
|
|
static netdev_tx_t
|
|
dpaa_start_xmit(struct sk_buff *skb, struct net_device *net_dev)
|
|
{
|
|
@@ -2095,6 +2249,15 @@ dpaa_start_xmit(struct sk_buff *skb, str
|
|
nonlinear = skb_is_nonlinear(skb);
|
|
}
|
|
|
|
+#ifndef CONFIG_PPC
|
|
+ if (unlikely(dpaa_errata_a010022)) {
|
|
+ skb = dpaa_errata_a010022_prevent(skb, priv);
|
|
+ if (!skb)
|
|
+ goto enomem;
|
|
+ nonlinear = skb_is_nonlinear(skb);
|
|
+ }
|
|
+#endif
|
|
+
|
|
if (nonlinear) {
|
|
/* Just create a S/G fd based on the skb */
|
|
err = skb_to_sg_fd(priv, skb, &fd);
|
|
@@ -2992,6 +3155,23 @@ static int dpaa_remove(struct platform_d
|
|
return err;
|
|
}
|
|
|
|
+#ifndef CONFIG_PPC
|
|
+static bool __init soc_has_errata_a010022(void)
|
|
+{
|
|
+#ifdef CONFIG_SOC_BUS
|
|
+ const struct soc_device_attribute soc_msi_matches[] = {
|
|
+ { .family = "QorIQ LS1043A",
|
|
+ .data = NULL },
|
|
+ { },
|
|
+ };
|
|
+
|
|
+ if (!soc_device_match(soc_msi_matches))
|
|
+ return false;
|
|
+#endif
|
|
+ return true; /* cannot identify SoC or errata applies */
|
|
+}
|
|
+#endif
|
|
+
|
|
static const struct platform_device_id dpaa_devtype[] = {
|
|
{
|
|
.name = "dpaa-ethernet",
|
|
@@ -3016,6 +3196,10 @@ static int __init dpaa_load(void)
|
|
|
|
pr_debug("FSL DPAA Ethernet driver\n");
|
|
|
|
+#ifndef CONFIG_PPC
|
|
+ /* Detect if the current SoC requires the DMA transfer alignment workaround */
|
|
+ dpaa_errata_a010022 = soc_has_errata_a010022();
|
|
+#endif
|
|
/* initialize dpaa_eth mirror values */
|
|
dpaa_rx_extra_headroom = fman_get_rx_extra_headroom();
|
|
dpaa_max_frm = fman_get_max_frm();
|