| /* |
| ************************************************************************** |
| * Copyright (c) 2014, 2016-2017, 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" |
| #include "nss_fifo.h" |
| |
| struct nss_fifo_sched_data { |
| struct nss_qdisc nq; /* Common base class for all nss qdiscs */ |
| u32 limit; /* Queue length in packets */ |
| /* TODO: Support for queue length in bytes */ |
| u8 set_default; /* Flag to set qdisc as default qdisc for enqueue */ |
| bool is_bfifo; /* Flag to identify bfifo or pfifo */ |
| }; |
| |
| static struct nla_policy nss_fifo_policy[TCA_NSSFIFO_MAX + 1] = { |
| [TCA_NSSFIFO_PARMS] = { .len = sizeof(struct tc_nssfifo_qopt) }, |
| }; |
| |
| #if (LINUX_VERSION_CODE < KERNEL_VERSION(4, 8, 0)) |
| static int nss_fifo_enqueue(struct sk_buff *skb, struct Qdisc *sch) |
| #else |
| static int nss_fifo_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_fifo_dequeue(struct Qdisc *sch) |
| { |
| return nss_qdisc_dequeue(sch); |
| } |
| |
| #if (LINUX_VERSION_CODE < KERNEL_VERSION(4, 8, 0)) |
| static unsigned int nss_fifo_drop(struct Qdisc *sch) |
| { |
| nss_qdisc_info("nss_fifo dropping"); |
| return nss_qdisc_drop(sch); |
| } |
| #endif |
| |
| static void nss_fifo_reset(struct Qdisc *sch) |
| { |
| nss_qdisc_info("nss_fifo resetting!"); |
| nss_qdisc_reset(sch); |
| } |
| |
| static void nss_fifo_destroy(struct Qdisc *sch) |
| { |
| struct nss_qdisc *nq = (struct nss_qdisc *)qdisc_priv(sch); |
| |
| /* |
| * Stop the polling of basic stats |
| */ |
| nss_qdisc_stop_basic_stats_polling(nq); |
| |
| nss_qdisc_destroy(nq); |
| nss_qdisc_info("nss_fifo destroyed"); |
| } |
| |
| #if (LINUX_VERSION_CODE < KERNEL_VERSION(4, 16, 0)) |
| static int nss_fifo_params_validate_and_save(struct Qdisc *sch, struct nlattr *opt) |
| #else |
| static int nss_fifo_params_validate_and_save(struct Qdisc *sch, struct nlattr *opt, |
| struct netlink_ext_ack *extack) |
| #endif |
| { |
| struct nlattr *tb[TCA_NSSFIFO_MAX + 1]; |
| struct tc_nssfifo_qopt *qopt; |
| struct nss_fifo_sched_data *q = qdisc_priv(sch); |
| bool is_bfifo = (sch->ops == &nss_bfifo_qdisc_ops); |
| |
| if (!opt) { |
| return -EINVAL; |
| } |
| |
| #if (LINUX_VERSION_CODE < KERNEL_VERSION(4, 16, 0)) |
| qopt = nss_qdisc_qopt_get(opt, nss_fifo_policy, tb, TCA_NSSFIFO_MAX, TCA_NSSFIFO_PARMS); |
| #else |
| qopt = nss_qdisc_qopt_get(opt, nss_fifo_policy, tb, TCA_NSSFIFO_MAX, TCA_NSSFIFO_PARMS, extack); |
| #endif |
| if (!qopt) { |
| nss_qdisc_warning("Invalid input to fifo %x", sch->handle); |
| return -EINVAL; |
| } |
| |
| if (!qopt->limit) { |
| qopt->limit = qdisc_dev(sch)->tx_queue_len ? : 1; |
| |
| if (is_bfifo) { |
| qopt->limit *= psched_mtu(qdisc_dev(sch)); |
| } |
| } |
| |
| /* |
| * Required for basic stats display |
| */ |
| sch->limit = qopt->limit; |
| q->limit = qopt->limit; |
| q->set_default = qopt->set_default; |
| q->is_bfifo = is_bfifo; |
| |
| nss_qdisc_info("limit:%u set_default:%u\n", qopt->limit, qopt->set_default); |
| return 0; |
| } |
| |
| #if defined(NSS_QDISC_PPE_SUPPORT) |
| #if (LINUX_VERSION_CODE < KERNEL_VERSION(4, 16, 0)) |
| static int nss_fifo_ppe_change(struct Qdisc *sch, struct nlattr *opt) |
| #else |
| static int nss_fifo_ppe_change(struct Qdisc *sch, struct nlattr *opt, struct netlink_ext_ack *extack) |
| #endif |
| { |
| struct nss_fifo_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; |
| } |
| |
| /* |
| * In case of bfifo, change the queue limit to number of blocks |
| * as PPE HW has memory in blocks of 256 bytes. |
| */ |
| if (q->is_bfifo) { |
| nq->npq.q.qlimit = q->limit / NSS_PPE_MEM_BLOCK_SIZE; |
| } else { |
| nq->npq.q.qlimit = q->limit; |
| } |
| |
| nq->npq.q.color_en = false; |
| nq->npq.q.red_en = false; |
| |
| /* |
| * Currently multicast queue configuration is based on set_default. |
| * TODO: Enhance multicast queue configuration on the basis of |
| * multicast parameter specified in tc commands. |
| */ |
| nq->npq.q.mcast_enable = q->set_default; |
| |
| if (nss_ppe_configure(&q->nq, &prev_npq) < 0) { |
| nss_qdisc_warning("nss_fifo %x configuration failed\n", sch->handle); |
| goto fail; |
| } |
| |
| return 0; |
| |
| fail: |
| if (nq->npq.is_configured) { |
| nss_qdisc_warning("nss_fifo %x configuration failed\n", sch->handle); |
| return -EINVAL; |
| } |
| |
| /* |
| * Fallback to nss qdisc if PPE Qdisc configuration failed at init time. |
| */ |
| #if (LINUX_VERSION_CODE < KERNEL_VERSION(4, 16, 0)) |
| if (nss_ppe_fallback_to_nss(&q->nq, opt) < 0) { |
| #else |
| if (nss_ppe_fallback_to_nss(&q->nq, opt, extack) < 0) { |
| #endif |
| nss_qdisc_warning("nss_fifo %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_fifo_change(struct Qdisc *sch, struct nlattr *opt) |
| #else |
| static int nss_fifo_change(struct Qdisc *sch, struct nlattr *opt, |
| struct netlink_ext_ack *extack) |
| #endif |
| { |
| struct nss_fifo_sched_data *q = qdisc_priv(sch); |
| struct nss_qdisc *nq = &q->nq; |
| struct nss_if_msg nim; |
| |
| #if (LINUX_VERSION_CODE < KERNEL_VERSION(4, 16, 0)) |
| if (nss_fifo_params_validate_and_save(sch, opt) < 0) { |
| #else |
| if (nss_fifo_params_validate_and_save(sch, opt, extack) < 0) { |
| #endif |
| nss_qdisc_warning("nss_fifo %px params validate and save failed\n", sch); |
| return -EINVAL; |
| } |
| |
| #if defined(NSS_QDISC_PPE_SUPPORT) |
| if (nq->mode == NSS_QDISC_MODE_PPE) { |
| #if (LINUX_VERSION_CODE < KERNEL_VERSION(4, 16, 0)) |
| if (nss_fifo_ppe_change(sch, opt) < 0) { |
| #else |
| if (nss_fifo_ppe_change(sch, opt, extack) < 0) { |
| #endif |
| nss_qdisc_warning("nss_fifo %px params validate and save failed\n", sch); |
| return -EINVAL; |
| } |
| return 0; |
| } |
| #endif |
| |
| nim.msg.shaper_configure.config.msg.shaper_node_config.qos_tag = nq->qos_tag; |
| nim.msg.shaper_configure.config.msg.shaper_node_config.snc.fifo_param.limit = q->limit; |
| nim.msg.shaper_configure.config.msg.shaper_node_config.snc.fifo_param.drop_mode = NSS_SHAPER_FIFO_DROP_MODE_TAIL; |
| if (nss_qdisc_configure(&q->nq, &nim, NSS_SHAPER_CONFIG_TYPE_SHAPER_NODE_CHANGE_PARAM) < 0) { |
| nss_qdisc_error("nss_fifo %px configuration failed\n", sch); |
| return -EINVAL; |
| } |
| |
| /* |
| * There is nothing we need to do if the qdisc is not |
| * set as default qdisc. |
| */ |
| if (q->set_default == 0) { |
| return 0; |
| } |
| |
| /* |
| * Set this qdisc to be the default qdisc for enqueuing packets. |
| */ |
| if (nss_qdisc_set_default(nq) < 0) { |
| nss_qdisc_error("nss_fifo %px set_default failed\n", sch); |
| return -EINVAL; |
| } |
| |
| nss_qdisc_info("nss_fifo queue (qos_tag:%u) set as default\n", nq->qos_tag); |
| |
| return 0; |
| } |
| |
| #if (LINUX_VERSION_CODE < KERNEL_VERSION(4, 16, 0)) |
| static int nss_fifo_init(struct Qdisc *sch, struct nlattr *opt) |
| { |
| struct netlink_ext_ack *extack = NULL; |
| #else |
| static int nss_fifo_init(struct Qdisc *sch, struct nlattr *opt, |
| struct netlink_ext_ack *extack) |
| { |
| #endif |
| struct nss_qdisc *nq = qdisc_priv(sch); |
| struct nlattr *tb[TCA_NSSFIFO_MAX + 1]; |
| struct tc_nssfifo_qopt *qopt; |
| |
| if (!opt) { |
| return -EINVAL; |
| } |
| |
| nss_qdisc_info("Initializing Fifo - type %d\n", NSS_SHAPER_NODE_TYPE_FIFO); |
| nss_fifo_reset(sch); |
| |
| #if (LINUX_VERSION_CODE < KERNEL_VERSION(4, 16, 0)) |
| qopt = nss_qdisc_qopt_get(opt, nss_fifo_policy, tb, TCA_NSSFIFO_MAX, TCA_NSSFIFO_PARMS); |
| #else |
| qopt = nss_qdisc_qopt_get(opt, nss_fifo_policy, tb, TCA_NSSFIFO_MAX, TCA_NSSFIFO_PARMS, extack); |
| #endif |
| if (!qopt) { |
| nss_qdisc_warning("Invalid input to fifo %x", sch->handle); |
| return -EINVAL; |
| } |
| |
| if (nss_qdisc_init(sch, nq, NSS_SHAPER_NODE_TYPE_FIFO, 0, qopt->accel_mode, extack) < 0) |
| { |
| nss_qdisc_warning("Fifo %x init failed", sch->handle); |
| return -EINVAL; |
| } |
| |
| nss_qdisc_info("NSS fifo initialized - handle %x parent %x\n", sch->handle, sch->parent); |
| #if (LINUX_VERSION_CODE < KERNEL_VERSION(4, 16, 0)) |
| if (nss_fifo_change(sch, opt) < 0) { |
| #else |
| if (nss_fifo_change(sch, opt, extack) < 0) { |
| #endif |
| nss_qdisc_destroy(nq); |
| return -EINVAL; |
| } |
| |
| /* |
| * Start the stats polling timer |
| */ |
| nss_qdisc_start_basic_stats_polling(nq); |
| |
| return 0; |
| } |
| |
| static int nss_fifo_dump(struct Qdisc *sch, struct sk_buff *skb) |
| { |
| struct nss_fifo_sched_data *q; |
| struct nlattr *opts = NULL; |
| struct tc_nssfifo_qopt opt; |
| |
| nss_qdisc_info("Nssfifo Dumping!"); |
| |
| q = qdisc_priv(sch); |
| if (q == NULL) { |
| return -1; |
| } |
| |
| opt.limit = q->limit; |
| opt.set_default = q->set_default; |
| opt.accel_mode = nss_qdisc_accel_mode_get(&q->nq); |
| |
| opts = nss_qdisc_nla_nest_start(skb, TCA_OPTIONS); |
| |
| if (opts == NULL) { |
| goto nla_put_failure; |
| } |
| |
| if (nla_put(skb, TCA_NSSFIFO_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 struct sk_buff *nss_fifo_peek(struct Qdisc *sch) |
| { |
| nss_qdisc_info("Nssfifo Peeking"); |
| return nss_qdisc_peek(sch); |
| } |
| |
| struct Qdisc_ops nss_pfifo_qdisc_ops __read_mostly = { |
| .id = "nsspfifo", |
| .priv_size = sizeof(struct nss_fifo_sched_data), |
| .enqueue = nss_fifo_enqueue, |
| .dequeue = nss_fifo_dequeue, |
| .peek = nss_fifo_peek, |
| #if (LINUX_VERSION_CODE < KERNEL_VERSION(4, 8, 0)) |
| .drop = nss_fifo_drop, |
| #endif |
| .init = nss_fifo_init, |
| .reset = nss_fifo_reset, |
| .destroy = nss_fifo_destroy, |
| .change = nss_fifo_change, |
| .dump = nss_fifo_dump, |
| .owner = THIS_MODULE, |
| }; |
| |
| struct Qdisc_ops nss_bfifo_qdisc_ops __read_mostly = { |
| .id = "nssbfifo", |
| .priv_size = sizeof(struct nss_fifo_sched_data), |
| .enqueue = nss_fifo_enqueue, |
| .dequeue = nss_fifo_dequeue, |
| .peek = nss_fifo_peek, |
| #if (LINUX_VERSION_CODE < KERNEL_VERSION(4, 8, 0)) |
| .drop = nss_fifo_drop, |
| #endif |
| .init = nss_fifo_init, |
| .reset = nss_fifo_reset, |
| .destroy = nss_fifo_destroy, |
| .change = nss_fifo_change, |
| .dump = nss_fifo_dump, |
| .owner = THIS_MODULE, |
| }; |