148 Commits
Author SHA1 Message Date
Julius Bairaktaris 7724018064 qualcommax: qca_ppe: do not classify host frames into a bridge VLAN
The ingress VLAN rule of a filtering bridge VLAN keyed the CPU port as
well, so every frame the host sent with that VID was classified into the
VLAN's VSI, also when it was addressed to a port outside the bridge. A
standalone port, or the conduit of a QCA8337 behind the PPE, then dropped
it. On the RT-AX89X a VLAN-aware bridge of lan3 and 10g-copper with PVID
10 passed no traffic towards lan3.

The host sends each frame already formatted for the port it names, so
leave the CPU port out of the rule and keep it in the VSI's members.

Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Julius Bairaktaris <julius@bairaktaris.de>
Link: https://github.com/openwrt/openwrt/pull/25700
Signed-off-by: Robert Marko <robimarko@gmail.com>
2026-10-18 12:50:09 +02:00
Julius Bairaktaris 4d37c515e7 qualcommax: qca_edma: add ethtool DMA reset
The EDMA block has a reset line of its own, but the driver asserts it
only at probe and when it rebuilds the rings. qca_edma is built in and
the switch sits on the conduit, so a reboot is the only other way back
to the engine's power-on state. Implement the ethtool reset op for
ETH_RESET_DMA with the rebuild ring resizing already uses.

Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Julius Bairaktaris <julius@bairaktaris.de>
Link: https://github.com/openwrt/openwrt/pull/25700
Signed-off-by: Robert Marko <robimarko@gmail.com>
2026-10-18 12:50:09 +02:00
Julius Bairaktaris 9aac12f7b1 qualcommax: qca_ppe: warn when the setup FDB flush times out
qca_ppe_setup() discards what ppe_fdb_flush() returns, so a flush that
never completes leaves the entries left behind by the boot loader in
place without a trace. Warn when it times out.

Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Julius Bairaktaris <julius@bairaktaris.de>
Link: https://github.com/openwrt/openwrt/pull/25700
Signed-off-by: Robert Marko <robimarko@gmail.com>
2026-10-18 12:50:09 +02:00
Julius Bairaktaris 7f5630527a qualcommax: qca_edma: mask the misc interrupt once it has fired
edma_misc_irq_handle() reads MISC_INT_STAT and returns without clearing
the condition, so a condition that persists keeps the interrupt asserted
and the handler runs back to back. Mask the misc interrupt on the first
report, as qca-nss-dp does in edma_handle_misc_irq(). edma_hw_init()
unmasks it again.

Fixes: bdb0b722b7 ("qualcommax: add EDMA driver")
Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Julius Bairaktaris <julius@bairaktaris.de>
Link: https://github.com/openwrt/openwrt/pull/25700
Signed-off-by: Robert Marko <robimarko@gmail.com>
2026-10-18 12:50:08 +02:00
Julius Bairaktaris 23d81764e3 qualcommax: qca_edma: clear only the TX interrupt masks that exist
edma_irq_disable_all() clears a TX interrupt mask for every TX
descriptor ring, but the TX interrupt registers come one per completion
ring. IPQ807x has eight completion rings, so indices 8 to 23 land in the
RXFILL block and zero the producer index and the interrupt mask of fill
rings 0 to 7. Bound the loop by the completion ring.

