From 105eb9ca95c5906011d3e5eac10e6332f9c3ff60 Mon Sep 17 00:00:00 2001 From: Rui Salvaterra Date: Wed, 21 Jan 2026 17:27:10 +0000 Subject: [PATCH] kernel: add cake-mq support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add the required patches in order to backport cake-mq from Linux 7.0. Many thanks to Toke Høiland-Jørgensen for providing the git trees with backports for both 6.12 and 6.18. Signed-off-by: Rui Salvaterra Link: https://github.com/openwrt/openwrt/pull/21964 Signed-off-by: Christian Marangi --- ...-sched-Export-mq-functions-for-reuse.patch | 255 +++++++ ...ke-Factor-out-config-variables-into-.patch | 636 ++++++++++++++++++ ...ke-Add-cake_mq-qdisc-for-using-cake-.patch | 130 ++++ ...ke-Share-config-across-cake_mq-sub-q.patch | 318 +++++++++ ...ke-share-shaper-state-across-sub-ins.patch | 175 +++++ ...ting-add-selftests-for-cake_mq-qdisc.patch | 606 +++++++++++++++++ ...void-separate-allocation-of-struct-c.patch | 112 +++ 7 files changed, 2232 insertions(+) create mode 100644 target/linux/generic/backport-6.12/700-01-v7.0-net-sched-Export-mq-functions-for-reuse.patch create mode 100644 target/linux/generic/backport-6.12/700-02-v7.0-net-sched-sch_cake-Factor-out-config-variables-into-.patch create mode 100644 target/linux/generic/backport-6.12/700-03-v7.0-net-sched-sch_cake-Add-cake_mq-qdisc-for-using-cake-.patch create mode 100644 target/linux/generic/backport-6.12/700-04-v7.0-net-sched-sch_cake-Share-config-across-cake_mq-sub-q.patch create mode 100644 target/linux/generic/backport-6.12/700-05-v7.0-net-sched-sch_cake-share-shaper-state-across-sub-ins.patch create mode 100644 target/linux/generic/backport-6.12/700-06-v7.0-selftests-tc-testing-add-selftests-for-cake_mq-qdisc.patch create mode 100644 target/linux/generic/backport-6.12/700-07-v7.0-net-sched-cake-avoid-separate-allocation-of-struct-c.patch diff --git a/target/linux/generic/backport-6.12/700-01-v7.0-net-sched-Export-mq-functions-for-reuse.patch b/target/linux/generic/backport-6.12/700-01-v7.0-net-sched-Export-mq-functions-for-reuse.patch new file mode 100644 index 0000000000..01439cdbd7 --- /dev/null +++ b/target/linux/generic/backport-6.12/700-01-v7.0-net-sched-Export-mq-functions-for-reuse.patch @@ -0,0 +1,255 @@ +From 8560edcc05114e42d4d580baaf124bce8c291d55 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Toke=20H=C3=B8iland-J=C3=B8rgensen?= +Date: Fri, 9 Jan 2026 14:15:30 +0100 +Subject: [PATCH 1/7] net/sched: Export mq functions for reuse +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +To enable the cake_mq qdisc to reuse code from the mq qdisc, export a +bunch of functions from sch_mq. Split common functionality out from some +functions so it can be composed with other code, and export other +functions wholesale. To discourage wanton reuse, put the symbols into a +new NET_SCHED_INTERNAL namespace, and a sch_priv.h header file. + +No functional change intended. + +Reviewed-by: Willem de Bruijn +Signed-off-by: Toke Høiland-Jørgensen +Link: https://patch.msgid.link/20260109-mq-cake-sub-qdisc-v8-1-8d613fece5d8@redhat.com +Signed-off-by: Paolo Abeni +--- + MAINTAINERS | 1 + + include/net/sch_priv.h | 27 ++++++++++++++++ + net/sched/sch_mq.c | 71 +++++++++++++++++++++++++++++------------- + 3 files changed, 77 insertions(+), 22 deletions(-) + create mode 100644 include/net/sch_priv.h + +--- a/MAINTAINERS ++++ b/MAINTAINERS +@@ -22541,6 +22541,7 @@ L: netdev@vger.kernel.org + S: Maintained + F: include/net/pkt_cls.h + F: include/net/pkt_sched.h ++F: include/net/sch_priv.h + F: include/net/tc_act/ + F: include/uapi/linux/pkt_cls.h + F: include/uapi/linux/pkt_sched.h +--- /dev/null ++++ b/include/net/sch_priv.h +@@ -0,0 +1,27 @@ ++/* SPDX-License-Identifier: GPL-2.0 */ ++#ifndef __NET_SCHED_PRIV_H ++#define __NET_SCHED_PRIV_H ++ ++#include ++ ++struct mq_sched { ++ struct Qdisc **qdiscs; ++}; ++ ++int mq_init_common(struct Qdisc *sch, struct nlattr *opt, ++ struct netlink_ext_ack *extack, ++ const struct Qdisc_ops *qdisc_ops); ++void mq_destroy_common(struct Qdisc *sch); ++void mq_attach(struct Qdisc *sch); ++void mq_dump_common(struct Qdisc *sch, struct sk_buff *skb); ++struct netdev_queue *mq_select_queue(struct Qdisc *sch, ++ struct tcmsg *tcm); ++struct Qdisc *mq_leaf(struct Qdisc *sch, unsigned long cl); ++unsigned long mq_find(struct Qdisc *sch, u32 classid); ++int mq_dump_class(struct Qdisc *sch, unsigned long cl, ++ struct sk_buff *skb, struct tcmsg *tcm); ++int mq_dump_class_stats(struct Qdisc *sch, unsigned long cl, ++ struct gnet_dump *d); ++void mq_walk(struct Qdisc *sch, struct qdisc_walker *arg); ++ ++#endif +--- a/net/sched/sch_mq.c ++++ b/net/sched/sch_mq.c +@@ -15,11 +15,7 @@ + #include + #include + #include +-#include +- +-struct mq_sched { +- struct Qdisc **qdiscs; +-}; ++#include + + static int mq_offload(struct Qdisc *sch, enum tc_mq_command cmd) + { +@@ -49,23 +45,29 @@ static int mq_offload_stats(struct Qdisc + return qdisc_offload_dump_helper(sch, TC_SETUP_QDISC_MQ, &opt); + } + +-static void mq_destroy(struct Qdisc *sch) ++void mq_destroy_common(struct Qdisc *sch) + { + struct net_device *dev = qdisc_dev(sch); + struct mq_sched *priv = qdisc_priv(sch); + unsigned int ntx; + +- mq_offload(sch, TC_MQ_DESTROY); +- + if (!priv->qdiscs) + return; + for (ntx = 0; ntx < dev->num_tx_queues && priv->qdiscs[ntx]; ntx++) + qdisc_put(priv->qdiscs[ntx]); + kfree(priv->qdiscs); + } ++EXPORT_SYMBOL_NS_GPL(mq_destroy_common, NET_SCHED_INTERNAL); + +-static int mq_init(struct Qdisc *sch, struct nlattr *opt, +- struct netlink_ext_ack *extack) ++static void mq_destroy(struct Qdisc *sch) ++{ ++ mq_offload(sch, TC_MQ_DESTROY); ++ mq_destroy_common(sch); ++} ++ ++int mq_init_common(struct Qdisc *sch, struct nlattr *opt, ++ struct netlink_ext_ack *extack, ++ const struct Qdisc_ops *qdisc_ops) + { + struct net_device *dev = qdisc_dev(sch); + struct mq_sched *priv = qdisc_priv(sch); +@@ -87,7 +89,8 @@ static int mq_init(struct Qdisc *sch, st + + for (ntx = 0; ntx < dev->num_tx_queues; ntx++) { + dev_queue = netdev_get_tx_queue(dev, ntx); +- qdisc = qdisc_create_dflt(dev_queue, get_default_qdisc_ops(dev, ntx), ++ qdisc = qdisc_create_dflt(dev_queue, ++ qdisc_ops ?: get_default_qdisc_ops(dev, ntx), + TC_H_MAKE(TC_H_MAJ(sch->handle), + TC_H_MIN(ntx + 1)), + extack); +@@ -98,12 +101,24 @@ static int mq_init(struct Qdisc *sch, st + } + + sch->flags |= TCQ_F_MQROOT; ++ return 0; ++} ++EXPORT_SYMBOL_NS_GPL(mq_init_common, NET_SCHED_INTERNAL); ++ ++static int mq_init(struct Qdisc *sch, struct nlattr *opt, ++ struct netlink_ext_ack *extack) ++{ ++ int ret; ++ ++ ret = mq_init_common(sch, opt, extack, NULL); ++ if (ret) ++ return ret; + + mq_offload(sch, TC_MQ_CREATE); + return 0; + } + +-static void mq_attach(struct Qdisc *sch) ++void mq_attach(struct Qdisc *sch) + { + struct net_device *dev = qdisc_dev(sch); + struct mq_sched *priv = qdisc_priv(sch); +@@ -124,8 +139,9 @@ static void mq_attach(struct Qdisc *sch) + kfree(priv->qdiscs); + priv->qdiscs = NULL; + } ++EXPORT_SYMBOL_NS_GPL(mq_attach, NET_SCHED_INTERNAL); + +-static int mq_dump(struct Qdisc *sch, struct sk_buff *skb) ++void mq_dump_common(struct Qdisc *sch, struct sk_buff *skb) + { + struct net_device *dev = qdisc_dev(sch); + struct Qdisc *qdisc; +@@ -152,7 +168,12 @@ static int mq_dump(struct Qdisc *sch, st + + spin_unlock_bh(qdisc_lock(qdisc)); + } ++} ++EXPORT_SYMBOL_NS_GPL(mq_dump_common, NET_SCHED_INTERNAL); + ++static int mq_dump(struct Qdisc *sch, struct sk_buff *skb) ++{ ++ mq_dump_common(sch, skb); + return mq_offload_stats(sch); + } + +@@ -166,11 +187,12 @@ static struct netdev_queue *mq_queue_get + return netdev_get_tx_queue(dev, ntx); + } + +-static struct netdev_queue *mq_select_queue(struct Qdisc *sch, +- struct tcmsg *tcm) ++struct netdev_queue *mq_select_queue(struct Qdisc *sch, ++ struct tcmsg *tcm) + { + return mq_queue_get(sch, TC_H_MIN(tcm->tcm_parent)); + } ++EXPORT_SYMBOL_NS_GPL(mq_select_queue, NET_SCHED_INTERNAL); + + static int mq_graft(struct Qdisc *sch, unsigned long cl, struct Qdisc *new, + struct Qdisc **old, struct netlink_ext_ack *extack) +@@ -198,14 +220,15 @@ static int mq_graft(struct Qdisc *sch, u + return 0; + } + +-static struct Qdisc *mq_leaf(struct Qdisc *sch, unsigned long cl) ++struct Qdisc *mq_leaf(struct Qdisc *sch, unsigned long cl) + { + struct netdev_queue *dev_queue = mq_queue_get(sch, cl); + + return rtnl_dereference(dev_queue->qdisc_sleeping); + } ++EXPORT_SYMBOL_NS_GPL(mq_leaf, NET_SCHED_INTERNAL); + +-static unsigned long mq_find(struct Qdisc *sch, u32 classid) ++unsigned long mq_find(struct Qdisc *sch, u32 classid) + { + unsigned int ntx = TC_H_MIN(classid); + +@@ -213,9 +236,10 @@ static unsigned long mq_find(struct Qdis + return 0; + return ntx; + } ++EXPORT_SYMBOL_NS_GPL(mq_find, NET_SCHED_INTERNAL); + +-static int mq_dump_class(struct Qdisc *sch, unsigned long cl, +- struct sk_buff *skb, struct tcmsg *tcm) ++int mq_dump_class(struct Qdisc *sch, unsigned long cl, ++ struct sk_buff *skb, struct tcmsg *tcm) + { + struct netdev_queue *dev_queue = mq_queue_get(sch, cl); + +@@ -224,9 +248,10 @@ static int mq_dump_class(struct Qdisc *s + tcm->tcm_info = rtnl_dereference(dev_queue->qdisc_sleeping)->handle; + return 0; + } ++EXPORT_SYMBOL_NS_GPL(mq_dump_class, NET_SCHED_INTERNAL); + +-static int mq_dump_class_stats(struct Qdisc *sch, unsigned long cl, +- struct gnet_dump *d) ++int mq_dump_class_stats(struct Qdisc *sch, unsigned long cl, ++ struct gnet_dump *d) + { + struct netdev_queue *dev_queue = mq_queue_get(sch, cl); + +@@ -236,8 +261,9 @@ static int mq_dump_class_stats(struct Qd + return -1; + return 0; + } ++EXPORT_SYMBOL_NS_GPL(mq_dump_class_stats, NET_SCHED_INTERNAL); + +-static void mq_walk(struct Qdisc *sch, struct qdisc_walker *arg) ++void mq_walk(struct Qdisc *sch, struct qdisc_walker *arg) + { + struct net_device *dev = qdisc_dev(sch); + unsigned int ntx; +@@ -251,6 +277,7 @@ static void mq_walk(struct Qdisc *sch, s + break; + } + } ++EXPORT_SYMBOL_NS_GPL(mq_walk, NET_SCHED_INTERNAL); + + static const struct Qdisc_class_ops mq_class_ops = { + .select_queue = mq_select_queue, diff --git a/target/linux/generic/backport-6.12/700-02-v7.0-net-sched-sch_cake-Factor-out-config-variables-into-.patch b/target/linux/generic/backport-6.12/700-02-v7.0-net-sched-sch_cake-Factor-out-config-variables-into-.patch new file mode 100644 index 0000000000..8df110b556 --- /dev/null +++ b/target/linux/generic/backport-6.12/700-02-v7.0-net-sched-sch_cake-Factor-out-config-variables-into-.patch @@ -0,0 +1,636 @@ +From 23090d3e9db80c2a374df1e636247eff9b6dd972 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Toke=20H=C3=B8iland-J=C3=B8rgensen?= +Date: Fri, 9 Jan 2026 14:15:31 +0100 +Subject: [PATCH 2/7] net/sched: sch_cake: Factor out config variables into + separate struct +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Factor out all the user-configurable variables into a separate struct +and embed it into struct cake_sched_data. This is done in preparation +for sharing the configuration across multiple instances of cake in an mq +setup. + +No functional change is intended with this patch. + +Reviewed-by: Jamal Hadi Salim +Reviewed-by: Willem de Bruijn +Signed-off-by: Toke Høiland-Jørgensen +Link: https://patch.msgid.link/20260109-mq-cake-sub-qdisc-v8-2-8d613fece5d8@redhat.com +Signed-off-by: Paolo Abeni +--- + net/sched/sch_cake.c | 245 +++++++++++++++++++++++-------------------- + 1 file changed, 133 insertions(+), 112 deletions(-) + +--- a/net/sched/sch_cake.c ++++ b/net/sched/sch_cake.c +@@ -197,40 +197,42 @@ struct cake_tin_data { + u32 way_collisions; + }; /* number of tins is small, so size of this struct doesn't matter much */ + ++struct cake_sched_config { ++ u64 rate_bps; ++ u64 interval; ++ u64 target; ++ u32 buffer_config_limit; ++ u32 fwmark_mask; ++ u16 fwmark_shft; ++ s16 rate_overhead; ++ u16 rate_mpu; ++ u16 rate_flags; ++ u8 tin_mode; ++ u8 flow_mode; ++ u8 atm_mode; ++ u8 ack_filter; ++}; ++ + struct cake_sched_data { + struct tcf_proto __rcu *filter_list; /* optional external classifier */ + struct tcf_block *block; + struct cake_tin_data *tins; ++ struct cake_sched_config *config; + + struct cake_heap_entry overflow_heap[CAKE_QUEUES * CAKE_MAX_TINS]; +- u16 overflow_timeout; +- +- u16 tin_cnt; +- u8 tin_mode; +- u8 flow_mode; +- u8 ack_filter; +- u8 atm_mode; +- +- u32 fwmark_mask; +- u16 fwmark_shft; + + /* time_next = time_this + ((len * rate_ns) >> rate_shft) */ +- u16 rate_shft; + ktime_t time_next_packet; + ktime_t failsafe_next_packet; + u64 rate_ns; +- u64 rate_bps; +- u16 rate_flags; +- s16 rate_overhead; +- u16 rate_mpu; +- u64 interval; +- u64 target; ++ u16 rate_shft; ++ u16 overflow_timeout; ++ u16 tin_cnt; + + /* resource tracking */ + u32 buffer_used; + u32 buffer_max_used; + u32 buffer_limit; +- u32 buffer_config_limit; + + /* indices for dequeue */ + u16 cur_tin; +@@ -1195,7 +1197,7 @@ static bool cake_tcph_may_drop(const str + static struct sk_buff *cake_ack_filter(struct cake_sched_data *q, + struct cake_flow *flow) + { +- bool aggressive = q->ack_filter == CAKE_ACK_AGGRESSIVE; ++ bool aggressive = q->config->ack_filter == CAKE_ACK_AGGRESSIVE; + struct sk_buff *elig_ack = NULL, *elig_ack_prev = NULL; + struct sk_buff *skb_check, *skb_prev = NULL; + const struct ipv6hdr *ipv6h, *ipv6h_check; +@@ -1355,15 +1357,17 @@ static u64 cake_ewma(u64 avg, u64 sample + return avg; + } + +-static u32 cake_calc_overhead(struct cake_sched_data *q, u32 len, u32 off) ++static u32 cake_calc_overhead(struct cake_sched_data *qd, u32 len, u32 off) + { ++ struct cake_sched_config *q = qd->config; ++ + if (q->rate_flags & CAKE_FLAG_OVERHEAD) + len -= off; + +- if (q->max_netlen < len) +- q->max_netlen = len; +- if (q->min_netlen > len) +- q->min_netlen = len; ++ if (qd->max_netlen < len) ++ qd->max_netlen = len; ++ if (qd->min_netlen > len) ++ qd->min_netlen = len; + + len += q->rate_overhead; + +@@ -1382,10 +1386,10 @@ static u32 cake_calc_overhead(struct cak + len += (len + 63) / 64; + } + +- if (q->max_adjlen < len) +- q->max_adjlen = len; +- if (q->min_adjlen > len) +- q->min_adjlen = len; ++ if (qd->max_adjlen < len) ++ qd->max_adjlen = len; ++ if (qd->min_adjlen > len) ++ qd->min_adjlen = len; + + return len; + } +@@ -1587,7 +1591,7 @@ static unsigned int cake_drop(struct Qdi + b->tin_dropped++; + sch->qstats.drops++; + +- if (q->rate_flags & CAKE_FLAG_INGRESS) ++ if (q->config->rate_flags & CAKE_FLAG_INGRESS) + cake_advance_shaper(q, b, skb, now, true); + + __qdisc_drop(skb, to_free); +@@ -1657,7 +1661,8 @@ static u8 cake_handle_diffserv(struct sk + static struct cake_tin_data *cake_select_tin(struct Qdisc *sch, + struct sk_buff *skb) + { +- struct cake_sched_data *q = qdisc_priv(sch); ++ struct cake_sched_data *qd = qdisc_priv(sch); ++ struct cake_sched_config *q = qd->config; + u32 tin, mark; + bool wash; + u8 dscp; +@@ -1674,24 +1679,24 @@ static struct cake_tin_data *cake_select + if (q->tin_mode == CAKE_DIFFSERV_BESTEFFORT) + tin = 0; + +- else if (mark && mark <= q->tin_cnt) +- tin = q->tin_order[mark - 1]; ++ else if (mark && mark <= qd->tin_cnt) ++ tin = qd->tin_order[mark - 1]; + + else if (TC_H_MAJ(skb->priority) == sch->handle && + TC_H_MIN(skb->priority) > 0 && +- TC_H_MIN(skb->priority) <= q->tin_cnt) +- tin = q->tin_order[TC_H_MIN(skb->priority) - 1]; ++ TC_H_MIN(skb->priority) <= qd->tin_cnt) ++ tin = qd->tin_order[TC_H_MIN(skb->priority) - 1]; + + else { + if (!wash) + dscp = cake_handle_diffserv(skb, wash); +- tin = q->tin_index[dscp]; ++ tin = qd->tin_index[dscp]; + +- if (unlikely(tin >= q->tin_cnt)) ++ if (unlikely(tin >= qd->tin_cnt)) + tin = 0; + } + +- return &q->tins[tin]; ++ return &qd->tins[tin]; + } + + static u32 cake_classify(struct Qdisc *sch, struct cake_tin_data **t, +@@ -1747,7 +1752,7 @@ static s32 cake_enqueue(struct sk_buff * + bool same_flow = false; + + /* choose flow to insert into */ +- idx = cake_classify(sch, &b, skb, q->flow_mode, &ret); ++ idx = cake_classify(sch, &b, skb, q->config->flow_mode, &ret); + if (idx == 0) { + if (ret & __NET_XMIT_BYPASS) + qdisc_qstats_drop(sch); +@@ -1782,7 +1787,7 @@ static s32 cake_enqueue(struct sk_buff * + if (unlikely(len > b->max_skblen)) + b->max_skblen = len; + +- if (skb_is_gso(skb) && q->rate_flags & CAKE_FLAG_SPLIT_GSO) { ++ if (skb_is_gso(skb) && q->config->rate_flags & CAKE_FLAG_SPLIT_GSO) { + struct sk_buff *segs, *nskb; + netdev_features_t features = netif_skb_features(skb); + unsigned int slen = 0, numsegs = 0; +@@ -1823,7 +1828,7 @@ static s32 cake_enqueue(struct sk_buff * + get_cobalt_cb(skb)->adjusted_len = cake_overhead(q, skb); + flow_queue_add(flow, skb); + +- if (q->ack_filter) ++ if (q->config->ack_filter) + ack = cake_ack_filter(q, flow); + + if (ack) { +@@ -1832,7 +1837,7 @@ static s32 cake_enqueue(struct sk_buff * + ack_pkt_len = qdisc_pkt_len(ack); + b->bytes += ack_pkt_len; + q->buffer_used += skb->truesize - ack->truesize; +- if (q->rate_flags & CAKE_FLAG_INGRESS) ++ if (q->config->rate_flags & CAKE_FLAG_INGRESS) + cake_advance_shaper(q, b, ack, now, true); + + qdisc_tree_reduce_backlog(sch, 1, ack_pkt_len); +@@ -1855,7 +1860,7 @@ static s32 cake_enqueue(struct sk_buff * + cake_heapify_up(q, b->overflow_idx[idx]); + + /* incoming bandwidth capacity estimate */ +- if (q->rate_flags & CAKE_FLAG_AUTORATE_INGRESS) { ++ if (q->config->rate_flags & CAKE_FLAG_AUTORATE_INGRESS) { + u64 packet_interval = \ + ktime_to_ns(ktime_sub(now, q->last_packet_time)); + +@@ -1887,7 +1892,7 @@ static s32 cake_enqueue(struct sk_buff * + if (ktime_after(now, + ktime_add_ms(q->last_reconfig_time, + 250))) { +- q->rate_bps = (q->avg_peak_bandwidth * 15) >> 4; ++ q->config->rate_bps = (q->avg_peak_bandwidth * 15) >> 4; + cake_reconfigure(sch); + } + } +@@ -1907,7 +1912,7 @@ static s32 cake_enqueue(struct sk_buff * + flow->set = CAKE_SET_SPARSE; + b->sparse_flow_count++; + +- flow->deficit = cake_get_flow_quantum(b, flow, q->flow_mode); ++ flow->deficit = cake_get_flow_quantum(b, flow, q->config->flow_mode); + } else if (flow->set == CAKE_SET_SPARSE_WAIT) { + /* this flow was empty, accounted as a sparse flow, but actually + * in the bulk rotation. +@@ -1916,8 +1921,8 @@ static s32 cake_enqueue(struct sk_buff * + b->sparse_flow_count--; + b->bulk_flow_count++; + +- cake_inc_srchost_bulk_flow_count(b, flow, q->flow_mode); +- cake_inc_dsthost_bulk_flow_count(b, flow, q->flow_mode); ++ cake_inc_srchost_bulk_flow_count(b, flow, q->config->flow_mode); ++ cake_inc_dsthost_bulk_flow_count(b, flow, q->config->flow_mode); + } + + if (q->buffer_used > q->buffer_max_used) +@@ -2103,8 +2108,8 @@ retry: + b->sparse_flow_count--; + b->bulk_flow_count++; + +- cake_inc_srchost_bulk_flow_count(b, flow, q->flow_mode); +- cake_inc_dsthost_bulk_flow_count(b, flow, q->flow_mode); ++ cake_inc_srchost_bulk_flow_count(b, flow, q->config->flow_mode); ++ cake_inc_dsthost_bulk_flow_count(b, flow, q->config->flow_mode); + + flow->set = CAKE_SET_BULK; + } else { +@@ -2116,7 +2121,7 @@ retry: + } + } + +- flow->deficit += cake_get_flow_quantum(b, flow, q->flow_mode); ++ flow->deficit += cake_get_flow_quantum(b, flow, q->config->flow_mode); + list_move_tail(&flow->flowchain, &b->old_flows); + + goto retry; +@@ -2140,8 +2145,8 @@ retry: + if (flow->set == CAKE_SET_BULK) { + b->bulk_flow_count--; + +- cake_dec_srchost_bulk_flow_count(b, flow, q->flow_mode); +- cake_dec_dsthost_bulk_flow_count(b, flow, q->flow_mode); ++ cake_dec_srchost_bulk_flow_count(b, flow, q->config->flow_mode); ++ cake_dec_dsthost_bulk_flow_count(b, flow, q->config->flow_mode); + + b->decaying_flow_count++; + } else if (flow->set == CAKE_SET_SPARSE || +@@ -2159,8 +2164,8 @@ retry: + else if (flow->set == CAKE_SET_BULK) { + b->bulk_flow_count--; + +- cake_dec_srchost_bulk_flow_count(b, flow, q->flow_mode); +- cake_dec_dsthost_bulk_flow_count(b, flow, q->flow_mode); ++ cake_dec_srchost_bulk_flow_count(b, flow, q->config->flow_mode); ++ cake_dec_dsthost_bulk_flow_count(b, flow, q->config->flow_mode); + } else + b->decaying_flow_count--; + +@@ -2172,13 +2177,13 @@ retry: + /* Last packet in queue may be marked, shouldn't be dropped */ + if (!cobalt_should_drop(&flow->cvars, &b->cparams, now, skb, + (b->bulk_flow_count * +- !!(q->rate_flags & ++ !!(q->config->rate_flags & + CAKE_FLAG_INGRESS))) || + !flow->head) + break; + + /* drop this packet, get another one */ +- if (q->rate_flags & CAKE_FLAG_INGRESS) { ++ if (q->config->rate_flags & CAKE_FLAG_INGRESS) { + len = cake_advance_shaper(q, b, skb, + now, true); + flow->deficit -= len; +@@ -2189,7 +2194,7 @@ retry: + qdisc_tree_reduce_backlog(sch, 1, qdisc_pkt_len(skb)); + qdisc_qstats_drop(sch); + kfree_skb(skb); +- if (q->rate_flags & CAKE_FLAG_INGRESS) ++ if (q->config->rate_flags & CAKE_FLAG_INGRESS) + goto retry; + } + +@@ -2311,7 +2316,7 @@ static int cake_config_besteffort(struct + struct cake_sched_data *q = qdisc_priv(sch); + struct cake_tin_data *b = &q->tins[0]; + u32 mtu = psched_mtu(qdisc_dev(sch)); +- u64 rate = q->rate_bps; ++ u64 rate = q->config->rate_bps; + + q->tin_cnt = 1; + +@@ -2319,7 +2324,7 @@ static int cake_config_besteffort(struct + q->tin_order = normal_order; + + cake_set_rate(b, rate, mtu, +- us_to_ns(q->target), us_to_ns(q->interval)); ++ us_to_ns(q->config->target), us_to_ns(q->config->interval)); + b->tin_quantum = 65535; + + return 0; +@@ -2330,7 +2335,7 @@ static int cake_config_precedence(struct + /* convert high-level (user visible) parameters into internal format */ + struct cake_sched_data *q = qdisc_priv(sch); + u32 mtu = psched_mtu(qdisc_dev(sch)); +- u64 rate = q->rate_bps; ++ u64 rate = q->config->rate_bps; + u32 quantum = 256; + u32 i; + +@@ -2341,8 +2346,8 @@ static int cake_config_precedence(struct + for (i = 0; i < q->tin_cnt; i++) { + struct cake_tin_data *b = &q->tins[i]; + +- cake_set_rate(b, rate, mtu, us_to_ns(q->target), +- us_to_ns(q->interval)); ++ cake_set_rate(b, rate, mtu, us_to_ns(q->config->target), ++ us_to_ns(q->config->interval)); + + b->tin_quantum = max_t(u16, 1U, quantum); + +@@ -2419,7 +2424,7 @@ static int cake_config_diffserv8(struct + + struct cake_sched_data *q = qdisc_priv(sch); + u32 mtu = psched_mtu(qdisc_dev(sch)); +- u64 rate = q->rate_bps; ++ u64 rate = q->config->rate_bps; + u32 quantum = 256; + u32 i; + +@@ -2433,8 +2438,8 @@ static int cake_config_diffserv8(struct + for (i = 0; i < q->tin_cnt; i++) { + struct cake_tin_data *b = &q->tins[i]; + +- cake_set_rate(b, rate, mtu, us_to_ns(q->target), +- us_to_ns(q->interval)); ++ cake_set_rate(b, rate, mtu, us_to_ns(q->config->target), ++ us_to_ns(q->config->interval)); + + b->tin_quantum = max_t(u16, 1U, quantum); + +@@ -2463,7 +2468,7 @@ static int cake_config_diffserv4(struct + + struct cake_sched_data *q = qdisc_priv(sch); + u32 mtu = psched_mtu(qdisc_dev(sch)); +- u64 rate = q->rate_bps; ++ u64 rate = q->config->rate_bps; + u32 quantum = 1024; + + q->tin_cnt = 4; +@@ -2474,13 +2479,13 @@ static int cake_config_diffserv4(struct + + /* class characteristics */ + cake_set_rate(&q->tins[0], rate, mtu, +- us_to_ns(q->target), us_to_ns(q->interval)); ++ us_to_ns(q->config->target), us_to_ns(q->config->interval)); + cake_set_rate(&q->tins[1], rate >> 4, mtu, +- us_to_ns(q->target), us_to_ns(q->interval)); ++ us_to_ns(q->config->target), us_to_ns(q->config->interval)); + cake_set_rate(&q->tins[2], rate >> 1, mtu, +- us_to_ns(q->target), us_to_ns(q->interval)); ++ us_to_ns(q->config->target), us_to_ns(q->config->interval)); + cake_set_rate(&q->tins[3], rate >> 2, mtu, +- us_to_ns(q->target), us_to_ns(q->interval)); ++ us_to_ns(q->config->target), us_to_ns(q->config->interval)); + + /* bandwidth-sharing weights */ + q->tins[0].tin_quantum = quantum; +@@ -2500,7 +2505,7 @@ static int cake_config_diffserv3(struct + */ + struct cake_sched_data *q = qdisc_priv(sch); + u32 mtu = psched_mtu(qdisc_dev(sch)); +- u64 rate = q->rate_bps; ++ u64 rate = q->config->rate_bps; + u32 quantum = 1024; + + q->tin_cnt = 3; +@@ -2511,11 +2516,11 @@ static int cake_config_diffserv3(struct + + /* class characteristics */ + cake_set_rate(&q->tins[0], rate, mtu, +- us_to_ns(q->target), us_to_ns(q->interval)); ++ us_to_ns(q->config->target), us_to_ns(q->config->interval)); + cake_set_rate(&q->tins[1], rate >> 4, mtu, +- us_to_ns(q->target), us_to_ns(q->interval)); ++ us_to_ns(q->config->target), us_to_ns(q->config->interval)); + cake_set_rate(&q->tins[2], rate >> 2, mtu, +- us_to_ns(q->target), us_to_ns(q->interval)); ++ us_to_ns(q->config->target), us_to_ns(q->config->interval)); + + /* bandwidth-sharing weights */ + q->tins[0].tin_quantum = quantum; +@@ -2527,7 +2532,8 @@ static int cake_config_diffserv3(struct + + static void cake_reconfigure(struct Qdisc *sch) + { +- struct cake_sched_data *q = qdisc_priv(sch); ++ struct cake_sched_data *qd = qdisc_priv(sch); ++ struct cake_sched_config *q = qd->config; + int c, ft; + + switch (q->tin_mode) { +@@ -2553,36 +2559,37 @@ static void cake_reconfigure(struct Qdis + break; + } + +- for (c = q->tin_cnt; c < CAKE_MAX_TINS; c++) { ++ for (c = qd->tin_cnt; c < CAKE_MAX_TINS; c++) { + cake_clear_tin(sch, c); +- q->tins[c].cparams.mtu_time = q->tins[ft].cparams.mtu_time; ++ qd->tins[c].cparams.mtu_time = qd->tins[ft].cparams.mtu_time; + } + +- q->rate_ns = q->tins[ft].tin_rate_ns; +- q->rate_shft = q->tins[ft].tin_rate_shft; ++ qd->rate_ns = qd->tins[ft].tin_rate_ns; ++ qd->rate_shft = qd->tins[ft].tin_rate_shft; + + if (q->buffer_config_limit) { +- q->buffer_limit = q->buffer_config_limit; ++ qd->buffer_limit = q->buffer_config_limit; + } else if (q->rate_bps) { + u64 t = q->rate_bps * q->interval; + + do_div(t, USEC_PER_SEC / 4); +- q->buffer_limit = max_t(u32, t, 4U << 20); ++ qd->buffer_limit = max_t(u32, t, 4U << 20); + } else { +- q->buffer_limit = ~0; ++ qd->buffer_limit = ~0; + } + + sch->flags &= ~TCQ_F_CAN_BYPASS; + +- q->buffer_limit = min(q->buffer_limit, +- max(sch->limit * psched_mtu(qdisc_dev(sch)), +- q->buffer_config_limit)); ++ qd->buffer_limit = min(qd->buffer_limit, ++ max(sch->limit * psched_mtu(qdisc_dev(sch)), ++ q->buffer_config_limit)); + } + + static int cake_change(struct Qdisc *sch, struct nlattr *opt, + struct netlink_ext_ack *extack) + { +- struct cake_sched_data *q = qdisc_priv(sch); ++ struct cake_sched_data *qd = qdisc_priv(sch); ++ struct cake_sched_config *q = qd->config; + struct nlattr *tb[TCA_CAKE_MAX + 1]; + u16 rate_flags; + u8 flow_mode; +@@ -2636,19 +2643,19 @@ static int cake_change(struct Qdisc *sch + nla_get_s32(tb[TCA_CAKE_OVERHEAD])); + rate_flags |= CAKE_FLAG_OVERHEAD; + +- q->max_netlen = 0; +- q->max_adjlen = 0; +- q->min_netlen = ~0; +- q->min_adjlen = ~0; ++ qd->max_netlen = 0; ++ qd->max_adjlen = 0; ++ qd->min_netlen = ~0; ++ qd->min_adjlen = ~0; + } + + if (tb[TCA_CAKE_RAW]) { + rate_flags &= ~CAKE_FLAG_OVERHEAD; + +- q->max_netlen = 0; +- q->max_adjlen = 0; +- q->min_netlen = ~0; +- q->min_adjlen = ~0; ++ qd->max_netlen = 0; ++ qd->max_adjlen = 0; ++ qd->min_netlen = ~0; ++ qd->min_adjlen = ~0; + } + + if (tb[TCA_CAKE_MPU]) +@@ -2704,7 +2711,7 @@ static int cake_change(struct Qdisc *sch + + WRITE_ONCE(q->rate_flags, rate_flags); + WRITE_ONCE(q->flow_mode, flow_mode); +- if (q->tins) { ++ if (qd->tins) { + sch_tree_lock(sch); + cake_reconfigure(sch); + sch_tree_unlock(sch); +@@ -2720,14 +2727,20 @@ static void cake_destroy(struct Qdisc *s + qdisc_watchdog_cancel(&q->watchdog); + tcf_block_put(q->block); + kvfree(q->tins); ++ kvfree(q->config); + } + + static int cake_init(struct Qdisc *sch, struct nlattr *opt, + struct netlink_ext_ack *extack) + { +- struct cake_sched_data *q = qdisc_priv(sch); ++ struct cake_sched_data *qd = qdisc_priv(sch); ++ struct cake_sched_config *q; + int i, j, err; + ++ q = kzalloc(sizeof(*q), GFP_KERNEL); ++ if (!q) ++ return -ENOMEM; ++ + sch->limit = 10240; + q->tin_mode = CAKE_DIFFSERV_DIFFSERV3; + q->flow_mode = CAKE_FLOW_TRIPLE; +@@ -2739,33 +2752,36 @@ static int cake_init(struct Qdisc *sch, + * for 5 to 10% of interval + */ + q->rate_flags |= CAKE_FLAG_SPLIT_GSO; +- q->cur_tin = 0; +- q->cur_flow = 0; ++ qd->cur_tin = 0; ++ qd->cur_flow = 0; ++ qd->config = q; + +- qdisc_watchdog_init(&q->watchdog, sch); ++ qdisc_watchdog_init(&qd->watchdog, sch); + + if (opt) { + err = cake_change(sch, opt, extack); + + if (err) +- return err; ++ goto err; + } + +- err = tcf_block_get(&q->block, &q->filter_list, sch, extack); ++ err = tcf_block_get(&qd->block, &qd->filter_list, sch, extack); + if (err) +- return err; ++ goto err; + + quantum_div[0] = ~0; + for (i = 1; i <= CAKE_QUEUES; i++) + quantum_div[i] = 65535 / i; + +- q->tins = kvcalloc(CAKE_MAX_TINS, sizeof(struct cake_tin_data), +- GFP_KERNEL); +- if (!q->tins) +- return -ENOMEM; ++ qd->tins = kvcalloc(CAKE_MAX_TINS, sizeof(struct cake_tin_data), ++ GFP_KERNEL); ++ if (!qd->tins) { ++ err = -ENOMEM; ++ goto err; ++ } + + for (i = 0; i < CAKE_MAX_TINS; i++) { +- struct cake_tin_data *b = q->tins + i; ++ struct cake_tin_data *b = qd->tins + i; + + INIT_LIST_HEAD(&b->new_flows); + INIT_LIST_HEAD(&b->old_flows); +@@ -2781,22 +2797,27 @@ static int cake_init(struct Qdisc *sch, + INIT_LIST_HEAD(&flow->flowchain); + cobalt_vars_init(&flow->cvars); + +- q->overflow_heap[k].t = i; +- q->overflow_heap[k].b = j; ++ qd->overflow_heap[k].t = i; ++ qd->overflow_heap[k].b = j; + b->overflow_idx[j] = k; + } + } + + cake_reconfigure(sch); +- q->avg_peak_bandwidth = q->rate_bps; +- q->min_netlen = ~0; +- q->min_adjlen = ~0; ++ qd->avg_peak_bandwidth = q->rate_bps; ++ qd->min_netlen = ~0; ++ qd->min_adjlen = ~0; + return 0; ++err: ++ kvfree(qd->config); ++ qd->config = NULL; ++ return err; + } + + static int cake_dump(struct Qdisc *sch, struct sk_buff *skb) + { +- struct cake_sched_data *q = qdisc_priv(sch); ++ struct cake_sched_data *qd = qdisc_priv(sch); ++ struct cake_sched_config *q = qd->config; + struct nlattr *opts; + u16 rate_flags; + u8 flow_mode; diff --git a/target/linux/generic/backport-6.12/700-03-v7.0-net-sched-sch_cake-Add-cake_mq-qdisc-for-using-cake-.patch b/target/linux/generic/backport-6.12/700-03-v7.0-net-sched-sch_cake-Add-cake_mq-qdisc-for-using-cake-.patch new file mode 100644 index 0000000000..645b27f874 --- /dev/null +++ b/target/linux/generic/backport-6.12/700-03-v7.0-net-sched-sch_cake-Add-cake_mq-qdisc-for-using-cake-.patch @@ -0,0 +1,130 @@ +From c076e98c66afe06e41e118eb96d6b15019b822f2 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Toke=20H=C3=B8iland-J=C3=B8rgensen?= +Date: Fri, 9 Jan 2026 14:15:32 +0100 +Subject: [PATCH 3/7] net/sched: sch_cake: Add cake_mq qdisc for using cake on + mq devices +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Add a cake_mq qdisc which installs cake instances on each hardware +queue on a multi-queue device. + +This is just a copy of sch_mq that installs cake instead of the default +qdisc on each queue. Subsequent commits will add sharing of the config +between cake instances, as well as a multi-queue aware shaper algorithm. + +Reviewed-by: Willem de Bruijn +Signed-off-by: Toke Høiland-Jørgensen +Link: https://patch.msgid.link/20260109-mq-cake-sub-qdisc-v8-3-8d613fece5d8@redhat.com +Signed-off-by: Paolo Abeni +--- + net/sched/sch_cake.c | 79 +++++++++++++++++++++++++++++++++++++++++++- + 1 file changed, 78 insertions(+), 1 deletion(-) + +--- a/net/sched/sch_cake.c ++++ b/net/sched/sch_cake.c +@@ -67,6 +67,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -3154,14 +3155,89 @@ static struct Qdisc_ops cake_qdisc_ops _ + }; + MODULE_ALIAS_NET_SCH("cake"); + ++struct cake_mq_sched { ++ struct mq_sched mq_priv; /* must be first */ ++}; ++ ++static void cake_mq_destroy(struct Qdisc *sch) ++{ ++ mq_destroy_common(sch); ++} ++ ++static int cake_mq_init(struct Qdisc *sch, struct nlattr *opt, ++ struct netlink_ext_ack *extack) ++{ ++ int ret; ++ ++ ret = mq_init_common(sch, opt, extack, &cake_qdisc_ops); ++ if (ret) ++ return ret; ++ ++ return 0; ++} ++ ++static int cake_mq_dump(struct Qdisc *sch, struct sk_buff *skb) ++{ ++ mq_dump_common(sch, skb); ++ return 0; ++} ++ ++static int cake_mq_change(struct Qdisc *sch, struct nlattr *opt, ++ struct netlink_ext_ack *extack) ++{ ++ return -EOPNOTSUPP; ++} ++ ++static int cake_mq_graft(struct Qdisc *sch, unsigned long cl, struct Qdisc *new, ++ struct Qdisc **old, struct netlink_ext_ack *extack) ++{ ++ NL_SET_ERR_MSG(extack, "can't replace cake_mq sub-qdiscs"); ++ return -EOPNOTSUPP; ++} ++ ++static const struct Qdisc_class_ops cake_mq_class_ops = { ++ .select_queue = mq_select_queue, ++ .graft = cake_mq_graft, ++ .leaf = mq_leaf, ++ .find = mq_find, ++ .walk = mq_walk, ++ .dump = mq_dump_class, ++ .dump_stats = mq_dump_class_stats, ++}; ++ ++static struct Qdisc_ops cake_mq_qdisc_ops __read_mostly = { ++ .cl_ops = &cake_mq_class_ops, ++ .id = "cake_mq", ++ .priv_size = sizeof(struct cake_mq_sched), ++ .init = cake_mq_init, ++ .destroy = cake_mq_destroy, ++ .attach = mq_attach, ++ .change = cake_mq_change, ++ .change_real_num_tx = mq_change_real_num_tx, ++ .dump = cake_mq_dump, ++ .owner = THIS_MODULE, ++}; ++MODULE_ALIAS_NET_SCH("cake_mq"); ++ + static int __init cake_module_init(void) + { +- return register_qdisc(&cake_qdisc_ops); ++ int ret; ++ ++ ret = register_qdisc(&cake_qdisc_ops); ++ if (ret) ++ return ret; ++ ++ ret = register_qdisc(&cake_mq_qdisc_ops); ++ if (ret) ++ unregister_qdisc(&cake_qdisc_ops); ++ ++ return ret; + } + + static void __exit cake_module_exit(void) + { + unregister_qdisc(&cake_qdisc_ops); ++ unregister_qdisc(&cake_mq_qdisc_ops); + } + + module_init(cake_module_init) +@@ -3169,3 +3245,4 @@ module_exit(cake_module_exit) + MODULE_AUTHOR("Jonathan Morton"); + MODULE_LICENSE("Dual BSD/GPL"); + MODULE_DESCRIPTION("The CAKE shaper."); ++MODULE_IMPORT_NS(NET_SCHED_INTERNAL); diff --git a/target/linux/generic/backport-6.12/700-04-v7.0-net-sched-sch_cake-Share-config-across-cake_mq-sub-q.patch b/target/linux/generic/backport-6.12/700-04-v7.0-net-sched-sch_cake-Share-config-across-cake_mq-sub-q.patch new file mode 100644 index 0000000000..e481ef9c0e --- /dev/null +++ b/target/linux/generic/backport-6.12/700-04-v7.0-net-sched-sch_cake-Share-config-across-cake_mq-sub-q.patch @@ -0,0 +1,318 @@ +From 68e71714d1e02ca388ea99defbe76061b7dd09a3 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Toke=20H=C3=B8iland-J=C3=B8rgensen?= +Date: Fri, 9 Jan 2026 14:15:33 +0100 +Subject: [PATCH 4/7] net/sched: sch_cake: Share config across cake_mq + sub-qdiscs +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +This adds support for configuring the cake_mq instance directly, sharing +the config across the cake sub-qdiscs. + +Signed-off-by: Toke Høiland-Jørgensen +Link: https://patch.msgid.link/20260109-mq-cake-sub-qdisc-v8-4-8d613fece5d8@redhat.com +Signed-off-by: Paolo Abeni +--- + net/sched/sch_cake.c | 173 +++++++++++++++++++++++++++++++++---------- + 1 file changed, 133 insertions(+), 40 deletions(-) + +--- a/net/sched/sch_cake.c ++++ b/net/sched/sch_cake.c +@@ -212,6 +212,7 @@ struct cake_sched_config { + u8 flow_mode; + u8 atm_mode; + u8 ack_filter; ++ u8 is_shared; + }; + + struct cake_sched_data { +@@ -2586,14 +2587,12 @@ static void cake_reconfigure(struct Qdis + q->buffer_config_limit)); + } + +-static int cake_change(struct Qdisc *sch, struct nlattr *opt, +- struct netlink_ext_ack *extack) ++static int cake_config_change(struct cake_sched_config *q, struct nlattr *opt, ++ struct netlink_ext_ack *extack, bool *overhead_changed) + { +- struct cake_sched_data *qd = qdisc_priv(sch); +- struct cake_sched_config *q = qd->config; + struct nlattr *tb[TCA_CAKE_MAX + 1]; +- u16 rate_flags; +- u8 flow_mode; ++ u16 rate_flags = q->rate_flags; ++ u8 flow_mode = q->flow_mode; + int err; + + err = nla_parse_nested_deprecated(tb, TCA_CAKE_MAX, opt, cake_policy, +@@ -2601,7 +2600,6 @@ static int cake_change(struct Qdisc *sch + if (err < 0) + return err; + +- flow_mode = q->flow_mode; + if (tb[TCA_CAKE_NAT]) { + #if IS_ENABLED(CONFIG_NF_CONNTRACK) + flow_mode &= ~CAKE_FLOW_NAT_FLAG; +@@ -2614,6 +2612,19 @@ static int cake_change(struct Qdisc *sch + #endif + } + ++ if (tb[TCA_CAKE_AUTORATE]) { ++ if (!!nla_get_u32(tb[TCA_CAKE_AUTORATE])) { ++ if (q->is_shared) { ++ NL_SET_ERR_MSG_ATTR(extack, tb[TCA_CAKE_AUTORATE], ++ "Can't use autorate-ingress with cake_mq"); ++ return -EOPNOTSUPP; ++ } ++ rate_flags |= CAKE_FLAG_AUTORATE_INGRESS; ++ } else { ++ rate_flags &= ~CAKE_FLAG_AUTORATE_INGRESS; ++ } ++ } ++ + if (tb[TCA_CAKE_BASE_RATE64]) + WRITE_ONCE(q->rate_bps, + nla_get_u64(tb[TCA_CAKE_BASE_RATE64])); +@@ -2622,7 +2633,6 @@ static int cake_change(struct Qdisc *sch + WRITE_ONCE(q->tin_mode, + nla_get_u32(tb[TCA_CAKE_DIFFSERV_MODE])); + +- rate_flags = q->rate_flags; + if (tb[TCA_CAKE_WASH]) { + if (!!nla_get_u32(tb[TCA_CAKE_WASH])) + rate_flags |= CAKE_FLAG_WASH; +@@ -2643,20 +2653,12 @@ static int cake_change(struct Qdisc *sch + WRITE_ONCE(q->rate_overhead, + nla_get_s32(tb[TCA_CAKE_OVERHEAD])); + rate_flags |= CAKE_FLAG_OVERHEAD; +- +- qd->max_netlen = 0; +- qd->max_adjlen = 0; +- qd->min_netlen = ~0; +- qd->min_adjlen = ~0; ++ *overhead_changed = true; + } + + if (tb[TCA_CAKE_RAW]) { + rate_flags &= ~CAKE_FLAG_OVERHEAD; +- +- qd->max_netlen = 0; +- qd->max_adjlen = 0; +- qd->min_netlen = ~0; +- qd->min_adjlen = ~0; ++ *overhead_changed = true; + } + + if (tb[TCA_CAKE_MPU]) +@@ -2675,13 +2677,6 @@ static int cake_change(struct Qdisc *sch + WRITE_ONCE(q->target, max(target, 1U)); + } + +- if (tb[TCA_CAKE_AUTORATE]) { +- if (!!nla_get_u32(tb[TCA_CAKE_AUTORATE])) +- rate_flags |= CAKE_FLAG_AUTORATE_INGRESS; +- else +- rate_flags &= ~CAKE_FLAG_AUTORATE_INGRESS; +- } +- + if (tb[TCA_CAKE_INGRESS]) { + if (!!nla_get_u32(tb[TCA_CAKE_INGRESS])) + rate_flags |= CAKE_FLAG_INGRESS; +@@ -2712,6 +2707,34 @@ static int cake_change(struct Qdisc *sch + + WRITE_ONCE(q->rate_flags, rate_flags); + WRITE_ONCE(q->flow_mode, flow_mode); ++ ++ return 0; ++} ++ ++static int cake_change(struct Qdisc *sch, struct nlattr *opt, ++ struct netlink_ext_ack *extack) ++{ ++ struct cake_sched_data *qd = qdisc_priv(sch); ++ struct cake_sched_config *q = qd->config; ++ bool overhead_changed = false; ++ int ret; ++ ++ if (q->is_shared) { ++ NL_SET_ERR_MSG(extack, "can't reconfigure cake_mq sub-qdiscs"); ++ return -EOPNOTSUPP; ++ } ++ ++ ret = cake_config_change(q, opt, extack, &overhead_changed); ++ if (ret) ++ return ret; ++ ++ if (overhead_changed) { ++ qd->max_netlen = 0; ++ qd->max_adjlen = 0; ++ qd->min_netlen = ~0; ++ qd->min_adjlen = ~0; ++ } ++ + if (qd->tins) { + sch_tree_lock(sch); + cake_reconfigure(sch); +@@ -2728,7 +2751,23 @@ static void cake_destroy(struct Qdisc *s + qdisc_watchdog_cancel(&q->watchdog); + tcf_block_put(q->block); + kvfree(q->tins); +- kvfree(q->config); ++ if (q->config && !q->config->is_shared) ++ kvfree(q->config); ++} ++ ++static void cake_config_init(struct cake_sched_config *q, bool is_shared) ++{ ++ q->tin_mode = CAKE_DIFFSERV_DIFFSERV3; ++ q->flow_mode = CAKE_FLOW_TRIPLE; ++ ++ q->rate_bps = 0; /* unlimited by default */ ++ ++ q->interval = 100000; /* 100ms default */ ++ q->target = 5000; /* 5ms: codel RFC argues ++ * for 5 to 10% of interval ++ */ ++ q->rate_flags |= CAKE_FLAG_SPLIT_GSO; ++ q->is_shared = is_shared; + } + + static int cake_init(struct Qdisc *sch, struct nlattr *opt, +@@ -2742,17 +2781,9 @@ static int cake_init(struct Qdisc *sch, + if (!q) + return -ENOMEM; + +- sch->limit = 10240; +- q->tin_mode = CAKE_DIFFSERV_DIFFSERV3; +- q->flow_mode = CAKE_FLOW_TRIPLE; ++ cake_config_init(q, false); + +- q->rate_bps = 0; /* unlimited by default */ +- +- q->interval = 100000; /* 100ms default */ +- q->target = 5000; /* 5ms: codel RFC argues +- * for 5 to 10% of interval +- */ +- q->rate_flags |= CAKE_FLAG_SPLIT_GSO; ++ sch->limit = 10240; + qd->cur_tin = 0; + qd->cur_flow = 0; + qd->config = q; +@@ -2815,10 +2846,21 @@ err: + return err; + } + +-static int cake_dump(struct Qdisc *sch, struct sk_buff *skb) ++static void cake_config_replace(struct Qdisc *sch, struct cake_sched_config *cfg) + { + struct cake_sched_data *qd = qdisc_priv(sch); + struct cake_sched_config *q = qd->config; ++ ++ qd->config = cfg; ++ ++ if (!q->is_shared) ++ kvfree(q); ++ ++ cake_reconfigure(sch); ++} ++ ++static int cake_config_dump(struct cake_sched_config *q, struct sk_buff *skb) ++{ + struct nlattr *opts; + u16 rate_flags; + u8 flow_mode; +@@ -2894,6 +2936,13 @@ nla_put_failure: + return -1; + } + ++static int cake_dump(struct Qdisc *sch, struct sk_buff *skb) ++{ ++ struct cake_sched_data *qd = qdisc_priv(sch); ++ ++ return cake_config_dump(qd->config, skb); ++} ++ + static int cake_dump_stats(struct Qdisc *sch, struct gnet_dump *d) + { + struct nlattr *stats = nla_nest_start_noflag(d->skb, TCA_STATS_APP); +@@ -3157,6 +3206,7 @@ MODULE_ALIAS_NET_SCH("cake"); + + struct cake_mq_sched { + struct mq_sched mq_priv; /* must be first */ ++ struct cake_sched_config cake_config; + }; + + static void cake_mq_destroy(struct Qdisc *sch) +@@ -3167,25 +3217,68 @@ static void cake_mq_destroy(struct Qdisc + static int cake_mq_init(struct Qdisc *sch, struct nlattr *opt, + struct netlink_ext_ack *extack) + { +- int ret; ++ struct cake_mq_sched *priv = qdisc_priv(sch); ++ struct net_device *dev = qdisc_dev(sch); ++ int ret, ntx; ++ bool _unused; ++ ++ cake_config_init(&priv->cake_config, true); ++ if (opt) { ++ ret = cake_config_change(&priv->cake_config, opt, extack, &_unused); ++ if (ret) ++ return ret; ++ } + + ret = mq_init_common(sch, opt, extack, &cake_qdisc_ops); + if (ret) + return ret; + ++ for (ntx = 0; ntx < dev->num_tx_queues; ntx++) ++ cake_config_replace(priv->mq_priv.qdiscs[ntx], &priv->cake_config); ++ + return 0; + } + + static int cake_mq_dump(struct Qdisc *sch, struct sk_buff *skb) + { ++ struct cake_mq_sched *priv = qdisc_priv(sch); ++ + mq_dump_common(sch, skb); +- return 0; ++ return cake_config_dump(&priv->cake_config, skb); + } + + static int cake_mq_change(struct Qdisc *sch, struct nlattr *opt, + struct netlink_ext_ack *extack) + { +- return -EOPNOTSUPP; ++ struct cake_mq_sched *priv = qdisc_priv(sch); ++ struct net_device *dev = qdisc_dev(sch); ++ bool overhead_changed = false; ++ unsigned int ntx; ++ int ret; ++ ++ ret = cake_config_change(&priv->cake_config, opt, extack, &overhead_changed); ++ if (ret) ++ return ret; ++ ++ for (ntx = 0; ntx < dev->num_tx_queues; ntx++) { ++ struct Qdisc *chld = rtnl_dereference(netdev_get_tx_queue(dev, ntx)->qdisc_sleeping); ++ struct cake_sched_data *qd = qdisc_priv(chld); ++ ++ if (overhead_changed) { ++ qd->max_netlen = 0; ++ qd->max_adjlen = 0; ++ qd->min_netlen = ~0; ++ qd->min_adjlen = ~0; ++ } ++ ++ if (qd->tins) { ++ sch_tree_lock(chld); ++ cake_reconfigure(chld); ++ sch_tree_unlock(chld); ++ } ++ } ++ ++ return 0; + } + + static int cake_mq_graft(struct Qdisc *sch, unsigned long cl, struct Qdisc *new, diff --git a/target/linux/generic/backport-6.12/700-05-v7.0-net-sched-sch_cake-share-shaper-state-across-sub-ins.patch b/target/linux/generic/backport-6.12/700-05-v7.0-net-sched-sch_cake-share-shaper-state-across-sub-ins.patch new file mode 100644 index 0000000000..62bf38d35d --- /dev/null +++ b/target/linux/generic/backport-6.12/700-05-v7.0-net-sched-sch_cake-share-shaper-state-across-sub-ins.patch @@ -0,0 +1,175 @@ +From 090dde0780266eb80126fa970870bafdb4e937c3 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jonas=20K=C3=B6ppeler?= +Date: Fri, 9 Jan 2026 14:15:34 +0100 +Subject: [PATCH 5/7] net/sched: sch_cake: share shaper state across + sub-instances of cake_mq +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +This commit adds shared shaper state across the cake instances beneath a +cake_mq qdisc. It works by periodically tracking the number of active +instances, and scaling the configured rate by the number of active +queues. + +The scan is lockless and simply reads the qlen and the last_active state +variable of each of the instances configured beneath the parent cake_mq +instance. Locking is not required since the values are only updated by +the owning instance, and eventual consistency is sufficient for the +purpose of estimating the number of active queues. + +The interval for scanning the number of active queues is set to 200 us. +We found this to be a good tradeoff between overhead and response time. +For a detailed analysis of this aspect see the Netdevconf talk: + +https://netdevconf.info/0x19/docs/netdev-0x19-paper16-talk-paper.pdf + +Reviewed-by: Jamal Hadi Salim +Signed-off-by: Jonas Köppeler +Signed-off-by: Toke Høiland-Jørgensen +Link: https://patch.msgid.link/20260109-mq-cake-sub-qdisc-v8-5-8d613fece5d8@redhat.com +Signed-off-by: Paolo Abeni +--- + Documentation/netlink/specs/tc.yaml | 3 ++ + include/uapi/linux/pkt_sched.h | 1 + + net/sched/sch_cake.c | 51 +++++++++++++++++++++++++++++ + 3 files changed, 55 insertions(+) + +--- a/Documentation/netlink/specs/tc.yaml ++++ b/Documentation/netlink/specs/tc.yaml +@@ -2161,6 +2161,9 @@ attribute-sets: + - + name: blue-timer-us + type: s32 ++ - ++ name: active-queues ++ type: u32 + - + name: tc-cake-tin-stats-attrs + attributes: +--- a/include/uapi/linux/pkt_sched.h ++++ b/include/uapi/linux/pkt_sched.h +@@ -1034,6 +1034,7 @@ enum { + TCA_CAKE_STATS_DROP_NEXT_US, + TCA_CAKE_STATS_P_DROP, + TCA_CAKE_STATS_BLUE_TIMER_US, ++ TCA_CAKE_STATS_ACTIVE_QUEUES, + __TCA_CAKE_STATS_MAX + }; + #define TCA_CAKE_STATS_MAX (__TCA_CAKE_STATS_MAX - 1) +--- a/net/sched/sch_cake.c ++++ b/net/sched/sch_cake.c +@@ -202,6 +202,7 @@ struct cake_sched_config { + u64 rate_bps; + u64 interval; + u64 target; ++ u64 sync_time; + u32 buffer_config_limit; + u32 fwmark_mask; + u16 fwmark_shft; +@@ -258,6 +259,11 @@ struct cake_sched_data { + u16 max_adjlen; + u16 min_netlen; + u16 min_adjlen; ++ ++ /* mq sync state */ ++ u64 last_checked_active; ++ u64 last_active; ++ u32 active_queues; + }; + + enum { +@@ -384,6 +390,8 @@ static const u32 inv_sqrt_cache[REC_INV_ + 1239850263, 1191209601, 1147878294, 1108955788 + }; + ++static void cake_set_rate(struct cake_tin_data *b, u64 rate, u32 mtu, ++ u64 target_ns, u64 rtt_est_ns); + /* http://en.wikipedia.org/wiki/Methods_of_computing_square_roots + * new_invsqrt = (invsqrt / 2) * (3 - count * invsqrt^2) + * +@@ -2003,6 +2011,40 @@ static struct sk_buff *cake_dequeue(stru + u64 delay; + u32 len; + ++ if (q->config->is_shared && now - q->last_checked_active >= q->config->sync_time) { ++ struct net_device *dev = qdisc_dev(sch); ++ struct cake_sched_data *other_priv; ++ u64 new_rate = q->config->rate_bps; ++ u64 other_qlen, other_last_active; ++ struct Qdisc *other_sch; ++ u32 num_active_qs = 1; ++ unsigned int ntx; ++ ++ for (ntx = 0; ntx < dev->num_tx_queues; ntx++) { ++ other_sch = rcu_dereference(netdev_get_tx_queue(dev, ntx)->qdisc_sleeping); ++ other_priv = qdisc_priv(other_sch); ++ ++ if (other_priv == q) ++ continue; ++ ++ other_qlen = READ_ONCE(other_sch->q.qlen); ++ other_last_active = READ_ONCE(other_priv->last_active); ++ ++ if (other_qlen || other_last_active > q->last_checked_active) ++ num_active_qs++; ++ } ++ ++ if (num_active_qs > 1) ++ new_rate = div64_u64(q->config->rate_bps, num_active_qs); ++ ++ /* mtu = 0 is used to only update the rate and not mess with cobalt params */ ++ cake_set_rate(b, new_rate, 0, 0, 0); ++ q->last_checked_active = now; ++ q->active_queues = num_active_qs; ++ q->rate_ns = b->tin_rate_ns; ++ q->rate_shft = b->tin_rate_shft; ++ } ++ + begin: + if (!sch->q.qlen) + return NULL; +@@ -2202,6 +2244,7 @@ retry: + + b->tin_ecn_mark += !!flow->cvars.ecn_marked; + qdisc_bstats_update(sch, skb); ++ WRITE_ONCE(q->last_active, now); + + /* collect delay stats */ + delay = ktime_to_ns(ktime_sub(now, cobalt_get_enqueue_time(skb))); +@@ -2302,6 +2345,9 @@ static void cake_set_rate(struct cake_ti + b->tin_rate_ns = rate_ns; + b->tin_rate_shft = rate_shft; + ++ if (mtu == 0) ++ return; ++ + byte_target_ns = (byte_target * rate_ns) >> rate_shft; + + b->cparams.target = max((byte_target_ns * 3) / 2, target_ns); +@@ -2768,6 +2814,7 @@ static void cake_config_init(struct cake + */ + q->rate_flags |= CAKE_FLAG_SPLIT_GSO; + q->is_shared = is_shared; ++ q->sync_time = 200 * NSEC_PER_USEC; + } + + static int cake_init(struct Qdisc *sch, struct nlattr *opt, +@@ -2839,6 +2886,9 @@ static int cake_init(struct Qdisc *sch, + qd->avg_peak_bandwidth = q->rate_bps; + qd->min_netlen = ~0; + qd->min_adjlen = ~0; ++ qd->active_queues = 0; ++ qd->last_checked_active = 0; ++ + return 0; + err: + kvfree(qd->config); +@@ -2971,6 +3021,7 @@ static int cake_dump_stats(struct Qdisc + PUT_STAT_U32(MAX_ADJLEN, q->max_adjlen); + PUT_STAT_U32(MIN_NETLEN, q->min_netlen); + PUT_STAT_U32(MIN_ADJLEN, q->min_adjlen); ++ PUT_STAT_U32(ACTIVE_QUEUES, q->active_queues); + + #undef PUT_STAT_U32 + #undef PUT_STAT_U64 diff --git a/target/linux/generic/backport-6.12/700-06-v7.0-selftests-tc-testing-add-selftests-for-cake_mq-qdisc.patch b/target/linux/generic/backport-6.12/700-06-v7.0-selftests-tc-testing-add-selftests-for-cake_mq-qdisc.patch new file mode 100644 index 0000000000..c012a15884 --- /dev/null +++ b/target/linux/generic/backport-6.12/700-06-v7.0-selftests-tc-testing-add-selftests-for-cake_mq-qdisc.patch @@ -0,0 +1,606 @@ +From e5e7c7c6a027ce2f001fc60dc388a560c01427fd Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jonas=20K=C3=B6ppeler?= +Date: Fri, 9 Jan 2026 14:15:35 +0100 +Subject: [PATCH 6/7] selftests/tc-testing: add selftests for cake_mq qdisc +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Test 684b: Create CAKE_MQ with default setting (4 queues) +Test 7ee8: Create CAKE_MQ with bandwidth limit (4 queues) +Test 1f87: Create CAKE_MQ with rtt time (4 queues) +Test e9cf: Create CAKE_MQ with besteffort flag (4 queues) +Test 7c05: Create CAKE_MQ with diffserv8 flag (4 queues) +Test 5a77: Create CAKE_MQ with diffserv4 flag (4 queues) +Test 8f7a: Create CAKE_MQ with flowblind flag (4 queues) +Test 7ef7: Create CAKE_MQ with dsthost and nat flag (4 queues) +Test 2e4d: Create CAKE_MQ with wash flag (4 queues) +Test b3e6: Create CAKE_MQ with flowblind and no-split-gso flag (4 queues) +Test 62cd: Create CAKE_MQ with dual-srchost and ack-filter flag (4 queues) +Test 0df3: Create CAKE_MQ with dual-dsthost and ack-filter-aggressive flag (4 queues) +Test 9a75: Create CAKE_MQ with memlimit and ptm flag (4 queues) +Test cdef: Create CAKE_MQ with fwmark and atm flag (4 queues) +Test 93dd: Create CAKE_MQ with overhead 0 and mpu (4 queues) +Test 1475: Create CAKE_MQ with conservative and ingress flag (4 queues) +Test 7bf1: Delete CAKE_MQ with conservative and ingress flag (4 queues) +Test ee55: Replace CAKE_MQ with mpu (4 queues) +Test 6df9: Change CAKE_MQ with mpu (4 queues) +Test 67e2: Show CAKE_MQ class (4 queues) +Test 2de4: Change bandwidth of CAKE_MQ (4 queues) +Test 5f62: Fail to create CAKE_MQ with autorate-ingress flag (4 queues) +Test 038e: Fail to change setting of sub-qdisc under CAKE_MQ +Test 7bdc: Fail to replace sub-qdisc under CAKE_MQ +Test 18e0: Fail to install CAKE_MQ on single queue device + +Reviewed-by: Victor Nogueira +Signed-off-by: Jonas Köppeler +Signed-off-by: Toke Høiland-Jørgensen +Link: https://patch.msgid.link/20260109-mq-cake-sub-qdisc-v8-6-8d613fece5d8@redhat.com +Signed-off-by: Paolo Abeni +--- + .../tc-testing/tc-tests/qdiscs/cake_mq.json | 559 ++++++++++++++++++ + 1 file changed, 559 insertions(+) + create mode 100644 tools/testing/selftests/tc-testing/tc-tests/qdiscs/cake_mq.json + +--- /dev/null ++++ b/tools/testing/selftests/tc-testing/tc-tests/qdiscs/cake_mq.json +@@ -0,0 +1,559 @@ ++[ ++ { ++ "id": "684b", ++ "name": "Create CAKE_MQ with default setting (4 queues)", ++ "category": [ ++ "qdisc", ++ "cake_mq" ++ ], ++ "plugins": { ++ "requires": "nsPlugin" ++ }, ++ "setup": [ ++ "echo \"1\" > /sys/bus/netdevsim/del_device || true", ++ "echo \"1 1 4\" > /sys/bus/netdevsim/new_device" ++ ], ++ "cmdUnderTest": "$TC qdisc add dev $ETH handle 1: root cake_mq", ++ "expExitCode": "0", ++ "verifyCmd": "$TC qdisc show dev $ETH", ++ "matchPattern": "qdisc (cake_mq 1: root|cake 0: parent 1:[1-4]) bandwidth unlimited diffserv3 triple-isolate nonat nowash no-ack-filter split-gso rtt 100ms raw overhead 0 ", ++ "matchCount": "5", ++ "teardown": [ ++ "echo \"1\" > /sys/bus/netdevsim/del_device" ++ ] ++ }, ++ { ++ "id": "7ee8", ++ "name": "Create CAKE_MQ with bandwidth limit (4 queues)", ++ "category": [ ++ "qdisc", ++ "cake_mq" ++ ], ++ "plugins": { ++ "requires": "nsPlugin" ++ }, ++ "setup": [ ++ "echo \"1 1 4\" > /sys/bus/netdevsim/new_device" ++ ], ++ "cmdUnderTest": "$TC qdisc add dev $ETH handle 1: root cake_mq bandwidth 1000", ++ "expExitCode": "0", ++ "verifyCmd": "$TC qdisc show dev $ETH", ++ "matchPattern": "qdisc (cake_mq 1: root|cake 0: parent 1:[1-4]) bandwidth 1Kbit diffserv3 triple-isolate nonat nowash no-ack-filter split-gso rtt 100ms raw overhead 0 ", ++ "matchCount": "5", ++ "teardown": [ ++ "echo \"1\" > /sys/bus/netdevsim/del_device" ++ ] ++ }, ++ { ++ "id": "1f87", ++ "name": "Create CAKE_MQ with rtt time (4 queues)", ++ "category": [ ++ "qdisc", ++ "cake_mq" ++ ], ++ "plugins": { ++ "requires": "nsPlugin" ++ }, ++ "setup": [ ++ "echo \"1 1 4\" > /sys/bus/netdevsim/new_device" ++ ], ++ "cmdUnderTest": "$TC qdisc add dev $ETH handle 1: root cake_mq rtt 200", ++ "expExitCode": "0", ++ "verifyCmd": "$TC qdisc show dev $ETH", ++ "matchPattern": "qdisc (cake_mq 1: root|cake 0: parent 1:[1-4]) bandwidth unlimited diffserv3 triple-isolate nonat nowash no-ack-filter split-gso rtt 200us raw overhead 0 ", ++ "matchCount": "5", ++ "teardown": [ ++ "echo \"1\" > /sys/bus/netdevsim/del_device" ++ ] ++ }, ++ { ++ "id": "e9cf", ++ "name": "Create CAKE_MQ with besteffort flag (4 queues)", ++ "category": [ ++ "qdisc", ++ "cake_mq" ++ ], ++ "plugins": { ++ "requires": "nsPlugin" ++ }, ++ "setup": [ ++ "echo \"1 1 4\" > /sys/bus/netdevsim/new_device" ++ ], ++ "cmdUnderTest": "$TC qdisc add dev $ETH handle 1: root cake_mq besteffort", ++ "expExitCode": "0", ++ "verifyCmd": "$TC qdisc show dev $ETH", ++ "matchPattern": "qdisc (cake_mq 1: root|cake 0: parent 1:[1-4]) bandwidth unlimited besteffort triple-isolate nonat nowash no-ack-filter split-gso rtt 100ms raw overhead 0 ", ++ "matchCount": "5", ++ "teardown": [ ++ "echo \"1\" > /sys/bus/netdevsim/del_device" ++ ] ++ }, ++ { ++ "id": "7c05", ++ "name": "Create CAKE_MQ with diffserv8 flag (4 queues)", ++ "category": [ ++ "qdisc", ++ "cake_mq" ++ ], ++ "plugins": { ++ "requires": "nsPlugin" ++ }, ++ "setup": [ ++ "echo \"1 1 4\" > /sys/bus/netdevsim/new_device" ++ ], ++ "cmdUnderTest": "$TC qdisc add dev $ETH handle 1: root cake_mq diffserv8", ++ "expExitCode": "0", ++ "verifyCmd": "$TC qdisc show dev $ETH", ++ "matchPattern": "qdisc (cake_mq 1: root|cake 0: parent 1:[1-4]) bandwidth unlimited diffserv8 triple-isolate nonat nowash no-ack-filter split-gso rtt 100ms raw overhead 0 ", ++ "matchCount": "5", ++ "teardown": [ ++ "echo \"1\" > /sys/bus/netdevsim/del_device" ++ ] ++ }, ++ { ++ "id": "5a77", ++ "name": "Create CAKE_MQ with diffserv4 flag (4 queues)", ++ "category": [ ++ "qdisc", ++ "cake_mq" ++ ], ++ "plugins": { ++ "requires": "nsPlugin" ++ }, ++ "setup": [ ++ "echo \"1 1 4\" > /sys/bus/netdevsim/new_device" ++ ], ++ "cmdUnderTest": "$TC qdisc add dev $ETH handle 1: root cake_mq diffserv4", ++ "expExitCode": "0", ++ "verifyCmd": "$TC qdisc show dev $ETH", ++ "matchPattern": "qdisc (cake_mq 1: root|cake 0: parent 1:[1-4]) bandwidth unlimited diffserv4 triple-isolate nonat nowash no-ack-filter split-gso rtt 100ms raw overhead 0 ", ++ "matchCount": "5", ++ "teardown": [ ++ "echo \"1\" > /sys/bus/netdevsim/del_device" ++ ] ++ }, ++ { ++ "id": "8f7a", ++ "name": "Create CAKE_MQ with flowblind flag (4 queues)", ++ "category": [ ++ "qdisc", ++ "cake_mq" ++ ], ++ "plugins": { ++ "requires": "nsPlugin" ++ }, ++ "setup": [ ++ "echo \"1 1 4\" > /sys/bus/netdevsim/new_device" ++ ], ++ "cmdUnderTest": "$TC qdisc add dev $ETH handle 1: root cake_mq flowblind", ++ "expExitCode": "0", ++ "verifyCmd": "$TC qdisc show dev $ETH", ++ "matchPattern": "qdisc (cake_mq 1: root|cake 0: parent 1:[1-4]) bandwidth unlimited diffserv3 flowblind nonat nowash no-ack-filter split-gso rtt 100ms raw overhead 0 ", ++ "matchCount": "5", ++ "teardown": [ ++ "echo \"1\" > /sys/bus/netdevsim/del_device" ++ ] ++ }, ++ { ++ "id": "7ef7", ++ "name": "Create CAKE_MQ with dsthost and nat flag (4 queues)", ++ "category": [ ++ "qdisc", ++ "cake_mq" ++ ], ++ "plugins": { ++ "requires": "nsPlugin" ++ }, ++ "setup": [ ++ "echo \"1 1 4\" > /sys/bus/netdevsim/new_device" ++ ], ++ "cmdUnderTest": "$TC qdisc add dev $ETH handle 1: root cake_mq dsthost nat", ++ "expExitCode": "0", ++ "verifyCmd": "$TC qdisc show dev $ETH", ++ "matchPattern": "qdisc (cake_mq 1: root|cake 0: parent 1:[1-4]) bandwidth unlimited diffserv3 dsthost nat nowash no-ack-filter split-gso rtt 100ms raw overhead 0 ", ++ "matchCount": "5", ++ "teardown": [ ++ "echo \"1\" > /sys/bus/netdevsim/del_device" ++ ] ++ }, ++ { ++ "id": "2e4d", ++ "name": "Create CAKE_MQ with wash flag (4 queues)", ++ "category": [ ++ "qdisc", ++ "cake_mq" ++ ], ++ "plugins": { ++ "requires": "nsPlugin" ++ }, ++ "setup": [ ++ "echo \"1 1 4\" > /sys/bus/netdevsim/new_device" ++ ], ++ "cmdUnderTest": "$TC qdisc add dev $ETH handle 1: root cake_mq hosts wash", ++ "expExitCode": "0", ++ "verifyCmd": "$TC qdisc show dev $ETH", ++ "matchPattern": "qdisc (cake_mq 1: root|cake 0: parent 1:[1-4]) bandwidth unlimited diffserv3 hosts nonat wash no-ack-filter split-gso rtt 100ms raw overhead 0 ", ++ "matchCount": "5", ++ "teardown": [ ++ "echo \"1\" > /sys/bus/netdevsim/del_device" ++ ] ++ }, ++ { ++ "id": "b3e6", ++ "name": "Create CAKE_MQ with flowblind and no-split-gso flag (4 queues)", ++ "category": [ ++ "qdisc", ++ "cake_mq" ++ ], ++ "plugins": { ++ "requires": "nsPlugin" ++ }, ++ "setup": [ ++ "echo \"1 1 4\" > /sys/bus/netdevsim/new_device" ++ ], ++ "cmdUnderTest": "$TC qdisc add dev $ETH handle 1: root cake_mq flowblind no-split-gso", ++ "expExitCode": "0", ++ "verifyCmd": "$TC qdisc show dev $ETH", ++ "matchPattern": "qdisc (cake_mq 1: root|cake 0: parent 1:[1-4]) bandwidth unlimited diffserv3 flowblind nonat nowash no-ack-filter no-split-gso rtt 100ms raw overhead 0 ", ++ "matchCount": "5", ++ "teardown": [ ++ "echo \"1\" > /sys/bus/netdevsim/del_device" ++ ] ++ }, ++ { ++ "id": "62cd", ++ "name": "Create CAKE_MQ with dual-srchost and ack-filter flag (4 queues)", ++ "category": [ ++ "qdisc", ++ "cake_mq" ++ ], ++ "plugins": { ++ "requires": "nsPlugin" ++ }, ++ "setup": [ ++ "echo \"1 1 4\" > /sys/bus/netdevsim/new_device" ++ ], ++ "cmdUnderTest": "$TC qdisc add dev $ETH handle 1: root cake_mq dual-srchost ack-filter", ++ "expExitCode": "0", ++ "verifyCmd": "$TC qdisc show dev $ETH", ++ "matchPattern": "qdisc (cake_mq 1: root|cake 0: parent 1:[1-4]) bandwidth unlimited diffserv3 dual-srchost nonat nowash ack-filter split-gso rtt 100ms raw overhead 0 ", ++ "matchCount": "5", ++ "teardown": [ ++ "echo \"1\" > /sys/bus/netdevsim/del_device" ++ ] ++ }, ++ { ++ "id": "0df3", ++ "name": "Create CAKE_MQ with dual-dsthost and ack-filter-aggressive flag (4 queues)", ++ "category": [ ++ "qdisc", ++ "cake_mq" ++ ], ++ "plugins": { ++ "requires": "nsPlugin" ++ }, ++ "setup": [ ++ "echo \"1 1 4\" > /sys/bus/netdevsim/new_device" ++ ], ++ "cmdUnderTest": "$TC qdisc add dev $ETH handle 1: root cake_mq dual-dsthost ack-filter-aggressive", ++ "expExitCode": "0", ++ "verifyCmd": "$TC qdisc show dev $ETH", ++ "matchPattern": "qdisc (cake_mq 1: root|cake 0: parent 1:[1-4]) bandwidth unlimited diffserv3 dual-dsthost nonat nowash ack-filter-aggressive split-gso rtt 100ms raw overhead 0 ", ++ "matchCount": "5", ++ "teardown": [ ++ "echo \"1\" > /sys/bus/netdevsim/del_device" ++ ] ++ }, ++ { ++ "id": "9a75", ++ "name": "Create CAKE_MQ with memlimit and ptm flag (4 queues)", ++ "category": [ ++ "qdisc", ++ "cake_mq" ++ ], ++ "plugins": { ++ "requires": "nsPlugin" ++ }, ++ "setup": [ ++ "echo \"1 1 4\" > /sys/bus/netdevsim/new_device" ++ ], ++ "cmdUnderTest": "$TC qdisc add dev $ETH handle 1: root cake_mq memlimit 10000 ptm", ++ "expExitCode": "0", ++ "verifyCmd": "$TC qdisc show dev $ETH", ++ "matchPattern": "qdisc (cake_mq 1: root|cake 0: parent 1:[1-4]) bandwidth unlimited diffserv3 triple-isolate nonat nowash no-ack-filter split-gso rtt 100ms raw ptm overhead 0 memlimit 10000b ", ++ "matchCount": "5", ++ "teardown": [ ++ "echo \"1\" > /sys/bus/netdevsim/del_device" ++ ] ++ }, ++ { ++ "id": "cdef", ++ "name": "Create CAKE_MQ with fwmark and atm flag (4 queues)", ++ "category": [ ++ "qdisc", ++ "cake_mq" ++ ], ++ "plugins": { ++ "requires": "nsPlugin" ++ }, ++ "setup": [ ++ "echo \"1 1 4\" > /sys/bus/netdevsim/new_device" ++ ], ++ "cmdUnderTest": "$TC qdisc add dev $ETH handle 1: root cake_mq fwmark 8 atm", ++ "expExitCode": "0", ++ "verifyCmd": "$TC qdisc show dev $ETH", ++ "matchPattern": "qdisc (cake_mq 1: root|cake 0: parent 1:[1-4]) bandwidth unlimited diffserv3 triple-isolate nonat nowash no-ack-filter split-gso rtt 100ms raw atm overhead 0 fwmark 0x8 ", ++ "matchCount": "5", ++ "teardown": [ ++ "echo \"1\" > /sys/bus/netdevsim/del_device" ++ ] ++ }, ++ { ++ "id": "93dd", ++ "name": "Create CAKE_MQ with overhead 0 and mpu (4 queues)", ++ "category": [ ++ "qdisc", ++ "cake_mq" ++ ], ++ "plugins": { ++ "requires": "nsPlugin" ++ }, ++ "setup": [ ++ "echo \"1 1 4\" > /sys/bus/netdevsim/new_device" ++ ], ++ "cmdUnderTest": "$TC qdisc add dev $ETH handle 1: root cake_mq overhead 128 mpu 256", ++ "expExitCode": "0", ++ "verifyCmd": "$TC qdisc show dev $ETH", ++ "matchPattern": "qdisc (cake_mq 1: root|cake 0: parent 1:[1-4]) bandwidth unlimited diffserv3 triple-isolate nonat nowash no-ack-filter split-gso rtt 100ms noatm overhead 128 mpu 256 ", ++ "matchCount": "5", ++ "teardown": [ ++ "echo \"1\" > /sys/bus/netdevsim/del_device" ++ ] ++ }, ++ { ++ "id": "1475", ++ "name": "Create CAKE_MQ with conservative and ingress flag (4 queues)", ++ "category": [ ++ "qdisc", ++ "cake_mq" ++ ], ++ "plugins": { ++ "requires": "nsPlugin" ++ }, ++ "setup": [ ++ "echo \"1 1 4\" > /sys/bus/netdevsim/new_device" ++ ], ++ "cmdUnderTest": "$TC qdisc add dev $ETH handle 1: root cake_mq conservative ingress", ++ "expExitCode": "0", ++ "verifyCmd": "$TC qdisc show dev $ETH", ++ "matchPattern": "qdisc (cake_mq 1: root|cake 0: parent 1:[1-4]) bandwidth unlimited diffserv3 triple-isolate nonat nowash ingress no-ack-filter split-gso rtt 100ms atm overhead 48 ", ++ "matchCount": "5", ++ "teardown": [ ++ "echo \"1\" > /sys/bus/netdevsim/del_device" ++ ] ++ }, ++ { ++ "id": "7bf1", ++ "name": "Delete CAKE_MQ with conservative and ingress flag (4 queues)", ++ "category": [ ++ "qdisc", ++ "cake_mq" ++ ], ++ "plugins": { ++ "requires": "nsPlugin" ++ }, ++ "setup": [ ++ "echo \"1 1 4\" > /sys/bus/netdevsim/new_device", ++ "$TC qdisc add dev $ETH handle 1: root cake_mq conservative ingress" ++ ], ++ "cmdUnderTest": "$TC qdisc del dev $ETH handle 1: root", ++ "expExitCode": "0", ++ "verifyCmd": "$TC qdisc show dev $ETH", ++ "matchPattern": "qdisc (cake_mq 1: root|cake 0: parent 1:[1-4]) bandwidth unlimited diffserv3 triple-isolate nonat nowash ingress no-ack-filter split-gso rtt 100ms atm overhead 48 ", ++ "matchCount": "0", ++ "teardown": [ ++ "echo \"1\" > /sys/bus/netdevsim/del_device" ++ ] ++ }, ++ { ++ "id": "ee55", ++ "name": "Replace CAKE_MQ with mpu (4 queues)", ++ "category": [ ++ "qdisc", ++ "cake_mq" ++ ], ++ "plugins": { ++ "requires": "nsPlugin" ++ }, ++ "setup": [ ++ "echo \"1 1 4\" > /sys/bus/netdevsim/new_device", ++ "$TC qdisc add dev $ETH handle 1: root cake_mq overhead 128 mpu 256" ++ ], ++ "cmdUnderTest": "$TC qdisc replace dev $ETH handle 1: root cake_mq mpu 128", ++ "expExitCode": "0", ++ "verifyCmd": "$TC qdisc show dev $ETH", ++ "matchPattern": "qdisc (cake_mq 1: root|cake 0: parent 1:[1-4]) bandwidth unlimited diffserv3 triple-isolate nonat nowash no-ack-filter split-gso rtt 100ms noatm overhead 128 mpu 128 ", ++ "matchCount": "5", ++ "teardown": [ ++ "echo \"1\" > /sys/bus/netdevsim/del_device" ++ ] ++ }, ++ { ++ "id": "6df9", ++ "name": "Change CAKE_MQ with mpu (4 queues)", ++ "category": [ ++ "qdisc", ++ "cake_mq" ++ ], ++ "plugins": { ++ "requires": "nsPlugin" ++ }, ++ "setup": [ ++ "echo \"1 1 4\" > /sys/bus/netdevsim/new_device", ++ "$TC qdisc add dev $ETH handle 1: root cake_mq overhead 128 mpu 256" ++ ], ++ "cmdUnderTest": "$TC qdisc change dev $ETH handle 1: root cake_mq mpu 128", ++ "expExitCode": "0", ++ "verifyCmd": "$TC qdisc show dev $ETH", ++ "matchPattern": "qdisc (cake_mq 1: root|cake 0: parent 1:[1-4]) bandwidth unlimited diffserv3 triple-isolate nonat nowash no-ack-filter split-gso rtt 100ms noatm overhead 128 mpu 128 ", ++ "matchCount": "5", ++ "teardown": [ ++ "echo \"1\" > /sys/bus/netdevsim/del_device" ++ ] ++ }, ++ { ++ "id": "67e2", ++ "name": "Show CAKE_MQ class (4 queues)", ++ "category": [ ++ "qdisc", ++ "cake_mq" ++ ], ++ "plugins": { ++ "requires": "nsPlugin" ++ }, ++ "setup": [ ++ "echo \"1 1 4\" > /sys/bus/netdevsim/new_device" ++ ], ++ "cmdUnderTest": "$TC qdisc add dev $ETH handle 1: root cake_mq", ++ "expExitCode": "0", ++ "verifyCmd": "$TC class show dev $ETH", ++ "matchPattern": "class cake_mq", ++ "matchCount": "4", ++ "teardown": [ ++ "echo \"1\" > /sys/bus/netdevsim/del_device" ++ ] ++ }, ++ { ++ "id": "2de4", ++ "name": "Change bandwidth of CAKE_MQ (4 queues)", ++ "category": [ ++ "qdisc", ++ "cake_mq" ++ ], ++ "plugins": { ++ "requires": "nsPlugin" ++ }, ++ "setup": [ ++ "echo \"1 1 4\" > /sys/bus/netdevsim/new_device", ++ "$TC qdisc add dev $ETH handle 1: root cake_mq" ++ ], ++ "cmdUnderTest": "$TC qdisc replace dev $ETH handle 1: root cake_mq bandwidth 1000", ++ "expExitCode": "0", ++ "verifyCmd": "$TC qdisc show dev $ETH", ++ "matchPattern": "qdisc (cake_mq 1: root|cake 0: parent 1:[1-4]) bandwidth 1Kbit diffserv3 triple-isolate nonat nowash no-ack-filter split-gso rtt 100ms raw overhead 0 ", ++ "matchCount": "5", ++ "teardown": [ ++ "echo \"1\" > /sys/bus/netdevsim/del_device" ++ ] ++ }, ++ { ++ "id": "5f62", ++ "name": "Fail to create CAKE_MQ with autorate-ingress flag (4 queues)", ++ "category": [ ++ "qdisc", ++ "cake_mq" ++ ], ++ "plugins": { ++ "requires": "nsPlugin" ++ }, ++ "setup": [ ++ "echo \"1 1 4\" > /sys/bus/netdevsim/new_device" ++ ], ++ "cmdUnderTest": "$TC qdisc add dev $ETH handle 1: root cake_mq autorate-ingress", ++ "expExitCode": "2", ++ "verifyCmd": "$TC qdisc show dev $ETH", ++ "matchPattern": "qdisc (cake_mq 1: root|cake 0: parent 1:[1-4]) bandwidth unlimited autorate-ingress diffserv3 triple-isolate nonat nowash no-ack-filter split-gso rtt 100ms raw overhead 0 ", ++ "matchCount": "0", ++ "teardown": [ ++ "echo \"1\" > /sys/bus/netdevsim/del_device" ++ ] ++ }, ++ { ++ "id": "038e", ++ "name": "Fail to change setting of sub-qdisc under CAKE_MQ", ++ "category": [ ++ "qdisc", ++ "cake_mq" ++ ], ++ "plugins": { ++ "requires": "nsPlugin" ++ }, ++ "setup": [ ++ "echo \"1 1 4\" > /sys/bus/netdevsim/new_device", ++ "$TC qdisc add dev $ETH handle 1: root cake_mq" ++ ], ++ "cmdUnderTest": "$TC qdisc add dev $ETH parent 1:1 cake besteffort flows", ++ "expExitCode": "2", ++ "verifyCmd": "$TC qdisc show dev $ETH", ++ "matchPattern": "qdisc (cake_mq 1: root|cake 0: parent 1:[1-4]) bandwidth unlimited diffserv3 triple-isolate nonat nowash no-ack-filter split-gso rtt 100ms raw overhead 0 ", ++ "matchCount": "5", ++ "teardown": [ ++ "echo \"1\" > /sys/bus/netdevsim/del_device" ++ ] ++ }, ++ { ++ "id": "7bdc", ++ "name": "Fail to replace sub-qdisc under CAKE_MQ", ++ "category": [ ++ "qdisc", ++ "cake_mq" ++ ], ++ "plugins": { ++ "requires": "nsPlugin" ++ }, ++ "setup": [ ++ "echo \"1 1 4\" > /sys/bus/netdevsim/new_device", ++ "$TC qdisc add dev $ETH handle 1: root cake_mq" ++ ], ++ "cmdUnderTest": "$TC qdisc add dev $ETH parent 1:1 fq", ++ "expExitCode": "2", ++ "verifyCmd": "$TC qdisc show dev $ETH", ++ "matchPattern": "qdisc (cake_mq 1: root|cake 0: parent 1:[1-4]) bandwidth unlimited diffserv3 triple-isolate nonat nowash no-ack-filter split-gso rtt 100ms raw overhead 0 ", ++ "matchCount": "5", ++ "teardown": [ ++ "echo \"1\" > /sys/bus/netdevsim/del_device" ++ ] ++ }, ++ { ++ "id": "18e0", ++ "name": "Fail to install CAKE_MQ on single queue device", ++ "category": [ ++ "qdisc", ++ "cake_mq" ++ ], ++ "plugins": { ++ "requires": "nsPlugin" ++ }, ++ "setup": [ ++ "echo \"1 1 1\" > /sys/bus/netdevsim/new_device" ++ ], ++ "cmdUnderTest": "$TC qdisc add dev $ETH handle 1: root cake_mq", ++ "expExitCode": "2", ++ "verifyCmd": "$TC qdisc show dev $ETH", ++ "matchPattern": "qdisc (cake_mq 1: root|cake 0: parent 1:[1-4]) bandwidth unlimited diffserv3 triple-isolate nonat nowash no-ack-filter split-gso rtt 100ms raw overhead 0 ", ++ "matchCount": "0", ++ "teardown": [ ++ "echo \"1\" > /sys/bus/netdevsim/del_device" ++ ] ++ } ++] diff --git a/target/linux/generic/backport-6.12/700-07-v7.0-net-sched-cake-avoid-separate-allocation-of-struct-c.patch b/target/linux/generic/backport-6.12/700-07-v7.0-net-sched-cake-avoid-separate-allocation-of-struct-c.patch new file mode 100644 index 0000000000..1ba4cd3bc4 --- /dev/null +++ b/target/linux/generic/backport-6.12/700-07-v7.0-net-sched-cake-avoid-separate-allocation-of-struct-c.patch @@ -0,0 +1,112 @@ +From 060969bc048d2f85149b7f1a8bbb56e6b1434cae Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Toke=20H=C3=B8iland-J=C3=B8rgensen?= +Date: Tue, 13 Jan 2026 15:31:56 +0100 +Subject: [PATCH 7/7] net/sched: cake: avoid separate allocation of struct + cake_sched_config +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Paolo pointed out that we can avoid separately allocating struct +cake_sched_config even in the non-mq case, by embedding it into struct +cake_sched_data. This reduces the complexity of the logic that swaps the +pointers and frees the old value, at the cost of adding 56 bytes to the +latter. Since cake_sched_data is already almost 17k bytes, this seems +like a reasonable tradeoff. + +Suggested-by: Paolo Abeni +Signed-off-by: Toke Høiland-Jørgensen +Fixes: bc0ce2bad36c ("net/sched: sch_cake: Factor out config variables into separate struct") +Link: https://patch.msgid.link/20260113143157.2581680-1-toke@redhat.com +Signed-off-by: Jakub Kicinski +--- + net/sched/sch_cake.c | 29 ++++++----------------------- + 1 file changed, 6 insertions(+), 23 deletions(-) + +--- a/net/sched/sch_cake.c ++++ b/net/sched/sch_cake.c +@@ -221,6 +221,7 @@ struct cake_sched_data { + struct tcf_block *block; + struct cake_tin_data *tins; + struct cake_sched_config *config; ++ struct cake_sched_config initial_config; + + struct cake_heap_entry overflow_heap[CAKE_QUEUES * CAKE_MAX_TINS]; + +@@ -2797,8 +2798,6 @@ static void cake_destroy(struct Qdisc *s + qdisc_watchdog_cancel(&q->watchdog); + tcf_block_put(q->block); + kvfree(q->tins); +- if (q->config && !q->config->is_shared) +- kvfree(q->config); + } + + static void cake_config_init(struct cake_sched_config *q, bool is_shared) +@@ -2821,13 +2820,9 @@ static int cake_init(struct Qdisc *sch, + struct netlink_ext_ack *extack) + { + struct cake_sched_data *qd = qdisc_priv(sch); +- struct cake_sched_config *q; ++ struct cake_sched_config *q = &qd->initial_config; + int i, j, err; + +- q = kzalloc(sizeof(*q), GFP_KERNEL); +- if (!q) +- return -ENOMEM; +- + cake_config_init(q, false); + + sch->limit = 10240; +@@ -2839,14 +2834,13 @@ static int cake_init(struct Qdisc *sch, + + if (opt) { + err = cake_change(sch, opt, extack); +- + if (err) +- goto err; ++ return err; + } + + err = tcf_block_get(&qd->block, &qd->filter_list, sch, extack); + if (err) +- goto err; ++ return err; + + quantum_div[0] = ~0; + for (i = 1; i <= CAKE_QUEUES; i++) +@@ -2854,10 +2848,8 @@ static int cake_init(struct Qdisc *sch, + + qd->tins = kvcalloc(CAKE_MAX_TINS, sizeof(struct cake_tin_data), + GFP_KERNEL); +- if (!qd->tins) { +- err = -ENOMEM; +- goto err; +- } ++ if (!qd->tins) ++ return -ENOMEM; + + for (i = 0; i < CAKE_MAX_TINS; i++) { + struct cake_tin_data *b = qd->tins + i; +@@ -2890,22 +2882,13 @@ static int cake_init(struct Qdisc *sch, + qd->last_checked_active = 0; + + return 0; +-err: +- kvfree(qd->config); +- qd->config = NULL; +- return err; + } + + static void cake_config_replace(struct Qdisc *sch, struct cake_sched_config *cfg) + { + struct cake_sched_data *qd = qdisc_priv(sch); +- struct cake_sched_config *q = qd->config; + + qd->config = cfg; +- +- if (!q->is_shared) +- kvfree(q); +- + cake_reconfigure(sch); + } +