/* ************************************************************************** * Copyright (c) 2014, 2015 The Linux Foundation. All rights reserved. * Permission to use, copy, modify, and/or distribute this software for * any purpose with or without fee is hereby granted, provided that the * above copyright notice and this permission notice appear in all copies. * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. ************************************************************************** */ #include "nss_qdisc.h" struct nss_tbl_sched_data { struct nss_qdisc nq; /* Common base class for all nss qdiscs */ u32 rate; /* Limiting rate of TBL */ u32 peakrate; /* Maximum rate to control bursts */ u32 burst; /* Maximum allowed burst size */ u32 mtu; /* MTU of the interface attached to */ struct Qdisc *qdisc; /* Qdisc to which it is attached to */ }; static int nss_tbl_enqueue(struct sk_buff *skb, struct Qdisc *sch) { return nss_qdisc_enqueue(skb, sch); } static struct sk_buff *nss_tbl_dequeue(struct Qdisc *sch) { return nss_qdisc_dequeue(sch); } static unsigned int nss_tbl_drop(struct Qdisc *sch) { return nss_qdisc_drop(sch); } static struct sk_buff *nss_tbl_peek(struct Qdisc *sch) { return nss_qdisc_peek(sch); } static void nss_tbl_reset(struct Qdisc *sch) { nss_qdisc_reset(sch); } static void nss_tbl_destroy(struct Qdisc *sch) { struct nss_tbl_sched_data *q = qdisc_priv(sch); struct nss_qdisc *nq_child = (struct nss_qdisc *)qdisc_priv(q->qdisc); struct nss_if_msg nim; /* * We must always detach our child node in NSS before destroying it. * Also, we make sure we dont send down the command for noop qdiscs. */ if (q->qdisc != &noop_qdisc) { nim.msg.shaper_configure.config.msg.shaper_node_config.qos_tag = q->nq.qos_tag; if (nss_qdisc_node_detach(&q->nq, nq_child, &nim, NSS_SHAPER_CONFIG_TYPE_TBL_DETACH) < 0) { nss_qdisc_error("%s: Failed to detach child %x from nss_tbl %x\n", __func__, q->qdisc->handle, q->nq.qos_tag); return; } } /* * Now we can destroy our child qdisc */ qdisc_destroy(q->qdisc); /* * Stop the polling of basic stats and destroy qdisc. */ nss_qdisc_stop_basic_stats_polling(&q->nq); nss_qdisc_destroy(&q->nq); } static const struct nla_policy nss_tbl_policy[TCA_NSSTBL_MAX + 1] = { [TCA_NSSTBL_PARMS] = { .len = sizeof(struct tc_nsstbl_qopt) }, }; static int nss_tbl_change(struct Qdisc *sch, struct nlattr *opt) { struct nss_tbl_sched_data *q = qdisc_priv(sch); struct nlattr *na[TCA_NSSTBL_MAX + 1]; struct tc_nsstbl_qopt *qopt; struct nss_if_msg nim; int err; struct net_device *dev = qdisc_dev(sch); if (opt == NULL) return -EINVAL; err = nla_parse_nested(na, TCA_NSSTBL_MAX, opt, nss_tbl_policy); if (err < 0) return err; if (na[TCA_NSSTBL_PARMS] == NULL) return -EINVAL; qopt = nla_data(na[TCA_NSSTBL_PARMS]); /* * Set MTU if it wasn't specified explicitely */ if (!qopt->mtu) { qopt->mtu = psched_mtu(dev); nss_qdisc_info("MTU not provided for nss_tbl. Setting it to %s's default %u bytes\n", dev->name, qopt->mtu); } /* * Burst size cannot be less than MTU */ if (qopt->burst < qopt->mtu) { nss_qdisc_error("Burst size: %u is less than the specified MTU: %u\n", qopt->burst, qopt->mtu); return -EINVAL; } /* * Rate can be zero. Therefore we dont do a check on it. */ q->rate = qopt->rate; nss_qdisc_info("Rate = %u", qopt->rate); q->burst = qopt->burst; nss_qdisc_info("Burst = %u", qopt->burst); q->mtu = qopt->mtu; nss_qdisc_info("MTU = %u", qopt->mtu); q->peakrate = qopt->peakrate; nss_qdisc_info("Peak Rate = %u", qopt->peakrate); nim.msg.shaper_configure.config.msg.shaper_node_config.qos_tag = q->nq.qos_tag; nim.msg.shaper_configure.config.msg.shaper_node_config.snc.tbl_param.lap_cir.rate = q->rate; nim.msg.shaper_configure.config.msg.shaper_node_config.snc.tbl_param.lap_cir.burst = q->burst; nim.msg.shaper_configure.config.msg.shaper_node_config.snc.tbl_param.lap_cir.max_size = q->mtu; nim.msg.shaper_configure.config.msg.shaper_node_config.snc.tbl_param.lap_cir.short_circuit = false; nim.msg.shaper_configure.config.msg.shaper_node_config.snc.tbl_param.lap_pir.rate = q->peakrate; /* * It is important to set these two parameters to be the same as MTU. * This ensures bursts from CIR dont go above the specified peakrate. */ nim.msg.shaper_configure.config.msg.shaper_node_config.snc.tbl_param.lap_pir.burst = q->mtu; nim.msg.shaper_configure.config.msg.shaper_node_config.snc.tbl_param.lap_pir.max_size = q->mtu; /* * We can short circuit peakrate limiter if it is not being configured. */ if (q->peakrate) { nim.msg.shaper_configure.config.msg.shaper_node_config.snc.tbl_param.lap_pir.short_circuit = false; } else { nim.msg.shaper_configure.config.msg.shaper_node_config.snc.tbl_param.lap_pir.short_circuit = true; } if (nss_qdisc_configure(&q->nq, &nim, NSS_SHAPER_CONFIG_TYPE_TBL_CHANGE_PARAM) < 0) { return -EINVAL; } return 0; } static int nss_tbl_init(struct Qdisc *sch, struct nlattr *opt) { struct nss_tbl_sched_data *q = qdisc_priv(sch); if (opt == NULL) return -EINVAL; q->qdisc = &noop_qdisc; if (nss_qdisc_init(sch, &q->nq, NSS_SHAPER_NODE_TYPE_TBL, 0) < 0) return -EINVAL; if (nss_tbl_change(sch, opt) < 0) { nss_qdisc_info("Failed to configure tbl\n"); nss_qdisc_destroy(&q->nq); return -EINVAL; } /* * Start the stats polling timer */ nss_qdisc_start_basic_stats_polling(&q->nq); return 0; } static int nss_tbl_dump(struct Qdisc *sch, struct sk_buff *skb) { struct nss_tbl_sched_data *q = qdisc_priv(sch); struct nlattr *opts = NULL; struct tc_nsstbl_qopt opt = { .rate = q->rate, .peakrate = q->peakrate, .burst = q->burst, .mtu = q->mtu, }; nss_qdisc_info("Nsstbl dumping"); opts = nla_nest_start(skb, TCA_OPTIONS); if (opts == NULL || nla_put(skb, TCA_NSSTBL_PARMS, sizeof(opt), &opt)) { goto nla_put_failure; } return nla_nest_end(skb, opts); nla_put_failure: nla_nest_cancel(skb, opts); return -EMSGSIZE; } static int nss_tbl_dump_class(struct Qdisc *sch, unsigned long cl, struct sk_buff *skb, struct tcmsg *tcm) { struct nss_tbl_sched_data *q = qdisc_priv(sch); nss_qdisc_info("Nsstbl dumping class"); tcm->tcm_handle |= TC_H_MIN(1); tcm->tcm_info = q->qdisc->handle; return 0; } static int nss_tbl_graft(struct Qdisc *sch, unsigned long arg, struct Qdisc *new, struct Qdisc **old) { struct nss_tbl_sched_data *q = qdisc_priv(sch); struct nss_qdisc *nq_new = (struct nss_qdisc *)qdisc_priv(new); struct nss_if_msg nim_attach; struct nss_if_msg nim_detach; if (new == NULL) new = &noop_qdisc; sch_tree_lock(sch); *old = q->qdisc; q->qdisc = new; qdisc_tree_decrease_qlen(*old, (*old)->q.qlen); qdisc_reset(*old); sch_tree_unlock(sch); nss_qdisc_info("%s:Grafting old: %p with new: %p\n", __func__, *old, new); if (*old != &noop_qdisc) { struct nss_qdisc *nq_old = (struct nss_qdisc *)qdisc_priv(*old); nss_qdisc_info("%s: Detaching old: %p\n", __func__, *old); nim_detach.msg.shaper_configure.config.msg.shaper_node_config.qos_tag = q->nq.qos_tag; if (nss_qdisc_node_detach(&q->nq, nq_old, &nim_detach, NSS_SHAPER_CONFIG_TYPE_TBL_DETACH) < 0) { return -EINVAL; } } if (new != &noop_qdisc) { nss_qdisc_info("%s: Attaching new: %p\n", __func__, new); nim_attach.msg.shaper_configure.config.msg.shaper_node_config.qos_tag = q->nq.qos_tag; nim_attach.msg.shaper_configure.config.msg.shaper_node_config.snc.tbl_attach.child_qos_tag = nq_new->qos_tag; if (nss_qdisc_node_attach(&q->nq, nq_new, &nim_attach, NSS_SHAPER_CONFIG_TYPE_TBL_ATTACH) < 0) { return -EINVAL; } } nss_qdisc_info("Nsstbl grafted"); return 0; } static struct Qdisc *nss_tbl_leaf(struct Qdisc *sch, unsigned long arg) { struct nss_tbl_sched_data *q = qdisc_priv(sch); nss_qdisc_info("Nsstbl returns leaf"); return q->qdisc; } static unsigned long nss_tbl_get(struct Qdisc *sch, u32 classid) { return 1; } static void nss_tbl_put(struct Qdisc *sch, unsigned long arg) { } static void nss_tbl_walk(struct Qdisc *sch, struct qdisc_walker *walker) { nss_qdisc_info("Nsstbl walk called"); if (!walker->stop) { if (walker->count >= walker->skip) if (walker->fn(sch, 1, walker) < 0) { walker->stop = 1; return; } walker->count++; } } const struct Qdisc_class_ops nss_tbl_class_ops = { .graft = nss_tbl_graft, .leaf = nss_tbl_leaf, .get = nss_tbl_get, .put = nss_tbl_put, .walk = nss_tbl_walk, .dump = nss_tbl_dump_class, }; struct Qdisc_ops nss_tbl_qdisc_ops __read_mostly = { .next = NULL, .id = "nsstbl", .priv_size = sizeof(struct nss_tbl_sched_data), .cl_ops = &nss_tbl_class_ops, .enqueue = nss_tbl_enqueue, .dequeue = nss_tbl_dequeue, .peek = nss_tbl_peek, .drop = nss_tbl_drop, .init = nss_tbl_init, .reset = nss_tbl_reset, .destroy = nss_tbl_destroy, .change = nss_tbl_change, .dump = nss_tbl_dump, .owner = THIS_MODULE, };