Thread (6 messages) flat view 6 messages, 4 authors, 2026-08-07

Re: [PATCH net] hsr: Avoid holding seqnr_lock while transmitting packets

From: Hangbin Liu <hidden>
Date: 2026-08-07 12:42:42
Also in: lkml

Hi Simon,
On Fri, Aug 07, 2026 at 10:43:25AM +0100, Simon Horman wrote:
[ ... ]
Simon says:

I don't think the advice on how to annotate this is correct

Tags trump freeform text, but Depends-on is a not a standard tag and as
such should not be used.

I believe that the correct approach would be a tag like this:

  Cc: [off-list ref] # aae9d6b616b5: hsr: Implement more robust duplicate discard for HSR

Link: https://docs.kernel.org/process/stable-kernel-rules.html#option-1
Thanks, I will update with this.
quoted
diff --git a/net/hsr/hsr_device.c b/net/hsr/hsr_device.c
index 5555b71ab19b5..14e8d06762294 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);
 	} else {
 		dev_core_stats_tx_dropped_inc(dev);
 		dev_kfree_skb_any(skb);
[Severity: Medium]
[Finding: main-2-3]
[Sources: opus-5]
Can this lose statistics updates? The tail of hsr_forward_skb() does plain
read-modify-write on struct net_device_stats for the master and interlink
ports:

net/hsr/hsr_forward.c:hsr_forward_skb() {
	...
	if (port->type == HSR_PT_MASTER || port->type == HSR_PT_INTERLINK) {
		port->dev->stats.tx_packets++;
		port->dev->stats.tx_bytes += skb->len;
	}
	...
out_drop:
	rcu_read_unlock();
	port->dev->stats.tx_dropped++;
}

Before this change every caller reaching those increments for
master/interlink held hsr->seqnr_lock across all of hsr_forward_skb(), which
made them mutually exclusive as a side effect.

hsr_dev_setup() sets dev->lltx = true ("Prevent recursive tx locking") and
the master has no qdisc, so __dev_queue_xmit() calls hsr_dev_xmit() without
the txq lock. Two CPUs can then run tx_packets++ concurrently and drop an
increment, and tx_bytes += skb->len can tear on 32-bit. The interlink RX
path, hsr_handle_frame() -> hsr_forward_skb(), now races the same counters.

Would switching these to dev_sw_netstats or otherwise per-CPU counters be
preferable while the lock goes away?
Using dev_dstats_* looks good since it has drop count. But hsr also counts
dev->stats.multicast. Do you have any advice other than re-implement a
per-CPU counter for multicast specifically?


[ ... ]
[Severity: Low]
[Finding: main-2-2]
[Sources: opus-5]
This isn't a bug, but the two implementers of the same
hsr_proto_ops->send_sv_frame callback now follow different rules for
seqnr_lock.

send_hsr_supervision_frame() was converted to the minimal scope the patch
establishes in handle_std_frame(): lock, bump sup_sequence_nr/sequence_nr,
unlock.

send_prp_supervision_frame() still takes seqnr_lock before the sequence
number bump and holds it across skb_put(), ether_addr_copy() and the
possibly reallocating skb_put_padto(), which is also why it keeps the
duplicated unlock in the error path and just before hsr_forward_skb():

net/hsr/hsr_device.c:send_prp_supervision_frame() {
	spin_lock_bh(&hsr->seqnr_lock);
	hsr_stag->sequence_nr = htons(hsr->sup_sequence_nr);
	hsr->sup_sequence_nr++;
	...
	if (skb_put_padto(skb, ETH_ZLEN)) {
		spin_unlock_bh(&hsr->seqnr_lock);
		return;
	}

	spin_unlock_bh(&hsr->seqnr_lock);

	hsr_forward_skb(skb, master);
}

Nothing in the extended region re-acquires seqnr_lock and the two unlock
sites are on mutually exclusive paths, so this is not an unbalanced unlock.
Could the PRP side be shrunk the same way so both callbacks obey one
convention and the duplicated unlock disappears?
OK, I will update the prp path.
quoted
diff --git a/net/hsr/hsr_forward.c b/net/hsr/hsr_forward.c
index 0774981a65c16..8e4158a9b57c8 100644
--- a/net/hsr/hsr_forward.c
+++ b/net/hsr/hsr_forward.c
@@ -621,9 +621,10 @@ static void handle_std_frame(struct sk_buff *skb,
 	if (port->type == HSR_PT_MASTER ||
 	    port->type == HSR_PT_INTERLINK) {
 		/* Sequence nr for the master/interlink node */
-		lockdep_assert_held(&hsr->seqnr_lock);
+		spin_lock_bh(&hsr->seqnr_lock);
 		frame->sequence_nr = hsr->sequence_nr;
 		hsr->sequence_nr++;
+		spin_unlock_bh(&hsr->seqnr_lock);
 	}
 }
[Severity: Medium]
[Finding: main-1-1]
[Sources: opus-5]
With the lock released here, allocation of the sequence number and the
actual transmit are no longer atomic with respect to each other. Can frames
now leave the ring out of sequence order?

CPU0 handle_std_frame()
	spin_lock_bh(&hsr->seqnr_lock);
	frame->sequence_nr = 100;
	hsr->sequence_nr = 101;
	spin_unlock_bh(&hsr->seqnr_lock);

CPU1 handle_std_frame()
	spin_lock_bh(&hsr->seqnr_lock);
	frame->sequence_nr = 101;
	hsr->sequence_nr = 102;
	spin_unlock_bh(&hsr->seqnr_lock);

CPU1 then reaches hsr_forward_do() -> hsr_xmit() -> dev_queue_xmit() on the
slaves before CPU0 does, so 101 goes on the wire before 100. hsr_dev_setup()
sets dev->lltx = true and the master has no queue, so nothing re-establishes
ordering between the two.

Local receive is fine, since hsr_check_duplicate() uses the per-node bitmap
under node->seq_out_lock. The changelog only justifies the relaxation with
this kernel's own array based discard, which is the point 06afd2c31d33
raised for the peer side:

	"if the higher sequence number leaves on wire before the lower does
	 and the destination receives them in that order ... it will drop the
	 packet with the lower sequence number and never inject into the
	 stack"

How is a remote DANH, RedBox or hardware peer that still uses last-seq or
window based duplicate discard expected to behave here, including a Linux
peer older than aae9d6b616b5? If out-of-order egress is considered
acceptable, could the changelog say so explicitly?
I think this should not be a problem. The devices running HSR should be in
the same network. It is seldom that one device is upgraded while another
one is left with an old kernel. If the packets go through the internet
via interlink, then no one could promise that the packets are in order.

Thanks
Hangbin
Keyboard shortcuts
hback out one level
jnext message in thread
kprevious message in thread
ldrill in
Escclose help / fold thread tree
?toggle this help