blob: ddca81bec03ab5968fa92327236019cb2be6f16d [file] [log] [blame]
/*
**************************************************************************
* Copyright (c) 2014-2017, 2019-2020, 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 struct nla_policy nss_tbl_policy[TCA_NSSTBL_MAX + 1] = {
[TCA_NSSTBL_PARMS] = { .len = sizeof(struct tc_nsstbl_qopt) },
};
#if (LINUX_VERSION_CODE < KERNEL_VERSION(4, 8, 0))
static int nss_tbl_enqueue(struct sk_buff *skb, struct Qdisc *sch)
#else
static int nss_tbl_enqueue(struct sk_buff *skb, struct Qdisc *sch,
struct sk_buff **to_free)
#endif
{
#if (LINUX_VERSION_CODE < KERNEL_VERSION(4, 8, 0))
return nss_qdisc_enqueue(skb, sch);
#else
return nss_qdisc_enqueue(skb, sch, to_free);
#endif
}
static struct sk_buff *nss_tbl_dequeue(struct Qdisc *sch)
{
return nss_qdisc_dequeue(sch);
}
#if (LINUX_VERSION_CODE < KERNEL_VERSION(4, 8, 0))
static unsigned int nss_tbl_drop(struct Qdisc *sch)
{
return nss_qdisc_drop(sch);
}
#endif
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_SHAPER_NODE_DETACH) < 0) {
nss_qdisc_error("Failed to detach child %x from nss_tbl %x\n",
q->qdisc->handle, q->nq.qos_tag);
return;
}
}
/*
* Now we can destroy our child qdisc
*/
nss_qdisc_put(q->qdisc);
/*
* Stop the polling of basic stats and destroy qdisc.
*/
nss_qdisc_stop_basic_stats_polling(&q->nq);
nss_qdisc_destroy(&q->nq);
}
#if defined(NSS_QDISC_PPE_SUPPORT)
#if (LINUX_VERSION_CODE < KERNEL_VERSION(4, 16, 0))
static int nss_tbl_ppe_change(struct Qdisc *sch, struct nlattr *opt)
#else
static int nss_tbl_ppe_change(struct Qdisc *sch, struct nlattr *opt, struct netlink_ext_ack *extack)
#endif
{
struct nss_tbl_sched_data *q = qdisc_priv(sch);
struct nss_qdisc *nq = &q->nq;
struct nss_ppe_qdisc prev_npq;
/*
* Save previous configuration for reset purpose
*/
if (nq->npq.is_configured) {
prev_npq = nq->npq;
}
nq->npq.shaper_present = true;
nq->npq.shaper.rate = q->rate;
nq->npq.shaper.burst = q->burst;
nq->npq.shaper.crate = q->rate;
nq->npq.shaper.cburst = q->burst;
nq->npq.shaper.overhead = 0;
if (nss_ppe_configure(nq, &prev_npq) != 0) {
nss_qdisc_warning("nss_tbl %x SSDK scheduler configuration failed\n", sch->handle);
goto fail;
}
return 0;
fail:
if (nq->npq.is_configured) {
nss_qdisc_warning("nss_tbl %x SSDK scheduler configuration failed\n", sch->handle);
return -EINVAL;
}
/*
* PPE qdisc config failed, try to initialize in NSS.
*/
#if (LINUX_VERSION_CODE < KERNEL_VERSION(4, 16, 0))
if (nss_ppe_fallback_to_nss(nq, opt)) {
#else
if (nss_ppe_fallback_to_nss(nq, opt, extack)) {
#endif
nss_qdisc_warning("nss_tbl %x fallback to nss failed\n", sch->handle);
return -EINVAL;
}
return 0;
}
#endif
#if (LINUX_VERSION_CODE < KERNEL_VERSION(4, 16, 0))
static int nss_tbl_change(struct Qdisc *sch, struct nlattr *opt)
#else
static int nss_tbl_change(struct Qdisc *sch, struct nlattr *opt,
struct netlink_ext_ack *extack)
#endif
{
struct nss_tbl_sched_data *q = qdisc_priv(sch);
struct nlattr *tb[TCA_NSSTBL_MAX + 1];
struct tc_nsstbl_qopt *qopt;
struct nss_if_msg nim;
struct net_device *dev = qdisc_dev(sch);
if (!opt) {
return -EINVAL;
}
#if (LINUX_VERSION_CODE < KERNEL_VERSION(4, 16, 0))
qopt = nss_qdisc_qopt_get(opt, nss_tbl_policy, tb, TCA_NSSTBL_MAX, TCA_NSSTBL_PARMS);
#else
qopt = nss_qdisc_qopt_get(opt, nss_tbl_policy, tb, TCA_NSSTBL_MAX, TCA_NSSTBL_PARMS, extack);
#endif
if (!qopt) {
return -EINVAL;
}
/*
* 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);
#if defined(NSS_QDISC_PPE_SUPPORT)
if (q->nq.mode == NSS_QDISC_MODE_PPE) {
#if (LINUX_VERSION_CODE < KERNEL_VERSION(4, 16, 0))
if (nss_tbl_ppe_change(sch, opt) < 0) {
#else
if (nss_tbl_ppe_change(sch, opt, extack) < 0) {
#endif
nss_qdisc_warning("nss_tbl %x SSDK scheduler config failed\n", sch->handle);
return -EINVAL;
}
return 0;
}
#endif
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_SHAPER_NODE_CHANGE_PARAM) < 0) {
return -EINVAL;
}
return 0;
}
#if (LINUX_VERSION_CODE < KERNEL_VERSION(4, 16, 0))
static int nss_tbl_init(struct Qdisc *sch, struct nlattr *opt)
{
struct netlink_ext_ack *extack = NULL;
#else
static int nss_tbl_init(struct Qdisc *sch, struct nlattr *opt,
struct netlink_ext_ack *extack)
{
#endif
struct nss_tbl_sched_data *q = qdisc_priv(sch);
struct nlattr *tb[TCA_NSSTBL_MAX + 1];
struct tc_nsstbl_qopt *qopt;
if (!opt) {
return -EINVAL;
}
q->qdisc = &noop_qdisc;
#if (LINUX_VERSION_CODE < KERNEL_VERSION(4, 16, 0))
qopt = nss_qdisc_qopt_get(opt, nss_tbl_policy, tb, TCA_NSSTBL_MAX, TCA_NSSTBL_PARMS);
#else
qopt = nss_qdisc_qopt_get(opt, nss_tbl_policy, tb, TCA_NSSTBL_MAX, TCA_NSSTBL_PARMS, extack);
#endif
if (!qopt) {
return -EINVAL;
}
if (nss_qdisc_init(sch, &q->nq, NSS_SHAPER_NODE_TYPE_TBL, 0, qopt->accel_mode, extack) < 0)
{
return -EINVAL;
}
#if (LINUX_VERSION_CODE < KERNEL_VERSION(4, 16, 0))
if (nss_tbl_change(sch, opt) < 0) {
#else
if (nss_tbl_change(sch, opt, extack) < 0) {
#endif
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;
opt.rate = q->rate;
opt.peakrate = q->peakrate;
opt.burst = q->burst;
opt.mtu = q->mtu;
opt.accel_mode = nss_qdisc_accel_mode_get(&q->nq);
nss_qdisc_info("Nsstbl dumping");
opts = nss_qdisc_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;
}
#if (LINUX_VERSION_CODE < KERNEL_VERSION(4, 16, 0))
static int nss_tbl_graft(struct Qdisc *sch, unsigned long arg, struct Qdisc *new,
struct Qdisc **old)
#else
static int nss_tbl_graft(struct Qdisc *sch, unsigned long arg, struct Qdisc *new,
struct Qdisc **old, struct netlink_ext_ack *extack)
#endif
{
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;
sch_tree_unlock(sch);
nss_qdisc_info("Grafting old: %px with new: %px\n", *old, new);
if (*old != &noop_qdisc) {
struct nss_qdisc *nq_old = (struct nss_qdisc *)qdisc_priv(*old);
nss_qdisc_info("Detaching old: %px\n", *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_SHAPER_NODE_DETACH) < 0) {
return -EINVAL;
}
}
if (new != &noop_qdisc) {
nss_qdisc_info("Attaching new: %px\n", 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_SHAPER_NODE_ATTACH) < 0) {
return -EINVAL;
}
}
/*
* Replaced in NSS, now replace in Linux.
*/
nss_qdisc_replace(sch, new, &q->qdisc);
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;
}
#if (LINUX_VERSION_CODE < KERNEL_VERSION(4, 14, 0))
static unsigned long nss_tbl_get(struct Qdisc *sch, u32 classid)
{
return 1;
}
static void nss_tbl_put(struct Qdisc *sch, unsigned long arg)
{
}
#else
static unsigned long nss_tbl_search(struct Qdisc *sch, u32 classid)
{
return 1;
}
#endif
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,
#if (LINUX_VERSION_CODE < KERNEL_VERSION(4, 14, 0))
.get = nss_tbl_get,
.put = nss_tbl_put,
#else
.find = nss_tbl_search,
#endif
#if (LINUX_VERSION_CODE < KERNEL_VERSION(5, 4, 0))
.tcf_chain = nss_qdisc_tcf_chain,
#else
.tcf_block = nss_qdisc_tcf_block,
#endif
.bind_tcf = nss_qdisc_tcf_bind,
.unbind_tcf = nss_qdisc_tcf_unbind,
.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,
#if (LINUX_VERSION_CODE < KERNEL_VERSION(4, 8, 0))
.drop = nss_tbl_drop,
#endif
.init = nss_tbl_init,
.reset = nss_tbl_reset,
.destroy = nss_tbl_destroy,
.change = nss_tbl_change,
.dump = nss_tbl_dump,
.owner = THIS_MODULE,
};