162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 262306a36Sopenharmony_ci 362306a36Sopenharmony_ci/* net/sched/sch_taprio.c Time Aware Priority Scheduler 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Authors: Vinicius Costa Gomes <vinicius.gomes@intel.com> 662306a36Sopenharmony_ci * 762306a36Sopenharmony_ci */ 862306a36Sopenharmony_ci 962306a36Sopenharmony_ci#include <linux/ethtool.h> 1062306a36Sopenharmony_ci#include <linux/ethtool_netlink.h> 1162306a36Sopenharmony_ci#include <linux/types.h> 1262306a36Sopenharmony_ci#include <linux/slab.h> 1362306a36Sopenharmony_ci#include <linux/kernel.h> 1462306a36Sopenharmony_ci#include <linux/string.h> 1562306a36Sopenharmony_ci#include <linux/list.h> 1662306a36Sopenharmony_ci#include <linux/errno.h> 1762306a36Sopenharmony_ci#include <linux/skbuff.h> 1862306a36Sopenharmony_ci#include <linux/math64.h> 1962306a36Sopenharmony_ci#include <linux/module.h> 2062306a36Sopenharmony_ci#include <linux/spinlock.h> 2162306a36Sopenharmony_ci#include <linux/rcupdate.h> 2262306a36Sopenharmony_ci#include <linux/time.h> 2362306a36Sopenharmony_ci#include <net/gso.h> 2462306a36Sopenharmony_ci#include <net/netlink.h> 2562306a36Sopenharmony_ci#include <net/pkt_sched.h> 2662306a36Sopenharmony_ci#include <net/pkt_cls.h> 2762306a36Sopenharmony_ci#include <net/sch_generic.h> 2862306a36Sopenharmony_ci#include <net/sock.h> 2962306a36Sopenharmony_ci#include <net/tcp.h> 3062306a36Sopenharmony_ci 3162306a36Sopenharmony_ci#define TAPRIO_STAT_NOT_SET (~0ULL) 3262306a36Sopenharmony_ci 3362306a36Sopenharmony_ci#include "sch_mqprio_lib.h" 3462306a36Sopenharmony_ci 3562306a36Sopenharmony_cistatic LIST_HEAD(taprio_list); 3662306a36Sopenharmony_cistatic struct static_key_false taprio_have_broken_mqprio; 3762306a36Sopenharmony_cistatic struct static_key_false taprio_have_working_mqprio; 3862306a36Sopenharmony_ci 3962306a36Sopenharmony_ci#define TAPRIO_ALL_GATES_OPEN -1 4062306a36Sopenharmony_ci 4162306a36Sopenharmony_ci#define TXTIME_ASSIST_IS_ENABLED(flags) ((flags) & TCA_TAPRIO_ATTR_FLAG_TXTIME_ASSIST) 4262306a36Sopenharmony_ci#define FULL_OFFLOAD_IS_ENABLED(flags) ((flags) & TCA_TAPRIO_ATTR_FLAG_FULL_OFFLOAD) 4362306a36Sopenharmony_ci#define TAPRIO_FLAGS_INVALID U32_MAX 4462306a36Sopenharmony_ci 4562306a36Sopenharmony_cistruct sched_entry { 4662306a36Sopenharmony_ci /* Durations between this GCL entry and the GCL entry where the 4762306a36Sopenharmony_ci * respective traffic class gate closes 4862306a36Sopenharmony_ci */ 4962306a36Sopenharmony_ci u64 gate_duration[TC_MAX_QUEUE]; 5062306a36Sopenharmony_ci atomic_t budget[TC_MAX_QUEUE]; 5162306a36Sopenharmony_ci /* The qdisc makes some effort so that no packet leaves 5262306a36Sopenharmony_ci * after this time 5362306a36Sopenharmony_ci */ 5462306a36Sopenharmony_ci ktime_t gate_close_time[TC_MAX_QUEUE]; 5562306a36Sopenharmony_ci struct list_head list; 5662306a36Sopenharmony_ci /* Used to calculate when to advance the schedule */ 5762306a36Sopenharmony_ci ktime_t end_time; 5862306a36Sopenharmony_ci ktime_t next_txtime; 5962306a36Sopenharmony_ci int index; 6062306a36Sopenharmony_ci u32 gate_mask; 6162306a36Sopenharmony_ci u32 interval; 6262306a36Sopenharmony_ci u8 command; 6362306a36Sopenharmony_ci}; 6462306a36Sopenharmony_ci 6562306a36Sopenharmony_cistruct sched_gate_list { 6662306a36Sopenharmony_ci /* Longest non-zero contiguous gate durations per traffic class, 6762306a36Sopenharmony_ci * or 0 if a traffic class gate never opens during the schedule. 6862306a36Sopenharmony_ci */ 6962306a36Sopenharmony_ci u64 max_open_gate_duration[TC_MAX_QUEUE]; 7062306a36Sopenharmony_ci u32 max_frm_len[TC_MAX_QUEUE]; /* for the fast path */ 7162306a36Sopenharmony_ci u32 max_sdu[TC_MAX_QUEUE]; /* for dump */ 7262306a36Sopenharmony_ci struct rcu_head rcu; 7362306a36Sopenharmony_ci struct list_head entries; 7462306a36Sopenharmony_ci size_t num_entries; 7562306a36Sopenharmony_ci ktime_t cycle_end_time; 7662306a36Sopenharmony_ci s64 cycle_time; 7762306a36Sopenharmony_ci s64 cycle_time_extension; 7862306a36Sopenharmony_ci s64 base_time; 7962306a36Sopenharmony_ci}; 8062306a36Sopenharmony_ci 8162306a36Sopenharmony_cistruct taprio_sched { 8262306a36Sopenharmony_ci struct Qdisc **qdiscs; 8362306a36Sopenharmony_ci struct Qdisc *root; 8462306a36Sopenharmony_ci u32 flags; 8562306a36Sopenharmony_ci enum tk_offsets tk_offset; 8662306a36Sopenharmony_ci int clockid; 8762306a36Sopenharmony_ci bool offloaded; 8862306a36Sopenharmony_ci bool detected_mqprio; 8962306a36Sopenharmony_ci bool broken_mqprio; 9062306a36Sopenharmony_ci atomic64_t picos_per_byte; /* Using picoseconds because for 10Gbps+ 9162306a36Sopenharmony_ci * speeds it's sub-nanoseconds per byte 9262306a36Sopenharmony_ci */ 9362306a36Sopenharmony_ci 9462306a36Sopenharmony_ci /* Protects the update side of the RCU protected current_entry */ 9562306a36Sopenharmony_ci spinlock_t current_entry_lock; 9662306a36Sopenharmony_ci struct sched_entry __rcu *current_entry; 9762306a36Sopenharmony_ci struct sched_gate_list __rcu *oper_sched; 9862306a36Sopenharmony_ci struct sched_gate_list __rcu *admin_sched; 9962306a36Sopenharmony_ci struct hrtimer advance_timer; 10062306a36Sopenharmony_ci struct list_head taprio_list; 10162306a36Sopenharmony_ci int cur_txq[TC_MAX_QUEUE]; 10262306a36Sopenharmony_ci u32 max_sdu[TC_MAX_QUEUE]; /* save info from the user */ 10362306a36Sopenharmony_ci u32 fp[TC_QOPT_MAX_QUEUE]; /* only for dump and offloading */ 10462306a36Sopenharmony_ci u32 txtime_delay; 10562306a36Sopenharmony_ci}; 10662306a36Sopenharmony_ci 10762306a36Sopenharmony_cistruct __tc_taprio_qopt_offload { 10862306a36Sopenharmony_ci refcount_t users; 10962306a36Sopenharmony_ci struct tc_taprio_qopt_offload offload; 11062306a36Sopenharmony_ci}; 11162306a36Sopenharmony_ci 11262306a36Sopenharmony_cistatic void taprio_calculate_gate_durations(struct taprio_sched *q, 11362306a36Sopenharmony_ci struct sched_gate_list *sched) 11462306a36Sopenharmony_ci{ 11562306a36Sopenharmony_ci struct net_device *dev = qdisc_dev(q->root); 11662306a36Sopenharmony_ci int num_tc = netdev_get_num_tc(dev); 11762306a36Sopenharmony_ci struct sched_entry *entry, *cur; 11862306a36Sopenharmony_ci int tc; 11962306a36Sopenharmony_ci 12062306a36Sopenharmony_ci list_for_each_entry(entry, &sched->entries, list) { 12162306a36Sopenharmony_ci u32 gates_still_open = entry->gate_mask; 12262306a36Sopenharmony_ci 12362306a36Sopenharmony_ci /* For each traffic class, calculate each open gate duration, 12462306a36Sopenharmony_ci * starting at this schedule entry and ending at the schedule 12562306a36Sopenharmony_ci * entry containing a gate close event for that TC. 12662306a36Sopenharmony_ci */ 12762306a36Sopenharmony_ci cur = entry; 12862306a36Sopenharmony_ci 12962306a36Sopenharmony_ci do { 13062306a36Sopenharmony_ci if (!gates_still_open) 13162306a36Sopenharmony_ci break; 13262306a36Sopenharmony_ci 13362306a36Sopenharmony_ci for (tc = 0; tc < num_tc; tc++) { 13462306a36Sopenharmony_ci if (!(gates_still_open & BIT(tc))) 13562306a36Sopenharmony_ci continue; 13662306a36Sopenharmony_ci 13762306a36Sopenharmony_ci if (cur->gate_mask & BIT(tc)) 13862306a36Sopenharmony_ci entry->gate_duration[tc] += cur->interval; 13962306a36Sopenharmony_ci else 14062306a36Sopenharmony_ci gates_still_open &= ~BIT(tc); 14162306a36Sopenharmony_ci } 14262306a36Sopenharmony_ci 14362306a36Sopenharmony_ci cur = list_next_entry_circular(cur, &sched->entries, list); 14462306a36Sopenharmony_ci } while (cur != entry); 14562306a36Sopenharmony_ci 14662306a36Sopenharmony_ci /* Keep track of the maximum gate duration for each traffic 14762306a36Sopenharmony_ci * class, taking care to not confuse a traffic class which is 14862306a36Sopenharmony_ci * temporarily closed with one that is always closed. 14962306a36Sopenharmony_ci */ 15062306a36Sopenharmony_ci for (tc = 0; tc < num_tc; tc++) 15162306a36Sopenharmony_ci if (entry->gate_duration[tc] && 15262306a36Sopenharmony_ci sched->max_open_gate_duration[tc] < entry->gate_duration[tc]) 15362306a36Sopenharmony_ci sched->max_open_gate_duration[tc] = entry->gate_duration[tc]; 15462306a36Sopenharmony_ci } 15562306a36Sopenharmony_ci} 15662306a36Sopenharmony_ci 15762306a36Sopenharmony_cistatic bool taprio_entry_allows_tx(ktime_t skb_end_time, 15862306a36Sopenharmony_ci struct sched_entry *entry, int tc) 15962306a36Sopenharmony_ci{ 16062306a36Sopenharmony_ci return ktime_before(skb_end_time, entry->gate_close_time[tc]); 16162306a36Sopenharmony_ci} 16262306a36Sopenharmony_ci 16362306a36Sopenharmony_cistatic ktime_t sched_base_time(const struct sched_gate_list *sched) 16462306a36Sopenharmony_ci{ 16562306a36Sopenharmony_ci if (!sched) 16662306a36Sopenharmony_ci return KTIME_MAX; 16762306a36Sopenharmony_ci 16862306a36Sopenharmony_ci return ns_to_ktime(sched->base_time); 16962306a36Sopenharmony_ci} 17062306a36Sopenharmony_ci 17162306a36Sopenharmony_cistatic ktime_t taprio_mono_to_any(const struct taprio_sched *q, ktime_t mono) 17262306a36Sopenharmony_ci{ 17362306a36Sopenharmony_ci /* This pairs with WRITE_ONCE() in taprio_parse_clockid() */ 17462306a36Sopenharmony_ci enum tk_offsets tk_offset = READ_ONCE(q->tk_offset); 17562306a36Sopenharmony_ci 17662306a36Sopenharmony_ci switch (tk_offset) { 17762306a36Sopenharmony_ci case TK_OFFS_MAX: 17862306a36Sopenharmony_ci return mono; 17962306a36Sopenharmony_ci default: 18062306a36Sopenharmony_ci return ktime_mono_to_any(mono, tk_offset); 18162306a36Sopenharmony_ci } 18262306a36Sopenharmony_ci} 18362306a36Sopenharmony_ci 18462306a36Sopenharmony_cistatic ktime_t taprio_get_time(const struct taprio_sched *q) 18562306a36Sopenharmony_ci{ 18662306a36Sopenharmony_ci return taprio_mono_to_any(q, ktime_get()); 18762306a36Sopenharmony_ci} 18862306a36Sopenharmony_ci 18962306a36Sopenharmony_cistatic void taprio_free_sched_cb(struct rcu_head *head) 19062306a36Sopenharmony_ci{ 19162306a36Sopenharmony_ci struct sched_gate_list *sched = container_of(head, struct sched_gate_list, rcu); 19262306a36Sopenharmony_ci struct sched_entry *entry, *n; 19362306a36Sopenharmony_ci 19462306a36Sopenharmony_ci list_for_each_entry_safe(entry, n, &sched->entries, list) { 19562306a36Sopenharmony_ci list_del(&entry->list); 19662306a36Sopenharmony_ci kfree(entry); 19762306a36Sopenharmony_ci } 19862306a36Sopenharmony_ci 19962306a36Sopenharmony_ci kfree(sched); 20062306a36Sopenharmony_ci} 20162306a36Sopenharmony_ci 20262306a36Sopenharmony_cistatic void switch_schedules(struct taprio_sched *q, 20362306a36Sopenharmony_ci struct sched_gate_list **admin, 20462306a36Sopenharmony_ci struct sched_gate_list **oper) 20562306a36Sopenharmony_ci{ 20662306a36Sopenharmony_ci rcu_assign_pointer(q->oper_sched, *admin); 20762306a36Sopenharmony_ci rcu_assign_pointer(q->admin_sched, NULL); 20862306a36Sopenharmony_ci 20962306a36Sopenharmony_ci if (*oper) 21062306a36Sopenharmony_ci call_rcu(&(*oper)->rcu, taprio_free_sched_cb); 21162306a36Sopenharmony_ci 21262306a36Sopenharmony_ci *oper = *admin; 21362306a36Sopenharmony_ci *admin = NULL; 21462306a36Sopenharmony_ci} 21562306a36Sopenharmony_ci 21662306a36Sopenharmony_ci/* Get how much time has been already elapsed in the current cycle. */ 21762306a36Sopenharmony_cistatic s32 get_cycle_time_elapsed(struct sched_gate_list *sched, ktime_t time) 21862306a36Sopenharmony_ci{ 21962306a36Sopenharmony_ci ktime_t time_since_sched_start; 22062306a36Sopenharmony_ci s32 time_elapsed; 22162306a36Sopenharmony_ci 22262306a36Sopenharmony_ci time_since_sched_start = ktime_sub(time, sched->base_time); 22362306a36Sopenharmony_ci div_s64_rem(time_since_sched_start, sched->cycle_time, &time_elapsed); 22462306a36Sopenharmony_ci 22562306a36Sopenharmony_ci return time_elapsed; 22662306a36Sopenharmony_ci} 22762306a36Sopenharmony_ci 22862306a36Sopenharmony_cistatic ktime_t get_interval_end_time(struct sched_gate_list *sched, 22962306a36Sopenharmony_ci struct sched_gate_list *admin, 23062306a36Sopenharmony_ci struct sched_entry *entry, 23162306a36Sopenharmony_ci ktime_t intv_start) 23262306a36Sopenharmony_ci{ 23362306a36Sopenharmony_ci s32 cycle_elapsed = get_cycle_time_elapsed(sched, intv_start); 23462306a36Sopenharmony_ci ktime_t intv_end, cycle_ext_end, cycle_end; 23562306a36Sopenharmony_ci 23662306a36Sopenharmony_ci cycle_end = ktime_add_ns(intv_start, sched->cycle_time - cycle_elapsed); 23762306a36Sopenharmony_ci intv_end = ktime_add_ns(intv_start, entry->interval); 23862306a36Sopenharmony_ci cycle_ext_end = ktime_add(cycle_end, sched->cycle_time_extension); 23962306a36Sopenharmony_ci 24062306a36Sopenharmony_ci if (ktime_before(intv_end, cycle_end)) 24162306a36Sopenharmony_ci return intv_end; 24262306a36Sopenharmony_ci else if (admin && admin != sched && 24362306a36Sopenharmony_ci ktime_after(admin->base_time, cycle_end) && 24462306a36Sopenharmony_ci ktime_before(admin->base_time, cycle_ext_end)) 24562306a36Sopenharmony_ci return admin->base_time; 24662306a36Sopenharmony_ci else 24762306a36Sopenharmony_ci return cycle_end; 24862306a36Sopenharmony_ci} 24962306a36Sopenharmony_ci 25062306a36Sopenharmony_cistatic int length_to_duration(struct taprio_sched *q, int len) 25162306a36Sopenharmony_ci{ 25262306a36Sopenharmony_ci return div_u64(len * atomic64_read(&q->picos_per_byte), PSEC_PER_NSEC); 25362306a36Sopenharmony_ci} 25462306a36Sopenharmony_ci 25562306a36Sopenharmony_cistatic int duration_to_length(struct taprio_sched *q, u64 duration) 25662306a36Sopenharmony_ci{ 25762306a36Sopenharmony_ci return div_u64(duration * PSEC_PER_NSEC, atomic64_read(&q->picos_per_byte)); 25862306a36Sopenharmony_ci} 25962306a36Sopenharmony_ci 26062306a36Sopenharmony_ci/* Sets sched->max_sdu[] and sched->max_frm_len[] to the minimum between the 26162306a36Sopenharmony_ci * q->max_sdu[] requested by the user and the max_sdu dynamically determined by 26262306a36Sopenharmony_ci * the maximum open gate durations at the given link speed. 26362306a36Sopenharmony_ci */ 26462306a36Sopenharmony_cistatic void taprio_update_queue_max_sdu(struct taprio_sched *q, 26562306a36Sopenharmony_ci struct sched_gate_list *sched, 26662306a36Sopenharmony_ci struct qdisc_size_table *stab) 26762306a36Sopenharmony_ci{ 26862306a36Sopenharmony_ci struct net_device *dev = qdisc_dev(q->root); 26962306a36Sopenharmony_ci int num_tc = netdev_get_num_tc(dev); 27062306a36Sopenharmony_ci u32 max_sdu_from_user; 27162306a36Sopenharmony_ci u32 max_sdu_dynamic; 27262306a36Sopenharmony_ci u32 max_sdu; 27362306a36Sopenharmony_ci int tc; 27462306a36Sopenharmony_ci 27562306a36Sopenharmony_ci for (tc = 0; tc < num_tc; tc++) { 27662306a36Sopenharmony_ci max_sdu_from_user = q->max_sdu[tc] ?: U32_MAX; 27762306a36Sopenharmony_ci 27862306a36Sopenharmony_ci /* TC gate never closes => keep the queueMaxSDU 27962306a36Sopenharmony_ci * selected by the user 28062306a36Sopenharmony_ci */ 28162306a36Sopenharmony_ci if (sched->max_open_gate_duration[tc] == sched->cycle_time) { 28262306a36Sopenharmony_ci max_sdu_dynamic = U32_MAX; 28362306a36Sopenharmony_ci } else { 28462306a36Sopenharmony_ci u32 max_frm_len; 28562306a36Sopenharmony_ci 28662306a36Sopenharmony_ci max_frm_len = duration_to_length(q, sched->max_open_gate_duration[tc]); 28762306a36Sopenharmony_ci /* Compensate for L1 overhead from size table, 28862306a36Sopenharmony_ci * but don't let the frame size go negative 28962306a36Sopenharmony_ci */ 29062306a36Sopenharmony_ci if (stab) { 29162306a36Sopenharmony_ci max_frm_len -= stab->szopts.overhead; 29262306a36Sopenharmony_ci max_frm_len = max_t(int, max_frm_len, 29362306a36Sopenharmony_ci dev->hard_header_len + 1); 29462306a36Sopenharmony_ci } 29562306a36Sopenharmony_ci max_sdu_dynamic = max_frm_len - dev->hard_header_len; 29662306a36Sopenharmony_ci if (max_sdu_dynamic > dev->max_mtu) 29762306a36Sopenharmony_ci max_sdu_dynamic = U32_MAX; 29862306a36Sopenharmony_ci } 29962306a36Sopenharmony_ci 30062306a36Sopenharmony_ci max_sdu = min(max_sdu_dynamic, max_sdu_from_user); 30162306a36Sopenharmony_ci 30262306a36Sopenharmony_ci if (max_sdu != U32_MAX) { 30362306a36Sopenharmony_ci sched->max_frm_len[tc] = max_sdu + dev->hard_header_len; 30462306a36Sopenharmony_ci sched->max_sdu[tc] = max_sdu; 30562306a36Sopenharmony_ci } else { 30662306a36Sopenharmony_ci sched->max_frm_len[tc] = U32_MAX; /* never oversized */ 30762306a36Sopenharmony_ci sched->max_sdu[tc] = 0; 30862306a36Sopenharmony_ci } 30962306a36Sopenharmony_ci } 31062306a36Sopenharmony_ci} 31162306a36Sopenharmony_ci 31262306a36Sopenharmony_ci/* Returns the entry corresponding to next available interval. If 31362306a36Sopenharmony_ci * validate_interval is set, it only validates whether the timestamp occurs 31462306a36Sopenharmony_ci * when the gate corresponding to the skb's traffic class is open. 31562306a36Sopenharmony_ci */ 31662306a36Sopenharmony_cistatic struct sched_entry *find_entry_to_transmit(struct sk_buff *skb, 31762306a36Sopenharmony_ci struct Qdisc *sch, 31862306a36Sopenharmony_ci struct sched_gate_list *sched, 31962306a36Sopenharmony_ci struct sched_gate_list *admin, 32062306a36Sopenharmony_ci ktime_t time, 32162306a36Sopenharmony_ci ktime_t *interval_start, 32262306a36Sopenharmony_ci ktime_t *interval_end, 32362306a36Sopenharmony_ci bool validate_interval) 32462306a36Sopenharmony_ci{ 32562306a36Sopenharmony_ci ktime_t curr_intv_start, curr_intv_end, cycle_end, packet_transmit_time; 32662306a36Sopenharmony_ci ktime_t earliest_txtime = KTIME_MAX, txtime, cycle, transmit_end_time; 32762306a36Sopenharmony_ci struct sched_entry *entry = NULL, *entry_found = NULL; 32862306a36Sopenharmony_ci struct taprio_sched *q = qdisc_priv(sch); 32962306a36Sopenharmony_ci struct net_device *dev = qdisc_dev(sch); 33062306a36Sopenharmony_ci bool entry_available = false; 33162306a36Sopenharmony_ci s32 cycle_elapsed; 33262306a36Sopenharmony_ci int tc, n; 33362306a36Sopenharmony_ci 33462306a36Sopenharmony_ci tc = netdev_get_prio_tc_map(dev, skb->priority); 33562306a36Sopenharmony_ci packet_transmit_time = length_to_duration(q, qdisc_pkt_len(skb)); 33662306a36Sopenharmony_ci 33762306a36Sopenharmony_ci *interval_start = 0; 33862306a36Sopenharmony_ci *interval_end = 0; 33962306a36Sopenharmony_ci 34062306a36Sopenharmony_ci if (!sched) 34162306a36Sopenharmony_ci return NULL; 34262306a36Sopenharmony_ci 34362306a36Sopenharmony_ci cycle = sched->cycle_time; 34462306a36Sopenharmony_ci cycle_elapsed = get_cycle_time_elapsed(sched, time); 34562306a36Sopenharmony_ci curr_intv_end = ktime_sub_ns(time, cycle_elapsed); 34662306a36Sopenharmony_ci cycle_end = ktime_add_ns(curr_intv_end, cycle); 34762306a36Sopenharmony_ci 34862306a36Sopenharmony_ci list_for_each_entry(entry, &sched->entries, list) { 34962306a36Sopenharmony_ci curr_intv_start = curr_intv_end; 35062306a36Sopenharmony_ci curr_intv_end = get_interval_end_time(sched, admin, entry, 35162306a36Sopenharmony_ci curr_intv_start); 35262306a36Sopenharmony_ci 35362306a36Sopenharmony_ci if (ktime_after(curr_intv_start, cycle_end)) 35462306a36Sopenharmony_ci break; 35562306a36Sopenharmony_ci 35662306a36Sopenharmony_ci if (!(entry->gate_mask & BIT(tc)) || 35762306a36Sopenharmony_ci packet_transmit_time > entry->interval) 35862306a36Sopenharmony_ci continue; 35962306a36Sopenharmony_ci 36062306a36Sopenharmony_ci txtime = entry->next_txtime; 36162306a36Sopenharmony_ci 36262306a36Sopenharmony_ci if (ktime_before(txtime, time) || validate_interval) { 36362306a36Sopenharmony_ci transmit_end_time = ktime_add_ns(time, packet_transmit_time); 36462306a36Sopenharmony_ci if ((ktime_before(curr_intv_start, time) && 36562306a36Sopenharmony_ci ktime_before(transmit_end_time, curr_intv_end)) || 36662306a36Sopenharmony_ci (ktime_after(curr_intv_start, time) && !validate_interval)) { 36762306a36Sopenharmony_ci entry_found = entry; 36862306a36Sopenharmony_ci *interval_start = curr_intv_start; 36962306a36Sopenharmony_ci *interval_end = curr_intv_end; 37062306a36Sopenharmony_ci break; 37162306a36Sopenharmony_ci } else if (!entry_available && !validate_interval) { 37262306a36Sopenharmony_ci /* Here, we are just trying to find out the 37362306a36Sopenharmony_ci * first available interval in the next cycle. 37462306a36Sopenharmony_ci */ 37562306a36Sopenharmony_ci entry_available = true; 37662306a36Sopenharmony_ci entry_found = entry; 37762306a36Sopenharmony_ci *interval_start = ktime_add_ns(curr_intv_start, cycle); 37862306a36Sopenharmony_ci *interval_end = ktime_add_ns(curr_intv_end, cycle); 37962306a36Sopenharmony_ci } 38062306a36Sopenharmony_ci } else if (ktime_before(txtime, earliest_txtime) && 38162306a36Sopenharmony_ci !entry_available) { 38262306a36Sopenharmony_ci earliest_txtime = txtime; 38362306a36Sopenharmony_ci entry_found = entry; 38462306a36Sopenharmony_ci n = div_s64(ktime_sub(txtime, curr_intv_start), cycle); 38562306a36Sopenharmony_ci *interval_start = ktime_add(curr_intv_start, n * cycle); 38662306a36Sopenharmony_ci *interval_end = ktime_add(curr_intv_end, n * cycle); 38762306a36Sopenharmony_ci } 38862306a36Sopenharmony_ci } 38962306a36Sopenharmony_ci 39062306a36Sopenharmony_ci return entry_found; 39162306a36Sopenharmony_ci} 39262306a36Sopenharmony_ci 39362306a36Sopenharmony_cistatic bool is_valid_interval(struct sk_buff *skb, struct Qdisc *sch) 39462306a36Sopenharmony_ci{ 39562306a36Sopenharmony_ci struct taprio_sched *q = qdisc_priv(sch); 39662306a36Sopenharmony_ci struct sched_gate_list *sched, *admin; 39762306a36Sopenharmony_ci ktime_t interval_start, interval_end; 39862306a36Sopenharmony_ci struct sched_entry *entry; 39962306a36Sopenharmony_ci 40062306a36Sopenharmony_ci rcu_read_lock(); 40162306a36Sopenharmony_ci sched = rcu_dereference(q->oper_sched); 40262306a36Sopenharmony_ci admin = rcu_dereference(q->admin_sched); 40362306a36Sopenharmony_ci 40462306a36Sopenharmony_ci entry = find_entry_to_transmit(skb, sch, sched, admin, skb->tstamp, 40562306a36Sopenharmony_ci &interval_start, &interval_end, true); 40662306a36Sopenharmony_ci rcu_read_unlock(); 40762306a36Sopenharmony_ci 40862306a36Sopenharmony_ci return entry; 40962306a36Sopenharmony_ci} 41062306a36Sopenharmony_ci 41162306a36Sopenharmony_cistatic bool taprio_flags_valid(u32 flags) 41262306a36Sopenharmony_ci{ 41362306a36Sopenharmony_ci /* Make sure no other flag bits are set. */ 41462306a36Sopenharmony_ci if (flags & ~(TCA_TAPRIO_ATTR_FLAG_TXTIME_ASSIST | 41562306a36Sopenharmony_ci TCA_TAPRIO_ATTR_FLAG_FULL_OFFLOAD)) 41662306a36Sopenharmony_ci return false; 41762306a36Sopenharmony_ci /* txtime-assist and full offload are mutually exclusive */ 41862306a36Sopenharmony_ci if ((flags & TCA_TAPRIO_ATTR_FLAG_TXTIME_ASSIST) && 41962306a36Sopenharmony_ci (flags & TCA_TAPRIO_ATTR_FLAG_FULL_OFFLOAD)) 42062306a36Sopenharmony_ci return false; 42162306a36Sopenharmony_ci return true; 42262306a36Sopenharmony_ci} 42362306a36Sopenharmony_ci 42462306a36Sopenharmony_ci/* This returns the tstamp value set by TCP in terms of the set clock. */ 42562306a36Sopenharmony_cistatic ktime_t get_tcp_tstamp(struct taprio_sched *q, struct sk_buff *skb) 42662306a36Sopenharmony_ci{ 42762306a36Sopenharmony_ci unsigned int offset = skb_network_offset(skb); 42862306a36Sopenharmony_ci const struct ipv6hdr *ipv6h; 42962306a36Sopenharmony_ci const struct iphdr *iph; 43062306a36Sopenharmony_ci struct ipv6hdr _ipv6h; 43162306a36Sopenharmony_ci 43262306a36Sopenharmony_ci ipv6h = skb_header_pointer(skb, offset, sizeof(_ipv6h), &_ipv6h); 43362306a36Sopenharmony_ci if (!ipv6h) 43462306a36Sopenharmony_ci return 0; 43562306a36Sopenharmony_ci 43662306a36Sopenharmony_ci if (ipv6h->version == 4) { 43762306a36Sopenharmony_ci iph = (struct iphdr *)ipv6h; 43862306a36Sopenharmony_ci offset += iph->ihl * 4; 43962306a36Sopenharmony_ci 44062306a36Sopenharmony_ci /* special-case 6in4 tunnelling, as that is a common way to get 44162306a36Sopenharmony_ci * v6 connectivity in the home 44262306a36Sopenharmony_ci */ 44362306a36Sopenharmony_ci if (iph->protocol == IPPROTO_IPV6) { 44462306a36Sopenharmony_ci ipv6h = skb_header_pointer(skb, offset, 44562306a36Sopenharmony_ci sizeof(_ipv6h), &_ipv6h); 44662306a36Sopenharmony_ci 44762306a36Sopenharmony_ci if (!ipv6h || ipv6h->nexthdr != IPPROTO_TCP) 44862306a36Sopenharmony_ci return 0; 44962306a36Sopenharmony_ci } else if (iph->protocol != IPPROTO_TCP) { 45062306a36Sopenharmony_ci return 0; 45162306a36Sopenharmony_ci } 45262306a36Sopenharmony_ci } else if (ipv6h->version == 6 && ipv6h->nexthdr != IPPROTO_TCP) { 45362306a36Sopenharmony_ci return 0; 45462306a36Sopenharmony_ci } 45562306a36Sopenharmony_ci 45662306a36Sopenharmony_ci return taprio_mono_to_any(q, skb->skb_mstamp_ns); 45762306a36Sopenharmony_ci} 45862306a36Sopenharmony_ci 45962306a36Sopenharmony_ci/* There are a few scenarios where we will have to modify the txtime from 46062306a36Sopenharmony_ci * what is read from next_txtime in sched_entry. They are: 46162306a36Sopenharmony_ci * 1. If txtime is in the past, 46262306a36Sopenharmony_ci * a. The gate for the traffic class is currently open and packet can be 46362306a36Sopenharmony_ci * transmitted before it closes, schedule the packet right away. 46462306a36Sopenharmony_ci * b. If the gate corresponding to the traffic class is going to open later 46562306a36Sopenharmony_ci * in the cycle, set the txtime of packet to the interval start. 46662306a36Sopenharmony_ci * 2. If txtime is in the future, there are packets corresponding to the 46762306a36Sopenharmony_ci * current traffic class waiting to be transmitted. So, the following 46862306a36Sopenharmony_ci * possibilities exist: 46962306a36Sopenharmony_ci * a. We can transmit the packet before the window containing the txtime 47062306a36Sopenharmony_ci * closes. 47162306a36Sopenharmony_ci * b. The window might close before the transmission can be completed 47262306a36Sopenharmony_ci * successfully. So, schedule the packet in the next open window. 47362306a36Sopenharmony_ci */ 47462306a36Sopenharmony_cistatic long get_packet_txtime(struct sk_buff *skb, struct Qdisc *sch) 47562306a36Sopenharmony_ci{ 47662306a36Sopenharmony_ci ktime_t transmit_end_time, interval_end, interval_start, tcp_tstamp; 47762306a36Sopenharmony_ci struct taprio_sched *q = qdisc_priv(sch); 47862306a36Sopenharmony_ci struct sched_gate_list *sched, *admin; 47962306a36Sopenharmony_ci ktime_t minimum_time, now, txtime; 48062306a36Sopenharmony_ci int len, packet_transmit_time; 48162306a36Sopenharmony_ci struct sched_entry *entry; 48262306a36Sopenharmony_ci bool sched_changed; 48362306a36Sopenharmony_ci 48462306a36Sopenharmony_ci now = taprio_get_time(q); 48562306a36Sopenharmony_ci minimum_time = ktime_add_ns(now, q->txtime_delay); 48662306a36Sopenharmony_ci 48762306a36Sopenharmony_ci tcp_tstamp = get_tcp_tstamp(q, skb); 48862306a36Sopenharmony_ci minimum_time = max_t(ktime_t, minimum_time, tcp_tstamp); 48962306a36Sopenharmony_ci 49062306a36Sopenharmony_ci rcu_read_lock(); 49162306a36Sopenharmony_ci admin = rcu_dereference(q->admin_sched); 49262306a36Sopenharmony_ci sched = rcu_dereference(q->oper_sched); 49362306a36Sopenharmony_ci if (admin && ktime_after(minimum_time, admin->base_time)) 49462306a36Sopenharmony_ci switch_schedules(q, &admin, &sched); 49562306a36Sopenharmony_ci 49662306a36Sopenharmony_ci /* Until the schedule starts, all the queues are open */ 49762306a36Sopenharmony_ci if (!sched || ktime_before(minimum_time, sched->base_time)) { 49862306a36Sopenharmony_ci txtime = minimum_time; 49962306a36Sopenharmony_ci goto done; 50062306a36Sopenharmony_ci } 50162306a36Sopenharmony_ci 50262306a36Sopenharmony_ci len = qdisc_pkt_len(skb); 50362306a36Sopenharmony_ci packet_transmit_time = length_to_duration(q, len); 50462306a36Sopenharmony_ci 50562306a36Sopenharmony_ci do { 50662306a36Sopenharmony_ci sched_changed = false; 50762306a36Sopenharmony_ci 50862306a36Sopenharmony_ci entry = find_entry_to_transmit(skb, sch, sched, admin, 50962306a36Sopenharmony_ci minimum_time, 51062306a36Sopenharmony_ci &interval_start, &interval_end, 51162306a36Sopenharmony_ci false); 51262306a36Sopenharmony_ci if (!entry) { 51362306a36Sopenharmony_ci txtime = 0; 51462306a36Sopenharmony_ci goto done; 51562306a36Sopenharmony_ci } 51662306a36Sopenharmony_ci 51762306a36Sopenharmony_ci txtime = entry->next_txtime; 51862306a36Sopenharmony_ci txtime = max_t(ktime_t, txtime, minimum_time); 51962306a36Sopenharmony_ci txtime = max_t(ktime_t, txtime, interval_start); 52062306a36Sopenharmony_ci 52162306a36Sopenharmony_ci if (admin && admin != sched && 52262306a36Sopenharmony_ci ktime_after(txtime, admin->base_time)) { 52362306a36Sopenharmony_ci sched = admin; 52462306a36Sopenharmony_ci sched_changed = true; 52562306a36Sopenharmony_ci continue; 52662306a36Sopenharmony_ci } 52762306a36Sopenharmony_ci 52862306a36Sopenharmony_ci transmit_end_time = ktime_add(txtime, packet_transmit_time); 52962306a36Sopenharmony_ci minimum_time = transmit_end_time; 53062306a36Sopenharmony_ci 53162306a36Sopenharmony_ci /* Update the txtime of current entry to the next time it's 53262306a36Sopenharmony_ci * interval starts. 53362306a36Sopenharmony_ci */ 53462306a36Sopenharmony_ci if (ktime_after(transmit_end_time, interval_end)) 53562306a36Sopenharmony_ci entry->next_txtime = ktime_add(interval_start, sched->cycle_time); 53662306a36Sopenharmony_ci } while (sched_changed || ktime_after(transmit_end_time, interval_end)); 53762306a36Sopenharmony_ci 53862306a36Sopenharmony_ci entry->next_txtime = transmit_end_time; 53962306a36Sopenharmony_ci 54062306a36Sopenharmony_cidone: 54162306a36Sopenharmony_ci rcu_read_unlock(); 54262306a36Sopenharmony_ci return txtime; 54362306a36Sopenharmony_ci} 54462306a36Sopenharmony_ci 54562306a36Sopenharmony_ci/* Devices with full offload are expected to honor this in hardware */ 54662306a36Sopenharmony_cistatic bool taprio_skb_exceeds_queue_max_sdu(struct Qdisc *sch, 54762306a36Sopenharmony_ci struct sk_buff *skb) 54862306a36Sopenharmony_ci{ 54962306a36Sopenharmony_ci struct taprio_sched *q = qdisc_priv(sch); 55062306a36Sopenharmony_ci struct net_device *dev = qdisc_dev(sch); 55162306a36Sopenharmony_ci struct sched_gate_list *sched; 55262306a36Sopenharmony_ci int prio = skb->priority; 55362306a36Sopenharmony_ci bool exceeds = false; 55462306a36Sopenharmony_ci u8 tc; 55562306a36Sopenharmony_ci 55662306a36Sopenharmony_ci tc = netdev_get_prio_tc_map(dev, prio); 55762306a36Sopenharmony_ci 55862306a36Sopenharmony_ci rcu_read_lock(); 55962306a36Sopenharmony_ci sched = rcu_dereference(q->oper_sched); 56062306a36Sopenharmony_ci if (sched && skb->len > sched->max_frm_len[tc]) 56162306a36Sopenharmony_ci exceeds = true; 56262306a36Sopenharmony_ci rcu_read_unlock(); 56362306a36Sopenharmony_ci 56462306a36Sopenharmony_ci return exceeds; 56562306a36Sopenharmony_ci} 56662306a36Sopenharmony_ci 56762306a36Sopenharmony_cistatic int taprio_enqueue_one(struct sk_buff *skb, struct Qdisc *sch, 56862306a36Sopenharmony_ci struct Qdisc *child, struct sk_buff **to_free) 56962306a36Sopenharmony_ci{ 57062306a36Sopenharmony_ci struct taprio_sched *q = qdisc_priv(sch); 57162306a36Sopenharmony_ci 57262306a36Sopenharmony_ci /* sk_flags are only safe to use on full sockets. */ 57362306a36Sopenharmony_ci if (skb->sk && sk_fullsock(skb->sk) && sock_flag(skb->sk, SOCK_TXTIME)) { 57462306a36Sopenharmony_ci if (!is_valid_interval(skb, sch)) 57562306a36Sopenharmony_ci return qdisc_drop(skb, sch, to_free); 57662306a36Sopenharmony_ci } else if (TXTIME_ASSIST_IS_ENABLED(q->flags)) { 57762306a36Sopenharmony_ci skb->tstamp = get_packet_txtime(skb, sch); 57862306a36Sopenharmony_ci if (!skb->tstamp) 57962306a36Sopenharmony_ci return qdisc_drop(skb, sch, to_free); 58062306a36Sopenharmony_ci } 58162306a36Sopenharmony_ci 58262306a36Sopenharmony_ci qdisc_qstats_backlog_inc(sch, skb); 58362306a36Sopenharmony_ci sch->q.qlen++; 58462306a36Sopenharmony_ci 58562306a36Sopenharmony_ci return qdisc_enqueue(skb, child, to_free); 58662306a36Sopenharmony_ci} 58762306a36Sopenharmony_ci 58862306a36Sopenharmony_cistatic int taprio_enqueue_segmented(struct sk_buff *skb, struct Qdisc *sch, 58962306a36Sopenharmony_ci struct Qdisc *child, 59062306a36Sopenharmony_ci struct sk_buff **to_free) 59162306a36Sopenharmony_ci{ 59262306a36Sopenharmony_ci unsigned int slen = 0, numsegs = 0, len = qdisc_pkt_len(skb); 59362306a36Sopenharmony_ci netdev_features_t features = netif_skb_features(skb); 59462306a36Sopenharmony_ci struct sk_buff *segs, *nskb; 59562306a36Sopenharmony_ci int ret; 59662306a36Sopenharmony_ci 59762306a36Sopenharmony_ci segs = skb_gso_segment(skb, features & ~NETIF_F_GSO_MASK); 59862306a36Sopenharmony_ci if (IS_ERR_OR_NULL(segs)) 59962306a36Sopenharmony_ci return qdisc_drop(skb, sch, to_free); 60062306a36Sopenharmony_ci 60162306a36Sopenharmony_ci skb_list_walk_safe(segs, segs, nskb) { 60262306a36Sopenharmony_ci skb_mark_not_on_list(segs); 60362306a36Sopenharmony_ci qdisc_skb_cb(segs)->pkt_len = segs->len; 60462306a36Sopenharmony_ci slen += segs->len; 60562306a36Sopenharmony_ci 60662306a36Sopenharmony_ci /* FIXME: we should be segmenting to a smaller size 60762306a36Sopenharmony_ci * rather than dropping these 60862306a36Sopenharmony_ci */ 60962306a36Sopenharmony_ci if (taprio_skb_exceeds_queue_max_sdu(sch, segs)) 61062306a36Sopenharmony_ci ret = qdisc_drop(segs, sch, to_free); 61162306a36Sopenharmony_ci else 61262306a36Sopenharmony_ci ret = taprio_enqueue_one(segs, sch, child, to_free); 61362306a36Sopenharmony_ci 61462306a36Sopenharmony_ci if (ret != NET_XMIT_SUCCESS) { 61562306a36Sopenharmony_ci if (net_xmit_drop_count(ret)) 61662306a36Sopenharmony_ci qdisc_qstats_drop(sch); 61762306a36Sopenharmony_ci } else { 61862306a36Sopenharmony_ci numsegs++; 61962306a36Sopenharmony_ci } 62062306a36Sopenharmony_ci } 62162306a36Sopenharmony_ci 62262306a36Sopenharmony_ci if (numsegs > 1) 62362306a36Sopenharmony_ci qdisc_tree_reduce_backlog(sch, 1 - numsegs, len - slen); 62462306a36Sopenharmony_ci consume_skb(skb); 62562306a36Sopenharmony_ci 62662306a36Sopenharmony_ci return numsegs > 0 ? NET_XMIT_SUCCESS : NET_XMIT_DROP; 62762306a36Sopenharmony_ci} 62862306a36Sopenharmony_ci 62962306a36Sopenharmony_ci/* Will not be called in the full offload case, since the TX queues are 63062306a36Sopenharmony_ci * attached to the Qdisc created using qdisc_create_dflt() 63162306a36Sopenharmony_ci */ 63262306a36Sopenharmony_cistatic int taprio_enqueue(struct sk_buff *skb, struct Qdisc *sch, 63362306a36Sopenharmony_ci struct sk_buff **to_free) 63462306a36Sopenharmony_ci{ 63562306a36Sopenharmony_ci struct taprio_sched *q = qdisc_priv(sch); 63662306a36Sopenharmony_ci struct Qdisc *child; 63762306a36Sopenharmony_ci int queue; 63862306a36Sopenharmony_ci 63962306a36Sopenharmony_ci queue = skb_get_queue_mapping(skb); 64062306a36Sopenharmony_ci 64162306a36Sopenharmony_ci child = q->qdiscs[queue]; 64262306a36Sopenharmony_ci if (unlikely(!child)) 64362306a36Sopenharmony_ci return qdisc_drop(skb, sch, to_free); 64462306a36Sopenharmony_ci 64562306a36Sopenharmony_ci if (taprio_skb_exceeds_queue_max_sdu(sch, skb)) { 64662306a36Sopenharmony_ci /* Large packets might not be transmitted when the transmission 64762306a36Sopenharmony_ci * duration exceeds any configured interval. Therefore, segment 64862306a36Sopenharmony_ci * the skb into smaller chunks. Drivers with full offload are 64962306a36Sopenharmony_ci * expected to handle this in hardware. 65062306a36Sopenharmony_ci */ 65162306a36Sopenharmony_ci if (skb_is_gso(skb)) 65262306a36Sopenharmony_ci return taprio_enqueue_segmented(skb, sch, child, 65362306a36Sopenharmony_ci to_free); 65462306a36Sopenharmony_ci 65562306a36Sopenharmony_ci return qdisc_drop(skb, sch, to_free); 65662306a36Sopenharmony_ci } 65762306a36Sopenharmony_ci 65862306a36Sopenharmony_ci return taprio_enqueue_one(skb, sch, child, to_free); 65962306a36Sopenharmony_ci} 66062306a36Sopenharmony_ci 66162306a36Sopenharmony_cistatic struct sk_buff *taprio_peek(struct Qdisc *sch) 66262306a36Sopenharmony_ci{ 66362306a36Sopenharmony_ci WARN_ONCE(1, "taprio only supports operating as root qdisc, peek() not implemented"); 66462306a36Sopenharmony_ci return NULL; 66562306a36Sopenharmony_ci} 66662306a36Sopenharmony_ci 66762306a36Sopenharmony_cistatic void taprio_set_budgets(struct taprio_sched *q, 66862306a36Sopenharmony_ci struct sched_gate_list *sched, 66962306a36Sopenharmony_ci struct sched_entry *entry) 67062306a36Sopenharmony_ci{ 67162306a36Sopenharmony_ci struct net_device *dev = qdisc_dev(q->root); 67262306a36Sopenharmony_ci int num_tc = netdev_get_num_tc(dev); 67362306a36Sopenharmony_ci int tc, budget; 67462306a36Sopenharmony_ci 67562306a36Sopenharmony_ci for (tc = 0; tc < num_tc; tc++) { 67662306a36Sopenharmony_ci /* Traffic classes which never close have infinite budget */ 67762306a36Sopenharmony_ci if (entry->gate_duration[tc] == sched->cycle_time) 67862306a36Sopenharmony_ci budget = INT_MAX; 67962306a36Sopenharmony_ci else 68062306a36Sopenharmony_ci budget = div64_u64((u64)entry->gate_duration[tc] * PSEC_PER_NSEC, 68162306a36Sopenharmony_ci atomic64_read(&q->picos_per_byte)); 68262306a36Sopenharmony_ci 68362306a36Sopenharmony_ci atomic_set(&entry->budget[tc], budget); 68462306a36Sopenharmony_ci } 68562306a36Sopenharmony_ci} 68662306a36Sopenharmony_ci 68762306a36Sopenharmony_ci/* When an skb is sent, it consumes from the budget of all traffic classes */ 68862306a36Sopenharmony_cistatic int taprio_update_budgets(struct sched_entry *entry, size_t len, 68962306a36Sopenharmony_ci int tc_consumed, int num_tc) 69062306a36Sopenharmony_ci{ 69162306a36Sopenharmony_ci int tc, budget, new_budget = 0; 69262306a36Sopenharmony_ci 69362306a36Sopenharmony_ci for (tc = 0; tc < num_tc; tc++) { 69462306a36Sopenharmony_ci budget = atomic_read(&entry->budget[tc]); 69562306a36Sopenharmony_ci /* Don't consume from infinite budget */ 69662306a36Sopenharmony_ci if (budget == INT_MAX) { 69762306a36Sopenharmony_ci if (tc == tc_consumed) 69862306a36Sopenharmony_ci new_budget = budget; 69962306a36Sopenharmony_ci continue; 70062306a36Sopenharmony_ci } 70162306a36Sopenharmony_ci 70262306a36Sopenharmony_ci if (tc == tc_consumed) 70362306a36Sopenharmony_ci new_budget = atomic_sub_return(len, &entry->budget[tc]); 70462306a36Sopenharmony_ci else 70562306a36Sopenharmony_ci atomic_sub(len, &entry->budget[tc]); 70662306a36Sopenharmony_ci } 70762306a36Sopenharmony_ci 70862306a36Sopenharmony_ci return new_budget; 70962306a36Sopenharmony_ci} 71062306a36Sopenharmony_ci 71162306a36Sopenharmony_cistatic struct sk_buff *taprio_dequeue_from_txq(struct Qdisc *sch, int txq, 71262306a36Sopenharmony_ci struct sched_entry *entry, 71362306a36Sopenharmony_ci u32 gate_mask) 71462306a36Sopenharmony_ci{ 71562306a36Sopenharmony_ci struct taprio_sched *q = qdisc_priv(sch); 71662306a36Sopenharmony_ci struct net_device *dev = qdisc_dev(sch); 71762306a36Sopenharmony_ci struct Qdisc *child = q->qdiscs[txq]; 71862306a36Sopenharmony_ci int num_tc = netdev_get_num_tc(dev); 71962306a36Sopenharmony_ci struct sk_buff *skb; 72062306a36Sopenharmony_ci ktime_t guard; 72162306a36Sopenharmony_ci int prio; 72262306a36Sopenharmony_ci int len; 72362306a36Sopenharmony_ci u8 tc; 72462306a36Sopenharmony_ci 72562306a36Sopenharmony_ci if (unlikely(!child)) 72662306a36Sopenharmony_ci return NULL; 72762306a36Sopenharmony_ci 72862306a36Sopenharmony_ci if (TXTIME_ASSIST_IS_ENABLED(q->flags)) 72962306a36Sopenharmony_ci goto skip_peek_checks; 73062306a36Sopenharmony_ci 73162306a36Sopenharmony_ci skb = child->ops->peek(child); 73262306a36Sopenharmony_ci if (!skb) 73362306a36Sopenharmony_ci return NULL; 73462306a36Sopenharmony_ci 73562306a36Sopenharmony_ci prio = skb->priority; 73662306a36Sopenharmony_ci tc = netdev_get_prio_tc_map(dev, prio); 73762306a36Sopenharmony_ci 73862306a36Sopenharmony_ci if (!(gate_mask & BIT(tc))) 73962306a36Sopenharmony_ci return NULL; 74062306a36Sopenharmony_ci 74162306a36Sopenharmony_ci len = qdisc_pkt_len(skb); 74262306a36Sopenharmony_ci guard = ktime_add_ns(taprio_get_time(q), length_to_duration(q, len)); 74362306a36Sopenharmony_ci 74462306a36Sopenharmony_ci /* In the case that there's no gate entry, there's no 74562306a36Sopenharmony_ci * guard band ... 74662306a36Sopenharmony_ci */ 74762306a36Sopenharmony_ci if (gate_mask != TAPRIO_ALL_GATES_OPEN && 74862306a36Sopenharmony_ci !taprio_entry_allows_tx(guard, entry, tc)) 74962306a36Sopenharmony_ci return NULL; 75062306a36Sopenharmony_ci 75162306a36Sopenharmony_ci /* ... and no budget. */ 75262306a36Sopenharmony_ci if (gate_mask != TAPRIO_ALL_GATES_OPEN && 75362306a36Sopenharmony_ci taprio_update_budgets(entry, len, tc, num_tc) < 0) 75462306a36Sopenharmony_ci return NULL; 75562306a36Sopenharmony_ci 75662306a36Sopenharmony_ciskip_peek_checks: 75762306a36Sopenharmony_ci skb = child->ops->dequeue(child); 75862306a36Sopenharmony_ci if (unlikely(!skb)) 75962306a36Sopenharmony_ci return NULL; 76062306a36Sopenharmony_ci 76162306a36Sopenharmony_ci qdisc_bstats_update(sch, skb); 76262306a36Sopenharmony_ci qdisc_qstats_backlog_dec(sch, skb); 76362306a36Sopenharmony_ci sch->q.qlen--; 76462306a36Sopenharmony_ci 76562306a36Sopenharmony_ci return skb; 76662306a36Sopenharmony_ci} 76762306a36Sopenharmony_ci 76862306a36Sopenharmony_cistatic void taprio_next_tc_txq(struct net_device *dev, int tc, int *txq) 76962306a36Sopenharmony_ci{ 77062306a36Sopenharmony_ci int offset = dev->tc_to_txq[tc].offset; 77162306a36Sopenharmony_ci int count = dev->tc_to_txq[tc].count; 77262306a36Sopenharmony_ci 77362306a36Sopenharmony_ci (*txq)++; 77462306a36Sopenharmony_ci if (*txq == offset + count) 77562306a36Sopenharmony_ci *txq = offset; 77662306a36Sopenharmony_ci} 77762306a36Sopenharmony_ci 77862306a36Sopenharmony_ci/* Prioritize higher traffic classes, and select among TXQs belonging to the 77962306a36Sopenharmony_ci * same TC using round robin 78062306a36Sopenharmony_ci */ 78162306a36Sopenharmony_cistatic struct sk_buff *taprio_dequeue_tc_priority(struct Qdisc *sch, 78262306a36Sopenharmony_ci struct sched_entry *entry, 78362306a36Sopenharmony_ci u32 gate_mask) 78462306a36Sopenharmony_ci{ 78562306a36Sopenharmony_ci struct taprio_sched *q = qdisc_priv(sch); 78662306a36Sopenharmony_ci struct net_device *dev = qdisc_dev(sch); 78762306a36Sopenharmony_ci int num_tc = netdev_get_num_tc(dev); 78862306a36Sopenharmony_ci struct sk_buff *skb; 78962306a36Sopenharmony_ci int tc; 79062306a36Sopenharmony_ci 79162306a36Sopenharmony_ci for (tc = num_tc - 1; tc >= 0; tc--) { 79262306a36Sopenharmony_ci int first_txq = q->cur_txq[tc]; 79362306a36Sopenharmony_ci 79462306a36Sopenharmony_ci if (!(gate_mask & BIT(tc))) 79562306a36Sopenharmony_ci continue; 79662306a36Sopenharmony_ci 79762306a36Sopenharmony_ci do { 79862306a36Sopenharmony_ci skb = taprio_dequeue_from_txq(sch, q->cur_txq[tc], 79962306a36Sopenharmony_ci entry, gate_mask); 80062306a36Sopenharmony_ci 80162306a36Sopenharmony_ci taprio_next_tc_txq(dev, tc, &q->cur_txq[tc]); 80262306a36Sopenharmony_ci 80362306a36Sopenharmony_ci if (q->cur_txq[tc] >= dev->num_tx_queues) 80462306a36Sopenharmony_ci q->cur_txq[tc] = first_txq; 80562306a36Sopenharmony_ci 80662306a36Sopenharmony_ci if (skb) 80762306a36Sopenharmony_ci return skb; 80862306a36Sopenharmony_ci } while (q->cur_txq[tc] != first_txq); 80962306a36Sopenharmony_ci } 81062306a36Sopenharmony_ci 81162306a36Sopenharmony_ci return NULL; 81262306a36Sopenharmony_ci} 81362306a36Sopenharmony_ci 81462306a36Sopenharmony_ci/* Broken way of prioritizing smaller TXQ indices and ignoring the traffic 81562306a36Sopenharmony_ci * class other than to determine whether the gate is open or not 81662306a36Sopenharmony_ci */ 81762306a36Sopenharmony_cistatic struct sk_buff *taprio_dequeue_txq_priority(struct Qdisc *sch, 81862306a36Sopenharmony_ci struct sched_entry *entry, 81962306a36Sopenharmony_ci u32 gate_mask) 82062306a36Sopenharmony_ci{ 82162306a36Sopenharmony_ci struct net_device *dev = qdisc_dev(sch); 82262306a36Sopenharmony_ci struct sk_buff *skb; 82362306a36Sopenharmony_ci int i; 82462306a36Sopenharmony_ci 82562306a36Sopenharmony_ci for (i = 0; i < dev->num_tx_queues; i++) { 82662306a36Sopenharmony_ci skb = taprio_dequeue_from_txq(sch, i, entry, gate_mask); 82762306a36Sopenharmony_ci if (skb) 82862306a36Sopenharmony_ci return skb; 82962306a36Sopenharmony_ci } 83062306a36Sopenharmony_ci 83162306a36Sopenharmony_ci return NULL; 83262306a36Sopenharmony_ci} 83362306a36Sopenharmony_ci 83462306a36Sopenharmony_ci/* Will not be called in the full offload case, since the TX queues are 83562306a36Sopenharmony_ci * attached to the Qdisc created using qdisc_create_dflt() 83662306a36Sopenharmony_ci */ 83762306a36Sopenharmony_cistatic struct sk_buff *taprio_dequeue(struct Qdisc *sch) 83862306a36Sopenharmony_ci{ 83962306a36Sopenharmony_ci struct taprio_sched *q = qdisc_priv(sch); 84062306a36Sopenharmony_ci struct sk_buff *skb = NULL; 84162306a36Sopenharmony_ci struct sched_entry *entry; 84262306a36Sopenharmony_ci u32 gate_mask; 84362306a36Sopenharmony_ci 84462306a36Sopenharmony_ci rcu_read_lock(); 84562306a36Sopenharmony_ci entry = rcu_dereference(q->current_entry); 84662306a36Sopenharmony_ci /* if there's no entry, it means that the schedule didn't 84762306a36Sopenharmony_ci * start yet, so force all gates to be open, this is in 84862306a36Sopenharmony_ci * accordance to IEEE 802.1Qbv-2015 Section 8.6.9.4.5 84962306a36Sopenharmony_ci * "AdminGateStates" 85062306a36Sopenharmony_ci */ 85162306a36Sopenharmony_ci gate_mask = entry ? entry->gate_mask : TAPRIO_ALL_GATES_OPEN; 85262306a36Sopenharmony_ci if (!gate_mask) 85362306a36Sopenharmony_ci goto done; 85462306a36Sopenharmony_ci 85562306a36Sopenharmony_ci if (static_branch_unlikely(&taprio_have_broken_mqprio) && 85662306a36Sopenharmony_ci !static_branch_likely(&taprio_have_working_mqprio)) { 85762306a36Sopenharmony_ci /* Single NIC kind which is broken */ 85862306a36Sopenharmony_ci skb = taprio_dequeue_txq_priority(sch, entry, gate_mask); 85962306a36Sopenharmony_ci } else if (static_branch_likely(&taprio_have_working_mqprio) && 86062306a36Sopenharmony_ci !static_branch_unlikely(&taprio_have_broken_mqprio)) { 86162306a36Sopenharmony_ci /* Single NIC kind which prioritizes properly */ 86262306a36Sopenharmony_ci skb = taprio_dequeue_tc_priority(sch, entry, gate_mask); 86362306a36Sopenharmony_ci } else { 86462306a36Sopenharmony_ci /* Mixed NIC kinds present in system, need dynamic testing */ 86562306a36Sopenharmony_ci if (q->broken_mqprio) 86662306a36Sopenharmony_ci skb = taprio_dequeue_txq_priority(sch, entry, gate_mask); 86762306a36Sopenharmony_ci else 86862306a36Sopenharmony_ci skb = taprio_dequeue_tc_priority(sch, entry, gate_mask); 86962306a36Sopenharmony_ci } 87062306a36Sopenharmony_ci 87162306a36Sopenharmony_cidone: 87262306a36Sopenharmony_ci rcu_read_unlock(); 87362306a36Sopenharmony_ci 87462306a36Sopenharmony_ci return skb; 87562306a36Sopenharmony_ci} 87662306a36Sopenharmony_ci 87762306a36Sopenharmony_cistatic bool should_restart_cycle(const struct sched_gate_list *oper, 87862306a36Sopenharmony_ci const struct sched_entry *entry) 87962306a36Sopenharmony_ci{ 88062306a36Sopenharmony_ci if (list_is_last(&entry->list, &oper->entries)) 88162306a36Sopenharmony_ci return true; 88262306a36Sopenharmony_ci 88362306a36Sopenharmony_ci if (ktime_compare(entry->end_time, oper->cycle_end_time) == 0) 88462306a36Sopenharmony_ci return true; 88562306a36Sopenharmony_ci 88662306a36Sopenharmony_ci return false; 88762306a36Sopenharmony_ci} 88862306a36Sopenharmony_ci 88962306a36Sopenharmony_cistatic bool should_change_schedules(const struct sched_gate_list *admin, 89062306a36Sopenharmony_ci const struct sched_gate_list *oper, 89162306a36Sopenharmony_ci ktime_t end_time) 89262306a36Sopenharmony_ci{ 89362306a36Sopenharmony_ci ktime_t next_base_time, extension_time; 89462306a36Sopenharmony_ci 89562306a36Sopenharmony_ci if (!admin) 89662306a36Sopenharmony_ci return false; 89762306a36Sopenharmony_ci 89862306a36Sopenharmony_ci next_base_time = sched_base_time(admin); 89962306a36Sopenharmony_ci 90062306a36Sopenharmony_ci /* This is the simple case, the end_time would fall after 90162306a36Sopenharmony_ci * the next schedule base_time. 90262306a36Sopenharmony_ci */ 90362306a36Sopenharmony_ci if (ktime_compare(next_base_time, end_time) <= 0) 90462306a36Sopenharmony_ci return true; 90562306a36Sopenharmony_ci 90662306a36Sopenharmony_ci /* This is the cycle_time_extension case, if the end_time 90762306a36Sopenharmony_ci * plus the amount that can be extended would fall after the 90862306a36Sopenharmony_ci * next schedule base_time, we can extend the current schedule 90962306a36Sopenharmony_ci * for that amount. 91062306a36Sopenharmony_ci */ 91162306a36Sopenharmony_ci extension_time = ktime_add_ns(end_time, oper->cycle_time_extension); 91262306a36Sopenharmony_ci 91362306a36Sopenharmony_ci /* FIXME: the IEEE 802.1Q-2018 Specification isn't clear about 91462306a36Sopenharmony_ci * how precisely the extension should be made. So after 91562306a36Sopenharmony_ci * conformance testing, this logic may change. 91662306a36Sopenharmony_ci */ 91762306a36Sopenharmony_ci if (ktime_compare(next_base_time, extension_time) <= 0) 91862306a36Sopenharmony_ci return true; 91962306a36Sopenharmony_ci 92062306a36Sopenharmony_ci return false; 92162306a36Sopenharmony_ci} 92262306a36Sopenharmony_ci 92362306a36Sopenharmony_cistatic enum hrtimer_restart advance_sched(struct hrtimer *timer) 92462306a36Sopenharmony_ci{ 92562306a36Sopenharmony_ci struct taprio_sched *q = container_of(timer, struct taprio_sched, 92662306a36Sopenharmony_ci advance_timer); 92762306a36Sopenharmony_ci struct net_device *dev = qdisc_dev(q->root); 92862306a36Sopenharmony_ci struct sched_gate_list *oper, *admin; 92962306a36Sopenharmony_ci int num_tc = netdev_get_num_tc(dev); 93062306a36Sopenharmony_ci struct sched_entry *entry, *next; 93162306a36Sopenharmony_ci struct Qdisc *sch = q->root; 93262306a36Sopenharmony_ci ktime_t end_time; 93362306a36Sopenharmony_ci int tc; 93462306a36Sopenharmony_ci 93562306a36Sopenharmony_ci spin_lock(&q->current_entry_lock); 93662306a36Sopenharmony_ci entry = rcu_dereference_protected(q->current_entry, 93762306a36Sopenharmony_ci lockdep_is_held(&q->current_entry_lock)); 93862306a36Sopenharmony_ci oper = rcu_dereference_protected(q->oper_sched, 93962306a36Sopenharmony_ci lockdep_is_held(&q->current_entry_lock)); 94062306a36Sopenharmony_ci admin = rcu_dereference_protected(q->admin_sched, 94162306a36Sopenharmony_ci lockdep_is_held(&q->current_entry_lock)); 94262306a36Sopenharmony_ci 94362306a36Sopenharmony_ci if (!oper) 94462306a36Sopenharmony_ci switch_schedules(q, &admin, &oper); 94562306a36Sopenharmony_ci 94662306a36Sopenharmony_ci /* This can happen in two cases: 1. this is the very first run 94762306a36Sopenharmony_ci * of this function (i.e. we weren't running any schedule 94862306a36Sopenharmony_ci * previously); 2. The previous schedule just ended. The first 94962306a36Sopenharmony_ci * entry of all schedules are pre-calculated during the 95062306a36Sopenharmony_ci * schedule initialization. 95162306a36Sopenharmony_ci */ 95262306a36Sopenharmony_ci if (unlikely(!entry || entry->end_time == oper->base_time)) { 95362306a36Sopenharmony_ci next = list_first_entry(&oper->entries, struct sched_entry, 95462306a36Sopenharmony_ci list); 95562306a36Sopenharmony_ci end_time = next->end_time; 95662306a36Sopenharmony_ci goto first_run; 95762306a36Sopenharmony_ci } 95862306a36Sopenharmony_ci 95962306a36Sopenharmony_ci if (should_restart_cycle(oper, entry)) { 96062306a36Sopenharmony_ci next = list_first_entry(&oper->entries, struct sched_entry, 96162306a36Sopenharmony_ci list); 96262306a36Sopenharmony_ci oper->cycle_end_time = ktime_add_ns(oper->cycle_end_time, 96362306a36Sopenharmony_ci oper->cycle_time); 96462306a36Sopenharmony_ci } else { 96562306a36Sopenharmony_ci next = list_next_entry(entry, list); 96662306a36Sopenharmony_ci } 96762306a36Sopenharmony_ci 96862306a36Sopenharmony_ci end_time = ktime_add_ns(entry->end_time, next->interval); 96962306a36Sopenharmony_ci end_time = min_t(ktime_t, end_time, oper->cycle_end_time); 97062306a36Sopenharmony_ci 97162306a36Sopenharmony_ci for (tc = 0; tc < num_tc; tc++) { 97262306a36Sopenharmony_ci if (next->gate_duration[tc] == oper->cycle_time) 97362306a36Sopenharmony_ci next->gate_close_time[tc] = KTIME_MAX; 97462306a36Sopenharmony_ci else 97562306a36Sopenharmony_ci next->gate_close_time[tc] = ktime_add_ns(entry->end_time, 97662306a36Sopenharmony_ci next->gate_duration[tc]); 97762306a36Sopenharmony_ci } 97862306a36Sopenharmony_ci 97962306a36Sopenharmony_ci if (should_change_schedules(admin, oper, end_time)) { 98062306a36Sopenharmony_ci /* Set things so the next time this runs, the new 98162306a36Sopenharmony_ci * schedule runs. 98262306a36Sopenharmony_ci */ 98362306a36Sopenharmony_ci end_time = sched_base_time(admin); 98462306a36Sopenharmony_ci switch_schedules(q, &admin, &oper); 98562306a36Sopenharmony_ci } 98662306a36Sopenharmony_ci 98762306a36Sopenharmony_ci next->end_time = end_time; 98862306a36Sopenharmony_ci taprio_set_budgets(q, oper, next); 98962306a36Sopenharmony_ci 99062306a36Sopenharmony_cifirst_run: 99162306a36Sopenharmony_ci rcu_assign_pointer(q->current_entry, next); 99262306a36Sopenharmony_ci spin_unlock(&q->current_entry_lock); 99362306a36Sopenharmony_ci 99462306a36Sopenharmony_ci hrtimer_set_expires(&q->advance_timer, end_time); 99562306a36Sopenharmony_ci 99662306a36Sopenharmony_ci rcu_read_lock(); 99762306a36Sopenharmony_ci __netif_schedule(sch); 99862306a36Sopenharmony_ci rcu_read_unlock(); 99962306a36Sopenharmony_ci 100062306a36Sopenharmony_ci return HRTIMER_RESTART; 100162306a36Sopenharmony_ci} 100262306a36Sopenharmony_ci 100362306a36Sopenharmony_cistatic const struct nla_policy entry_policy[TCA_TAPRIO_SCHED_ENTRY_MAX + 1] = { 100462306a36Sopenharmony_ci [TCA_TAPRIO_SCHED_ENTRY_INDEX] = { .type = NLA_U32 }, 100562306a36Sopenharmony_ci [TCA_TAPRIO_SCHED_ENTRY_CMD] = { .type = NLA_U8 }, 100662306a36Sopenharmony_ci [TCA_TAPRIO_SCHED_ENTRY_GATE_MASK] = { .type = NLA_U32 }, 100762306a36Sopenharmony_ci [TCA_TAPRIO_SCHED_ENTRY_INTERVAL] = { .type = NLA_U32 }, 100862306a36Sopenharmony_ci}; 100962306a36Sopenharmony_ci 101062306a36Sopenharmony_cistatic const struct nla_policy taprio_tc_policy[TCA_TAPRIO_TC_ENTRY_MAX + 1] = { 101162306a36Sopenharmony_ci [TCA_TAPRIO_TC_ENTRY_INDEX] = NLA_POLICY_MAX(NLA_U32, 101262306a36Sopenharmony_ci TC_QOPT_MAX_QUEUE), 101362306a36Sopenharmony_ci [TCA_TAPRIO_TC_ENTRY_MAX_SDU] = { .type = NLA_U32 }, 101462306a36Sopenharmony_ci [TCA_TAPRIO_TC_ENTRY_FP] = NLA_POLICY_RANGE(NLA_U32, 101562306a36Sopenharmony_ci TC_FP_EXPRESS, 101662306a36Sopenharmony_ci TC_FP_PREEMPTIBLE), 101762306a36Sopenharmony_ci}; 101862306a36Sopenharmony_ci 101962306a36Sopenharmony_cistatic struct netlink_range_validation_signed taprio_cycle_time_range = { 102062306a36Sopenharmony_ci .min = 0, 102162306a36Sopenharmony_ci .max = INT_MAX, 102262306a36Sopenharmony_ci}; 102362306a36Sopenharmony_ci 102462306a36Sopenharmony_cistatic const struct nla_policy taprio_policy[TCA_TAPRIO_ATTR_MAX + 1] = { 102562306a36Sopenharmony_ci [TCA_TAPRIO_ATTR_PRIOMAP] = { 102662306a36Sopenharmony_ci .len = sizeof(struct tc_mqprio_qopt) 102762306a36Sopenharmony_ci }, 102862306a36Sopenharmony_ci [TCA_TAPRIO_ATTR_SCHED_ENTRY_LIST] = { .type = NLA_NESTED }, 102962306a36Sopenharmony_ci [TCA_TAPRIO_ATTR_SCHED_BASE_TIME] = { .type = NLA_S64 }, 103062306a36Sopenharmony_ci [TCA_TAPRIO_ATTR_SCHED_SINGLE_ENTRY] = { .type = NLA_NESTED }, 103162306a36Sopenharmony_ci [TCA_TAPRIO_ATTR_SCHED_CLOCKID] = { .type = NLA_S32 }, 103262306a36Sopenharmony_ci [TCA_TAPRIO_ATTR_SCHED_CYCLE_TIME] = 103362306a36Sopenharmony_ci NLA_POLICY_FULL_RANGE_SIGNED(NLA_S64, &taprio_cycle_time_range), 103462306a36Sopenharmony_ci [TCA_TAPRIO_ATTR_SCHED_CYCLE_TIME_EXTENSION] = { .type = NLA_S64 }, 103562306a36Sopenharmony_ci [TCA_TAPRIO_ATTR_FLAGS] = { .type = NLA_U32 }, 103662306a36Sopenharmony_ci [TCA_TAPRIO_ATTR_TXTIME_DELAY] = { .type = NLA_U32 }, 103762306a36Sopenharmony_ci [TCA_TAPRIO_ATTR_TC_ENTRY] = { .type = NLA_NESTED }, 103862306a36Sopenharmony_ci}; 103962306a36Sopenharmony_ci 104062306a36Sopenharmony_cistatic int fill_sched_entry(struct taprio_sched *q, struct nlattr **tb, 104162306a36Sopenharmony_ci struct sched_entry *entry, 104262306a36Sopenharmony_ci struct netlink_ext_ack *extack) 104362306a36Sopenharmony_ci{ 104462306a36Sopenharmony_ci int min_duration = length_to_duration(q, ETH_ZLEN); 104562306a36Sopenharmony_ci u32 interval = 0; 104662306a36Sopenharmony_ci 104762306a36Sopenharmony_ci if (tb[TCA_TAPRIO_SCHED_ENTRY_CMD]) 104862306a36Sopenharmony_ci entry->command = nla_get_u8( 104962306a36Sopenharmony_ci tb[TCA_TAPRIO_SCHED_ENTRY_CMD]); 105062306a36Sopenharmony_ci 105162306a36Sopenharmony_ci if (tb[TCA_TAPRIO_SCHED_ENTRY_GATE_MASK]) 105262306a36Sopenharmony_ci entry->gate_mask = nla_get_u32( 105362306a36Sopenharmony_ci tb[TCA_TAPRIO_SCHED_ENTRY_GATE_MASK]); 105462306a36Sopenharmony_ci 105562306a36Sopenharmony_ci if (tb[TCA_TAPRIO_SCHED_ENTRY_INTERVAL]) 105662306a36Sopenharmony_ci interval = nla_get_u32( 105762306a36Sopenharmony_ci tb[TCA_TAPRIO_SCHED_ENTRY_INTERVAL]); 105862306a36Sopenharmony_ci 105962306a36Sopenharmony_ci /* The interval should allow at least the minimum ethernet 106062306a36Sopenharmony_ci * frame to go out. 106162306a36Sopenharmony_ci */ 106262306a36Sopenharmony_ci if (interval < min_duration) { 106362306a36Sopenharmony_ci NL_SET_ERR_MSG(extack, "Invalid interval for schedule entry"); 106462306a36Sopenharmony_ci return -EINVAL; 106562306a36Sopenharmony_ci } 106662306a36Sopenharmony_ci 106762306a36Sopenharmony_ci entry->interval = interval; 106862306a36Sopenharmony_ci 106962306a36Sopenharmony_ci return 0; 107062306a36Sopenharmony_ci} 107162306a36Sopenharmony_ci 107262306a36Sopenharmony_cistatic int parse_sched_entry(struct taprio_sched *q, struct nlattr *n, 107362306a36Sopenharmony_ci struct sched_entry *entry, int index, 107462306a36Sopenharmony_ci struct netlink_ext_ack *extack) 107562306a36Sopenharmony_ci{ 107662306a36Sopenharmony_ci struct nlattr *tb[TCA_TAPRIO_SCHED_ENTRY_MAX + 1] = { }; 107762306a36Sopenharmony_ci int err; 107862306a36Sopenharmony_ci 107962306a36Sopenharmony_ci err = nla_parse_nested_deprecated(tb, TCA_TAPRIO_SCHED_ENTRY_MAX, n, 108062306a36Sopenharmony_ci entry_policy, NULL); 108162306a36Sopenharmony_ci if (err < 0) { 108262306a36Sopenharmony_ci NL_SET_ERR_MSG(extack, "Could not parse nested entry"); 108362306a36Sopenharmony_ci return -EINVAL; 108462306a36Sopenharmony_ci } 108562306a36Sopenharmony_ci 108662306a36Sopenharmony_ci entry->index = index; 108762306a36Sopenharmony_ci 108862306a36Sopenharmony_ci return fill_sched_entry(q, tb, entry, extack); 108962306a36Sopenharmony_ci} 109062306a36Sopenharmony_ci 109162306a36Sopenharmony_cistatic int parse_sched_list(struct taprio_sched *q, struct nlattr *list, 109262306a36Sopenharmony_ci struct sched_gate_list *sched, 109362306a36Sopenharmony_ci struct netlink_ext_ack *extack) 109462306a36Sopenharmony_ci{ 109562306a36Sopenharmony_ci struct nlattr *n; 109662306a36Sopenharmony_ci int err, rem; 109762306a36Sopenharmony_ci int i = 0; 109862306a36Sopenharmony_ci 109962306a36Sopenharmony_ci if (!list) 110062306a36Sopenharmony_ci return -EINVAL; 110162306a36Sopenharmony_ci 110262306a36Sopenharmony_ci nla_for_each_nested(n, list, rem) { 110362306a36Sopenharmony_ci struct sched_entry *entry; 110462306a36Sopenharmony_ci 110562306a36Sopenharmony_ci if (nla_type(n) != TCA_TAPRIO_SCHED_ENTRY) { 110662306a36Sopenharmony_ci NL_SET_ERR_MSG(extack, "Attribute is not of type 'entry'"); 110762306a36Sopenharmony_ci continue; 110862306a36Sopenharmony_ci } 110962306a36Sopenharmony_ci 111062306a36Sopenharmony_ci entry = kzalloc(sizeof(*entry), GFP_KERNEL); 111162306a36Sopenharmony_ci if (!entry) { 111262306a36Sopenharmony_ci NL_SET_ERR_MSG(extack, "Not enough memory for entry"); 111362306a36Sopenharmony_ci return -ENOMEM; 111462306a36Sopenharmony_ci } 111562306a36Sopenharmony_ci 111662306a36Sopenharmony_ci err = parse_sched_entry(q, n, entry, i, extack); 111762306a36Sopenharmony_ci if (err < 0) { 111862306a36Sopenharmony_ci kfree(entry); 111962306a36Sopenharmony_ci return err; 112062306a36Sopenharmony_ci } 112162306a36Sopenharmony_ci 112262306a36Sopenharmony_ci list_add_tail(&entry->list, &sched->entries); 112362306a36Sopenharmony_ci i++; 112462306a36Sopenharmony_ci } 112562306a36Sopenharmony_ci 112662306a36Sopenharmony_ci sched->num_entries = i; 112762306a36Sopenharmony_ci 112862306a36Sopenharmony_ci return i; 112962306a36Sopenharmony_ci} 113062306a36Sopenharmony_ci 113162306a36Sopenharmony_cistatic int parse_taprio_schedule(struct taprio_sched *q, struct nlattr **tb, 113262306a36Sopenharmony_ci struct sched_gate_list *new, 113362306a36Sopenharmony_ci struct netlink_ext_ack *extack) 113462306a36Sopenharmony_ci{ 113562306a36Sopenharmony_ci int err = 0; 113662306a36Sopenharmony_ci 113762306a36Sopenharmony_ci if (tb[TCA_TAPRIO_ATTR_SCHED_SINGLE_ENTRY]) { 113862306a36Sopenharmony_ci NL_SET_ERR_MSG(extack, "Adding a single entry is not supported"); 113962306a36Sopenharmony_ci return -ENOTSUPP; 114062306a36Sopenharmony_ci } 114162306a36Sopenharmony_ci 114262306a36Sopenharmony_ci if (tb[TCA_TAPRIO_ATTR_SCHED_BASE_TIME]) 114362306a36Sopenharmony_ci new->base_time = nla_get_s64(tb[TCA_TAPRIO_ATTR_SCHED_BASE_TIME]); 114462306a36Sopenharmony_ci 114562306a36Sopenharmony_ci if (tb[TCA_TAPRIO_ATTR_SCHED_CYCLE_TIME_EXTENSION]) 114662306a36Sopenharmony_ci new->cycle_time_extension = nla_get_s64(tb[TCA_TAPRIO_ATTR_SCHED_CYCLE_TIME_EXTENSION]); 114762306a36Sopenharmony_ci 114862306a36Sopenharmony_ci if (tb[TCA_TAPRIO_ATTR_SCHED_CYCLE_TIME]) 114962306a36Sopenharmony_ci new->cycle_time = nla_get_s64(tb[TCA_TAPRIO_ATTR_SCHED_CYCLE_TIME]); 115062306a36Sopenharmony_ci 115162306a36Sopenharmony_ci if (tb[TCA_TAPRIO_ATTR_SCHED_ENTRY_LIST]) 115262306a36Sopenharmony_ci err = parse_sched_list(q, tb[TCA_TAPRIO_ATTR_SCHED_ENTRY_LIST], 115362306a36Sopenharmony_ci new, extack); 115462306a36Sopenharmony_ci if (err < 0) 115562306a36Sopenharmony_ci return err; 115662306a36Sopenharmony_ci 115762306a36Sopenharmony_ci if (!new->cycle_time) { 115862306a36Sopenharmony_ci struct sched_entry *entry; 115962306a36Sopenharmony_ci ktime_t cycle = 0; 116062306a36Sopenharmony_ci 116162306a36Sopenharmony_ci list_for_each_entry(entry, &new->entries, list) 116262306a36Sopenharmony_ci cycle = ktime_add_ns(cycle, entry->interval); 116362306a36Sopenharmony_ci 116462306a36Sopenharmony_ci if (!cycle) { 116562306a36Sopenharmony_ci NL_SET_ERR_MSG(extack, "'cycle_time' can never be 0"); 116662306a36Sopenharmony_ci return -EINVAL; 116762306a36Sopenharmony_ci } 116862306a36Sopenharmony_ci 116962306a36Sopenharmony_ci if (cycle < 0 || cycle > INT_MAX) { 117062306a36Sopenharmony_ci NL_SET_ERR_MSG(extack, "'cycle_time' is too big"); 117162306a36Sopenharmony_ci return -EINVAL; 117262306a36Sopenharmony_ci } 117362306a36Sopenharmony_ci 117462306a36Sopenharmony_ci new->cycle_time = cycle; 117562306a36Sopenharmony_ci } 117662306a36Sopenharmony_ci 117762306a36Sopenharmony_ci taprio_calculate_gate_durations(q, new); 117862306a36Sopenharmony_ci 117962306a36Sopenharmony_ci return 0; 118062306a36Sopenharmony_ci} 118162306a36Sopenharmony_ci 118262306a36Sopenharmony_cistatic int taprio_parse_mqprio_opt(struct net_device *dev, 118362306a36Sopenharmony_ci struct tc_mqprio_qopt *qopt, 118462306a36Sopenharmony_ci struct netlink_ext_ack *extack, 118562306a36Sopenharmony_ci u32 taprio_flags) 118662306a36Sopenharmony_ci{ 118762306a36Sopenharmony_ci bool allow_overlapping_txqs = TXTIME_ASSIST_IS_ENABLED(taprio_flags); 118862306a36Sopenharmony_ci 118962306a36Sopenharmony_ci if (!qopt && !dev->num_tc) { 119062306a36Sopenharmony_ci NL_SET_ERR_MSG(extack, "'mqprio' configuration is necessary"); 119162306a36Sopenharmony_ci return -EINVAL; 119262306a36Sopenharmony_ci } 119362306a36Sopenharmony_ci 119462306a36Sopenharmony_ci /* If num_tc is already set, it means that the user already 119562306a36Sopenharmony_ci * configured the mqprio part 119662306a36Sopenharmony_ci */ 119762306a36Sopenharmony_ci if (dev->num_tc) 119862306a36Sopenharmony_ci return 0; 119962306a36Sopenharmony_ci 120062306a36Sopenharmony_ci /* taprio imposes that traffic classes map 1:n to tx queues */ 120162306a36Sopenharmony_ci if (qopt->num_tc > dev->num_tx_queues) { 120262306a36Sopenharmony_ci NL_SET_ERR_MSG(extack, "Number of traffic classes is greater than number of HW queues"); 120362306a36Sopenharmony_ci return -EINVAL; 120462306a36Sopenharmony_ci } 120562306a36Sopenharmony_ci 120662306a36Sopenharmony_ci /* For some reason, in txtime-assist mode, we allow TXQ ranges for 120762306a36Sopenharmony_ci * different TCs to overlap, and just validate the TXQ ranges. 120862306a36Sopenharmony_ci */ 120962306a36Sopenharmony_ci return mqprio_validate_qopt(dev, qopt, true, allow_overlapping_txqs, 121062306a36Sopenharmony_ci extack); 121162306a36Sopenharmony_ci} 121262306a36Sopenharmony_ci 121362306a36Sopenharmony_cistatic int taprio_get_start_time(struct Qdisc *sch, 121462306a36Sopenharmony_ci struct sched_gate_list *sched, 121562306a36Sopenharmony_ci ktime_t *start) 121662306a36Sopenharmony_ci{ 121762306a36Sopenharmony_ci struct taprio_sched *q = qdisc_priv(sch); 121862306a36Sopenharmony_ci ktime_t now, base, cycle; 121962306a36Sopenharmony_ci s64 n; 122062306a36Sopenharmony_ci 122162306a36Sopenharmony_ci base = sched_base_time(sched); 122262306a36Sopenharmony_ci now = taprio_get_time(q); 122362306a36Sopenharmony_ci 122462306a36Sopenharmony_ci if (ktime_after(base, now)) { 122562306a36Sopenharmony_ci *start = base; 122662306a36Sopenharmony_ci return 0; 122762306a36Sopenharmony_ci } 122862306a36Sopenharmony_ci 122962306a36Sopenharmony_ci cycle = sched->cycle_time; 123062306a36Sopenharmony_ci 123162306a36Sopenharmony_ci /* The qdisc is expected to have at least one sched_entry. Moreover, 123262306a36Sopenharmony_ci * any entry must have 'interval' > 0. Thus if the cycle time is zero, 123362306a36Sopenharmony_ci * something went really wrong. In that case, we should warn about this 123462306a36Sopenharmony_ci * inconsistent state and return error. 123562306a36Sopenharmony_ci */ 123662306a36Sopenharmony_ci if (WARN_ON(!cycle)) 123762306a36Sopenharmony_ci return -EFAULT; 123862306a36Sopenharmony_ci 123962306a36Sopenharmony_ci /* Schedule the start time for the beginning of the next 124062306a36Sopenharmony_ci * cycle. 124162306a36Sopenharmony_ci */ 124262306a36Sopenharmony_ci n = div64_s64(ktime_sub_ns(now, base), cycle); 124362306a36Sopenharmony_ci *start = ktime_add_ns(base, (n + 1) * cycle); 124462306a36Sopenharmony_ci return 0; 124562306a36Sopenharmony_ci} 124662306a36Sopenharmony_ci 124762306a36Sopenharmony_cistatic void setup_first_end_time(struct taprio_sched *q, 124862306a36Sopenharmony_ci struct sched_gate_list *sched, ktime_t base) 124962306a36Sopenharmony_ci{ 125062306a36Sopenharmony_ci struct net_device *dev = qdisc_dev(q->root); 125162306a36Sopenharmony_ci int num_tc = netdev_get_num_tc(dev); 125262306a36Sopenharmony_ci struct sched_entry *first; 125362306a36Sopenharmony_ci ktime_t cycle; 125462306a36Sopenharmony_ci int tc; 125562306a36Sopenharmony_ci 125662306a36Sopenharmony_ci first = list_first_entry(&sched->entries, 125762306a36Sopenharmony_ci struct sched_entry, list); 125862306a36Sopenharmony_ci 125962306a36Sopenharmony_ci cycle = sched->cycle_time; 126062306a36Sopenharmony_ci 126162306a36Sopenharmony_ci /* FIXME: find a better place to do this */ 126262306a36Sopenharmony_ci sched->cycle_end_time = ktime_add_ns(base, cycle); 126362306a36Sopenharmony_ci 126462306a36Sopenharmony_ci first->end_time = ktime_add_ns(base, first->interval); 126562306a36Sopenharmony_ci taprio_set_budgets(q, sched, first); 126662306a36Sopenharmony_ci 126762306a36Sopenharmony_ci for (tc = 0; tc < num_tc; tc++) { 126862306a36Sopenharmony_ci if (first->gate_duration[tc] == sched->cycle_time) 126962306a36Sopenharmony_ci first->gate_close_time[tc] = KTIME_MAX; 127062306a36Sopenharmony_ci else 127162306a36Sopenharmony_ci first->gate_close_time[tc] = ktime_add_ns(base, first->gate_duration[tc]); 127262306a36Sopenharmony_ci } 127362306a36Sopenharmony_ci 127462306a36Sopenharmony_ci rcu_assign_pointer(q->current_entry, NULL); 127562306a36Sopenharmony_ci} 127662306a36Sopenharmony_ci 127762306a36Sopenharmony_cistatic void taprio_start_sched(struct Qdisc *sch, 127862306a36Sopenharmony_ci ktime_t start, struct sched_gate_list *new) 127962306a36Sopenharmony_ci{ 128062306a36Sopenharmony_ci struct taprio_sched *q = qdisc_priv(sch); 128162306a36Sopenharmony_ci ktime_t expires; 128262306a36Sopenharmony_ci 128362306a36Sopenharmony_ci if (FULL_OFFLOAD_IS_ENABLED(q->flags)) 128462306a36Sopenharmony_ci return; 128562306a36Sopenharmony_ci 128662306a36Sopenharmony_ci expires = hrtimer_get_expires(&q->advance_timer); 128762306a36Sopenharmony_ci if (expires == 0) 128862306a36Sopenharmony_ci expires = KTIME_MAX; 128962306a36Sopenharmony_ci 129062306a36Sopenharmony_ci /* If the new schedule starts before the next expiration, we 129162306a36Sopenharmony_ci * reprogram it to the earliest one, so we change the admin 129262306a36Sopenharmony_ci * schedule to the operational one at the right time. 129362306a36Sopenharmony_ci */ 129462306a36Sopenharmony_ci start = min_t(ktime_t, start, expires); 129562306a36Sopenharmony_ci 129662306a36Sopenharmony_ci hrtimer_start(&q->advance_timer, start, HRTIMER_MODE_ABS); 129762306a36Sopenharmony_ci} 129862306a36Sopenharmony_ci 129962306a36Sopenharmony_cistatic void taprio_set_picos_per_byte(struct net_device *dev, 130062306a36Sopenharmony_ci struct taprio_sched *q) 130162306a36Sopenharmony_ci{ 130262306a36Sopenharmony_ci struct ethtool_link_ksettings ecmd; 130362306a36Sopenharmony_ci int speed = SPEED_10; 130462306a36Sopenharmony_ci int picos_per_byte; 130562306a36Sopenharmony_ci int err; 130662306a36Sopenharmony_ci 130762306a36Sopenharmony_ci err = __ethtool_get_link_ksettings(dev, &ecmd); 130862306a36Sopenharmony_ci if (err < 0) 130962306a36Sopenharmony_ci goto skip; 131062306a36Sopenharmony_ci 131162306a36Sopenharmony_ci if (ecmd.base.speed && ecmd.base.speed != SPEED_UNKNOWN) 131262306a36Sopenharmony_ci speed = ecmd.base.speed; 131362306a36Sopenharmony_ci 131462306a36Sopenharmony_ciskip: 131562306a36Sopenharmony_ci picos_per_byte = (USEC_PER_SEC * 8) / speed; 131662306a36Sopenharmony_ci 131762306a36Sopenharmony_ci atomic64_set(&q->picos_per_byte, picos_per_byte); 131862306a36Sopenharmony_ci netdev_dbg(dev, "taprio: set %s's picos_per_byte to: %lld, linkspeed: %d\n", 131962306a36Sopenharmony_ci dev->name, (long long)atomic64_read(&q->picos_per_byte), 132062306a36Sopenharmony_ci ecmd.base.speed); 132162306a36Sopenharmony_ci} 132262306a36Sopenharmony_ci 132362306a36Sopenharmony_cistatic int taprio_dev_notifier(struct notifier_block *nb, unsigned long event, 132462306a36Sopenharmony_ci void *ptr) 132562306a36Sopenharmony_ci{ 132662306a36Sopenharmony_ci struct net_device *dev = netdev_notifier_info_to_dev(ptr); 132762306a36Sopenharmony_ci struct sched_gate_list *oper, *admin; 132862306a36Sopenharmony_ci struct qdisc_size_table *stab; 132962306a36Sopenharmony_ci struct taprio_sched *q; 133062306a36Sopenharmony_ci 133162306a36Sopenharmony_ci ASSERT_RTNL(); 133262306a36Sopenharmony_ci 133362306a36Sopenharmony_ci if (event != NETDEV_UP && event != NETDEV_CHANGE) 133462306a36Sopenharmony_ci return NOTIFY_DONE; 133562306a36Sopenharmony_ci 133662306a36Sopenharmony_ci list_for_each_entry(q, &taprio_list, taprio_list) { 133762306a36Sopenharmony_ci if (dev != qdisc_dev(q->root)) 133862306a36Sopenharmony_ci continue; 133962306a36Sopenharmony_ci 134062306a36Sopenharmony_ci taprio_set_picos_per_byte(dev, q); 134162306a36Sopenharmony_ci 134262306a36Sopenharmony_ci stab = rtnl_dereference(q->root->stab); 134362306a36Sopenharmony_ci 134462306a36Sopenharmony_ci oper = rtnl_dereference(q->oper_sched); 134562306a36Sopenharmony_ci if (oper) 134662306a36Sopenharmony_ci taprio_update_queue_max_sdu(q, oper, stab); 134762306a36Sopenharmony_ci 134862306a36Sopenharmony_ci admin = rtnl_dereference(q->admin_sched); 134962306a36Sopenharmony_ci if (admin) 135062306a36Sopenharmony_ci taprio_update_queue_max_sdu(q, admin, stab); 135162306a36Sopenharmony_ci 135262306a36Sopenharmony_ci break; 135362306a36Sopenharmony_ci } 135462306a36Sopenharmony_ci 135562306a36Sopenharmony_ci return NOTIFY_DONE; 135662306a36Sopenharmony_ci} 135762306a36Sopenharmony_ci 135862306a36Sopenharmony_cistatic void setup_txtime(struct taprio_sched *q, 135962306a36Sopenharmony_ci struct sched_gate_list *sched, ktime_t base) 136062306a36Sopenharmony_ci{ 136162306a36Sopenharmony_ci struct sched_entry *entry; 136262306a36Sopenharmony_ci u64 interval = 0; 136362306a36Sopenharmony_ci 136462306a36Sopenharmony_ci list_for_each_entry(entry, &sched->entries, list) { 136562306a36Sopenharmony_ci entry->next_txtime = ktime_add_ns(base, interval); 136662306a36Sopenharmony_ci interval += entry->interval; 136762306a36Sopenharmony_ci } 136862306a36Sopenharmony_ci} 136962306a36Sopenharmony_ci 137062306a36Sopenharmony_cistatic struct tc_taprio_qopt_offload *taprio_offload_alloc(int num_entries) 137162306a36Sopenharmony_ci{ 137262306a36Sopenharmony_ci struct __tc_taprio_qopt_offload *__offload; 137362306a36Sopenharmony_ci 137462306a36Sopenharmony_ci __offload = kzalloc(struct_size(__offload, offload.entries, num_entries), 137562306a36Sopenharmony_ci GFP_KERNEL); 137662306a36Sopenharmony_ci if (!__offload) 137762306a36Sopenharmony_ci return NULL; 137862306a36Sopenharmony_ci 137962306a36Sopenharmony_ci refcount_set(&__offload->users, 1); 138062306a36Sopenharmony_ci 138162306a36Sopenharmony_ci return &__offload->offload; 138262306a36Sopenharmony_ci} 138362306a36Sopenharmony_ci 138462306a36Sopenharmony_cistruct tc_taprio_qopt_offload *taprio_offload_get(struct tc_taprio_qopt_offload 138562306a36Sopenharmony_ci *offload) 138662306a36Sopenharmony_ci{ 138762306a36Sopenharmony_ci struct __tc_taprio_qopt_offload *__offload; 138862306a36Sopenharmony_ci 138962306a36Sopenharmony_ci __offload = container_of(offload, struct __tc_taprio_qopt_offload, 139062306a36Sopenharmony_ci offload); 139162306a36Sopenharmony_ci 139262306a36Sopenharmony_ci refcount_inc(&__offload->users); 139362306a36Sopenharmony_ci 139462306a36Sopenharmony_ci return offload; 139562306a36Sopenharmony_ci} 139662306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(taprio_offload_get); 139762306a36Sopenharmony_ci 139862306a36Sopenharmony_civoid taprio_offload_free(struct tc_taprio_qopt_offload *offload) 139962306a36Sopenharmony_ci{ 140062306a36Sopenharmony_ci struct __tc_taprio_qopt_offload *__offload; 140162306a36Sopenharmony_ci 140262306a36Sopenharmony_ci __offload = container_of(offload, struct __tc_taprio_qopt_offload, 140362306a36Sopenharmony_ci offload); 140462306a36Sopenharmony_ci 140562306a36Sopenharmony_ci if (!refcount_dec_and_test(&__offload->users)) 140662306a36Sopenharmony_ci return; 140762306a36Sopenharmony_ci 140862306a36Sopenharmony_ci kfree(__offload); 140962306a36Sopenharmony_ci} 141062306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(taprio_offload_free); 141162306a36Sopenharmony_ci 141262306a36Sopenharmony_ci/* The function will only serve to keep the pointers to the "oper" and "admin" 141362306a36Sopenharmony_ci * schedules valid in relation to their base times, so when calling dump() the 141462306a36Sopenharmony_ci * users looks at the right schedules. 141562306a36Sopenharmony_ci * When using full offload, the admin configuration is promoted to oper at the 141662306a36Sopenharmony_ci * base_time in the PHC time domain. But because the system time is not 141762306a36Sopenharmony_ci * necessarily in sync with that, we can't just trigger a hrtimer to call 141862306a36Sopenharmony_ci * switch_schedules at the right hardware time. 141962306a36Sopenharmony_ci * At the moment we call this by hand right away from taprio, but in the future 142062306a36Sopenharmony_ci * it will be useful to create a mechanism for drivers to notify taprio of the 142162306a36Sopenharmony_ci * offload state (PENDING, ACTIVE, INACTIVE) so it can be visible in dump(). 142262306a36Sopenharmony_ci * This is left as TODO. 142362306a36Sopenharmony_ci */ 142462306a36Sopenharmony_cistatic void taprio_offload_config_changed(struct taprio_sched *q) 142562306a36Sopenharmony_ci{ 142662306a36Sopenharmony_ci struct sched_gate_list *oper, *admin; 142762306a36Sopenharmony_ci 142862306a36Sopenharmony_ci oper = rtnl_dereference(q->oper_sched); 142962306a36Sopenharmony_ci admin = rtnl_dereference(q->admin_sched); 143062306a36Sopenharmony_ci 143162306a36Sopenharmony_ci switch_schedules(q, &admin, &oper); 143262306a36Sopenharmony_ci} 143362306a36Sopenharmony_ci 143462306a36Sopenharmony_cistatic u32 tc_map_to_queue_mask(struct net_device *dev, u32 tc_mask) 143562306a36Sopenharmony_ci{ 143662306a36Sopenharmony_ci u32 i, queue_mask = 0; 143762306a36Sopenharmony_ci 143862306a36Sopenharmony_ci for (i = 0; i < dev->num_tc; i++) { 143962306a36Sopenharmony_ci u32 offset, count; 144062306a36Sopenharmony_ci 144162306a36Sopenharmony_ci if (!(tc_mask & BIT(i))) 144262306a36Sopenharmony_ci continue; 144362306a36Sopenharmony_ci 144462306a36Sopenharmony_ci offset = dev->tc_to_txq[i].offset; 144562306a36Sopenharmony_ci count = dev->tc_to_txq[i].count; 144662306a36Sopenharmony_ci 144762306a36Sopenharmony_ci queue_mask |= GENMASK(offset + count - 1, offset); 144862306a36Sopenharmony_ci } 144962306a36Sopenharmony_ci 145062306a36Sopenharmony_ci return queue_mask; 145162306a36Sopenharmony_ci} 145262306a36Sopenharmony_ci 145362306a36Sopenharmony_cistatic void taprio_sched_to_offload(struct net_device *dev, 145462306a36Sopenharmony_ci struct sched_gate_list *sched, 145562306a36Sopenharmony_ci struct tc_taprio_qopt_offload *offload, 145662306a36Sopenharmony_ci const struct tc_taprio_caps *caps) 145762306a36Sopenharmony_ci{ 145862306a36Sopenharmony_ci struct sched_entry *entry; 145962306a36Sopenharmony_ci int i = 0; 146062306a36Sopenharmony_ci 146162306a36Sopenharmony_ci offload->base_time = sched->base_time; 146262306a36Sopenharmony_ci offload->cycle_time = sched->cycle_time; 146362306a36Sopenharmony_ci offload->cycle_time_extension = sched->cycle_time_extension; 146462306a36Sopenharmony_ci 146562306a36Sopenharmony_ci list_for_each_entry(entry, &sched->entries, list) { 146662306a36Sopenharmony_ci struct tc_taprio_sched_entry *e = &offload->entries[i]; 146762306a36Sopenharmony_ci 146862306a36Sopenharmony_ci e->command = entry->command; 146962306a36Sopenharmony_ci e->interval = entry->interval; 147062306a36Sopenharmony_ci if (caps->gate_mask_per_txq) 147162306a36Sopenharmony_ci e->gate_mask = tc_map_to_queue_mask(dev, 147262306a36Sopenharmony_ci entry->gate_mask); 147362306a36Sopenharmony_ci else 147462306a36Sopenharmony_ci e->gate_mask = entry->gate_mask; 147562306a36Sopenharmony_ci 147662306a36Sopenharmony_ci i++; 147762306a36Sopenharmony_ci } 147862306a36Sopenharmony_ci 147962306a36Sopenharmony_ci offload->num_entries = i; 148062306a36Sopenharmony_ci} 148162306a36Sopenharmony_ci 148262306a36Sopenharmony_cistatic void taprio_detect_broken_mqprio(struct taprio_sched *q) 148362306a36Sopenharmony_ci{ 148462306a36Sopenharmony_ci struct net_device *dev = qdisc_dev(q->root); 148562306a36Sopenharmony_ci struct tc_taprio_caps caps; 148662306a36Sopenharmony_ci 148762306a36Sopenharmony_ci qdisc_offload_query_caps(dev, TC_SETUP_QDISC_TAPRIO, 148862306a36Sopenharmony_ci &caps, sizeof(caps)); 148962306a36Sopenharmony_ci 149062306a36Sopenharmony_ci q->broken_mqprio = caps.broken_mqprio; 149162306a36Sopenharmony_ci if (q->broken_mqprio) 149262306a36Sopenharmony_ci static_branch_inc(&taprio_have_broken_mqprio); 149362306a36Sopenharmony_ci else 149462306a36Sopenharmony_ci static_branch_inc(&taprio_have_working_mqprio); 149562306a36Sopenharmony_ci 149662306a36Sopenharmony_ci q->detected_mqprio = true; 149762306a36Sopenharmony_ci} 149862306a36Sopenharmony_ci 149962306a36Sopenharmony_cistatic void taprio_cleanup_broken_mqprio(struct taprio_sched *q) 150062306a36Sopenharmony_ci{ 150162306a36Sopenharmony_ci if (!q->detected_mqprio) 150262306a36Sopenharmony_ci return; 150362306a36Sopenharmony_ci 150462306a36Sopenharmony_ci if (q->broken_mqprio) 150562306a36Sopenharmony_ci static_branch_dec(&taprio_have_broken_mqprio); 150662306a36Sopenharmony_ci else 150762306a36Sopenharmony_ci static_branch_dec(&taprio_have_working_mqprio); 150862306a36Sopenharmony_ci} 150962306a36Sopenharmony_ci 151062306a36Sopenharmony_cistatic int taprio_enable_offload(struct net_device *dev, 151162306a36Sopenharmony_ci struct taprio_sched *q, 151262306a36Sopenharmony_ci struct sched_gate_list *sched, 151362306a36Sopenharmony_ci struct netlink_ext_ack *extack) 151462306a36Sopenharmony_ci{ 151562306a36Sopenharmony_ci const struct net_device_ops *ops = dev->netdev_ops; 151662306a36Sopenharmony_ci struct tc_taprio_qopt_offload *offload; 151762306a36Sopenharmony_ci struct tc_taprio_caps caps; 151862306a36Sopenharmony_ci int tc, err = 0; 151962306a36Sopenharmony_ci 152062306a36Sopenharmony_ci if (!ops->ndo_setup_tc) { 152162306a36Sopenharmony_ci NL_SET_ERR_MSG(extack, 152262306a36Sopenharmony_ci "Device does not support taprio offload"); 152362306a36Sopenharmony_ci return -EOPNOTSUPP; 152462306a36Sopenharmony_ci } 152562306a36Sopenharmony_ci 152662306a36Sopenharmony_ci qdisc_offload_query_caps(dev, TC_SETUP_QDISC_TAPRIO, 152762306a36Sopenharmony_ci &caps, sizeof(caps)); 152862306a36Sopenharmony_ci 152962306a36Sopenharmony_ci if (!caps.supports_queue_max_sdu) { 153062306a36Sopenharmony_ci for (tc = 0; tc < TC_MAX_QUEUE; tc++) { 153162306a36Sopenharmony_ci if (q->max_sdu[tc]) { 153262306a36Sopenharmony_ci NL_SET_ERR_MSG_MOD(extack, 153362306a36Sopenharmony_ci "Device does not handle queueMaxSDU"); 153462306a36Sopenharmony_ci return -EOPNOTSUPP; 153562306a36Sopenharmony_ci } 153662306a36Sopenharmony_ci } 153762306a36Sopenharmony_ci } 153862306a36Sopenharmony_ci 153962306a36Sopenharmony_ci offload = taprio_offload_alloc(sched->num_entries); 154062306a36Sopenharmony_ci if (!offload) { 154162306a36Sopenharmony_ci NL_SET_ERR_MSG(extack, 154262306a36Sopenharmony_ci "Not enough memory for enabling offload mode"); 154362306a36Sopenharmony_ci return -ENOMEM; 154462306a36Sopenharmony_ci } 154562306a36Sopenharmony_ci offload->cmd = TAPRIO_CMD_REPLACE; 154662306a36Sopenharmony_ci offload->extack = extack; 154762306a36Sopenharmony_ci mqprio_qopt_reconstruct(dev, &offload->mqprio.qopt); 154862306a36Sopenharmony_ci offload->mqprio.extack = extack; 154962306a36Sopenharmony_ci taprio_sched_to_offload(dev, sched, offload, &caps); 155062306a36Sopenharmony_ci mqprio_fp_to_offload(q->fp, &offload->mqprio); 155162306a36Sopenharmony_ci 155262306a36Sopenharmony_ci for (tc = 0; tc < TC_MAX_QUEUE; tc++) 155362306a36Sopenharmony_ci offload->max_sdu[tc] = q->max_sdu[tc]; 155462306a36Sopenharmony_ci 155562306a36Sopenharmony_ci err = ops->ndo_setup_tc(dev, TC_SETUP_QDISC_TAPRIO, offload); 155662306a36Sopenharmony_ci if (err < 0) { 155762306a36Sopenharmony_ci NL_SET_ERR_MSG_WEAK(extack, 155862306a36Sopenharmony_ci "Device failed to setup taprio offload"); 155962306a36Sopenharmony_ci goto done; 156062306a36Sopenharmony_ci } 156162306a36Sopenharmony_ci 156262306a36Sopenharmony_ci q->offloaded = true; 156362306a36Sopenharmony_ci 156462306a36Sopenharmony_cidone: 156562306a36Sopenharmony_ci /* The offload structure may linger around via a reference taken by the 156662306a36Sopenharmony_ci * device driver, so clear up the netlink extack pointer so that the 156762306a36Sopenharmony_ci * driver isn't tempted to dereference data which stopped being valid 156862306a36Sopenharmony_ci */ 156962306a36Sopenharmony_ci offload->extack = NULL; 157062306a36Sopenharmony_ci offload->mqprio.extack = NULL; 157162306a36Sopenharmony_ci taprio_offload_free(offload); 157262306a36Sopenharmony_ci 157362306a36Sopenharmony_ci return err; 157462306a36Sopenharmony_ci} 157562306a36Sopenharmony_ci 157662306a36Sopenharmony_cistatic int taprio_disable_offload(struct net_device *dev, 157762306a36Sopenharmony_ci struct taprio_sched *q, 157862306a36Sopenharmony_ci struct netlink_ext_ack *extack) 157962306a36Sopenharmony_ci{ 158062306a36Sopenharmony_ci const struct net_device_ops *ops = dev->netdev_ops; 158162306a36Sopenharmony_ci struct tc_taprio_qopt_offload *offload; 158262306a36Sopenharmony_ci int err; 158362306a36Sopenharmony_ci 158462306a36Sopenharmony_ci if (!q->offloaded) 158562306a36Sopenharmony_ci return 0; 158662306a36Sopenharmony_ci 158762306a36Sopenharmony_ci offload = taprio_offload_alloc(0); 158862306a36Sopenharmony_ci if (!offload) { 158962306a36Sopenharmony_ci NL_SET_ERR_MSG(extack, 159062306a36Sopenharmony_ci "Not enough memory to disable offload mode"); 159162306a36Sopenharmony_ci return -ENOMEM; 159262306a36Sopenharmony_ci } 159362306a36Sopenharmony_ci offload->cmd = TAPRIO_CMD_DESTROY; 159462306a36Sopenharmony_ci 159562306a36Sopenharmony_ci err = ops->ndo_setup_tc(dev, TC_SETUP_QDISC_TAPRIO, offload); 159662306a36Sopenharmony_ci if (err < 0) { 159762306a36Sopenharmony_ci NL_SET_ERR_MSG(extack, 159862306a36Sopenharmony_ci "Device failed to disable offload"); 159962306a36Sopenharmony_ci goto out; 160062306a36Sopenharmony_ci } 160162306a36Sopenharmony_ci 160262306a36Sopenharmony_ci q->offloaded = false; 160362306a36Sopenharmony_ci 160462306a36Sopenharmony_ciout: 160562306a36Sopenharmony_ci taprio_offload_free(offload); 160662306a36Sopenharmony_ci 160762306a36Sopenharmony_ci return err; 160862306a36Sopenharmony_ci} 160962306a36Sopenharmony_ci 161062306a36Sopenharmony_ci/* If full offload is enabled, the only possible clockid is the net device's 161162306a36Sopenharmony_ci * PHC. For that reason, specifying a clockid through netlink is incorrect. 161262306a36Sopenharmony_ci * For txtime-assist, it is implicitly assumed that the device's PHC is kept 161362306a36Sopenharmony_ci * in sync with the specified clockid via a user space daemon such as phc2sys. 161462306a36Sopenharmony_ci * For both software taprio and txtime-assist, the clockid is used for the 161562306a36Sopenharmony_ci * hrtimer that advances the schedule and hence mandatory. 161662306a36Sopenharmony_ci */ 161762306a36Sopenharmony_cistatic int taprio_parse_clockid(struct Qdisc *sch, struct nlattr **tb, 161862306a36Sopenharmony_ci struct netlink_ext_ack *extack) 161962306a36Sopenharmony_ci{ 162062306a36Sopenharmony_ci struct taprio_sched *q = qdisc_priv(sch); 162162306a36Sopenharmony_ci struct net_device *dev = qdisc_dev(sch); 162262306a36Sopenharmony_ci int err = -EINVAL; 162362306a36Sopenharmony_ci 162462306a36Sopenharmony_ci if (FULL_OFFLOAD_IS_ENABLED(q->flags)) { 162562306a36Sopenharmony_ci const struct ethtool_ops *ops = dev->ethtool_ops; 162662306a36Sopenharmony_ci struct ethtool_ts_info info = { 162762306a36Sopenharmony_ci .cmd = ETHTOOL_GET_TS_INFO, 162862306a36Sopenharmony_ci .phc_index = -1, 162962306a36Sopenharmony_ci }; 163062306a36Sopenharmony_ci 163162306a36Sopenharmony_ci if (tb[TCA_TAPRIO_ATTR_SCHED_CLOCKID]) { 163262306a36Sopenharmony_ci NL_SET_ERR_MSG(extack, 163362306a36Sopenharmony_ci "The 'clockid' cannot be specified for full offload"); 163462306a36Sopenharmony_ci goto out; 163562306a36Sopenharmony_ci } 163662306a36Sopenharmony_ci 163762306a36Sopenharmony_ci if (ops && ops->get_ts_info) 163862306a36Sopenharmony_ci err = ops->get_ts_info(dev, &info); 163962306a36Sopenharmony_ci 164062306a36Sopenharmony_ci if (err || info.phc_index < 0) { 164162306a36Sopenharmony_ci NL_SET_ERR_MSG(extack, 164262306a36Sopenharmony_ci "Device does not have a PTP clock"); 164362306a36Sopenharmony_ci err = -ENOTSUPP; 164462306a36Sopenharmony_ci goto out; 164562306a36Sopenharmony_ci } 164662306a36Sopenharmony_ci } else if (tb[TCA_TAPRIO_ATTR_SCHED_CLOCKID]) { 164762306a36Sopenharmony_ci int clockid = nla_get_s32(tb[TCA_TAPRIO_ATTR_SCHED_CLOCKID]); 164862306a36Sopenharmony_ci enum tk_offsets tk_offset; 164962306a36Sopenharmony_ci 165062306a36Sopenharmony_ci /* We only support static clockids and we don't allow 165162306a36Sopenharmony_ci * for it to be modified after the first init. 165262306a36Sopenharmony_ci */ 165362306a36Sopenharmony_ci if (clockid < 0 || 165462306a36Sopenharmony_ci (q->clockid != -1 && q->clockid != clockid)) { 165562306a36Sopenharmony_ci NL_SET_ERR_MSG(extack, 165662306a36Sopenharmony_ci "Changing the 'clockid' of a running schedule is not supported"); 165762306a36Sopenharmony_ci err = -ENOTSUPP; 165862306a36Sopenharmony_ci goto out; 165962306a36Sopenharmony_ci } 166062306a36Sopenharmony_ci 166162306a36Sopenharmony_ci switch (clockid) { 166262306a36Sopenharmony_ci case CLOCK_REALTIME: 166362306a36Sopenharmony_ci tk_offset = TK_OFFS_REAL; 166462306a36Sopenharmony_ci break; 166562306a36Sopenharmony_ci case CLOCK_MONOTONIC: 166662306a36Sopenharmony_ci tk_offset = TK_OFFS_MAX; 166762306a36Sopenharmony_ci break; 166862306a36Sopenharmony_ci case CLOCK_BOOTTIME: 166962306a36Sopenharmony_ci tk_offset = TK_OFFS_BOOT; 167062306a36Sopenharmony_ci break; 167162306a36Sopenharmony_ci case CLOCK_TAI: 167262306a36Sopenharmony_ci tk_offset = TK_OFFS_TAI; 167362306a36Sopenharmony_ci break; 167462306a36Sopenharmony_ci default: 167562306a36Sopenharmony_ci NL_SET_ERR_MSG(extack, "Invalid 'clockid'"); 167662306a36Sopenharmony_ci err = -EINVAL; 167762306a36Sopenharmony_ci goto out; 167862306a36Sopenharmony_ci } 167962306a36Sopenharmony_ci /* This pairs with READ_ONCE() in taprio_mono_to_any */ 168062306a36Sopenharmony_ci WRITE_ONCE(q->tk_offset, tk_offset); 168162306a36Sopenharmony_ci 168262306a36Sopenharmony_ci q->clockid = clockid; 168362306a36Sopenharmony_ci } else { 168462306a36Sopenharmony_ci NL_SET_ERR_MSG(extack, "Specifying a 'clockid' is mandatory"); 168562306a36Sopenharmony_ci goto out; 168662306a36Sopenharmony_ci } 168762306a36Sopenharmony_ci 168862306a36Sopenharmony_ci /* Everything went ok, return success. */ 168962306a36Sopenharmony_ci err = 0; 169062306a36Sopenharmony_ci 169162306a36Sopenharmony_ciout: 169262306a36Sopenharmony_ci return err; 169362306a36Sopenharmony_ci} 169462306a36Sopenharmony_ci 169562306a36Sopenharmony_cistatic int taprio_parse_tc_entry(struct Qdisc *sch, 169662306a36Sopenharmony_ci struct nlattr *opt, 169762306a36Sopenharmony_ci u32 max_sdu[TC_QOPT_MAX_QUEUE], 169862306a36Sopenharmony_ci u32 fp[TC_QOPT_MAX_QUEUE], 169962306a36Sopenharmony_ci unsigned long *seen_tcs, 170062306a36Sopenharmony_ci struct netlink_ext_ack *extack) 170162306a36Sopenharmony_ci{ 170262306a36Sopenharmony_ci struct nlattr *tb[TCA_TAPRIO_TC_ENTRY_MAX + 1] = { }; 170362306a36Sopenharmony_ci struct net_device *dev = qdisc_dev(sch); 170462306a36Sopenharmony_ci int err, tc; 170562306a36Sopenharmony_ci u32 val; 170662306a36Sopenharmony_ci 170762306a36Sopenharmony_ci err = nla_parse_nested(tb, TCA_TAPRIO_TC_ENTRY_MAX, opt, 170862306a36Sopenharmony_ci taprio_tc_policy, extack); 170962306a36Sopenharmony_ci if (err < 0) 171062306a36Sopenharmony_ci return err; 171162306a36Sopenharmony_ci 171262306a36Sopenharmony_ci if (!tb[TCA_TAPRIO_TC_ENTRY_INDEX]) { 171362306a36Sopenharmony_ci NL_SET_ERR_MSG_MOD(extack, "TC entry index missing"); 171462306a36Sopenharmony_ci return -EINVAL; 171562306a36Sopenharmony_ci } 171662306a36Sopenharmony_ci 171762306a36Sopenharmony_ci tc = nla_get_u32(tb[TCA_TAPRIO_TC_ENTRY_INDEX]); 171862306a36Sopenharmony_ci if (tc >= TC_QOPT_MAX_QUEUE) { 171962306a36Sopenharmony_ci NL_SET_ERR_MSG_MOD(extack, "TC entry index out of range"); 172062306a36Sopenharmony_ci return -ERANGE; 172162306a36Sopenharmony_ci } 172262306a36Sopenharmony_ci 172362306a36Sopenharmony_ci if (*seen_tcs & BIT(tc)) { 172462306a36Sopenharmony_ci NL_SET_ERR_MSG_MOD(extack, "Duplicate TC entry"); 172562306a36Sopenharmony_ci return -EINVAL; 172662306a36Sopenharmony_ci } 172762306a36Sopenharmony_ci 172862306a36Sopenharmony_ci *seen_tcs |= BIT(tc); 172962306a36Sopenharmony_ci 173062306a36Sopenharmony_ci if (tb[TCA_TAPRIO_TC_ENTRY_MAX_SDU]) { 173162306a36Sopenharmony_ci val = nla_get_u32(tb[TCA_TAPRIO_TC_ENTRY_MAX_SDU]); 173262306a36Sopenharmony_ci if (val > dev->max_mtu) { 173362306a36Sopenharmony_ci NL_SET_ERR_MSG_MOD(extack, "TC max SDU exceeds device max MTU"); 173462306a36Sopenharmony_ci return -ERANGE; 173562306a36Sopenharmony_ci } 173662306a36Sopenharmony_ci 173762306a36Sopenharmony_ci max_sdu[tc] = val; 173862306a36Sopenharmony_ci } 173962306a36Sopenharmony_ci 174062306a36Sopenharmony_ci if (tb[TCA_TAPRIO_TC_ENTRY_FP]) 174162306a36Sopenharmony_ci fp[tc] = nla_get_u32(tb[TCA_TAPRIO_TC_ENTRY_FP]); 174262306a36Sopenharmony_ci 174362306a36Sopenharmony_ci return 0; 174462306a36Sopenharmony_ci} 174562306a36Sopenharmony_ci 174662306a36Sopenharmony_cistatic int taprio_parse_tc_entries(struct Qdisc *sch, 174762306a36Sopenharmony_ci struct nlattr *opt, 174862306a36Sopenharmony_ci struct netlink_ext_ack *extack) 174962306a36Sopenharmony_ci{ 175062306a36Sopenharmony_ci struct taprio_sched *q = qdisc_priv(sch); 175162306a36Sopenharmony_ci struct net_device *dev = qdisc_dev(sch); 175262306a36Sopenharmony_ci u32 max_sdu[TC_QOPT_MAX_QUEUE]; 175362306a36Sopenharmony_ci bool have_preemption = false; 175462306a36Sopenharmony_ci unsigned long seen_tcs = 0; 175562306a36Sopenharmony_ci u32 fp[TC_QOPT_MAX_QUEUE]; 175662306a36Sopenharmony_ci struct nlattr *n; 175762306a36Sopenharmony_ci int tc, rem; 175862306a36Sopenharmony_ci int err = 0; 175962306a36Sopenharmony_ci 176062306a36Sopenharmony_ci for (tc = 0; tc < TC_QOPT_MAX_QUEUE; tc++) { 176162306a36Sopenharmony_ci max_sdu[tc] = q->max_sdu[tc]; 176262306a36Sopenharmony_ci fp[tc] = q->fp[tc]; 176362306a36Sopenharmony_ci } 176462306a36Sopenharmony_ci 176562306a36Sopenharmony_ci nla_for_each_nested(n, opt, rem) { 176662306a36Sopenharmony_ci if (nla_type(n) != TCA_TAPRIO_ATTR_TC_ENTRY) 176762306a36Sopenharmony_ci continue; 176862306a36Sopenharmony_ci 176962306a36Sopenharmony_ci err = taprio_parse_tc_entry(sch, n, max_sdu, fp, &seen_tcs, 177062306a36Sopenharmony_ci extack); 177162306a36Sopenharmony_ci if (err) 177262306a36Sopenharmony_ci return err; 177362306a36Sopenharmony_ci } 177462306a36Sopenharmony_ci 177562306a36Sopenharmony_ci for (tc = 0; tc < TC_QOPT_MAX_QUEUE; tc++) { 177662306a36Sopenharmony_ci q->max_sdu[tc] = max_sdu[tc]; 177762306a36Sopenharmony_ci q->fp[tc] = fp[tc]; 177862306a36Sopenharmony_ci if (fp[tc] != TC_FP_EXPRESS) 177962306a36Sopenharmony_ci have_preemption = true; 178062306a36Sopenharmony_ci } 178162306a36Sopenharmony_ci 178262306a36Sopenharmony_ci if (have_preemption) { 178362306a36Sopenharmony_ci if (!FULL_OFFLOAD_IS_ENABLED(q->flags)) { 178462306a36Sopenharmony_ci NL_SET_ERR_MSG(extack, 178562306a36Sopenharmony_ci "Preemption only supported with full offload"); 178662306a36Sopenharmony_ci return -EOPNOTSUPP; 178762306a36Sopenharmony_ci } 178862306a36Sopenharmony_ci 178962306a36Sopenharmony_ci if (!ethtool_dev_mm_supported(dev)) { 179062306a36Sopenharmony_ci NL_SET_ERR_MSG(extack, 179162306a36Sopenharmony_ci "Device does not support preemption"); 179262306a36Sopenharmony_ci return -EOPNOTSUPP; 179362306a36Sopenharmony_ci } 179462306a36Sopenharmony_ci } 179562306a36Sopenharmony_ci 179662306a36Sopenharmony_ci return err; 179762306a36Sopenharmony_ci} 179862306a36Sopenharmony_ci 179962306a36Sopenharmony_cistatic int taprio_mqprio_cmp(const struct net_device *dev, 180062306a36Sopenharmony_ci const struct tc_mqprio_qopt *mqprio) 180162306a36Sopenharmony_ci{ 180262306a36Sopenharmony_ci int i; 180362306a36Sopenharmony_ci 180462306a36Sopenharmony_ci if (!mqprio || mqprio->num_tc != dev->num_tc) 180562306a36Sopenharmony_ci return -1; 180662306a36Sopenharmony_ci 180762306a36Sopenharmony_ci for (i = 0; i < mqprio->num_tc; i++) 180862306a36Sopenharmony_ci if (dev->tc_to_txq[i].count != mqprio->count[i] || 180962306a36Sopenharmony_ci dev->tc_to_txq[i].offset != mqprio->offset[i]) 181062306a36Sopenharmony_ci return -1; 181162306a36Sopenharmony_ci 181262306a36Sopenharmony_ci for (i = 0; i <= TC_BITMASK; i++) 181362306a36Sopenharmony_ci if (dev->prio_tc_map[i] != mqprio->prio_tc_map[i]) 181462306a36Sopenharmony_ci return -1; 181562306a36Sopenharmony_ci 181662306a36Sopenharmony_ci return 0; 181762306a36Sopenharmony_ci} 181862306a36Sopenharmony_ci 181962306a36Sopenharmony_ci/* The semantics of the 'flags' argument in relation to 'change()' 182062306a36Sopenharmony_ci * requests, are interpreted following two rules (which are applied in 182162306a36Sopenharmony_ci * this order): (1) an omitted 'flags' argument is interpreted as 182262306a36Sopenharmony_ci * zero; (2) the 'flags' of a "running" taprio instance cannot be 182362306a36Sopenharmony_ci * changed. 182462306a36Sopenharmony_ci */ 182562306a36Sopenharmony_cistatic int taprio_new_flags(const struct nlattr *attr, u32 old, 182662306a36Sopenharmony_ci struct netlink_ext_ack *extack) 182762306a36Sopenharmony_ci{ 182862306a36Sopenharmony_ci u32 new = 0; 182962306a36Sopenharmony_ci 183062306a36Sopenharmony_ci if (attr) 183162306a36Sopenharmony_ci new = nla_get_u32(attr); 183262306a36Sopenharmony_ci 183362306a36Sopenharmony_ci if (old != TAPRIO_FLAGS_INVALID && old != new) { 183462306a36Sopenharmony_ci NL_SET_ERR_MSG_MOD(extack, "Changing 'flags' of a running schedule is not supported"); 183562306a36Sopenharmony_ci return -EOPNOTSUPP; 183662306a36Sopenharmony_ci } 183762306a36Sopenharmony_ci 183862306a36Sopenharmony_ci if (!taprio_flags_valid(new)) { 183962306a36Sopenharmony_ci NL_SET_ERR_MSG_MOD(extack, "Specified 'flags' are not valid"); 184062306a36Sopenharmony_ci return -EINVAL; 184162306a36Sopenharmony_ci } 184262306a36Sopenharmony_ci 184362306a36Sopenharmony_ci return new; 184462306a36Sopenharmony_ci} 184562306a36Sopenharmony_ci 184662306a36Sopenharmony_cistatic int taprio_change(struct Qdisc *sch, struct nlattr *opt, 184762306a36Sopenharmony_ci struct netlink_ext_ack *extack) 184862306a36Sopenharmony_ci{ 184962306a36Sopenharmony_ci struct qdisc_size_table *stab = rtnl_dereference(sch->stab); 185062306a36Sopenharmony_ci struct nlattr *tb[TCA_TAPRIO_ATTR_MAX + 1] = { }; 185162306a36Sopenharmony_ci struct sched_gate_list *oper, *admin, *new_admin; 185262306a36Sopenharmony_ci struct taprio_sched *q = qdisc_priv(sch); 185362306a36Sopenharmony_ci struct net_device *dev = qdisc_dev(sch); 185462306a36Sopenharmony_ci struct tc_mqprio_qopt *mqprio = NULL; 185562306a36Sopenharmony_ci unsigned long flags; 185662306a36Sopenharmony_ci ktime_t start; 185762306a36Sopenharmony_ci int i, err; 185862306a36Sopenharmony_ci 185962306a36Sopenharmony_ci err = nla_parse_nested_deprecated(tb, TCA_TAPRIO_ATTR_MAX, opt, 186062306a36Sopenharmony_ci taprio_policy, extack); 186162306a36Sopenharmony_ci if (err < 0) 186262306a36Sopenharmony_ci return err; 186362306a36Sopenharmony_ci 186462306a36Sopenharmony_ci if (tb[TCA_TAPRIO_ATTR_PRIOMAP]) 186562306a36Sopenharmony_ci mqprio = nla_data(tb[TCA_TAPRIO_ATTR_PRIOMAP]); 186662306a36Sopenharmony_ci 186762306a36Sopenharmony_ci err = taprio_new_flags(tb[TCA_TAPRIO_ATTR_FLAGS], 186862306a36Sopenharmony_ci q->flags, extack); 186962306a36Sopenharmony_ci if (err < 0) 187062306a36Sopenharmony_ci return err; 187162306a36Sopenharmony_ci 187262306a36Sopenharmony_ci q->flags = err; 187362306a36Sopenharmony_ci 187462306a36Sopenharmony_ci err = taprio_parse_mqprio_opt(dev, mqprio, extack, q->flags); 187562306a36Sopenharmony_ci if (err < 0) 187662306a36Sopenharmony_ci return err; 187762306a36Sopenharmony_ci 187862306a36Sopenharmony_ci err = taprio_parse_tc_entries(sch, opt, extack); 187962306a36Sopenharmony_ci if (err) 188062306a36Sopenharmony_ci return err; 188162306a36Sopenharmony_ci 188262306a36Sopenharmony_ci new_admin = kzalloc(sizeof(*new_admin), GFP_KERNEL); 188362306a36Sopenharmony_ci if (!new_admin) { 188462306a36Sopenharmony_ci NL_SET_ERR_MSG(extack, "Not enough memory for a new schedule"); 188562306a36Sopenharmony_ci return -ENOMEM; 188662306a36Sopenharmony_ci } 188762306a36Sopenharmony_ci INIT_LIST_HEAD(&new_admin->entries); 188862306a36Sopenharmony_ci 188962306a36Sopenharmony_ci oper = rtnl_dereference(q->oper_sched); 189062306a36Sopenharmony_ci admin = rtnl_dereference(q->admin_sched); 189162306a36Sopenharmony_ci 189262306a36Sopenharmony_ci /* no changes - no new mqprio settings */ 189362306a36Sopenharmony_ci if (!taprio_mqprio_cmp(dev, mqprio)) 189462306a36Sopenharmony_ci mqprio = NULL; 189562306a36Sopenharmony_ci 189662306a36Sopenharmony_ci if (mqprio && (oper || admin)) { 189762306a36Sopenharmony_ci NL_SET_ERR_MSG(extack, "Changing the traffic mapping of a running schedule is not supported"); 189862306a36Sopenharmony_ci err = -ENOTSUPP; 189962306a36Sopenharmony_ci goto free_sched; 190062306a36Sopenharmony_ci } 190162306a36Sopenharmony_ci 190262306a36Sopenharmony_ci if (mqprio) { 190362306a36Sopenharmony_ci err = netdev_set_num_tc(dev, mqprio->num_tc); 190462306a36Sopenharmony_ci if (err) 190562306a36Sopenharmony_ci goto free_sched; 190662306a36Sopenharmony_ci for (i = 0; i < mqprio->num_tc; i++) { 190762306a36Sopenharmony_ci netdev_set_tc_queue(dev, i, 190862306a36Sopenharmony_ci mqprio->count[i], 190962306a36Sopenharmony_ci mqprio->offset[i]); 191062306a36Sopenharmony_ci q->cur_txq[i] = mqprio->offset[i]; 191162306a36Sopenharmony_ci } 191262306a36Sopenharmony_ci 191362306a36Sopenharmony_ci /* Always use supplied priority mappings */ 191462306a36Sopenharmony_ci for (i = 0; i <= TC_BITMASK; i++) 191562306a36Sopenharmony_ci netdev_set_prio_tc_map(dev, i, 191662306a36Sopenharmony_ci mqprio->prio_tc_map[i]); 191762306a36Sopenharmony_ci } 191862306a36Sopenharmony_ci 191962306a36Sopenharmony_ci err = parse_taprio_schedule(q, tb, new_admin, extack); 192062306a36Sopenharmony_ci if (err < 0) 192162306a36Sopenharmony_ci goto free_sched; 192262306a36Sopenharmony_ci 192362306a36Sopenharmony_ci if (new_admin->num_entries == 0) { 192462306a36Sopenharmony_ci NL_SET_ERR_MSG(extack, "There should be at least one entry in the schedule"); 192562306a36Sopenharmony_ci err = -EINVAL; 192662306a36Sopenharmony_ci goto free_sched; 192762306a36Sopenharmony_ci } 192862306a36Sopenharmony_ci 192962306a36Sopenharmony_ci err = taprio_parse_clockid(sch, tb, extack); 193062306a36Sopenharmony_ci if (err < 0) 193162306a36Sopenharmony_ci goto free_sched; 193262306a36Sopenharmony_ci 193362306a36Sopenharmony_ci taprio_set_picos_per_byte(dev, q); 193462306a36Sopenharmony_ci taprio_update_queue_max_sdu(q, new_admin, stab); 193562306a36Sopenharmony_ci 193662306a36Sopenharmony_ci if (FULL_OFFLOAD_IS_ENABLED(q->flags)) 193762306a36Sopenharmony_ci err = taprio_enable_offload(dev, q, new_admin, extack); 193862306a36Sopenharmony_ci else 193962306a36Sopenharmony_ci err = taprio_disable_offload(dev, q, extack); 194062306a36Sopenharmony_ci if (err) 194162306a36Sopenharmony_ci goto free_sched; 194262306a36Sopenharmony_ci 194362306a36Sopenharmony_ci /* Protects against enqueue()/dequeue() */ 194462306a36Sopenharmony_ci spin_lock_bh(qdisc_lock(sch)); 194562306a36Sopenharmony_ci 194662306a36Sopenharmony_ci if (tb[TCA_TAPRIO_ATTR_TXTIME_DELAY]) { 194762306a36Sopenharmony_ci if (!TXTIME_ASSIST_IS_ENABLED(q->flags)) { 194862306a36Sopenharmony_ci NL_SET_ERR_MSG_MOD(extack, "txtime-delay can only be set when txtime-assist mode is enabled"); 194962306a36Sopenharmony_ci err = -EINVAL; 195062306a36Sopenharmony_ci goto unlock; 195162306a36Sopenharmony_ci } 195262306a36Sopenharmony_ci 195362306a36Sopenharmony_ci q->txtime_delay = nla_get_u32(tb[TCA_TAPRIO_ATTR_TXTIME_DELAY]); 195462306a36Sopenharmony_ci } 195562306a36Sopenharmony_ci 195662306a36Sopenharmony_ci if (!TXTIME_ASSIST_IS_ENABLED(q->flags) && 195762306a36Sopenharmony_ci !FULL_OFFLOAD_IS_ENABLED(q->flags) && 195862306a36Sopenharmony_ci !hrtimer_active(&q->advance_timer)) { 195962306a36Sopenharmony_ci hrtimer_init(&q->advance_timer, q->clockid, HRTIMER_MODE_ABS); 196062306a36Sopenharmony_ci q->advance_timer.function = advance_sched; 196162306a36Sopenharmony_ci } 196262306a36Sopenharmony_ci 196362306a36Sopenharmony_ci err = taprio_get_start_time(sch, new_admin, &start); 196462306a36Sopenharmony_ci if (err < 0) { 196562306a36Sopenharmony_ci NL_SET_ERR_MSG(extack, "Internal error: failed get start time"); 196662306a36Sopenharmony_ci goto unlock; 196762306a36Sopenharmony_ci } 196862306a36Sopenharmony_ci 196962306a36Sopenharmony_ci setup_txtime(q, new_admin, start); 197062306a36Sopenharmony_ci 197162306a36Sopenharmony_ci if (TXTIME_ASSIST_IS_ENABLED(q->flags)) { 197262306a36Sopenharmony_ci if (!oper) { 197362306a36Sopenharmony_ci rcu_assign_pointer(q->oper_sched, new_admin); 197462306a36Sopenharmony_ci err = 0; 197562306a36Sopenharmony_ci new_admin = NULL; 197662306a36Sopenharmony_ci goto unlock; 197762306a36Sopenharmony_ci } 197862306a36Sopenharmony_ci 197962306a36Sopenharmony_ci rcu_assign_pointer(q->admin_sched, new_admin); 198062306a36Sopenharmony_ci if (admin) 198162306a36Sopenharmony_ci call_rcu(&admin->rcu, taprio_free_sched_cb); 198262306a36Sopenharmony_ci } else { 198362306a36Sopenharmony_ci setup_first_end_time(q, new_admin, start); 198462306a36Sopenharmony_ci 198562306a36Sopenharmony_ci /* Protects against advance_sched() */ 198662306a36Sopenharmony_ci spin_lock_irqsave(&q->current_entry_lock, flags); 198762306a36Sopenharmony_ci 198862306a36Sopenharmony_ci taprio_start_sched(sch, start, new_admin); 198962306a36Sopenharmony_ci 199062306a36Sopenharmony_ci rcu_assign_pointer(q->admin_sched, new_admin); 199162306a36Sopenharmony_ci if (admin) 199262306a36Sopenharmony_ci call_rcu(&admin->rcu, taprio_free_sched_cb); 199362306a36Sopenharmony_ci 199462306a36Sopenharmony_ci spin_unlock_irqrestore(&q->current_entry_lock, flags); 199562306a36Sopenharmony_ci 199662306a36Sopenharmony_ci if (FULL_OFFLOAD_IS_ENABLED(q->flags)) 199762306a36Sopenharmony_ci taprio_offload_config_changed(q); 199862306a36Sopenharmony_ci } 199962306a36Sopenharmony_ci 200062306a36Sopenharmony_ci new_admin = NULL; 200162306a36Sopenharmony_ci err = 0; 200262306a36Sopenharmony_ci 200362306a36Sopenharmony_ci if (!stab) 200462306a36Sopenharmony_ci NL_SET_ERR_MSG_MOD(extack, 200562306a36Sopenharmony_ci "Size table not specified, frame length estimations may be inaccurate"); 200662306a36Sopenharmony_ci 200762306a36Sopenharmony_ciunlock: 200862306a36Sopenharmony_ci spin_unlock_bh(qdisc_lock(sch)); 200962306a36Sopenharmony_ci 201062306a36Sopenharmony_cifree_sched: 201162306a36Sopenharmony_ci if (new_admin) 201262306a36Sopenharmony_ci call_rcu(&new_admin->rcu, taprio_free_sched_cb); 201362306a36Sopenharmony_ci 201462306a36Sopenharmony_ci return err; 201562306a36Sopenharmony_ci} 201662306a36Sopenharmony_ci 201762306a36Sopenharmony_cistatic void taprio_reset(struct Qdisc *sch) 201862306a36Sopenharmony_ci{ 201962306a36Sopenharmony_ci struct taprio_sched *q = qdisc_priv(sch); 202062306a36Sopenharmony_ci struct net_device *dev = qdisc_dev(sch); 202162306a36Sopenharmony_ci int i; 202262306a36Sopenharmony_ci 202362306a36Sopenharmony_ci hrtimer_cancel(&q->advance_timer); 202462306a36Sopenharmony_ci 202562306a36Sopenharmony_ci if (q->qdiscs) { 202662306a36Sopenharmony_ci for (i = 0; i < dev->num_tx_queues; i++) 202762306a36Sopenharmony_ci if (q->qdiscs[i]) 202862306a36Sopenharmony_ci qdisc_reset(q->qdiscs[i]); 202962306a36Sopenharmony_ci } 203062306a36Sopenharmony_ci} 203162306a36Sopenharmony_ci 203262306a36Sopenharmony_cistatic void taprio_destroy(struct Qdisc *sch) 203362306a36Sopenharmony_ci{ 203462306a36Sopenharmony_ci struct taprio_sched *q = qdisc_priv(sch); 203562306a36Sopenharmony_ci struct net_device *dev = qdisc_dev(sch); 203662306a36Sopenharmony_ci struct sched_gate_list *oper, *admin; 203762306a36Sopenharmony_ci unsigned int i; 203862306a36Sopenharmony_ci 203962306a36Sopenharmony_ci list_del(&q->taprio_list); 204062306a36Sopenharmony_ci 204162306a36Sopenharmony_ci /* Note that taprio_reset() might not be called if an error 204262306a36Sopenharmony_ci * happens in qdisc_create(), after taprio_init() has been called. 204362306a36Sopenharmony_ci */ 204462306a36Sopenharmony_ci hrtimer_cancel(&q->advance_timer); 204562306a36Sopenharmony_ci qdisc_synchronize(sch); 204662306a36Sopenharmony_ci 204762306a36Sopenharmony_ci taprio_disable_offload(dev, q, NULL); 204862306a36Sopenharmony_ci 204962306a36Sopenharmony_ci if (q->qdiscs) { 205062306a36Sopenharmony_ci for (i = 0; i < dev->num_tx_queues; i++) 205162306a36Sopenharmony_ci qdisc_put(q->qdiscs[i]); 205262306a36Sopenharmony_ci 205362306a36Sopenharmony_ci kfree(q->qdiscs); 205462306a36Sopenharmony_ci } 205562306a36Sopenharmony_ci q->qdiscs = NULL; 205662306a36Sopenharmony_ci 205762306a36Sopenharmony_ci netdev_reset_tc(dev); 205862306a36Sopenharmony_ci 205962306a36Sopenharmony_ci oper = rtnl_dereference(q->oper_sched); 206062306a36Sopenharmony_ci admin = rtnl_dereference(q->admin_sched); 206162306a36Sopenharmony_ci 206262306a36Sopenharmony_ci if (oper) 206362306a36Sopenharmony_ci call_rcu(&oper->rcu, taprio_free_sched_cb); 206462306a36Sopenharmony_ci 206562306a36Sopenharmony_ci if (admin) 206662306a36Sopenharmony_ci call_rcu(&admin->rcu, taprio_free_sched_cb); 206762306a36Sopenharmony_ci 206862306a36Sopenharmony_ci taprio_cleanup_broken_mqprio(q); 206962306a36Sopenharmony_ci} 207062306a36Sopenharmony_ci 207162306a36Sopenharmony_cistatic int taprio_init(struct Qdisc *sch, struct nlattr *opt, 207262306a36Sopenharmony_ci struct netlink_ext_ack *extack) 207362306a36Sopenharmony_ci{ 207462306a36Sopenharmony_ci struct taprio_sched *q = qdisc_priv(sch); 207562306a36Sopenharmony_ci struct net_device *dev = qdisc_dev(sch); 207662306a36Sopenharmony_ci int i, tc; 207762306a36Sopenharmony_ci 207862306a36Sopenharmony_ci spin_lock_init(&q->current_entry_lock); 207962306a36Sopenharmony_ci 208062306a36Sopenharmony_ci hrtimer_init(&q->advance_timer, CLOCK_TAI, HRTIMER_MODE_ABS); 208162306a36Sopenharmony_ci q->advance_timer.function = advance_sched; 208262306a36Sopenharmony_ci 208362306a36Sopenharmony_ci q->root = sch; 208462306a36Sopenharmony_ci 208562306a36Sopenharmony_ci /* We only support static clockids. Use an invalid value as default 208662306a36Sopenharmony_ci * and get the valid one on taprio_change(). 208762306a36Sopenharmony_ci */ 208862306a36Sopenharmony_ci q->clockid = -1; 208962306a36Sopenharmony_ci q->flags = TAPRIO_FLAGS_INVALID; 209062306a36Sopenharmony_ci 209162306a36Sopenharmony_ci list_add(&q->taprio_list, &taprio_list); 209262306a36Sopenharmony_ci 209362306a36Sopenharmony_ci if (sch->parent != TC_H_ROOT) { 209462306a36Sopenharmony_ci NL_SET_ERR_MSG_MOD(extack, "Can only be attached as root qdisc"); 209562306a36Sopenharmony_ci return -EOPNOTSUPP; 209662306a36Sopenharmony_ci } 209762306a36Sopenharmony_ci 209862306a36Sopenharmony_ci if (!netif_is_multiqueue(dev)) { 209962306a36Sopenharmony_ci NL_SET_ERR_MSG_MOD(extack, "Multi-queue device is required"); 210062306a36Sopenharmony_ci return -EOPNOTSUPP; 210162306a36Sopenharmony_ci } 210262306a36Sopenharmony_ci 210362306a36Sopenharmony_ci q->qdiscs = kcalloc(dev->num_tx_queues, sizeof(q->qdiscs[0]), 210462306a36Sopenharmony_ci GFP_KERNEL); 210562306a36Sopenharmony_ci if (!q->qdiscs) 210662306a36Sopenharmony_ci return -ENOMEM; 210762306a36Sopenharmony_ci 210862306a36Sopenharmony_ci if (!opt) 210962306a36Sopenharmony_ci return -EINVAL; 211062306a36Sopenharmony_ci 211162306a36Sopenharmony_ci for (i = 0; i < dev->num_tx_queues; i++) { 211262306a36Sopenharmony_ci struct netdev_queue *dev_queue; 211362306a36Sopenharmony_ci struct Qdisc *qdisc; 211462306a36Sopenharmony_ci 211562306a36Sopenharmony_ci dev_queue = netdev_get_tx_queue(dev, i); 211662306a36Sopenharmony_ci qdisc = qdisc_create_dflt(dev_queue, 211762306a36Sopenharmony_ci &pfifo_qdisc_ops, 211862306a36Sopenharmony_ci TC_H_MAKE(TC_H_MAJ(sch->handle), 211962306a36Sopenharmony_ci TC_H_MIN(i + 1)), 212062306a36Sopenharmony_ci extack); 212162306a36Sopenharmony_ci if (!qdisc) 212262306a36Sopenharmony_ci return -ENOMEM; 212362306a36Sopenharmony_ci 212462306a36Sopenharmony_ci if (i < dev->real_num_tx_queues) 212562306a36Sopenharmony_ci qdisc_hash_add(qdisc, false); 212662306a36Sopenharmony_ci 212762306a36Sopenharmony_ci q->qdiscs[i] = qdisc; 212862306a36Sopenharmony_ci } 212962306a36Sopenharmony_ci 213062306a36Sopenharmony_ci for (tc = 0; tc < TC_QOPT_MAX_QUEUE; tc++) 213162306a36Sopenharmony_ci q->fp[tc] = TC_FP_EXPRESS; 213262306a36Sopenharmony_ci 213362306a36Sopenharmony_ci taprio_detect_broken_mqprio(q); 213462306a36Sopenharmony_ci 213562306a36Sopenharmony_ci return taprio_change(sch, opt, extack); 213662306a36Sopenharmony_ci} 213762306a36Sopenharmony_ci 213862306a36Sopenharmony_cistatic void taprio_attach(struct Qdisc *sch) 213962306a36Sopenharmony_ci{ 214062306a36Sopenharmony_ci struct taprio_sched *q = qdisc_priv(sch); 214162306a36Sopenharmony_ci struct net_device *dev = qdisc_dev(sch); 214262306a36Sopenharmony_ci unsigned int ntx; 214362306a36Sopenharmony_ci 214462306a36Sopenharmony_ci /* Attach underlying qdisc */ 214562306a36Sopenharmony_ci for (ntx = 0; ntx < dev->num_tx_queues; ntx++) { 214662306a36Sopenharmony_ci struct netdev_queue *dev_queue = netdev_get_tx_queue(dev, ntx); 214762306a36Sopenharmony_ci struct Qdisc *old, *dev_queue_qdisc; 214862306a36Sopenharmony_ci 214962306a36Sopenharmony_ci if (FULL_OFFLOAD_IS_ENABLED(q->flags)) { 215062306a36Sopenharmony_ci struct Qdisc *qdisc = q->qdiscs[ntx]; 215162306a36Sopenharmony_ci 215262306a36Sopenharmony_ci /* In offload mode, the root taprio qdisc is bypassed 215362306a36Sopenharmony_ci * and the netdev TX queues see the children directly 215462306a36Sopenharmony_ci */ 215562306a36Sopenharmony_ci qdisc->flags |= TCQ_F_ONETXQUEUE | TCQ_F_NOPARENT; 215662306a36Sopenharmony_ci dev_queue_qdisc = qdisc; 215762306a36Sopenharmony_ci } else { 215862306a36Sopenharmony_ci /* In software mode, attach the root taprio qdisc 215962306a36Sopenharmony_ci * to all netdev TX queues, so that dev_qdisc_enqueue() 216062306a36Sopenharmony_ci * goes through taprio_enqueue(). 216162306a36Sopenharmony_ci */ 216262306a36Sopenharmony_ci dev_queue_qdisc = sch; 216362306a36Sopenharmony_ci } 216462306a36Sopenharmony_ci old = dev_graft_qdisc(dev_queue, dev_queue_qdisc); 216562306a36Sopenharmony_ci /* The qdisc's refcount requires to be elevated once 216662306a36Sopenharmony_ci * for each netdev TX queue it is grafted onto 216762306a36Sopenharmony_ci */ 216862306a36Sopenharmony_ci qdisc_refcount_inc(dev_queue_qdisc); 216962306a36Sopenharmony_ci if (old) 217062306a36Sopenharmony_ci qdisc_put(old); 217162306a36Sopenharmony_ci } 217262306a36Sopenharmony_ci} 217362306a36Sopenharmony_ci 217462306a36Sopenharmony_cistatic struct netdev_queue *taprio_queue_get(struct Qdisc *sch, 217562306a36Sopenharmony_ci unsigned long cl) 217662306a36Sopenharmony_ci{ 217762306a36Sopenharmony_ci struct net_device *dev = qdisc_dev(sch); 217862306a36Sopenharmony_ci unsigned long ntx = cl - 1; 217962306a36Sopenharmony_ci 218062306a36Sopenharmony_ci if (ntx >= dev->num_tx_queues) 218162306a36Sopenharmony_ci return NULL; 218262306a36Sopenharmony_ci 218362306a36Sopenharmony_ci return netdev_get_tx_queue(dev, ntx); 218462306a36Sopenharmony_ci} 218562306a36Sopenharmony_ci 218662306a36Sopenharmony_cistatic int taprio_graft(struct Qdisc *sch, unsigned long cl, 218762306a36Sopenharmony_ci struct Qdisc *new, struct Qdisc **old, 218862306a36Sopenharmony_ci struct netlink_ext_ack *extack) 218962306a36Sopenharmony_ci{ 219062306a36Sopenharmony_ci struct taprio_sched *q = qdisc_priv(sch); 219162306a36Sopenharmony_ci struct net_device *dev = qdisc_dev(sch); 219262306a36Sopenharmony_ci struct netdev_queue *dev_queue = taprio_queue_get(sch, cl); 219362306a36Sopenharmony_ci 219462306a36Sopenharmony_ci if (!dev_queue) 219562306a36Sopenharmony_ci return -EINVAL; 219662306a36Sopenharmony_ci 219762306a36Sopenharmony_ci if (dev->flags & IFF_UP) 219862306a36Sopenharmony_ci dev_deactivate(dev); 219962306a36Sopenharmony_ci 220062306a36Sopenharmony_ci /* In offload mode, the child Qdisc is directly attached to the netdev 220162306a36Sopenharmony_ci * TX queue, and thus, we need to keep its refcount elevated in order 220262306a36Sopenharmony_ci * to counteract qdisc_graft()'s call to qdisc_put() once per TX queue. 220362306a36Sopenharmony_ci * However, save the reference to the new qdisc in the private array in 220462306a36Sopenharmony_ci * both software and offload cases, to have an up-to-date reference to 220562306a36Sopenharmony_ci * our children. 220662306a36Sopenharmony_ci */ 220762306a36Sopenharmony_ci *old = q->qdiscs[cl - 1]; 220862306a36Sopenharmony_ci if (FULL_OFFLOAD_IS_ENABLED(q->flags)) { 220962306a36Sopenharmony_ci WARN_ON_ONCE(dev_graft_qdisc(dev_queue, new) != *old); 221062306a36Sopenharmony_ci if (new) 221162306a36Sopenharmony_ci qdisc_refcount_inc(new); 221262306a36Sopenharmony_ci if (*old) 221362306a36Sopenharmony_ci qdisc_put(*old); 221462306a36Sopenharmony_ci } 221562306a36Sopenharmony_ci 221662306a36Sopenharmony_ci q->qdiscs[cl - 1] = new; 221762306a36Sopenharmony_ci if (new) 221862306a36Sopenharmony_ci new->flags |= TCQ_F_ONETXQUEUE | TCQ_F_NOPARENT; 221962306a36Sopenharmony_ci 222062306a36Sopenharmony_ci if (dev->flags & IFF_UP) 222162306a36Sopenharmony_ci dev_activate(dev); 222262306a36Sopenharmony_ci 222362306a36Sopenharmony_ci return 0; 222462306a36Sopenharmony_ci} 222562306a36Sopenharmony_ci 222662306a36Sopenharmony_cistatic int dump_entry(struct sk_buff *msg, 222762306a36Sopenharmony_ci const struct sched_entry *entry) 222862306a36Sopenharmony_ci{ 222962306a36Sopenharmony_ci struct nlattr *item; 223062306a36Sopenharmony_ci 223162306a36Sopenharmony_ci item = nla_nest_start_noflag(msg, TCA_TAPRIO_SCHED_ENTRY); 223262306a36Sopenharmony_ci if (!item) 223362306a36Sopenharmony_ci return -ENOSPC; 223462306a36Sopenharmony_ci 223562306a36Sopenharmony_ci if (nla_put_u32(msg, TCA_TAPRIO_SCHED_ENTRY_INDEX, entry->index)) 223662306a36Sopenharmony_ci goto nla_put_failure; 223762306a36Sopenharmony_ci 223862306a36Sopenharmony_ci if (nla_put_u8(msg, TCA_TAPRIO_SCHED_ENTRY_CMD, entry->command)) 223962306a36Sopenharmony_ci goto nla_put_failure; 224062306a36Sopenharmony_ci 224162306a36Sopenharmony_ci if (nla_put_u32(msg, TCA_TAPRIO_SCHED_ENTRY_GATE_MASK, 224262306a36Sopenharmony_ci entry->gate_mask)) 224362306a36Sopenharmony_ci goto nla_put_failure; 224462306a36Sopenharmony_ci 224562306a36Sopenharmony_ci if (nla_put_u32(msg, TCA_TAPRIO_SCHED_ENTRY_INTERVAL, 224662306a36Sopenharmony_ci entry->interval)) 224762306a36Sopenharmony_ci goto nla_put_failure; 224862306a36Sopenharmony_ci 224962306a36Sopenharmony_ci return nla_nest_end(msg, item); 225062306a36Sopenharmony_ci 225162306a36Sopenharmony_cinla_put_failure: 225262306a36Sopenharmony_ci nla_nest_cancel(msg, item); 225362306a36Sopenharmony_ci return -1; 225462306a36Sopenharmony_ci} 225562306a36Sopenharmony_ci 225662306a36Sopenharmony_cistatic int dump_schedule(struct sk_buff *msg, 225762306a36Sopenharmony_ci const struct sched_gate_list *root) 225862306a36Sopenharmony_ci{ 225962306a36Sopenharmony_ci struct nlattr *entry_list; 226062306a36Sopenharmony_ci struct sched_entry *entry; 226162306a36Sopenharmony_ci 226262306a36Sopenharmony_ci if (nla_put_s64(msg, TCA_TAPRIO_ATTR_SCHED_BASE_TIME, 226362306a36Sopenharmony_ci root->base_time, TCA_TAPRIO_PAD)) 226462306a36Sopenharmony_ci return -1; 226562306a36Sopenharmony_ci 226662306a36Sopenharmony_ci if (nla_put_s64(msg, TCA_TAPRIO_ATTR_SCHED_CYCLE_TIME, 226762306a36Sopenharmony_ci root->cycle_time, TCA_TAPRIO_PAD)) 226862306a36Sopenharmony_ci return -1; 226962306a36Sopenharmony_ci 227062306a36Sopenharmony_ci if (nla_put_s64(msg, TCA_TAPRIO_ATTR_SCHED_CYCLE_TIME_EXTENSION, 227162306a36Sopenharmony_ci root->cycle_time_extension, TCA_TAPRIO_PAD)) 227262306a36Sopenharmony_ci return -1; 227362306a36Sopenharmony_ci 227462306a36Sopenharmony_ci entry_list = nla_nest_start_noflag(msg, 227562306a36Sopenharmony_ci TCA_TAPRIO_ATTR_SCHED_ENTRY_LIST); 227662306a36Sopenharmony_ci if (!entry_list) 227762306a36Sopenharmony_ci goto error_nest; 227862306a36Sopenharmony_ci 227962306a36Sopenharmony_ci list_for_each_entry(entry, &root->entries, list) { 228062306a36Sopenharmony_ci if (dump_entry(msg, entry) < 0) 228162306a36Sopenharmony_ci goto error_nest; 228262306a36Sopenharmony_ci } 228362306a36Sopenharmony_ci 228462306a36Sopenharmony_ci nla_nest_end(msg, entry_list); 228562306a36Sopenharmony_ci return 0; 228662306a36Sopenharmony_ci 228762306a36Sopenharmony_cierror_nest: 228862306a36Sopenharmony_ci nla_nest_cancel(msg, entry_list); 228962306a36Sopenharmony_ci return -1; 229062306a36Sopenharmony_ci} 229162306a36Sopenharmony_ci 229262306a36Sopenharmony_cistatic int taprio_dump_tc_entries(struct sk_buff *skb, 229362306a36Sopenharmony_ci struct taprio_sched *q, 229462306a36Sopenharmony_ci struct sched_gate_list *sched) 229562306a36Sopenharmony_ci{ 229662306a36Sopenharmony_ci struct nlattr *n; 229762306a36Sopenharmony_ci int tc; 229862306a36Sopenharmony_ci 229962306a36Sopenharmony_ci for (tc = 0; tc < TC_MAX_QUEUE; tc++) { 230062306a36Sopenharmony_ci n = nla_nest_start(skb, TCA_TAPRIO_ATTR_TC_ENTRY); 230162306a36Sopenharmony_ci if (!n) 230262306a36Sopenharmony_ci return -EMSGSIZE; 230362306a36Sopenharmony_ci 230462306a36Sopenharmony_ci if (nla_put_u32(skb, TCA_TAPRIO_TC_ENTRY_INDEX, tc)) 230562306a36Sopenharmony_ci goto nla_put_failure; 230662306a36Sopenharmony_ci 230762306a36Sopenharmony_ci if (nla_put_u32(skb, TCA_TAPRIO_TC_ENTRY_MAX_SDU, 230862306a36Sopenharmony_ci sched->max_sdu[tc])) 230962306a36Sopenharmony_ci goto nla_put_failure; 231062306a36Sopenharmony_ci 231162306a36Sopenharmony_ci if (nla_put_u32(skb, TCA_TAPRIO_TC_ENTRY_FP, q->fp[tc])) 231262306a36Sopenharmony_ci goto nla_put_failure; 231362306a36Sopenharmony_ci 231462306a36Sopenharmony_ci nla_nest_end(skb, n); 231562306a36Sopenharmony_ci } 231662306a36Sopenharmony_ci 231762306a36Sopenharmony_ci return 0; 231862306a36Sopenharmony_ci 231962306a36Sopenharmony_cinla_put_failure: 232062306a36Sopenharmony_ci nla_nest_cancel(skb, n); 232162306a36Sopenharmony_ci return -EMSGSIZE; 232262306a36Sopenharmony_ci} 232362306a36Sopenharmony_ci 232462306a36Sopenharmony_cistatic int taprio_put_stat(struct sk_buff *skb, u64 val, u16 attrtype) 232562306a36Sopenharmony_ci{ 232662306a36Sopenharmony_ci if (val == TAPRIO_STAT_NOT_SET) 232762306a36Sopenharmony_ci return 0; 232862306a36Sopenharmony_ci if (nla_put_u64_64bit(skb, attrtype, val, TCA_TAPRIO_OFFLOAD_STATS_PAD)) 232962306a36Sopenharmony_ci return -EMSGSIZE; 233062306a36Sopenharmony_ci return 0; 233162306a36Sopenharmony_ci} 233262306a36Sopenharmony_ci 233362306a36Sopenharmony_cistatic int taprio_dump_xstats(struct Qdisc *sch, struct gnet_dump *d, 233462306a36Sopenharmony_ci struct tc_taprio_qopt_offload *offload, 233562306a36Sopenharmony_ci struct tc_taprio_qopt_stats *stats) 233662306a36Sopenharmony_ci{ 233762306a36Sopenharmony_ci struct net_device *dev = qdisc_dev(sch); 233862306a36Sopenharmony_ci const struct net_device_ops *ops; 233962306a36Sopenharmony_ci struct sk_buff *skb = d->skb; 234062306a36Sopenharmony_ci struct nlattr *xstats; 234162306a36Sopenharmony_ci int err; 234262306a36Sopenharmony_ci 234362306a36Sopenharmony_ci ops = qdisc_dev(sch)->netdev_ops; 234462306a36Sopenharmony_ci 234562306a36Sopenharmony_ci /* FIXME I could use qdisc_offload_dump_helper(), but that messes 234662306a36Sopenharmony_ci * with sch->flags depending on whether the device reports taprio 234762306a36Sopenharmony_ci * stats, and I'm not sure whether that's a good idea, considering 234862306a36Sopenharmony_ci * that stats are optional to the offload itself 234962306a36Sopenharmony_ci */ 235062306a36Sopenharmony_ci if (!ops->ndo_setup_tc) 235162306a36Sopenharmony_ci return 0; 235262306a36Sopenharmony_ci 235362306a36Sopenharmony_ci memset(stats, 0xff, sizeof(*stats)); 235462306a36Sopenharmony_ci 235562306a36Sopenharmony_ci err = ops->ndo_setup_tc(dev, TC_SETUP_QDISC_TAPRIO, offload); 235662306a36Sopenharmony_ci if (err == -EOPNOTSUPP) 235762306a36Sopenharmony_ci return 0; 235862306a36Sopenharmony_ci if (err) 235962306a36Sopenharmony_ci return err; 236062306a36Sopenharmony_ci 236162306a36Sopenharmony_ci xstats = nla_nest_start(skb, TCA_STATS_APP); 236262306a36Sopenharmony_ci if (!xstats) 236362306a36Sopenharmony_ci goto err; 236462306a36Sopenharmony_ci 236562306a36Sopenharmony_ci if (taprio_put_stat(skb, stats->window_drops, 236662306a36Sopenharmony_ci TCA_TAPRIO_OFFLOAD_STATS_WINDOW_DROPS) || 236762306a36Sopenharmony_ci taprio_put_stat(skb, stats->tx_overruns, 236862306a36Sopenharmony_ci TCA_TAPRIO_OFFLOAD_STATS_TX_OVERRUNS)) 236962306a36Sopenharmony_ci goto err_cancel; 237062306a36Sopenharmony_ci 237162306a36Sopenharmony_ci nla_nest_end(skb, xstats); 237262306a36Sopenharmony_ci 237362306a36Sopenharmony_ci return 0; 237462306a36Sopenharmony_ci 237562306a36Sopenharmony_cierr_cancel: 237662306a36Sopenharmony_ci nla_nest_cancel(skb, xstats); 237762306a36Sopenharmony_cierr: 237862306a36Sopenharmony_ci return -EMSGSIZE; 237962306a36Sopenharmony_ci} 238062306a36Sopenharmony_ci 238162306a36Sopenharmony_cistatic int taprio_dump_stats(struct Qdisc *sch, struct gnet_dump *d) 238262306a36Sopenharmony_ci{ 238362306a36Sopenharmony_ci struct tc_taprio_qopt_offload offload = { 238462306a36Sopenharmony_ci .cmd = TAPRIO_CMD_STATS, 238562306a36Sopenharmony_ci }; 238662306a36Sopenharmony_ci 238762306a36Sopenharmony_ci return taprio_dump_xstats(sch, d, &offload, &offload.stats); 238862306a36Sopenharmony_ci} 238962306a36Sopenharmony_ci 239062306a36Sopenharmony_cistatic int taprio_dump(struct Qdisc *sch, struct sk_buff *skb) 239162306a36Sopenharmony_ci{ 239262306a36Sopenharmony_ci struct taprio_sched *q = qdisc_priv(sch); 239362306a36Sopenharmony_ci struct net_device *dev = qdisc_dev(sch); 239462306a36Sopenharmony_ci struct sched_gate_list *oper, *admin; 239562306a36Sopenharmony_ci struct tc_mqprio_qopt opt = { 0 }; 239662306a36Sopenharmony_ci struct nlattr *nest, *sched_nest; 239762306a36Sopenharmony_ci 239862306a36Sopenharmony_ci oper = rtnl_dereference(q->oper_sched); 239962306a36Sopenharmony_ci admin = rtnl_dereference(q->admin_sched); 240062306a36Sopenharmony_ci 240162306a36Sopenharmony_ci mqprio_qopt_reconstruct(dev, &opt); 240262306a36Sopenharmony_ci 240362306a36Sopenharmony_ci nest = nla_nest_start_noflag(skb, TCA_OPTIONS); 240462306a36Sopenharmony_ci if (!nest) 240562306a36Sopenharmony_ci goto start_error; 240662306a36Sopenharmony_ci 240762306a36Sopenharmony_ci if (nla_put(skb, TCA_TAPRIO_ATTR_PRIOMAP, sizeof(opt), &opt)) 240862306a36Sopenharmony_ci goto options_error; 240962306a36Sopenharmony_ci 241062306a36Sopenharmony_ci if (!FULL_OFFLOAD_IS_ENABLED(q->flags) && 241162306a36Sopenharmony_ci nla_put_s32(skb, TCA_TAPRIO_ATTR_SCHED_CLOCKID, q->clockid)) 241262306a36Sopenharmony_ci goto options_error; 241362306a36Sopenharmony_ci 241462306a36Sopenharmony_ci if (q->flags && nla_put_u32(skb, TCA_TAPRIO_ATTR_FLAGS, q->flags)) 241562306a36Sopenharmony_ci goto options_error; 241662306a36Sopenharmony_ci 241762306a36Sopenharmony_ci if (q->txtime_delay && 241862306a36Sopenharmony_ci nla_put_u32(skb, TCA_TAPRIO_ATTR_TXTIME_DELAY, q->txtime_delay)) 241962306a36Sopenharmony_ci goto options_error; 242062306a36Sopenharmony_ci 242162306a36Sopenharmony_ci if (oper && taprio_dump_tc_entries(skb, q, oper)) 242262306a36Sopenharmony_ci goto options_error; 242362306a36Sopenharmony_ci 242462306a36Sopenharmony_ci if (oper && dump_schedule(skb, oper)) 242562306a36Sopenharmony_ci goto options_error; 242662306a36Sopenharmony_ci 242762306a36Sopenharmony_ci if (!admin) 242862306a36Sopenharmony_ci goto done; 242962306a36Sopenharmony_ci 243062306a36Sopenharmony_ci sched_nest = nla_nest_start_noflag(skb, TCA_TAPRIO_ATTR_ADMIN_SCHED); 243162306a36Sopenharmony_ci if (!sched_nest) 243262306a36Sopenharmony_ci goto options_error; 243362306a36Sopenharmony_ci 243462306a36Sopenharmony_ci if (dump_schedule(skb, admin)) 243562306a36Sopenharmony_ci goto admin_error; 243662306a36Sopenharmony_ci 243762306a36Sopenharmony_ci nla_nest_end(skb, sched_nest); 243862306a36Sopenharmony_ci 243962306a36Sopenharmony_cidone: 244062306a36Sopenharmony_ci return nla_nest_end(skb, nest); 244162306a36Sopenharmony_ci 244262306a36Sopenharmony_ciadmin_error: 244362306a36Sopenharmony_ci nla_nest_cancel(skb, sched_nest); 244462306a36Sopenharmony_ci 244562306a36Sopenharmony_cioptions_error: 244662306a36Sopenharmony_ci nla_nest_cancel(skb, nest); 244762306a36Sopenharmony_ci 244862306a36Sopenharmony_cistart_error: 244962306a36Sopenharmony_ci return -ENOSPC; 245062306a36Sopenharmony_ci} 245162306a36Sopenharmony_ci 245262306a36Sopenharmony_cistatic struct Qdisc *taprio_leaf(struct Qdisc *sch, unsigned long cl) 245362306a36Sopenharmony_ci{ 245462306a36Sopenharmony_ci struct taprio_sched *q = qdisc_priv(sch); 245562306a36Sopenharmony_ci struct net_device *dev = qdisc_dev(sch); 245662306a36Sopenharmony_ci unsigned int ntx = cl - 1; 245762306a36Sopenharmony_ci 245862306a36Sopenharmony_ci if (ntx >= dev->num_tx_queues) 245962306a36Sopenharmony_ci return NULL; 246062306a36Sopenharmony_ci 246162306a36Sopenharmony_ci return q->qdiscs[ntx]; 246262306a36Sopenharmony_ci} 246362306a36Sopenharmony_ci 246462306a36Sopenharmony_cistatic unsigned long taprio_find(struct Qdisc *sch, u32 classid) 246562306a36Sopenharmony_ci{ 246662306a36Sopenharmony_ci unsigned int ntx = TC_H_MIN(classid); 246762306a36Sopenharmony_ci 246862306a36Sopenharmony_ci if (!taprio_queue_get(sch, ntx)) 246962306a36Sopenharmony_ci return 0; 247062306a36Sopenharmony_ci return ntx; 247162306a36Sopenharmony_ci} 247262306a36Sopenharmony_ci 247362306a36Sopenharmony_cistatic int taprio_dump_class(struct Qdisc *sch, unsigned long cl, 247462306a36Sopenharmony_ci struct sk_buff *skb, struct tcmsg *tcm) 247562306a36Sopenharmony_ci{ 247662306a36Sopenharmony_ci struct Qdisc *child = taprio_leaf(sch, cl); 247762306a36Sopenharmony_ci 247862306a36Sopenharmony_ci tcm->tcm_parent = TC_H_ROOT; 247962306a36Sopenharmony_ci tcm->tcm_handle |= TC_H_MIN(cl); 248062306a36Sopenharmony_ci tcm->tcm_info = child->handle; 248162306a36Sopenharmony_ci 248262306a36Sopenharmony_ci return 0; 248362306a36Sopenharmony_ci} 248462306a36Sopenharmony_ci 248562306a36Sopenharmony_cistatic int taprio_dump_class_stats(struct Qdisc *sch, unsigned long cl, 248662306a36Sopenharmony_ci struct gnet_dump *d) 248762306a36Sopenharmony_ci __releases(d->lock) 248862306a36Sopenharmony_ci __acquires(d->lock) 248962306a36Sopenharmony_ci{ 249062306a36Sopenharmony_ci struct Qdisc *child = taprio_leaf(sch, cl); 249162306a36Sopenharmony_ci struct tc_taprio_qopt_offload offload = { 249262306a36Sopenharmony_ci .cmd = TAPRIO_CMD_QUEUE_STATS, 249362306a36Sopenharmony_ci .queue_stats = { 249462306a36Sopenharmony_ci .queue = cl - 1, 249562306a36Sopenharmony_ci }, 249662306a36Sopenharmony_ci }; 249762306a36Sopenharmony_ci 249862306a36Sopenharmony_ci if (gnet_stats_copy_basic(d, NULL, &child->bstats, true) < 0 || 249962306a36Sopenharmony_ci qdisc_qstats_copy(d, child) < 0) 250062306a36Sopenharmony_ci return -1; 250162306a36Sopenharmony_ci 250262306a36Sopenharmony_ci return taprio_dump_xstats(sch, d, &offload, &offload.queue_stats.stats); 250362306a36Sopenharmony_ci} 250462306a36Sopenharmony_ci 250562306a36Sopenharmony_cistatic void taprio_walk(struct Qdisc *sch, struct qdisc_walker *arg) 250662306a36Sopenharmony_ci{ 250762306a36Sopenharmony_ci struct net_device *dev = qdisc_dev(sch); 250862306a36Sopenharmony_ci unsigned long ntx; 250962306a36Sopenharmony_ci 251062306a36Sopenharmony_ci if (arg->stop) 251162306a36Sopenharmony_ci return; 251262306a36Sopenharmony_ci 251362306a36Sopenharmony_ci arg->count = arg->skip; 251462306a36Sopenharmony_ci for (ntx = arg->skip; ntx < dev->num_tx_queues; ntx++) { 251562306a36Sopenharmony_ci if (!tc_qdisc_stats_dump(sch, ntx + 1, arg)) 251662306a36Sopenharmony_ci break; 251762306a36Sopenharmony_ci } 251862306a36Sopenharmony_ci} 251962306a36Sopenharmony_ci 252062306a36Sopenharmony_cistatic struct netdev_queue *taprio_select_queue(struct Qdisc *sch, 252162306a36Sopenharmony_ci struct tcmsg *tcm) 252262306a36Sopenharmony_ci{ 252362306a36Sopenharmony_ci return taprio_queue_get(sch, TC_H_MIN(tcm->tcm_parent)); 252462306a36Sopenharmony_ci} 252562306a36Sopenharmony_ci 252662306a36Sopenharmony_cistatic const struct Qdisc_class_ops taprio_class_ops = { 252762306a36Sopenharmony_ci .graft = taprio_graft, 252862306a36Sopenharmony_ci .leaf = taprio_leaf, 252962306a36Sopenharmony_ci .find = taprio_find, 253062306a36Sopenharmony_ci .walk = taprio_walk, 253162306a36Sopenharmony_ci .dump = taprio_dump_class, 253262306a36Sopenharmony_ci .dump_stats = taprio_dump_class_stats, 253362306a36Sopenharmony_ci .select_queue = taprio_select_queue, 253462306a36Sopenharmony_ci}; 253562306a36Sopenharmony_ci 253662306a36Sopenharmony_cistatic struct Qdisc_ops taprio_qdisc_ops __read_mostly = { 253762306a36Sopenharmony_ci .cl_ops = &taprio_class_ops, 253862306a36Sopenharmony_ci .id = "taprio", 253962306a36Sopenharmony_ci .priv_size = sizeof(struct taprio_sched), 254062306a36Sopenharmony_ci .init = taprio_init, 254162306a36Sopenharmony_ci .change = taprio_change, 254262306a36Sopenharmony_ci .destroy = taprio_destroy, 254362306a36Sopenharmony_ci .reset = taprio_reset, 254462306a36Sopenharmony_ci .attach = taprio_attach, 254562306a36Sopenharmony_ci .peek = taprio_peek, 254662306a36Sopenharmony_ci .dequeue = taprio_dequeue, 254762306a36Sopenharmony_ci .enqueue = taprio_enqueue, 254862306a36Sopenharmony_ci .dump = taprio_dump, 254962306a36Sopenharmony_ci .dump_stats = taprio_dump_stats, 255062306a36Sopenharmony_ci .owner = THIS_MODULE, 255162306a36Sopenharmony_ci}; 255262306a36Sopenharmony_ci 255362306a36Sopenharmony_cistatic struct notifier_block taprio_device_notifier = { 255462306a36Sopenharmony_ci .notifier_call = taprio_dev_notifier, 255562306a36Sopenharmony_ci}; 255662306a36Sopenharmony_ci 255762306a36Sopenharmony_cistatic int __init taprio_module_init(void) 255862306a36Sopenharmony_ci{ 255962306a36Sopenharmony_ci int err = register_netdevice_notifier(&taprio_device_notifier); 256062306a36Sopenharmony_ci 256162306a36Sopenharmony_ci if (err) 256262306a36Sopenharmony_ci return err; 256362306a36Sopenharmony_ci 256462306a36Sopenharmony_ci return register_qdisc(&taprio_qdisc_ops); 256562306a36Sopenharmony_ci} 256662306a36Sopenharmony_ci 256762306a36Sopenharmony_cistatic void __exit taprio_module_exit(void) 256862306a36Sopenharmony_ci{ 256962306a36Sopenharmony_ci unregister_qdisc(&taprio_qdisc_ops); 257062306a36Sopenharmony_ci unregister_netdevice_notifier(&taprio_device_notifier); 257162306a36Sopenharmony_ci} 257262306a36Sopenharmony_ci 257362306a36Sopenharmony_cimodule_init(taprio_module_init); 257462306a36Sopenharmony_cimodule_exit(taprio_module_exit); 257562306a36Sopenharmony_ciMODULE_LICENSE("GPL"); 2576