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:
parent
bed17f6d1d
commit
c6e14f9e5f
4 changed files with 60 additions and 0 deletions
|
@ -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.
|
||||
*/
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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.
|
||||
*/
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Reference in a new issue