Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

Merge branch 'dualpi2-patch'

Chia-Yu Chang says:

====================
DUALPI2 patch

This patch serise adds DualPI Improved with a Square (DualPI2) with
following features:
* Supports congestion controls that comply with the Prague requirements
in RFC9331 (e.g. TCP-Prague)
* Coupled dual-queue that separates the L4S traffic in a low latency
queue (L-queue), without harming remaining traffic that is scheduled
in classic queue (C-queue) due to congestion-coupling using PI2
as defined in RFC9332
* Configurable overload strategies
* Use of sojourn time to reliably estimate queue delay
* Supports ECN L4S-identifier (IP.ECN==0b*1) to classify traffic into
respective queues

For more details of DualPI2, please refer IETF RFC9332
(https://datatracker.ietf.org/doc/html/rfc9332).
====================

Link: https://patch.msgid.link/20250722095915.24485-1-chia-yu.chang@nokia-bell-labs.com
Signed-off-by: Jakub Kicinski <kuba@kernel.org>

+1669 -5
+149 -2
Documentation/netlink/specs/tc.yaml
··· 57 57 - tunoam 58 58 - tuncrit 59 59 - 60 + name: dualpi2-drop-overload 61 + type: enum 62 + entries: [overflow, drop] 63 + - 64 + name: dualpi2-drop-early 65 + type: enum 66 + entries: [drop-dequeue, drop-enqueue] 67 + - 68 + name: dualpi2-ecn-mask 69 + type: enum 70 + value-start: 1 71 + entries: [l4s-ect, cla-ect, any-ect] 72 + - 73 + name: dualpi2-split-gso 74 + type: enum 75 + entries: [no-split-gso, split-gso] 76 + - 60 77 name: tc-stats 61 78 type: struct 62 79 members: ··· 843 826 name: drop-overmemory 844 827 type: u32 845 828 - 829 + name: tc-dualpi2-xstats 830 + type: struct 831 + members: 832 + - 833 + name: prob 834 + type: u32 835 + doc: Current base PI probability 836 + - 837 + name: delay-c 838 + type: u32 839 + doc: Current C-queue delay in microseconds 840 + - 841 + name: delay-l 842 + type: u32 843 + doc: Current L-queue delay in microseconds 844 + - 845 + name: pkts-in-c 846 + type: u32 847 + doc: Number of packets enqueued in the C-queue 848 + - 849 + name: pkts-in-l 850 + type: u32 851 + doc: Number of packets enqueued in the L-queue 852 + - 853 + name: maxq 854 + type: u32 855 + doc: Maximum number of packets seen by the DualPI2 856 + - 857 + name: ecn-mark 858 + type: u32 859 + doc: All packets marked with ECN 860 + - 861 + name: step-mark 862 + type: u32 863 + doc: Only packets marked with ECN due to L-queue step AQM 864 + - 865 + name: credit 866 + type: s32 867 + doc: Current credit value for WRR 868 + - 869 + name: memory-used 870 + type: u32 871 + doc: Memory used in bytes by the DualPI2 872 + - 873 + name: max-memory-used 874 + type: u32 875 + doc: Maximum memory used in bytes by the DualPI2 876 + - 877 + name: memory-limit 878 + type: u32 879 + doc: Memory limit in bytes 880 + - 846 881 name: tc-fq-pie-xstats 847 882 type: struct 848 883 members: ··· 917 848 - 918 849 name: ecn-mark 919 850 type: u32 920 - doc: Packets marked with ecn 851 + doc: Packets marked with ECN 921 852 - 922 853 name: new-flow-count 923 854 type: u32 ··· 1060 991 - 1061 992 name: ecn-mark 1062 993 type: u32 1063 - doc: Packets marked with ecn 994 + doc: Packets marked with ECN 1064 995 - 1065 996 name: tc-red-xstats 1066 997 type: struct ··· 2353 2284 - 2354 2285 name: quantum 2355 2286 type: u32 2287 + - 2288 + name: dualpi2-attrs 2289 + name-prefix: tca-dualpi2- 2290 + attributes: 2291 + - 2292 + name: limit 2293 + type: u32 2294 + doc: Limit of total number of packets in queue 2295 + - 2296 + name: memory-limit 2297 + type: u32 2298 + doc: Memory limit of total number of packets in queue 2299 + - 2300 + name: target 2301 + type: u32 2302 + doc: Classic target delay in microseconds 2303 + - 2304 + name: tupdate 2305 + type: u32 2306 + doc: Drop probability update interval time in microseconds 2307 + - 2308 + name: alpha 2309 + type: u32 2310 + doc: Integral gain factor in Hz for PI controller 2311 + - 2312 + name: beta 2313 + type: u32 2314 + doc: Proportional gain factor in Hz for PI controller 2315 + - 2316 + name: step-thresh-pkts 2317 + type: u32 2318 + doc: L4S step marking threshold in packets 2319 + - 2320 + name: step-thresh-us 2321 + type: u32 2322 + doc: L4S Step marking threshold in microseconds 2323 + - 2324 + name: min-qlen-step 2325 + type: u32 2326 + doc: Packets enqueued to the L-queue can apply the step threshold 2327 + when the queue length of L-queue is larger than this value. 2328 + (0 is recommended) 2329 + - 2330 + name: coupling 2331 + type: u8 2332 + doc: Probability coupling factor between Classic and L4S 2333 + (2 is recommended) 2334 + - 2335 + name: drop-overload 2336 + type: u8 2337 + doc: Control the overload strategy (drop to preserve latency or 2338 + let the queue overflow) 2339 + enum: dualpi2-drop-overload 2340 + - 2341 + name: drop-early 2342 + type: u8 2343 + doc: Decide where the Classic packets are PI-based dropped or marked 2344 + enum: dualpi2-drop-early 2345 + - 2346 + name: c-protection 2347 + type: u8 2348 + doc: Classic WRR weight in percentage (from 0 to 100) 2349 + - 2350 + name: ecn-mask 2351 + type: u8 2352 + doc: Configure the L-queue ECN classifier 2353 + enum: dualpi2-ecn-mask 2354 + - 2355 + name: split-gso 2356 + type: u8 2357 + doc: Split aggregated skb or not 2358 + enum: dualpi2-split-gso 2356 2359 - 2357 2360 name: ematch-attrs 2358 2361 name-prefix: tca-ematch- ··· 3850 3709 value: drr 3851 3710 attribute-set: drr-attrs 3852 3711 - 3712 + value: dualpi2 3713 + attribute-set: dualpi2-attrs 3714 + - 3853 3715 value: etf 3854 3716 attribute-set: etf-attrs 3855 3717 - ··· 4019 3875 - 4020 3876 value: codel 4021 3877 fixed-header: tc-codel-xstats 3878 + - 3879 + value: dualpi2 3880 + fixed-header: tc-dualpi2-xstats 4022 3881 - 4023 3882 value: fq 4024 3883 fixed-header: tc-fq-qd-stats
+6
include/net/dropreason-core.h
··· 126 126 FN(CANFD_RX_INVALID_FRAME) \ 127 127 FN(CANXL_RX_INVALID_FRAME) \ 128 128 FN(PFMEMALLOC) \ 129 + FN(DUALPI2_STEP_DROP) \ 129 130 FNe(MAX) 130 131 131 132 /** ··· 605 604 * reached a path or socket not eligible for use of memory reserves 606 605 */ 607 606 SKB_DROP_REASON_PFMEMALLOC, 607 + /** 608 + * @SKB_DROP_REASON_DUALPI2_STEP_DROP: dropped by the step drop 609 + * threshold of DualPI2 qdisc. 610 + */ 611 + SKB_DROP_REASON_DUALPI2_STEP_DROP, 608 612 /** 609 613 * @SKB_DROP_REASON_MAX: the maximum of core drop reasons, which 610 614 * shouldn't be used as a real 'reason' - only for tracing code gen
+68
include/uapi/linux/pkt_sched.h
··· 1211 1211 1212 1212 #define TCA_ETS_MAX (__TCA_ETS_MAX - 1) 1213 1213 1214 + /* DUALPI2 */ 1215 + enum tc_dualpi2_drop_overload { 1216 + TC_DUALPI2_DROP_OVERLOAD_OVERFLOW = 0, 1217 + TC_DUALPI2_DROP_OVERLOAD_DROP = 1, 1218 + __TCA_DUALPI2_DROP_OVERLOAD_MAX, 1219 + }; 1220 + #define TCA_DUALPI2_DROP_OVERLOAD_MAX (__TCA_DUALPI2_DROP_OVERLOAD_MAX - 1) 1221 + 1222 + enum tc_dualpi2_drop_early { 1223 + TC_DUALPI2_DROP_EARLY_DROP_DEQUEUE = 0, 1224 + TC_DUALPI2_DROP_EARLY_DROP_ENQUEUE = 1, 1225 + __TCA_DUALPI2_DROP_EARLY_MAX, 1226 + }; 1227 + #define TCA_DUALPI2_DROP_EARLY_MAX (__TCA_DUALPI2_DROP_EARLY_MAX - 1) 1228 + 1229 + enum tc_dualpi2_ecn_mask { 1230 + TC_DUALPI2_ECN_MASK_L4S_ECT = 1, 1231 + TC_DUALPI2_ECN_MASK_CLA_ECT = 2, 1232 + TC_DUALPI2_ECN_MASK_ANY_ECT = 3, 1233 + __TCA_DUALPI2_ECN_MASK_MAX, 1234 + }; 1235 + #define TCA_DUALPI2_ECN_MASK_MAX (__TCA_DUALPI2_ECN_MASK_MAX - 1) 1236 + 1237 + enum tc_dualpi2_split_gso { 1238 + TC_DUALPI2_SPLIT_GSO_NO_SPLIT_GSO = 0, 1239 + TC_DUALPI2_SPLIT_GSO_SPLIT_GSO = 1, 1240 + __TCA_DUALPI2_SPLIT_GSO_MAX, 1241 + }; 1242 + #define TCA_DUALPI2_SPLIT_GSO_MAX (__TCA_DUALPI2_SPLIT_GSO_MAX - 1) 1243 + 1244 + enum { 1245 + TCA_DUALPI2_UNSPEC, 1246 + TCA_DUALPI2_LIMIT, /* Packets */ 1247 + TCA_DUALPI2_MEMORY_LIMIT, /* Bytes */ 1248 + TCA_DUALPI2_TARGET, /* us */ 1249 + TCA_DUALPI2_TUPDATE, /* us */ 1250 + TCA_DUALPI2_ALPHA, /* Hz scaled up by 256 */ 1251 + TCA_DUALPI2_BETA, /* Hz scaled up by 256 */ 1252 + TCA_DUALPI2_STEP_THRESH_PKTS, /* Step threshold in packets */ 1253 + TCA_DUALPI2_STEP_THRESH_US, /* Step threshold in microseconds */ 1254 + TCA_DUALPI2_MIN_QLEN_STEP, /* Minimum qlen to apply STEP_THRESH */ 1255 + TCA_DUALPI2_COUPLING, /* Coupling factor between queues */ 1256 + TCA_DUALPI2_DROP_OVERLOAD, /* Whether to drop on overload */ 1257 + TCA_DUALPI2_DROP_EARLY, /* Whether to drop on enqueue */ 1258 + TCA_DUALPI2_C_PROTECTION, /* Percentage */ 1259 + TCA_DUALPI2_ECN_MASK, /* L4S queue classification mask */ 1260 + TCA_DUALPI2_SPLIT_GSO, /* Split GSO packets at enqueue */ 1261 + TCA_DUALPI2_PAD, 1262 + __TCA_DUALPI2_MAX 1263 + }; 1264 + 1265 + #define TCA_DUALPI2_MAX (__TCA_DUALPI2_MAX - 1) 1266 + 1267 + struct tc_dualpi2_xstats { 1268 + __u32 prob; /* current probability */ 1269 + __u32 delay_c; /* current delay in C queue */ 1270 + __u32 delay_l; /* current delay in L queue */ 1271 + __u32 packets_in_c; /* number of packets enqueued in C queue */ 1272 + __u32 packets_in_l; /* number of packets enqueued in L queue */ 1273 + __u32 maxq; /* maximum queue size */ 1274 + __u32 ecn_mark; /* packets marked with ecn*/ 1275 + __u32 step_marks; /* ECN marks due to the step AQM */ 1276 + __s32 credit; /* current c_protection credit */ 1277 + __u32 memory_used; /* Memory used by both queues */ 1278 + __u32 max_memory_used; /* Maximum used memory */ 1279 + __u32 memory_limit; /* Memory limit of both queues */ 1280 + }; 1281 + 1214 1282 #endif
+12
net/sched/Kconfig
··· 415 415 416 416 If unsure, say N. 417 417 418 + config NET_SCH_DUALPI2 419 + tristate "Dual Queue PI Square (DUALPI2) scheduler" 420 + help 421 + Say Y here if you want to use the Dual Queue Proportional Integral 422 + Controller Improved with a Square scheduling algorithm. 423 + For more information, please see https://tools.ietf.org/html/rfc9332 424 + 425 + To compile this driver as a module, choose M here: the module 426 + will be called sch_dualpi2. 427 + 428 + If unsure, say N. 429 + 418 430 menuconfig NET_SCH_DEFAULT 419 431 bool "Allow override default queue discipline" 420 432 help
+1
net/sched/Makefile
··· 63 63 obj-$(CONFIG_NET_SCH_ETF) += sch_etf.o 64 64 obj-$(CONFIG_NET_SCH_TAPRIO) += sch_taprio.o 65 65 obj-$(CONFIG_NET_SCH_BPF) += bpf_qdisc.o 66 + obj-$(CONFIG_NET_SCH_DUALPI2) += sch_dualpi2.o 66 67 67 68 obj-$(CONFIG_NET_CLS_U32) += cls_u32.o 68 69 obj-$(CONFIG_NET_CLS_ROUTE4) += cls_route.o
+1175
net/sched/sch_dualpi2.c
··· 1 + // SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause 2 + /* Copyright (C) 2024 Nokia 3 + * 4 + * Author: Koen De Schepper <koen.de_schepper@nokia-bell-labs.com> 5 + * Author: Olga Albisser <olga@albisser.org> 6 + * Author: Henrik Steen <henrist@henrist.net> 7 + * Author: Olivier Tilmans <olivier.tilmans@nokia.com> 8 + * Author: Chia-Yu Chang <chia-yu.chang@nokia-bell-labs.com> 9 + * 10 + * DualPI Improved with a Square (dualpi2): 11 + * - Supports congestion controls that comply with the Prague requirements 12 + * in RFC9331 (e.g. TCP-Prague) 13 + * - Supports coupled dual-queue with PI2 as defined in RFC9332 14 + * - Supports ECN L4S-identifier (IP.ECN==0b*1) 15 + * 16 + * note: Although DCTCP and BBRv3 can use shallow-threshold ECN marks, 17 + * they do not meet the 'Prague L4S Requirements' listed in RFC 9331 18 + * Section 4, so they can only be used with DualPI2 in a datacenter 19 + * context. 20 + * 21 + * References: 22 + * - RFC9332: https://datatracker.ietf.org/doc/html/rfc9332 23 + * - De Schepper, Koen, et al. "PI 2: A linearized AQM for both classic and 24 + * scalable TCP." in proc. ACM CoNEXT'16, 2016. 25 + */ 26 + 27 + #include <linux/errno.h> 28 + #include <linux/hrtimer.h> 29 + #include <linux/if_vlan.h> 30 + #include <linux/kernel.h> 31 + #include <linux/limits.h> 32 + #include <linux/module.h> 33 + #include <linux/skbuff.h> 34 + #include <linux/types.h> 35 + 36 + #include <net/gso.h> 37 + #include <net/inet_ecn.h> 38 + #include <net/pkt_cls.h> 39 + #include <net/pkt_sched.h> 40 + 41 + /* 32b enable to support flows with windows up to ~8.6 * 1e9 packets 42 + * i.e., twice the maximal snd_cwnd. 43 + * MAX_PROB must be consistent with the RNG in dualpi2_roll(). 44 + */ 45 + #define MAX_PROB U32_MAX 46 + 47 + /* alpha/beta values exchanged over netlink are in units of 256ns */ 48 + #define ALPHA_BETA_SHIFT 8 49 + 50 + /* Scaled values of alpha/beta must fit in 32b to avoid overflow in later 51 + * computations. Consequently (see and dualpi2_scale_alpha_beta()), their 52 + * netlink-provided values can use at most 31b, i.e. be at most (2^23)-1 53 + * (~4MHz) as those are given in 1/256th. This enable to tune alpha/beta to 54 + * control flows whose maximal RTTs can be in usec up to few secs. 55 + */ 56 + #define ALPHA_BETA_MAX ((1U << 31) - 1) 57 + 58 + /* Internal alpha/beta are in units of 64ns. 59 + * This enables to use all alpha/beta values in the allowed range without loss 60 + * of precision due to rounding when scaling them internally, e.g., 61 + * scale_alpha_beta(1) will not round down to 0. 62 + */ 63 + #define ALPHA_BETA_GRANULARITY 6 64 + 65 + #define ALPHA_BETA_SCALING (ALPHA_BETA_SHIFT - ALPHA_BETA_GRANULARITY) 66 + 67 + /* We express the weights (wc, wl) in %, i.e., wc + wl = 100 */ 68 + #define MAX_WC 100 69 + 70 + struct dualpi2_sched_data { 71 + struct Qdisc *l_queue; /* The L4S Low latency queue (L-queue) */ 72 + struct Qdisc *sch; /* The Classic queue (C-queue) */ 73 + 74 + /* Registered tc filters */ 75 + struct tcf_proto __rcu *tcf_filters; 76 + struct tcf_block *tcf_block; 77 + 78 + /* PI2 parameters */ 79 + u64 pi2_target; /* Target delay in nanoseconds */ 80 + u32 pi2_tupdate; /* Timer frequency in nanoseconds */ 81 + u32 pi2_prob; /* Base PI probability */ 82 + u32 pi2_alpha; /* Gain factor for the integral rate response */ 83 + u32 pi2_beta; /* Gain factor for the proportional response */ 84 + struct hrtimer pi2_timer; /* prob update timer */ 85 + 86 + /* Step AQM (L-queue only) parameters */ 87 + u32 step_thresh; /* Step threshold */ 88 + bool step_in_packets; /* Step thresh in packets (1) or time (0) */ 89 + 90 + /* C-queue starvation protection */ 91 + s32 c_protection_credit; /* Credit (sign indicates which queue) */ 92 + s32 c_protection_init; /* Reset value of the credit */ 93 + u8 c_protection_wc; /* C-queue weight (between 0 and MAX_WC) */ 94 + u8 c_protection_wl; /* L-queue weight (MAX_WC - wc) */ 95 + 96 + /* General dualQ parameters */ 97 + u32 memory_limit; /* Memory limit of both queues */ 98 + u8 coupling_factor;/* Coupling factor (k) between both queues */ 99 + u8 ecn_mask; /* Mask to match packets into L-queue */ 100 + u32 min_qlen_step; /* Minimum queue length to apply step thresh */ 101 + bool drop_early; /* Drop at enqueue (1) instead of dequeue (0) */ 102 + bool drop_overload; /* Drop (1) on overload, or overflow (0) */ 103 + bool split_gso; /* Split aggregated skb (1) or leave as is (0) */ 104 + 105 + /* Statistics */ 106 + u64 c_head_ts; /* Enqueue timestamp of the C-queue head */ 107 + u64 l_head_ts; /* Enqueue timestamp of the L-queue head */ 108 + u64 last_qdelay; /* Q delay val at the last probability update */ 109 + u32 packets_in_c; /* Enqueue packet counter of the C-queue */ 110 + u32 packets_in_l; /* Enqueue packet counter of the L-queue */ 111 + u32 maxq; /* Maximum queue size of the C-queue */ 112 + u32 ecn_mark; /* ECN mark pkt counter due to PI probability */ 113 + u32 step_marks; /* ECN mark pkt counter due to step AQM */ 114 + u32 memory_used; /* Memory used of both queues */ 115 + u32 max_memory_used;/* Maximum used memory */ 116 + 117 + /* Deferred drop statistics */ 118 + u32 deferred_drops_cnt; /* Packets dropped */ 119 + u32 deferred_drops_len; /* Bytes dropped */ 120 + }; 121 + 122 + struct dualpi2_skb_cb { 123 + u64 ts; /* Timestamp at enqueue */ 124 + u8 apply_step:1, /* Can we apply the step threshold */ 125 + classified:2, /* Packet classification results */ 126 + ect:2; /* Packet ECT codepoint */ 127 + }; 128 + 129 + enum dualpi2_classification_results { 130 + DUALPI2_C_CLASSIC = 0, /* C-queue */ 131 + DUALPI2_C_L4S = 1, /* L-queue (scale mark/classic drop) */ 132 + DUALPI2_C_LLLL = 2, /* L-queue (no drops/marks) */ 133 + __DUALPI2_C_MAX /* Keep last*/ 134 + }; 135 + 136 + static struct dualpi2_skb_cb *dualpi2_skb_cb(struct sk_buff *skb) 137 + { 138 + qdisc_cb_private_validate(skb, sizeof(struct dualpi2_skb_cb)); 139 + return (struct dualpi2_skb_cb *)qdisc_skb_cb(skb)->data; 140 + } 141 + 142 + static u64 dualpi2_sojourn_time(struct sk_buff *skb, u64 reference) 143 + { 144 + return reference - dualpi2_skb_cb(skb)->ts; 145 + } 146 + 147 + static u64 head_enqueue_time(struct Qdisc *q) 148 + { 149 + struct sk_buff *skb = qdisc_peek_head(q); 150 + 151 + return skb ? dualpi2_skb_cb(skb)->ts : 0; 152 + } 153 + 154 + static u32 dualpi2_scale_alpha_beta(u32 param) 155 + { 156 + u64 tmp = ((u64)param * MAX_PROB >> ALPHA_BETA_SCALING); 157 + 158 + do_div(tmp, NSEC_PER_SEC); 159 + return tmp; 160 + } 161 + 162 + static u32 dualpi2_unscale_alpha_beta(u32 param) 163 + { 164 + u64 tmp = ((u64)param * NSEC_PER_SEC << ALPHA_BETA_SCALING); 165 + 166 + do_div(tmp, MAX_PROB); 167 + return tmp; 168 + } 169 + 170 + static ktime_t next_pi2_timeout(struct dualpi2_sched_data *q) 171 + { 172 + return ktime_add_ns(ktime_get_ns(), q->pi2_tupdate); 173 + } 174 + 175 + static bool skb_is_l4s(struct sk_buff *skb) 176 + { 177 + return dualpi2_skb_cb(skb)->classified == DUALPI2_C_L4S; 178 + } 179 + 180 + static bool skb_in_l_queue(struct sk_buff *skb) 181 + { 182 + return dualpi2_skb_cb(skb)->classified != DUALPI2_C_CLASSIC; 183 + } 184 + 185 + static bool skb_apply_step(struct sk_buff *skb, struct dualpi2_sched_data *q) 186 + { 187 + return skb_is_l4s(skb) && qdisc_qlen(q->l_queue) >= q->min_qlen_step; 188 + } 189 + 190 + static bool dualpi2_mark(struct dualpi2_sched_data *q, struct sk_buff *skb) 191 + { 192 + if (INET_ECN_set_ce(skb)) { 193 + q->ecn_mark++; 194 + return true; 195 + } 196 + return false; 197 + } 198 + 199 + static void dualpi2_reset_c_protection(struct dualpi2_sched_data *q) 200 + { 201 + q->c_protection_credit = q->c_protection_init; 202 + } 203 + 204 + /* This computes the initial credit value and WRR weight for the L queue (wl) 205 + * from the weight of the C queue (wc). 206 + * If wl > wc, the scheduler will start with the L queue when reset. 207 + */ 208 + static void dualpi2_calculate_c_protection(struct Qdisc *sch, 209 + struct dualpi2_sched_data *q, u32 wc) 210 + { 211 + q->c_protection_wc = wc; 212 + q->c_protection_wl = MAX_WC - wc; 213 + q->c_protection_init = (s32)psched_mtu(qdisc_dev(sch)) * 214 + ((int)q->c_protection_wc - (int)q->c_protection_wl); 215 + dualpi2_reset_c_protection(q); 216 + } 217 + 218 + static bool dualpi2_roll(u32 prob) 219 + { 220 + return get_random_u32() <= prob; 221 + } 222 + 223 + /* Packets in the C-queue are subject to a marking probability pC, which is the 224 + * square of the internal PI probability (i.e., have an overall lower mark/drop 225 + * probability). If the qdisc is overloaded, ignore ECT values and only drop. 226 + * 227 + * Note that this marking scheme is also applied to L4S packets during overload. 228 + * Return true if packet dropping is required in C queue 229 + */ 230 + static bool dualpi2_classic_marking(struct dualpi2_sched_data *q, 231 + struct sk_buff *skb, u32 prob, 232 + bool overload) 233 + { 234 + if (dualpi2_roll(prob) && dualpi2_roll(prob)) { 235 + if (overload || dualpi2_skb_cb(skb)->ect == INET_ECN_NOT_ECT) 236 + return true; 237 + dualpi2_mark(q, skb); 238 + } 239 + return false; 240 + } 241 + 242 + /* Packets in the L-queue are subject to a marking probability pL given by the 243 + * internal PI probability scaled by the coupling factor. 244 + * 245 + * On overload (i.e., @local_l_prob is >= 100%): 246 + * - if the qdisc is configured to trade losses to preserve latency (i.e., 247 + * @q->drop_overload), apply classic drops first before marking. 248 + * - otherwise, preserve the "no loss" property of ECN at the cost of queueing 249 + * delay, eventually resulting in taildrop behavior once sch->limit is 250 + * reached. 251 + * Return true if packet dropping is required in L queue 252 + */ 253 + static bool dualpi2_scalable_marking(struct dualpi2_sched_data *q, 254 + struct sk_buff *skb, 255 + u64 local_l_prob, u32 prob, 256 + bool overload) 257 + { 258 + if (overload) { 259 + /* Apply classic drop */ 260 + if (!q->drop_overload || 261 + !(dualpi2_roll(prob) && dualpi2_roll(prob))) 262 + goto mark; 263 + return true; 264 + } 265 + 266 + /* We can safely cut the upper 32b as overload==false */ 267 + if (dualpi2_roll(local_l_prob)) { 268 + /* Non-ECT packets could have classified as L4S by filters. */ 269 + if (dualpi2_skb_cb(skb)->ect == INET_ECN_NOT_ECT) 270 + return true; 271 + mark: 272 + dualpi2_mark(q, skb); 273 + } 274 + return false; 275 + } 276 + 277 + /* Decide whether a given packet must be dropped (or marked if ECT), according 278 + * to the PI2 probability. 279 + * 280 + * Never mark/drop if we have a standing queue of less than 2 MTUs. 281 + */ 282 + static bool must_drop(struct Qdisc *sch, struct dualpi2_sched_data *q, 283 + struct sk_buff *skb) 284 + { 285 + u64 local_l_prob; 286 + bool overload; 287 + u32 prob; 288 + 289 + if (sch->qstats.backlog < 2 * psched_mtu(qdisc_dev(sch))) 290 + return false; 291 + 292 + prob = READ_ONCE(q->pi2_prob); 293 + local_l_prob = (u64)prob * q->coupling_factor; 294 + overload = local_l_prob > MAX_PROB; 295 + 296 + switch (dualpi2_skb_cb(skb)->classified) { 297 + case DUALPI2_C_CLASSIC: 298 + return dualpi2_classic_marking(q, skb, prob, overload); 299 + case DUALPI2_C_L4S: 300 + return dualpi2_scalable_marking(q, skb, local_l_prob, prob, 301 + overload); 302 + default: /* DUALPI2_C_LLLL */ 303 + return false; 304 + } 305 + } 306 + 307 + static void dualpi2_read_ect(struct sk_buff *skb) 308 + { 309 + struct dualpi2_skb_cb *cb = dualpi2_skb_cb(skb); 310 + int wlen = skb_network_offset(skb); 311 + 312 + switch (skb_protocol(skb, true)) { 313 + case htons(ETH_P_IP): 314 + wlen += sizeof(struct iphdr); 315 + if (!pskb_may_pull(skb, wlen) || 316 + skb_try_make_writable(skb, wlen)) 317 + goto not_ecn; 318 + 319 + cb->ect = ipv4_get_dsfield(ip_hdr(skb)) & INET_ECN_MASK; 320 + break; 321 + case htons(ETH_P_IPV6): 322 + wlen += sizeof(struct ipv6hdr); 323 + if (!pskb_may_pull(skb, wlen) || 324 + skb_try_make_writable(skb, wlen)) 325 + goto not_ecn; 326 + 327 + cb->ect = ipv6_get_dsfield(ipv6_hdr(skb)) & INET_ECN_MASK; 328 + break; 329 + default: 330 + goto not_ecn; 331 + } 332 + return; 333 + 334 + not_ecn: 335 + /* Non pullable/writable packets can only be dropped hence are 336 + * classified as not ECT. 337 + */ 338 + cb->ect = INET_ECN_NOT_ECT; 339 + } 340 + 341 + static int dualpi2_skb_classify(struct dualpi2_sched_data *q, 342 + struct sk_buff *skb) 343 + { 344 + struct dualpi2_skb_cb *cb = dualpi2_skb_cb(skb); 345 + struct tcf_result res; 346 + struct tcf_proto *fl; 347 + int result; 348 + 349 + dualpi2_read_ect(skb); 350 + if (cb->ect & q->ecn_mask) { 351 + cb->classified = DUALPI2_C_L4S; 352 + return NET_XMIT_SUCCESS; 353 + } 354 + 355 + if (TC_H_MAJ(skb->priority) == q->sch->handle && 356 + TC_H_MIN(skb->priority) < __DUALPI2_C_MAX) { 357 + cb->classified = TC_H_MIN(skb->priority); 358 + return NET_XMIT_SUCCESS; 359 + } 360 + 361 + fl = rcu_dereference_bh(q->tcf_filters); 362 + if (!fl) { 363 + cb->classified = DUALPI2_C_CLASSIC; 364 + return NET_XMIT_SUCCESS; 365 + } 366 + 367 + result = tcf_classify(skb, NULL, fl, &res, false); 368 + if (result >= 0) { 369 + #ifdef CONFIG_NET_CLS_ACT 370 + switch (result) { 371 + case TC_ACT_STOLEN: 372 + case TC_ACT_QUEUED: 373 + case TC_ACT_TRAP: 374 + return NET_XMIT_SUCCESS | __NET_XMIT_STOLEN; 375 + case TC_ACT_SHOT: 376 + return NET_XMIT_SUCCESS | __NET_XMIT_BYPASS; 377 + } 378 + #endif 379 + cb->classified = TC_H_MIN(res.classid) < __DUALPI2_C_MAX ? 380 + TC_H_MIN(res.classid) : DUALPI2_C_CLASSIC; 381 + } 382 + return NET_XMIT_SUCCESS; 383 + } 384 + 385 + static int dualpi2_enqueue_skb(struct sk_buff *skb, struct Qdisc *sch, 386 + struct sk_buff **to_free) 387 + { 388 + struct dualpi2_sched_data *q = qdisc_priv(sch); 389 + struct dualpi2_skb_cb *cb; 390 + 391 + if (unlikely(qdisc_qlen(sch) >= sch->limit) || 392 + unlikely((u64)q->memory_used + skb->truesize > q->memory_limit)) { 393 + qdisc_qstats_overlimit(sch); 394 + if (skb_in_l_queue(skb)) 395 + qdisc_qstats_overlimit(q->l_queue); 396 + return qdisc_drop_reason(skb, sch, to_free, 397 + SKB_DROP_REASON_QDISC_OVERLIMIT); 398 + } 399 + 400 + if (q->drop_early && must_drop(sch, q, skb)) { 401 + qdisc_drop_reason(skb, sch, to_free, 402 + SKB_DROP_REASON_QDISC_CONGESTED); 403 + return NET_XMIT_SUCCESS | __NET_XMIT_BYPASS; 404 + } 405 + 406 + cb = dualpi2_skb_cb(skb); 407 + cb->ts = ktime_get_ns(); 408 + q->memory_used += skb->truesize; 409 + if (q->memory_used > q->max_memory_used) 410 + q->max_memory_used = q->memory_used; 411 + 412 + if (qdisc_qlen(sch) > q->maxq) 413 + q->maxq = qdisc_qlen(sch); 414 + 415 + if (skb_in_l_queue(skb)) { 416 + /* Apply step thresh if skb is L4S && L-queue len >= min_qlen */ 417 + dualpi2_skb_cb(skb)->apply_step = skb_apply_step(skb, q); 418 + 419 + /* Keep the overall qdisc stats consistent */ 420 + ++sch->q.qlen; 421 + qdisc_qstats_backlog_inc(sch, skb); 422 + ++q->packets_in_l; 423 + if (!q->l_head_ts) 424 + q->l_head_ts = cb->ts; 425 + return qdisc_enqueue_tail(skb, q->l_queue); 426 + } 427 + ++q->packets_in_c; 428 + if (!q->c_head_ts) 429 + q->c_head_ts = cb->ts; 430 + return qdisc_enqueue_tail(skb, sch); 431 + } 432 + 433 + /* By default, dualpi2 will split GSO skbs into independent skbs and enqueue 434 + * each of those individually. This yields the following benefits, at the 435 + * expense of CPU usage: 436 + * - Finer-grained AQM actions as the sub-packets of a burst no longer share the 437 + * same fate (e.g., the random mark/drop probability is applied individually) 438 + * - Improved precision of the starvation protection/WRR scheduler at dequeue, 439 + * as the size of the dequeued packets will be smaller. 440 + */ 441 + static int dualpi2_qdisc_enqueue(struct sk_buff *skb, struct Qdisc *sch, 442 + struct sk_buff **to_free) 443 + { 444 + struct dualpi2_sched_data *q = qdisc_priv(sch); 445 + int err; 446 + 447 + err = dualpi2_skb_classify(q, skb); 448 + if (err != NET_XMIT_SUCCESS) { 449 + if (err & __NET_XMIT_BYPASS) 450 + qdisc_qstats_drop(sch); 451 + __qdisc_drop(skb, to_free); 452 + return err; 453 + } 454 + 455 + if (q->split_gso && skb_is_gso(skb)) { 456 + netdev_features_t features; 457 + struct sk_buff *nskb, *next; 458 + int cnt, byte_len, orig_len; 459 + int err; 460 + 461 + features = netif_skb_features(skb); 462 + nskb = skb_gso_segment(skb, features & ~NETIF_F_GSO_MASK); 463 + if (IS_ERR_OR_NULL(nskb)) 464 + return qdisc_drop(skb, sch, to_free); 465 + 466 + cnt = 1; 467 + byte_len = 0; 468 + orig_len = qdisc_pkt_len(skb); 469 + skb_list_walk_safe(nskb, nskb, next) { 470 + skb_mark_not_on_list(nskb); 471 + 472 + /* Iterate through GSO fragments of an skb: 473 + * (1) Set pkt_len from the single GSO fragments 474 + * (2) Copy classified and ect values of an skb 475 + * (3) Enqueue fragment & set ts in dualpi2_enqueue_skb 476 + */ 477 + qdisc_skb_cb(nskb)->pkt_len = nskb->len; 478 + dualpi2_skb_cb(nskb)->classified = 479 + dualpi2_skb_cb(skb)->classified; 480 + dualpi2_skb_cb(nskb)->ect = dualpi2_skb_cb(skb)->ect; 481 + err = dualpi2_enqueue_skb(nskb, sch, to_free); 482 + 483 + if (err == NET_XMIT_SUCCESS) { 484 + /* Compute the backlog adjustment that needs 485 + * to be propagated in the qdisc tree to reflect 486 + * all new skbs successfully enqueued. 487 + */ 488 + ++cnt; 489 + byte_len += nskb->len; 490 + } 491 + } 492 + if (cnt > 1) { 493 + /* The caller will add the original skb stats to its 494 + * backlog, compensate this if any nskb is enqueued. 495 + */ 496 + --cnt; 497 + byte_len -= orig_len; 498 + } 499 + qdisc_tree_reduce_backlog(sch, -cnt, -byte_len); 500 + consume_skb(skb); 501 + return err; 502 + } 503 + return dualpi2_enqueue_skb(skb, sch, to_free); 504 + } 505 + 506 + /* Select the queue from which the next packet can be dequeued, ensuring that 507 + * neither queue can starve the other with a WRR scheduler. 508 + * 509 + * The sign of the WRR credit determines the next queue, while the size of 510 + * the dequeued packet determines the magnitude of the WRR credit change. If 511 + * either queue is empty, the WRR credit is kept unchanged. 512 + * 513 + * As the dequeued packet can be dropped later, the caller has to perform the 514 + * qdisc_bstats_update() calls. 515 + */ 516 + static struct sk_buff *dequeue_packet(struct Qdisc *sch, 517 + struct dualpi2_sched_data *q, 518 + int *credit_change, 519 + u64 now) 520 + { 521 + struct sk_buff *skb = NULL; 522 + int c_len; 523 + 524 + *credit_change = 0; 525 + c_len = qdisc_qlen(sch) - qdisc_qlen(q->l_queue); 526 + if (qdisc_qlen(q->l_queue) && (!c_len || q->c_protection_credit <= 0)) { 527 + skb = __qdisc_dequeue_head(&q->l_queue->q); 528 + WRITE_ONCE(q->l_head_ts, head_enqueue_time(q->l_queue)); 529 + if (c_len) 530 + *credit_change = q->c_protection_wc; 531 + qdisc_qstats_backlog_dec(q->l_queue, skb); 532 + 533 + /* Keep the global queue size consistent */ 534 + --sch->q.qlen; 535 + q->memory_used -= skb->truesize; 536 + } else if (c_len) { 537 + skb = __qdisc_dequeue_head(&sch->q); 538 + WRITE_ONCE(q->c_head_ts, head_enqueue_time(sch)); 539 + if (qdisc_qlen(q->l_queue)) 540 + *credit_change = ~((s32)q->c_protection_wl) + 1; 541 + q->memory_used -= skb->truesize; 542 + } else { 543 + dualpi2_reset_c_protection(q); 544 + return NULL; 545 + } 546 + *credit_change *= qdisc_pkt_len(skb); 547 + qdisc_qstats_backlog_dec(sch, skb); 548 + return skb; 549 + } 550 + 551 + static int do_step_aqm(struct dualpi2_sched_data *q, struct sk_buff *skb, 552 + u64 now) 553 + { 554 + u64 qdelay = 0; 555 + 556 + if (q->step_in_packets) 557 + qdelay = qdisc_qlen(q->l_queue); 558 + else 559 + qdelay = dualpi2_sojourn_time(skb, now); 560 + 561 + if (dualpi2_skb_cb(skb)->apply_step && qdelay > q->step_thresh) { 562 + if (!dualpi2_skb_cb(skb)->ect) { 563 + /* Drop this non-ECT packet */ 564 + return 1; 565 + } 566 + 567 + if (dualpi2_mark(q, skb)) 568 + ++q->step_marks; 569 + } 570 + qdisc_bstats_update(q->l_queue, skb); 571 + return 0; 572 + } 573 + 574 + static void drop_and_retry(struct dualpi2_sched_data *q, struct sk_buff *skb, 575 + struct Qdisc *sch, enum skb_drop_reason reason) 576 + { 577 + ++q->deferred_drops_cnt; 578 + q->deferred_drops_len += qdisc_pkt_len(skb); 579 + kfree_skb_reason(skb, reason); 580 + qdisc_qstats_drop(sch); 581 + } 582 + 583 + static struct sk_buff *dualpi2_qdisc_dequeue(struct Qdisc *sch) 584 + { 585 + struct dualpi2_sched_data *q = qdisc_priv(sch); 586 + struct sk_buff *skb; 587 + int credit_change; 588 + u64 now; 589 + 590 + now = ktime_get_ns(); 591 + 592 + while ((skb = dequeue_packet(sch, q, &credit_change, now))) { 593 + if (!q->drop_early && must_drop(sch, q, skb)) { 594 + drop_and_retry(q, skb, sch, 595 + SKB_DROP_REASON_QDISC_CONGESTED); 596 + continue; 597 + } 598 + 599 + if (skb_in_l_queue(skb) && do_step_aqm(q, skb, now)) { 600 + qdisc_qstats_drop(q->l_queue); 601 + drop_and_retry(q, skb, sch, 602 + SKB_DROP_REASON_DUALPI2_STEP_DROP); 603 + continue; 604 + } 605 + 606 + q->c_protection_credit += credit_change; 607 + qdisc_bstats_update(sch, skb); 608 + break; 609 + } 610 + 611 + if (q->deferred_drops_cnt) { 612 + qdisc_tree_reduce_backlog(sch, q->deferred_drops_cnt, 613 + q->deferred_drops_len); 614 + q->deferred_drops_cnt = 0; 615 + q->deferred_drops_len = 0; 616 + } 617 + return skb; 618 + } 619 + 620 + static s64 __scale_delta(u64 diff) 621 + { 622 + do_div(diff, 1 << ALPHA_BETA_GRANULARITY); 623 + return diff; 624 + } 625 + 626 + static void get_queue_delays(struct dualpi2_sched_data *q, u64 *qdelay_c, 627 + u64 *qdelay_l) 628 + { 629 + u64 now, qc, ql; 630 + 631 + now = ktime_get_ns(); 632 + qc = READ_ONCE(q->c_head_ts); 633 + ql = READ_ONCE(q->l_head_ts); 634 + 635 + *qdelay_c = qc ? now - qc : 0; 636 + *qdelay_l = ql ? now - ql : 0; 637 + } 638 + 639 + static u32 calculate_probability(struct Qdisc *sch) 640 + { 641 + struct dualpi2_sched_data *q = qdisc_priv(sch); 642 + u32 new_prob; 643 + u64 qdelay_c; 644 + u64 qdelay_l; 645 + u64 qdelay; 646 + s64 delta; 647 + 648 + get_queue_delays(q, &qdelay_c, &qdelay_l); 649 + qdelay = max(qdelay_l, qdelay_c); 650 + 651 + /* Alpha and beta take at most 32b, i.e, the delay difference would 652 + * overflow for queuing delay differences > ~4.2sec. 653 + */ 654 + delta = ((s64)qdelay - (s64)q->pi2_target) * q->pi2_alpha; 655 + delta += ((s64)qdelay - (s64)q->last_qdelay) * q->pi2_beta; 656 + q->last_qdelay = qdelay; 657 + 658 + /* Bound new_prob between 0 and MAX_PROB */ 659 + if (delta > 0) { 660 + new_prob = __scale_delta(delta) + q->pi2_prob; 661 + if (new_prob < q->pi2_prob) 662 + new_prob = MAX_PROB; 663 + } else { 664 + new_prob = q->pi2_prob - __scale_delta(~delta + 1); 665 + if (new_prob > q->pi2_prob) 666 + new_prob = 0; 667 + } 668 + 669 + /* If we do not drop on overload, ensure we cap the L4S probability to 670 + * 100% to keep window fairness when overflowing. 671 + */ 672 + if (!q->drop_overload) 673 + return min_t(u32, new_prob, MAX_PROB / q->coupling_factor); 674 + return new_prob; 675 + } 676 + 677 + static u32 get_memory_limit(struct Qdisc *sch, u32 limit) 678 + { 679 + /* Apply rule of thumb, i.e., doubling the packet length, 680 + * to further include per packet overhead in memory_limit. 681 + */ 682 + u64 memlim = mul_u32_u32(limit, 2 * psched_mtu(qdisc_dev(sch))); 683 + 684 + if (upper_32_bits(memlim)) 685 + return U32_MAX; 686 + else 687 + return lower_32_bits(memlim); 688 + } 689 + 690 + static u32 convert_us_to_nsec(u32 us) 691 + { 692 + u64 ns = mul_u32_u32(us, NSEC_PER_USEC); 693 + 694 + if (upper_32_bits(ns)) 695 + return U32_MAX; 696 + 697 + return lower_32_bits(ns); 698 + } 699 + 700 + static u32 convert_ns_to_usec(u64 ns) 701 + { 702 + do_div(ns, NSEC_PER_USEC); 703 + if (upper_32_bits(ns)) 704 + return U32_MAX; 705 + 706 + return lower_32_bits(ns); 707 + } 708 + 709 + static enum hrtimer_restart dualpi2_timer(struct hrtimer *timer) 710 + { 711 + struct dualpi2_sched_data *q = timer_container_of(q, timer, pi2_timer); 712 + struct Qdisc *sch = q->sch; 713 + spinlock_t *root_lock; /* to lock qdisc for probability calculations */ 714 + 715 + rcu_read_lock(); 716 + root_lock = qdisc_lock(qdisc_root_sleeping(sch)); 717 + spin_lock(root_lock); 718 + 719 + WRITE_ONCE(q->pi2_prob, calculate_probability(sch)); 720 + hrtimer_set_expires(&q->pi2_timer, next_pi2_timeout(q)); 721 + 722 + spin_unlock(root_lock); 723 + rcu_read_unlock(); 724 + return HRTIMER_RESTART; 725 + } 726 + 727 + static struct netlink_range_validation dualpi2_alpha_beta_range = { 728 + .min = 1, 729 + .max = ALPHA_BETA_MAX, 730 + }; 731 + 732 + static const struct nla_policy dualpi2_policy[TCA_DUALPI2_MAX + 1] = { 733 + [TCA_DUALPI2_LIMIT] = NLA_POLICY_MIN(NLA_U32, 1), 734 + [TCA_DUALPI2_MEMORY_LIMIT] = NLA_POLICY_MIN(NLA_U32, 1), 735 + [TCA_DUALPI2_TARGET] = { .type = NLA_U32 }, 736 + [TCA_DUALPI2_TUPDATE] = NLA_POLICY_MIN(NLA_U32, 1), 737 + [TCA_DUALPI2_ALPHA] = 738 + NLA_POLICY_FULL_RANGE(NLA_U32, &dualpi2_alpha_beta_range), 739 + [TCA_DUALPI2_BETA] = 740 + NLA_POLICY_FULL_RANGE(NLA_U32, &dualpi2_alpha_beta_range), 741 + [TCA_DUALPI2_STEP_THRESH_PKTS] = { .type = NLA_U32 }, 742 + [TCA_DUALPI2_STEP_THRESH_US] = { .type = NLA_U32 }, 743 + [TCA_DUALPI2_MIN_QLEN_STEP] = { .type = NLA_U32 }, 744 + [TCA_DUALPI2_COUPLING] = NLA_POLICY_MIN(NLA_U8, 1), 745 + [TCA_DUALPI2_DROP_OVERLOAD] = 746 + NLA_POLICY_MAX(NLA_U8, TCA_DUALPI2_DROP_OVERLOAD_MAX), 747 + [TCA_DUALPI2_DROP_EARLY] = 748 + NLA_POLICY_MAX(NLA_U8, TCA_DUALPI2_DROP_EARLY_MAX), 749 + [TCA_DUALPI2_C_PROTECTION] = 750 + NLA_POLICY_RANGE(NLA_U8, 0, MAX_WC), 751 + [TCA_DUALPI2_ECN_MASK] = 752 + NLA_POLICY_RANGE(NLA_U8, TC_DUALPI2_ECN_MASK_L4S_ECT, 753 + TCA_DUALPI2_ECN_MASK_MAX), 754 + [TCA_DUALPI2_SPLIT_GSO] = 755 + NLA_POLICY_MAX(NLA_U8, TCA_DUALPI2_SPLIT_GSO_MAX), 756 + }; 757 + 758 + static int dualpi2_change(struct Qdisc *sch, struct nlattr *opt, 759 + struct netlink_ext_ack *extack) 760 + { 761 + struct nlattr *tb[TCA_DUALPI2_MAX + 1]; 762 + struct dualpi2_sched_data *q; 763 + int old_backlog; 764 + int old_qlen; 765 + int err; 766 + 767 + if (!opt || !nla_len(opt)) { 768 + NL_SET_ERR_MSG_MOD(extack, "Dualpi2 options are required"); 769 + return -EINVAL; 770 + } 771 + err = nla_parse_nested(tb, TCA_DUALPI2_MAX, opt, dualpi2_policy, 772 + extack); 773 + if (err < 0) 774 + return err; 775 + if (tb[TCA_DUALPI2_STEP_THRESH_PKTS] && tb[TCA_DUALPI2_STEP_THRESH_US]) { 776 + NL_SET_ERR_MSG_MOD(extack, "multiple step thresh attributes"); 777 + return -EINVAL; 778 + } 779 + 780 + q = qdisc_priv(sch); 781 + sch_tree_lock(sch); 782 + 783 + if (tb[TCA_DUALPI2_LIMIT]) { 784 + u32 limit = nla_get_u32(tb[TCA_DUALPI2_LIMIT]); 785 + 786 + WRITE_ONCE(sch->limit, limit); 787 + WRITE_ONCE(q->memory_limit, get_memory_limit(sch, limit)); 788 + } 789 + 790 + if (tb[TCA_DUALPI2_MEMORY_LIMIT]) 791 + WRITE_ONCE(q->memory_limit, 792 + nla_get_u32(tb[TCA_DUALPI2_MEMORY_LIMIT])); 793 + 794 + if (tb[TCA_DUALPI2_TARGET]) { 795 + u64 target = nla_get_u32(tb[TCA_DUALPI2_TARGET]); 796 + 797 + WRITE_ONCE(q->pi2_target, target * NSEC_PER_USEC); 798 + } 799 + 800 + if (tb[TCA_DUALPI2_TUPDATE]) { 801 + u64 tupdate = nla_get_u32(tb[TCA_DUALPI2_TUPDATE]); 802 + 803 + WRITE_ONCE(q->pi2_tupdate, convert_us_to_nsec(tupdate)); 804 + } 805 + 806 + if (tb[TCA_DUALPI2_ALPHA]) { 807 + u32 alpha = nla_get_u32(tb[TCA_DUALPI2_ALPHA]); 808 + 809 + WRITE_ONCE(q->pi2_alpha, dualpi2_scale_alpha_beta(alpha)); 810 + } 811 + 812 + if (tb[TCA_DUALPI2_BETA]) { 813 + u32 beta = nla_get_u32(tb[TCA_DUALPI2_BETA]); 814 + 815 + WRITE_ONCE(q->pi2_beta, dualpi2_scale_alpha_beta(beta)); 816 + } 817 + 818 + if (tb[TCA_DUALPI2_STEP_THRESH_PKTS]) { 819 + u32 step_th = nla_get_u32(tb[TCA_DUALPI2_STEP_THRESH_PKTS]); 820 + 821 + WRITE_ONCE(q->step_in_packets, true); 822 + WRITE_ONCE(q->step_thresh, step_th); 823 + } else if (tb[TCA_DUALPI2_STEP_THRESH_US]) { 824 + u32 step_th = nla_get_u32(tb[TCA_DUALPI2_STEP_THRESH_US]); 825 + 826 + WRITE_ONCE(q->step_in_packets, false); 827 + WRITE_ONCE(q->step_thresh, convert_us_to_nsec(step_th)); 828 + } 829 + 830 + if (tb[TCA_DUALPI2_MIN_QLEN_STEP]) 831 + WRITE_ONCE(q->min_qlen_step, 832 + nla_get_u32(tb[TCA_DUALPI2_MIN_QLEN_STEP])); 833 + 834 + if (tb[TCA_DUALPI2_COUPLING]) { 835 + u8 coupling = nla_get_u8(tb[TCA_DUALPI2_COUPLING]); 836 + 837 + WRITE_ONCE(q->coupling_factor, coupling); 838 + } 839 + 840 + if (tb[TCA_DUALPI2_DROP_OVERLOAD]) { 841 + u8 drop_overload = nla_get_u8(tb[TCA_DUALPI2_DROP_OVERLOAD]); 842 + 843 + WRITE_ONCE(q->drop_overload, (bool)drop_overload); 844 + } 845 + 846 + if (tb[TCA_DUALPI2_DROP_EARLY]) { 847 + u8 drop_early = nla_get_u8(tb[TCA_DUALPI2_DROP_EARLY]); 848 + 849 + WRITE_ONCE(q->drop_early, (bool)drop_early); 850 + } 851 + 852 + if (tb[TCA_DUALPI2_C_PROTECTION]) { 853 + u8 wc = nla_get_u8(tb[TCA_DUALPI2_C_PROTECTION]); 854 + 855 + dualpi2_calculate_c_protection(sch, q, wc); 856 + } 857 + 858 + if (tb[TCA_DUALPI2_ECN_MASK]) { 859 + u8 ecn_mask = nla_get_u8(tb[TCA_DUALPI2_ECN_MASK]); 860 + 861 + WRITE_ONCE(q->ecn_mask, ecn_mask); 862 + } 863 + 864 + if (tb[TCA_DUALPI2_SPLIT_GSO]) { 865 + u8 split_gso = nla_get_u8(tb[TCA_DUALPI2_SPLIT_GSO]); 866 + 867 + WRITE_ONCE(q->split_gso, (bool)split_gso); 868 + } 869 + 870 + old_qlen = qdisc_qlen(sch); 871 + old_backlog = sch->qstats.backlog; 872 + while (qdisc_qlen(sch) > sch->limit || 873 + q->memory_used > q->memory_limit) { 874 + struct sk_buff *skb = qdisc_dequeue_internal(sch, true); 875 + 876 + q->memory_used -= skb->truesize; 877 + qdisc_qstats_backlog_dec(sch, skb); 878 + rtnl_qdisc_drop(skb, sch); 879 + } 880 + qdisc_tree_reduce_backlog(sch, old_qlen - qdisc_qlen(sch), 881 + old_backlog - sch->qstats.backlog); 882 + 883 + sch_tree_unlock(sch); 884 + return 0; 885 + } 886 + 887 + /* Default alpha/beta values give a 10dB stability margin with max_rtt=100ms. */ 888 + static void dualpi2_reset_default(struct Qdisc *sch) 889 + { 890 + struct dualpi2_sched_data *q = qdisc_priv(sch); 891 + 892 + q->sch->limit = 10000; /* Max 125ms at 1Gbps */ 893 + q->memory_limit = get_memory_limit(sch, q->sch->limit); 894 + 895 + q->pi2_target = 15 * NSEC_PER_MSEC; 896 + q->pi2_tupdate = 16 * NSEC_PER_MSEC; 897 + q->pi2_alpha = dualpi2_scale_alpha_beta(41); /* ~0.16 Hz * 256 */ 898 + q->pi2_beta = dualpi2_scale_alpha_beta(819); /* ~3.20 Hz * 256 */ 899 + 900 + q->step_thresh = 1 * NSEC_PER_MSEC; 901 + q->step_in_packets = false; 902 + 903 + dualpi2_calculate_c_protection(q->sch, q, 10); /* wc=10%, wl=90% */ 904 + 905 + q->ecn_mask = TC_DUALPI2_ECN_MASK_L4S_ECT; /* INET_ECN_ECT_1 */ 906 + q->min_qlen_step = 0; /* Always apply step mark in L-queue */ 907 + q->coupling_factor = 2; /* window fairness for equal RTTs */ 908 + q->drop_overload = TC_DUALPI2_DROP_OVERLOAD_DROP; /* Drop overload */ 909 + q->drop_early = TC_DUALPI2_DROP_EARLY_DROP_DEQUEUE; /* Drop dequeue */ 910 + q->split_gso = TC_DUALPI2_SPLIT_GSO_SPLIT_GSO; /* Split GSO */ 911 + } 912 + 913 + static int dualpi2_init(struct Qdisc *sch, struct nlattr *opt, 914 + struct netlink_ext_ack *extack) 915 + { 916 + struct dualpi2_sched_data *q = qdisc_priv(sch); 917 + int err; 918 + 919 + q->l_queue = qdisc_create_dflt(sch->dev_queue, &pfifo_qdisc_ops, 920 + TC_H_MAKE(sch->handle, 1), extack); 921 + if (!q->l_queue) 922 + return -ENOMEM; 923 + 924 + err = tcf_block_get(&q->tcf_block, &q->tcf_filters, sch, extack); 925 + if (err) 926 + return err; 927 + 928 + q->sch = sch; 929 + dualpi2_reset_default(sch); 930 + hrtimer_setup(&q->pi2_timer, dualpi2_timer, CLOCK_MONOTONIC, HRTIMER_MODE_ABS_PINNED); 931 + 932 + if (opt && nla_len(opt)) { 933 + err = dualpi2_change(sch, opt, extack); 934 + 935 + if (err) 936 + return err; 937 + } 938 + 939 + hrtimer_start(&q->pi2_timer, next_pi2_timeout(q), 940 + HRTIMER_MODE_ABS_PINNED); 941 + return 0; 942 + } 943 + 944 + static int dualpi2_dump(struct Qdisc *sch, struct sk_buff *skb) 945 + { 946 + struct dualpi2_sched_data *q = qdisc_priv(sch); 947 + struct nlattr *opts; 948 + bool step_in_pkts; 949 + u32 step_th; 950 + 951 + step_in_pkts = READ_ONCE(q->step_in_packets); 952 + step_th = READ_ONCE(q->step_thresh); 953 + 954 + opts = nla_nest_start_noflag(skb, TCA_OPTIONS); 955 + if (!opts) 956 + goto nla_put_failure; 957 + 958 + if (step_in_pkts && 959 + (nla_put_u32(skb, TCA_DUALPI2_LIMIT, READ_ONCE(sch->limit)) || 960 + nla_put_u32(skb, TCA_DUALPI2_MEMORY_LIMIT, 961 + READ_ONCE(q->memory_limit)) || 962 + nla_put_u32(skb, TCA_DUALPI2_TARGET, 963 + convert_ns_to_usec(READ_ONCE(q->pi2_target))) || 964 + nla_put_u32(skb, TCA_DUALPI2_TUPDATE, 965 + convert_ns_to_usec(READ_ONCE(q->pi2_tupdate))) || 966 + nla_put_u32(skb, TCA_DUALPI2_ALPHA, 967 + dualpi2_unscale_alpha_beta(READ_ONCE(q->pi2_alpha))) || 968 + nla_put_u32(skb, TCA_DUALPI2_BETA, 969 + dualpi2_unscale_alpha_beta(READ_ONCE(q->pi2_beta))) || 970 + nla_put_u32(skb, TCA_DUALPI2_STEP_THRESH_PKTS, step_th) || 971 + nla_put_u32(skb, TCA_DUALPI2_MIN_QLEN_STEP, 972 + READ_ONCE(q->min_qlen_step)) || 973 + nla_put_u8(skb, TCA_DUALPI2_COUPLING, 974 + READ_ONCE(q->coupling_factor)) || 975 + nla_put_u8(skb, TCA_DUALPI2_DROP_OVERLOAD, 976 + READ_ONCE(q->drop_overload)) || 977 + nla_put_u8(skb, TCA_DUALPI2_DROP_EARLY, 978 + READ_ONCE(q->drop_early)) || 979 + nla_put_u8(skb, TCA_DUALPI2_C_PROTECTION, 980 + READ_ONCE(q->c_protection_wc)) || 981 + nla_put_u8(skb, TCA_DUALPI2_ECN_MASK, READ_ONCE(q->ecn_mask)) || 982 + nla_put_u8(skb, TCA_DUALPI2_SPLIT_GSO, READ_ONCE(q->split_gso)))) 983 + goto nla_put_failure; 984 + 985 + if (!step_in_pkts && 986 + (nla_put_u32(skb, TCA_DUALPI2_LIMIT, READ_ONCE(sch->limit)) || 987 + nla_put_u32(skb, TCA_DUALPI2_MEMORY_LIMIT, 988 + READ_ONCE(q->memory_limit)) || 989 + nla_put_u32(skb, TCA_DUALPI2_TARGET, 990 + convert_ns_to_usec(READ_ONCE(q->pi2_target))) || 991 + nla_put_u32(skb, TCA_DUALPI2_TUPDATE, 992 + convert_ns_to_usec(READ_ONCE(q->pi2_tupdate))) || 993 + nla_put_u32(skb, TCA_DUALPI2_ALPHA, 994 + dualpi2_unscale_alpha_beta(READ_ONCE(q->pi2_alpha))) || 995 + nla_put_u32(skb, TCA_DUALPI2_BETA, 996 + dualpi2_unscale_alpha_beta(READ_ONCE(q->pi2_beta))) || 997 + nla_put_u32(skb, TCA_DUALPI2_STEP_THRESH_US, 998 + convert_ns_to_usec(step_th)) || 999 + nla_put_u32(skb, TCA_DUALPI2_MIN_QLEN_STEP, 1000 + READ_ONCE(q->min_qlen_step)) || 1001 + nla_put_u8(skb, TCA_DUALPI2_COUPLING, 1002 + READ_ONCE(q->coupling_factor)) || 1003 + nla_put_u8(skb, TCA_DUALPI2_DROP_OVERLOAD, 1004 + READ_ONCE(q->drop_overload)) || 1005 + nla_put_u8(skb, TCA_DUALPI2_DROP_EARLY, 1006 + READ_ONCE(q->drop_early)) || 1007 + nla_put_u8(skb, TCA_DUALPI2_C_PROTECTION, 1008 + READ_ONCE(q->c_protection_wc)) || 1009 + nla_put_u8(skb, TCA_DUALPI2_ECN_MASK, READ_ONCE(q->ecn_mask)) || 1010 + nla_put_u8(skb, TCA_DUALPI2_SPLIT_GSO, READ_ONCE(q->split_gso)))) 1011 + goto nla_put_failure; 1012 + 1013 + return nla_nest_end(skb, opts); 1014 + 1015 + nla_put_failure: 1016 + nla_nest_cancel(skb, opts); 1017 + return -1; 1018 + } 1019 + 1020 + static int dualpi2_dump_stats(struct Qdisc *sch, struct gnet_dump *d) 1021 + { 1022 + struct dualpi2_sched_data *q = qdisc_priv(sch); 1023 + struct tc_dualpi2_xstats st = { 1024 + .prob = READ_ONCE(q->pi2_prob), 1025 + .packets_in_c = q->packets_in_c, 1026 + .packets_in_l = q->packets_in_l, 1027 + .maxq = q->maxq, 1028 + .ecn_mark = q->ecn_mark, 1029 + .credit = q->c_protection_credit, 1030 + .step_marks = q->step_marks, 1031 + .memory_used = q->memory_used, 1032 + .max_memory_used = q->max_memory_used, 1033 + .memory_limit = q->memory_limit, 1034 + }; 1035 + u64 qc, ql; 1036 + 1037 + get_queue_delays(q, &qc, &ql); 1038 + st.delay_l = convert_ns_to_usec(ql); 1039 + st.delay_c = convert_ns_to_usec(qc); 1040 + return gnet_stats_copy_app(d, &st, sizeof(st)); 1041 + } 1042 + 1043 + /* Reset both L-queue and C-queue, internal packet counters, PI probability, 1044 + * C-queue protection credit, and timestamps, while preserving current 1045 + * configuration of DUALPI2. 1046 + */ 1047 + static void dualpi2_reset(struct Qdisc *sch) 1048 + { 1049 + struct dualpi2_sched_data *q = qdisc_priv(sch); 1050 + 1051 + qdisc_reset_queue(sch); 1052 + qdisc_reset_queue(q->l_queue); 1053 + q->c_head_ts = 0; 1054 + q->l_head_ts = 0; 1055 + q->pi2_prob = 0; 1056 + q->packets_in_c = 0; 1057 + q->packets_in_l = 0; 1058 + q->maxq = 0; 1059 + q->ecn_mark = 0; 1060 + q->step_marks = 0; 1061 + q->memory_used = 0; 1062 + q->max_memory_used = 0; 1063 + dualpi2_reset_c_protection(q); 1064 + } 1065 + 1066 + static void dualpi2_destroy(struct Qdisc *sch) 1067 + { 1068 + struct dualpi2_sched_data *q = qdisc_priv(sch); 1069 + 1070 + q->pi2_tupdate = 0; 1071 + hrtimer_cancel(&q->pi2_timer); 1072 + if (q->l_queue) 1073 + qdisc_put(q->l_queue); 1074 + tcf_block_put(q->tcf_block); 1075 + } 1076 + 1077 + static struct Qdisc *dualpi2_leaf(struct Qdisc *sch, unsigned long arg) 1078 + { 1079 + return NULL; 1080 + } 1081 + 1082 + static unsigned long dualpi2_find(struct Qdisc *sch, u32 classid) 1083 + { 1084 + return 0; 1085 + } 1086 + 1087 + static unsigned long dualpi2_bind(struct Qdisc *sch, unsigned long parent, 1088 + u32 classid) 1089 + { 1090 + return 0; 1091 + } 1092 + 1093 + static void dualpi2_unbind(struct Qdisc *q, unsigned long cl) 1094 + { 1095 + } 1096 + 1097 + static struct tcf_block *dualpi2_tcf_block(struct Qdisc *sch, unsigned long cl, 1098 + struct netlink_ext_ack *extack) 1099 + { 1100 + struct dualpi2_sched_data *q = qdisc_priv(sch); 1101 + 1102 + if (cl) 1103 + return NULL; 1104 + return q->tcf_block; 1105 + } 1106 + 1107 + static void dualpi2_walk(struct Qdisc *sch, struct qdisc_walker *arg) 1108 + { 1109 + unsigned int i; 1110 + 1111 + if (arg->stop) 1112 + return; 1113 + 1114 + /* We statically define only 2 queues */ 1115 + for (i = 0; i < 2; i++) { 1116 + if (arg->count < arg->skip) { 1117 + arg->count++; 1118 + continue; 1119 + } 1120 + if (arg->fn(sch, i + 1, arg) < 0) { 1121 + arg->stop = 1; 1122 + break; 1123 + } 1124 + arg->count++; 1125 + } 1126 + } 1127 + 1128 + /* Minimal class support to handle tc filters */ 1129 + static const struct Qdisc_class_ops dualpi2_class_ops = { 1130 + .leaf = dualpi2_leaf, 1131 + .find = dualpi2_find, 1132 + .tcf_block = dualpi2_tcf_block, 1133 + .bind_tcf = dualpi2_bind, 1134 + .unbind_tcf = dualpi2_unbind, 1135 + .walk = dualpi2_walk, 1136 + }; 1137 + 1138 + static struct Qdisc_ops dualpi2_qdisc_ops __read_mostly = { 1139 + .id = "dualpi2", 1140 + .cl_ops = &dualpi2_class_ops, 1141 + .priv_size = sizeof(struct dualpi2_sched_data), 1142 + .enqueue = dualpi2_qdisc_enqueue, 1143 + .dequeue = dualpi2_qdisc_dequeue, 1144 + .peek = qdisc_peek_dequeued, 1145 + .init = dualpi2_init, 1146 + .destroy = dualpi2_destroy, 1147 + .reset = dualpi2_reset, 1148 + .change = dualpi2_change, 1149 + .dump = dualpi2_dump, 1150 + .dump_stats = dualpi2_dump_stats, 1151 + .owner = THIS_MODULE, 1152 + }; 1153 + 1154 + static int __init dualpi2_module_init(void) 1155 + { 1156 + return register_qdisc(&dualpi2_qdisc_ops); 1157 + } 1158 + 1159 + static void __exit dualpi2_module_exit(void) 1160 + { 1161 + unregister_qdisc(&dualpi2_qdisc_ops); 1162 + } 1163 + 1164 + module_init(dualpi2_module_init); 1165 + module_exit(dualpi2_module_exit); 1166 + 1167 + MODULE_DESCRIPTION("Dual Queue with Proportional Integral controller Improved with a Square (dualpi2) scheduler"); 1168 + MODULE_AUTHOR("Koen De Schepper <koen.de_schepper@nokia-bell-labs.com>"); 1169 + MODULE_AUTHOR("Chia-Yu Chang <chia-yu.chang@nokia-bell-labs.com>"); 1170 + MODULE_AUTHOR("Olga Albisser <olga@albisser.org>"); 1171 + MODULE_AUTHOR("Henrik Steen <henrist@henrist.net>"); 1172 + MODULE_AUTHOR("Olivier Tilmans <olivier.tilmans@nokia.com>"); 1173 + 1174 + MODULE_LICENSE("Dual BSD/GPL"); 1175 + MODULE_VERSION("1.0");
+1
tools/testing/selftests/tc-testing/config
··· 31 31 CONFIG_NET_SCH_CHOKE=m 32 32 CONFIG_NET_SCH_CODEL=m 33 33 CONFIG_NET_SCH_DRR=m 34 + CONFIG_NET_SCH_DUALPI2=m 34 35 CONFIG_NET_SCH_ETF=m 35 36 CONFIG_NET_SCH_FQ=m 36 37 CONFIG_NET_SCH_FQ_CODEL=m
+254
tools/testing/selftests/tc-testing/tc-tests/qdiscs/dualpi2.json
··· 1 + [ 2 + { 3 + "id": "a4c7", 4 + "name": "Create DualPI2 with default setting", 5 + "category": [ 6 + "qdisc", 7 + "dualpi2" 8 + ], 9 + "plugins": { 10 + "requires": "nsPlugin" 11 + }, 12 + "setup": [ 13 + ], 14 + "cmdUnderTest": "$TC qdisc add dev $DUMMY handle 1: root dualpi2", 15 + "expExitCode": "0", 16 + "verifyCmd": "$TC qdisc show dev $DUMMY", 17 + "matchPattern": "qdisc dualpi2 1: root refcnt [0-9]+ limit 10000p.* step_thresh 1ms min_qlen_step 0p coupling_factor 2 drop_on_overload drop_dequeue classic_protection 10% l4s_ect split_gso", 18 + "matchCount": "1", 19 + "teardown": [ 20 + "$TC qdisc del dev $DUMMY handle 1: root" 21 + ] 22 + }, 23 + { 24 + "id": "1ea4", 25 + "name": "Create DualPI2 with memlimit", 26 + "category": [ 27 + "qdisc", 28 + "dualpi2" 29 + ], 30 + "plugins": { 31 + "requires": "nsPlugin" 32 + }, 33 + "setup": [ 34 + ], 35 + "cmdUnderTest": "$TC qdisc add dev $DUMMY handle 1: root dualpi2 memlimit 20000000", 36 + "expExitCode": "0", 37 + "verifyCmd": "$TC qdisc show dev $DUMMY", 38 + "matchPattern": "qdisc dualpi2 1: root refcnt [0-9]+ limit 10000p.* memlimit 20000000B", 39 + "matchCount": "1", 40 + "teardown": [ 41 + "$TC qdisc del dev $DUMMY handle 1: root" 42 + ] 43 + }, 44 + { 45 + "id": "2130", 46 + "name": "Create DualPI2 with typical_rtt and max_rtt", 47 + "category": [ 48 + "qdisc", 49 + "dualpi2" 50 + ], 51 + "plugins": { 52 + "requires": "nsPlugin" 53 + }, 54 + "setup": [ 55 + ], 56 + "cmdUnderTest": "$TC qdisc add dev $DUMMY handle 1: root dualpi2 typical_rtt 20ms max_rtt 200ms", 57 + "expExitCode": "0", 58 + "verifyCmd": "$TC qdisc show dev $DUMMY", 59 + "matchPattern": "qdisc dualpi2 1: root refcnt [0-9]+ limit 10000p.* target 20ms tupdate 20ms alpha 0.042969 beta 1.496094", 60 + "matchCount": "1", 61 + "teardown": [ 62 + "$TC qdisc del dev $DUMMY handle 1: root" 63 + ] 64 + }, 65 + { 66 + "id": "90c1", 67 + "name": "Create DualPI2 with max_rtt", 68 + "category": [ 69 + "qdisc", 70 + "dualpi2" 71 + ], 72 + "plugins": { 73 + "requires": "nsPlugin" 74 + }, 75 + "setup": [ 76 + ], 77 + "cmdUnderTest": "$TC qdisc add dev $DUMMY handle 1: root dualpi2 max_rtt 300ms", 78 + "expExitCode": "0", 79 + "verifyCmd": "$TC qdisc show dev $DUMMY", 80 + "matchPattern": "qdisc dualpi2 1: root refcnt [0-9]+ limit 10000p.* target 50ms tupdate 50ms alpha 0.050781 beta 0.996094", 81 + "matchCount": "1", 82 + "teardown": [ 83 + "$TC qdisc del dev $DUMMY handle 1: root" 84 + ] 85 + }, 86 + { 87 + "id": "7b3c", 88 + "name": "Create DualPI2 with any_ect option", 89 + "category": [ 90 + "qdisc", 91 + "dualpi2" 92 + ], 93 + "plugins": { 94 + "requires": "nsPlugin" 95 + }, 96 + "setup": [ 97 + ], 98 + "cmdUnderTest": "$TC qdisc add dev $DUMMY handle 1: root dualpi2 any_ect", 99 + "expExitCode": "0", 100 + "verifyCmd": "$TC qdisc show dev $DUMMY", 101 + "matchPattern": "qdisc dualpi2 1: root refcnt [0-9]+ limit 10000p .* any_ect", 102 + "matchCount": "1", 103 + "teardown": [ 104 + "$TC qdisc del dev $DUMMY handle 1: root" 105 + ] 106 + }, 107 + { 108 + "id": "49a3", 109 + "name": "Create DualPI2 with overflow option", 110 + "category": [ 111 + "qdisc", 112 + "dualpi2" 113 + ], 114 + "plugins": { 115 + "requires": "nsPlugin" 116 + }, 117 + "setup": [ 118 + ], 119 + "cmdUnderTest": "$TC qdisc add dev $DUMMY handle 1: root dualpi2 overflow", 120 + "expExitCode": "0", 121 + "verifyCmd": "$TC qdisc show dev $DUMMY", 122 + "matchPattern": "qdisc dualpi2 1: root refcnt [0-9]+ limit 10000p.* overflow", 123 + "matchCount": "1", 124 + "teardown": [ 125 + "$TC qdisc del dev $DUMMY handle 1: root" 126 + ] 127 + }, 128 + { 129 + "id": "d0a1", 130 + "name": "Create DualPI2 with drop_enqueue option", 131 + "category": [ 132 + "qdisc", 133 + "dualpi2" 134 + ], 135 + "plugins": { 136 + "requires": "nsPlugin" 137 + }, 138 + "setup": [ 139 + ], 140 + "cmdUnderTest": "$TC qdisc add dev $DUMMY handle 1: root dualpi2 drop_enqueue", 141 + "expExitCode": "0", 142 + "verifyCmd": "$TC qdisc show dev $DUMMY", 143 + "matchPattern": "qdisc dualpi2 1: root refcnt [0-9]+ limit 10000p .* drop_enqueue", 144 + "matchCount": "1", 145 + "teardown": [ 146 + "$TC qdisc del dev $DUMMY handle 1: root" 147 + ] 148 + }, 149 + { 150 + "id": "f051", 151 + "name": "Create DualPI2 with no_split_gso option", 152 + "category": [ 153 + "qdisc", 154 + "dualpi2" 155 + ], 156 + "plugins": { 157 + "requires": "nsPlugin" 158 + }, 159 + "setup": [ 160 + ], 161 + "cmdUnderTest": "$TC qdisc add dev $DUMMY handle 1: root dualpi2 no_split_gso", 162 + "expExitCode": "0", 163 + "verifyCmd": "$TC qdisc show dev $DUMMY", 164 + "matchPattern": "qdisc dualpi2 1: root refcnt [0-9]+ limit 10000p .* no_split_gso", 165 + "matchCount": "1", 166 + "teardown": [ 167 + "$TC qdisc del dev $DUMMY handle 1: root" 168 + ] 169 + }, 170 + { 171 + "id": "456b", 172 + "name": "Create DualPI2 with packet step_thresh", 173 + "category": [ 174 + "qdisc", 175 + "dualpi2" 176 + ], 177 + "plugins": { 178 + "requires": "nsPlugin" 179 + }, 180 + "setup": [ 181 + ], 182 + "cmdUnderTest": "$TC qdisc add dev $DUMMY handle 1: root dualpi2 step_thresh 3p", 183 + "expExitCode": "0", 184 + "verifyCmd": "$TC qdisc show dev $DUMMY", 185 + "matchPattern": "qdisc dualpi2 1: root refcnt [0-9]+ limit 10000p .* step_thresh 3p", 186 + "matchCount": "1", 187 + "teardown": [ 188 + "$TC qdisc del dev $DUMMY handle 1: root" 189 + ] 190 + }, 191 + { 192 + "id": "610c", 193 + "name": "Create DualPI2 with packet min_qlen_step", 194 + "category": [ 195 + "qdisc", 196 + "dualpi2" 197 + ], 198 + "plugins": { 199 + "requires": "nsPlugin" 200 + }, 201 + "setup": [ 202 + ], 203 + "cmdUnderTest": "$TC qdisc add dev $DUMMY handle 1: root dualpi2 min_qlen_step 1", 204 + "expExitCode": "0", 205 + "verifyCmd": "$TC qdisc show dev $DUMMY", 206 + "matchPattern": "qdisc dualpi2 1: root refcnt [0-9]+ limit 10000p .* min_qlen_step 1p", 207 + "matchCount": "1", 208 + "teardown": [ 209 + "$TC qdisc del dev $DUMMY handle 1: root" 210 + ] 211 + }, 212 + { 213 + "id": "b4fa", 214 + "name": "Create DualPI2 with packet coupling_factor", 215 + "category": [ 216 + "qdisc", 217 + "dualpi2" 218 + ], 219 + "plugins": { 220 + "requires": "nsPlugin" 221 + }, 222 + "setup": [ 223 + ], 224 + "cmdUnderTest": "$TC qdisc add dev $DUMMY handle 1: root dualpi2 coupling_factor 1", 225 + "expExitCode": "0", 226 + "verifyCmd": "$TC qdisc show dev $DUMMY", 227 + "matchPattern": "qdisc dualpi2 1: root refcnt [0-9]+ limit 10000p .* coupling_factor 1", 228 + "matchCount": "1", 229 + "teardown": [ 230 + "$TC qdisc del dev $DUMMY handle 1: root" 231 + ] 232 + }, 233 + { 234 + "id": "37f1", 235 + "name": "Create DualPI2 with packet classic_protection", 236 + "category": [ 237 + "qdisc", 238 + "dualpi2" 239 + ], 240 + "plugins": { 241 + "requires": "nsPlugin" 242 + }, 243 + "setup": [ 244 + ], 245 + "cmdUnderTest": "$TC qdisc add dev $DUMMY handle 1: root dualpi2 classic_protection 0", 246 + "expExitCode": "0", 247 + "verifyCmd": "$TC qdisc show dev $DUMMY", 248 + "matchPattern": "qdisc dualpi2 1: root refcnt [0-9]+ limit 10000p .* classic_protection 0%", 249 + "matchCount": "1", 250 + "teardown": [ 251 + "$TC qdisc del dev $DUMMY handle 1: root" 252 + ] 253 + } 254 + ]
+3 -3
tools/testing/selftests/tc-testing/tdc.sh
··· 4 4 # If a module is required and was not compiled 5 5 # the test that requires it will fail anyways 6 6 try_modprobe() { 7 - modprobe -q -R "$1" 8 - if [ $? -ne 0 ]; then 7 + if ! modprobe -q -R "$1"; then 9 8 echo "Module $1 not found... skipping." 10 9 else 11 10 modprobe "$1" ··· 66 67 try_modprobe sch_hhf 67 68 try_modprobe sch_htb 68 69 try_modprobe sch_teql 69 - ./tdc.py -J`nproc` 70 + try_modprobe sch_dualpi2 71 + ./tdc.py -J"$(nproc)"