Fixes: bdb0b722b7 ("qualcommax: add EDMA driver")
Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Julius Bairaktaris <julius@bairaktaris.de>
Link: https://github.com/openwrt/openwrt/pull/25700
Signed-off-by: Robert Marko <robimarko@gmail.com>
2026-10-18 12:50:08 +02:00
Julius Bairaktaris 3218bba2d2 qualcommax: qca_ppe: count the CPU port's queues in ethtool -S
The CPU port reports the MAC counters of a MAC it does not have, all
zero, and none of its queue counters. Report its unicast and multicast
queue counters instead, as for the user ports.

Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Julius Bairaktaris <julius@bairaktaris.de>
Link: https://github.com/openwrt/openwrt/pull/25700
Signed-off-by: Robert Marko <robimarko@gmail.com>
2026-10-18 12:50:08 +02:00
Julius Bairaktaris 06a80616a7 qualcommax: qca_ppe: offload gred
The queue manager has a WRED threshold pair per colour on every unicast
queue, but only a colour-blind red is offloaded. Offload gred in WRED
mode on the same queues a red takes: DP 0 sets the queue limit and the
green thresholds, DP 1 and 2 the yellow and red ones below it, and the
queue turns colour aware; ECN and per-VQ averaging are refused. On
IPQ8074, two uploads under the 165 Mbit/s WAN shaper, one marked yellow,
ran at 14/47/6 and 140/71/146 Mbit/s with only yellow dropped
(572/343/162), against 110/135/80 and 43/25/82 under a colour-blind red.

Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Julius Bairaktaris <julius@bairaktaris.de>
Link: https://github.com/openwrt/openwrt/pull/25700
Signed-off-by: Robert Marko <robimarko@gmail.com>
2026-10-18 12:50:08 +02:00
Julius Bairaktaris fa41f4d99a qualcommax: qca_ppe: set the drop precedence from a flower classid
An ingress flower classid sets skb->tc_index, which gred reads as the
drop precedence, but the driver ignores the classid and offloads the
rule without it. Map classid :0 to :2 to the ACL action's internal drop
precedence, green, yellow or red, and refuse any other classid.

Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Julius Bairaktaris <julius@bairaktaris.de>
Link: https://github.com/openwrt/openwrt/pull/25700
Signed-off-by: Robert Marko <robimarko@gmail.com>
2026-10-18 12:50:08 +02:00
Julius Bairaktaris ae6592dc6a qualcommax: qca_ppe: count queue drops in tx_dropped
A port's egress queues drop frames that tx_dropped never shows: the
counter it folds sits ahead of the queues. Add the drops of the port's
unicast and multicast queues to tx_dropped, folded like the others. On
IPQ8074 a port shaped to 20 Mbit/s under a download reports the same
drops in tx_dropped as its queues do in ethtool -S (618, 539 and 503).

Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Julius Bairaktaris <julius@bairaktaris.de>
Link: https://github.com/openwrt/openwrt/pull/25700
Signed-off-by: Robert Marko <robimarko@gmail.com>
2026-10-18 12:50:08 +02:00
Julius Bairaktaris ef958a1767 qualcommax: qca_ppe: keep the VLAN tag on frames to the CPU
VSI_TAG_EN in PORT_EG_VLAN is set out of reset, so every port takes its
egress tag mode from the per-VSI table instead of its own. On the CPU
port this strips the C-tag of a frame that enters a VLAN-unaware bridge
port tagged, and a VLAN upper of the bridge never receives it. Clear the
bit on every port at setup; a VLAN-filtering port sets it again.

Fixes: 142104bb90 ("qualcommax: add PPE driver")
Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Julius Bairaktaris <julius@bairaktaris.de>
Link: https://github.com/openwrt/openwrt/pull/25700
Signed-off-by: Robert Marko <robimarko@gmail.com>
2026-10-18 12:50:08 +02:00
Julius Bairaktaris da09ab3c20 qualcommax: qca_edma: add TX VLAN offload
The TX preheader can carry a C-tag or S-tag for the switch to insert,
but the driver makes the stack push every tag into the frame. Add
NETIF_F_HW_VLAN_CTAG_TX and NETIF_F_HW_VLAN_STAG_TX: the tag is flagged
in pre2, its TCI written to pre3 and the insertion commanded in pre4.
They are in vlan_features, so the DSA user ports and their VLAN uppers
hand their tag to the conduit out of band. The switch inserts a C-tag
behind an S-tag already in the frame and drops a tag of the type the
frame starts with, so for those frames the stack still pushes the tag.

Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Julius Bairaktaris <julius@bairaktaris.de>
Link: https://github.com/openwrt/openwrt/pull/25700
Signed-off-by: Robert Marko <robimarko@gmail.com>
2026-10-18 12:50:08 +02:00
Julius Bairaktaris 2fe319438f qualcommax: qca_edma: add RX VLAN offload
The receive ring can strip the C-tag of a frame and report it in the
preheader, but the driver leaves every tag for the stack to pull. Add
NETIF_F_HW_VLAN_CTAG_RX: the ring strips the outer C-tag and the driver
hands it up out of band. A C-tag below an S-tag is not stripped and
stays in the frame.

Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Julius Bairaktaris <julius@bairaktaris.de>
Link: https://github.com/openwrt/openwrt/pull/25700
Signed-off-by: Robert Marko <robimarko@gmail.com>
2026-10-18 12:50:08 +02:00
Julius Bairaktaris 89ac01dc9b qualcommax: qca_edma: add NETIF_F_FRAGLIST support
A GRO aggregate that outgrows MAX_SKB_FRAGS keeps its further segments
on a frag_list, and without NETIF_F_FRAGLIST the stack segments it
before it reaches the conduit. Write the heads and fragments of the
frag_list skbs to descriptors like any other fragment. An aggregate
that needs more than 32 descriptors is segmented in software by
ndo_features_check rather than linearised.

Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Julius Bairaktaris <julius@bairaktaris.de>
Link: https://github.com/openwrt/openwrt/pull/25700
Signed-off-by: Robert Marko <robimarko@gmail.com>
2026-10-18 12:50:08 +02:00
Julius Bairaktaris 812890612e qualcommax: qca_ppe: put a port back on VSI 0 when it leaves a bridge
port_bridge_leave() marks the port's VSI invalid, and a port without a
valid VSI drops every frame it receives, so a port taken out of a bridge
stops receiving until the next probe. Program VSI 0, which carries the
standalone ports, as probe does.

