Commit 2f530df7 authored by Vladimir Oltean's avatar Vladimir Oltean Committed by David S. Miller

net/sched: taprio: give higher priority to higher TCs in software dequeue mode

Current taprio software implementation is haunted by the shadow of the
igb/igc hardware model. It iterates over child qdiscs in increasing
order of TXQ index, therefore giving higher xmit priority to TXQ 0 and
lower to TXQ N. According to discussions with Vinicius, that is the
default (perhaps even unchangeable) prioritization scheme used for the
NICs that taprio was first written for (igb, igc), and we have a case of
two bugs canceling out, resulting in a functional setup on igb/igc, but
a less sane one on other NICs.

To the best of my understanding, taprio should prioritize based on the
traffic class, so it should really dequeue starting with the highest
traffic class and going down from there. We get to the TXQ using the
tc_to_txq[] netdev property.

TXQs within the same TC have the same (strict) priority, so we should
pick from them as fairly as we can. We can achieve that by implementing
something very similar to q->curband from multiq_dequeue().

Since igb/igc really do have TXQ 0 of higher hardware priority than
TXQ 1 etc, we need to preserve the behavior for them as well. We really
have no choice, because in txtime-assist mode, taprio is essentially a
software scheduler towards offloaded child tc-etf qdiscs, so the TXQ
selection really does matter (not all igb TXQs support ETF/SO_TXTIME,
says Kurt Kanzenbach).

To preserve the behavior, we need a capability bit so that taprio can
determine if it's running on igb/igc, or on something else. Because igb
doesn't offload taprio at all, we can't piggyback on the
qdisc_offload_query_caps() call from taprio_enable_offload(), but
instead we need a separate call which is also made for software
scheduling.

