net_sched: Add flow control support to prio qdisc

Add enable_flow flag to the prio qdisc. Packet flow is enabled by
default, but can be disabled from userspace (e.g. IPROUTE2 tc tool).
This allows for suspending packet dequeue on a per-qdisc basis,
which is needed to support Quality of Service (QOS).

Export a function that will look up desired qdisc and call it's
registered change function to enable/disable flow. This API also
returns the size of the qdisc in order to be able to collect data on
the size of the qdisc before doing flow control operations. This is
required to effectively diagnose the state of the queues when
debugging flow control.

The PRIO qdisc supports flow control, such that packet
dequeue can be disabled based on boolean flag 'enable_flow'.
When flow is re-enabled, the latency for new packets
arriving at network driver is high.  To reduce the delay in
scheduling packets, the qdisc will now invoke
__netif_schedule() to expedite dequeue.  This significantly
reduces the latency of packets arriving at network driver.

Change-Id: I0e9096e4241d459540028558fdec18ece460d517
Signed-off-by: Subash Abhinov Kasiviswanathan <subashab@codeaurora.org>
Signed-off-by: Sharath Chandra Vurukala <sharathv@codeaurora.org>
This commit is contained in:
Subash Abhinov Kasiviswanathan 2017-11-01 15:33:57 -06:00 committed by Sharath Chandra Vurukala
parent bed17f6d1d
commit c6e14f9e5f
4 changed files with 60 additions and 0 deletions

View file

@ -133,6 +133,8 @@ static inline __be16 tc_skb_protocol(const struct sk_buff *skb)
return skb->protocol;
}
extern int tc_qdisc_flow_control(struct net_device *dev, u32 tcm_handle,
int flow_enable);
/* Calculate maximal size of packet seen by hard_start_xmit
routine of this device.
*/

View file

@ -147,8 +147,11 @@ struct tc_skbprio_qopt {
struct tc_prio_qopt {
int bands; /* Number of bands */
__u8 priomap[TC_PRIO_MAX+1]; /* Map: logical priority -> PRIO band */
__u8 enable_flow; /* Enable dequeue */
};
#define TCQ_PRIO_FLOW_CONTROL 1
/* MULTIQ section */
struct tc_multiq_qopt {

View file

@ -1404,6 +1404,40 @@ static int tc_get_qdisc(struct sk_buff *skb, struct nlmsghdr *n,
return 0;
}
/*
* enable/disable flow on qdisc.
*/
int
tc_qdisc_flow_control(struct net_device *dev, u32 tcm_handle, int enable_flow)
{
struct Qdisc *q;
int qdisc_len = 0;
struct __qdisc_change_req {
struct nlattr attr;
struct tc_prio_qopt data;
} req = {
.attr = {sizeof(struct __qdisc_change_req), TCA_OPTIONS},
.data = {3, {1, 2, 2, 2, 1, 2, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1}, 1}
};
/* override flow bit */
req.data.enable_flow = enable_flow;
/* look up using tcm handle */
q = qdisc_lookup(dev, tcm_handle);
/* call registered change function */
if (likely(q && q->ops)) {
if (likely(q->ops->change)) {
qdisc_len = q->q.qlen;
if (q->ops->change(q, &req.attr, NULL))
pr_err("%s(): qdisc change failed\n", __func__);
}
}
return qdisc_len;
}
EXPORT_SYMBOL(tc_qdisc_flow_control);
/*
* Create/change qdisc.
*/

View file

@ -18,6 +18,7 @@
#include <linux/string.h>
#include <linux/errno.h>
#include <linux/skbuff.h>
#include <linux/netdevice.h>
#include <net/netlink.h>
#include <net/pkt_sched.h>
#include <net/pkt_cls.h>
@ -28,6 +29,7 @@ struct prio_sched_data {
struct tcf_block *block;
u8 prio2band[TC_PRIO_MAX+1];
struct Qdisc *queues[TCQ_PRIO_BANDS];
u8 enable_flow;
};
@ -102,6 +104,9 @@ static struct sk_buff *prio_peek(struct Qdisc *sch)
struct prio_sched_data *q = qdisc_priv(sch);
int prio;
if (!q->enable_flow)
return NULL;
for (prio = 0; prio < q->bands; prio++) {
struct Qdisc *qdisc = q->queues[prio];
struct sk_buff *skb = qdisc->ops->peek(qdisc);
@ -116,6 +121,9 @@ static struct sk_buff *prio_dequeue(struct Qdisc *sch)
struct prio_sched_data *q = qdisc_priv(sch);
int prio;
if (!q->enable_flow)
return NULL;
for (prio = 0; prio < q->bands; prio++) {
struct Qdisc *qdisc = q->queues[prio];
struct sk_buff *skb = qdisc_dequeue_peeked(qdisc);
@ -140,6 +148,7 @@ prio_reset(struct Qdisc *sch)
qdisc_reset(q->queues[prio]);
sch->qstats.backlog = 0;
sch->q.qlen = 0;
q->enable_flow = 1;
}
static int prio_offload(struct Qdisc *sch, struct tc_prio_qopt *qopt)
@ -185,6 +194,7 @@ static int prio_tune(struct Qdisc *sch, struct nlattr *opt,
struct Qdisc *queues[TCQ_PRIO_BANDS];
int oldbands = q->bands, i;
struct tc_prio_qopt *qopt;
int flow_change = 0;
if (nla_len(opt) < sizeof(*qopt))
return -EINVAL;
@ -212,6 +222,10 @@ static int prio_tune(struct Qdisc *sch, struct nlattr *opt,
prio_offload(sch, qopt);
sch_tree_lock(sch);
if (q->enable_flow != qopt->enable_flow) {
q->enable_flow = qopt->enable_flow;
flow_change = 1;
}
q->bands = qopt->bands;
memcpy(q->prio2band, qopt->priomap, TC_PRIO_MAX+1);
@ -230,6 +244,12 @@ static int prio_tune(struct Qdisc *sch, struct nlattr *opt,
}
sch_tree_unlock(sch);
/* Schedule qdisc when flow re-enabled */
if (flow_change && q->enable_flow) {
if (!test_bit(__QDISC_STATE_DEACTIVATED, &sch->state))
__netif_schedule(qdisc_root(sch));
}
return 0;
}
@ -288,6 +308,7 @@ static int prio_dump(struct Qdisc *sch, struct sk_buff *skb)
int err;
opt.bands = q->bands;
opt.enable_flow = q->enable_flow;
memcpy(&opt.priomap, q->prio2band, TC_PRIO_MAX + 1);
err = prio_dump_offload(sch);