Fixes: 142104bb90 ("qualcommax: add PPE driver")
Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Julius Bairaktaris <julius@bairaktaris.de>
Link: https://github.com/openwrt/openwrt/pull/25700
Signed-off-by: Robert Marko <robimarko@gmail.com>
2026-10-18 12:50:08 +02:00
Julius Bairaktaris 5b835fbcd8 qualcommax: qca_ppe: add devlink sb port pool and tc pool bind
devlink shows the shared buffer's pools but no port's share of them. A
switch port's ingress is one buffer manager port, port + 7 as qca-ssdk's
PHY_PORT_TO_BM_PORT() has it, so its single ingress class binds to that
port's buffer group and its threshold is the dynamic weight. The egress
classes are the port's unicast queues and report their admission group and
weight, and moving them is refused because the driver assigns the queue
groups. Occupancy is read from the buffer and queue counters at each
snapshot; they report only the current value, so the maximum is the
largest value a snapshot has seen.

On IPQ8074 a port threshold changes only the weight of that BM port's
entry, the neighbouring port's entry stays intact, an egress bind is
refused, and a port shaped to 50 Mbit/s with a 200000-byte limit reports
87040 bytes occupied in its egress pool under load.

Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Julius Bairaktaris <julius@bairaktaris.de>
Link: https://github.com/openwrt/openwrt/pull/25700
Signed-off-by: Robert Marko <robimarko@gmail.com>
2026-10-18 12:50:08 +02:00
Julius Bairaktaris 4ca73a4695 qualcommax: qca_ppe: offload ets DRR bands
An ets with DRR bands is either refused or, with equal quanta, run as
strict bands while it reports offloaded. With a strict top band, DRR lower
bands now move the middle band's queues to a spare L1 node of the port at
the bottom band's priority, and the two nodes share the port's DRR list
with weights scaled from the quanta. Weighting the queues instead would
split a band's share by how many of its hash queues are busy, and a DRR top
band is refused, because its node also carries multicast.

Measured on IPQ8074, n=3: quanta 3000:1000 give 69 and 18-21 Mbit/s, and
1500:1500 about 1:1.

Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Julius Bairaktaris <julius@bairaktaris.de>
Link: https://github.com/openwrt/openwrt/pull/25700
Signed-off-by: Robert Marko <robimarko@gmail.com>
2026-10-18 12:50:08 +02:00
Julius Bairaktaris 17fd68c109 qualcommax: qca_ppe: offload mqprio min_rate
mqprio's min_rate is refused, although every scheduler node has a committed
and an excess token bucket. A class's min_rate now goes in the committed
bucket and the difference up to max_rate in the excess one, a floor without
a max_rate leaves the excess unmetered, and both buckets share one token
unit chosen for the faster of the two. The port serves committed traffic
ahead of excess, so when any class has a floor, a class without one sends
only as excess: its committed bucket is armed empty and its max_rate goes
in the excess one.

Measured on IPQ8074 on a 100 Mbit/s port, n=3: a class with a 60 Mbit/s
floor against a strict top class carries 52-53 Mbit/s of TCP goodput, and 0
without the floor.

Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Julius Bairaktaris <julius@bairaktaris.de>
Link: https://github.com/openwrt/openwrt/pull/25700
Signed-off-by: Robert Marko <robimarko@gmail.com>
2026-10-18 12:50:08 +02:00
Julius Bairaktaris da6e886853 qualcommax: qca_ppe: offload the red qdisc
The queue manager has WRED on every unicast queue, and nothing programs it.
A red at the root or under a root tbf now covers the port's unicast queues,
one under a band of an offloaded ets or prio covers that band's queues, and
its stats and xstats come from those queues' counters, which also give a
tbf under the top band its stats. Its max becomes a static queue limit and
its min the gap below it where early drop starts, in 256-byte buffers and
colour blind, and ECN marking is refused because the queue manager only
drops. The drop probability is fixed in hardware, so the probability is
ignored with a one-time notice: measured on IPQ8074 with a 50 Mbit/s tbf
and red min 30000 max 90000, under UDP at 1.6, 2.4 and 4 times the rate
(n=3 each), the early-drop probability reaches about 0.66 at max.

Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Julius Bairaktaris <julius@bairaktaris.de>
Link: https://github.com/openwrt/openwrt/pull/25700
Signed-off-by: Robert Marko <robimarko@gmail.com>
2026-10-18 12:50:08 +02:00
Julius Bairaktaris 7ed28a5b6b qualcommax: qca_ppe: offload tbf under the top ets/prio band
Only a root tbf is offloaded, and TC_ETS_GRAFT and TC_PRIO_GRAFT are
refused, so a rate on one band of an offloaded ets or prio stays in
software. A tbf on the top band now programs that band's L1 node shaper,
which also carries the port's multicast queues and sparse list. The lower
two bands share one L1 node and are four L0 queues each, so no single
bucket meters one of them, and a tbf there is refused.

