History

taprio: Add support for txtime-assist mode

Currently, we are seeing non-critical packets being transmitted outside of their timeslice. We can confirm that the packets are being dequeued at the right time. So, the delay is induced in the hardware side. The most likely reason is the hardware queues are starving the lower priority queues. In order to improve the performance of taprio, we will be making use of the txtime feature provided by the ETF qdisc. For all the packets which do not have the SO_TXTIME option set, taprio will set the transmit timestamp (set in skb->tstamp) in this mode. TAPrio Qdisc will ensure that the transmit time for the packet is set to when the gate is open. If SO_TXTIME is set, the TAPrio qdisc will validate whether the timestamp (in skb->tstamp) occurs when the gate corresponding to skb's traffic class is open. Following two parameters added to support this mode: - flags: used to enable txtime-assist mode. Will also be used to enable other modes (like hardware offloading) later. - txtime-delay: This indicates the minimum time it will take for the packet to hit the wire. This is useful in determining whether we can transmit the packet in the remaining time if the gate corresponding to the packet is currently open. An example configuration for enabling txtime-assist: tc qdisc replace dev eth0 parent root handle 100 taprio \\ num_tc 3 \\ map 2 2 1 0 2 2 2 2 2 2 2 2 2 2 2 2 \\ queues 1@0 1@0 1@0 \\ base-time 1558653424279842568 \\ sched-entry S 01 300000 \\ sched-entry S 02 300000 \\ sched-entry S 04 400000 \\ flags 0x1 \\ txtime-delay 40000 \\ clockid CLOCK_TAI tc qdisc replace dev $IFACE parent 100:1 etf skip_sock_check \\ offload delta 200000 clockid CLOCK_TAI Note that all the traffic classes are mapped to the same queue. This is only possible in taprio when txtime-assist is enabled. Also, note that the ETF Qdisc is enabled with offload mode set. In this mode, if the packet's traffic class is open and the complete packet can be transmitted, taprio will try to transmit the packet immediately. This will be done by setting skb->tstamp to current_time + the time delta indicated in the txtime-delay parameter. This parameter indicates the time taken (in software) for packet to reach the network adapter. If the packet cannot be transmitted in the current interval or if the packet's traffic is not currently transmitting, the skb->tstamp is set to the next available timestamp value. This is tracked in the next_launchtime parameter in the struct sched_entry. The behaviour w.r.t admin and oper schedules is not changed from what is present in software mode. The transmit time is already known in advance. So, we do not need the HR timers to advance the schedule and wakeup the dequeue side of taprio. So, HR timer won't be run when this mode is enabled.

Vedang Patel committed 7 years ago

Commit 4cfd5771 parent 566af332 files · +328 −17Browse files

Unified · Split

2 files
  1. include/uapi/linux/pkt_sched.h +4 −0
  2. net/sched/sch_taprio.c +324 −17
