DPAA2 drops frames on some TCP/UDP ports

I noticed that my Ten64 doesn’t forward packets to port 3386 over a linux bridge, so I went down a bit of a rabbit hole.

The Ten64’s DPAA2 hardware discards some valid frames before Linux sees them: TCP to port 3386, UDP to 2123/2152/3386 unless the payload is GTP, IPv6 UDP to 4500/4789 with fewer than 8 bytes of payload, and frames with IPv4 header errors. The only trace is [hw] rx discarded frames in ethtool -S. Cause: dpaa2-eth asks the MC to discard every frame with any parser or checksum error bit, and the WRIOP parser guesses layer-5 protocols by port number.

The fix I came up with: discard only frame-length and physical errors and let the stack recheck checksums.

Patch 1/2: dpaa2-eth checksum guard
diff --git c/drivers/net/ethernet/freescale/dpaa2/dpaa2-eth.c i/drivers/net/ethernet/freescale/dpaa2/dpaa2-eth.c
index 6f1046c9cc51..1745867723a1 100644
--- c/drivers/net/ethernet/freescale/dpaa2/dpaa2-eth.c
+++ i/drivers/net/ethernet/freescale/dpaa2/dpaa2-eth.c
@@ -129,6 +129,10 @@ static void dpaa2_eth_validate_rx_csum(struct dpaa2_eth_priv *priv,
              (fd_status & DPAA2_FAS_L4CV)))
                return;

+       /* The hardware validated the checksums and found an error */
+       if (fd_status & (DPAA2_FAS_L3CE | DPAA2_FAS_L4CE))
+               return;
+
        /* Inform the stack there's no need to compute L3/L4 csum anymore */
        skb->ip_summed = CHECKSUM_UNNECESSARY;
 }