Measured on IPQ8074 on a 100 Mbit/s port, n=3: a 20 Mbit/s tbf on the top
band holds it to 18 Mbit/s, against 94 Mbit/s without the tbf.

Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Julius Bairaktaris <julius@bairaktaris.de>
Link: https://github.com/openwrt/openwrt/pull/25700
Signed-off-by: Robert Marko <robimarko@gmail.com>
2026-10-18 12:50:08 +02:00
Julius Bairaktaris dae98fe7f5 qualcommax: qca_ppe: add per-queue and per-colour ethtool -S counters
ethtool -S reports only the MAC MIB, while the per-queue transmit counters,
the per-colour queue drop counters, the buffer manager drop counters and
the port meter colour counters are read only by debugfs. Each user port now
lists its own unicast and multicast queues, its buffer manager port and its
meter. The packet counts are 32 bits and the byte counts 40, so they are
folded with the MIB to survive a wrap.

Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Julius Bairaktaris <julius@bairaktaris.de>
Link: https://github.com/openwrt/openwrt/pull/25700
Signed-off-by: Robert Marko <robimarko@gmail.com>
2026-10-18 12:50:08 +02:00
Julius Bairaktaris 7e2ad821fa qualcommax: qca_ppe: report rx_dropped and tx_dropped in get_stats64
The port receiver's drop counter and the port's egress drop counter are
read only by debugfs, so `ip -s link` shows no drops for a port. Both are
32-bit packet counters outside the MAC, so they are folded with the MIB
counters to survive a wrap, and unlike the MAC MIB a port reset does not
clear them.

Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Julius Bairaktaris <julius@bairaktaris.de>
Link: https://github.com/openwrt/openwrt/pull/25700
Signed-off-by: Robert Marko <robimarko@gmail.com>
2026-10-18 12:50:08 +02:00
Julius Bairaktaris f89340fa5f qualcommax: qca_ppe: offload FLOW_ACTION_RX_QUEUE_MAPPING
tc reaches the classifier's queue action only through ethtool n-tuple.
Accept skbedit queue_mapping with skip_sw, as ice does, by redirecting the
frame to the CPU port and selecting that queue among the CPU port's twelve
unicast queues.

Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Julius Bairaktaris <julius@bairaktaris.de>
Link: https://github.com/openwrt/openwrt/pull/25700
Signed-off-by: Robert Marko <robimarko@gmail.com>
2026-10-18 12:50:08 +02:00
Julius Bairaktaris 167b1d8d1d qualcommax: qca_ppe: match ARP sender and target IP
The IPv4 address rule types carry an L3 packet type, one value of which is
ARP, but FLOW_DISSECTOR_KEY_ARP is refused. Match flower's arp_sip and
arp_tip in the source and destination address of those rule types with the
packet type set to ARP. Opcode and hardware addresses stay refused.

Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Julius Bairaktaris <julius@bairaktaris.de>
Link: https://github.com/openwrt/openwrt/pull/25700
Signed-off-by: Robert Marko <robimarko@gmail.com>
2026-10-18 12:50:08 +02:00
Julius Bairaktaris b213b14de6 qualcommax: qca_ppe: match ethtool user-def data in UDF windows
The parser extracts four 16-bit user-defined fields per packet class at a
configurable offset from the L2, L3 or L4 header, and two ACL rule types
compare them, but nothing programs them. Match the ethtool n-tuple user-def
field there. It is laid out as i40e does, the word in bits 15:0 and the
byte offset in bits 31:16, with the header in bits 33:32. The windows are
shared by all rules of a class and refcounted.

Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Julius Bairaktaris <julius@bairaktaris.de>
Link: https://github.com/openwrt/openwrt/pull/25700
Signed-off-by: Robert Marko <robimarko@gmail.com>
2026-10-18 12:50:08 +02:00
Julius Bairaktaris 222e61fdf7 qualcommax: qca_ppe: match IP keys inside a PPPoE session
A flower filter with ppp_proto is refused, so no rule can match the
addresses or ports of traffic inside a PPPoE session. For ppp_proto ip or
ipv6, flower sets n_proto to the IP ethertype, which the classifier matches
with the IPv6 bit of the IP misc rule type and not with the ethertype field
the session match uses. Accept those two and keep refusing every other
ppp_proto.

