diff --git a/net/hsr/hsr_device.c b/net/hsr/hsr_device.c
index fc512204600c..2521a72280ac 100644
--- a/net/hsr/hsr_device.c
+++ b/net/hsr/hsr_device.c
@@ -232,9 +232,7 @@ static netdev_tx_t hsr_dev_xmit(struct sk_buff *skb, struct net_device *dev)
skb->dev = master->dev;
skb_reset_mac_header(skb);
skb_reset_mac_len(skb);
- spin_lock_bh(&hsr->seqnr_lock);
- hsr_forward_skb(skb, master);
- spin_unlock_bh(&hsr->seqnr_lock);
+ hsr_forward_input(skb, master);
} else {
dev_core_stats_tx_dropped_inc(dev);
dev_kfree_skb_any(skb);@@ -686,7 +684,7 @@ void hsr_dev_setup(struct net_device *dev)
dev->needs_free_netdev = true;
dev->hw_features = NETIF_F_SG | NETIF_F_FRAGLIST | NETIF_F_HIGHDMA |
- NETIF_F_GSO_MASK | NETIF_F_HW_CSUM |
+ NETIF_F_HW_CSUM |
NETIF_F_HW_VLAN_CTAG_TX |
NETIF_F_HW_VLAN_CTAG_FILTER;
diff --git a/net/hsr/hsr_forward.c b/net/hsr/hsr_forward.c
index 44f6d9c83180..c9cd5d920879 100644
--- a/net/hsr/hsr_forward.c
+++ b/net/hsr/hsr_forward.c
@@ -12,6 +12,9 @@
#include <linux/skbuff.h>
#include <linux/etherdevice.h>
#include <linux/if_vlan.h>
+#include <linux/ip.h>
+#include <linux/ipv6.h>
+#include <net/gso.h>
#include "hsr_main.h"
#include "hsr_framereg.h"
@@ -794,3 +797,154 @@ void hsr_forward_skb(struct sk_buff *skb, struct hsr_port *port)
port->dev->stats.tx_dropped++;
kfree_skb(skb);
}
+
+/* Master TX and interlink RX are numbered locally: number and submit
+ * one frame at a time under seqnr_lock, as before. LAN A/B input
+ * keeps its received number and needs no lock.
+ */
+static void hsr_forward_frame(struct sk_buff *skb, struct hsr_port *port)
+{
+ struct hsr_priv *hsr = port->hsr;
+ bool numbered = port->type == HSR_PT_MASTER ||
+ port->type == HSR_PT_INTERLINK;
+
+ if (numbered)
+ spin_lock_bh(&hsr->seqnr_lock);
+ hsr_forward_skb(skb, port);
+ if (numbered)
+ spin_unlock_bh(&hsr->seqnr_lock);
+}
+
+/* One count per rejected GSO input on its entry device. */
+static void hsr_gso_drop_stat(struct hsr_port *port)
+{
+ if (port->type == HSR_PT_MASTER)
+ dev_core_stats_tx_dropped_inc(port->dev);
+ else
+ dev_core_stats_rx_dropped_inc(port->dev);
+}
+
+/* Return the inner EtherType using the core's bounded 802.1Q/802.1AD
+ * decoder. An accelerated outer tag is metadata, not frame bytes.
+ */
+static __be16 hsr_gso_effective_proto(const struct sk_buff *skb, int *l3off)
+{
+ struct ethhdr eh;
+ const struct ethhdr *eth;
+
+ if (skb_vlan_tag_present(skb) &&
+ !eth_type_vlan(skb->vlan_proto))
+ return 0;
+
+ eth = skb_header_pointer(skb, 0, sizeof(eh), &eh);
+ if (!eth)
+ return 0;
+
+ return __vlan_get_protocol(skb, eth->h_proto, l3off);
+}
+
+/* Bytes beyond the IP datagram must not become segment payload. Older
+ * PRP senders can append an RCT to a whole GSO skb. Zero IP lengths and
+ * GSO_PARTIAL headers do not provide the full aggregate length here.
+ */
+static bool hsr_gso_trailing_data(const struct sk_buff *skb, __be16 proto,
+ int l3off)
+{
+ unsigned int l3len;
+
+ if (skb_shinfo(skb)->gso_type & SKB_GSO_PARTIAL)
+ return false;
+
+ if (proto == htons(ETH_P_IP)) {
+ struct iphdr buffer;
+ const struct iphdr *iph;
+
+ iph = skb_header_pointer(skb, l3off, sizeof(buffer), &buffer);
+ if (!iph)
+ return true;
+ l3len = ntohs(iph->tot_len);
+ } else if (proto == htons(ETH_P_IPV6)) {
+ struct ipv6hdr buffer;
+ const struct ipv6hdr *ip6h;
+
+ ip6h = skb_header_pointer(skb, l3off, sizeof(buffer), &buffer);
+ if (!ip6h)
+ return true;
+ l3len = ntohs(ip6h->payload_len);
+ if (l3len)
+ l3len += sizeof(*ip6h);
+ } else {
+ return false;
+ }
+
+ return l3len && l3off + l3len < skb->len;
+}
+
+/* Unfold GSO super-packets before per-frame processing so that each
+ * wire frame gets its own HSR tag or PRP RCT and sequence number.
+ * Segmentation runs outside seqnr_lock; every segment is numbered and
+ * submitted under it like any other frame.
+ */
+void hsr_forward_input(struct sk_buff *skb, struct hsr_port *port)
+{
+ struct sk_buff *segs, *next;
+ __be16 proto;
+ int l3off;
+
+ if (likely(!skb_is_gso(skb))) {
+ hsr_forward_frame(skb, port);
+ return;
+ }
+
+ /* The gate is content-based, not port-based. An aggregate
+ * whose effective protocol is ETH_P_HSR or ETH_P_PRP cannot be
+ * segmented safely and is dropped, as is any skb whose headers
+ * cannot be read or that is marked unreadable (net_iov device
+ * memory): software segmentation cannot read its payload, so
+ * each segment would carry uninitialized data. Ordinary VLAN
+ * GSO (802.1Q/802.1AD at any reachable tag depth, accelerated
+ * or in-band) is classified to its inner protocol through the
+ * core VLAN decoder and segmented like plain input. With
+ * NETIF_F_HW_HSR_TAG_RM the lower has already stripped the tag,
+ * so such aggregates arrive plain.
+ */
+ if (!skb_frags_readable(skb))
+ goto drop;
+ proto = hsr_gso_effective_proto(skb, &l3off);
+ if (!proto)
+ goto drop;
+ if (proto == htons(ETH_P_HSR) || proto == htons(ETH_P_PRP))
+ goto drop;
+ if (hsr_gso_trailing_data(skb, proto, l3off))
+ goto drop;
+
+ /* Segmentation writes skb->cb and header metadata: work on a
+ * private skb if the input is still shared with a packet socket.
+ */
+ skb = skb_share_check(skb, GFP_ATOMIC);
+ if (!skb) {
+ hsr_gso_drop_stat(port);
+ return;
+ }
+
+ /* features = 0 requests full software segmentation into single
+ * wire frames; tx_path is true only for locally generated
+ * traffic on the master.
+ */
+ segs = __skb_gso_segment(skb, 0, port->type == HSR_PT_MASTER, 0);
+ if (IS_ERR_OR_NULL(segs))
+ goto drop;
+
+ consume_skb(skb);
+ while (segs) {
+ next = segs->next;
+ segs->next = NULL;
+ hsr_forward_frame(segs, port);
+ segs = next;
+ }
+ return;
+
+drop:
+ hsr_gso_drop_stat(port);
+ kfree_skb(skb);
+}diff --git a/net/hsr/hsr_forward.h b/net/hsr/hsr_forward.h
index 206636750b30..ec08c4330259 100644
--- a/net/hsr/hsr_forward.h
+++ b/net/hsr/hsr_forward.h
@@ -14,6 +14,7 @@
#include "hsr_main.h"
void hsr_forward_skb(struct sk_buff *skb, struct hsr_port *port);
+void hsr_forward_input(struct sk_buff *skb, struct hsr_port *port);
struct sk_buff *prp_create_tagged_frame(struct hsr_frame_info *frame,
struct hsr_port *port);
struct sk_buff *hsr_create_tagged_frame(struct hsr_frame_info *frame,
diff --git a/net/hsr/hsr_slave.c b/net/hsr/hsr_slave.c
index 26f16d98e479..73f4e555e18e 100644
--- a/net/hsr/hsr_slave.c
+++ b/net/hsr/hsr_slave.c
@@ -75,15 +75,11 @@ static rx_handler_result_t hsr_handle_frame(struct sk_buff **pskb)
skb_reset_mac_len(skb);
/* Only the frames received over the interlink port will assign a
- * sequence number and require synchronisation vs other sender.
+ * sequence number and require synchronisation vs other sender;
+ * hsr_forward_input() takes seqnr_lock for them, one frame at a
+ * time.
*/
- if (port->type == HSR_PT_INTERLINK) {
- spin_lock_bh(&hsr->seqnr_lock);
- hsr_forward_skb(skb, port);
- spin_unlock_bh(&hsr->seqnr_lock);
- } else {
- hsr_forward_skb(skb, port);
- }
+ hsr_forward_input(skb, port);
finish_consume:
return RX_HANDLER_CONSUMED;--
2.53.0