18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 28c2ecf20Sopenharmony_ci 38c2ecf20Sopenharmony_ci/* net/sched/sch_taprio.c Time Aware Priority Scheduler 48c2ecf20Sopenharmony_ci * 58c2ecf20Sopenharmony_ci * Authors: Vinicius Costa Gomes <vinicius.gomes@intel.com> 68c2ecf20Sopenharmony_ci * 78c2ecf20Sopenharmony_ci */ 88c2ecf20Sopenharmony_ci 98c2ecf20Sopenharmony_ci#include <linux/types.h> 108c2ecf20Sopenharmony_ci#include <linux/slab.h> 118c2ecf20Sopenharmony_ci#include <linux/kernel.h> 128c2ecf20Sopenharmony_ci#include <linux/string.h> 138c2ecf20Sopenharmony_ci#include <linux/list.h> 148c2ecf20Sopenharmony_ci#include <linux/errno.h> 158c2ecf20Sopenharmony_ci#include <linux/skbuff.h> 168c2ecf20Sopenharmony_ci#include <linux/math64.h> 178c2ecf20Sopenharmony_ci#include <linux/module.h> 188c2ecf20Sopenharmony_ci#include <linux/spinlock.h> 198c2ecf20Sopenharmony_ci#include <linux/rcupdate.h> 208c2ecf20Sopenharmony_ci#include <net/netlink.h> 218c2ecf20Sopenharmony_ci#include <net/pkt_sched.h> 228c2ecf20Sopenharmony_ci#include <net/pkt_cls.h> 238c2ecf20Sopenharmony_ci#include <net/sch_generic.h> 248c2ecf20Sopenharmony_ci#include <net/sock.h> 258c2ecf20Sopenharmony_ci#include <net/tcp.h> 268c2ecf20Sopenharmony_ci 278c2ecf20Sopenharmony_cistatic LIST_HEAD(taprio_list); 288c2ecf20Sopenharmony_cistatic DEFINE_SPINLOCK(taprio_list_lock); 298c2ecf20Sopenharmony_ci 308c2ecf20Sopenharmony_ci#define TAPRIO_ALL_GATES_OPEN -1 318c2ecf20Sopenharmony_ci 328c2ecf20Sopenharmony_ci#define TXTIME_ASSIST_IS_ENABLED(flags) ((flags) & TCA_TAPRIO_ATTR_FLAG_TXTIME_ASSIST) 338c2ecf20Sopenharmony_ci#define FULL_OFFLOAD_IS_ENABLED(flags) ((flags) & TCA_TAPRIO_ATTR_FLAG_FULL_OFFLOAD) 348c2ecf20Sopenharmony_ci#define TAPRIO_FLAGS_INVALID U32_MAX 358c2ecf20Sopenharmony_ci 368c2ecf20Sopenharmony_cistruct sched_entry { 378c2ecf20Sopenharmony_ci struct list_head list; 388c2ecf20Sopenharmony_ci 398c2ecf20Sopenharmony_ci /* The instant that this entry "closes" and the next one 408c2ecf20Sopenharmony_ci * should open, the qdisc will make some effort so that no 418c2ecf20Sopenharmony_ci * packet leaves after this time. 428c2ecf20Sopenharmony_ci */ 438c2ecf20Sopenharmony_ci ktime_t close_time; 448c2ecf20Sopenharmony_ci ktime_t next_txtime; 458c2ecf20Sopenharmony_ci atomic_t budget; 468c2ecf20Sopenharmony_ci int index; 478c2ecf20Sopenharmony_ci u32 gate_mask; 488c2ecf20Sopenharmony_ci u32 interval; 498c2ecf20Sopenharmony_ci u8 command; 508c2ecf20Sopenharmony_ci}; 518c2ecf20Sopenharmony_ci 528c2ecf20Sopenharmony_cistruct sched_gate_list { 538c2ecf20Sopenharmony_ci struct rcu_head rcu; 548c2ecf20Sopenharmony_ci struct list_head entries; 558c2ecf20Sopenharmony_ci size_t num_entries; 568c2ecf20Sopenharmony_ci ktime_t cycle_close_time; 578c2ecf20Sopenharmony_ci s64 cycle_time; 588c2ecf20Sopenharmony_ci s64 cycle_time_extension; 598c2ecf20Sopenharmony_ci s64 base_time; 608c2ecf20Sopenharmony_ci}; 618c2ecf20Sopenharmony_ci 628c2ecf20Sopenharmony_cistruct taprio_sched { 638c2ecf20Sopenharmony_ci struct Qdisc **qdiscs; 648c2ecf20Sopenharmony_ci struct Qdisc *root; 658c2ecf20Sopenharmony_ci u32 flags; 668c2ecf20Sopenharmony_ci enum tk_offsets tk_offset; 678c2ecf20Sopenharmony_ci int clockid; 688c2ecf20Sopenharmony_ci bool offloaded; 698c2ecf20Sopenharmony_ci atomic64_t picos_per_byte; /* Using picoseconds because for 10Gbps+ 708c2ecf20Sopenharmony_ci * speeds it's sub-nanoseconds per byte 718c2ecf20Sopenharmony_ci */ 728c2ecf20Sopenharmony_ci 738c2ecf20Sopenharmony_ci /* Protects the update side of the RCU protected current_entry */ 748c2ecf20Sopenharmony_ci spinlock_t current_entry_lock; 758c2ecf20Sopenharmony_ci struct sched_entry __rcu *current_entry; 768c2ecf20Sopenharmony_ci struct sched_gate_list __rcu *oper_sched; 778c2ecf20Sopenharmony_ci struct sched_gate_list __rcu *admin_sched; 788c2ecf20Sopenharmony_ci struct hrtimer advance_timer; 798c2ecf20Sopenharmony_ci struct list_head taprio_list; 808c2ecf20Sopenharmony_ci struct sk_buff *(*dequeue)(struct Qdisc *sch); 818c2ecf20Sopenharmony_ci struct sk_buff *(*peek)(struct Qdisc *sch); 828c2ecf20Sopenharmony_ci u32 txtime_delay; 838c2ecf20Sopenharmony_ci}; 848c2ecf20Sopenharmony_ci 858c2ecf20Sopenharmony_cistruct __tc_taprio_qopt_offload { 868c2ecf20Sopenharmony_ci refcount_t users; 878c2ecf20Sopenharmony_ci struct tc_taprio_qopt_offload offload; 888c2ecf20Sopenharmony_ci}; 898c2ecf20Sopenharmony_ci 908c2ecf20Sopenharmony_cistatic ktime_t sched_base_time(const struct sched_gate_list *sched) 918c2ecf20Sopenharmony_ci{ 928c2ecf20Sopenharmony_ci if (!sched) 938c2ecf20Sopenharmony_ci return KTIME_MAX; 948c2ecf20Sopenharmony_ci 958c2ecf20Sopenharmony_ci return ns_to_ktime(sched->base_time); 968c2ecf20Sopenharmony_ci} 978c2ecf20Sopenharmony_ci 988c2ecf20Sopenharmony_cistatic ktime_t taprio_mono_to_any(const struct taprio_sched *q, ktime_t mono) 998c2ecf20Sopenharmony_ci{ 1008c2ecf20Sopenharmony_ci /* This pairs with WRITE_ONCE() in taprio_parse_clockid() */ 1018c2ecf20Sopenharmony_ci enum tk_offsets tk_offset = READ_ONCE(q->tk_offset); 1028c2ecf20Sopenharmony_ci 1038c2ecf20Sopenharmony_ci switch (tk_offset) { 1048c2ecf20Sopenharmony_ci case TK_OFFS_MAX: 1058c2ecf20Sopenharmony_ci return mono; 1068c2ecf20Sopenharmony_ci default: 1078c2ecf20Sopenharmony_ci return ktime_mono_to_any(mono, tk_offset); 1088c2ecf20Sopenharmony_ci } 1098c2ecf20Sopenharmony_ci} 1108c2ecf20Sopenharmony_ci 1118c2ecf20Sopenharmony_cistatic ktime_t taprio_get_time(const struct taprio_sched *q) 1128c2ecf20Sopenharmony_ci{ 1138c2ecf20Sopenharmony_ci return taprio_mono_to_any(q, ktime_get()); 1148c2ecf20Sopenharmony_ci} 1158c2ecf20Sopenharmony_ci 1168c2ecf20Sopenharmony_cistatic void taprio_free_sched_cb(struct rcu_head *head) 1178c2ecf20Sopenharmony_ci{ 1188c2ecf20Sopenharmony_ci struct sched_gate_list *sched = container_of(head, struct sched_gate_list, rcu); 1198c2ecf20Sopenharmony_ci struct sched_entry *entry, *n; 1208c2ecf20Sopenharmony_ci 1218c2ecf20Sopenharmony_ci if (!sched) 1228c2ecf20Sopenharmony_ci return; 1238c2ecf20Sopenharmony_ci 1248c2ecf20Sopenharmony_ci list_for_each_entry_safe(entry, n, &sched->entries, list) { 1258c2ecf20Sopenharmony_ci list_del(&entry->list); 1268c2ecf20Sopenharmony_ci kfree(entry); 1278c2ecf20Sopenharmony_ci } 1288c2ecf20Sopenharmony_ci 1298c2ecf20Sopenharmony_ci kfree(sched); 1308c2ecf20Sopenharmony_ci} 1318c2ecf20Sopenharmony_ci 1328c2ecf20Sopenharmony_cistatic void switch_schedules(struct taprio_sched *q, 1338c2ecf20Sopenharmony_ci struct sched_gate_list **admin, 1348c2ecf20Sopenharmony_ci struct sched_gate_list **oper) 1358c2ecf20Sopenharmony_ci{ 1368c2ecf20Sopenharmony_ci rcu_assign_pointer(q->oper_sched, *admin); 1378c2ecf20Sopenharmony_ci rcu_assign_pointer(q->admin_sched, NULL); 1388c2ecf20Sopenharmony_ci 1398c2ecf20Sopenharmony_ci if (*oper) 1408c2ecf20Sopenharmony_ci call_rcu(&(*oper)->rcu, taprio_free_sched_cb); 1418c2ecf20Sopenharmony_ci 1428c2ecf20Sopenharmony_ci *oper = *admin; 1438c2ecf20Sopenharmony_ci *admin = NULL; 1448c2ecf20Sopenharmony_ci} 1458c2ecf20Sopenharmony_ci 1468c2ecf20Sopenharmony_ci/* Get how much time has been already elapsed in the current cycle. */ 1478c2ecf20Sopenharmony_cistatic s32 get_cycle_time_elapsed(struct sched_gate_list *sched, ktime_t time) 1488c2ecf20Sopenharmony_ci{ 1498c2ecf20Sopenharmony_ci ktime_t time_since_sched_start; 1508c2ecf20Sopenharmony_ci s32 time_elapsed; 1518c2ecf20Sopenharmony_ci 1528c2ecf20Sopenharmony_ci time_since_sched_start = ktime_sub(time, sched->base_time); 1538c2ecf20Sopenharmony_ci div_s64_rem(time_since_sched_start, sched->cycle_time, &time_elapsed); 1548c2ecf20Sopenharmony_ci 1558c2ecf20Sopenharmony_ci return time_elapsed; 1568c2ecf20Sopenharmony_ci} 1578c2ecf20Sopenharmony_ci 1588c2ecf20Sopenharmony_cistatic ktime_t get_interval_end_time(struct sched_gate_list *sched, 1598c2ecf20Sopenharmony_ci struct sched_gate_list *admin, 1608c2ecf20Sopenharmony_ci struct sched_entry *entry, 1618c2ecf20Sopenharmony_ci ktime_t intv_start) 1628c2ecf20Sopenharmony_ci{ 1638c2ecf20Sopenharmony_ci s32 cycle_elapsed = get_cycle_time_elapsed(sched, intv_start); 1648c2ecf20Sopenharmony_ci ktime_t intv_end, cycle_ext_end, cycle_end; 1658c2ecf20Sopenharmony_ci 1668c2ecf20Sopenharmony_ci cycle_end = ktime_add_ns(intv_start, sched->cycle_time - cycle_elapsed); 1678c2ecf20Sopenharmony_ci intv_end = ktime_add_ns(intv_start, entry->interval); 1688c2ecf20Sopenharmony_ci cycle_ext_end = ktime_add(cycle_end, sched->cycle_time_extension); 1698c2ecf20Sopenharmony_ci 1708c2ecf20Sopenharmony_ci if (ktime_before(intv_end, cycle_end)) 1718c2ecf20Sopenharmony_ci return intv_end; 1728c2ecf20Sopenharmony_ci else if (admin && admin != sched && 1738c2ecf20Sopenharmony_ci ktime_after(admin->base_time, cycle_end) && 1748c2ecf20Sopenharmony_ci ktime_before(admin->base_time, cycle_ext_end)) 1758c2ecf20Sopenharmony_ci return admin->base_time; 1768c2ecf20Sopenharmony_ci else 1778c2ecf20Sopenharmony_ci return cycle_end; 1788c2ecf20Sopenharmony_ci} 1798c2ecf20Sopenharmony_ci 1808c2ecf20Sopenharmony_cistatic int length_to_duration(struct taprio_sched *q, int len) 1818c2ecf20Sopenharmony_ci{ 1828c2ecf20Sopenharmony_ci return div_u64(len * atomic64_read(&q->picos_per_byte), 1000); 1838c2ecf20Sopenharmony_ci} 1848c2ecf20Sopenharmony_ci 1858c2ecf20Sopenharmony_ci/* Returns the entry corresponding to next available interval. If 1868c2ecf20Sopenharmony_ci * validate_interval is set, it only validates whether the timestamp occurs 1878c2ecf20Sopenharmony_ci * when the gate corresponding to the skb's traffic class is open. 1888c2ecf20Sopenharmony_ci */ 1898c2ecf20Sopenharmony_cistatic struct sched_entry *find_entry_to_transmit(struct sk_buff *skb, 1908c2ecf20Sopenharmony_ci struct Qdisc *sch, 1918c2ecf20Sopenharmony_ci struct sched_gate_list *sched, 1928c2ecf20Sopenharmony_ci struct sched_gate_list *admin, 1938c2ecf20Sopenharmony_ci ktime_t time, 1948c2ecf20Sopenharmony_ci ktime_t *interval_start, 1958c2ecf20Sopenharmony_ci ktime_t *interval_end, 1968c2ecf20Sopenharmony_ci bool validate_interval) 1978c2ecf20Sopenharmony_ci{ 1988c2ecf20Sopenharmony_ci ktime_t curr_intv_start, curr_intv_end, cycle_end, packet_transmit_time; 1998c2ecf20Sopenharmony_ci ktime_t earliest_txtime = KTIME_MAX, txtime, cycle, transmit_end_time; 2008c2ecf20Sopenharmony_ci struct sched_entry *entry = NULL, *entry_found = NULL; 2018c2ecf20Sopenharmony_ci struct taprio_sched *q = qdisc_priv(sch); 2028c2ecf20Sopenharmony_ci struct net_device *dev = qdisc_dev(sch); 2038c2ecf20Sopenharmony_ci bool entry_available = false; 2048c2ecf20Sopenharmony_ci s32 cycle_elapsed; 2058c2ecf20Sopenharmony_ci int tc, n; 2068c2ecf20Sopenharmony_ci 2078c2ecf20Sopenharmony_ci tc = netdev_get_prio_tc_map(dev, skb->priority); 2088c2ecf20Sopenharmony_ci packet_transmit_time = length_to_duration(q, qdisc_pkt_len(skb)); 2098c2ecf20Sopenharmony_ci 2108c2ecf20Sopenharmony_ci *interval_start = 0; 2118c2ecf20Sopenharmony_ci *interval_end = 0; 2128c2ecf20Sopenharmony_ci 2138c2ecf20Sopenharmony_ci if (!sched) 2148c2ecf20Sopenharmony_ci return NULL; 2158c2ecf20Sopenharmony_ci 2168c2ecf20Sopenharmony_ci cycle = sched->cycle_time; 2178c2ecf20Sopenharmony_ci cycle_elapsed = get_cycle_time_elapsed(sched, time); 2188c2ecf20Sopenharmony_ci curr_intv_end = ktime_sub_ns(time, cycle_elapsed); 2198c2ecf20Sopenharmony_ci cycle_end = ktime_add_ns(curr_intv_end, cycle); 2208c2ecf20Sopenharmony_ci 2218c2ecf20Sopenharmony_ci list_for_each_entry(entry, &sched->entries, list) { 2228c2ecf20Sopenharmony_ci curr_intv_start = curr_intv_end; 2238c2ecf20Sopenharmony_ci curr_intv_end = get_interval_end_time(sched, admin, entry, 2248c2ecf20Sopenharmony_ci curr_intv_start); 2258c2ecf20Sopenharmony_ci 2268c2ecf20Sopenharmony_ci if (ktime_after(curr_intv_start, cycle_end)) 2278c2ecf20Sopenharmony_ci break; 2288c2ecf20Sopenharmony_ci 2298c2ecf20Sopenharmony_ci if (!(entry->gate_mask & BIT(tc)) || 2308c2ecf20Sopenharmony_ci packet_transmit_time > entry->interval) 2318c2ecf20Sopenharmony_ci continue; 2328c2ecf20Sopenharmony_ci 2338c2ecf20Sopenharmony_ci txtime = entry->next_txtime; 2348c2ecf20Sopenharmony_ci 2358c2ecf20Sopenharmony_ci if (ktime_before(txtime, time) || validate_interval) { 2368c2ecf20Sopenharmony_ci transmit_end_time = ktime_add_ns(time, packet_transmit_time); 2378c2ecf20Sopenharmony_ci if ((ktime_before(curr_intv_start, time) && 2388c2ecf20Sopenharmony_ci ktime_before(transmit_end_time, curr_intv_end)) || 2398c2ecf20Sopenharmony_ci (ktime_after(curr_intv_start, time) && !validate_interval)) { 2408c2ecf20Sopenharmony_ci entry_found = entry; 2418c2ecf20Sopenharmony_ci *interval_start = curr_intv_start; 2428c2ecf20Sopenharmony_ci *interval_end = curr_intv_end; 2438c2ecf20Sopenharmony_ci break; 2448c2ecf20Sopenharmony_ci } else if (!entry_available && !validate_interval) { 2458c2ecf20Sopenharmony_ci /* Here, we are just trying to find out the 2468c2ecf20Sopenharmony_ci * first available interval in the next cycle. 2478c2ecf20Sopenharmony_ci */ 2488c2ecf20Sopenharmony_ci entry_available = 1; 2498c2ecf20Sopenharmony_ci entry_found = entry; 2508c2ecf20Sopenharmony_ci *interval_start = ktime_add_ns(curr_intv_start, cycle); 2518c2ecf20Sopenharmony_ci *interval_end = ktime_add_ns(curr_intv_end, cycle); 2528c2ecf20Sopenharmony_ci } 2538c2ecf20Sopenharmony_ci } else if (ktime_before(txtime, earliest_txtime) && 2548c2ecf20Sopenharmony_ci !entry_available) { 2558c2ecf20Sopenharmony_ci earliest_txtime = txtime; 2568c2ecf20Sopenharmony_ci entry_found = entry; 2578c2ecf20Sopenharmony_ci n = div_s64(ktime_sub(txtime, curr_intv_start), cycle); 2588c2ecf20Sopenharmony_ci *interval_start = ktime_add(curr_intv_start, n * cycle); 2598c2ecf20Sopenharmony_ci *interval_end = ktime_add(curr_intv_end, n * cycle); 2608c2ecf20Sopenharmony_ci } 2618c2ecf20Sopenharmony_ci } 2628c2ecf20Sopenharmony_ci 2638c2ecf20Sopenharmony_ci return entry_found; 2648c2ecf20Sopenharmony_ci} 2658c2ecf20Sopenharmony_ci 2668c2ecf20Sopenharmony_cistatic bool is_valid_interval(struct sk_buff *skb, struct Qdisc *sch) 2678c2ecf20Sopenharmony_ci{ 2688c2ecf20Sopenharmony_ci struct taprio_sched *q = qdisc_priv(sch); 2698c2ecf20Sopenharmony_ci struct sched_gate_list *sched, *admin; 2708c2ecf20Sopenharmony_ci ktime_t interval_start, interval_end; 2718c2ecf20Sopenharmony_ci struct sched_entry *entry; 2728c2ecf20Sopenharmony_ci 2738c2ecf20Sopenharmony_ci rcu_read_lock(); 2748c2ecf20Sopenharmony_ci sched = rcu_dereference(q->oper_sched); 2758c2ecf20Sopenharmony_ci admin = rcu_dereference(q->admin_sched); 2768c2ecf20Sopenharmony_ci 2778c2ecf20Sopenharmony_ci entry = find_entry_to_transmit(skb, sch, sched, admin, skb->tstamp, 2788c2ecf20Sopenharmony_ci &interval_start, &interval_end, true); 2798c2ecf20Sopenharmony_ci rcu_read_unlock(); 2808c2ecf20Sopenharmony_ci 2818c2ecf20Sopenharmony_ci return entry; 2828c2ecf20Sopenharmony_ci} 2838c2ecf20Sopenharmony_ci 2848c2ecf20Sopenharmony_cistatic bool taprio_flags_valid(u32 flags) 2858c2ecf20Sopenharmony_ci{ 2868c2ecf20Sopenharmony_ci /* Make sure no other flag bits are set. */ 2878c2ecf20Sopenharmony_ci if (flags & ~(TCA_TAPRIO_ATTR_FLAG_TXTIME_ASSIST | 2888c2ecf20Sopenharmony_ci TCA_TAPRIO_ATTR_FLAG_FULL_OFFLOAD)) 2898c2ecf20Sopenharmony_ci return false; 2908c2ecf20Sopenharmony_ci /* txtime-assist and full offload are mutually exclusive */ 2918c2ecf20Sopenharmony_ci if ((flags & TCA_TAPRIO_ATTR_FLAG_TXTIME_ASSIST) && 2928c2ecf20Sopenharmony_ci (flags & TCA_TAPRIO_ATTR_FLAG_FULL_OFFLOAD)) 2938c2ecf20Sopenharmony_ci return false; 2948c2ecf20Sopenharmony_ci return true; 2958c2ecf20Sopenharmony_ci} 2968c2ecf20Sopenharmony_ci 2978c2ecf20Sopenharmony_ci/* This returns the tstamp value set by TCP in terms of the set clock. */ 2988c2ecf20Sopenharmony_cistatic ktime_t get_tcp_tstamp(struct taprio_sched *q, struct sk_buff *skb) 2998c2ecf20Sopenharmony_ci{ 3008c2ecf20Sopenharmony_ci unsigned int offset = skb_network_offset(skb); 3018c2ecf20Sopenharmony_ci const struct ipv6hdr *ipv6h; 3028c2ecf20Sopenharmony_ci const struct iphdr *iph; 3038c2ecf20Sopenharmony_ci struct ipv6hdr _ipv6h; 3048c2ecf20Sopenharmony_ci 3058c2ecf20Sopenharmony_ci ipv6h = skb_header_pointer(skb, offset, sizeof(_ipv6h), &_ipv6h); 3068c2ecf20Sopenharmony_ci if (!ipv6h) 3078c2ecf20Sopenharmony_ci return 0; 3088c2ecf20Sopenharmony_ci 3098c2ecf20Sopenharmony_ci if (ipv6h->version == 4) { 3108c2ecf20Sopenharmony_ci iph = (struct iphdr *)ipv6h; 3118c2ecf20Sopenharmony_ci offset += iph->ihl * 4; 3128c2ecf20Sopenharmony_ci 3138c2ecf20Sopenharmony_ci /* special-case 6in4 tunnelling, as that is a common way to get 3148c2ecf20Sopenharmony_ci * v6 connectivity in the home 3158c2ecf20Sopenharmony_ci */ 3168c2ecf20Sopenharmony_ci if (iph->protocol == IPPROTO_IPV6) { 3178c2ecf20Sopenharmony_ci ipv6h = skb_header_pointer(skb, offset, 3188c2ecf20Sopenharmony_ci sizeof(_ipv6h), &_ipv6h); 3198c2ecf20Sopenharmony_ci 3208c2ecf20Sopenharmony_ci if (!ipv6h || ipv6h->nexthdr != IPPROTO_TCP) 3218c2ecf20Sopenharmony_ci return 0; 3228c2ecf20Sopenharmony_ci } else if (iph->protocol != IPPROTO_TCP) { 3238c2ecf20Sopenharmony_ci return 0; 3248c2ecf20Sopenharmony_ci } 3258c2ecf20Sopenharmony_ci } else if (ipv6h->version == 6 && ipv6h->nexthdr != IPPROTO_TCP) { 3268c2ecf20Sopenharmony_ci return 0; 3278c2ecf20Sopenharmony_ci } 3288c2ecf20Sopenharmony_ci 3298c2ecf20Sopenharmony_ci return taprio_mono_to_any(q, skb->skb_mstamp_ns); 3308c2ecf20Sopenharmony_ci} 3318c2ecf20Sopenharmony_ci 3328c2ecf20Sopenharmony_ci/* There are a few scenarios where we will have to modify the txtime from 3338c2ecf20Sopenharmony_ci * what is read from next_txtime in sched_entry. They are: 3348c2ecf20Sopenharmony_ci * 1. If txtime is in the past, 3358c2ecf20Sopenharmony_ci * a. The gate for the traffic class is currently open and packet can be 3368c2ecf20Sopenharmony_ci * transmitted before it closes, schedule the packet right away. 3378c2ecf20Sopenharmony_ci * b. If the gate corresponding to the traffic class is going to open later 3388c2ecf20Sopenharmony_ci * in the cycle, set the txtime of packet to the interval start. 3398c2ecf20Sopenharmony_ci * 2. If txtime is in the future, there are packets corresponding to the 3408c2ecf20Sopenharmony_ci * current traffic class waiting to be transmitted. So, the following 3418c2ecf20Sopenharmony_ci * possibilities exist: 3428c2ecf20Sopenharmony_ci * a. We can transmit the packet before the window containing the txtime 3438c2ecf20Sopenharmony_ci * closes. 3448c2ecf20Sopenharmony_ci * b. The window might close before the transmission can be completed 3458c2ecf20Sopenharmony_ci * successfully. So, schedule the packet in the next open window. 3468c2ecf20Sopenharmony_ci */ 3478c2ecf20Sopenharmony_cistatic long get_packet_txtime(struct sk_buff *skb, struct Qdisc *sch) 3488c2ecf20Sopenharmony_ci{ 3498c2ecf20Sopenharmony_ci ktime_t transmit_end_time, interval_end, interval_start, tcp_tstamp; 3508c2ecf20Sopenharmony_ci struct taprio_sched *q = qdisc_priv(sch); 3518c2ecf20Sopenharmony_ci struct sched_gate_list *sched, *admin; 3528c2ecf20Sopenharmony_ci ktime_t minimum_time, now, txtime; 3538c2ecf20Sopenharmony_ci int len, packet_transmit_time; 3548c2ecf20Sopenharmony_ci struct sched_entry *entry; 3558c2ecf20Sopenharmony_ci bool sched_changed; 3568c2ecf20Sopenharmony_ci 3578c2ecf20Sopenharmony_ci now = taprio_get_time(q); 3588c2ecf20Sopenharmony_ci minimum_time = ktime_add_ns(now, q->txtime_delay); 3598c2ecf20Sopenharmony_ci 3608c2ecf20Sopenharmony_ci tcp_tstamp = get_tcp_tstamp(q, skb); 3618c2ecf20Sopenharmony_ci minimum_time = max_t(ktime_t, minimum_time, tcp_tstamp); 3628c2ecf20Sopenharmony_ci 3638c2ecf20Sopenharmony_ci rcu_read_lock(); 3648c2ecf20Sopenharmony_ci admin = rcu_dereference(q->admin_sched); 3658c2ecf20Sopenharmony_ci sched = rcu_dereference(q->oper_sched); 3668c2ecf20Sopenharmony_ci if (admin && ktime_after(minimum_time, admin->base_time)) 3678c2ecf20Sopenharmony_ci switch_schedules(q, &admin, &sched); 3688c2ecf20Sopenharmony_ci 3698c2ecf20Sopenharmony_ci /* Until the schedule starts, all the queues are open */ 3708c2ecf20Sopenharmony_ci if (!sched || ktime_before(minimum_time, sched->base_time)) { 3718c2ecf20Sopenharmony_ci txtime = minimum_time; 3728c2ecf20Sopenharmony_ci goto done; 3738c2ecf20Sopenharmony_ci } 3748c2ecf20Sopenharmony_ci 3758c2ecf20Sopenharmony_ci len = qdisc_pkt_len(skb); 3768c2ecf20Sopenharmony_ci packet_transmit_time = length_to_duration(q, len); 3778c2ecf20Sopenharmony_ci 3788c2ecf20Sopenharmony_ci do { 3798c2ecf20Sopenharmony_ci sched_changed = 0; 3808c2ecf20Sopenharmony_ci 3818c2ecf20Sopenharmony_ci entry = find_entry_to_transmit(skb, sch, sched, admin, 3828c2ecf20Sopenharmony_ci minimum_time, 3838c2ecf20Sopenharmony_ci &interval_start, &interval_end, 3848c2ecf20Sopenharmony_ci false); 3858c2ecf20Sopenharmony_ci if (!entry) { 3868c2ecf20Sopenharmony_ci txtime = 0; 3878c2ecf20Sopenharmony_ci goto done; 3888c2ecf20Sopenharmony_ci } 3898c2ecf20Sopenharmony_ci 3908c2ecf20Sopenharmony_ci txtime = entry->next_txtime; 3918c2ecf20Sopenharmony_ci txtime = max_t(ktime_t, txtime, minimum_time); 3928c2ecf20Sopenharmony_ci txtime = max_t(ktime_t, txtime, interval_start); 3938c2ecf20Sopenharmony_ci 3948c2ecf20Sopenharmony_ci if (admin && admin != sched && 3958c2ecf20Sopenharmony_ci ktime_after(txtime, admin->base_time)) { 3968c2ecf20Sopenharmony_ci sched = admin; 3978c2ecf20Sopenharmony_ci sched_changed = 1; 3988c2ecf20Sopenharmony_ci continue; 3998c2ecf20Sopenharmony_ci } 4008c2ecf20Sopenharmony_ci 4018c2ecf20Sopenharmony_ci transmit_end_time = ktime_add(txtime, packet_transmit_time); 4028c2ecf20Sopenharmony_ci minimum_time = transmit_end_time; 4038c2ecf20Sopenharmony_ci 4048c2ecf20Sopenharmony_ci /* Update the txtime of current entry to the next time it's 4058c2ecf20Sopenharmony_ci * interval starts. 4068c2ecf20Sopenharmony_ci */ 4078c2ecf20Sopenharmony_ci if (ktime_after(transmit_end_time, interval_end)) 4088c2ecf20Sopenharmony_ci entry->next_txtime = ktime_add(interval_start, sched->cycle_time); 4098c2ecf20Sopenharmony_ci } while (sched_changed || ktime_after(transmit_end_time, interval_end)); 4108c2ecf20Sopenharmony_ci 4118c2ecf20Sopenharmony_ci entry->next_txtime = transmit_end_time; 4128c2ecf20Sopenharmony_ci 4138c2ecf20Sopenharmony_cidone: 4148c2ecf20Sopenharmony_ci rcu_read_unlock(); 4158c2ecf20Sopenharmony_ci return txtime; 4168c2ecf20Sopenharmony_ci} 4178c2ecf20Sopenharmony_ci 4188c2ecf20Sopenharmony_cistatic int taprio_enqueue(struct sk_buff *skb, struct Qdisc *sch, 4198c2ecf20Sopenharmony_ci struct sk_buff **to_free) 4208c2ecf20Sopenharmony_ci{ 4218c2ecf20Sopenharmony_ci struct taprio_sched *q = qdisc_priv(sch); 4228c2ecf20Sopenharmony_ci struct Qdisc *child; 4238c2ecf20Sopenharmony_ci int queue; 4248c2ecf20Sopenharmony_ci 4258c2ecf20Sopenharmony_ci queue = skb_get_queue_mapping(skb); 4268c2ecf20Sopenharmony_ci 4278c2ecf20Sopenharmony_ci child = q->qdiscs[queue]; 4288c2ecf20Sopenharmony_ci if (unlikely(!child)) 4298c2ecf20Sopenharmony_ci return qdisc_drop(skb, sch, to_free); 4308c2ecf20Sopenharmony_ci 4318c2ecf20Sopenharmony_ci /* sk_flags are only safe to use on full sockets. */ 4328c2ecf20Sopenharmony_ci if (skb->sk && sk_fullsock(skb->sk) && sock_flag(skb->sk, SOCK_TXTIME)) { 4338c2ecf20Sopenharmony_ci if (!is_valid_interval(skb, sch)) 4348c2ecf20Sopenharmony_ci return qdisc_drop(skb, sch, to_free); 4358c2ecf20Sopenharmony_ci } else if (TXTIME_ASSIST_IS_ENABLED(q->flags)) { 4368c2ecf20Sopenharmony_ci skb->tstamp = get_packet_txtime(skb, sch); 4378c2ecf20Sopenharmony_ci if (!skb->tstamp) 4388c2ecf20Sopenharmony_ci return qdisc_drop(skb, sch, to_free); 4398c2ecf20Sopenharmony_ci } 4408c2ecf20Sopenharmony_ci 4418c2ecf20Sopenharmony_ci qdisc_qstats_backlog_inc(sch, skb); 4428c2ecf20Sopenharmony_ci sch->q.qlen++; 4438c2ecf20Sopenharmony_ci 4448c2ecf20Sopenharmony_ci return qdisc_enqueue(skb, child, to_free); 4458c2ecf20Sopenharmony_ci} 4468c2ecf20Sopenharmony_ci 4478c2ecf20Sopenharmony_cistatic struct sk_buff *taprio_peek_soft(struct Qdisc *sch) 4488c2ecf20Sopenharmony_ci{ 4498c2ecf20Sopenharmony_ci struct taprio_sched *q = qdisc_priv(sch); 4508c2ecf20Sopenharmony_ci struct net_device *dev = qdisc_dev(sch); 4518c2ecf20Sopenharmony_ci struct sched_entry *entry; 4528c2ecf20Sopenharmony_ci struct sk_buff *skb; 4538c2ecf20Sopenharmony_ci u32 gate_mask; 4548c2ecf20Sopenharmony_ci int i; 4558c2ecf20Sopenharmony_ci 4568c2ecf20Sopenharmony_ci rcu_read_lock(); 4578c2ecf20Sopenharmony_ci entry = rcu_dereference(q->current_entry); 4588c2ecf20Sopenharmony_ci gate_mask = entry ? entry->gate_mask : TAPRIO_ALL_GATES_OPEN; 4598c2ecf20Sopenharmony_ci rcu_read_unlock(); 4608c2ecf20Sopenharmony_ci 4618c2ecf20Sopenharmony_ci if (!gate_mask) 4628c2ecf20Sopenharmony_ci return NULL; 4638c2ecf20Sopenharmony_ci 4648c2ecf20Sopenharmony_ci for (i = 0; i < dev->num_tx_queues; i++) { 4658c2ecf20Sopenharmony_ci struct Qdisc *child = q->qdiscs[i]; 4668c2ecf20Sopenharmony_ci int prio; 4678c2ecf20Sopenharmony_ci u8 tc; 4688c2ecf20Sopenharmony_ci 4698c2ecf20Sopenharmony_ci if (unlikely(!child)) 4708c2ecf20Sopenharmony_ci continue; 4718c2ecf20Sopenharmony_ci 4728c2ecf20Sopenharmony_ci skb = child->ops->peek(child); 4738c2ecf20Sopenharmony_ci if (!skb) 4748c2ecf20Sopenharmony_ci continue; 4758c2ecf20Sopenharmony_ci 4768c2ecf20Sopenharmony_ci if (TXTIME_ASSIST_IS_ENABLED(q->flags)) 4778c2ecf20Sopenharmony_ci return skb; 4788c2ecf20Sopenharmony_ci 4798c2ecf20Sopenharmony_ci prio = skb->priority; 4808c2ecf20Sopenharmony_ci tc = netdev_get_prio_tc_map(dev, prio); 4818c2ecf20Sopenharmony_ci 4828c2ecf20Sopenharmony_ci if (!(gate_mask & BIT(tc))) 4838c2ecf20Sopenharmony_ci continue; 4848c2ecf20Sopenharmony_ci 4858c2ecf20Sopenharmony_ci return skb; 4868c2ecf20Sopenharmony_ci } 4878c2ecf20Sopenharmony_ci 4888c2ecf20Sopenharmony_ci return NULL; 4898c2ecf20Sopenharmony_ci} 4908c2ecf20Sopenharmony_ci 4918c2ecf20Sopenharmony_cistatic struct sk_buff *taprio_peek_offload(struct Qdisc *sch) 4928c2ecf20Sopenharmony_ci{ 4938c2ecf20Sopenharmony_ci struct taprio_sched *q = qdisc_priv(sch); 4948c2ecf20Sopenharmony_ci struct net_device *dev = qdisc_dev(sch); 4958c2ecf20Sopenharmony_ci struct sk_buff *skb; 4968c2ecf20Sopenharmony_ci int i; 4978c2ecf20Sopenharmony_ci 4988c2ecf20Sopenharmony_ci for (i = 0; i < dev->num_tx_queues; i++) { 4998c2ecf20Sopenharmony_ci struct Qdisc *child = q->qdiscs[i]; 5008c2ecf20Sopenharmony_ci 5018c2ecf20Sopenharmony_ci if (unlikely(!child)) 5028c2ecf20Sopenharmony_ci continue; 5038c2ecf20Sopenharmony_ci 5048c2ecf20Sopenharmony_ci skb = child->ops->peek(child); 5058c2ecf20Sopenharmony_ci if (!skb) 5068c2ecf20Sopenharmony_ci continue; 5078c2ecf20Sopenharmony_ci 5088c2ecf20Sopenharmony_ci return skb; 5098c2ecf20Sopenharmony_ci } 5108c2ecf20Sopenharmony_ci 5118c2ecf20Sopenharmony_ci return NULL; 5128c2ecf20Sopenharmony_ci} 5138c2ecf20Sopenharmony_ci 5148c2ecf20Sopenharmony_cistatic struct sk_buff *taprio_peek(struct Qdisc *sch) 5158c2ecf20Sopenharmony_ci{ 5168c2ecf20Sopenharmony_ci struct taprio_sched *q = qdisc_priv(sch); 5178c2ecf20Sopenharmony_ci 5188c2ecf20Sopenharmony_ci return q->peek(sch); 5198c2ecf20Sopenharmony_ci} 5208c2ecf20Sopenharmony_ci 5218c2ecf20Sopenharmony_cistatic void taprio_set_budget(struct taprio_sched *q, struct sched_entry *entry) 5228c2ecf20Sopenharmony_ci{ 5238c2ecf20Sopenharmony_ci atomic_set(&entry->budget, 5248c2ecf20Sopenharmony_ci div64_u64((u64)entry->interval * 1000, 5258c2ecf20Sopenharmony_ci atomic64_read(&q->picos_per_byte))); 5268c2ecf20Sopenharmony_ci} 5278c2ecf20Sopenharmony_ci 5288c2ecf20Sopenharmony_cistatic struct sk_buff *taprio_dequeue_soft(struct Qdisc *sch) 5298c2ecf20Sopenharmony_ci{ 5308c2ecf20Sopenharmony_ci struct taprio_sched *q = qdisc_priv(sch); 5318c2ecf20Sopenharmony_ci struct net_device *dev = qdisc_dev(sch); 5328c2ecf20Sopenharmony_ci struct sk_buff *skb = NULL; 5338c2ecf20Sopenharmony_ci struct sched_entry *entry; 5348c2ecf20Sopenharmony_ci u32 gate_mask; 5358c2ecf20Sopenharmony_ci int i; 5368c2ecf20Sopenharmony_ci 5378c2ecf20Sopenharmony_ci rcu_read_lock(); 5388c2ecf20Sopenharmony_ci entry = rcu_dereference(q->current_entry); 5398c2ecf20Sopenharmony_ci /* if there's no entry, it means that the schedule didn't 5408c2ecf20Sopenharmony_ci * start yet, so force all gates to be open, this is in 5418c2ecf20Sopenharmony_ci * accordance to IEEE 802.1Qbv-2015 Section 8.6.9.4.5 5428c2ecf20Sopenharmony_ci * "AdminGateSates" 5438c2ecf20Sopenharmony_ci */ 5448c2ecf20Sopenharmony_ci gate_mask = entry ? entry->gate_mask : TAPRIO_ALL_GATES_OPEN; 5458c2ecf20Sopenharmony_ci 5468c2ecf20Sopenharmony_ci if (!gate_mask) 5478c2ecf20Sopenharmony_ci goto done; 5488c2ecf20Sopenharmony_ci 5498c2ecf20Sopenharmony_ci for (i = 0; i < dev->num_tx_queues; i++) { 5508c2ecf20Sopenharmony_ci struct Qdisc *child = q->qdiscs[i]; 5518c2ecf20Sopenharmony_ci ktime_t guard; 5528c2ecf20Sopenharmony_ci int prio; 5538c2ecf20Sopenharmony_ci int len; 5548c2ecf20Sopenharmony_ci u8 tc; 5558c2ecf20Sopenharmony_ci 5568c2ecf20Sopenharmony_ci if (unlikely(!child)) 5578c2ecf20Sopenharmony_ci continue; 5588c2ecf20Sopenharmony_ci 5598c2ecf20Sopenharmony_ci if (TXTIME_ASSIST_IS_ENABLED(q->flags)) { 5608c2ecf20Sopenharmony_ci skb = child->ops->dequeue(child); 5618c2ecf20Sopenharmony_ci if (!skb) 5628c2ecf20Sopenharmony_ci continue; 5638c2ecf20Sopenharmony_ci goto skb_found; 5648c2ecf20Sopenharmony_ci } 5658c2ecf20Sopenharmony_ci 5668c2ecf20Sopenharmony_ci skb = child->ops->peek(child); 5678c2ecf20Sopenharmony_ci if (!skb) 5688c2ecf20Sopenharmony_ci continue; 5698c2ecf20Sopenharmony_ci 5708c2ecf20Sopenharmony_ci prio = skb->priority; 5718c2ecf20Sopenharmony_ci tc = netdev_get_prio_tc_map(dev, prio); 5728c2ecf20Sopenharmony_ci 5738c2ecf20Sopenharmony_ci if (!(gate_mask & BIT(tc))) { 5748c2ecf20Sopenharmony_ci skb = NULL; 5758c2ecf20Sopenharmony_ci continue; 5768c2ecf20Sopenharmony_ci } 5778c2ecf20Sopenharmony_ci 5788c2ecf20Sopenharmony_ci len = qdisc_pkt_len(skb); 5798c2ecf20Sopenharmony_ci guard = ktime_add_ns(taprio_get_time(q), 5808c2ecf20Sopenharmony_ci length_to_duration(q, len)); 5818c2ecf20Sopenharmony_ci 5828c2ecf20Sopenharmony_ci /* In the case that there's no gate entry, there's no 5838c2ecf20Sopenharmony_ci * guard band ... 5848c2ecf20Sopenharmony_ci */ 5858c2ecf20Sopenharmony_ci if (gate_mask != TAPRIO_ALL_GATES_OPEN && 5868c2ecf20Sopenharmony_ci ktime_after(guard, entry->close_time)) { 5878c2ecf20Sopenharmony_ci skb = NULL; 5888c2ecf20Sopenharmony_ci continue; 5898c2ecf20Sopenharmony_ci } 5908c2ecf20Sopenharmony_ci 5918c2ecf20Sopenharmony_ci /* ... and no budget. */ 5928c2ecf20Sopenharmony_ci if (gate_mask != TAPRIO_ALL_GATES_OPEN && 5938c2ecf20Sopenharmony_ci atomic_sub_return(len, &entry->budget) < 0) { 5948c2ecf20Sopenharmony_ci skb = NULL; 5958c2ecf20Sopenharmony_ci continue; 5968c2ecf20Sopenharmony_ci } 5978c2ecf20Sopenharmony_ci 5988c2ecf20Sopenharmony_ci skb = child->ops->dequeue(child); 5998c2ecf20Sopenharmony_ci if (unlikely(!skb)) 6008c2ecf20Sopenharmony_ci goto done; 6018c2ecf20Sopenharmony_ci 6028c2ecf20Sopenharmony_ciskb_found: 6038c2ecf20Sopenharmony_ci qdisc_bstats_update(sch, skb); 6048c2ecf20Sopenharmony_ci qdisc_qstats_backlog_dec(sch, skb); 6058c2ecf20Sopenharmony_ci sch->q.qlen--; 6068c2ecf20Sopenharmony_ci 6078c2ecf20Sopenharmony_ci goto done; 6088c2ecf20Sopenharmony_ci } 6098c2ecf20Sopenharmony_ci 6108c2ecf20Sopenharmony_cidone: 6118c2ecf20Sopenharmony_ci rcu_read_unlock(); 6128c2ecf20Sopenharmony_ci 6138c2ecf20Sopenharmony_ci return skb; 6148c2ecf20Sopenharmony_ci} 6158c2ecf20Sopenharmony_ci 6168c2ecf20Sopenharmony_cistatic struct sk_buff *taprio_dequeue_offload(struct Qdisc *sch) 6178c2ecf20Sopenharmony_ci{ 6188c2ecf20Sopenharmony_ci struct taprio_sched *q = qdisc_priv(sch); 6198c2ecf20Sopenharmony_ci struct net_device *dev = qdisc_dev(sch); 6208c2ecf20Sopenharmony_ci struct sk_buff *skb; 6218c2ecf20Sopenharmony_ci int i; 6228c2ecf20Sopenharmony_ci 6238c2ecf20Sopenharmony_ci for (i = 0; i < dev->num_tx_queues; i++) { 6248c2ecf20Sopenharmony_ci struct Qdisc *child = q->qdiscs[i]; 6258c2ecf20Sopenharmony_ci 6268c2ecf20Sopenharmony_ci if (unlikely(!child)) 6278c2ecf20Sopenharmony_ci continue; 6288c2ecf20Sopenharmony_ci 6298c2ecf20Sopenharmony_ci skb = child->ops->dequeue(child); 6308c2ecf20Sopenharmony_ci if (unlikely(!skb)) 6318c2ecf20Sopenharmony_ci continue; 6328c2ecf20Sopenharmony_ci 6338c2ecf20Sopenharmony_ci qdisc_bstats_update(sch, skb); 6348c2ecf20Sopenharmony_ci qdisc_qstats_backlog_dec(sch, skb); 6358c2ecf20Sopenharmony_ci sch->q.qlen--; 6368c2ecf20Sopenharmony_ci 6378c2ecf20Sopenharmony_ci return skb; 6388c2ecf20Sopenharmony_ci } 6398c2ecf20Sopenharmony_ci 6408c2ecf20Sopenharmony_ci return NULL; 6418c2ecf20Sopenharmony_ci} 6428c2ecf20Sopenharmony_ci 6438c2ecf20Sopenharmony_cistatic struct sk_buff *taprio_dequeue(struct Qdisc *sch) 6448c2ecf20Sopenharmony_ci{ 6458c2ecf20Sopenharmony_ci struct taprio_sched *q = qdisc_priv(sch); 6468c2ecf20Sopenharmony_ci 6478c2ecf20Sopenharmony_ci return q->dequeue(sch); 6488c2ecf20Sopenharmony_ci} 6498c2ecf20Sopenharmony_ci 6508c2ecf20Sopenharmony_cistatic bool should_restart_cycle(const struct sched_gate_list *oper, 6518c2ecf20Sopenharmony_ci const struct sched_entry *entry) 6528c2ecf20Sopenharmony_ci{ 6538c2ecf20Sopenharmony_ci if (list_is_last(&entry->list, &oper->entries)) 6548c2ecf20Sopenharmony_ci return true; 6558c2ecf20Sopenharmony_ci 6568c2ecf20Sopenharmony_ci if (ktime_compare(entry->close_time, oper->cycle_close_time) == 0) 6578c2ecf20Sopenharmony_ci return true; 6588c2ecf20Sopenharmony_ci 6598c2ecf20Sopenharmony_ci return false; 6608c2ecf20Sopenharmony_ci} 6618c2ecf20Sopenharmony_ci 6628c2ecf20Sopenharmony_cistatic bool should_change_schedules(const struct sched_gate_list *admin, 6638c2ecf20Sopenharmony_ci const struct sched_gate_list *oper, 6648c2ecf20Sopenharmony_ci ktime_t close_time) 6658c2ecf20Sopenharmony_ci{ 6668c2ecf20Sopenharmony_ci ktime_t next_base_time, extension_time; 6678c2ecf20Sopenharmony_ci 6688c2ecf20Sopenharmony_ci if (!admin) 6698c2ecf20Sopenharmony_ci return false; 6708c2ecf20Sopenharmony_ci 6718c2ecf20Sopenharmony_ci next_base_time = sched_base_time(admin); 6728c2ecf20Sopenharmony_ci 6738c2ecf20Sopenharmony_ci /* This is the simple case, the close_time would fall after 6748c2ecf20Sopenharmony_ci * the next schedule base_time. 6758c2ecf20Sopenharmony_ci */ 6768c2ecf20Sopenharmony_ci if (ktime_compare(next_base_time, close_time) <= 0) 6778c2ecf20Sopenharmony_ci return true; 6788c2ecf20Sopenharmony_ci 6798c2ecf20Sopenharmony_ci /* This is the cycle_time_extension case, if the close_time 6808c2ecf20Sopenharmony_ci * plus the amount that can be extended would fall after the 6818c2ecf20Sopenharmony_ci * next schedule base_time, we can extend the current schedule 6828c2ecf20Sopenharmony_ci * for that amount. 6838c2ecf20Sopenharmony_ci */ 6848c2ecf20Sopenharmony_ci extension_time = ktime_add_ns(close_time, oper->cycle_time_extension); 6858c2ecf20Sopenharmony_ci 6868c2ecf20Sopenharmony_ci /* FIXME: the IEEE 802.1Q-2018 Specification isn't clear about 6878c2ecf20Sopenharmony_ci * how precisely the extension should be made. So after 6888c2ecf20Sopenharmony_ci * conformance testing, this logic may change. 6898c2ecf20Sopenharmony_ci */ 6908c2ecf20Sopenharmony_ci if (ktime_compare(next_base_time, extension_time) <= 0) 6918c2ecf20Sopenharmony_ci return true; 6928c2ecf20Sopenharmony_ci 6938c2ecf20Sopenharmony_ci return false; 6948c2ecf20Sopenharmony_ci} 6958c2ecf20Sopenharmony_ci 6968c2ecf20Sopenharmony_cistatic enum hrtimer_restart advance_sched(struct hrtimer *timer) 6978c2ecf20Sopenharmony_ci{ 6988c2ecf20Sopenharmony_ci struct taprio_sched *q = container_of(timer, struct taprio_sched, 6998c2ecf20Sopenharmony_ci advance_timer); 7008c2ecf20Sopenharmony_ci struct sched_gate_list *oper, *admin; 7018c2ecf20Sopenharmony_ci struct sched_entry *entry, *next; 7028c2ecf20Sopenharmony_ci struct Qdisc *sch = q->root; 7038c2ecf20Sopenharmony_ci ktime_t close_time; 7048c2ecf20Sopenharmony_ci 7058c2ecf20Sopenharmony_ci spin_lock(&q->current_entry_lock); 7068c2ecf20Sopenharmony_ci entry = rcu_dereference_protected(q->current_entry, 7078c2ecf20Sopenharmony_ci lockdep_is_held(&q->current_entry_lock)); 7088c2ecf20Sopenharmony_ci oper = rcu_dereference_protected(q->oper_sched, 7098c2ecf20Sopenharmony_ci lockdep_is_held(&q->current_entry_lock)); 7108c2ecf20Sopenharmony_ci admin = rcu_dereference_protected(q->admin_sched, 7118c2ecf20Sopenharmony_ci lockdep_is_held(&q->current_entry_lock)); 7128c2ecf20Sopenharmony_ci 7138c2ecf20Sopenharmony_ci if (!oper) 7148c2ecf20Sopenharmony_ci switch_schedules(q, &admin, &oper); 7158c2ecf20Sopenharmony_ci 7168c2ecf20Sopenharmony_ci /* This can happen in two cases: 1. this is the very first run 7178c2ecf20Sopenharmony_ci * of this function (i.e. we weren't running any schedule 7188c2ecf20Sopenharmony_ci * previously); 2. The previous schedule just ended. The first 7198c2ecf20Sopenharmony_ci * entry of all schedules are pre-calculated during the 7208c2ecf20Sopenharmony_ci * schedule initialization. 7218c2ecf20Sopenharmony_ci */ 7228c2ecf20Sopenharmony_ci if (unlikely(!entry || entry->close_time == oper->base_time)) { 7238c2ecf20Sopenharmony_ci next = list_first_entry(&oper->entries, struct sched_entry, 7248c2ecf20Sopenharmony_ci list); 7258c2ecf20Sopenharmony_ci close_time = next->close_time; 7268c2ecf20Sopenharmony_ci goto first_run; 7278c2ecf20Sopenharmony_ci } 7288c2ecf20Sopenharmony_ci 7298c2ecf20Sopenharmony_ci if (should_restart_cycle(oper, entry)) { 7308c2ecf20Sopenharmony_ci next = list_first_entry(&oper->entries, struct sched_entry, 7318c2ecf20Sopenharmony_ci list); 7328c2ecf20Sopenharmony_ci oper->cycle_close_time = ktime_add_ns(oper->cycle_close_time, 7338c2ecf20Sopenharmony_ci oper->cycle_time); 7348c2ecf20Sopenharmony_ci } else { 7358c2ecf20Sopenharmony_ci next = list_next_entry(entry, list); 7368c2ecf20Sopenharmony_ci } 7378c2ecf20Sopenharmony_ci 7388c2ecf20Sopenharmony_ci close_time = ktime_add_ns(entry->close_time, next->interval); 7398c2ecf20Sopenharmony_ci close_time = min_t(ktime_t, close_time, oper->cycle_close_time); 7408c2ecf20Sopenharmony_ci 7418c2ecf20Sopenharmony_ci if (should_change_schedules(admin, oper, close_time)) { 7428c2ecf20Sopenharmony_ci /* Set things so the next time this runs, the new 7438c2ecf20Sopenharmony_ci * schedule runs. 7448c2ecf20Sopenharmony_ci */ 7458c2ecf20Sopenharmony_ci close_time = sched_base_time(admin); 7468c2ecf20Sopenharmony_ci switch_schedules(q, &admin, &oper); 7478c2ecf20Sopenharmony_ci } 7488c2ecf20Sopenharmony_ci 7498c2ecf20Sopenharmony_ci next->close_time = close_time; 7508c2ecf20Sopenharmony_ci taprio_set_budget(q, next); 7518c2ecf20Sopenharmony_ci 7528c2ecf20Sopenharmony_cifirst_run: 7538c2ecf20Sopenharmony_ci rcu_assign_pointer(q->current_entry, next); 7548c2ecf20Sopenharmony_ci spin_unlock(&q->current_entry_lock); 7558c2ecf20Sopenharmony_ci 7568c2ecf20Sopenharmony_ci hrtimer_set_expires(&q->advance_timer, close_time); 7578c2ecf20Sopenharmony_ci 7588c2ecf20Sopenharmony_ci rcu_read_lock(); 7598c2ecf20Sopenharmony_ci __netif_schedule(sch); 7608c2ecf20Sopenharmony_ci rcu_read_unlock(); 7618c2ecf20Sopenharmony_ci 7628c2ecf20Sopenharmony_ci return HRTIMER_RESTART; 7638c2ecf20Sopenharmony_ci} 7648c2ecf20Sopenharmony_ci 7658c2ecf20Sopenharmony_cistatic const struct nla_policy entry_policy[TCA_TAPRIO_SCHED_ENTRY_MAX + 1] = { 7668c2ecf20Sopenharmony_ci [TCA_TAPRIO_SCHED_ENTRY_INDEX] = { .type = NLA_U32 }, 7678c2ecf20Sopenharmony_ci [TCA_TAPRIO_SCHED_ENTRY_CMD] = { .type = NLA_U8 }, 7688c2ecf20Sopenharmony_ci [TCA_TAPRIO_SCHED_ENTRY_GATE_MASK] = { .type = NLA_U32 }, 7698c2ecf20Sopenharmony_ci [TCA_TAPRIO_SCHED_ENTRY_INTERVAL] = { .type = NLA_U32 }, 7708c2ecf20Sopenharmony_ci}; 7718c2ecf20Sopenharmony_ci 7728c2ecf20Sopenharmony_cistatic const struct nla_policy taprio_policy[TCA_TAPRIO_ATTR_MAX + 1] = { 7738c2ecf20Sopenharmony_ci [TCA_TAPRIO_ATTR_PRIOMAP] = { 7748c2ecf20Sopenharmony_ci .len = sizeof(struct tc_mqprio_qopt) 7758c2ecf20Sopenharmony_ci }, 7768c2ecf20Sopenharmony_ci [TCA_TAPRIO_ATTR_SCHED_ENTRY_LIST] = { .type = NLA_NESTED }, 7778c2ecf20Sopenharmony_ci [TCA_TAPRIO_ATTR_SCHED_BASE_TIME] = { .type = NLA_S64 }, 7788c2ecf20Sopenharmony_ci [TCA_TAPRIO_ATTR_SCHED_SINGLE_ENTRY] = { .type = NLA_NESTED }, 7798c2ecf20Sopenharmony_ci [TCA_TAPRIO_ATTR_SCHED_CLOCKID] = { .type = NLA_S32 }, 7808c2ecf20Sopenharmony_ci [TCA_TAPRIO_ATTR_SCHED_CYCLE_TIME] = { .type = NLA_S64 }, 7818c2ecf20Sopenharmony_ci [TCA_TAPRIO_ATTR_SCHED_CYCLE_TIME_EXTENSION] = { .type = NLA_S64 }, 7828c2ecf20Sopenharmony_ci [TCA_TAPRIO_ATTR_FLAGS] = { .type = NLA_U32 }, 7838c2ecf20Sopenharmony_ci [TCA_TAPRIO_ATTR_TXTIME_DELAY] = { .type = NLA_U32 }, 7848c2ecf20Sopenharmony_ci}; 7858c2ecf20Sopenharmony_ci 7868c2ecf20Sopenharmony_cistatic int fill_sched_entry(struct taprio_sched *q, struct nlattr **tb, 7878c2ecf20Sopenharmony_ci struct sched_entry *entry, 7888c2ecf20Sopenharmony_ci struct netlink_ext_ack *extack) 7898c2ecf20Sopenharmony_ci{ 7908c2ecf20Sopenharmony_ci int min_duration = length_to_duration(q, ETH_ZLEN); 7918c2ecf20Sopenharmony_ci u32 interval = 0; 7928c2ecf20Sopenharmony_ci 7938c2ecf20Sopenharmony_ci if (tb[TCA_TAPRIO_SCHED_ENTRY_CMD]) 7948c2ecf20Sopenharmony_ci entry->command = nla_get_u8( 7958c2ecf20Sopenharmony_ci tb[TCA_TAPRIO_SCHED_ENTRY_CMD]); 7968c2ecf20Sopenharmony_ci 7978c2ecf20Sopenharmony_ci if (tb[TCA_TAPRIO_SCHED_ENTRY_GATE_MASK]) 7988c2ecf20Sopenharmony_ci entry->gate_mask = nla_get_u32( 7998c2ecf20Sopenharmony_ci tb[TCA_TAPRIO_SCHED_ENTRY_GATE_MASK]); 8008c2ecf20Sopenharmony_ci 8018c2ecf20Sopenharmony_ci if (tb[TCA_TAPRIO_SCHED_ENTRY_INTERVAL]) 8028c2ecf20Sopenharmony_ci interval = nla_get_u32( 8038c2ecf20Sopenharmony_ci tb[TCA_TAPRIO_SCHED_ENTRY_INTERVAL]); 8048c2ecf20Sopenharmony_ci 8058c2ecf20Sopenharmony_ci /* The interval should allow at least the minimum ethernet 8068c2ecf20Sopenharmony_ci * frame to go out. 8078c2ecf20Sopenharmony_ci */ 8088c2ecf20Sopenharmony_ci if (interval < min_duration) { 8098c2ecf20Sopenharmony_ci NL_SET_ERR_MSG(extack, "Invalid interval for schedule entry"); 8108c2ecf20Sopenharmony_ci return -EINVAL; 8118c2ecf20Sopenharmony_ci } 8128c2ecf20Sopenharmony_ci 8138c2ecf20Sopenharmony_ci entry->interval = interval; 8148c2ecf20Sopenharmony_ci 8158c2ecf20Sopenharmony_ci return 0; 8168c2ecf20Sopenharmony_ci} 8178c2ecf20Sopenharmony_ci 8188c2ecf20Sopenharmony_cistatic int parse_sched_entry(struct taprio_sched *q, struct nlattr *n, 8198c2ecf20Sopenharmony_ci struct sched_entry *entry, int index, 8208c2ecf20Sopenharmony_ci struct netlink_ext_ack *extack) 8218c2ecf20Sopenharmony_ci{ 8228c2ecf20Sopenharmony_ci struct nlattr *tb[TCA_TAPRIO_SCHED_ENTRY_MAX + 1] = { }; 8238c2ecf20Sopenharmony_ci int err; 8248c2ecf20Sopenharmony_ci 8258c2ecf20Sopenharmony_ci err = nla_parse_nested_deprecated(tb, TCA_TAPRIO_SCHED_ENTRY_MAX, n, 8268c2ecf20Sopenharmony_ci entry_policy, NULL); 8278c2ecf20Sopenharmony_ci if (err < 0) { 8288c2ecf20Sopenharmony_ci NL_SET_ERR_MSG(extack, "Could not parse nested entry"); 8298c2ecf20Sopenharmony_ci return -EINVAL; 8308c2ecf20Sopenharmony_ci } 8318c2ecf20Sopenharmony_ci 8328c2ecf20Sopenharmony_ci entry->index = index; 8338c2ecf20Sopenharmony_ci 8348c2ecf20Sopenharmony_ci return fill_sched_entry(q, tb, entry, extack); 8358c2ecf20Sopenharmony_ci} 8368c2ecf20Sopenharmony_ci 8378c2ecf20Sopenharmony_cistatic int parse_sched_list(struct taprio_sched *q, struct nlattr *list, 8388c2ecf20Sopenharmony_ci struct sched_gate_list *sched, 8398c2ecf20Sopenharmony_ci struct netlink_ext_ack *extack) 8408c2ecf20Sopenharmony_ci{ 8418c2ecf20Sopenharmony_ci struct nlattr *n; 8428c2ecf20Sopenharmony_ci int err, rem; 8438c2ecf20Sopenharmony_ci int i = 0; 8448c2ecf20Sopenharmony_ci 8458c2ecf20Sopenharmony_ci if (!list) 8468c2ecf20Sopenharmony_ci return -EINVAL; 8478c2ecf20Sopenharmony_ci 8488c2ecf20Sopenharmony_ci nla_for_each_nested(n, list, rem) { 8498c2ecf20Sopenharmony_ci struct sched_entry *entry; 8508c2ecf20Sopenharmony_ci 8518c2ecf20Sopenharmony_ci if (nla_type(n) != TCA_TAPRIO_SCHED_ENTRY) { 8528c2ecf20Sopenharmony_ci NL_SET_ERR_MSG(extack, "Attribute is not of type 'entry'"); 8538c2ecf20Sopenharmony_ci continue; 8548c2ecf20Sopenharmony_ci } 8558c2ecf20Sopenharmony_ci 8568c2ecf20Sopenharmony_ci entry = kzalloc(sizeof(*entry), GFP_KERNEL); 8578c2ecf20Sopenharmony_ci if (!entry) { 8588c2ecf20Sopenharmony_ci NL_SET_ERR_MSG(extack, "Not enough memory for entry"); 8598c2ecf20Sopenharmony_ci return -ENOMEM; 8608c2ecf20Sopenharmony_ci } 8618c2ecf20Sopenharmony_ci 8628c2ecf20Sopenharmony_ci err = parse_sched_entry(q, n, entry, i, extack); 8638c2ecf20Sopenharmony_ci if (err < 0) { 8648c2ecf20Sopenharmony_ci kfree(entry); 8658c2ecf20Sopenharmony_ci return err; 8668c2ecf20Sopenharmony_ci } 8678c2ecf20Sopenharmony_ci 8688c2ecf20Sopenharmony_ci list_add_tail(&entry->list, &sched->entries); 8698c2ecf20Sopenharmony_ci i++; 8708c2ecf20Sopenharmony_ci } 8718c2ecf20Sopenharmony_ci 8728c2ecf20Sopenharmony_ci sched->num_entries = i; 8738c2ecf20Sopenharmony_ci 8748c2ecf20Sopenharmony_ci return i; 8758c2ecf20Sopenharmony_ci} 8768c2ecf20Sopenharmony_ci 8778c2ecf20Sopenharmony_cistatic int parse_taprio_schedule(struct taprio_sched *q, struct nlattr **tb, 8788c2ecf20Sopenharmony_ci struct sched_gate_list *new, 8798c2ecf20Sopenharmony_ci struct netlink_ext_ack *extack) 8808c2ecf20Sopenharmony_ci{ 8818c2ecf20Sopenharmony_ci int err = 0; 8828c2ecf20Sopenharmony_ci 8838c2ecf20Sopenharmony_ci if (tb[TCA_TAPRIO_ATTR_SCHED_SINGLE_ENTRY]) { 8848c2ecf20Sopenharmony_ci NL_SET_ERR_MSG(extack, "Adding a single entry is not supported"); 8858c2ecf20Sopenharmony_ci return -ENOTSUPP; 8868c2ecf20Sopenharmony_ci } 8878c2ecf20Sopenharmony_ci 8888c2ecf20Sopenharmony_ci if (tb[TCA_TAPRIO_ATTR_SCHED_BASE_TIME]) 8898c2ecf20Sopenharmony_ci new->base_time = nla_get_s64(tb[TCA_TAPRIO_ATTR_SCHED_BASE_TIME]); 8908c2ecf20Sopenharmony_ci 8918c2ecf20Sopenharmony_ci if (tb[TCA_TAPRIO_ATTR_SCHED_CYCLE_TIME_EXTENSION]) 8928c2ecf20Sopenharmony_ci new->cycle_time_extension = nla_get_s64(tb[TCA_TAPRIO_ATTR_SCHED_CYCLE_TIME_EXTENSION]); 8938c2ecf20Sopenharmony_ci 8948c2ecf20Sopenharmony_ci if (tb[TCA_TAPRIO_ATTR_SCHED_CYCLE_TIME]) 8958c2ecf20Sopenharmony_ci new->cycle_time = nla_get_s64(tb[TCA_TAPRIO_ATTR_SCHED_CYCLE_TIME]); 8968c2ecf20Sopenharmony_ci 8978c2ecf20Sopenharmony_ci if (tb[TCA_TAPRIO_ATTR_SCHED_ENTRY_LIST]) 8988c2ecf20Sopenharmony_ci err = parse_sched_list(q, tb[TCA_TAPRIO_ATTR_SCHED_ENTRY_LIST], 8998c2ecf20Sopenharmony_ci new, extack); 9008c2ecf20Sopenharmony_ci if (err < 0) 9018c2ecf20Sopenharmony_ci return err; 9028c2ecf20Sopenharmony_ci 9038c2ecf20Sopenharmony_ci if (!new->cycle_time) { 9048c2ecf20Sopenharmony_ci struct sched_entry *entry; 9058c2ecf20Sopenharmony_ci ktime_t cycle = 0; 9068c2ecf20Sopenharmony_ci 9078c2ecf20Sopenharmony_ci list_for_each_entry(entry, &new->entries, list) 9088c2ecf20Sopenharmony_ci cycle = ktime_add_ns(cycle, entry->interval); 9098c2ecf20Sopenharmony_ci 9108c2ecf20Sopenharmony_ci if (!cycle) { 9118c2ecf20Sopenharmony_ci NL_SET_ERR_MSG(extack, "'cycle_time' can never be 0"); 9128c2ecf20Sopenharmony_ci return -EINVAL; 9138c2ecf20Sopenharmony_ci } 9148c2ecf20Sopenharmony_ci 9158c2ecf20Sopenharmony_ci new->cycle_time = cycle; 9168c2ecf20Sopenharmony_ci } 9178c2ecf20Sopenharmony_ci 9188c2ecf20Sopenharmony_ci return 0; 9198c2ecf20Sopenharmony_ci} 9208c2ecf20Sopenharmony_ci 9218c2ecf20Sopenharmony_cistatic int taprio_parse_mqprio_opt(struct net_device *dev, 9228c2ecf20Sopenharmony_ci struct tc_mqprio_qopt *qopt, 9238c2ecf20Sopenharmony_ci struct netlink_ext_ack *extack, 9248c2ecf20Sopenharmony_ci u32 taprio_flags) 9258c2ecf20Sopenharmony_ci{ 9268c2ecf20Sopenharmony_ci int i, j; 9278c2ecf20Sopenharmony_ci 9288c2ecf20Sopenharmony_ci if (!qopt && !dev->num_tc) { 9298c2ecf20Sopenharmony_ci NL_SET_ERR_MSG(extack, "'mqprio' configuration is necessary"); 9308c2ecf20Sopenharmony_ci return -EINVAL; 9318c2ecf20Sopenharmony_ci } 9328c2ecf20Sopenharmony_ci 9338c2ecf20Sopenharmony_ci /* If num_tc is already set, it means that the user already 9348c2ecf20Sopenharmony_ci * configured the mqprio part 9358c2ecf20Sopenharmony_ci */ 9368c2ecf20Sopenharmony_ci if (dev->num_tc) 9378c2ecf20Sopenharmony_ci return 0; 9388c2ecf20Sopenharmony_ci 9398c2ecf20Sopenharmony_ci /* Verify num_tc is not out of max range */ 9408c2ecf20Sopenharmony_ci if (qopt->num_tc > TC_MAX_QUEUE) { 9418c2ecf20Sopenharmony_ci NL_SET_ERR_MSG(extack, "Number of traffic classes is outside valid range"); 9428c2ecf20Sopenharmony_ci return -EINVAL; 9438c2ecf20Sopenharmony_ci } 9448c2ecf20Sopenharmony_ci 9458c2ecf20Sopenharmony_ci /* taprio imposes that traffic classes map 1:n to tx queues */ 9468c2ecf20Sopenharmony_ci if (qopt->num_tc > dev->num_tx_queues) { 9478c2ecf20Sopenharmony_ci NL_SET_ERR_MSG(extack, "Number of traffic classes is greater than number of HW queues"); 9488c2ecf20Sopenharmony_ci return -EINVAL; 9498c2ecf20Sopenharmony_ci } 9508c2ecf20Sopenharmony_ci 9518c2ecf20Sopenharmony_ci /* Verify priority mapping uses valid tcs */ 9528c2ecf20Sopenharmony_ci for (i = 0; i <= TC_BITMASK; i++) { 9538c2ecf20Sopenharmony_ci if (qopt->prio_tc_map[i] >= qopt->num_tc) { 9548c2ecf20Sopenharmony_ci NL_SET_ERR_MSG(extack, "Invalid traffic class in priority to traffic class mapping"); 9558c2ecf20Sopenharmony_ci return -EINVAL; 9568c2ecf20Sopenharmony_ci } 9578c2ecf20Sopenharmony_ci } 9588c2ecf20Sopenharmony_ci 9598c2ecf20Sopenharmony_ci for (i = 0; i < qopt->num_tc; i++) { 9608c2ecf20Sopenharmony_ci unsigned int last = qopt->offset[i] + qopt->count[i]; 9618c2ecf20Sopenharmony_ci 9628c2ecf20Sopenharmony_ci /* Verify the queue count is in tx range being equal to the 9638c2ecf20Sopenharmony_ci * real_num_tx_queues indicates the last queue is in use. 9648c2ecf20Sopenharmony_ci */ 9658c2ecf20Sopenharmony_ci if (qopt->offset[i] >= dev->num_tx_queues || 9668c2ecf20Sopenharmony_ci !qopt->count[i] || 9678c2ecf20Sopenharmony_ci last > dev->real_num_tx_queues) { 9688c2ecf20Sopenharmony_ci NL_SET_ERR_MSG(extack, "Invalid queue in traffic class to queue mapping"); 9698c2ecf20Sopenharmony_ci return -EINVAL; 9708c2ecf20Sopenharmony_ci } 9718c2ecf20Sopenharmony_ci 9728c2ecf20Sopenharmony_ci if (TXTIME_ASSIST_IS_ENABLED(taprio_flags)) 9738c2ecf20Sopenharmony_ci continue; 9748c2ecf20Sopenharmony_ci 9758c2ecf20Sopenharmony_ci /* Verify that the offset and counts do not overlap */ 9768c2ecf20Sopenharmony_ci for (j = i + 1; j < qopt->num_tc; j++) { 9778c2ecf20Sopenharmony_ci if (last > qopt->offset[j]) { 9788c2ecf20Sopenharmony_ci NL_SET_ERR_MSG(extack, "Detected overlap in the traffic class to queue mapping"); 9798c2ecf20Sopenharmony_ci return -EINVAL; 9808c2ecf20Sopenharmony_ci } 9818c2ecf20Sopenharmony_ci } 9828c2ecf20Sopenharmony_ci } 9838c2ecf20Sopenharmony_ci 9848c2ecf20Sopenharmony_ci return 0; 9858c2ecf20Sopenharmony_ci} 9868c2ecf20Sopenharmony_ci 9878c2ecf20Sopenharmony_cistatic int taprio_get_start_time(struct Qdisc *sch, 9888c2ecf20Sopenharmony_ci struct sched_gate_list *sched, 9898c2ecf20Sopenharmony_ci ktime_t *start) 9908c2ecf20Sopenharmony_ci{ 9918c2ecf20Sopenharmony_ci struct taprio_sched *q = qdisc_priv(sch); 9928c2ecf20Sopenharmony_ci ktime_t now, base, cycle; 9938c2ecf20Sopenharmony_ci s64 n; 9948c2ecf20Sopenharmony_ci 9958c2ecf20Sopenharmony_ci base = sched_base_time(sched); 9968c2ecf20Sopenharmony_ci now = taprio_get_time(q); 9978c2ecf20Sopenharmony_ci 9988c2ecf20Sopenharmony_ci if (ktime_after(base, now)) { 9998c2ecf20Sopenharmony_ci *start = base; 10008c2ecf20Sopenharmony_ci return 0; 10018c2ecf20Sopenharmony_ci } 10028c2ecf20Sopenharmony_ci 10038c2ecf20Sopenharmony_ci cycle = sched->cycle_time; 10048c2ecf20Sopenharmony_ci 10058c2ecf20Sopenharmony_ci /* The qdisc is expected to have at least one sched_entry. Moreover, 10068c2ecf20Sopenharmony_ci * any entry must have 'interval' > 0. Thus if the cycle time is zero, 10078c2ecf20Sopenharmony_ci * something went really wrong. In that case, we should warn about this 10088c2ecf20Sopenharmony_ci * inconsistent state and return error. 10098c2ecf20Sopenharmony_ci */ 10108c2ecf20Sopenharmony_ci if (WARN_ON(!cycle)) 10118c2ecf20Sopenharmony_ci return -EFAULT; 10128c2ecf20Sopenharmony_ci 10138c2ecf20Sopenharmony_ci /* Schedule the start time for the beginning of the next 10148c2ecf20Sopenharmony_ci * cycle. 10158c2ecf20Sopenharmony_ci */ 10168c2ecf20Sopenharmony_ci n = div64_s64(ktime_sub_ns(now, base), cycle); 10178c2ecf20Sopenharmony_ci *start = ktime_add_ns(base, (n + 1) * cycle); 10188c2ecf20Sopenharmony_ci return 0; 10198c2ecf20Sopenharmony_ci} 10208c2ecf20Sopenharmony_ci 10218c2ecf20Sopenharmony_cistatic void setup_first_close_time(struct taprio_sched *q, 10228c2ecf20Sopenharmony_ci struct sched_gate_list *sched, ktime_t base) 10238c2ecf20Sopenharmony_ci{ 10248c2ecf20Sopenharmony_ci struct sched_entry *first; 10258c2ecf20Sopenharmony_ci ktime_t cycle; 10268c2ecf20Sopenharmony_ci 10278c2ecf20Sopenharmony_ci first = list_first_entry(&sched->entries, 10288c2ecf20Sopenharmony_ci struct sched_entry, list); 10298c2ecf20Sopenharmony_ci 10308c2ecf20Sopenharmony_ci cycle = sched->cycle_time; 10318c2ecf20Sopenharmony_ci 10328c2ecf20Sopenharmony_ci /* FIXME: find a better place to do this */ 10338c2ecf20Sopenharmony_ci sched->cycle_close_time = ktime_add_ns(base, cycle); 10348c2ecf20Sopenharmony_ci 10358c2ecf20Sopenharmony_ci first->close_time = ktime_add_ns(base, first->interval); 10368c2ecf20Sopenharmony_ci taprio_set_budget(q, first); 10378c2ecf20Sopenharmony_ci rcu_assign_pointer(q->current_entry, NULL); 10388c2ecf20Sopenharmony_ci} 10398c2ecf20Sopenharmony_ci 10408c2ecf20Sopenharmony_cistatic void taprio_start_sched(struct Qdisc *sch, 10418c2ecf20Sopenharmony_ci ktime_t start, struct sched_gate_list *new) 10428c2ecf20Sopenharmony_ci{ 10438c2ecf20Sopenharmony_ci struct taprio_sched *q = qdisc_priv(sch); 10448c2ecf20Sopenharmony_ci ktime_t expires; 10458c2ecf20Sopenharmony_ci 10468c2ecf20Sopenharmony_ci if (FULL_OFFLOAD_IS_ENABLED(q->flags)) 10478c2ecf20Sopenharmony_ci return; 10488c2ecf20Sopenharmony_ci 10498c2ecf20Sopenharmony_ci expires = hrtimer_get_expires(&q->advance_timer); 10508c2ecf20Sopenharmony_ci if (expires == 0) 10518c2ecf20Sopenharmony_ci expires = KTIME_MAX; 10528c2ecf20Sopenharmony_ci 10538c2ecf20Sopenharmony_ci /* If the new schedule starts before the next expiration, we 10548c2ecf20Sopenharmony_ci * reprogram it to the earliest one, so we change the admin 10558c2ecf20Sopenharmony_ci * schedule to the operational one at the right time. 10568c2ecf20Sopenharmony_ci */ 10578c2ecf20Sopenharmony_ci start = min_t(ktime_t, start, expires); 10588c2ecf20Sopenharmony_ci 10598c2ecf20Sopenharmony_ci hrtimer_start(&q->advance_timer, start, HRTIMER_MODE_ABS); 10608c2ecf20Sopenharmony_ci} 10618c2ecf20Sopenharmony_ci 10628c2ecf20Sopenharmony_cistatic void taprio_set_picos_per_byte(struct net_device *dev, 10638c2ecf20Sopenharmony_ci struct taprio_sched *q) 10648c2ecf20Sopenharmony_ci{ 10658c2ecf20Sopenharmony_ci struct ethtool_link_ksettings ecmd; 10668c2ecf20Sopenharmony_ci int speed = SPEED_10; 10678c2ecf20Sopenharmony_ci int picos_per_byte; 10688c2ecf20Sopenharmony_ci int err; 10698c2ecf20Sopenharmony_ci 10708c2ecf20Sopenharmony_ci err = __ethtool_get_link_ksettings(dev, &ecmd); 10718c2ecf20Sopenharmony_ci if (err < 0) 10728c2ecf20Sopenharmony_ci goto skip; 10738c2ecf20Sopenharmony_ci 10748c2ecf20Sopenharmony_ci if (ecmd.base.speed && ecmd.base.speed != SPEED_UNKNOWN) 10758c2ecf20Sopenharmony_ci speed = ecmd.base.speed; 10768c2ecf20Sopenharmony_ci 10778c2ecf20Sopenharmony_ciskip: 10788c2ecf20Sopenharmony_ci picos_per_byte = (USEC_PER_SEC * 8) / speed; 10798c2ecf20Sopenharmony_ci 10808c2ecf20Sopenharmony_ci atomic64_set(&q->picos_per_byte, picos_per_byte); 10818c2ecf20Sopenharmony_ci netdev_dbg(dev, "taprio: set %s's picos_per_byte to: %lld, linkspeed: %d\n", 10828c2ecf20Sopenharmony_ci dev->name, (long long)atomic64_read(&q->picos_per_byte), 10838c2ecf20Sopenharmony_ci ecmd.base.speed); 10848c2ecf20Sopenharmony_ci} 10858c2ecf20Sopenharmony_ci 10868c2ecf20Sopenharmony_cistatic int taprio_dev_notifier(struct notifier_block *nb, unsigned long event, 10878c2ecf20Sopenharmony_ci void *ptr) 10888c2ecf20Sopenharmony_ci{ 10898c2ecf20Sopenharmony_ci struct net_device *dev = netdev_notifier_info_to_dev(ptr); 10908c2ecf20Sopenharmony_ci struct net_device *qdev; 10918c2ecf20Sopenharmony_ci struct taprio_sched *q; 10928c2ecf20Sopenharmony_ci bool found = false; 10938c2ecf20Sopenharmony_ci 10948c2ecf20Sopenharmony_ci ASSERT_RTNL(); 10958c2ecf20Sopenharmony_ci 10968c2ecf20Sopenharmony_ci if (event != NETDEV_UP && event != NETDEV_CHANGE) 10978c2ecf20Sopenharmony_ci return NOTIFY_DONE; 10988c2ecf20Sopenharmony_ci 10998c2ecf20Sopenharmony_ci spin_lock(&taprio_list_lock); 11008c2ecf20Sopenharmony_ci list_for_each_entry(q, &taprio_list, taprio_list) { 11018c2ecf20Sopenharmony_ci qdev = qdisc_dev(q->root); 11028c2ecf20Sopenharmony_ci if (qdev == dev) { 11038c2ecf20Sopenharmony_ci found = true; 11048c2ecf20Sopenharmony_ci break; 11058c2ecf20Sopenharmony_ci } 11068c2ecf20Sopenharmony_ci } 11078c2ecf20Sopenharmony_ci spin_unlock(&taprio_list_lock); 11088c2ecf20Sopenharmony_ci 11098c2ecf20Sopenharmony_ci if (found) 11108c2ecf20Sopenharmony_ci taprio_set_picos_per_byte(dev, q); 11118c2ecf20Sopenharmony_ci 11128c2ecf20Sopenharmony_ci return NOTIFY_DONE; 11138c2ecf20Sopenharmony_ci} 11148c2ecf20Sopenharmony_ci 11158c2ecf20Sopenharmony_cistatic void setup_txtime(struct taprio_sched *q, 11168c2ecf20Sopenharmony_ci struct sched_gate_list *sched, ktime_t base) 11178c2ecf20Sopenharmony_ci{ 11188c2ecf20Sopenharmony_ci struct sched_entry *entry; 11198c2ecf20Sopenharmony_ci u32 interval = 0; 11208c2ecf20Sopenharmony_ci 11218c2ecf20Sopenharmony_ci list_for_each_entry(entry, &sched->entries, list) { 11228c2ecf20Sopenharmony_ci entry->next_txtime = ktime_add_ns(base, interval); 11238c2ecf20Sopenharmony_ci interval += entry->interval; 11248c2ecf20Sopenharmony_ci } 11258c2ecf20Sopenharmony_ci} 11268c2ecf20Sopenharmony_ci 11278c2ecf20Sopenharmony_cistatic struct tc_taprio_qopt_offload *taprio_offload_alloc(int num_entries) 11288c2ecf20Sopenharmony_ci{ 11298c2ecf20Sopenharmony_ci struct __tc_taprio_qopt_offload *__offload; 11308c2ecf20Sopenharmony_ci 11318c2ecf20Sopenharmony_ci __offload = kzalloc(struct_size(__offload, offload.entries, num_entries), 11328c2ecf20Sopenharmony_ci GFP_KERNEL); 11338c2ecf20Sopenharmony_ci if (!__offload) 11348c2ecf20Sopenharmony_ci return NULL; 11358c2ecf20Sopenharmony_ci 11368c2ecf20Sopenharmony_ci refcount_set(&__offload->users, 1); 11378c2ecf20Sopenharmony_ci 11388c2ecf20Sopenharmony_ci return &__offload->offload; 11398c2ecf20Sopenharmony_ci} 11408c2ecf20Sopenharmony_ci 11418c2ecf20Sopenharmony_cistruct tc_taprio_qopt_offload *taprio_offload_get(struct tc_taprio_qopt_offload 11428c2ecf20Sopenharmony_ci *offload) 11438c2ecf20Sopenharmony_ci{ 11448c2ecf20Sopenharmony_ci struct __tc_taprio_qopt_offload *__offload; 11458c2ecf20Sopenharmony_ci 11468c2ecf20Sopenharmony_ci __offload = container_of(offload, struct __tc_taprio_qopt_offload, 11478c2ecf20Sopenharmony_ci offload); 11488c2ecf20Sopenharmony_ci 11498c2ecf20Sopenharmony_ci refcount_inc(&__offload->users); 11508c2ecf20Sopenharmony_ci 11518c2ecf20Sopenharmony_ci return offload; 11528c2ecf20Sopenharmony_ci} 11538c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(taprio_offload_get); 11548c2ecf20Sopenharmony_ci 11558c2ecf20Sopenharmony_civoid taprio_offload_free(struct tc_taprio_qopt_offload *offload) 11568c2ecf20Sopenharmony_ci{ 11578c2ecf20Sopenharmony_ci struct __tc_taprio_qopt_offload *__offload; 11588c2ecf20Sopenharmony_ci 11598c2ecf20Sopenharmony_ci __offload = container_of(offload, struct __tc_taprio_qopt_offload, 11608c2ecf20Sopenharmony_ci offload); 11618c2ecf20Sopenharmony_ci 11628c2ecf20Sopenharmony_ci if (!refcount_dec_and_test(&__offload->users)) 11638c2ecf20Sopenharmony_ci return; 11648c2ecf20Sopenharmony_ci 11658c2ecf20Sopenharmony_ci kfree(__offload); 11668c2ecf20Sopenharmony_ci} 11678c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(taprio_offload_free); 11688c2ecf20Sopenharmony_ci 11698c2ecf20Sopenharmony_ci/* The function will only serve to keep the pointers to the "oper" and "admin" 11708c2ecf20Sopenharmony_ci * schedules valid in relation to their base times, so when calling dump() the 11718c2ecf20Sopenharmony_ci * users looks at the right schedules. 11728c2ecf20Sopenharmony_ci * When using full offload, the admin configuration is promoted to oper at the 11738c2ecf20Sopenharmony_ci * base_time in the PHC time domain. But because the system time is not 11748c2ecf20Sopenharmony_ci * necessarily in sync with that, we can't just trigger a hrtimer to call 11758c2ecf20Sopenharmony_ci * switch_schedules at the right hardware time. 11768c2ecf20Sopenharmony_ci * At the moment we call this by hand right away from taprio, but in the future 11778c2ecf20Sopenharmony_ci * it will be useful to create a mechanism for drivers to notify taprio of the 11788c2ecf20Sopenharmony_ci * offload state (PENDING, ACTIVE, INACTIVE) so it can be visible in dump(). 11798c2ecf20Sopenharmony_ci * This is left as TODO. 11808c2ecf20Sopenharmony_ci */ 11818c2ecf20Sopenharmony_cistatic void taprio_offload_config_changed(struct taprio_sched *q) 11828c2ecf20Sopenharmony_ci{ 11838c2ecf20Sopenharmony_ci struct sched_gate_list *oper, *admin; 11848c2ecf20Sopenharmony_ci 11858c2ecf20Sopenharmony_ci spin_lock(&q->current_entry_lock); 11868c2ecf20Sopenharmony_ci 11878c2ecf20Sopenharmony_ci oper = rcu_dereference_protected(q->oper_sched, 11888c2ecf20Sopenharmony_ci lockdep_is_held(&q->current_entry_lock)); 11898c2ecf20Sopenharmony_ci admin = rcu_dereference_protected(q->admin_sched, 11908c2ecf20Sopenharmony_ci lockdep_is_held(&q->current_entry_lock)); 11918c2ecf20Sopenharmony_ci 11928c2ecf20Sopenharmony_ci switch_schedules(q, &admin, &oper); 11938c2ecf20Sopenharmony_ci 11948c2ecf20Sopenharmony_ci spin_unlock(&q->current_entry_lock); 11958c2ecf20Sopenharmony_ci} 11968c2ecf20Sopenharmony_ci 11978c2ecf20Sopenharmony_cistatic u32 tc_map_to_queue_mask(struct net_device *dev, u32 tc_mask) 11988c2ecf20Sopenharmony_ci{ 11998c2ecf20Sopenharmony_ci u32 i, queue_mask = 0; 12008c2ecf20Sopenharmony_ci 12018c2ecf20Sopenharmony_ci for (i = 0; i < dev->num_tc; i++) { 12028c2ecf20Sopenharmony_ci u32 offset, count; 12038c2ecf20Sopenharmony_ci 12048c2ecf20Sopenharmony_ci if (!(tc_mask & BIT(i))) 12058c2ecf20Sopenharmony_ci continue; 12068c2ecf20Sopenharmony_ci 12078c2ecf20Sopenharmony_ci offset = dev->tc_to_txq[i].offset; 12088c2ecf20Sopenharmony_ci count = dev->tc_to_txq[i].count; 12098c2ecf20Sopenharmony_ci 12108c2ecf20Sopenharmony_ci queue_mask |= GENMASK(offset + count - 1, offset); 12118c2ecf20Sopenharmony_ci } 12128c2ecf20Sopenharmony_ci 12138c2ecf20Sopenharmony_ci return queue_mask; 12148c2ecf20Sopenharmony_ci} 12158c2ecf20Sopenharmony_ci 12168c2ecf20Sopenharmony_cistatic void taprio_sched_to_offload(struct net_device *dev, 12178c2ecf20Sopenharmony_ci struct sched_gate_list *sched, 12188c2ecf20Sopenharmony_ci struct tc_taprio_qopt_offload *offload) 12198c2ecf20Sopenharmony_ci{ 12208c2ecf20Sopenharmony_ci struct sched_entry *entry; 12218c2ecf20Sopenharmony_ci int i = 0; 12228c2ecf20Sopenharmony_ci 12238c2ecf20Sopenharmony_ci offload->base_time = sched->base_time; 12248c2ecf20Sopenharmony_ci offload->cycle_time = sched->cycle_time; 12258c2ecf20Sopenharmony_ci offload->cycle_time_extension = sched->cycle_time_extension; 12268c2ecf20Sopenharmony_ci 12278c2ecf20Sopenharmony_ci list_for_each_entry(entry, &sched->entries, list) { 12288c2ecf20Sopenharmony_ci struct tc_taprio_sched_entry *e = &offload->entries[i]; 12298c2ecf20Sopenharmony_ci 12308c2ecf20Sopenharmony_ci e->command = entry->command; 12318c2ecf20Sopenharmony_ci e->interval = entry->interval; 12328c2ecf20Sopenharmony_ci e->gate_mask = tc_map_to_queue_mask(dev, entry->gate_mask); 12338c2ecf20Sopenharmony_ci 12348c2ecf20Sopenharmony_ci i++; 12358c2ecf20Sopenharmony_ci } 12368c2ecf20Sopenharmony_ci 12378c2ecf20Sopenharmony_ci offload->num_entries = i; 12388c2ecf20Sopenharmony_ci} 12398c2ecf20Sopenharmony_ci 12408c2ecf20Sopenharmony_cistatic int taprio_enable_offload(struct net_device *dev, 12418c2ecf20Sopenharmony_ci struct taprio_sched *q, 12428c2ecf20Sopenharmony_ci struct sched_gate_list *sched, 12438c2ecf20Sopenharmony_ci struct netlink_ext_ack *extack) 12448c2ecf20Sopenharmony_ci{ 12458c2ecf20Sopenharmony_ci const struct net_device_ops *ops = dev->netdev_ops; 12468c2ecf20Sopenharmony_ci struct tc_taprio_qopt_offload *offload; 12478c2ecf20Sopenharmony_ci int err = 0; 12488c2ecf20Sopenharmony_ci 12498c2ecf20Sopenharmony_ci if (!ops->ndo_setup_tc) { 12508c2ecf20Sopenharmony_ci NL_SET_ERR_MSG(extack, 12518c2ecf20Sopenharmony_ci "Device does not support taprio offload"); 12528c2ecf20Sopenharmony_ci return -EOPNOTSUPP; 12538c2ecf20Sopenharmony_ci } 12548c2ecf20Sopenharmony_ci 12558c2ecf20Sopenharmony_ci offload = taprio_offload_alloc(sched->num_entries); 12568c2ecf20Sopenharmony_ci if (!offload) { 12578c2ecf20Sopenharmony_ci NL_SET_ERR_MSG(extack, 12588c2ecf20Sopenharmony_ci "Not enough memory for enabling offload mode"); 12598c2ecf20Sopenharmony_ci return -ENOMEM; 12608c2ecf20Sopenharmony_ci } 12618c2ecf20Sopenharmony_ci offload->enable = 1; 12628c2ecf20Sopenharmony_ci taprio_sched_to_offload(dev, sched, offload); 12638c2ecf20Sopenharmony_ci 12648c2ecf20Sopenharmony_ci err = ops->ndo_setup_tc(dev, TC_SETUP_QDISC_TAPRIO, offload); 12658c2ecf20Sopenharmony_ci if (err < 0) { 12668c2ecf20Sopenharmony_ci NL_SET_ERR_MSG(extack, 12678c2ecf20Sopenharmony_ci "Device failed to setup taprio offload"); 12688c2ecf20Sopenharmony_ci goto done; 12698c2ecf20Sopenharmony_ci } 12708c2ecf20Sopenharmony_ci 12718c2ecf20Sopenharmony_ci q->offloaded = true; 12728c2ecf20Sopenharmony_ci 12738c2ecf20Sopenharmony_cidone: 12748c2ecf20Sopenharmony_ci taprio_offload_free(offload); 12758c2ecf20Sopenharmony_ci 12768c2ecf20Sopenharmony_ci return err; 12778c2ecf20Sopenharmony_ci} 12788c2ecf20Sopenharmony_ci 12798c2ecf20Sopenharmony_cistatic int taprio_disable_offload(struct net_device *dev, 12808c2ecf20Sopenharmony_ci struct taprio_sched *q, 12818c2ecf20Sopenharmony_ci struct netlink_ext_ack *extack) 12828c2ecf20Sopenharmony_ci{ 12838c2ecf20Sopenharmony_ci const struct net_device_ops *ops = dev->netdev_ops; 12848c2ecf20Sopenharmony_ci struct tc_taprio_qopt_offload *offload; 12858c2ecf20Sopenharmony_ci int err; 12868c2ecf20Sopenharmony_ci 12878c2ecf20Sopenharmony_ci if (!q->offloaded) 12888c2ecf20Sopenharmony_ci return 0; 12898c2ecf20Sopenharmony_ci 12908c2ecf20Sopenharmony_ci offload = taprio_offload_alloc(0); 12918c2ecf20Sopenharmony_ci if (!offload) { 12928c2ecf20Sopenharmony_ci NL_SET_ERR_MSG(extack, 12938c2ecf20Sopenharmony_ci "Not enough memory to disable offload mode"); 12948c2ecf20Sopenharmony_ci return -ENOMEM; 12958c2ecf20Sopenharmony_ci } 12968c2ecf20Sopenharmony_ci offload->enable = 0; 12978c2ecf20Sopenharmony_ci 12988c2ecf20Sopenharmony_ci err = ops->ndo_setup_tc(dev, TC_SETUP_QDISC_TAPRIO, offload); 12998c2ecf20Sopenharmony_ci if (err < 0) { 13008c2ecf20Sopenharmony_ci NL_SET_ERR_MSG(extack, 13018c2ecf20Sopenharmony_ci "Device failed to disable offload"); 13028c2ecf20Sopenharmony_ci goto out; 13038c2ecf20Sopenharmony_ci } 13048c2ecf20Sopenharmony_ci 13058c2ecf20Sopenharmony_ci q->offloaded = false; 13068c2ecf20Sopenharmony_ci 13078c2ecf20Sopenharmony_ciout: 13088c2ecf20Sopenharmony_ci taprio_offload_free(offload); 13098c2ecf20Sopenharmony_ci 13108c2ecf20Sopenharmony_ci return err; 13118c2ecf20Sopenharmony_ci} 13128c2ecf20Sopenharmony_ci 13138c2ecf20Sopenharmony_ci/* If full offload is enabled, the only possible clockid is the net device's 13148c2ecf20Sopenharmony_ci * PHC. For that reason, specifying a clockid through netlink is incorrect. 13158c2ecf20Sopenharmony_ci * For txtime-assist, it is implicitly assumed that the device's PHC is kept 13168c2ecf20Sopenharmony_ci * in sync with the specified clockid via a user space daemon such as phc2sys. 13178c2ecf20Sopenharmony_ci * For both software taprio and txtime-assist, the clockid is used for the 13188c2ecf20Sopenharmony_ci * hrtimer that advances the schedule and hence mandatory. 13198c2ecf20Sopenharmony_ci */ 13208c2ecf20Sopenharmony_cistatic int taprio_parse_clockid(struct Qdisc *sch, struct nlattr **tb, 13218c2ecf20Sopenharmony_ci struct netlink_ext_ack *extack) 13228c2ecf20Sopenharmony_ci{ 13238c2ecf20Sopenharmony_ci struct taprio_sched *q = qdisc_priv(sch); 13248c2ecf20Sopenharmony_ci struct net_device *dev = qdisc_dev(sch); 13258c2ecf20Sopenharmony_ci int err = -EINVAL; 13268c2ecf20Sopenharmony_ci 13278c2ecf20Sopenharmony_ci if (FULL_OFFLOAD_IS_ENABLED(q->flags)) { 13288c2ecf20Sopenharmony_ci const struct ethtool_ops *ops = dev->ethtool_ops; 13298c2ecf20Sopenharmony_ci struct ethtool_ts_info info = { 13308c2ecf20Sopenharmony_ci .cmd = ETHTOOL_GET_TS_INFO, 13318c2ecf20Sopenharmony_ci .phc_index = -1, 13328c2ecf20Sopenharmony_ci }; 13338c2ecf20Sopenharmony_ci 13348c2ecf20Sopenharmony_ci if (tb[TCA_TAPRIO_ATTR_SCHED_CLOCKID]) { 13358c2ecf20Sopenharmony_ci NL_SET_ERR_MSG(extack, 13368c2ecf20Sopenharmony_ci "The 'clockid' cannot be specified for full offload"); 13378c2ecf20Sopenharmony_ci goto out; 13388c2ecf20Sopenharmony_ci } 13398c2ecf20Sopenharmony_ci 13408c2ecf20Sopenharmony_ci if (ops && ops->get_ts_info) 13418c2ecf20Sopenharmony_ci err = ops->get_ts_info(dev, &info); 13428c2ecf20Sopenharmony_ci 13438c2ecf20Sopenharmony_ci if (err || info.phc_index < 0) { 13448c2ecf20Sopenharmony_ci NL_SET_ERR_MSG(extack, 13458c2ecf20Sopenharmony_ci "Device does not have a PTP clock"); 13468c2ecf20Sopenharmony_ci err = -ENOTSUPP; 13478c2ecf20Sopenharmony_ci goto out; 13488c2ecf20Sopenharmony_ci } 13498c2ecf20Sopenharmony_ci } else if (tb[TCA_TAPRIO_ATTR_SCHED_CLOCKID]) { 13508c2ecf20Sopenharmony_ci int clockid = nla_get_s32(tb[TCA_TAPRIO_ATTR_SCHED_CLOCKID]); 13518c2ecf20Sopenharmony_ci enum tk_offsets tk_offset; 13528c2ecf20Sopenharmony_ci 13538c2ecf20Sopenharmony_ci /* We only support static clockids and we don't allow 13548c2ecf20Sopenharmony_ci * for it to be modified after the first init. 13558c2ecf20Sopenharmony_ci */ 13568c2ecf20Sopenharmony_ci if (clockid < 0 || 13578c2ecf20Sopenharmony_ci (q->clockid != -1 && q->clockid != clockid)) { 13588c2ecf20Sopenharmony_ci NL_SET_ERR_MSG(extack, 13598c2ecf20Sopenharmony_ci "Changing the 'clockid' of a running schedule is not supported"); 13608c2ecf20Sopenharmony_ci err = -ENOTSUPP; 13618c2ecf20Sopenharmony_ci goto out; 13628c2ecf20Sopenharmony_ci } 13638c2ecf20Sopenharmony_ci 13648c2ecf20Sopenharmony_ci switch (clockid) { 13658c2ecf20Sopenharmony_ci case CLOCK_REALTIME: 13668c2ecf20Sopenharmony_ci tk_offset = TK_OFFS_REAL; 13678c2ecf20Sopenharmony_ci break; 13688c2ecf20Sopenharmony_ci case CLOCK_MONOTONIC: 13698c2ecf20Sopenharmony_ci tk_offset = TK_OFFS_MAX; 13708c2ecf20Sopenharmony_ci break; 13718c2ecf20Sopenharmony_ci case CLOCK_BOOTTIME: 13728c2ecf20Sopenharmony_ci tk_offset = TK_OFFS_BOOT; 13738c2ecf20Sopenharmony_ci break; 13748c2ecf20Sopenharmony_ci case CLOCK_TAI: 13758c2ecf20Sopenharmony_ci tk_offset = TK_OFFS_TAI; 13768c2ecf20Sopenharmony_ci break; 13778c2ecf20Sopenharmony_ci default: 13788c2ecf20Sopenharmony_ci NL_SET_ERR_MSG(extack, "Invalid 'clockid'"); 13798c2ecf20Sopenharmony_ci err = -EINVAL; 13808c2ecf20Sopenharmony_ci goto out; 13818c2ecf20Sopenharmony_ci } 13828c2ecf20Sopenharmony_ci /* This pairs with READ_ONCE() in taprio_mono_to_any */ 13838c2ecf20Sopenharmony_ci WRITE_ONCE(q->tk_offset, tk_offset); 13848c2ecf20Sopenharmony_ci 13858c2ecf20Sopenharmony_ci q->clockid = clockid; 13868c2ecf20Sopenharmony_ci } else { 13878c2ecf20Sopenharmony_ci NL_SET_ERR_MSG(extack, "Specifying a 'clockid' is mandatory"); 13888c2ecf20Sopenharmony_ci goto out; 13898c2ecf20Sopenharmony_ci } 13908c2ecf20Sopenharmony_ci 13918c2ecf20Sopenharmony_ci /* Everything went ok, return success. */ 13928c2ecf20Sopenharmony_ci err = 0; 13938c2ecf20Sopenharmony_ci 13948c2ecf20Sopenharmony_ciout: 13958c2ecf20Sopenharmony_ci return err; 13968c2ecf20Sopenharmony_ci} 13978c2ecf20Sopenharmony_ci 13988c2ecf20Sopenharmony_cistatic int taprio_mqprio_cmp(const struct net_device *dev, 13998c2ecf20Sopenharmony_ci const struct tc_mqprio_qopt *mqprio) 14008c2ecf20Sopenharmony_ci{ 14018c2ecf20Sopenharmony_ci int i; 14028c2ecf20Sopenharmony_ci 14038c2ecf20Sopenharmony_ci if (!mqprio || mqprio->num_tc != dev->num_tc) 14048c2ecf20Sopenharmony_ci return -1; 14058c2ecf20Sopenharmony_ci 14068c2ecf20Sopenharmony_ci for (i = 0; i < mqprio->num_tc; i++) 14078c2ecf20Sopenharmony_ci if (dev->tc_to_txq[i].count != mqprio->count[i] || 14088c2ecf20Sopenharmony_ci dev->tc_to_txq[i].offset != mqprio->offset[i]) 14098c2ecf20Sopenharmony_ci return -1; 14108c2ecf20Sopenharmony_ci 14118c2ecf20Sopenharmony_ci for (i = 0; i <= TC_BITMASK; i++) 14128c2ecf20Sopenharmony_ci if (dev->prio_tc_map[i] != mqprio->prio_tc_map[i]) 14138c2ecf20Sopenharmony_ci return -1; 14148c2ecf20Sopenharmony_ci 14158c2ecf20Sopenharmony_ci return 0; 14168c2ecf20Sopenharmony_ci} 14178c2ecf20Sopenharmony_ci 14188c2ecf20Sopenharmony_ci/* The semantics of the 'flags' argument in relation to 'change()' 14198c2ecf20Sopenharmony_ci * requests, are interpreted following two rules (which are applied in 14208c2ecf20Sopenharmony_ci * this order): (1) an omitted 'flags' argument is interpreted as 14218c2ecf20Sopenharmony_ci * zero; (2) the 'flags' of a "running" taprio instance cannot be 14228c2ecf20Sopenharmony_ci * changed. 14238c2ecf20Sopenharmony_ci */ 14248c2ecf20Sopenharmony_cistatic int taprio_new_flags(const struct nlattr *attr, u32 old, 14258c2ecf20Sopenharmony_ci struct netlink_ext_ack *extack) 14268c2ecf20Sopenharmony_ci{ 14278c2ecf20Sopenharmony_ci u32 new = 0; 14288c2ecf20Sopenharmony_ci 14298c2ecf20Sopenharmony_ci if (attr) 14308c2ecf20Sopenharmony_ci new = nla_get_u32(attr); 14318c2ecf20Sopenharmony_ci 14328c2ecf20Sopenharmony_ci if (old != TAPRIO_FLAGS_INVALID && old != new) { 14338c2ecf20Sopenharmony_ci NL_SET_ERR_MSG_MOD(extack, "Changing 'flags' of a running schedule is not supported"); 14348c2ecf20Sopenharmony_ci return -EOPNOTSUPP; 14358c2ecf20Sopenharmony_ci } 14368c2ecf20Sopenharmony_ci 14378c2ecf20Sopenharmony_ci if (!taprio_flags_valid(new)) { 14388c2ecf20Sopenharmony_ci NL_SET_ERR_MSG_MOD(extack, "Specified 'flags' are not valid"); 14398c2ecf20Sopenharmony_ci return -EINVAL; 14408c2ecf20Sopenharmony_ci } 14418c2ecf20Sopenharmony_ci 14428c2ecf20Sopenharmony_ci return new; 14438c2ecf20Sopenharmony_ci} 14448c2ecf20Sopenharmony_ci 14458c2ecf20Sopenharmony_cistatic int taprio_change(struct Qdisc *sch, struct nlattr *opt, 14468c2ecf20Sopenharmony_ci struct netlink_ext_ack *extack) 14478c2ecf20Sopenharmony_ci{ 14488c2ecf20Sopenharmony_ci struct nlattr *tb[TCA_TAPRIO_ATTR_MAX + 1] = { }; 14498c2ecf20Sopenharmony_ci struct sched_gate_list *oper, *admin, *new_admin; 14508c2ecf20Sopenharmony_ci struct taprio_sched *q = qdisc_priv(sch); 14518c2ecf20Sopenharmony_ci struct net_device *dev = qdisc_dev(sch); 14528c2ecf20Sopenharmony_ci struct tc_mqprio_qopt *mqprio = NULL; 14538c2ecf20Sopenharmony_ci unsigned long flags; 14548c2ecf20Sopenharmony_ci ktime_t start; 14558c2ecf20Sopenharmony_ci int i, err; 14568c2ecf20Sopenharmony_ci 14578c2ecf20Sopenharmony_ci err = nla_parse_nested_deprecated(tb, TCA_TAPRIO_ATTR_MAX, opt, 14588c2ecf20Sopenharmony_ci taprio_policy, extack); 14598c2ecf20Sopenharmony_ci if (err < 0) 14608c2ecf20Sopenharmony_ci return err; 14618c2ecf20Sopenharmony_ci 14628c2ecf20Sopenharmony_ci if (tb[TCA_TAPRIO_ATTR_PRIOMAP]) 14638c2ecf20Sopenharmony_ci mqprio = nla_data(tb[TCA_TAPRIO_ATTR_PRIOMAP]); 14648c2ecf20Sopenharmony_ci 14658c2ecf20Sopenharmony_ci err = taprio_new_flags(tb[TCA_TAPRIO_ATTR_FLAGS], 14668c2ecf20Sopenharmony_ci q->flags, extack); 14678c2ecf20Sopenharmony_ci if (err < 0) 14688c2ecf20Sopenharmony_ci return err; 14698c2ecf20Sopenharmony_ci 14708c2ecf20Sopenharmony_ci q->flags = err; 14718c2ecf20Sopenharmony_ci 14728c2ecf20Sopenharmony_ci err = taprio_parse_mqprio_opt(dev, mqprio, extack, q->flags); 14738c2ecf20Sopenharmony_ci if (err < 0) 14748c2ecf20Sopenharmony_ci return err; 14758c2ecf20Sopenharmony_ci 14768c2ecf20Sopenharmony_ci new_admin = kzalloc(sizeof(*new_admin), GFP_KERNEL); 14778c2ecf20Sopenharmony_ci if (!new_admin) { 14788c2ecf20Sopenharmony_ci NL_SET_ERR_MSG(extack, "Not enough memory for a new schedule"); 14798c2ecf20Sopenharmony_ci return -ENOMEM; 14808c2ecf20Sopenharmony_ci } 14818c2ecf20Sopenharmony_ci INIT_LIST_HEAD(&new_admin->entries); 14828c2ecf20Sopenharmony_ci 14838c2ecf20Sopenharmony_ci rcu_read_lock(); 14848c2ecf20Sopenharmony_ci oper = rcu_dereference(q->oper_sched); 14858c2ecf20Sopenharmony_ci admin = rcu_dereference(q->admin_sched); 14868c2ecf20Sopenharmony_ci rcu_read_unlock(); 14878c2ecf20Sopenharmony_ci 14888c2ecf20Sopenharmony_ci /* no changes - no new mqprio settings */ 14898c2ecf20Sopenharmony_ci if (!taprio_mqprio_cmp(dev, mqprio)) 14908c2ecf20Sopenharmony_ci mqprio = NULL; 14918c2ecf20Sopenharmony_ci 14928c2ecf20Sopenharmony_ci if (mqprio && (oper || admin)) { 14938c2ecf20Sopenharmony_ci NL_SET_ERR_MSG(extack, "Changing the traffic mapping of a running schedule is not supported"); 14948c2ecf20Sopenharmony_ci err = -ENOTSUPP; 14958c2ecf20Sopenharmony_ci goto free_sched; 14968c2ecf20Sopenharmony_ci } 14978c2ecf20Sopenharmony_ci 14988c2ecf20Sopenharmony_ci err = parse_taprio_schedule(q, tb, new_admin, extack); 14998c2ecf20Sopenharmony_ci if (err < 0) 15008c2ecf20Sopenharmony_ci goto free_sched; 15018c2ecf20Sopenharmony_ci 15028c2ecf20Sopenharmony_ci if (new_admin->num_entries == 0) { 15038c2ecf20Sopenharmony_ci NL_SET_ERR_MSG(extack, "There should be at least one entry in the schedule"); 15048c2ecf20Sopenharmony_ci err = -EINVAL; 15058c2ecf20Sopenharmony_ci goto free_sched; 15068c2ecf20Sopenharmony_ci } 15078c2ecf20Sopenharmony_ci 15088c2ecf20Sopenharmony_ci err = taprio_parse_clockid(sch, tb, extack); 15098c2ecf20Sopenharmony_ci if (err < 0) 15108c2ecf20Sopenharmony_ci goto free_sched; 15118c2ecf20Sopenharmony_ci 15128c2ecf20Sopenharmony_ci taprio_set_picos_per_byte(dev, q); 15138c2ecf20Sopenharmony_ci 15148c2ecf20Sopenharmony_ci if (mqprio) { 15158c2ecf20Sopenharmony_ci err = netdev_set_num_tc(dev, mqprio->num_tc); 15168c2ecf20Sopenharmony_ci if (err) 15178c2ecf20Sopenharmony_ci goto free_sched; 15188c2ecf20Sopenharmony_ci for (i = 0; i < mqprio->num_tc; i++) 15198c2ecf20Sopenharmony_ci netdev_set_tc_queue(dev, i, 15208c2ecf20Sopenharmony_ci mqprio->count[i], 15218c2ecf20Sopenharmony_ci mqprio->offset[i]); 15228c2ecf20Sopenharmony_ci 15238c2ecf20Sopenharmony_ci /* Always use supplied priority mappings */ 15248c2ecf20Sopenharmony_ci for (i = 0; i <= TC_BITMASK; i++) 15258c2ecf20Sopenharmony_ci netdev_set_prio_tc_map(dev, i, 15268c2ecf20Sopenharmony_ci mqprio->prio_tc_map[i]); 15278c2ecf20Sopenharmony_ci } 15288c2ecf20Sopenharmony_ci 15298c2ecf20Sopenharmony_ci if (FULL_OFFLOAD_IS_ENABLED(q->flags)) 15308c2ecf20Sopenharmony_ci err = taprio_enable_offload(dev, q, new_admin, extack); 15318c2ecf20Sopenharmony_ci else 15328c2ecf20Sopenharmony_ci err = taprio_disable_offload(dev, q, extack); 15338c2ecf20Sopenharmony_ci if (err) 15348c2ecf20Sopenharmony_ci goto free_sched; 15358c2ecf20Sopenharmony_ci 15368c2ecf20Sopenharmony_ci /* Protects against enqueue()/dequeue() */ 15378c2ecf20Sopenharmony_ci spin_lock_bh(qdisc_lock(sch)); 15388c2ecf20Sopenharmony_ci 15398c2ecf20Sopenharmony_ci if (tb[TCA_TAPRIO_ATTR_TXTIME_DELAY]) { 15408c2ecf20Sopenharmony_ci if (!TXTIME_ASSIST_IS_ENABLED(q->flags)) { 15418c2ecf20Sopenharmony_ci NL_SET_ERR_MSG_MOD(extack, "txtime-delay can only be set when txtime-assist mode is enabled"); 15428c2ecf20Sopenharmony_ci err = -EINVAL; 15438c2ecf20Sopenharmony_ci goto unlock; 15448c2ecf20Sopenharmony_ci } 15458c2ecf20Sopenharmony_ci 15468c2ecf20Sopenharmony_ci q->txtime_delay = nla_get_u32(tb[TCA_TAPRIO_ATTR_TXTIME_DELAY]); 15478c2ecf20Sopenharmony_ci } 15488c2ecf20Sopenharmony_ci 15498c2ecf20Sopenharmony_ci if (!TXTIME_ASSIST_IS_ENABLED(q->flags) && 15508c2ecf20Sopenharmony_ci !FULL_OFFLOAD_IS_ENABLED(q->flags) && 15518c2ecf20Sopenharmony_ci !hrtimer_active(&q->advance_timer)) { 15528c2ecf20Sopenharmony_ci hrtimer_init(&q->advance_timer, q->clockid, HRTIMER_MODE_ABS); 15538c2ecf20Sopenharmony_ci q->advance_timer.function = advance_sched; 15548c2ecf20Sopenharmony_ci } 15558c2ecf20Sopenharmony_ci 15568c2ecf20Sopenharmony_ci if (FULL_OFFLOAD_IS_ENABLED(q->flags)) { 15578c2ecf20Sopenharmony_ci q->dequeue = taprio_dequeue_offload; 15588c2ecf20Sopenharmony_ci q->peek = taprio_peek_offload; 15598c2ecf20Sopenharmony_ci } else { 15608c2ecf20Sopenharmony_ci /* Be sure to always keep the function pointers 15618c2ecf20Sopenharmony_ci * in a consistent state. 15628c2ecf20Sopenharmony_ci */ 15638c2ecf20Sopenharmony_ci q->dequeue = taprio_dequeue_soft; 15648c2ecf20Sopenharmony_ci q->peek = taprio_peek_soft; 15658c2ecf20Sopenharmony_ci } 15668c2ecf20Sopenharmony_ci 15678c2ecf20Sopenharmony_ci err = taprio_get_start_time(sch, new_admin, &start); 15688c2ecf20Sopenharmony_ci if (err < 0) { 15698c2ecf20Sopenharmony_ci NL_SET_ERR_MSG(extack, "Internal error: failed get start time"); 15708c2ecf20Sopenharmony_ci goto unlock; 15718c2ecf20Sopenharmony_ci } 15728c2ecf20Sopenharmony_ci 15738c2ecf20Sopenharmony_ci setup_txtime(q, new_admin, start); 15748c2ecf20Sopenharmony_ci 15758c2ecf20Sopenharmony_ci if (TXTIME_ASSIST_IS_ENABLED(q->flags)) { 15768c2ecf20Sopenharmony_ci if (!oper) { 15778c2ecf20Sopenharmony_ci rcu_assign_pointer(q->oper_sched, new_admin); 15788c2ecf20Sopenharmony_ci err = 0; 15798c2ecf20Sopenharmony_ci new_admin = NULL; 15808c2ecf20Sopenharmony_ci goto unlock; 15818c2ecf20Sopenharmony_ci } 15828c2ecf20Sopenharmony_ci 15838c2ecf20Sopenharmony_ci rcu_assign_pointer(q->admin_sched, new_admin); 15848c2ecf20Sopenharmony_ci if (admin) 15858c2ecf20Sopenharmony_ci call_rcu(&admin->rcu, taprio_free_sched_cb); 15868c2ecf20Sopenharmony_ci } else { 15878c2ecf20Sopenharmony_ci setup_first_close_time(q, new_admin, start); 15888c2ecf20Sopenharmony_ci 15898c2ecf20Sopenharmony_ci /* Protects against advance_sched() */ 15908c2ecf20Sopenharmony_ci spin_lock_irqsave(&q->current_entry_lock, flags); 15918c2ecf20Sopenharmony_ci 15928c2ecf20Sopenharmony_ci taprio_start_sched(sch, start, new_admin); 15938c2ecf20Sopenharmony_ci 15948c2ecf20Sopenharmony_ci rcu_assign_pointer(q->admin_sched, new_admin); 15958c2ecf20Sopenharmony_ci if (admin) 15968c2ecf20Sopenharmony_ci call_rcu(&admin->rcu, taprio_free_sched_cb); 15978c2ecf20Sopenharmony_ci 15988c2ecf20Sopenharmony_ci spin_unlock_irqrestore(&q->current_entry_lock, flags); 15998c2ecf20Sopenharmony_ci 16008c2ecf20Sopenharmony_ci if (FULL_OFFLOAD_IS_ENABLED(q->flags)) 16018c2ecf20Sopenharmony_ci taprio_offload_config_changed(q); 16028c2ecf20Sopenharmony_ci } 16038c2ecf20Sopenharmony_ci 16048c2ecf20Sopenharmony_ci new_admin = NULL; 16058c2ecf20Sopenharmony_ci err = 0; 16068c2ecf20Sopenharmony_ci 16078c2ecf20Sopenharmony_ciunlock: 16088c2ecf20Sopenharmony_ci spin_unlock_bh(qdisc_lock(sch)); 16098c2ecf20Sopenharmony_ci 16108c2ecf20Sopenharmony_cifree_sched: 16118c2ecf20Sopenharmony_ci if (new_admin) 16128c2ecf20Sopenharmony_ci call_rcu(&new_admin->rcu, taprio_free_sched_cb); 16138c2ecf20Sopenharmony_ci 16148c2ecf20Sopenharmony_ci return err; 16158c2ecf20Sopenharmony_ci} 16168c2ecf20Sopenharmony_ci 16178c2ecf20Sopenharmony_cistatic void taprio_reset(struct Qdisc *sch) 16188c2ecf20Sopenharmony_ci{ 16198c2ecf20Sopenharmony_ci struct taprio_sched *q = qdisc_priv(sch); 16208c2ecf20Sopenharmony_ci struct net_device *dev = qdisc_dev(sch); 16218c2ecf20Sopenharmony_ci int i; 16228c2ecf20Sopenharmony_ci 16238c2ecf20Sopenharmony_ci hrtimer_cancel(&q->advance_timer); 16248c2ecf20Sopenharmony_ci 16258c2ecf20Sopenharmony_ci if (q->qdiscs) { 16268c2ecf20Sopenharmony_ci for (i = 0; i < dev->num_tx_queues; i++) 16278c2ecf20Sopenharmony_ci if (q->qdiscs[i]) 16288c2ecf20Sopenharmony_ci qdisc_reset(q->qdiscs[i]); 16298c2ecf20Sopenharmony_ci } 16308c2ecf20Sopenharmony_ci} 16318c2ecf20Sopenharmony_ci 16328c2ecf20Sopenharmony_cistatic void taprio_destroy(struct Qdisc *sch) 16338c2ecf20Sopenharmony_ci{ 16348c2ecf20Sopenharmony_ci struct taprio_sched *q = qdisc_priv(sch); 16358c2ecf20Sopenharmony_ci struct net_device *dev = qdisc_dev(sch); 16368c2ecf20Sopenharmony_ci unsigned int i; 16378c2ecf20Sopenharmony_ci 16388c2ecf20Sopenharmony_ci spin_lock(&taprio_list_lock); 16398c2ecf20Sopenharmony_ci list_del(&q->taprio_list); 16408c2ecf20Sopenharmony_ci spin_unlock(&taprio_list_lock); 16418c2ecf20Sopenharmony_ci 16428c2ecf20Sopenharmony_ci /* Note that taprio_reset() might not be called if an error 16438c2ecf20Sopenharmony_ci * happens in qdisc_create(), after taprio_init() has been called. 16448c2ecf20Sopenharmony_ci */ 16458c2ecf20Sopenharmony_ci hrtimer_cancel(&q->advance_timer); 16468c2ecf20Sopenharmony_ci qdisc_synchronize(sch); 16478c2ecf20Sopenharmony_ci 16488c2ecf20Sopenharmony_ci taprio_disable_offload(dev, q, NULL); 16498c2ecf20Sopenharmony_ci 16508c2ecf20Sopenharmony_ci if (q->qdiscs) { 16518c2ecf20Sopenharmony_ci for (i = 0; i < dev->num_tx_queues; i++) 16528c2ecf20Sopenharmony_ci qdisc_put(q->qdiscs[i]); 16538c2ecf20Sopenharmony_ci 16548c2ecf20Sopenharmony_ci kfree(q->qdiscs); 16558c2ecf20Sopenharmony_ci } 16568c2ecf20Sopenharmony_ci q->qdiscs = NULL; 16578c2ecf20Sopenharmony_ci 16588c2ecf20Sopenharmony_ci netdev_reset_tc(dev); 16598c2ecf20Sopenharmony_ci 16608c2ecf20Sopenharmony_ci if (q->oper_sched) 16618c2ecf20Sopenharmony_ci call_rcu(&q->oper_sched->rcu, taprio_free_sched_cb); 16628c2ecf20Sopenharmony_ci 16638c2ecf20Sopenharmony_ci if (q->admin_sched) 16648c2ecf20Sopenharmony_ci call_rcu(&q->admin_sched->rcu, taprio_free_sched_cb); 16658c2ecf20Sopenharmony_ci} 16668c2ecf20Sopenharmony_ci 16678c2ecf20Sopenharmony_cistatic int taprio_init(struct Qdisc *sch, struct nlattr *opt, 16688c2ecf20Sopenharmony_ci struct netlink_ext_ack *extack) 16698c2ecf20Sopenharmony_ci{ 16708c2ecf20Sopenharmony_ci struct taprio_sched *q = qdisc_priv(sch); 16718c2ecf20Sopenharmony_ci struct net_device *dev = qdisc_dev(sch); 16728c2ecf20Sopenharmony_ci int i; 16738c2ecf20Sopenharmony_ci 16748c2ecf20Sopenharmony_ci spin_lock_init(&q->current_entry_lock); 16758c2ecf20Sopenharmony_ci 16768c2ecf20Sopenharmony_ci hrtimer_init(&q->advance_timer, CLOCK_TAI, HRTIMER_MODE_ABS); 16778c2ecf20Sopenharmony_ci q->advance_timer.function = advance_sched; 16788c2ecf20Sopenharmony_ci 16798c2ecf20Sopenharmony_ci q->dequeue = taprio_dequeue_soft; 16808c2ecf20Sopenharmony_ci q->peek = taprio_peek_soft; 16818c2ecf20Sopenharmony_ci 16828c2ecf20Sopenharmony_ci q->root = sch; 16838c2ecf20Sopenharmony_ci 16848c2ecf20Sopenharmony_ci /* We only support static clockids. Use an invalid value as default 16858c2ecf20Sopenharmony_ci * and get the valid one on taprio_change(). 16868c2ecf20Sopenharmony_ci */ 16878c2ecf20Sopenharmony_ci q->clockid = -1; 16888c2ecf20Sopenharmony_ci q->flags = TAPRIO_FLAGS_INVALID; 16898c2ecf20Sopenharmony_ci 16908c2ecf20Sopenharmony_ci spin_lock(&taprio_list_lock); 16918c2ecf20Sopenharmony_ci list_add(&q->taprio_list, &taprio_list); 16928c2ecf20Sopenharmony_ci spin_unlock(&taprio_list_lock); 16938c2ecf20Sopenharmony_ci 16948c2ecf20Sopenharmony_ci if (sch->parent != TC_H_ROOT) 16958c2ecf20Sopenharmony_ci return -EOPNOTSUPP; 16968c2ecf20Sopenharmony_ci 16978c2ecf20Sopenharmony_ci if (!netif_is_multiqueue(dev)) 16988c2ecf20Sopenharmony_ci return -EOPNOTSUPP; 16998c2ecf20Sopenharmony_ci 17008c2ecf20Sopenharmony_ci /* pre-allocate qdisc, attachment can't fail */ 17018c2ecf20Sopenharmony_ci q->qdiscs = kcalloc(dev->num_tx_queues, 17028c2ecf20Sopenharmony_ci sizeof(q->qdiscs[0]), 17038c2ecf20Sopenharmony_ci GFP_KERNEL); 17048c2ecf20Sopenharmony_ci 17058c2ecf20Sopenharmony_ci if (!q->qdiscs) 17068c2ecf20Sopenharmony_ci return -ENOMEM; 17078c2ecf20Sopenharmony_ci 17088c2ecf20Sopenharmony_ci if (!opt) 17098c2ecf20Sopenharmony_ci return -EINVAL; 17108c2ecf20Sopenharmony_ci 17118c2ecf20Sopenharmony_ci for (i = 0; i < dev->num_tx_queues; i++) { 17128c2ecf20Sopenharmony_ci struct netdev_queue *dev_queue; 17138c2ecf20Sopenharmony_ci struct Qdisc *qdisc; 17148c2ecf20Sopenharmony_ci 17158c2ecf20Sopenharmony_ci dev_queue = netdev_get_tx_queue(dev, i); 17168c2ecf20Sopenharmony_ci qdisc = qdisc_create_dflt(dev_queue, 17178c2ecf20Sopenharmony_ci &pfifo_qdisc_ops, 17188c2ecf20Sopenharmony_ci TC_H_MAKE(TC_H_MAJ(sch->handle), 17198c2ecf20Sopenharmony_ci TC_H_MIN(i + 1)), 17208c2ecf20Sopenharmony_ci extack); 17218c2ecf20Sopenharmony_ci if (!qdisc) 17228c2ecf20Sopenharmony_ci return -ENOMEM; 17238c2ecf20Sopenharmony_ci 17248c2ecf20Sopenharmony_ci if (i < dev->real_num_tx_queues) 17258c2ecf20Sopenharmony_ci qdisc_hash_add(qdisc, false); 17268c2ecf20Sopenharmony_ci 17278c2ecf20Sopenharmony_ci q->qdiscs[i] = qdisc; 17288c2ecf20Sopenharmony_ci } 17298c2ecf20Sopenharmony_ci 17308c2ecf20Sopenharmony_ci return taprio_change(sch, opt, extack); 17318c2ecf20Sopenharmony_ci} 17328c2ecf20Sopenharmony_ci 17338c2ecf20Sopenharmony_cistatic struct netdev_queue *taprio_queue_get(struct Qdisc *sch, 17348c2ecf20Sopenharmony_ci unsigned long cl) 17358c2ecf20Sopenharmony_ci{ 17368c2ecf20Sopenharmony_ci struct net_device *dev = qdisc_dev(sch); 17378c2ecf20Sopenharmony_ci unsigned long ntx = cl - 1; 17388c2ecf20Sopenharmony_ci 17398c2ecf20Sopenharmony_ci if (ntx >= dev->num_tx_queues) 17408c2ecf20Sopenharmony_ci return NULL; 17418c2ecf20Sopenharmony_ci 17428c2ecf20Sopenharmony_ci return netdev_get_tx_queue(dev, ntx); 17438c2ecf20Sopenharmony_ci} 17448c2ecf20Sopenharmony_ci 17458c2ecf20Sopenharmony_cistatic int taprio_graft(struct Qdisc *sch, unsigned long cl, 17468c2ecf20Sopenharmony_ci struct Qdisc *new, struct Qdisc **old, 17478c2ecf20Sopenharmony_ci struct netlink_ext_ack *extack) 17488c2ecf20Sopenharmony_ci{ 17498c2ecf20Sopenharmony_ci struct taprio_sched *q = qdisc_priv(sch); 17508c2ecf20Sopenharmony_ci struct net_device *dev = qdisc_dev(sch); 17518c2ecf20Sopenharmony_ci struct netdev_queue *dev_queue = taprio_queue_get(sch, cl); 17528c2ecf20Sopenharmony_ci 17538c2ecf20Sopenharmony_ci if (!dev_queue) 17548c2ecf20Sopenharmony_ci return -EINVAL; 17558c2ecf20Sopenharmony_ci 17568c2ecf20Sopenharmony_ci if (dev->flags & IFF_UP) 17578c2ecf20Sopenharmony_ci dev_deactivate(dev); 17588c2ecf20Sopenharmony_ci 17598c2ecf20Sopenharmony_ci *old = q->qdiscs[cl - 1]; 17608c2ecf20Sopenharmony_ci q->qdiscs[cl - 1] = new; 17618c2ecf20Sopenharmony_ci 17628c2ecf20Sopenharmony_ci if (new) 17638c2ecf20Sopenharmony_ci new->flags |= TCQ_F_ONETXQUEUE | TCQ_F_NOPARENT; 17648c2ecf20Sopenharmony_ci 17658c2ecf20Sopenharmony_ci if (dev->flags & IFF_UP) 17668c2ecf20Sopenharmony_ci dev_activate(dev); 17678c2ecf20Sopenharmony_ci 17688c2ecf20Sopenharmony_ci return 0; 17698c2ecf20Sopenharmony_ci} 17708c2ecf20Sopenharmony_ci 17718c2ecf20Sopenharmony_cistatic int dump_entry(struct sk_buff *msg, 17728c2ecf20Sopenharmony_ci const struct sched_entry *entry) 17738c2ecf20Sopenharmony_ci{ 17748c2ecf20Sopenharmony_ci struct nlattr *item; 17758c2ecf20Sopenharmony_ci 17768c2ecf20Sopenharmony_ci item = nla_nest_start_noflag(msg, TCA_TAPRIO_SCHED_ENTRY); 17778c2ecf20Sopenharmony_ci if (!item) 17788c2ecf20Sopenharmony_ci return -ENOSPC; 17798c2ecf20Sopenharmony_ci 17808c2ecf20Sopenharmony_ci if (nla_put_u32(msg, TCA_TAPRIO_SCHED_ENTRY_INDEX, entry->index)) 17818c2ecf20Sopenharmony_ci goto nla_put_failure; 17828c2ecf20Sopenharmony_ci 17838c2ecf20Sopenharmony_ci if (nla_put_u8(msg, TCA_TAPRIO_SCHED_ENTRY_CMD, entry->command)) 17848c2ecf20Sopenharmony_ci goto nla_put_failure; 17858c2ecf20Sopenharmony_ci 17868c2ecf20Sopenharmony_ci if (nla_put_u32(msg, TCA_TAPRIO_SCHED_ENTRY_GATE_MASK, 17878c2ecf20Sopenharmony_ci entry->gate_mask)) 17888c2ecf20Sopenharmony_ci goto nla_put_failure; 17898c2ecf20Sopenharmony_ci 17908c2ecf20Sopenharmony_ci if (nla_put_u32(msg, TCA_TAPRIO_SCHED_ENTRY_INTERVAL, 17918c2ecf20Sopenharmony_ci entry->interval)) 17928c2ecf20Sopenharmony_ci goto nla_put_failure; 17938c2ecf20Sopenharmony_ci 17948c2ecf20Sopenharmony_ci return nla_nest_end(msg, item); 17958c2ecf20Sopenharmony_ci 17968c2ecf20Sopenharmony_cinla_put_failure: 17978c2ecf20Sopenharmony_ci nla_nest_cancel(msg, item); 17988c2ecf20Sopenharmony_ci return -1; 17998c2ecf20Sopenharmony_ci} 18008c2ecf20Sopenharmony_ci 18018c2ecf20Sopenharmony_cistatic int dump_schedule(struct sk_buff *msg, 18028c2ecf20Sopenharmony_ci const struct sched_gate_list *root) 18038c2ecf20Sopenharmony_ci{ 18048c2ecf20Sopenharmony_ci struct nlattr *entry_list; 18058c2ecf20Sopenharmony_ci struct sched_entry *entry; 18068c2ecf20Sopenharmony_ci 18078c2ecf20Sopenharmony_ci if (nla_put_s64(msg, TCA_TAPRIO_ATTR_SCHED_BASE_TIME, 18088c2ecf20Sopenharmony_ci root->base_time, TCA_TAPRIO_PAD)) 18098c2ecf20Sopenharmony_ci return -1; 18108c2ecf20Sopenharmony_ci 18118c2ecf20Sopenharmony_ci if (nla_put_s64(msg, TCA_TAPRIO_ATTR_SCHED_CYCLE_TIME, 18128c2ecf20Sopenharmony_ci root->cycle_time, TCA_TAPRIO_PAD)) 18138c2ecf20Sopenharmony_ci return -1; 18148c2ecf20Sopenharmony_ci 18158c2ecf20Sopenharmony_ci if (nla_put_s64(msg, TCA_TAPRIO_ATTR_SCHED_CYCLE_TIME_EXTENSION, 18168c2ecf20Sopenharmony_ci root->cycle_time_extension, TCA_TAPRIO_PAD)) 18178c2ecf20Sopenharmony_ci return -1; 18188c2ecf20Sopenharmony_ci 18198c2ecf20Sopenharmony_ci entry_list = nla_nest_start_noflag(msg, 18208c2ecf20Sopenharmony_ci TCA_TAPRIO_ATTR_SCHED_ENTRY_LIST); 18218c2ecf20Sopenharmony_ci if (!entry_list) 18228c2ecf20Sopenharmony_ci goto error_nest; 18238c2ecf20Sopenharmony_ci 18248c2ecf20Sopenharmony_ci list_for_each_entry(entry, &root->entries, list) { 18258c2ecf20Sopenharmony_ci if (dump_entry(msg, entry) < 0) 18268c2ecf20Sopenharmony_ci goto error_nest; 18278c2ecf20Sopenharmony_ci } 18288c2ecf20Sopenharmony_ci 18298c2ecf20Sopenharmony_ci nla_nest_end(msg, entry_list); 18308c2ecf20Sopenharmony_ci return 0; 18318c2ecf20Sopenharmony_ci 18328c2ecf20Sopenharmony_cierror_nest: 18338c2ecf20Sopenharmony_ci nla_nest_cancel(msg, entry_list); 18348c2ecf20Sopenharmony_ci return -1; 18358c2ecf20Sopenharmony_ci} 18368c2ecf20Sopenharmony_ci 18378c2ecf20Sopenharmony_cistatic int taprio_dump(struct Qdisc *sch, struct sk_buff *skb) 18388c2ecf20Sopenharmony_ci{ 18398c2ecf20Sopenharmony_ci struct taprio_sched *q = qdisc_priv(sch); 18408c2ecf20Sopenharmony_ci struct net_device *dev = qdisc_dev(sch); 18418c2ecf20Sopenharmony_ci struct sched_gate_list *oper, *admin; 18428c2ecf20Sopenharmony_ci struct tc_mqprio_qopt opt = { 0 }; 18438c2ecf20Sopenharmony_ci struct nlattr *nest, *sched_nest; 18448c2ecf20Sopenharmony_ci unsigned int i; 18458c2ecf20Sopenharmony_ci 18468c2ecf20Sopenharmony_ci rcu_read_lock(); 18478c2ecf20Sopenharmony_ci oper = rcu_dereference(q->oper_sched); 18488c2ecf20Sopenharmony_ci admin = rcu_dereference(q->admin_sched); 18498c2ecf20Sopenharmony_ci 18508c2ecf20Sopenharmony_ci opt.num_tc = netdev_get_num_tc(dev); 18518c2ecf20Sopenharmony_ci memcpy(opt.prio_tc_map, dev->prio_tc_map, sizeof(opt.prio_tc_map)); 18528c2ecf20Sopenharmony_ci 18538c2ecf20Sopenharmony_ci for (i = 0; i < netdev_get_num_tc(dev); i++) { 18548c2ecf20Sopenharmony_ci opt.count[i] = dev->tc_to_txq[i].count; 18558c2ecf20Sopenharmony_ci opt.offset[i] = dev->tc_to_txq[i].offset; 18568c2ecf20Sopenharmony_ci } 18578c2ecf20Sopenharmony_ci 18588c2ecf20Sopenharmony_ci nest = nla_nest_start_noflag(skb, TCA_OPTIONS); 18598c2ecf20Sopenharmony_ci if (!nest) 18608c2ecf20Sopenharmony_ci goto start_error; 18618c2ecf20Sopenharmony_ci 18628c2ecf20Sopenharmony_ci if (nla_put(skb, TCA_TAPRIO_ATTR_PRIOMAP, sizeof(opt), &opt)) 18638c2ecf20Sopenharmony_ci goto options_error; 18648c2ecf20Sopenharmony_ci 18658c2ecf20Sopenharmony_ci if (!FULL_OFFLOAD_IS_ENABLED(q->flags) && 18668c2ecf20Sopenharmony_ci nla_put_s32(skb, TCA_TAPRIO_ATTR_SCHED_CLOCKID, q->clockid)) 18678c2ecf20Sopenharmony_ci goto options_error; 18688c2ecf20Sopenharmony_ci 18698c2ecf20Sopenharmony_ci if (q->flags && nla_put_u32(skb, TCA_TAPRIO_ATTR_FLAGS, q->flags)) 18708c2ecf20Sopenharmony_ci goto options_error; 18718c2ecf20Sopenharmony_ci 18728c2ecf20Sopenharmony_ci if (q->txtime_delay && 18738c2ecf20Sopenharmony_ci nla_put_u32(skb, TCA_TAPRIO_ATTR_TXTIME_DELAY, q->txtime_delay)) 18748c2ecf20Sopenharmony_ci goto options_error; 18758c2ecf20Sopenharmony_ci 18768c2ecf20Sopenharmony_ci if (oper && dump_schedule(skb, oper)) 18778c2ecf20Sopenharmony_ci goto options_error; 18788c2ecf20Sopenharmony_ci 18798c2ecf20Sopenharmony_ci if (!admin) 18808c2ecf20Sopenharmony_ci goto done; 18818c2ecf20Sopenharmony_ci 18828c2ecf20Sopenharmony_ci sched_nest = nla_nest_start_noflag(skb, TCA_TAPRIO_ATTR_ADMIN_SCHED); 18838c2ecf20Sopenharmony_ci if (!sched_nest) 18848c2ecf20Sopenharmony_ci goto options_error; 18858c2ecf20Sopenharmony_ci 18868c2ecf20Sopenharmony_ci if (dump_schedule(skb, admin)) 18878c2ecf20Sopenharmony_ci goto admin_error; 18888c2ecf20Sopenharmony_ci 18898c2ecf20Sopenharmony_ci nla_nest_end(skb, sched_nest); 18908c2ecf20Sopenharmony_ci 18918c2ecf20Sopenharmony_cidone: 18928c2ecf20Sopenharmony_ci rcu_read_unlock(); 18938c2ecf20Sopenharmony_ci 18948c2ecf20Sopenharmony_ci return nla_nest_end(skb, nest); 18958c2ecf20Sopenharmony_ci 18968c2ecf20Sopenharmony_ciadmin_error: 18978c2ecf20Sopenharmony_ci nla_nest_cancel(skb, sched_nest); 18988c2ecf20Sopenharmony_ci 18998c2ecf20Sopenharmony_cioptions_error: 19008c2ecf20Sopenharmony_ci nla_nest_cancel(skb, nest); 19018c2ecf20Sopenharmony_ci 19028c2ecf20Sopenharmony_cistart_error: 19038c2ecf20Sopenharmony_ci rcu_read_unlock(); 19048c2ecf20Sopenharmony_ci return -ENOSPC; 19058c2ecf20Sopenharmony_ci} 19068c2ecf20Sopenharmony_ci 19078c2ecf20Sopenharmony_cistatic struct Qdisc *taprio_leaf(struct Qdisc *sch, unsigned long cl) 19088c2ecf20Sopenharmony_ci{ 19098c2ecf20Sopenharmony_ci struct netdev_queue *dev_queue = taprio_queue_get(sch, cl); 19108c2ecf20Sopenharmony_ci 19118c2ecf20Sopenharmony_ci if (!dev_queue) 19128c2ecf20Sopenharmony_ci return NULL; 19138c2ecf20Sopenharmony_ci 19148c2ecf20Sopenharmony_ci return dev_queue->qdisc_sleeping; 19158c2ecf20Sopenharmony_ci} 19168c2ecf20Sopenharmony_ci 19178c2ecf20Sopenharmony_cistatic unsigned long taprio_find(struct Qdisc *sch, u32 classid) 19188c2ecf20Sopenharmony_ci{ 19198c2ecf20Sopenharmony_ci unsigned int ntx = TC_H_MIN(classid); 19208c2ecf20Sopenharmony_ci 19218c2ecf20Sopenharmony_ci if (!taprio_queue_get(sch, ntx)) 19228c2ecf20Sopenharmony_ci return 0; 19238c2ecf20Sopenharmony_ci return ntx; 19248c2ecf20Sopenharmony_ci} 19258c2ecf20Sopenharmony_ci 19268c2ecf20Sopenharmony_cistatic int taprio_dump_class(struct Qdisc *sch, unsigned long cl, 19278c2ecf20Sopenharmony_ci struct sk_buff *skb, struct tcmsg *tcm) 19288c2ecf20Sopenharmony_ci{ 19298c2ecf20Sopenharmony_ci struct netdev_queue *dev_queue = taprio_queue_get(sch, cl); 19308c2ecf20Sopenharmony_ci 19318c2ecf20Sopenharmony_ci tcm->tcm_parent = TC_H_ROOT; 19328c2ecf20Sopenharmony_ci tcm->tcm_handle |= TC_H_MIN(cl); 19338c2ecf20Sopenharmony_ci tcm->tcm_info = dev_queue->qdisc_sleeping->handle; 19348c2ecf20Sopenharmony_ci 19358c2ecf20Sopenharmony_ci return 0; 19368c2ecf20Sopenharmony_ci} 19378c2ecf20Sopenharmony_ci 19388c2ecf20Sopenharmony_cistatic int taprio_dump_class_stats(struct Qdisc *sch, unsigned long cl, 19398c2ecf20Sopenharmony_ci struct gnet_dump *d) 19408c2ecf20Sopenharmony_ci __releases(d->lock) 19418c2ecf20Sopenharmony_ci __acquires(d->lock) 19428c2ecf20Sopenharmony_ci{ 19438c2ecf20Sopenharmony_ci struct netdev_queue *dev_queue = taprio_queue_get(sch, cl); 19448c2ecf20Sopenharmony_ci 19458c2ecf20Sopenharmony_ci sch = dev_queue->qdisc_sleeping; 19468c2ecf20Sopenharmony_ci if (gnet_stats_copy_basic(&sch->running, d, NULL, &sch->bstats) < 0 || 19478c2ecf20Sopenharmony_ci qdisc_qstats_copy(d, sch) < 0) 19488c2ecf20Sopenharmony_ci return -1; 19498c2ecf20Sopenharmony_ci return 0; 19508c2ecf20Sopenharmony_ci} 19518c2ecf20Sopenharmony_ci 19528c2ecf20Sopenharmony_cistatic void taprio_walk(struct Qdisc *sch, struct qdisc_walker *arg) 19538c2ecf20Sopenharmony_ci{ 19548c2ecf20Sopenharmony_ci struct net_device *dev = qdisc_dev(sch); 19558c2ecf20Sopenharmony_ci unsigned long ntx; 19568c2ecf20Sopenharmony_ci 19578c2ecf20Sopenharmony_ci if (arg->stop) 19588c2ecf20Sopenharmony_ci return; 19598c2ecf20Sopenharmony_ci 19608c2ecf20Sopenharmony_ci arg->count = arg->skip; 19618c2ecf20Sopenharmony_ci for (ntx = arg->skip; ntx < dev->num_tx_queues; ntx++) { 19628c2ecf20Sopenharmony_ci if (arg->fn(sch, ntx + 1, arg) < 0) { 19638c2ecf20Sopenharmony_ci arg->stop = 1; 19648c2ecf20Sopenharmony_ci break; 19658c2ecf20Sopenharmony_ci } 19668c2ecf20Sopenharmony_ci arg->count++; 19678c2ecf20Sopenharmony_ci } 19688c2ecf20Sopenharmony_ci} 19698c2ecf20Sopenharmony_ci 19708c2ecf20Sopenharmony_cistatic struct netdev_queue *taprio_select_queue(struct Qdisc *sch, 19718c2ecf20Sopenharmony_ci struct tcmsg *tcm) 19728c2ecf20Sopenharmony_ci{ 19738c2ecf20Sopenharmony_ci return taprio_queue_get(sch, TC_H_MIN(tcm->tcm_parent)); 19748c2ecf20Sopenharmony_ci} 19758c2ecf20Sopenharmony_ci 19768c2ecf20Sopenharmony_cistatic const struct Qdisc_class_ops taprio_class_ops = { 19778c2ecf20Sopenharmony_ci .graft = taprio_graft, 19788c2ecf20Sopenharmony_ci .leaf = taprio_leaf, 19798c2ecf20Sopenharmony_ci .find = taprio_find, 19808c2ecf20Sopenharmony_ci .walk = taprio_walk, 19818c2ecf20Sopenharmony_ci .dump = taprio_dump_class, 19828c2ecf20Sopenharmony_ci .dump_stats = taprio_dump_class_stats, 19838c2ecf20Sopenharmony_ci .select_queue = taprio_select_queue, 19848c2ecf20Sopenharmony_ci}; 19858c2ecf20Sopenharmony_ci 19868c2ecf20Sopenharmony_cistatic struct Qdisc_ops taprio_qdisc_ops __read_mostly = { 19878c2ecf20Sopenharmony_ci .cl_ops = &taprio_class_ops, 19888c2ecf20Sopenharmony_ci .id = "taprio", 19898c2ecf20Sopenharmony_ci .priv_size = sizeof(struct taprio_sched), 19908c2ecf20Sopenharmony_ci .init = taprio_init, 19918c2ecf20Sopenharmony_ci .change = taprio_change, 19928c2ecf20Sopenharmony_ci .destroy = taprio_destroy, 19938c2ecf20Sopenharmony_ci .reset = taprio_reset, 19948c2ecf20Sopenharmony_ci .peek = taprio_peek, 19958c2ecf20Sopenharmony_ci .dequeue = taprio_dequeue, 19968c2ecf20Sopenharmony_ci .enqueue = taprio_enqueue, 19978c2ecf20Sopenharmony_ci .dump = taprio_dump, 19988c2ecf20Sopenharmony_ci .owner = THIS_MODULE, 19998c2ecf20Sopenharmony_ci}; 20008c2ecf20Sopenharmony_ci 20018c2ecf20Sopenharmony_cistatic struct notifier_block taprio_device_notifier = { 20028c2ecf20Sopenharmony_ci .notifier_call = taprio_dev_notifier, 20038c2ecf20Sopenharmony_ci}; 20048c2ecf20Sopenharmony_ci 20058c2ecf20Sopenharmony_cistatic int __init taprio_module_init(void) 20068c2ecf20Sopenharmony_ci{ 20078c2ecf20Sopenharmony_ci int err = register_netdevice_notifier(&taprio_device_notifier); 20088c2ecf20Sopenharmony_ci 20098c2ecf20Sopenharmony_ci if (err) 20108c2ecf20Sopenharmony_ci return err; 20118c2ecf20Sopenharmony_ci 20128c2ecf20Sopenharmony_ci return register_qdisc(&taprio_qdisc_ops); 20138c2ecf20Sopenharmony_ci} 20148c2ecf20Sopenharmony_ci 20158c2ecf20Sopenharmony_cistatic void __exit taprio_module_exit(void) 20168c2ecf20Sopenharmony_ci{ 20178c2ecf20Sopenharmony_ci unregister_qdisc(&taprio_qdisc_ops); 20188c2ecf20Sopenharmony_ci unregister_netdevice_notifier(&taprio_device_notifier); 20198c2ecf20Sopenharmony_ci} 20208c2ecf20Sopenharmony_ci 20218c2ecf20Sopenharmony_cimodule_init(taprio_module_init); 20228c2ecf20Sopenharmony_cimodule_exit(taprio_module_exit); 20238c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL"); 2024