include/uapi/linux/pkt_sched.h+4−0
@@ -1159,6 +1159,8 @@ enum { · 1158 unmodified lines
11591159 * [TCA_TAPRIO_ATTR_SCHED_ENTRY_INTERVAL]
11601160 */
11611161
1162+#define TCA_TAPRIO_ATTR_FLAG_TXTIME_ASSIST 0x1
1163+
11621164 enum {
11631165 TCA_TAPRIO_ATTR_UNSPEC,
11641166 TCA_TAPRIO_ATTR_PRIOMAP, /* struct tc_mqprio_qopt */
@@ -1170,6 +1172,8 @@ enum { · 5 unmodified lines
11701172 TCA_TAPRIO_ATTR_ADMIN_SCHED, /* The admin sched, only used in dump */
11711173 TCA_TAPRIO_ATTR_SCHED_CYCLE_TIME, /* s64 */
11721174 TCA_TAPRIO_ATTR_SCHED_CYCLE_TIME_EXTENSION, /* s64 */
1175+ TCA_TAPRIO_ATTR_FLAGS, /* u32 */
1176+ TCA_TAPRIO_ATTR_TXTIME_DELAY, /* s32 */
11731177 __TCA_TAPRIO_ATTR_MAX,
11741178 };
11751179
net/sched/sch_taprio.c+324−17
@@ -21,12 +21,16 @@ · 20 unmodified lines
2121 #include <net/pkt_sched.h>
2222 #include <net/pkt_cls.h>
2323 #include <net/sch_generic.h>
24+#include <net/sock.h>
2425
2526 static LIST_HEAD(taprio_list);
2627 static DEFINE_SPINLOCK(taprio_list_lock);
2728
2829 #define TAPRIO_ALL_GATES_OPEN -1
2930
31+#define FLAGS_VALID(flags) (!((flags) & ~TCA_TAPRIO_ATTR_FLAG_TXTIME_ASSIST))
32+#define TXTIME_ASSIST_IS_ENABLED(flags) ((flags) & TCA_TAPRIO_ATTR_FLAG_TXTIME_ASSIST)
33+
3034 struct sched_entry {
3135 struct list_head list;
3236
@@ -35,6 +39,7 @@ struct sched_entry { · 2 unmodified lines
3539 * packet leaves after this time.
3640 */
3741 ktime_t close_time;
42+ ktime_t next_txtime;
3843 atomic_t budget;
3944 int index;
4045 u32 gate_mask;
@@ -55,6 +60,7 @@ struct sched_gate_list { · 14 unmodified lines
5560 struct taprio_sched {
5661 struct Qdisc **qdiscs;
5762 struct Qdisc *root;
63+ u32 flags;
5864 int clockid;
5965 atomic64_t picos_per_byte; /* Using picoseconds because for 10Gbps+
6066 * speeds it's sub-nanoseconds per byte
@@ -68,6 +74,7 @@ struct taprio_sched { · 7 unmodified lines
6874 ktime_t (*get_time)(void);
6975 struct hrtimer advance_timer;
7076 struct list_head taprio_list;
77+ int txtime_delay;
7178 };
7279
7380 static ktime_t sched_base_time(const struct sched_gate_list *sched)
@@ -108,6 +115,227 @@ static void switch_schedules(struct taprio_sched *q, · 34 unmodified lines
108115 *admin = NULL;
109116 }
110117
118+/* Get how much time has been already elapsed in the current cycle. */
119+static s32 get_cycle_time_elapsed(struct sched_gate_list *sched, ktime_t time)
120+{
121+ ktime_t time_since_sched_start;
122+ s32 time_elapsed;
123+
124+ time_since_sched_start = ktime_sub(time, sched->base_time);
125+ div_s64_rem(time_since_sched_start, sched->cycle_time, &time_elapsed);
126+
127+ return time_elapsed;
128+}
129+
130+static ktime_t get_interval_end_time(struct sched_gate_list *sched,
131+ struct sched_gate_list *admin,
132+ struct sched_entry *entry,
133+ ktime_t intv_start)
134+{
135+ s32 cycle_elapsed = get_cycle_time_elapsed(sched, intv_start);
136+ ktime_t intv_end, cycle_ext_end, cycle_end;
137+
138+ cycle_end = ktime_add_ns(intv_start, sched->cycle_time - cycle_elapsed);
139+ intv_end = ktime_add_ns(intv_start, entry->interval);
140+ cycle_ext_end = ktime_add(cycle_end, sched->cycle_time_extension);
141+
142+ if (ktime_before(intv_end, cycle_end))
143+ return intv_end;
144+ else if (admin && admin != sched &&
145+ ktime_after(admin->base_time, cycle_end) &&
146+ ktime_before(admin->base_time, cycle_ext_end))
147+ return admin->base_time;
148+ else
149+ return cycle_end;
150+}
151+
152+static int length_to_duration(struct taprio_sched *q, int len)
153+{
154+ return div_u64(len * atomic64_read(&q->picos_per_byte), 1000);
155+}
156+
157+/* Returns the entry corresponding to next available interval. If
158+ * validate_interval is set, it only validates whether the timestamp occurs
159+ * when the gate corresponding to the skb's traffic class is open.
160+ */
161+static struct sched_entry *find_entry_to_transmit(struct sk_buff *skb,
162+ struct Qdisc *sch,
163+ struct sched_gate_list *sched,
164+ struct sched_gate_list *admin,
165+ ktime_t time,
166+ ktime_t *interval_start,
167+ ktime_t *interval_end,
168+ bool validate_interval)
169+{
170+ ktime_t curr_intv_start, curr_intv_end, cycle_end, packet_transmit_time;
171+ ktime_t earliest_txtime = KTIME_MAX, txtime, cycle, transmit_end_time;
172+ struct sched_entry *entry = NULL, *entry_found = NULL;
173+ struct taprio_sched *q = qdisc_priv(sch);
174+ struct net_device *dev = qdisc_dev(sch);
175+ bool entry_available = false;
176+ s32 cycle_elapsed;
177+ int tc, n;
178+
179+ tc = netdev_get_prio_tc_map(dev, skb->priority);
180+ packet_transmit_time = length_to_duration(q, qdisc_pkt_len(skb));
181+
182+ *interval_start = 0;
183+ *interval_end = 0;
184+
185+ if (!sched)
186+ return NULL;
187+
188+ cycle = sched->cycle_time;
189+ cycle_elapsed = get_cycle_time_elapsed(sched, time);
190+ curr_intv_end = ktime_sub_ns(time, cycle_elapsed);
191+ cycle_end = ktime_add_ns(curr_intv_end, cycle);
192+
193+ list_for_each_entry(entry, &sched->entries, list) {
194+ curr_intv_start = curr_intv_end;
195+ curr_intv_end = get_interval_end_time(sched, admin, entry,
196+ curr_intv_start);
197+
198+ if (ktime_after(curr_intv_start, cycle_end))
199+ break;
200+
201+ if (!(entry->gate_mask & BIT(tc)) ||
202+ packet_transmit_time > entry->interval)
203+ continue;
204+
205+ txtime = entry->next_txtime;
206+
207+ if (ktime_before(txtime, time) || validate_interval) {
208+ transmit_end_time = ktime_add_ns(time, packet_transmit_time);
209+ if ((ktime_before(curr_intv_start, time) &&
210+ ktime_before(transmit_end_time, curr_intv_end)) ||
211+ (ktime_after(curr_intv_start, time) && !validate_interval)) {
212+ entry_found = entry;
213+ *interval_start = curr_intv_start;
214+ *interval_end = curr_intv_end;
215+ break;
216+ } else if (!entry_available && !validate_interval) {
217+ /* Here, we are just trying to find out the
218+ * first available interval in the next cycle.
219+ */
220+ entry_available = 1;
221+ entry_found = entry;
222+ *interval_start = ktime_add_ns(curr_intv_start, cycle);
223+ *interval_end = ktime_add_ns(curr_intv_end, cycle);
224+ }
225+ } else if (ktime_before(txtime, earliest_txtime) &&
226+ !entry_available) {
227+ earliest_txtime = txtime;
228+ entry_found = entry;
229+ n = div_s64(ktime_sub(txtime, curr_intv_start), cycle);
230+ *interval_start = ktime_add(curr_intv_start, n * cycle);
231+ *interval_end = ktime_add(curr_intv_end, n * cycle);
232+ }
233+ }
234+
235+ return entry_found;
236+}
237+
238+static bool is_valid_interval(struct sk_buff *skb, struct Qdisc *sch)
239+{
240+ struct taprio_sched *q = qdisc_priv(sch);
241+ struct sched_gate_list *sched, *admin;
242+ ktime_t interval_start, interval_end;
243+ struct sched_entry *entry;
244+
245+ rcu_read_lock();
246+ sched = rcu_dereference(q->oper_sched);
247+ admin = rcu_dereference(q->admin_sched);
248+
249+ entry = find_entry_to_transmit(skb, sch, sched, admin, skb->tstamp,
250+ &interval_start, &interval_end, true);
251+ rcu_read_unlock();
252+
253+ return entry;
254+}
255+
256+/* There are a few scenarios where we will have to modify the txtime from
257+ * what is read from next_txtime in sched_entry. They are:
258+ * 1. If txtime is in the past,
259+ * a. The gate for the traffic class is currently open and packet can be
260+ * transmitted before it closes, schedule the packet right away.
261+ * b. If the gate corresponding to the traffic class is going to open later
262+ * in the cycle, set the txtime of packet to the interval start.
263+ * 2. If txtime is in the future, there are packets corresponding to the
264+ * current traffic class waiting to be transmitted. So, the following
265+ * possibilities exist:
266+ * a. We can transmit the packet before the window containing the txtime
267+ * closes.
268+ * b. The window might close before the transmission can be completed
269+ * successfully. So, schedule the packet in the next open window.
270+ */
271+static long get_packet_txtime(struct sk_buff *skb, struct Qdisc *sch)
272+{
273+ ktime_t transmit_end_time, interval_end, interval_start;
274+ struct taprio_sched *q = qdisc_priv(sch);
275+ struct sched_gate_list *sched, *admin;
276+ ktime_t minimum_time, now, txtime;
277+ int len, packet_transmit_time;
278+ struct sched_entry *entry;
279+ bool sched_changed;
280+
281+ now = q->get_time();
282+ minimum_time = ktime_add_ns(now, q->txtime_delay);
283+
284+ rcu_read_lock();
285+ admin = rcu_dereference(q->admin_sched);
286+ sched = rcu_dereference(q->oper_sched);
287+ if (admin && ktime_after(minimum_time, admin->base_time))
288+ switch_schedules(q, &admin, &sched);
289+
290+ /* Until the schedule starts, all the queues are open */
291+ if (!sched || ktime_before(minimum_time, sched->base_time)) {
292+ txtime = minimum_time;
293+ goto done;
294+ }
295+
296+ len = qdisc_pkt_len(skb);
297+ packet_transmit_time = length_to_duration(q, len);
298+
299+ do {
300+ sched_changed = 0;
301+
302+ entry = find_entry_to_transmit(skb, sch, sched, admin,
303+ minimum_time,
304+ &interval_start, &interval_end,
305+ false);
306+ if (!entry) {
307+ txtime = 0;
308+ goto done;
309+ }
310+
311+ txtime = entry->next_txtime;
312+ txtime = max_t(ktime_t, txtime, minimum_time);
313+ txtime = max_t(ktime_t, txtime, interval_start);
314+
315+ if (admin && admin != sched &&
316+ ktime_after(txtime, admin->base_time)) {
317+ sched = admin;
318+ sched_changed = 1;
319+ continue;
320+ }
321+
322+ transmit_end_time = ktime_add(txtime, packet_transmit_time);
323+ minimum_time = transmit_end_time;
324+
325+ /* Update the txtime of current entry to the next time it's
326+ * interval starts.
327+ */
328+ if (ktime_after(transmit_end_time, interval_end))
329+ entry->next_txtime = ktime_add(interval_start, sched->cycle_time);
330+ } while (sched_changed || ktime_after(transmit_end_time, interval_end));
331+
332+ entry->next_txtime = transmit_end_time;
333+
334+done:
335+ rcu_read_unlock();
336+ return txtime;
337+}
338+
111339 static int taprio_enqueue(struct sk_buff *skb, struct Qdisc *sch,
112340 struct sk_buff **to_free)
113341 {
@@ -121,6 +349,15 @@ static int taprio_enqueue(struct sk_buff *skb, struct Qdisc *sch, · 7 unmodified lines
121349 if (unlikely(!child))
122350 return qdisc_drop(skb, sch, to_free);
123351
352+ if (skb->sk && sock_flag(skb->sk, SOCK_TXTIME)) {
353+ if (!is_valid_interval(skb, sch))
354+ return qdisc_drop(skb, sch, to_free);
355+ } else if (TXTIME_ASSIST_IS_ENABLED(q->flags)) {
356+ skb->tstamp = get_packet_txtime(skb, sch);
357+ if (!skb->tstamp)
358+ return qdisc_drop(skb, sch, to_free);
359+ }
360+
124361 qdisc_qstats_backlog_inc(sch, skb);
125362 sch->q.qlen++;
126363
@@ -156,6 +393,9 @@ static struct sk_buff *taprio_peek(struct Qdisc *sch) · 29 unmodified lines
156393 if (!skb)
157394 continue;
158395
396+ if (TXTIME_ASSIST_IS_ENABLED(q->flags))
397+ return skb;
398+
159399 prio = skb->priority;
160400 tc = netdev_get_prio_tc_map(dev, prio);
161401
@@ -168,11 +408,6 @@ static struct sk_buff *taprio_peek(struct Qdisc *sch) · 6 unmodified lines
168408 return NULL;
169409 }
170410
171−static int length_to_duration(struct taprio_sched *q, int len)
172−{
173− return div_u64(len * atomic64_read(&q->picos_per_byte), 1000);
174−}
175−
176411 static void taprio_set_budget(struct taprio_sched *q, struct sched_entry *entry)
177412 {
178413 atomic_set(&entry->budget,
@@ -216,6 +451,13 @@ static struct sk_buff *taprio_dequeue(struct Qdisc *sch) · 37 unmodified lines
216451 if (unlikely(!child))
217452 continue;
218453
454+ if (TXTIME_ASSIST_IS_ENABLED(q->flags)) {
455+ skb = child->ops->dequeue(child);
456+ if (!skb)
457+ continue;
458+ goto skb_found;
459+ }
460+
219461 skb = child->ops->peek(child);
220462 if (!skb)
221463 continue;
@@ -246,6 +488,7 @@ static struct sk_buff *taprio_dequeue(struct Qdisc *sch) · 24 unmodified lines
246488 if (unlikely(!skb))
247489 goto done;
248490
491+skb_found:
249492 qdisc_bstats_update(sch, skb);
250493 qdisc_qstats_backlog_dec(sch, skb);
251494 sch->q.qlen--;
@@ -522,7 +765,8 @@ static int parse_taprio_schedule(struct nlattr **tb, · 270 unmodified lines
522765
523766 static int taprio_parse_mqprio_opt(struct net_device *dev,
524767 struct tc_mqprio_qopt *qopt,
525− struct netlink_ext_ack *extack)
768+ struct netlink_ext_ack *extack,
769+ u32 taprio_flags)
526770 {
527771 int i, j;
528772
@@ -570,6 +814,9 @@ static int taprio_parse_mqprio_opt(struct net_device *dev, · 41 unmodified lines
570814 return -EINVAL;
571815 }
572816
817+ if (TXTIME_ASSIST_IS_ENABLED(taprio_flags))
818+ continue;
819+
573820 /* Verify that the offset and counts do not overlap */
574821 for (j = i + 1; j < qopt->num_tc; j++) {
575822 if (last > qopt->offset[j]) {
@@ -700,6 +947,18 @@ static int taprio_dev_notifier(struct notifier_block *nb, unsigned long event, · 124 unmodified lines
700947 return NOTIFY_DONE;
701948 }
702949
950+static void setup_txtime(struct taprio_sched *q,
951+ struct sched_gate_list *sched, ktime_t base)
952+{
953+ struct sched_entry *entry;
954+ u32 interval = 0;
955+
956+ list_for_each_entry(entry, &sched->entries, list) {
957+ entry->next_txtime = ktime_add_ns(base, interval);
958+ interval += entry->interval;
959+ }
960+}
961+
703962 static int taprio_change(struct Qdisc *sch, struct nlattr *opt,
704963 struct netlink_ext_ack *extack)
705964 {
@@ -708,6 +967,7 @@ static int taprio_change(struct Qdisc *sch, struct nlattr *opt, · 2 unmodified lines
708967 struct taprio_sched *q = qdisc_priv(sch);
709968 struct net_device *dev = qdisc_dev(sch);
710969 struct tc_mqprio_qopt *mqprio = NULL;
970+ u32 taprio_flags = 0;
711971 int i, err, clockid;
712972 unsigned long flags;
713973 ktime_t start;
@@ -720,7 +980,21 @@ static int taprio_change(struct Qdisc *sch, struct nlattr *opt, · 6 unmodified lines
720980 if (tb[TCA_TAPRIO_ATTR_PRIOMAP])
721981 mqprio = nla_data(tb[TCA_TAPRIO_ATTR_PRIOMAP]);
722982
723− err = taprio_parse_mqprio_opt(dev, mqprio, extack);
983+ if (tb[TCA_TAPRIO_ATTR_FLAGS]) {
984+ taprio_flags = nla_get_u32(tb[TCA_TAPRIO_ATTR_FLAGS]);
985+
986+ if (q->flags != 0 && q->flags != taprio_flags) {
987+ NL_SET_ERR_MSG_MOD(extack, "Changing 'flags' of a running schedule is not supported");
988+ return -EOPNOTSUPP;
989+ } else if (!FLAGS_VALID(taprio_flags)) {
990+ NL_SET_ERR_MSG_MOD(extack, "Specified 'flags' are not valid");
991+ return -EINVAL;
992+ }
993+
994+ q->flags = taprio_flags;
995+ }
996+
997+ err = taprio_parse_mqprio_opt(dev, mqprio, extack, taprio_flags);
724998 if (err < 0)
725999 return err;
7261000
@@ -779,7 +1053,18 @@ static int taprio_change(struct Qdisc *sch, struct nlattr *opt, · 52 unmodified lines
7791053 /* Protects against enqueue()/dequeue() */
7801054 spin_lock_bh(qdisc_lock(sch));
7811055
782− if (!hrtimer_active(&q->advance_timer)) {
1056+ if (tb[TCA_TAPRIO_ATTR_TXTIME_DELAY]) {
1057+ if (!TXTIME_ASSIST_IS_ENABLED(q->flags)) {
1058+ NL_SET_ERR_MSG_MOD(extack, "txtime-delay can only be set when txtime-assist mode is enabled");
1059+ err = -EINVAL;
1060+ goto unlock;
1061+ }
1062+
1063+ q->txtime_delay = nla_get_s32(tb[TCA_TAPRIO_ATTR_TXTIME_DELAY]);
1064+ }
1065+
1066+ if (!TXTIME_ASSIST_IS_ENABLED(taprio_flags) &&
1067+ !hrtimer_active(&q->advance_timer)) {
7831068 hrtimer_init(&q->advance_timer, q->clockid, HRTIMER_MODE_ABS);
7841069 q->advance_timer.function = advance_sched;
7851070 }
@@ -822,20 +1107,35 @@ static int taprio_change(struct Qdisc *sch, struct nlattr *opt, · 36 unmodified lines
8221107 goto unlock;
8231108 }
8241109
825− setup_first_close_time(q, new_admin, start);
1110+ if (TXTIME_ASSIST_IS_ENABLED(taprio_flags)) {
1111+ setup_txtime(q, new_admin, start);
8261112
827− /* Protects against advance_sched() */
828− spin_lock_irqsave(&q->current_entry_lock, flags);
1113+ if (!oper) {
1114+ rcu_assign_pointer(q->oper_sched, new_admin);
1115+ err = 0;
1116+ new_admin = NULL;
1117+ goto unlock;
1118+ }
8291119
830− taprio_start_sched(sch, start, new_admin);
1120+ rcu_assign_pointer(q->admin_sched, new_admin);
1121+ if (admin)
1122+ call_rcu(&admin->rcu, taprio_free_sched_cb);
1123+ } else {
1124+ setup_first_close_time(q, new_admin, start);
8311125
832− rcu_assign_pointer(q->admin_sched, new_admin);
833− if (admin)
834− call_rcu(&admin->rcu, taprio_free_sched_cb);
835− new_admin = NULL;
1126+ /* Protects against advance_sched() */
1127+ spin_lock_irqsave(&q->current_entry_lock, flags);
1128+
1129+ taprio_start_sched(sch, start, new_admin);
8361130
837− spin_unlock_irqrestore(&q->current_entry_lock, flags);
1131+ rcu_assign_pointer(q->admin_sched, new_admin);
1132+ if (admin)
1133+ call_rcu(&admin->rcu, taprio_free_sched_cb);
8381134
1135+ spin_unlock_irqrestore(&q->current_entry_lock, flags);
1136+ }
1137+
1138+ new_admin = NULL;
8391139 err = 0;
8401140
8411141 unlock:
@@ -1073,6 +1373,13 @@ static int taprio_dump(struct Qdisc *sch, struct sk_buff *skb) · 231 unmodified lines
10731373 if (nla_put_s32(skb, TCA_TAPRIO_ATTR_SCHED_CLOCKID, q->clockid))
10741374 goto options_error;
10751375
1376+ if (q->flags && nla_put_u32(skb, TCA_TAPRIO_ATTR_FLAGS, q->flags))
1377+ goto options_error;
1378+
1379+ if (q->txtime_delay &&
1380+ nla_put_s32(skb, TCA_TAPRIO_ATTR_TXTIME_DELAY, q->txtime_delay))
1381+ goto options_error;
1382+
10761383 if (oper && dump_schedule(skb, oper))
10771384 goto options_error;
10781385