On IPQ8074 a filter on an address inside a session matches its packets and
none of the control.

Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Julius Bairaktaris <julius@bairaktaris.de>
Link: https://github.com/openwrt/openwrt/pull/25700
Signed-off-by: Robert Marko <robimarko@gmail.com>
2026-10-18 12:50:08 +02:00
Julius Bairaktaris c9a39ff6a0 qualcommax: qca_ppe: match the VLAN DEI bit
ethtool's vlan_tci mask hands the DEI bit to the driver, which ignores it,
so an n-tuple rule naming DEI matches either value. The VLAN rule type
compares the C-tag DEI in bit 19 of its first word; match it there.

Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Julius Bairaktaris <julius@bairaktaris.de>
Link: https://github.com/openwrt/openwrt/pull/25700
Signed-off-by: Robert Marko <robimarko@gmail.com>
2026-10-18 12:50:08 +02:00
Julius Bairaktaris 757aab1037 qualcommax: qca_ppe: match IP TTL and hop limit of 0, 1 or 255
The IP misc rule type encodes the IPv4 TTL and IPv6 hop limit in two bits
as 0, 1, 255 or any other value. Offload a flower ip_ttl match on one of
those three values with a full mask and keep refusing every other TTL
match.

Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Julius Bairaktaris <julius@bairaktaris.de>
Link: https://github.com/openwrt/openwrt/pull/25700
Signed-off-by: Robert Marko <robimarko@gmail.com>
2026-10-18 12:50:08 +02:00
Julius Bairaktaris ac95cc8fee qualcommax: qca_ppe: match FLOW_DIS_FIRST_FRAG
The IP misc rule type has a first-fragment bit next to the fragment bit,
but flower's firstfrag flag is refused. Map FLOW_DIS_FIRST_FRAG to it.

Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Julius Bairaktaris <julius@bairaktaris.de>
Link: https://github.com/openwrt/openwrt/pull/25700
Signed-off-by: Robert Marko <robimarko@gmail.com>
2026-10-18 12:50:08 +02:00
Julius Bairaktaris ec172b8391 qualcommax: qca_ppe: offload packet-rate police
Both the ACL meter and the port meter have a meter unit bit that makes them
count frames instead of bytes, but the driver refuses rate_pkt_ps. Program
a packet rate in that mode. In packet mode a frame costs 128 times the
tokens a byte costs in byte mode at every token unit, as in the rate tables
of qca-ssdk adpt_hppe_policer.c, so the existing byte rate helper serves
both.

Measured on IPQ8074 at 4100 packets/s, n=3 at each of two frame sizes: an
ACL police passes 4100-4110 packets/s, and a packet-rate port police holds
its rate in the same test.

Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Julius Bairaktaris <julius@bairaktaris.de>
Link: https://github.com/openwrt/openwrt/pull/25700
Signed-off-by: Robert Marko <robimarko@gmail.com>
2026-10-18 12:50:08 +02:00
Julius Bairaktaris f92180d67b qualcommax: qca_ppe: accept FLOW_ACTION_CSUM after a DSCP rewrite
A pedit of the IPv4 DSCP needs "action csum iph" to be correct in software,
and the classifier refuses the csum action, so the rewrite cannot be
offloaded as tc users write it. Accept csum when it updates only the IPv4
header checksum and the rule also rewrites the DSCP, which the egress
editor does with the checksum fixed up.

Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Julius Bairaktaris <julius@bairaktaris.de>
Link: https://github.com/openwrt/openwrt/pull/25700
Signed-off-by: Robert Marko <robimarko@gmail.com>
2026-10-18 12:50:08 +02:00
Julius Bairaktaris c072765405 qualcommax: qca_ppe: add cls_flower_stats
An offloaded flower filter reports no packets, because the ACL hit counters
are read only by debugfs. Add .cls_flower_stats: sum the hit counters of
every entry the rule occupies and report the red count of its meter as
drops. Both are cleared when the entry or meter is freed, so the next rule
starts at zero, and filters that ask for a hw_stats type other than
immediate are refused.

Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Julius Bairaktaris <julius@bairaktaris.de>
Link: https://github.com/openwrt/openwrt/pull/25700
Signed-off-by: Robert Marko <robimarko@gmail.com>
2026-10-18 12:50:08 +02:00
Julius Bairaktaris 74646c5680 qualcommax: qca_ppe: offload active-backup bonds
Only hashing bonds are offloaded, because a frame arriving on a trunk
member is switched in hardware and never reaches the bond that would drop
it on a backup link. Accept NETDEV_LAG_TX_TYPE_ACTIVEBACKUP: the trunk
buckets already hold only the transmitting member, and a backup member's
isolation bitmap is now limited to the CPU, with learning off, so what
arrives on it reaches only the bond. The hash field register is left to the
hashing groups.

On IPQ8074, with an active-backup bond over two ports, isolation and
learning swap between the members on failover, the buckets follow the
active member, and the peer stays reachable over the active link.

Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Julius Bairaktaris <julius@bairaktaris.de>
Link: https://github.com/openwrt/openwrt/pull/25700
Signed-off-by: Robert Marko <robimarko@gmail.com>
2026-10-18 12:50:08 +02:00
Julius Bairaktaris a36d3d3ab0 qualcommax: qca_ppe: add devlink resources and regions for the L2 tables
Only the ACL table is reported as a devlink resource, so a full FDB, VSI or
VLAN translation table shows up only as a refused request. Register "fdb"
(2048), "vsi" (32) and "vlan_xlt" (64) with occupancy getters, and add
devlink regions of the same names that snapshot the raw table entries, as
mv88e6xxx does for its ATU and VTU. FDB occupancy counts the valid entries,
learned ones included, by reading the table back.

Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Julius Bairaktaris <julius@bairaktaris.de>
Link: https://github.com/openwrt/openwrt/pull/25700
Signed-off-by: Robert Marko <robimarko@gmail.com>
2026-10-18 12:50:08 +02:00
Julius Bairaktaris 9fcbcffc3d qualcommax: qca_ppe: offload VLAN ingress filtering
On a VLAN-filtering port, a frame whose VLAN matches no translation rule is
redirected to the CPU, so every frame in a VLAN the port is not a member of
reaches the bridge only to be dropped there. Every member VLAN and the PVID
have a rule naming the port, so drop the misses in hardware instead. The
link-local trap bypasses the ingress VLAN filter, as the bridge takes those
frames before filtering.