Patch 2/2: dpaa2-eth pass frames with parser errors
diff --git i/drivers/net/ethernet/freescale/dpaa2/dpaa2-eth-devlink.c w/drivers/net/ethernet/freescale/dpaa2/dpaa2-eth-devlink.c
index 8775c931106b..428354013c15 100644
--- i/drivers/net/ethernet/freescale/dpaa2/dpaa2-eth-devlink.c
+++ w/drivers/net/ethernet/freescale/dpaa2/dpaa2-eth-devlink.c
@@ -143,28 +143,17 @@ static int dpaa2_eth_dl_trap_group_action_set(struct devlink *devlink,
        struct dpaa2_eth_priv *priv = dl_priv->dpaa2_priv;
        struct net_device *net_dev = priv->net_dev;
        struct device *dev = net_dev->dev.parent;
-       struct dpni_error_cfg err_cfg = {0};
        int err;

        if (group->id != DEVLINK_TRAP_GROUP_GENERIC_ID_PARSER_ERROR_DROPS)
                return -EOPNOTSUPP;

-       /* Configure handling of frames marked as errors from the parser */
-       err_cfg.errors = DPAA2_FAS_RX_ERR_MASK;
-       err_cfg.set_frame_annotation = 1;
-
-       switch (action) {
-       case DEVLINK_TRAP_ACTION_DROP:
-               err_cfg.error_action = DPNI_ERROR_ACTION_DISCARD;
-               break;
-       case DEVLINK_TRAP_ACTION_TRAP:
-               err_cfg.error_action = DPNI_ERROR_ACTION_SEND_TO_ERROR_QUEUE;
-               break;
-       default:
+       if (action != DEVLINK_TRAP_ACTION_DROP &&
+           action != DEVLINK_TRAP_ACTION_TRAP)
                return -EOPNOTSUPP;
-       }

-       err = dpni_set_errors_behavior(priv->mc_io, 0, priv->mc_token, &err_cfg);
+       err = dpaa2_eth_set_rx_err_behavior(priv,
+                                           action == DEVLINK_TRAP_ACTION_TRAP);
        if (err) {
                dev_err(dev, "dpni_set_errors_behavior failed\n");
                return err;
diff --git i/drivers/net/ethernet/freescale/dpaa2/dpaa2-eth.c w/drivers/net/ethernet/freescale/dpaa2/dpaa2-eth.c
index 1745867723a1..1849a546c8e3 100644
--- i/drivers/net/ethernet/freescale/dpaa2/dpaa2-eth.c
+++ w/drivers/net/ethernet/freescale/dpaa2/dpaa2-eth.c
@@ -4373,6 +4373,37 @@ static int dpaa2_eth_set_default_cls(struct dpaa2_eth_priv *priv)
        return 0;
 }

+/*
+ * Drop broken frames and pass frames with other ingress errors to the stack,
+ * or send all frames with errors to the Rx error queue for devlink to trap.
+ */
+int dpaa2_eth_set_rx_err_behavior(struct dpaa2_eth_priv *priv, bool trap)
+{
+       struct dpni_error_cfg err_cfg = {0};
+       int err;
+
+       err_cfg.set_frame_annotation = 1;
+
+       if (trap) {
+               err_cfg.errors = DPAA2_FAS_RX_ERR_MASK;
+               err_cfg.error_action = DPNI_ERROR_ACTION_SEND_TO_ERROR_QUEUE;
+               return dpni_set_errors_behavior(priv->mc_io, 0, priv->mc_token,
+                                               &err_cfg);
+       }
+
+       err_cfg.errors = DPAA2_FAS_RX_DROP_ERR_MASK;
+       err_cfg.error_action = DPNI_ERROR_ACTION_DISCARD;
+       err = dpni_set_errors_behavior(priv->mc_io, 0, priv->mc_token,
+                                      &err_cfg);
+       if (err)
+               return err;
+
+       err_cfg.errors = DPAA2_FAS_RX_ERR_MASK & ~DPAA2_FAS_RX_DROP_ERR_MASK;
+       err_cfg.error_action = DPNI_ERROR_ACTION_CONTINUE;
+       return dpni_set_errors_behavior(priv->mc_io, 0, priv->mc_token,
+                                       &err_cfg);
+}
+
 /* Bind the DPNI to its needed objects and resources: buffer pool, DPIOs,
  * frame queues and channels
  */
@@ -4382,7 +4413,6 @@ static int dpaa2_eth_bind_dpni(struct dpaa2_eth_priv *priv)
        struct net_device *net_dev = priv->net_dev;
        struct dpni_pools_cfg pools_params = { 0 };
        struct device *dev = net_dev->dev.parent;
-       struct dpni_error_cfg err_cfg;
        int err = 0;
        int i;

@@ -4411,11 +4441,7 @@ static int dpaa2_eth_bind_dpni(struct dpaa2_eth_priv *priv)
                dev_err(dev, "Failed to configure Rx classification key\n");

        /* Configure handling of error frames */
-       err_cfg.errors = DPAA2_FAS_RX_ERR_MASK;
-       err_cfg.set_frame_annotation = 1;
-       err_cfg.error_action = DPNI_ERROR_ACTION_DISCARD;
-       err = dpni_set_errors_behavior(priv->mc_io, 0, priv->mc_token,
-                                      &err_cfg);
+       err = dpaa2_eth_set_rx_err_behavior(priv, false);
        if (err) {
                dev_err(dev, "dpni_set_errors_behavior failed\n");
                return err;
diff --git i/drivers/net/ethernet/freescale/dpaa2/dpaa2-eth.h w/drivers/net/ethernet/freescale/dpaa2/dpaa2-eth.h
index 834cba8c3a41..374dad7f0b5e 100644
--- i/drivers/net/ethernet/freescale/dpaa2/dpaa2-eth.h
+++ w/drivers/net/ethernet/freescale/dpaa2/dpaa2-eth.h
@@ -355,6 +355,13 @@ static inline struct dpaa2_faead *dpaa2_get_faead(void *buf_addr, bool swa)
                                         DPAA2_FAS_BLE          | \
                                         DPAA2_FAS_L3CE         | \
                                         DPAA2_FAS_L4CE)
+/*
+ * Ingress errors on which the frame is dropped: the frame itself is broken.
+ * The other errors come from the parser, the classifier and the checksum
+ * validation, and frames which are valid on the wire raise them too, e.g. a
+ * UDP datagram to the GTP-U port whose payload is not GTP.
+ */
+#define DPAA2_FAS_RX_DROP_ERR_MASK     (DPAA2_FAS_FLE | DPAA2_FAS_FPE)

 /* Time in milliseconds between link state updates */
 #define DPAA2_ETH_LINK_STATE_REFRESH   1000
@@ -785,6 +792,7 @@ static inline bool dpaa2_eth_has_mac(struct dpaa2_eth_priv *priv)

 int dpaa2_eth_set_hash(struct net_device *net_dev, u64 flags);
 int dpaa2_eth_set_cls(struct net_device *net_dev, u64 key);
+int dpaa2_eth_set_rx_err_behavior(struct dpaa2_eth_priv *priv, bool trap);
 int dpaa2_eth_cls_key_size(u64 key);
 int dpaa2_eth_cls_fld_off(int prot, int field);
 void dpaa2_eth_cls_trim_rule(void *key_mem, u64 fields);

What do you think?

Do you have a pcap I could replay to verify the issue?

There was a similar issue a while ago where DPAA2 was “incorrectly” calculating TCP and UDP checksums.
In that case, something else on the network was injecting data into the frame beyond the declared payload length, and the DPAA2 hardware was calculating the checksums based on the entire data section.

I wonder if it could be a similar problem here.