]> git.ipfire.org Git - thirdparty/openwrt.git/commitdiff
kernel: add cake-mq support
authorRui Salvaterra <rsalvaterra@gmail.com>
Wed, 21 Jan 2026 17:27:10 +0000 (17:27 +0000)
committerChristian Marangi <ansuelsmth@gmail.com>
Wed, 11 Feb 2026 01:07:50 +0000 (02:07 +0100)
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 <rsalvaterra@gmail.com>
Link: https://github.com/openwrt/openwrt/pull/21964
Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
target/linux/generic/backport-6.12/700-01-v7.0-net-sched-Export-mq-functions-for-reuse.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/700-02-v7.0-net-sched-sch_cake-Factor-out-config-variables-into-.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/700-03-v7.0-net-sched-sch_cake-Add-cake_mq-qdisc-for-using-cake-.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/700-04-v7.0-net-sched-sch_cake-Share-config-across-cake_mq-sub-q.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/700-05-v7.0-net-sched-sch_cake-share-shaper-state-across-sub-ins.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/700-06-v7.0-selftests-tc-testing-add-selftests-for-cake_mq-qdisc.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/700-07-v7.0-net-sched-cake-avoid-separate-allocation-of-struct-c.patch [new file with mode: 0644]

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 (file)
index 0000000..01439cd
--- /dev/null
@@ -0,0 +1,255 @@
+From 8560edcc05114e42d4d580baaf124bce8c291d55 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Toke=20H=C3=B8iland-J=C3=B8rgensen?= <toke@redhat.com>
+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 <willemb@google.com>
+Signed-off-by: Toke Høiland-Jørgensen <toke@redhat.com>
+Link: https://patch.msgid.link/20260109-mq-cake-sub-qdisc-v8-1-8d613fece5d8@redhat.com
+Signed-off-by: Paolo Abeni <pabeni@redhat.com>
+---
+ 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 <net/sch_generic.h>
++
++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 <net/netlink.h>
+ #include <net/pkt_cls.h>
+ #include <net/pkt_sched.h>
+-#include <net/sch_generic.h>
+-
+-struct mq_sched {
+-      struct Qdisc            **qdiscs;
+-};
++#include <net/sch_priv.h>
+ 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 (file)
index 0000000..8df110b
--- /dev/null
@@ -0,0 +1,636 @@
+From 23090d3e9db80c2a374df1e636247eff9b6dd972 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Toke=20H=C3=B8iland-J=C3=B8rgensen?= <toke@redhat.com>
+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 <jhs@mojatatu.com>
+Reviewed-by: Willem de Bruijn <willemb@google.com>
+Signed-off-by: Toke Høiland-Jørgensen <toke@redhat.com>
+Link: https://patch.msgid.link/20260109-mq-cake-sub-qdisc-v8-2-8d613fece5d8@redhat.com
+Signed-off-by: Paolo Abeni <pabeni@redhat.com>
+---
+ 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 (file)
index 0000000..645b27f
--- /dev/null
@@ -0,0 +1,130 @@
+From c076e98c66afe06e41e118eb96d6b15019b822f2 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Toke=20H=C3=B8iland-J=C3=B8rgensen?= <toke@redhat.com>
+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 <willemb@google.com>
+Signed-off-by: Toke Høiland-Jørgensen <toke@redhat.com>
+Link: https://patch.msgid.link/20260109-mq-cake-sub-qdisc-v8-3-8d613fece5d8@redhat.com
+Signed-off-by: Paolo Abeni <pabeni@redhat.com>
+---
+ 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 <linux/if_vlan.h>
+ #include <net/gso.h>
+ #include <net/pkt_sched.h>
++#include <net/sch_priv.h>
+ #include <net/pkt_cls.h>
+ #include <net/tcp.h>
+ #include <net/flow_dissector.h>
+@@ -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 (file)
index 0000000..e481ef9
--- /dev/null
@@ -0,0 +1,318 @@
+From 68e71714d1e02ca388ea99defbe76061b7dd09a3 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Toke=20H=C3=B8iland-J=C3=B8rgensen?= <toke@redhat.com>
+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 <toke@redhat.com>
+Link: https://patch.msgid.link/20260109-mq-cake-sub-qdisc-v8-4-8d613fece5d8@redhat.com
+Signed-off-by: Paolo Abeni <pabeni@redhat.com>
+---
+ 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 (file)
index 0000000..62bf38d
--- /dev/null
@@ -0,0 +1,175 @@
+From 090dde0780266eb80126fa970870bafdb4e937c3 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Jonas=20K=C3=B6ppeler?= <j.koeppeler@tu-berlin.de>
+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 <jhs@mojatatu.com>
+Signed-off-by: Jonas Köppeler <j.koeppeler@tu-berlin.de>
+Signed-off-by: Toke Høiland-Jørgensen <toke@redhat.com>
+Link: https://patch.msgid.link/20260109-mq-cake-sub-qdisc-v8-5-8d613fece5d8@redhat.com
+Signed-off-by: Paolo Abeni <pabeni@redhat.com>
+---
+ 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 (file)
index 0000000..c012a15
--- /dev/null
@@ -0,0 +1,606 @@
+From e5e7c7c6a027ce2f001fc60dc388a560c01427fd Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Jonas=20K=C3=B6ppeler?= <j.koeppeler@tu-berlin.de>
+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 <victor@mojatatu.com>
+Signed-off-by: Jonas Köppeler <j.koeppeler@tu-berlin.de>
+Signed-off-by: Toke Høiland-Jørgensen <toke@redhat.com>
+Link: https://patch.msgid.link/20260109-mq-cake-sub-qdisc-v8-6-8d613fece5d8@redhat.com
+Signed-off-by: Paolo Abeni <pabeni@redhat.com>
+---
+ .../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 (file)
index 0000000..1ba4cd3
--- /dev/null
@@ -0,0 +1,112 @@
+From 060969bc048d2f85149b7f1a8bbb56e6b1434cae Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Toke=20H=C3=B8iland-J=C3=B8rgensen?= <toke@redhat.com>
+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 <pabeni@redhat.com>
+Signed-off-by: Toke Høiland-Jørgensen <toke@redhat.com>
+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 <kuba@kernel.org>
+---
+ 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);
+ }