On IPQ8074 frames in a non-member VLAN are dropped with drop code 109, and
frames in a member VLAN pass.

Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Julius Bairaktaris <julius@bairaktaris.de>
Link: https://github.com/openwrt/openwrt/pull/25700
Signed-off-by: Robert Marko <robimarko@gmail.com>
2026-10-18 12:50:08 +02:00
Julius Bairaktaris f9690b582e qualcommax: qca_ppe: classify priority-tagged frames by the PVID
The PVID translation rule matches untagged frames only, so a
priority-tagged frame (VID 0) on a VLAN-filtering port misses every rule
and is redirected to the CPU, where the bridge assigns it to the PVID. Add
the priority-tagged format to the PVID rule's C-tag match so the switch
does the same.

On IPQ8074 priority-tagged frames are switched in the PVID's VLAN. Whether
such a frame leaves a tagged port with the PVID in place of VID 0 is not
measured, because the bench peer on the tagged port cannot capture.

Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Julius Bairaktaris <julius@bairaktaris.de>
Link: https://github.com/openwrt/openwrt/pull/25700
Signed-off-by: Robert Marko <robimarko@gmail.com>
2026-10-18 12:50:08 +02:00
Julius Bairaktaris 6c100e4a3a qualcommax: qca_ppe: offload BR_PORT_LOCKED and BR_PORT_MAB
A locked bridge port must forward only frames from sources the bridge has
authorized. Offload BR_PORT_LOCKED by turning hardware learning off and
redirecting frames with an unknown or moved source address to the CPU,
through the port's new-address and station-move commands, as mv88e6xxx does
for a locked port. The bridge then drops them, or under BR_PORT_MAB records
the source as a locked FDB entry, which needs nothing further from the
switch.

Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Julius Bairaktaris <julius@bairaktaris.de>
Link: https://github.com/openwrt/openwrt/pull/25700
Signed-off-by: Robert Marko <robimarko@gmail.com>
2026-10-18 12:50:08 +02:00
Julius Bairaktaris e0db3c82ec qualcommax: qca_ppe: trap IGMP and MLD to the CPU
IGMP and MLD frames are switched like any other multicast, so a report
addressed to a group that already has an MDB entry goes only to the group's
member ports, the bridge's multicast snooping never sees it and the
membership expires. Redirect IGMP and MLD from the user ports to the CPU
with an APP_CTRL protocol entry, as mv88e6xxx does with IGMP_MLD_SNOOP; the
bridge forwards them in software.

On IPQ8074 reports reach the other ports only as forwarded by the CPU.

Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Julius Bairaktaris <julius@bairaktaris.de>
Link: https://github.com/openwrt/openwrt/pull/25700
Signed-off-by: Robert Marko <robimarko@gmail.com>
2026-10-18 12:50:07 +02:00
Julius Bairaktaris 245b6e5354 qualcommax: qca_ppe: trap all IEEE 802.1D link-local frames
Only 01:80:c2:00:00:00 and :02 are trapped to the CPU, so the switch floods
the rest of the reserved block, EAPOL (:03) and LLDP (:0e) among them,
across a bridge that never forwards them. Trap the whole 01:80:c2:00:00:0x
range, one RFDB profile per address, as mv88e6xxx_rsvd2cpu_setup() does.
The bridge still forwards what group_fwd_mask allows, in software.

On IPQ8074 link-local frames reach the CPU and none is flooded.

Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Julius Bairaktaris <julius@bairaktaris.de>
Link: https://github.com/openwrt/openwrt/pull/25700
Signed-off-by: Robert Marko <robimarko@gmail.com>
2026-10-18 12:50:07 +02:00
Julius Bairaktaris 9b37a18516 qualcommax: qca_ppe: enable FDB isolation
Without ds->fdb_isolation, DSA keys its host address refcount on bridge
number 0 for every bridge, so a host address shared by two bridges is
installed in the first bridge's VSI only, and in the second bridge it
floods to every member port as unknown unicast. Set fdb_isolation and bound
max_num_bridges by the VSIs left after VSI 0, which carries the standalone
ports. Standalone host addresses (DSA_DB_PORT) now arrive as well and are
installed in VSI 0.

Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Julius Bairaktaris <julius@bairaktaris.de>
Link: https://github.com/openwrt/openwrt/pull/25700
Signed-off-by: Robert Marko <robimarko@gmail.com>
2026-10-18 12:50:07 +02:00
Julius Bairaktaris c830b453f1 qualcommax: qca_ppe: report qdisc backlog for offloaded root qdiscs
The tbf, ets and prio offloads report bytes, packets and drops, but no
backlog, so tc shows an empty queue while the hardware queues hold frames.
Sum the port's unicast and multicast queue occupancy and report it as the
qdisc backlog. Occupancy is counted in 256-byte buffers, so the backlog
moves in steps of 256 bytes.

Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Julius Bairaktaris <julius@bairaktaris.de>
Link: https://github.com/openwrt/openwrt/pull/25700
Signed-off-by: Robert Marko <robimarko@gmail.com>
2026-10-18 12:50:07 +02:00
Julius Bairaktaris f2c521a537 qualcommax: qca_ppe: send pause frames when TX pause is negotiated
The MAC sends a pause frame only when its buffer-manager port asks for one,
and the driver leaves flow control off on every physical BM port, so a link
that negotiates TX pause never sends one. Set the BM port's flow control
from tx_pause in mac_link_up, as qca-ssdk does in
adpt_hppe_port_txfc_status_set().

