-
Notifications
You must be signed in to change notification settings - Fork 71
Add hw_ppe.qos: shape in the switch where the switch offloads the flow #193
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
JuliusBairaktaris
wants to merge
2
commits into
tohojo:main
Choose a base branch
from
JuliusBairaktaris:hw-ppe-offload
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+242
−5
Open
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,222 @@ | ||
| ################################################################################ | ||
| # hw_ppe.qos (Qualcomm PPE hardware shaper) | ||
| # | ||
| # Abstract: | ||
| # Shapes in the switch rather than on the CPU, so that connections the kernel | ||
| # has offloaded to the PPE flow engine - which no qdisc on this box ever sees | ||
| # again - are shaped too. | ||
| # | ||
| # Egress is a tbf on the port itself, which the qca_ppe driver programs into | ||
| # that port's hardware token bucket. No packet is redirected, which is what | ||
| # lets an offloaded flow be governed at all. | ||
| # | ||
| # The switch cannot queue a packet arriving on a port, so the download | ||
| # direction is shaped where it leaves - but only the download. Every user | ||
| # port's scheduler keeps a band of queues that no frame selects on its own, | ||
| # and a classifier rule on the WAN port marks each arriving frame addressed | ||
| # to the router into that band: that is every routed download frame and | ||
| # nothing bridged. An mqprio class over the band carries the download rate, | ||
| # which the driver programs into the band's own scheduler node. A transfer | ||
| # between two local ports never wears the mark and passes at line rate; the | ||
| # port's own token bucket stays unprogrammed. | ||
| # | ||
| # Frames no longer than the driver's small-packet cutoff are promoted past | ||
| # the band by the classifier rule the driver owns, so acks, DNS and voice | ||
| # keep jumping the standing bulk queue exactly as they do on the shaped | ||
| # egress port. | ||
| # | ||
| # What the switch does not forward in hardware reaches the host by the CPU | ||
| # port, and only that - an offloaded flow's packets never do: the flows the | ||
| # flow table does not hold, and the path to Wi-Fi clients, whose frames all | ||
| # take it. That port has no netdev for a qdisc to name, so the driver's | ||
| # cpu_port_rate parameter carries the download rate to the same band on it, | ||
| # and the switch shapes that remainder too. Local traffic to a Wi-Fi client | ||
| # crosses the CPU port unmarked and is not shaped. | ||
| # | ||
| # The user ports' bands and the CPU port's each carry the full download | ||
| # rate; a workload split across both at once can therefore exceed it while | ||
| # the split lasts. Sustaining that takes a flow table too full to take new | ||
| # flows, which is thousands of concurrent bulk flows. | ||
| # | ||
| # Configure this on the physical WAN port (wan), not on a pppoe or vlan | ||
| # device above it - the hardware only knows the port. | ||
| # | ||
| ################################################################################ | ||
| # | ||
| # This program is free software; you can redistribute it and/or modify | ||
| # it under the terms of the GNU General Public License version 2 as | ||
| # published by the Free Software Foundation. | ||
| # | ||
| # Copyright (C) 2026 Julius Bairaktaris | ||
| # | ||
| ################################################################################ | ||
|
|
||
| . ${SQM_LIB_DIR}/defaults.sh | ||
|
|
||
| # The egress bulk queue can afford to be deep here, because the switch | ||
| # schedules higher-priority queues past it: latency-sensitive traffic rides | ||
| # those, not this queue. Measured on a 165 Mbit/s uplink, 1 ms of queue caps | ||
| # a single TCP flow at 50 Mbit/s where 8 ms delivers 162, and only traffic | ||
| # inside the bulk queue pays the 8 ms. | ||
| [ -n "$ESHAPER_QUEUE_DEFAULTED" ] && ESHAPER_QUEUE_DUR_US=8000 | ||
|
|
||
| # The internal priority whose queue class is the download band on every user | ||
| # port of this switch. | ||
| DL_BAND_PRIO=8 | ||
|
|
||
|
|
||
| ################################################################################ | ||
|
|
||
| egress() { | ||
| local burst limit min_limit | ||
|
|
||
| MTU=$(get_mtu $IFACE) | ||
| burst="$(get_burst ${MTU:-1514} ${UPLINK} ${ESHAPER_BURST_DUR_US})" | ||
| burst=${burst:-1514} | ||
|
|
||
| # The tbf limit is how deep the queue behind the shaper may get, and a | ||
| # driver that drains a real queue sizes it from this, so it is the number | ||
| # that decides the latency the shaper adds for the traffic queued in it. | ||
| # A queue of fewer than about eight full frames cannot hold a single | ||
| # flow at the rate it is shaped to, so that is the floor. | ||
| limit=$(( UPLINK * ESHAPER_QUEUE_DUR_US / 8000 )) | ||
| min_limit=$(( (${MTU:-1514} + 18) * 8 )) | ||
| [ $limit -lt $min_limit ] && limit=$min_limit | ||
| [ $limit -lt $burst ] && limit=$burst | ||
|
|
||
| SILENT=1 $TC qdisc del dev $IFACE root | ||
|
|
||
| # The root tbf is what the driver turns into the port's token bucket and | ||
| # queue limit. The qdisc below it shapes the CPU path, which is where | ||
| # anything not offloaded still travels. | ||
| $TC qdisc add dev $IFACE root handle 1: tbf \ | ||
| rate ${UPLINK}kbit burst $burst limit $limit | ||
| $TC qdisc add dev $IFACE parent 1: handle 110: $QDISC \ | ||
| $(get_limit ${ELIMIT}) $(get_target "${ETARGET}" ${UPLINK}) \ | ||
| $(get_ecn ${EECN}) $(get_flows ${UPLINK}) ${EQDISC_OPTS} | ||
| } | ||
|
|
||
| # The other ports of the switch this interface belongs to, taken from what is | ||
| # present rather than from a name or a count - the port set differs across | ||
| # IPQ807x boards. Also what a teardown walks, so that it reaches the same ports | ||
| # without having to know what the configuration was when they came up. | ||
| ingress_ports() { | ||
| local sw id dev | ||
|
|
||
| sw="$(cat /sys/class/net/$IFACE/phys_switch_id 2>/dev/null)" | ||
| [ -n "$sw" ] || return 1 | ||
|
|
||
| for id in /sys/class/net/*/phys_switch_id ; do | ||
| dev="${id%/phys_switch_id}" ; dev="${dev##*/}" | ||
| [ "$dev" = "$IFACE" ] && continue | ||
| [ "$(cat $id 2>/dev/null)" = "$sw" ] && echo "$dev" | ||
| done | ||
| } | ||
|
|
||
| ingress() { | ||
| local dev ports wanmac | ||
|
|
||
| if ! ports="$(ingress_ports)" ; then | ||
| sqm_error "$IFACE is not a switch port; the download direction needs one" | ||
| return 1 | ||
| fi | ||
|
|
||
| # The download band, shaped. The first class is every queue the port | ||
| # serves unmarked traffic from and carries no rate; the second is the | ||
| # band the WAN mark selects, and its rate goes to the band's own | ||
| # scheduler node, so only what wears the mark is shaped. hw 1 makes an | ||
| # offload failure a failure, the same stance skip_sw takes for a filter. | ||
| for dev in $ports ; do | ||
| SILENT=1 $TC qdisc del dev $dev root | ||
| $TC qdisc add dev $dev root handle 1: mqprio num_tc 2 \ | ||
| map 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 queues 8@0 4@8 \ | ||
| hw 1 mode channel shaper bw_rlimit \ | ||
| max_rate 0 ${DOWNLINK}kbit || return 1 | ||
| done | ||
|
|
||
| # The mark. A frame arriving on the WAN port addressed to the port is a | ||
| # routed download frame - bridged traffic is addressed to the host | ||
| # behind a local port, never to this one. | ||
| wanmac="$(cat /sys/class/net/$IFACE/address)" | ||
| $TC qdisc add dev $IFACE handle ffff: ingress 2>/dev/null | ||
| SILENT=1 $TC filter del dev $IFACE parent ffff: pref 511 | ||
| $TC filter add dev $IFACE parent ffff: pref 511 protocol all flower \ | ||
| skip_sw dst_mac $wanmac \ | ||
| action skbedit priority $DL_BAND_PRIO || return 1 | ||
|
|
||
| # The remainder: what the switch did not forward in hardware leaves for | ||
| # the host by the CPU port, and the mark selects the same band there. | ||
| # The driver sizes that band's queue from the rate. | ||
| echo ${DOWNLINK} > /sys/module/qca_ppe/parameters/cpu_port_rate || return 1 | ||
| } | ||
|
|
||
| # What ingress() built, deleted from what is present rather than from the | ||
| # configuration, so that it reaches the same ports whatever the configuration | ||
| # was when they came up. | ||
| ingress_teardown() { | ||
| local dev | ||
|
|
||
| SILENT=1 $TC qdisc del dev $IFACE ingress | ||
| for dev in $(ingress_ports) ; do | ||
| SILENT=1 $TC qdisc del dev $dev root | ||
| done | ||
| [ -w /sys/module/qca_ppe/parameters/cpu_port_rate ] && \ | ||
| echo 0 > /sys/module/qca_ppe/parameters/cpu_port_rate | ||
| } | ||
|
|
||
| # sqm_start_default allocates an ifb before either direction runs and fails | ||
| # hard without one; nothing here needs one, so this is that function without | ||
| # the allocation. | ||
| sqm_start() { | ||
| [ -n "$IFACE" ] || return 1 | ||
|
|
||
| nft_log_restart | ||
|
|
||
| if fn_exists sqm_prepare_script ; then | ||
| sqm_prepare_script || return 1 | ||
| fi | ||
|
|
||
| do_modules | ||
| verify_qdisc $QDISC || return 1 | ||
|
|
||
| if [ "${UPLINK}" -ne 0 ] ; then | ||
| CUR_DIRECTION="egress" | ||
| egress | ||
| else | ||
| SILENT=1 $TC qdisc del dev ${IFACE} root | ||
| fi | ||
|
|
||
| if [ "${DOWNLINK}" -ne 0 ] ; then | ||
| CUR_DIRECTION="ingress" | ||
| ingress || return 1 | ||
| else | ||
| ingress_teardown | ||
| fi | ||
|
|
||
| return 0 | ||
| } | ||
|
|
||
| sqm_stop() { | ||
| ingress_teardown | ||
| SILENT=1 $TC qdisc del dev $IFACE root | ||
| nft_cleanup | ||
| return 0 | ||
| } | ||
|
|
||
| sqm_prepare_script() { | ||
| do_modules | ||
| verify_qdisc "tbf" || return 1 | ||
|
|
||
| case $QDISC in | ||
| cake*) | ||
| sqm_warn "cake cannot be offloaded to the PPE; using fq_codel for the CPU path" | ||
| QDISC=fq_codel ;; | ||
| esac | ||
|
|
||
| case $LLAM in | ||
| htb_private|tc_stab) | ||
| sqm_warn "link layer adjustment is not expressible in the PPE shaper; the hardware meters the wire frame including preamble, gap and CRC" ;; | ||
| esac | ||
| } | ||
|
|
||
| ################################################################################ |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| Shapes in the switch instead of on the CPU, so that connections offloaded to the PPE flow engine are shaped as well - a software qdisc never sees those packets. Egress becomes the port's hardware token bucket, with the bulk queue behind it sized to eshaper_queue_dur_us (default 8 ms) of the shaped rate: deep enough for a single TCP flow to fill the uplink, while latency-sensitive traffic rides the switch's higher-priority queues past it (see the ppe-qos package for the classifiers). Ingress is shaped where it leaves the switch, but only the download: a classifier rule on the WAN port marks arriving routed frames, the mark selects a reserved band of queues on whichever local port the frame leaves by, and the band's scheduler node carries the download rate. Traffic between local ports never wears the mark and passes at line rate. What the hardware does not forward leaves for the CPU by the switch's CPU port, and the same mark selects the download band there, which the driver's cpu_port_rate parameter shapes - that covers the Wi-Fi path and the flows the switch's flow table does not hold, with no packet redirected and nothing shaped by the CPU. Set this on the physical WAN port (wan), not on a pppoe or vlan device above it. Requires a switch driver that offloads tbf, mqprio rate limits and flower on ingress; the WAN filter is installed skip_sw and the mqprio hw-mandatory, so both fail loudly rather than silently degrading to the CPU. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This one