| .. | .. |
|---|
| 1 | +// SPDX-License-Identifier: GPL-2.0-or-later |
|---|
| 1 | 2 | /* |
|---|
| 2 | 3 | * net/sched/sch_tbf.c Token Bucket Filter queue. |
|---|
| 3 | | - * |
|---|
| 4 | | - * This program is free software; you can redistribute it and/or |
|---|
| 5 | | - * modify it under the terms of the GNU General Public License |
|---|
| 6 | | - * as published by the Free Software Foundation; either version |
|---|
| 7 | | - * 2 of the License, or (at your option) any later version. |
|---|
| 8 | 4 | * |
|---|
| 9 | 5 | * Authors: Alexey Kuznetsov, <kuznet@ms2.inr.ac.ru> |
|---|
| 10 | 6 | * Dmitry Torokhov <dtor@mail.ru> - allow attaching inner qdiscs - |
|---|
| 11 | 7 | * original idea by Martin Devera |
|---|
| 12 | | - * |
|---|
| 13 | 8 | */ |
|---|
| 14 | 9 | |
|---|
| 15 | 10 | #include <linux/module.h> |
|---|
| .. | .. |
|---|
| 20 | 15 | #include <linux/skbuff.h> |
|---|
| 21 | 16 | #include <net/netlink.h> |
|---|
| 22 | 17 | #include <net/sch_generic.h> |
|---|
| 18 | +#include <net/pkt_cls.h> |
|---|
| 23 | 19 | #include <net/pkt_sched.h> |
|---|
| 24 | 20 | |
|---|
| 25 | 21 | |
|---|
| .. | .. |
|---|
| 142 | 138 | return len; |
|---|
| 143 | 139 | } |
|---|
| 144 | 140 | |
|---|
| 141 | +static void tbf_offload_change(struct Qdisc *sch) |
|---|
| 142 | +{ |
|---|
| 143 | + struct tbf_sched_data *q = qdisc_priv(sch); |
|---|
| 144 | + struct net_device *dev = qdisc_dev(sch); |
|---|
| 145 | + struct tc_tbf_qopt_offload qopt; |
|---|
| 146 | + |
|---|
| 147 | + if (!tc_can_offload(dev) || !dev->netdev_ops->ndo_setup_tc) |
|---|
| 148 | + return; |
|---|
| 149 | + |
|---|
| 150 | + qopt.command = TC_TBF_REPLACE; |
|---|
| 151 | + qopt.handle = sch->handle; |
|---|
| 152 | + qopt.parent = sch->parent; |
|---|
| 153 | + qopt.replace_params.rate = q->rate; |
|---|
| 154 | + qopt.replace_params.max_size = q->max_size; |
|---|
| 155 | + qopt.replace_params.qstats = &sch->qstats; |
|---|
| 156 | + |
|---|
| 157 | + dev->netdev_ops->ndo_setup_tc(dev, TC_SETUP_QDISC_TBF, &qopt); |
|---|
| 158 | +} |
|---|
| 159 | + |
|---|
| 160 | +static void tbf_offload_destroy(struct Qdisc *sch) |
|---|
| 161 | +{ |
|---|
| 162 | + struct net_device *dev = qdisc_dev(sch); |
|---|
| 163 | + struct tc_tbf_qopt_offload qopt; |
|---|
| 164 | + |
|---|
| 165 | + if (!tc_can_offload(dev) || !dev->netdev_ops->ndo_setup_tc) |
|---|
| 166 | + return; |
|---|
| 167 | + |
|---|
| 168 | + qopt.command = TC_TBF_DESTROY; |
|---|
| 169 | + qopt.handle = sch->handle; |
|---|
| 170 | + qopt.parent = sch->parent; |
|---|
| 171 | + dev->netdev_ops->ndo_setup_tc(dev, TC_SETUP_QDISC_TBF, &qopt); |
|---|
| 172 | +} |
|---|
| 173 | + |
|---|
| 174 | +static int tbf_offload_dump(struct Qdisc *sch) |
|---|
| 175 | +{ |
|---|
| 176 | + struct tc_tbf_qopt_offload qopt; |
|---|
| 177 | + |
|---|
| 178 | + qopt.command = TC_TBF_STATS; |
|---|
| 179 | + qopt.handle = sch->handle; |
|---|
| 180 | + qopt.parent = sch->parent; |
|---|
| 181 | + qopt.stats.bstats = &sch->bstats; |
|---|
| 182 | + qopt.stats.qstats = &sch->qstats; |
|---|
| 183 | + |
|---|
| 184 | + return qdisc_offload_dump_helper(sch, TC_SETUP_QDISC_TBF, &qopt); |
|---|
| 185 | +} |
|---|
| 186 | + |
|---|
| 145 | 187 | /* GSO packet is too big, segment it so that tbf can transmit |
|---|
| 146 | 188 | * each segment in time |
|---|
| 147 | 189 | */ |
|---|
| .. | .. |
|---|
| 160 | 202 | return qdisc_drop(skb, sch, to_free); |
|---|
| 161 | 203 | |
|---|
| 162 | 204 | nb = 0; |
|---|
| 163 | | - while (segs) { |
|---|
| 164 | | - nskb = segs->next; |
|---|
| 165 | | - segs->next = NULL; |
|---|
| 205 | + skb_list_walk_safe(segs, segs, nskb) { |
|---|
| 206 | + skb_mark_not_on_list(segs); |
|---|
| 166 | 207 | qdisc_skb_cb(segs)->pkt_len = segs->len; |
|---|
| 167 | 208 | len += segs->len; |
|---|
| 168 | 209 | ret = qdisc_enqueue(segs, q->qdisc, to_free); |
|---|
| .. | .. |
|---|
| 172 | 213 | } else { |
|---|
| 173 | 214 | nb++; |
|---|
| 174 | 215 | } |
|---|
| 175 | | - segs = nskb; |
|---|
| 176 | 216 | } |
|---|
| 177 | 217 | sch->q.qlen += nb; |
|---|
| 178 | 218 | if (nb > 1) |
|---|
| .. | .. |
|---|
| 185 | 225 | struct sk_buff **to_free) |
|---|
| 186 | 226 | { |
|---|
| 187 | 227 | struct tbf_sched_data *q = qdisc_priv(sch); |
|---|
| 228 | + unsigned int len = qdisc_pkt_len(skb); |
|---|
| 188 | 229 | int ret; |
|---|
| 189 | 230 | |
|---|
| 190 | 231 | if (qdisc_pkt_len(skb) > q->max_size) { |
|---|
| .. | .. |
|---|
| 200 | 241 | return ret; |
|---|
| 201 | 242 | } |
|---|
| 202 | 243 | |
|---|
| 203 | | - qdisc_qstats_backlog_inc(sch, skb); |
|---|
| 244 | + sch->qstats.backlog += len; |
|---|
| 204 | 245 | sch->q.qlen++; |
|---|
| 205 | 246 | return NET_XMIT_SUCCESS; |
|---|
| 206 | 247 | } |
|---|
| .. | .. |
|---|
| 275 | 316 | struct tbf_sched_data *q = qdisc_priv(sch); |
|---|
| 276 | 317 | |
|---|
| 277 | 318 | qdisc_reset(q->qdisc); |
|---|
| 278 | | - sch->qstats.backlog = 0; |
|---|
| 279 | | - sch->q.qlen = 0; |
|---|
| 280 | 319 | q->t_c = ktime_get_ns(); |
|---|
| 281 | 320 | q->tokens = q->buffer; |
|---|
| 282 | 321 | q->ptokens = q->mtu; |
|---|
| .. | .. |
|---|
| 301 | 340 | struct nlattr *tb[TCA_TBF_MAX + 1]; |
|---|
| 302 | 341 | struct tc_tbf_qopt *qopt; |
|---|
| 303 | 342 | struct Qdisc *child = NULL; |
|---|
| 343 | + struct Qdisc *old = NULL; |
|---|
| 304 | 344 | struct psched_ratecfg rate; |
|---|
| 305 | 345 | struct psched_ratecfg peak; |
|---|
| 306 | 346 | u64 max_size; |
|---|
| 307 | 347 | s64 buffer, mtu; |
|---|
| 308 | 348 | u64 rate64 = 0, prate64 = 0; |
|---|
| 309 | 349 | |
|---|
| 310 | | - err = nla_parse_nested(tb, TCA_TBF_MAX, opt, tbf_policy, NULL); |
|---|
| 350 | + err = nla_parse_nested_deprecated(tb, TCA_TBF_MAX, opt, tbf_policy, |
|---|
| 351 | + NULL); |
|---|
| 311 | 352 | if (err < 0) |
|---|
| 312 | 353 | return err; |
|---|
| 313 | 354 | |
|---|
| .. | .. |
|---|
| 390 | 431 | |
|---|
| 391 | 432 | sch_tree_lock(sch); |
|---|
| 392 | 433 | if (child) { |
|---|
| 393 | | - qdisc_tree_reduce_backlog(q->qdisc, q->qdisc->q.qlen, |
|---|
| 394 | | - q->qdisc->qstats.backlog); |
|---|
| 395 | | - qdisc_put(q->qdisc); |
|---|
| 434 | + qdisc_tree_flush_backlog(q->qdisc); |
|---|
| 435 | + old = q->qdisc; |
|---|
| 396 | 436 | q->qdisc = child; |
|---|
| 397 | 437 | } |
|---|
| 398 | 438 | q->limit = qopt->limit; |
|---|
| .. | .. |
|---|
| 412 | 452 | memcpy(&q->peak, &peak, sizeof(struct psched_ratecfg)); |
|---|
| 413 | 453 | |
|---|
| 414 | 454 | sch_tree_unlock(sch); |
|---|
| 455 | + qdisc_put(old); |
|---|
| 415 | 456 | err = 0; |
|---|
| 457 | + |
|---|
| 458 | + tbf_offload_change(sch); |
|---|
| 416 | 459 | done: |
|---|
| 417 | 460 | return err; |
|---|
| 418 | 461 | } |
|---|
| .. | .. |
|---|
| 438 | 481 | struct tbf_sched_data *q = qdisc_priv(sch); |
|---|
| 439 | 482 | |
|---|
| 440 | 483 | qdisc_watchdog_cancel(&q->watchdog); |
|---|
| 484 | + tbf_offload_destroy(sch); |
|---|
| 441 | 485 | qdisc_put(q->qdisc); |
|---|
| 442 | 486 | } |
|---|
| 443 | 487 | |
|---|
| .. | .. |
|---|
| 446 | 490 | struct tbf_sched_data *q = qdisc_priv(sch); |
|---|
| 447 | 491 | struct nlattr *nest; |
|---|
| 448 | 492 | struct tc_tbf_qopt opt; |
|---|
| 493 | + int err; |
|---|
| 449 | 494 | |
|---|
| 450 | | - sch->qstats.backlog = q->qdisc->qstats.backlog; |
|---|
| 451 | | - nest = nla_nest_start(skb, TCA_OPTIONS); |
|---|
| 495 | + err = tbf_offload_dump(sch); |
|---|
| 496 | + if (err) |
|---|
| 497 | + return err; |
|---|
| 498 | + |
|---|
| 499 | + nest = nla_nest_start_noflag(skb, TCA_OPTIONS); |
|---|
| 452 | 500 | if (nest == NULL) |
|---|
| 453 | 501 | goto nla_put_failure; |
|---|
| 454 | 502 | |
|---|