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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>