Introduce two static keys to minimize the performance penalty on systems
which only have igb/igc NICs, and on systems which only have other NICs.
For mixed systems, taprio will have to dynamically check whether to
dequeue using one prioritization algorithm or using the other.
Signed-off-by: default avatarVladimir Oltean <vladimir.oltean@nxp.com>
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parent 4c229427
...@@ -2810,6 +2810,22 @@ static int igb_offload_txtime(struct igb_adapter *adapter, ...@@ -2810,6 +2810,22 @@ static int igb_offload_txtime(struct igb_adapter *adapter,
return 0; return 0;
} }
static int igb_tc_query_caps(struct igb_adapter *adapter,
struct tc_query_caps_base *base)
{
switch (base->type) {
case TC_SETUP_QDISC_TAPRIO: {
struct tc_taprio_caps *caps = base->caps;
caps->broken_mqprio = true;
return 0;
}
default:
return -EOPNOTSUPP;
}
}
static LIST_HEAD(igb_block_cb_list); static LIST_HEAD(igb_block_cb_list);
static int igb_setup_tc(struct net_device *dev, enum tc_setup_type type, static int igb_setup_tc(struct net_device *dev, enum tc_setup_type type,
...@@ -2818,6 +2834,8 @@ static int igb_setup_tc(struct net_device *dev, enum tc_setup_type type, ...@@ -2818,6 +2834,8 @@ static int igb_setup_tc(struct net_device *dev, enum tc_setup_type type,
struct igb_adapter *adapter = netdev_priv(dev); struct igb_adapter *adapter = netdev_priv(dev);
switch (type) { switch (type) {
case TC_QUERY_CAPS:
return igb_tc_query_caps(adapter, type_data);
case TC_SETUP_QDISC_CBS: case TC_SETUP_QDISC_CBS:
return igb_offload_cbs(adapter, type_data); return igb_offload_cbs(adapter, type_data);
case TC_SETUP_BLOCK: case TC_SETUP_BLOCK:
......
...@@ -6214,9 +6214,9 @@ static int igc_tc_query_caps(struct igc_adapter *adapter, ...@@ -6214,9 +6214,9 @@ static int igc_tc_query_caps(struct igc_adapter *adapter,
case TC_SETUP_QDISC_TAPRIO: { case TC_SETUP_QDISC_TAPRIO: {
struct tc_taprio_caps *caps = base->caps; struct tc_taprio_caps *caps = base->caps;
if (hw->mac.type != igc_i225) caps->broken_mqprio = true;
return -EOPNOTSUPP;
if (hw->mac.type == igc_i225)
caps->gate_mask_per_txq = true; caps->gate_mask_per_txq = true;
return 0; return 0;
......
...@@ -177,6 +177,11 @@ struct tc_mqprio_qopt_offload { ...@@ -177,6 +177,11 @@ struct tc_mqprio_qopt_offload {
struct tc_taprio_caps { struct tc_taprio_caps {
bool supports_queue_max_sdu:1; bool supports_queue_max_sdu:1;
bool gate_mask_per_txq:1; bool gate_mask_per_txq:1;
/* Device expects lower TXQ numbers to have higher priority over higher
* TXQs, regardless of their TC mapping. DO NOT USE FOR NEW DRIVERS,
* INSTEAD ENFORCE A PROPER TC:TXQ MAPPING COMING FROM USER SPACE.
*/
bool broken_mqprio:1;
}; };
struct tc_taprio_sched_entry { struct tc_taprio_sched_entry {
......
...@@ -29,6 +29,8 @@ ...@@ -29,6 +29,8 @@
#include "sch_mqprio_lib.h" #include "sch_mqprio_lib.h"
static LIST_HEAD(taprio_list); static LIST_HEAD(taprio_list);
static struct static_key_false taprio_have_broken_mqprio;
static struct static_key_false taprio_have_working_mqprio;
#define TAPRIO_ALL_GATES_OPEN -1 #define TAPRIO_ALL_GATES_OPEN -1
...@@ -69,6 +71,8 @@ struct taprio_sched { ...@@ -69,6 +71,8 @@ struct taprio_sched {
enum tk_offsets tk_offset; enum tk_offsets tk_offset;
int clockid; int clockid;
bool offloaded; bool offloaded;
bool detected_mqprio;
bool broken_mqprio;
atomic64_t picos_per_byte; /* Using picoseconds because for 10Gbps+ atomic64_t picos_per_byte; /* Using picoseconds because for 10Gbps+
* speeds it's sub-nanoseconds per byte * speeds it's sub-nanoseconds per byte
*/ */
...@@ -80,6 +84,7 @@ struct taprio_sched { ...@@ -80,6 +84,7 @@ struct taprio_sched {
struct sched_gate_list __rcu *admin_sched; struct sched_gate_list __rcu *admin_sched;
struct hrtimer advance_timer; struct hrtimer advance_timer;
struct list_head taprio_list; struct list_head taprio_list;
int cur_txq[TC_MAX_QUEUE];
u32 max_frm_len[TC_MAX_QUEUE]; /* for the fast path */ u32 max_frm_len[TC_MAX_QUEUE]; /* for the fast path */
u32 max_sdu[TC_MAX_QUEUE]; /* for dump and offloading */ u32 max_sdu[TC_MAX_QUEUE]; /* for dump and offloading */
u32 txtime_delay; u32 txtime_delay;
...@@ -568,17 +573,78 @@ static struct sk_buff *taprio_dequeue_from_txq(struct Qdisc *sch, int txq, ...@@ -568,17 +573,78 @@ static struct sk_buff *taprio_dequeue_from_txq(struct Qdisc *sch, int txq,
return skb; return skb;
} }
static void taprio_next_tc_txq(struct net_device *dev, int tc, int *txq)
{
int offset = dev->tc_to_txq[tc].offset;
int count = dev->tc_to_txq[tc].count;
(*txq)++;
if (*txq == offset + count)
*txq = offset;
}
/* Prioritize higher traffic classes, and select among TXQs belonging to the
* same TC using round robin
*/
static struct sk_buff *taprio_dequeue_tc_priority(struct Qdisc *sch,
struct sched_entry *entry,
u32 gate_mask)
{
struct taprio_sched *q = qdisc_priv(sch);
struct net_device *dev = qdisc_dev(sch);
int num_tc = netdev_get_num_tc(dev);
struct sk_buff *skb;
int tc;
for (tc = num_tc - 1; tc >= 0; tc--) {
int first_txq = q->cur_txq[tc];
if (!(gate_mask & BIT(tc)))
continue;
do {
skb = taprio_dequeue_from_txq(sch, q->cur_txq[tc],
entry, gate_mask);
taprio_next_tc_txq(dev, tc, &q->cur_txq[tc]);
if (skb)
return skb;
} while (q->cur_txq[tc] != first_txq);
}
return NULL;
}
/* Broken way of prioritizing smaller TXQ indices and ignoring the traffic
* class other than to determine whether the gate is open or not
*/
static struct sk_buff *taprio_dequeue_txq_priority(struct Qdisc *sch,
struct sched_entry *entry,
u32 gate_mask)
{
struct net_device *dev = qdisc_dev(sch);
struct sk_buff *skb;
int i;
for (i = 0; i < dev->num_tx_queues; i++) {
skb = taprio_dequeue_from_txq(sch, i, entry, gate_mask);
if (skb)
return skb;
}
return NULL;
}
/* Will not be called in the full offload case, since the TX queues are /* Will not be called in the full offload case, since the TX queues are
* attached to the Qdisc created using qdisc_create_dflt() * attached to the Qdisc created using qdisc_create_dflt()
*/ */
static struct sk_buff *taprio_dequeue(struct Qdisc *sch) static struct sk_buff *taprio_dequeue(struct Qdisc *sch)
{ {
struct taprio_sched *q = qdisc_priv(sch); struct taprio_sched *q = qdisc_priv(sch);
struct net_device *dev = qdisc_dev(sch);
struct sk_buff *skb = NULL; struct sk_buff *skb = NULL;
struct sched_entry *entry; struct sched_entry *entry;
u32 gate_mask; u32 gate_mask;
int i;
rcu_read_lock(); rcu_read_lock();
entry = rcu_dereference(q->current_entry); entry = rcu_dereference(q->current_entry);
...@@ -588,14 +654,23 @@ static struct sk_buff *taprio_dequeue(struct Qdisc *sch) ...@@ -588,14 +654,23 @@ static struct sk_buff *taprio_dequeue(struct Qdisc *sch)
* "AdminGateStates" * "AdminGateStates"
*/ */
gate_mask = entry ? entry->gate_mask : TAPRIO_ALL_GATES_OPEN; gate_mask = entry ? entry->gate_mask : TAPRIO_ALL_GATES_OPEN;
if (!gate_mask) if (!gate_mask)
goto done; goto done;
for (i = 0; i < dev->num_tx_queues; i++) { if (static_branch_unlikely(&taprio_have_broken_mqprio) &&
skb = taprio_dequeue_from_txq(sch, i, entry, gate_mask); !static_branch_likely(&taprio_have_working_mqprio)) {
if (skb) /* Single NIC kind which is broken */
goto done; skb = taprio_dequeue_txq_priority(sch, entry, gate_mask);
} else if (static_branch_likely(&taprio_have_working_mqprio) &&
!static_branch_unlikely(&taprio_have_broken_mqprio)) {
/* Single NIC kind which prioritizes properly */
skb = taprio_dequeue_tc_priority(sch, entry, gate_mask);
} else {
/* Mixed NIC kinds present in system, need dynamic testing */
if (q->broken_mqprio)
skb = taprio_dequeue_txq_priority(sch, entry, gate_mask);
else
skb = taprio_dequeue_tc_priority(sch, entry, gate_mask);
} }
done: done:
...@@ -1157,6 +1232,34 @@ static void taprio_sched_to_offload(struct net_device *dev, ...@@ -1157,6 +1232,34 @@ static void taprio_sched_to_offload(struct net_device *dev,
offload->num_entries = i; offload->num_entries = i;
} }
static void taprio_detect_broken_mqprio(struct taprio_sched *q)
{
struct net_device *dev = qdisc_dev(q->root);
struct tc_taprio_caps caps;
qdisc_offload_query_caps(dev, TC_SETUP_QDISC_TAPRIO,
&caps, sizeof(caps));
q->broken_mqprio = caps.broken_mqprio;
if (q->broken_mqprio)
static_branch_inc(&taprio_have_broken_mqprio);
else
static_branch_inc(&taprio_have_working_mqprio);
q->detected_mqprio = true;
}
static void taprio_cleanup_broken_mqprio(struct taprio_sched *q)
{
if (!q->detected_mqprio)
return;
if (q->broken_mqprio)
static_branch_dec(&taprio_have_broken_mqprio);
else
static_branch_dec(&taprio_have_working_mqprio);
}
static int taprio_enable_offload(struct net_device *dev, static int taprio_enable_offload(struct net_device *dev,
struct taprio_sched *q, struct taprio_sched *q,
struct sched_gate_list *sched, struct sched_gate_list *sched,
...@@ -1538,10 +1641,12 @@ static int taprio_change(struct Qdisc *sch, struct nlattr *opt, ...@@ -1538,10 +1641,12 @@ static int taprio_change(struct Qdisc *sch, struct nlattr *opt,
err = netdev_set_num_tc(dev, mqprio->num_tc); err = netdev_set_num_tc(dev, mqprio->num_tc);
if (err) if (err)
goto free_sched; goto free_sched;
for (i = 0; i < mqprio->num_tc; i++) for (i = 0; i < mqprio->num_tc; i++) {
netdev_set_tc_queue(dev, i, netdev_set_tc_queue(dev, i,
mqprio->count[i], mqprio->count[i],
mqprio->offset[i]); mqprio->offset[i]);
q->cur_txq[i] = mqprio->offset[i];
}
/* Always use supplied priority mappings */ /* Always use supplied priority mappings */
for (i = 0; i <= TC_BITMASK; i++) for (i = 0; i <= TC_BITMASK; i++)
...@@ -1676,6 +1781,8 @@ static void taprio_destroy(struct Qdisc *sch) ...@@ -1676,6 +1781,8 @@ static void taprio_destroy(struct Qdisc *sch)
if (admin) if (admin)
call_rcu(&admin->rcu, taprio_free_sched_cb); call_rcu(&admin->rcu, taprio_free_sched_cb);
taprio_cleanup_broken_mqprio(q);
} }
static int taprio_init(struct Qdisc *sch, struct nlattr *opt, static int taprio_init(struct Qdisc *sch, struct nlattr *opt,
...@@ -1740,6 +1847,8 @@ static int taprio_init(struct Qdisc *sch, struct nlattr *opt, ...@@ -1740,6 +1847,8 @@ static int taprio_init(struct Qdisc *sch, struct nlattr *opt,
q->qdiscs[i] = qdisc; q->qdiscs[i] = qdisc;
} }
taprio_detect_broken_mqprio(q);
return taprio_change(sch, opt, extack); return taprio_change(sch, opt, extack);
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment