18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * net/sched/sch_mq.c Classful multiqueue dummy scheduler 48c2ecf20Sopenharmony_ci * 58c2ecf20Sopenharmony_ci * Copyright (c) 2009 Patrick McHardy <kaber@trash.net> 68c2ecf20Sopenharmony_ci */ 78c2ecf20Sopenharmony_ci 88c2ecf20Sopenharmony_ci#include <linux/types.h> 98c2ecf20Sopenharmony_ci#include <linux/slab.h> 108c2ecf20Sopenharmony_ci#include <linux/kernel.h> 118c2ecf20Sopenharmony_ci#include <linux/export.h> 128c2ecf20Sopenharmony_ci#include <linux/string.h> 138c2ecf20Sopenharmony_ci#include <linux/errno.h> 148c2ecf20Sopenharmony_ci#include <linux/skbuff.h> 158c2ecf20Sopenharmony_ci#include <net/netlink.h> 168c2ecf20Sopenharmony_ci#include <net/pkt_cls.h> 178c2ecf20Sopenharmony_ci#include <net/pkt_sched.h> 188c2ecf20Sopenharmony_ci#include <net/sch_generic.h> 198c2ecf20Sopenharmony_ci 208c2ecf20Sopenharmony_cistruct mq_sched { 218c2ecf20Sopenharmony_ci struct Qdisc **qdiscs; 228c2ecf20Sopenharmony_ci}; 238c2ecf20Sopenharmony_ci 248c2ecf20Sopenharmony_cistatic int mq_offload(struct Qdisc *sch, enum tc_mq_command cmd) 258c2ecf20Sopenharmony_ci{ 268c2ecf20Sopenharmony_ci struct net_device *dev = qdisc_dev(sch); 278c2ecf20Sopenharmony_ci struct tc_mq_qopt_offload opt = { 288c2ecf20Sopenharmony_ci .command = cmd, 298c2ecf20Sopenharmony_ci .handle = sch->handle, 308c2ecf20Sopenharmony_ci }; 318c2ecf20Sopenharmony_ci 328c2ecf20Sopenharmony_ci if (!tc_can_offload(dev) || !dev->netdev_ops->ndo_setup_tc) 338c2ecf20Sopenharmony_ci return -EOPNOTSUPP; 348c2ecf20Sopenharmony_ci 358c2ecf20Sopenharmony_ci return dev->netdev_ops->ndo_setup_tc(dev, TC_SETUP_QDISC_MQ, &opt); 368c2ecf20Sopenharmony_ci} 378c2ecf20Sopenharmony_ci 388c2ecf20Sopenharmony_cistatic int mq_offload_stats(struct Qdisc *sch) 398c2ecf20Sopenharmony_ci{ 408c2ecf20Sopenharmony_ci struct tc_mq_qopt_offload opt = { 418c2ecf20Sopenharmony_ci .command = TC_MQ_STATS, 428c2ecf20Sopenharmony_ci .handle = sch->handle, 438c2ecf20Sopenharmony_ci .stats = { 448c2ecf20Sopenharmony_ci .bstats = &sch->bstats, 458c2ecf20Sopenharmony_ci .qstats = &sch->qstats, 468c2ecf20Sopenharmony_ci }, 478c2ecf20Sopenharmony_ci }; 488c2ecf20Sopenharmony_ci 498c2ecf20Sopenharmony_ci return qdisc_offload_dump_helper(sch, TC_SETUP_QDISC_MQ, &opt); 508c2ecf20Sopenharmony_ci} 518c2ecf20Sopenharmony_ci 528c2ecf20Sopenharmony_cistatic void mq_destroy(struct Qdisc *sch) 538c2ecf20Sopenharmony_ci{ 548c2ecf20Sopenharmony_ci struct net_device *dev = qdisc_dev(sch); 558c2ecf20Sopenharmony_ci struct mq_sched *priv = qdisc_priv(sch); 568c2ecf20Sopenharmony_ci unsigned int ntx; 578c2ecf20Sopenharmony_ci 588c2ecf20Sopenharmony_ci mq_offload(sch, TC_MQ_DESTROY); 598c2ecf20Sopenharmony_ci 608c2ecf20Sopenharmony_ci if (!priv->qdiscs) 618c2ecf20Sopenharmony_ci return; 628c2ecf20Sopenharmony_ci for (ntx = 0; ntx < dev->num_tx_queues && priv->qdiscs[ntx]; ntx++) 638c2ecf20Sopenharmony_ci qdisc_put(priv->qdiscs[ntx]); 648c2ecf20Sopenharmony_ci kfree(priv->qdiscs); 658c2ecf20Sopenharmony_ci} 668c2ecf20Sopenharmony_ci 678c2ecf20Sopenharmony_cistatic int mq_init(struct Qdisc *sch, struct nlattr *opt, 688c2ecf20Sopenharmony_ci struct netlink_ext_ack *extack) 698c2ecf20Sopenharmony_ci{ 708c2ecf20Sopenharmony_ci struct net_device *dev = qdisc_dev(sch); 718c2ecf20Sopenharmony_ci struct mq_sched *priv = qdisc_priv(sch); 728c2ecf20Sopenharmony_ci struct netdev_queue *dev_queue; 738c2ecf20Sopenharmony_ci struct Qdisc *qdisc; 748c2ecf20Sopenharmony_ci unsigned int ntx; 758c2ecf20Sopenharmony_ci 768c2ecf20Sopenharmony_ci if (sch->parent != TC_H_ROOT) 778c2ecf20Sopenharmony_ci return -EOPNOTSUPP; 788c2ecf20Sopenharmony_ci 798c2ecf20Sopenharmony_ci if (!netif_is_multiqueue(dev)) 808c2ecf20Sopenharmony_ci return -EOPNOTSUPP; 818c2ecf20Sopenharmony_ci 828c2ecf20Sopenharmony_ci /* pre-allocate qdiscs, attachment can't fail */ 838c2ecf20Sopenharmony_ci priv->qdiscs = kcalloc(dev->num_tx_queues, sizeof(priv->qdiscs[0]), 848c2ecf20Sopenharmony_ci GFP_KERNEL); 858c2ecf20Sopenharmony_ci if (!priv->qdiscs) 868c2ecf20Sopenharmony_ci return -ENOMEM; 878c2ecf20Sopenharmony_ci 888c2ecf20Sopenharmony_ci for (ntx = 0; ntx < dev->num_tx_queues; ntx++) { 898c2ecf20Sopenharmony_ci dev_queue = netdev_get_tx_queue(dev, ntx); 908c2ecf20Sopenharmony_ci qdisc = qdisc_create_dflt(dev_queue, get_default_qdisc_ops(dev, ntx), 918c2ecf20Sopenharmony_ci TC_H_MAKE(TC_H_MAJ(sch->handle), 928c2ecf20Sopenharmony_ci TC_H_MIN(ntx + 1)), 938c2ecf20Sopenharmony_ci extack); 948c2ecf20Sopenharmony_ci if (!qdisc) 958c2ecf20Sopenharmony_ci return -ENOMEM; 968c2ecf20Sopenharmony_ci priv->qdiscs[ntx] = qdisc; 978c2ecf20Sopenharmony_ci qdisc->flags |= TCQ_F_ONETXQUEUE | TCQ_F_NOPARENT; 988c2ecf20Sopenharmony_ci } 998c2ecf20Sopenharmony_ci 1008c2ecf20Sopenharmony_ci sch->flags |= TCQ_F_MQROOT; 1018c2ecf20Sopenharmony_ci 1028c2ecf20Sopenharmony_ci mq_offload(sch, TC_MQ_CREATE); 1038c2ecf20Sopenharmony_ci return 0; 1048c2ecf20Sopenharmony_ci} 1058c2ecf20Sopenharmony_ci 1068c2ecf20Sopenharmony_cistatic void mq_attach(struct Qdisc *sch) 1078c2ecf20Sopenharmony_ci{ 1088c2ecf20Sopenharmony_ci struct net_device *dev = qdisc_dev(sch); 1098c2ecf20Sopenharmony_ci struct mq_sched *priv = qdisc_priv(sch); 1108c2ecf20Sopenharmony_ci struct Qdisc *qdisc, *old; 1118c2ecf20Sopenharmony_ci unsigned int ntx; 1128c2ecf20Sopenharmony_ci 1138c2ecf20Sopenharmony_ci for (ntx = 0; ntx < dev->num_tx_queues; ntx++) { 1148c2ecf20Sopenharmony_ci qdisc = priv->qdiscs[ntx]; 1158c2ecf20Sopenharmony_ci old = dev_graft_qdisc(qdisc->dev_queue, qdisc); 1168c2ecf20Sopenharmony_ci if (old) 1178c2ecf20Sopenharmony_ci qdisc_put(old); 1188c2ecf20Sopenharmony_ci#ifdef CONFIG_NET_SCHED 1198c2ecf20Sopenharmony_ci if (ntx < dev->real_num_tx_queues) 1208c2ecf20Sopenharmony_ci qdisc_hash_add(qdisc, false); 1218c2ecf20Sopenharmony_ci#endif 1228c2ecf20Sopenharmony_ci 1238c2ecf20Sopenharmony_ci } 1248c2ecf20Sopenharmony_ci kfree(priv->qdiscs); 1258c2ecf20Sopenharmony_ci priv->qdiscs = NULL; 1268c2ecf20Sopenharmony_ci} 1278c2ecf20Sopenharmony_ci 1288c2ecf20Sopenharmony_cistatic void mq_change_real_num_tx(struct Qdisc *sch, unsigned int new_real_tx) 1298c2ecf20Sopenharmony_ci{ 1308c2ecf20Sopenharmony_ci#ifdef CONFIG_NET_SCHED 1318c2ecf20Sopenharmony_ci struct net_device *dev = qdisc_dev(sch); 1328c2ecf20Sopenharmony_ci struct Qdisc *qdisc; 1338c2ecf20Sopenharmony_ci unsigned int i; 1348c2ecf20Sopenharmony_ci 1358c2ecf20Sopenharmony_ci for (i = new_real_tx; i < dev->real_num_tx_queues; i++) { 1368c2ecf20Sopenharmony_ci qdisc = netdev_get_tx_queue(dev, i)->qdisc_sleeping; 1378c2ecf20Sopenharmony_ci /* Only update the default qdiscs we created, 1388c2ecf20Sopenharmony_ci * qdiscs with handles are always hashed. 1398c2ecf20Sopenharmony_ci */ 1408c2ecf20Sopenharmony_ci if (qdisc != &noop_qdisc && !qdisc->handle) 1418c2ecf20Sopenharmony_ci qdisc_hash_del(qdisc); 1428c2ecf20Sopenharmony_ci } 1438c2ecf20Sopenharmony_ci for (i = dev->real_num_tx_queues; i < new_real_tx; i++) { 1448c2ecf20Sopenharmony_ci qdisc = netdev_get_tx_queue(dev, i)->qdisc_sleeping; 1458c2ecf20Sopenharmony_ci if (qdisc != &noop_qdisc && !qdisc->handle) 1468c2ecf20Sopenharmony_ci qdisc_hash_add(qdisc, false); 1478c2ecf20Sopenharmony_ci } 1488c2ecf20Sopenharmony_ci#endif 1498c2ecf20Sopenharmony_ci} 1508c2ecf20Sopenharmony_ci 1518c2ecf20Sopenharmony_cistatic int mq_dump(struct Qdisc *sch, struct sk_buff *skb) 1528c2ecf20Sopenharmony_ci{ 1538c2ecf20Sopenharmony_ci struct net_device *dev = qdisc_dev(sch); 1548c2ecf20Sopenharmony_ci struct Qdisc *qdisc; 1558c2ecf20Sopenharmony_ci unsigned int ntx; 1568c2ecf20Sopenharmony_ci __u32 qlen = 0; 1578c2ecf20Sopenharmony_ci 1588c2ecf20Sopenharmony_ci sch->q.qlen = 0; 1598c2ecf20Sopenharmony_ci memset(&sch->bstats, 0, sizeof(sch->bstats)); 1608c2ecf20Sopenharmony_ci memset(&sch->qstats, 0, sizeof(sch->qstats)); 1618c2ecf20Sopenharmony_ci 1628c2ecf20Sopenharmony_ci /* MQ supports lockless qdiscs. However, statistics accounting needs 1638c2ecf20Sopenharmony_ci * to account for all, none, or a mix of locked and unlocked child 1648c2ecf20Sopenharmony_ci * qdiscs. Percpu stats are added to counters in-band and locking 1658c2ecf20Sopenharmony_ci * qdisc totals are added at end. 1668c2ecf20Sopenharmony_ci */ 1678c2ecf20Sopenharmony_ci for (ntx = 0; ntx < dev->num_tx_queues; ntx++) { 1688c2ecf20Sopenharmony_ci qdisc = netdev_get_tx_queue(dev, ntx)->qdisc_sleeping; 1698c2ecf20Sopenharmony_ci spin_lock_bh(qdisc_lock(qdisc)); 1708c2ecf20Sopenharmony_ci 1718c2ecf20Sopenharmony_ci if (qdisc_is_percpu_stats(qdisc)) { 1728c2ecf20Sopenharmony_ci qlen = qdisc_qlen_sum(qdisc); 1738c2ecf20Sopenharmony_ci __gnet_stats_copy_basic(NULL, &sch->bstats, 1748c2ecf20Sopenharmony_ci qdisc->cpu_bstats, 1758c2ecf20Sopenharmony_ci &qdisc->bstats); 1768c2ecf20Sopenharmony_ci __gnet_stats_copy_queue(&sch->qstats, 1778c2ecf20Sopenharmony_ci qdisc->cpu_qstats, 1788c2ecf20Sopenharmony_ci &qdisc->qstats, qlen); 1798c2ecf20Sopenharmony_ci sch->q.qlen += qlen; 1808c2ecf20Sopenharmony_ci } else { 1818c2ecf20Sopenharmony_ci sch->q.qlen += qdisc->q.qlen; 1828c2ecf20Sopenharmony_ci sch->bstats.bytes += qdisc->bstats.bytes; 1838c2ecf20Sopenharmony_ci sch->bstats.packets += qdisc->bstats.packets; 1848c2ecf20Sopenharmony_ci sch->qstats.qlen += qdisc->qstats.qlen; 1858c2ecf20Sopenharmony_ci sch->qstats.backlog += qdisc->qstats.backlog; 1868c2ecf20Sopenharmony_ci sch->qstats.drops += qdisc->qstats.drops; 1878c2ecf20Sopenharmony_ci sch->qstats.requeues += qdisc->qstats.requeues; 1888c2ecf20Sopenharmony_ci sch->qstats.overlimits += qdisc->qstats.overlimits; 1898c2ecf20Sopenharmony_ci } 1908c2ecf20Sopenharmony_ci 1918c2ecf20Sopenharmony_ci spin_unlock_bh(qdisc_lock(qdisc)); 1928c2ecf20Sopenharmony_ci } 1938c2ecf20Sopenharmony_ci 1948c2ecf20Sopenharmony_ci return mq_offload_stats(sch); 1958c2ecf20Sopenharmony_ci} 1968c2ecf20Sopenharmony_ci 1978c2ecf20Sopenharmony_cistatic struct netdev_queue *mq_queue_get(struct Qdisc *sch, unsigned long cl) 1988c2ecf20Sopenharmony_ci{ 1998c2ecf20Sopenharmony_ci struct net_device *dev = qdisc_dev(sch); 2008c2ecf20Sopenharmony_ci unsigned long ntx = cl - 1; 2018c2ecf20Sopenharmony_ci 2028c2ecf20Sopenharmony_ci if (ntx >= dev->num_tx_queues) 2038c2ecf20Sopenharmony_ci return NULL; 2048c2ecf20Sopenharmony_ci return netdev_get_tx_queue(dev, ntx); 2058c2ecf20Sopenharmony_ci} 2068c2ecf20Sopenharmony_ci 2078c2ecf20Sopenharmony_cistatic struct netdev_queue *mq_select_queue(struct Qdisc *sch, 2088c2ecf20Sopenharmony_ci struct tcmsg *tcm) 2098c2ecf20Sopenharmony_ci{ 2108c2ecf20Sopenharmony_ci return mq_queue_get(sch, TC_H_MIN(tcm->tcm_parent)); 2118c2ecf20Sopenharmony_ci} 2128c2ecf20Sopenharmony_ci 2138c2ecf20Sopenharmony_cistatic int mq_graft(struct Qdisc *sch, unsigned long cl, struct Qdisc *new, 2148c2ecf20Sopenharmony_ci struct Qdisc **old, struct netlink_ext_ack *extack) 2158c2ecf20Sopenharmony_ci{ 2168c2ecf20Sopenharmony_ci struct netdev_queue *dev_queue = mq_queue_get(sch, cl); 2178c2ecf20Sopenharmony_ci struct tc_mq_qopt_offload graft_offload; 2188c2ecf20Sopenharmony_ci struct net_device *dev = qdisc_dev(sch); 2198c2ecf20Sopenharmony_ci 2208c2ecf20Sopenharmony_ci if (dev->flags & IFF_UP) 2218c2ecf20Sopenharmony_ci dev_deactivate(dev); 2228c2ecf20Sopenharmony_ci 2238c2ecf20Sopenharmony_ci *old = dev_graft_qdisc(dev_queue, new); 2248c2ecf20Sopenharmony_ci if (new) 2258c2ecf20Sopenharmony_ci new->flags |= TCQ_F_ONETXQUEUE | TCQ_F_NOPARENT; 2268c2ecf20Sopenharmony_ci if (dev->flags & IFF_UP) 2278c2ecf20Sopenharmony_ci dev_activate(dev); 2288c2ecf20Sopenharmony_ci 2298c2ecf20Sopenharmony_ci graft_offload.handle = sch->handle; 2308c2ecf20Sopenharmony_ci graft_offload.graft_params.queue = cl - 1; 2318c2ecf20Sopenharmony_ci graft_offload.graft_params.child_handle = new ? new->handle : 0; 2328c2ecf20Sopenharmony_ci graft_offload.command = TC_MQ_GRAFT; 2338c2ecf20Sopenharmony_ci 2348c2ecf20Sopenharmony_ci qdisc_offload_graft_helper(qdisc_dev(sch), sch, new, *old, 2358c2ecf20Sopenharmony_ci TC_SETUP_QDISC_MQ, &graft_offload, extack); 2368c2ecf20Sopenharmony_ci return 0; 2378c2ecf20Sopenharmony_ci} 2388c2ecf20Sopenharmony_ci 2398c2ecf20Sopenharmony_cistatic struct Qdisc *mq_leaf(struct Qdisc *sch, unsigned long cl) 2408c2ecf20Sopenharmony_ci{ 2418c2ecf20Sopenharmony_ci struct netdev_queue *dev_queue = mq_queue_get(sch, cl); 2428c2ecf20Sopenharmony_ci 2438c2ecf20Sopenharmony_ci return dev_queue->qdisc_sleeping; 2448c2ecf20Sopenharmony_ci} 2458c2ecf20Sopenharmony_ci 2468c2ecf20Sopenharmony_cistatic unsigned long mq_find(struct Qdisc *sch, u32 classid) 2478c2ecf20Sopenharmony_ci{ 2488c2ecf20Sopenharmony_ci unsigned int ntx = TC_H_MIN(classid); 2498c2ecf20Sopenharmony_ci 2508c2ecf20Sopenharmony_ci if (!mq_queue_get(sch, ntx)) 2518c2ecf20Sopenharmony_ci return 0; 2528c2ecf20Sopenharmony_ci return ntx; 2538c2ecf20Sopenharmony_ci} 2548c2ecf20Sopenharmony_ci 2558c2ecf20Sopenharmony_cistatic int mq_dump_class(struct Qdisc *sch, unsigned long cl, 2568c2ecf20Sopenharmony_ci struct sk_buff *skb, struct tcmsg *tcm) 2578c2ecf20Sopenharmony_ci{ 2588c2ecf20Sopenharmony_ci struct netdev_queue *dev_queue = mq_queue_get(sch, cl); 2598c2ecf20Sopenharmony_ci 2608c2ecf20Sopenharmony_ci tcm->tcm_parent = TC_H_ROOT; 2618c2ecf20Sopenharmony_ci tcm->tcm_handle |= TC_H_MIN(cl); 2628c2ecf20Sopenharmony_ci tcm->tcm_info = dev_queue->qdisc_sleeping->handle; 2638c2ecf20Sopenharmony_ci return 0; 2648c2ecf20Sopenharmony_ci} 2658c2ecf20Sopenharmony_ci 2668c2ecf20Sopenharmony_cistatic int mq_dump_class_stats(struct Qdisc *sch, unsigned long cl, 2678c2ecf20Sopenharmony_ci struct gnet_dump *d) 2688c2ecf20Sopenharmony_ci{ 2698c2ecf20Sopenharmony_ci struct netdev_queue *dev_queue = mq_queue_get(sch, cl); 2708c2ecf20Sopenharmony_ci 2718c2ecf20Sopenharmony_ci sch = dev_queue->qdisc_sleeping; 2728c2ecf20Sopenharmony_ci if (gnet_stats_copy_basic(&sch->running, d, sch->cpu_bstats, 2738c2ecf20Sopenharmony_ci &sch->bstats) < 0 || 2748c2ecf20Sopenharmony_ci qdisc_qstats_copy(d, sch) < 0) 2758c2ecf20Sopenharmony_ci return -1; 2768c2ecf20Sopenharmony_ci return 0; 2778c2ecf20Sopenharmony_ci} 2788c2ecf20Sopenharmony_ci 2798c2ecf20Sopenharmony_cistatic void mq_walk(struct Qdisc *sch, struct qdisc_walker *arg) 2808c2ecf20Sopenharmony_ci{ 2818c2ecf20Sopenharmony_ci struct net_device *dev = qdisc_dev(sch); 2828c2ecf20Sopenharmony_ci unsigned int ntx; 2838c2ecf20Sopenharmony_ci 2848c2ecf20Sopenharmony_ci if (arg->stop) 2858c2ecf20Sopenharmony_ci return; 2868c2ecf20Sopenharmony_ci 2878c2ecf20Sopenharmony_ci arg->count = arg->skip; 2888c2ecf20Sopenharmony_ci for (ntx = arg->skip; ntx < dev->num_tx_queues; ntx++) { 2898c2ecf20Sopenharmony_ci if (arg->fn(sch, ntx + 1, arg) < 0) { 2908c2ecf20Sopenharmony_ci arg->stop = 1; 2918c2ecf20Sopenharmony_ci break; 2928c2ecf20Sopenharmony_ci } 2938c2ecf20Sopenharmony_ci arg->count++; 2948c2ecf20Sopenharmony_ci } 2958c2ecf20Sopenharmony_ci} 2968c2ecf20Sopenharmony_ci 2978c2ecf20Sopenharmony_cistatic const struct Qdisc_class_ops mq_class_ops = { 2988c2ecf20Sopenharmony_ci .select_queue = mq_select_queue, 2998c2ecf20Sopenharmony_ci .graft = mq_graft, 3008c2ecf20Sopenharmony_ci .leaf = mq_leaf, 3018c2ecf20Sopenharmony_ci .find = mq_find, 3028c2ecf20Sopenharmony_ci .walk = mq_walk, 3038c2ecf20Sopenharmony_ci .dump = mq_dump_class, 3048c2ecf20Sopenharmony_ci .dump_stats = mq_dump_class_stats, 3058c2ecf20Sopenharmony_ci}; 3068c2ecf20Sopenharmony_ci 3078c2ecf20Sopenharmony_cistruct Qdisc_ops mq_qdisc_ops __read_mostly = { 3088c2ecf20Sopenharmony_ci .cl_ops = &mq_class_ops, 3098c2ecf20Sopenharmony_ci .id = "mq", 3108c2ecf20Sopenharmony_ci .priv_size = sizeof(struct mq_sched), 3118c2ecf20Sopenharmony_ci .init = mq_init, 3128c2ecf20Sopenharmony_ci .destroy = mq_destroy, 3138c2ecf20Sopenharmony_ci .attach = mq_attach, 3148c2ecf20Sopenharmony_ci .change_real_num_tx = mq_change_real_num_tx, 3158c2ecf20Sopenharmony_ci .dump = mq_dump, 3168c2ecf20Sopenharmony_ci .owner = THIS_MODULE, 3178c2ecf20Sopenharmony_ci}; 318