While any port, class or the CPU port carries a shaper, no BM port asks
for pause: a port that pauses moves the shaper's queue into the device on
the other end, where SQM cannot reach it. With a 165 Mbit/s upload shaper
and a Windows client, ping under load rose by 64 to 92 ms with pause and
by 2 ms without.

On IPQ8074, with TX pause on lan2 and UDP from a lan2 host to a 100 Mbit/s
lan3 link, the sender receives every pause frame the router sends and is
throttled to the egress rate without drops; with BM flow control off no
pause frame is sent and the egress queue drops.

Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Julius Bairaktaris <julius@bairaktaris.de>
Link: https://github.com/openwrt/openwrt/pull/25700
Signed-off-by: Robert Marko <robimarko@gmail.com>
2026-10-18 12:50:07 +02:00
Julius Bairaktaris da43d4a364 qualcommax: qca_edma: add ethtool interrupt coalescing
The RX and TX completion rings interrupt on every frame, because their
frame thresholds are zero and a zero threshold disables the moderation
timer. Add get_coalesce and set_coalesce for rx/tx-usecs and
rx/tx-max-frames; the timer counts 128 cycles of the EDMA clock. Default
to 16 frames, or 25 us on RX and 64 us on TX. On IPQ8074 with
router-terminated TCP at 941 Mbit/s, n=3, this takes 29k instead of
47-51k interrupts per second and 45-47% instead of 56-62% CPU, for 20 us
more idle ping latency.

Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Julius Bairaktaris <julius@bairaktaris.de>
Link: https://github.com/openwrt/openwrt/pull/25700
Signed-off-by: Robert Marko <robimarko@gmail.com>
2026-10-18 12:50:07 +02:00
Julius Bairaktaris 9082eff766 qualcommax: qca_ppe: add ethtool standard stats and get_ts_info
User ports report their MAC MIB counters only through ethtool -S. Add
get_eth_mac_stats, get_eth_ctrl_stats, get_rmon_stats and get_pause_stats
from the same totals; the RMON histogram merges the GMAC bins above 1518
into the one bin the XGMAC has, and counters neither MAC keeps are left
unset. Add get_ts_info too, so the core reports software timestamping, as
the driver takes no hardware timestamps.

Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Julius Bairaktaris <julius@bairaktaris.de>
Link: https://github.com/openwrt/openwrt/pull/25700
Signed-off-by: Robert Marko <robimarko@gmail.com>
2026-10-18 12:50:07 +02:00
Julius Bairaktaris 7becdde213 qualcommax: qca_ppe: add flower vlan, ethertype and PPPoE keys and act_vlan
The flower offload refuses VLAN, ethertype and PPPoE session keys and vlan
actions, though the ACL engine has rule types and a tag edit for them.
Encode the VLAN key into rule type 2, ethertype and PPPoE session into type
3, and vlan push, modify and pop into the action's tag edit. The tag-format
field holds one format, not a bitmap, and a single 802.1Q tag classifies
into the C slot, so a one-tag key is written as both slots and an 802.1ad
or second tag is refused.

Measured on IPQ8074, each key with a wrong-value control: vid, ethertype
and session filters drop only their frames, and a tag edit puts vid 99
priority 5 on the wire.

Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Julius Bairaktaris <julius@bairaktaris.de>
Link: https://github.com/openwrt/openwrt/pull/25700
Signed-off-by: Robert Marko <robimarko@gmail.com>
2026-10-18 12:50:07 +02:00
Julius Bairaktaris 7a2f2c6b2f qualcommax: qca_ppe: shape the CPU port's third band via cpu_port_rate
The CPU port has no netdev, so no qdisc can shape traffic towards Wi-Fi
clients in the switch, and priorities 8 and up fold into its bulk band.
Give it the user ports' third band on queues 8 to 11, under the port's
second L1 node a strict priority above the first, and add a
cpu_port_rate devlink parameter, in kbit/s, that programs that node's
shaper with a 10 ms burst and gives each band queue a quarter of 10 ms
of the rate as its admission limit. The burst is 10 ms because the host
drains the ring in bursts, and a one-frame bucket falls well short of
the rate.

Measured on IPQ8074 with an HE160 client at 305 Mbit/s, three interleaved
runs: 279.0/279.8/275.4 Mbit/s at 16 to 20% CPU, against 287.0/285.1/285.6
at 32 to 33% for tbf behind an ifb.

Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Julius Bairaktaris <julius@bairaktaris.de>
Link: https://github.com/openwrt/openwrt/pull/25700
Signed-off-by: Robert Marko <robimarko@gmail.com>
2026-10-18 12:50:07 +02:00
Julius Bairaktaris aaa4378dce qualcommax: qca_ppe: add devlink info and clear port meter credit
devlink dev info reports no chip id, and the ingress port meter keeps its
credit when a police action is removed. Report the device and revision
fields of the switch id register as the generic asic id and revision;
device 0x15 with revision 0 is IPQ807x and 1 is IPQ6018, and probe does not
depend on it. Clear both words of the port's meter credit entry when the
rate goes to zero, as the port shaper already does.

On IPQ8074 the credit entry reads zero after the police action is deleted,
and a re-armed police holds its rate.

Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Julius Bairaktaris <julius@bairaktaris.de>
Link: https://github.com/openwrt/openwrt/pull/25700
Signed-off-by: Robert Marko <robimarko@gmail.com>
2026-10-18 12:50:07 +02:00
Julius Bairaktaris 478da5ef34 qualcommax: qca_ppe: flush a port's queues on port_disable
Disabling a port leaves its queued frames holding buffers charged to the
admission and shared groups, so every other port is admitted against a
smaller pool. On port_disable, stop each unicast and multicast queue's
enqueue and dequeue, flush it through FLUSH_CFG and wait for completion,
one queue at a time with the QM clock gate dropped around the pass. The
gates stay closed until port_enable, and the CPU port is skipped.

On IPQ8074 a full queue drains to empty when its port goes down and fills
again once the port is back up.

Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Julius Bairaktaris <julius@bairaktaris.de>
Link: https://github.com/openwrt/openwrt/pull/25700
Signed-off-by: Robert Marko <robimarko@gmail.com>
2026-10-18 12:50:07 +02:00
Julius Bairaktaris cfaf5b3e4c qualcommax: qca_ppe: add LAG offload
The L2 block has two trunk groups, and without port_lag_join a bond over
switch ports stays in software. Implement port_lag_join, port_lag_leave,
port_lag_change and the LAG FDB ops; port_lag_change rebuilds the eight
hash buckets from the transmitting members, which is the failover.
num_lag_ids is 2, only hash-mode bonds are offloaded, and a second LAG must
hash on the same fields, since TRUNK_HASH_FIELD at 0x002068 in the ingress
parser is shared. Flooding is limited to one member per LAG, and a fast age
on a member ages the LAG.

On IPQ8074 a layer2 bond programs the hash field and the buckets, and the
peer is reachable through the bond; LACP and traffic over both links are
not measured, because the bench has no aggregation partner.

Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Julius Bairaktaris <julius@bairaktaris.de>
Link: https://github.com/openwrt/openwrt/pull/25700
Signed-off-by: Robert Marko <robimarko@gmail.com>
2026-10-18 12:50:07 +02:00
Julius Bairaktaris 4bbf87b1ce qualcommax: qca_ppe: add devlink resource and shared buffer support
Classifier occupancy, the buffer pool limits and a port's registers are
fixed or hidden in the driver. Register the ACL table as a devlink resource
with an occupancy callback, and the four BM and four QM groups as the
ingress and egress pools of one devlink shared buffer, settable except as a
static pool, and add get_regs on user ports as address and value pairs.
Pool occupancy is left out, because the hardware keeps no watermark.

Measured on IPQ8074 (the bench has no IPQ6018 board): the resource reads 2
of 512, and ingress pool 0 reads 358400 bytes and resizes.

Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Julius Bairaktaris <julius@bairaktaris.de>
Link: https://github.com/openwrt/openwrt/pull/25700
Signed-off-by: Robert Marko <robimarko@gmail.com>
2026-10-18 12:50:07 +02:00
Julius Bairaktaris e265fdad9a qualcommax: qca_edma: add ethtool ring resizing
The conduit's ring sizes are fixed at 128 and 2048, and ethtool cannot
change them. Add set_ringparam on the EDMA conduit, with power-of-two
rings between 64 and 8192, and reuse the reconfigure path for the resize,
which puts the old rings back if the new ones cannot be allocated.

Measured on IPQ8074: the rings resize from 128/2048 to 256/1024 and back
with the datapath intact.

Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Julius Bairaktaris <julius@bairaktaris.de>
Link: https://github.com/openwrt/openwrt/pull/25700
Signed-off-by: Robert Marko <robimarko@gmail.com>
2026-10-18 12:50:07 +02:00