From 3ae5cb7efaf1e33c2cf2e7959efea363b59fda84 Mon Sep 17 00:00:00 2001 From: Simon Wunderlich Date: Fri, 29 Mar 2024 20:16:23 +0100 Subject: [PATCH 01/15] batman-adv: Start new development cycle This version will contain all the (major or even only minor) changes for Linux 6.10. The version number isn't a semantic version number with major and minor information. It is just encoding the year of the expected publishing as Linux -rc1 and the number of published versions this year (starting at 0). Signed-off-by: Simon Wunderlich --- net/batman-adv/main.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/batman-adv/main.h b/net/batman-adv/main.h index 8ca854a75a32..3d4c36ae2e1a 100644 --- a/net/batman-adv/main.h +++ b/net/batman-adv/main.h @@ -13,7 +13,7 @@ #define BATADV_DRIVER_DEVICE "batman-adv" #ifndef BATADV_SOURCE_VERSION -#define BATADV_SOURCE_VERSION "2024.1" +#define BATADV_SOURCE_VERSION "2024.2" #endif /* B.A.T.M.A.N. parameters */ From 537db05da2ca8ccc1243c9dd1d0c148b84aa0432 Mon Sep 17 00:00:00 2001 From: Dmitry Antipov Date: Tue, 12 Mar 2024 21:16:28 +0300 Subject: [PATCH 02/15] batman-adv: prefer kfree_rcu() over call_rcu() with free-only callbacks Drop 'batadv_tt_local_entry_free_rcu()', 'batadv_tt_global_entry_free_rcu()' and 'batadv_tt_orig_list_entry_free_rcu()' in favor of 'kfree_rcu()' in 'batadv_tt_local_entry_release()', 'batadv_tt_global_entry_release()' and 'batadv_tt_orig_list_entry_release()', respectively. Signed-off-by: Dmitry Antipov Acked-by: Sven Eckelmann Signed-off-by: Simon Wunderlich --- net/batman-adv/translation-table.c | 47 ++---------------------------- 1 file changed, 3 insertions(+), 44 deletions(-) diff --git a/net/batman-adv/translation-table.c b/net/batman-adv/translation-table.c index b95c36765d04..0555cb611489 100644 --- a/net/batman-adv/translation-table.c +++ b/net/batman-adv/translation-table.c @@ -208,20 +208,6 @@ batadv_tt_global_hash_find(struct batadv_priv *bat_priv, const u8 *addr, return tt_global_entry; } -/** - * batadv_tt_local_entry_free_rcu() - free the tt_local_entry - * @rcu: rcu pointer of the tt_local_entry - */ -static void batadv_tt_local_entry_free_rcu(struct rcu_head *rcu) -{ - struct batadv_tt_local_entry *tt_local_entry; - - tt_local_entry = container_of(rcu, struct batadv_tt_local_entry, - common.rcu); - - kmem_cache_free(batadv_tl_cache, tt_local_entry); -} - /** * batadv_tt_local_entry_release() - release tt_local_entry from lists and queue * for free after rcu grace period @@ -236,7 +222,7 @@ static void batadv_tt_local_entry_release(struct kref *ref) batadv_softif_vlan_put(tt_local_entry->vlan); - call_rcu(&tt_local_entry->common.rcu, batadv_tt_local_entry_free_rcu); + kfree_rcu(tt_local_entry, common.rcu); } /** @@ -254,20 +240,6 @@ batadv_tt_local_entry_put(struct batadv_tt_local_entry *tt_local_entry) batadv_tt_local_entry_release); } -/** - * batadv_tt_global_entry_free_rcu() - free the tt_global_entry - * @rcu: rcu pointer of the tt_global_entry - */ -static void batadv_tt_global_entry_free_rcu(struct rcu_head *rcu) -{ - struct batadv_tt_global_entry *tt_global_entry; - - tt_global_entry = container_of(rcu, struct batadv_tt_global_entry, - common.rcu); - - kmem_cache_free(batadv_tg_cache, tt_global_entry); -} - /** * batadv_tt_global_entry_release() - release tt_global_entry from lists and * queue for free after rcu grace period @@ -282,7 +254,7 @@ void batadv_tt_global_entry_release(struct kref *ref) batadv_tt_global_del_orig_list(tt_global_entry); - call_rcu(&tt_global_entry->common.rcu, batadv_tt_global_entry_free_rcu); + kfree_rcu(tt_global_entry, common.rcu); } /** @@ -407,19 +379,6 @@ static void batadv_tt_global_size_dec(struct batadv_orig_node *orig_node, batadv_tt_global_size_mod(orig_node, vid, -1); } -/** - * batadv_tt_orig_list_entry_free_rcu() - free the orig_entry - * @rcu: rcu pointer of the orig_entry - */ -static void batadv_tt_orig_list_entry_free_rcu(struct rcu_head *rcu) -{ - struct batadv_tt_orig_list_entry *orig_entry; - - orig_entry = container_of(rcu, struct batadv_tt_orig_list_entry, rcu); - - kmem_cache_free(batadv_tt_orig_cache, orig_entry); -} - /** * batadv_tt_orig_list_entry_release() - release tt orig entry from lists and * queue for free after rcu grace period @@ -433,7 +392,7 @@ static void batadv_tt_orig_list_entry_release(struct kref *ref) refcount); batadv_orig_node_put(orig_entry->orig_node); - call_rcu(&orig_entry->rcu, batadv_tt_orig_list_entry_free_rcu); + kfree_rcu(orig_entry, rcu); } /** From 40dc8ab605894acae1473e434944924a22cfaaa0 Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Sat, 30 Mar 2024 15:54:38 +0000 Subject: [PATCH 03/15] batman-adv: bypass empty buckets in batadv_purge_orig_ref() Many syzbot reports are pointing to soft lockups in batadv_purge_orig_ref() [1] Root cause is unknown, but we can avoid spending too much time there and perhaps get more interesting reports. [1] watchdog: BUG: soft lockup - CPU#0 stuck for 27s! [kworker/u4:6:621] Modules linked in: irq event stamp: 6182794 hardirqs last enabled at (6182793): [] __local_bh_enable_ip+0x224/0x44c kernel/softirq.c:386 hardirqs last disabled at (6182794): [] __el1_irq arch/arm64/kernel/entry-common.c:533 [inline] hardirqs last disabled at (6182794): [] el1_interrupt+0x24/0x68 arch/arm64/kernel/entry-common.c:551 softirqs last enabled at (6182792): [] spin_unlock_bh include/linux/spinlock.h:396 [inline] softirqs last enabled at (6182792): [] batadv_purge_orig_ref+0x114c/0x1228 net/batman-adv/originator.c:1287 softirqs last disabled at (6182790): [] spin_lock_bh include/linux/spinlock.h:356 [inline] softirqs last disabled at (6182790): [] batadv_purge_orig_ref+0x164/0x1228 net/batman-adv/originator.c:1271 CPU: 0 PID: 621 Comm: kworker/u4:6 Not tainted 6.8.0-rc7-syzkaller-g707081b61156 #0 Hardware name: Google Google Compute Engine/Google Compute Engine, BIOS Google 02/29/2024 Workqueue: bat_events batadv_purge_orig pstate: 80400005 (Nzcv daif +PAN -UAO -TCO -DIT -SSBS BTYPE=--) pc : should_resched arch/arm64/include/asm/preempt.h:79 [inline] pc : __local_bh_enable_ip+0x228/0x44c kernel/softirq.c:388 lr : __local_bh_enable_ip+0x224/0x44c kernel/softirq.c:386 sp : ffff800099007970 x29: ffff800099007980 x28: 1fffe00018fce1bd x27: dfff800000000000 x26: ffff0000d2620008 x25: ffff0000c7e70de8 x24: 0000000000000001 x23: 1fffe00018e57781 x22: dfff800000000000 x21: ffff80008aab71c4 x20: ffff0001b40136c0 x19: ffff0000c72bbc08 x18: 1fffe0001a817bb0 x17: ffff800125414000 x16: ffff80008032116c x15: 0000000000000001 x14: 1fffe0001ee9d610 x13: 0000000000000000 x12: 0000000000000003 x11: 0000000000000000 x10: 0000000000ff0100 x9 : 0000000000000000 x8 : 00000000005e5789 x7 : ffff80008aab61dc x6 : 0000000000000000 x5 : 0000000000000000 x4 : 0000000000000001 x3 : 0000000000000000 x2 : 0000000000000006 x1 : 0000000000000080 x0 : ffff800125414000 Call trace: __daif_local_irq_enable arch/arm64/include/asm/irqflags.h:27 [inline] arch_local_irq_enable arch/arm64/include/asm/irqflags.h:49 [inline] __local_bh_enable_ip+0x228/0x44c kernel/softirq.c:386 __raw_spin_unlock_bh include/linux/spinlock_api_smp.h:167 [inline] _raw_spin_unlock_bh+0x3c/0x4c kernel/locking/spinlock.c:210 spin_unlock_bh include/linux/spinlock.h:396 [inline] batadv_purge_orig_ref+0x114c/0x1228 net/batman-adv/originator.c:1287 batadv_purge_orig+0x20/0x70 net/batman-adv/originator.c:1300 process_one_work+0x694/0x1204 kernel/workqueue.c:2633 process_scheduled_works kernel/workqueue.c:2706 [inline] worker_thread+0x938/0xef4 kernel/workqueue.c:2787 kthread+0x288/0x310 kernel/kthread.c:388 ret_from_fork+0x10/0x20 arch/arm64/kernel/entry.S:860 Sending NMI from CPU 0 to CPUs 1: NMI backtrace for cpu 1 CPU: 1 PID: 0 Comm: swapper/1 Not tainted 6.8.0-rc7-syzkaller-g707081b61156 #0 Hardware name: Google Google Compute Engine/Google Compute Engine, BIOS Google 02/29/2024 pstate: 80400005 (Nzcv daif +PAN -UAO -TCO -DIT -SSBS BTYPE=--) pc : arch_local_irq_enable+0x8/0xc arch/arm64/include/asm/irqflags.h:51 lr : default_idle_call+0xf8/0x128 kernel/sched/idle.c:103 sp : ffff800093a17d30 x29: ffff800093a17d30 x28: dfff800000000000 x27: 1ffff00012742fb4 x26: ffff80008ec9d000 x25: 0000000000000000 x24: 0000000000000002 x23: 1ffff00011d93a74 x22: ffff80008ec9d3a0 x21: 0000000000000000 x20: ffff0000c19dbc00 x19: ffff8000802d0fd8 x18: 1fffe00036804396 x17: ffff80008ec9d000 x16: ffff8000802d089c x15: 0000000000000001 x14: 1fffe00036805f10 x13: 0000000000000000 x12: 0000000000000003 x11: 0000000000000001 x10: 0000000000000003 x9 : 0000000000000000 x8 : 00000000000ce8d1 x7 : ffff8000804609e4 x6 : 0000000000000000 x5 : 0000000000000001 x4 : 0000000000000001 x3 : ffff80008ad6aac0 x2 : 0000000000000000 x1 : ffff80008aedea60 x0 : ffff800125436000 Call trace: __daif_local_irq_enable arch/arm64/include/asm/irqflags.h:27 [inline] arch_local_irq_enable+0x8/0xc arch/arm64/include/asm/irqflags.h:49 cpuidle_idle_call kernel/sched/idle.c:170 [inline] do_idle+0x1f0/0x4e8 kernel/sched/idle.c:312 cpu_startup_entry+0x5c/0x74 kernel/sched/idle.c:410 secondary_start_kernel+0x198/0x1c0 arch/arm64/kernel/smp.c:272 __secondary_switched+0xb8/0xbc arch/arm64/kernel/head.S:404 Signed-off-by: Eric Dumazet Signed-off-by: Sven Eckelmann Signed-off-by: Simon Wunderlich --- net/batman-adv/originator.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/net/batman-adv/originator.c b/net/batman-adv/originator.c index 71c143d4b6d0..ac74f6ead62d 100644 --- a/net/batman-adv/originator.c +++ b/net/batman-adv/originator.c @@ -1266,6 +1266,8 @@ void batadv_purge_orig_ref(struct batadv_priv *bat_priv) /* for all origins... */ for (i = 0; i < hash->size; i++) { head = &hash->table[i]; + if (hlist_empty(head)) + continue; list_lock = &hash->list_locks[i]; spin_lock_bh(list_lock); From a9b2d55a8f1e9652b800c84c3986442ca6d1fb50 Mon Sep 17 00:00:00 2001 From: Breno Leitao Date: Thu, 4 Apr 2024 05:52:51 -0700 Subject: [PATCH 04/15] ip6_vti: Do not use custom stat allocator With commit 34d21de99cea9 ("net: Move {l,t,d}stats allocation to core and convert veth & vrf"), stats allocation could be done on net core instead of in this driver. With this new approach, the driver doesn't have to bother with error handling (allocation failure checking, making sure free happens in the right spot, etc). This is core responsibility now. Remove the allocation in the ip6_vti and leverage the network core allocation instead. Signed-off-by: Breno Leitao Reviewed-by: David Ahern Signed-off-by: David S. Miller --- net/ipv6/ip6_vti.c | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/net/ipv6/ip6_vti.c b/net/ipv6/ip6_vti.c index 7f4f976aa24a..71c749bb4b3e 100644 --- a/net/ipv6/ip6_vti.c +++ b/net/ipv6/ip6_vti.c @@ -174,11 +174,6 @@ vti6_tnl_unlink(struct vti6_net *ip6n, struct ip6_tnl *t) } } -static void vti6_dev_free(struct net_device *dev) -{ - free_percpu(dev->tstats); -} - static int vti6_tnl_create2(struct net_device *dev) { struct ip6_tnl *t = netdev_priv(dev); @@ -907,9 +902,8 @@ static void vti6_dev_setup(struct net_device *dev) { dev->netdev_ops = &vti6_netdev_ops; dev->header_ops = &ip_tunnel_header_ops; - dev->needs_free_netdev = true; - dev->priv_destructor = vti6_dev_free; + dev->pcpu_stat_type = NETDEV_PCPU_STAT_TSTATS; dev->type = ARPHRD_TUNNEL6; dev->min_mtu = IPV4_MIN_MTU; dev->max_mtu = IP_MAX_MTU - sizeof(struct ipv6hdr); @@ -931,9 +925,6 @@ static inline int vti6_dev_init_gen(struct net_device *dev) t->dev = dev; t->net = dev_net(dev); - dev->tstats = netdev_alloc_pcpu_stats(struct pcpu_sw_netstats); - if (!dev->tstats) - return -ENOMEM; netdev_hold(dev, &t->dev_tracker, GFP_KERNEL); netdev_lockdep_set_classes(dev); return 0; From b2c919c108abda11ebf16c4e31cf687a4db95a6f Mon Sep 17 00:00:00 2001 From: Breno Leitao Date: Thu, 4 Apr 2024 05:52:52 -0700 Subject: [PATCH 05/15] ip6_vti: Remove generic .ndo_get_stats64 Commit 3e2f544dd8a33 ("net: get stats64 if device if driver is configured") moved the callback to dev_get_tstats64() to net core, so, unless the driver is doing some custom stats collection, it does not need to set .ndo_get_stats64. Since this driver is now relying in NETDEV_PCPU_STAT_TSTATS, then, it doesn't need to set the dev_get_tstats64() generic .ndo_get_stats64 function pointer. Signed-off-by: Breno Leitao Reviewed-by: David Ahern Signed-off-by: David S. Miller --- net/ipv6/ip6_vti.c | 1 - 1 file changed, 1 deletion(-) diff --git a/net/ipv6/ip6_vti.c b/net/ipv6/ip6_vti.c index 71c749bb4b3e..4d68a0777b0c 100644 --- a/net/ipv6/ip6_vti.c +++ b/net/ipv6/ip6_vti.c @@ -887,7 +887,6 @@ static const struct net_device_ops vti6_netdev_ops = { .ndo_uninit = vti6_dev_uninit, .ndo_start_xmit = vti6_tnl_xmit, .ndo_siocdevprivate = vti6_siocdevprivate, - .ndo_get_stats64 = dev_get_tstats64, .ndo_get_iflink = ip6_tnl_get_iflink, }; From 802e12ff9cbff3bafb0b6f0fe79cff04dce9de16 Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Thu, 4 Apr 2024 13:07:51 +0000 Subject: [PATCH 06/15] inet: frags: delay fqdir_free_fn() fqdir_free_fn() is using very expensive rcu_barrier() When one netns is dismantled, we often call fqdir_exit() multiple times, typically lauching fqdir_free_fn() twice. Delaying by one second fqdir_free_fn() helps to reduce the number of rcu_barrier() calls, and lock contention on rcu_state.barrier_mutex. Signed-off-by: Eric Dumazet Signed-off-by: David S. Miller --- net/ipv4/inet_fragment.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/net/ipv4/inet_fragment.c b/net/ipv4/inet_fragment.c index c88c9034d630..faaec92a46ac 100644 --- a/net/ipv4/inet_fragment.c +++ b/net/ipv4/inet_fragment.c @@ -175,7 +175,7 @@ static void fqdir_free_fn(struct work_struct *work) } } -static DECLARE_WORK(fqdir_free_work, fqdir_free_fn); +static DECLARE_DELAYED_WORK(fqdir_free_work, fqdir_free_fn); static void fqdir_work_fn(struct work_struct *work) { @@ -184,7 +184,7 @@ static void fqdir_work_fn(struct work_struct *work) rhashtable_free_and_destroy(&fqdir->rhashtable, inet_frags_free_cb, NULL); if (llist_add(&fqdir->free_list, &fqdir_free_list)) - queue_work(system_wq, &fqdir_free_work); + queue_delayed_work(system_wq, &fqdir_free_work, HZ); } int fqdir_init(struct fqdir **fqdirp, struct inet_frags *f, struct net *net) From eec53cc38c4aba41bc3fabd0a75bc30c2c8ea332 Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Thu, 4 Apr 2024 13:24:13 +0000 Subject: [PATCH 07/15] ipv6: remove RTNL protection from ip6addrlbl_dump() No longer hold RTNL while calling ip6addrlbl_dump() ("ip addrlabel show") ip6addrlbl_dump() was already mostly relying on RCU anyway. Add READ_ONCE()/WRITE_ONCE() annotations around net->ipv6.ip6addrlbl_table.seq Note that ifal_seq value is currently ignored in iproute2, and a bit weak. We might user later cb->seq / nl_dump_check_consistent() protocol if needed. Also change return value for a completed dump, so that NLMSG_DONE can be appended to current skb, saving one recvmsg() system call. v2: read net->ipv6.ip6addrlbl_table.seq once, (David Ahern) Signed-off-by: Eric Dumazet Link:https://lore.kernel.org/netdev/67f5cb70-14a4-4455-8372-f039da2f15c2@kernel.org/ Reviewed-by: David Ahern Signed-off-by: David S. Miller --- net/ipv6/addrlabel.c | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/net/ipv6/addrlabel.c b/net/ipv6/addrlabel.c index 17ac45aa7194..acd70b5992a7 100644 --- a/net/ipv6/addrlabel.c +++ b/net/ipv6/addrlabel.c @@ -234,7 +234,8 @@ static int __ip6addrlbl_add(struct net *net, struct ip6addrlbl_entry *newp, hlist_add_head_rcu(&newp->list, &net->ipv6.ip6addrlbl_table.head); out: if (!ret) - net->ipv6.ip6addrlbl_table.seq++; + WRITE_ONCE(net->ipv6.ip6addrlbl_table.seq, + net->ipv6.ip6addrlbl_table.seq + 1); return ret; } @@ -445,7 +446,7 @@ static void ip6addrlbl_putmsg(struct nlmsghdr *nlh, }; static int ip6addrlbl_fill(struct sk_buff *skb, - struct ip6addrlbl_entry *p, + const struct ip6addrlbl_entry *p, u32 lseq, u32 portid, u32 seq, int event, unsigned int flags) @@ -498,7 +499,8 @@ static int ip6addrlbl_dump(struct sk_buff *skb, struct netlink_callback *cb) struct net *net = sock_net(skb->sk); struct ip6addrlbl_entry *p; int idx = 0, s_idx = cb->args[0]; - int err; + int err = 0; + u32 lseq; if (cb->strict_check) { err = ip6addrlbl_valid_dump_req(nlh, cb->extack); @@ -507,10 +509,11 @@ static int ip6addrlbl_dump(struct sk_buff *skb, struct netlink_callback *cb) } rcu_read_lock(); + lseq = READ_ONCE(net->ipv6.ip6addrlbl_table.seq); hlist_for_each_entry_rcu(p, &net->ipv6.ip6addrlbl_table.head, list) { if (idx >= s_idx) { err = ip6addrlbl_fill(skb, p, - net->ipv6.ip6addrlbl_table.seq, + lseq, NETLINK_CB(cb->skb).portid, nlh->nlmsg_seq, RTM_NEWADDRLABEL, @@ -522,7 +525,7 @@ static int ip6addrlbl_dump(struct sk_buff *skb, struct netlink_callback *cb) } rcu_read_unlock(); cb->args[0] = idx; - return skb->len; + return err; } static inline int ip6addrlbl_msgsize(void) @@ -614,7 +617,7 @@ static int ip6addrlbl_get(struct sk_buff *in_skb, struct nlmsghdr *nlh, rcu_read_lock(); p = __ipv6_addr_label(net, addr, ipv6_addr_type(addr), ifal->ifal_index); - lseq = net->ipv6.ip6addrlbl_table.seq; + lseq = READ_ONCE(net->ipv6.ip6addrlbl_table.seq); if (p) err = ip6addrlbl_fill(skb, p, lseq, NETLINK_CB(in_skb).portid, @@ -647,6 +650,7 @@ int __init ipv6_addr_label_rtnl_register(void) return ret; ret = rtnl_register_module(THIS_MODULE, PF_INET6, RTM_GETADDRLABEL, ip6addrlbl_get, - ip6addrlbl_dump, RTNL_FLAG_DOIT_UNLOCKED); + ip6addrlbl_dump, RTNL_FLAG_DOIT_UNLOCKED | + RTNL_FLAG_DUMP_UNLOCKED); return ret; } From 5a66cda52d7daccc88aa358872816c35093759f9 Mon Sep 17 00:00:00 2001 From: Alexander Lobakin Date: Thu, 4 Apr 2024 18:03:02 +0200 Subject: [PATCH 08/15] ip_tunnel: harden copying IP tunnel params to userspace Structures which are about to be copied to userspace shouldn't have uninitialized fields or paddings. memset() the whole &ip_tunnel_parm in ip_tunnel_parm_to_user() before filling it with the kernel data. The compilers will hopefully combine writes to it. Fixes: 117aef12a7b1 ("ip_tunnel: use a separate struct to store tunnel params in the kernel") Reported-by: Dan Carpenter Closes: https://lore.kernel.org/netdev/5f63dd25-de94-4ca3-84e6-14095953db13@moroto.mountain Signed-off-by: Alexander Lobakin Reviewed-by: Simon Horman Signed-off-by: David S. Miller --- net/ipv4/ip_tunnel.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/net/ipv4/ip_tunnel.c b/net/ipv4/ip_tunnel.c index f65170a28106..177f40c3a8e8 100644 --- a/net/ipv4/ip_tunnel.c +++ b/net/ipv4/ip_tunnel.c @@ -1034,6 +1034,8 @@ bool ip_tunnel_parm_to_user(void __user *data, struct ip_tunnel_parm_kern *kp) !ip_tunnel_flags_is_be16_compat(kp->o_flags)) return false; + memset(&p, 0, sizeof(p)); + strscpy(p.name, kp->name); p.link = kp->link; p.i_flags = ip_tunnel_flags_to_be16(kp->i_flags); From db77cdc69684890b37ff2bb9fb9e0be2ba4d6efe Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Thu, 4 Apr 2024 16:46:04 +0000 Subject: [PATCH 09/15] net: dqs: use sysfs_emit() in favor of sprintf() Commit 6025b9135f7a ("net: dqs: add NIC stall detector based on BQL") added three sysfs files. Use the recommended sysfs_emit() helper. Signed-off-by: Eric Dumazet Cc: Jakub Kicinski Cc: Breno Leitao Signed-off-by: David S. Miller --- net/core/net-sysfs.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/net/core/net-sysfs.c b/net/core/net-sysfs.c index e3d7a8cfa20b..ff3ee45be64a 100644 --- a/net/core/net-sysfs.c +++ b/net/core/net-sysfs.c @@ -1419,7 +1419,7 @@ static ssize_t bql_show_stall_thrs(struct netdev_queue *queue, char *buf) { struct dql *dql = &queue->dql; - return sprintf(buf, "%u\n", jiffies_to_msecs(dql->stall_thrs)); + return sysfs_emit(buf, "%u\n", jiffies_to_msecs(dql->stall_thrs)); } static ssize_t bql_set_stall_thrs(struct netdev_queue *queue, @@ -1451,7 +1451,7 @@ static struct netdev_queue_attribute bql_stall_thrs_attribute __ro_after_init = static ssize_t bql_show_stall_max(struct netdev_queue *queue, char *buf) { - return sprintf(buf, "%u\n", READ_ONCE(queue->dql.stall_max)); + return sysfs_emit(buf, "%u\n", READ_ONCE(queue->dql.stall_max)); } static ssize_t bql_set_stall_max(struct netdev_queue *queue, @@ -1468,7 +1468,7 @@ static ssize_t bql_show_stall_cnt(struct netdev_queue *queue, char *buf) { struct dql *dql = &queue->dql; - return sprintf(buf, "%lu\n", dql->stall_cnt); + return sysfs_emit(buf, "%lu\n", dql->stall_cnt); } static struct netdev_queue_attribute bql_stall_cnt_attribute __ro_after_init = From 9d56c248e5030d17ea9cd132634e86fdf0622d0e Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Thu, 4 Apr 2024 20:55:57 +0300 Subject: [PATCH 10/15] net: mdio-gpio: Use device_is_compatible() Replace open coded variant of device_is_compatible(). Signed-off-by: Andy Shevchenko Reviewed-by: Andrew Lunn Signed-off-by: David S. Miller --- drivers/net/mdio/mdio-gpio.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/net/mdio/mdio-gpio.c b/drivers/net/mdio/mdio-gpio.c index 778db310a28d..82088741debd 100644 --- a/drivers/net/mdio/mdio-gpio.c +++ b/drivers/net/mdio/mdio-gpio.c @@ -132,8 +132,7 @@ static struct mii_bus *mdio_gpio_bus_init(struct device *dev, new_bus->phy_ignore_ta_mask = pdata->phy_ignore_ta_mask; } - if (dev->of_node && - of_device_is_compatible(dev->of_node, "microchip,mdio-smi0")) { + if (device_is_compatible(dev, "microchip,mdio-smi0")) { bitbang->ctrl.op_c22_read = 0; bitbang->ctrl.op_c22_write = 0; bitbang->ctrl.override_op_c22 = 1; From b86761ff6374813cdf64ffd6b95ddd1813c435d8 Mon Sep 17 00:00:00 2001 From: Jakub Kicinski Date: Thu, 4 Apr 2024 19:45:22 -0700 Subject: [PATCH 11/15] selftests: net: add scaffolding for Netlink tests in Python Add glue code for accessing the YNL library which lives under tools/net and YAML spec files from under Documentation/. Automatically figure out if tests are run in tree or not. Since we'll want to use this library both from net and drivers/net test targets make the library a target as well, and automatically include it when net or drivers/net are included. Making net/lib a target ensures that we end up with only one copy of it, and saves us some path guessing. Add a tiny bit of formatting support to be able to output KTAP from the start. Reviewed-by: Petr Machata Signed-off-by: Jakub Kicinski Signed-off-by: David S. Miller --- tools/testing/selftests/Makefile | 9 +- tools/testing/selftests/net/lib/Makefile | 8 ++ .../testing/selftests/net/lib/py/__init__.py | 6 ++ tools/testing/selftests/net/lib/py/consts.py | 9 ++ tools/testing/selftests/net/lib/py/ksft.py | 96 +++++++++++++++++++ tools/testing/selftests/net/lib/py/utils.py | 47 +++++++++ tools/testing/selftests/net/lib/py/ynl.py | 49 ++++++++++ 7 files changed, 223 insertions(+), 1 deletion(-) create mode 100644 tools/testing/selftests/net/lib/Makefile create mode 100644 tools/testing/selftests/net/lib/py/__init__.py create mode 100644 tools/testing/selftests/net/lib/py/consts.py create mode 100644 tools/testing/selftests/net/lib/py/ksft.py create mode 100644 tools/testing/selftests/net/lib/py/utils.py create mode 100644 tools/testing/selftests/net/lib/py/ynl.py diff --git a/tools/testing/selftests/Makefile b/tools/testing/selftests/Makefile index e1504833654d..f533eb7054fe 100644 --- a/tools/testing/selftests/Makefile +++ b/tools/testing/selftests/Makefile @@ -116,6 +116,13 @@ TARGETS += zram TARGETS_HOTPLUG = cpu-hotplug TARGETS_HOTPLUG += memory-hotplug +# Networking tests want the net/lib target, include it automatically +ifneq ($(filter net,$(TARGETS)),) +ifeq ($(filter net/lib,$(TARGETS)),) + INSTALL_DEP_TARGETS := net/lib +endif +endif + # User can optionally provide a TARGETS skiplist. By default we skip # BPF since it has cutting edge build time dependencies which require # more effort to install. @@ -245,7 +252,7 @@ ifdef INSTALL_PATH install -m 744 run_kselftest.sh $(INSTALL_PATH)/ rm -f $(TEST_LIST) @ret=1; \ - for TARGET in $(TARGETS); do \ + for TARGET in $(TARGETS) $(INSTALL_DEP_TARGETS); do \ BUILD_TARGET=$$BUILD/$$TARGET; \ $(MAKE) OUTPUT=$$BUILD_TARGET -C $$TARGET install \ INSTALL_PATH=$(INSTALL_PATH)/$$TARGET \ diff --git a/tools/testing/selftests/net/lib/Makefile b/tools/testing/selftests/net/lib/Makefile new file mode 100644 index 000000000000..48557e6250dd --- /dev/null +++ b/tools/testing/selftests/net/lib/Makefile @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: GPL-2.0 + +TEST_FILES := ../../../../../Documentation/netlink/specs +TEST_FILES += ../../../../net/ynl + +TEST_INCLUDES := $(wildcard py/*.py) + +include ../../lib.mk diff --git a/tools/testing/selftests/net/lib/py/__init__.py b/tools/testing/selftests/net/lib/py/__init__.py new file mode 100644 index 000000000000..7823b5c1f8d7 --- /dev/null +++ b/tools/testing/selftests/net/lib/py/__init__.py @@ -0,0 +1,6 @@ +# SPDX-License-Identifier: GPL-2.0 + +from .consts import KSRC +from .ksft import * +from .utils import * +from .ynl import NlError, YnlFamily, EthtoolFamily, NetdevFamily, RtnlFamily diff --git a/tools/testing/selftests/net/lib/py/consts.py b/tools/testing/selftests/net/lib/py/consts.py new file mode 100644 index 000000000000..f518ce79d82c --- /dev/null +++ b/tools/testing/selftests/net/lib/py/consts.py @@ -0,0 +1,9 @@ +# SPDX-License-Identifier: GPL-2.0 + +import sys +from pathlib import Path + +KSFT_DIR = (Path(__file__).parent / "../../..").resolve() +KSRC = (Path(__file__).parent / "../../../../../..").resolve() + +KSFT_MAIN_NAME = Path(sys.argv[0]).with_suffix("").name diff --git a/tools/testing/selftests/net/lib/py/ksft.py b/tools/testing/selftests/net/lib/py/ksft.py new file mode 100644 index 000000000000..c7210525981c --- /dev/null +++ b/tools/testing/selftests/net/lib/py/ksft.py @@ -0,0 +1,96 @@ +# SPDX-License-Identifier: GPL-2.0 + +import builtins +from .consts import KSFT_MAIN_NAME + +KSFT_RESULT = None + + +class KsftSkipEx(Exception): + pass + + +class KsftXfailEx(Exception): + pass + + +def ksft_pr(*objs, **kwargs): + print("#", *objs, **kwargs) + + +def ksft_eq(a, b, comment=""): + global KSFT_RESULT + if a != b: + KSFT_RESULT = False + ksft_pr("Check failed", a, "!=", b, comment) + + +def ksft_true(a, comment=""): + global KSFT_RESULT + if not a: + KSFT_RESULT = False + ksft_pr("Check failed", a, "does not eval to True", comment) + + +def ksft_in(a, b, comment=""): + global KSFT_RESULT + if a not in b: + KSFT_RESULT = False + ksft_pr("Check failed", a, "not in", b, comment) + + +def ksft_ge(a, b, comment=""): + global KSFT_RESULT + if a < b: + KSFT_RESULT = False + ksft_pr("Check failed", a, "<", b, comment) + + +def ktap_result(ok, cnt=1, case="", comment=""): + res = "" + if not ok: + res += "not " + res += "ok " + res += str(cnt) + " " + res += KSFT_MAIN_NAME + if case: + res += "." + str(case.__name__) + if comment: + res += " # " + comment + print(res) + + +def ksft_run(cases, args=()): + totals = {"pass": 0, "fail": 0, "skip": 0, "xfail": 0} + + print("KTAP version 1") + print("1.." + str(len(cases))) + + global KSFT_RESULT + cnt = 0 + for case in cases: + KSFT_RESULT = True + cnt += 1 + try: + case(*args) + except KsftSkipEx as e: + ktap_result(True, cnt, case, comment="SKIP " + str(e)) + totals['skip'] += 1 + continue + except KsftXfailEx as e: + ktap_result(True, cnt, case, comment="XFAIL " + str(e)) + totals['xfail'] += 1 + continue + except Exception as e: + for line in str(e).split('\n'): + ksft_pr("Exception|", line) + ktap_result(False, cnt, case) + totals['fail'] += 1 + continue + + ktap_result(KSFT_RESULT, cnt, case) + totals['pass'] += 1 + + print( + f"# Totals: pass:{totals['pass']} fail:{totals['fail']} xfail:{totals['xfail']} xpass:0 skip:{totals['skip']} error:0" + ) diff --git a/tools/testing/selftests/net/lib/py/utils.py b/tools/testing/selftests/net/lib/py/utils.py new file mode 100644 index 000000000000..f0d425731fd4 --- /dev/null +++ b/tools/testing/selftests/net/lib/py/utils.py @@ -0,0 +1,47 @@ +# SPDX-License-Identifier: GPL-2.0 + +import json as _json +import subprocess + +class cmd: + def __init__(self, comm, shell=True, fail=True, ns=None, background=False): + if ns: + if isinstance(ns, NetNS): + ns = ns.name + comm = f'ip netns exec {ns} ' + comm + + self.stdout = None + self.stderr = None + self.ret = None + + self.comm = comm + self.proc = subprocess.Popen(comm, shell=shell, stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + if not background: + self.process(terminate=False, fail=fail) + + def process(self, terminate=True, fail=None): + if terminate: + self.proc.terminate() + stdout, stderr = self.proc.communicate() + self.stdout = stdout.decode("utf-8") + self.stderr = stderr.decode("utf-8") + self.proc.stdout.close() + self.proc.stderr.close() + self.ret = self.proc.returncode + + if self.proc.returncode != 0 and fail: + if len(stderr) > 0 and stderr[-1] == "\n": + stderr = stderr[:-1] + raise Exception("Command failed: %s\n%s" % (self.proc.args, stderr)) + + +def ip(args, json=None, ns=None): + cmd_str = "ip " + if json: + cmd_str += '-j ' + cmd_str += args + cmd_obj = cmd(cmd_str, ns=ns) + if json: + return _json.loads(cmd_obj.stdout) + return cmd_obj diff --git a/tools/testing/selftests/net/lib/py/ynl.py b/tools/testing/selftests/net/lib/py/ynl.py new file mode 100644 index 000000000000..1ace58370c06 --- /dev/null +++ b/tools/testing/selftests/net/lib/py/ynl.py @@ -0,0 +1,49 @@ +# SPDX-License-Identifier: GPL-2.0 + +import sys +from pathlib import Path +from .consts import KSRC, KSFT_DIR +from .ksft import ksft_pr, ktap_result + +# Resolve paths +try: + if (KSFT_DIR / "kselftest-list.txt").exists(): + # Running in "installed" selftests + tools_full_path = KSFT_DIR + SPEC_PATH = KSFT_DIR / "net/lib/specs" + + sys.path.append(tools_full_path.as_posix()) + from net.lib.ynl.lib import YnlFamily, NlError + else: + # Running in tree + tools_full_path = KSRC / "tools" + SPEC_PATH = KSRC / "Documentation/netlink/specs" + + sys.path.append(tools_full_path.as_posix()) + from net.ynl.lib import YnlFamily, NlError +except ModuleNotFoundError as e: + ksft_pr("Failed importing `ynl` library from kernel sources") + ksft_pr(str(e)) + ktap_result(True, comment="SKIP") + sys.exit(4) + +# +# Wrapper classes, loading the right specs +# Set schema='' to avoid jsonschema validation, it's slow +# +class EthtoolFamily(YnlFamily): + def __init__(self): + super().__init__((SPEC_PATH / Path('ethtool.yaml')).as_posix(), + schema='') + + +class RtnlFamily(YnlFamily): + def __init__(self): + super().__init__((SPEC_PATH / Path('rt_link.yaml')).as_posix(), + schema='') + + +class NetdevFamily(YnlFamily): + def __init__(self): + super().__init__((SPEC_PATH / Path('netdev.yaml')).as_posix(), + schema='') From 796c8c7fd257abe5c1e1ee22d4344123cd614f9f Mon Sep 17 00:00:00 2001 From: Jakub Kicinski Date: Thu, 4 Apr 2024 19:45:23 -0700 Subject: [PATCH 12/15] selftests: nl_netdev: add a trivial Netlink netdev test Add a trivial test using YNL. $ ./tools/testing/selftests/net/nl_netdev.py KTAP version 1 1..2 ok 1 nl_netdev.empty_check ok 2 nl_netdev.lo_check Instantiate the family once, it takes longer than the test itself. Reviewed-by: Petr Machata Signed-off-by: Jakub Kicinski Signed-off-by: David S. Miller --- tools/testing/selftests/net/Makefile | 1 + tools/testing/selftests/net/nl_netdev.py | 24 ++++++++++++++++++++++++ 2 files changed, 25 insertions(+) create mode 100755 tools/testing/selftests/net/nl_netdev.py diff --git a/tools/testing/selftests/net/Makefile b/tools/testing/selftests/net/Makefile index cb418a2346bc..5e34c93aa51b 100644 --- a/tools/testing/selftests/net/Makefile +++ b/tools/testing/selftests/net/Makefile @@ -34,6 +34,7 @@ TEST_PROGS += gre_gso.sh TEST_PROGS += cmsg_so_mark.sh TEST_PROGS += cmsg_time.sh cmsg_ipv6.sh TEST_PROGS += netns-name.sh +TEST_PROGS += nl_netdev.py TEST_PROGS += srv6_end_dt46_l3vpn_test.sh TEST_PROGS += srv6_end_dt4_l3vpn_test.sh TEST_PROGS += srv6_end_dt6_l3vpn_test.sh diff --git a/tools/testing/selftests/net/nl_netdev.py b/tools/testing/selftests/net/nl_netdev.py new file mode 100755 index 000000000000..2b8b488fb419 --- /dev/null +++ b/tools/testing/selftests/net/nl_netdev.py @@ -0,0 +1,24 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: GPL-2.0 + +from lib.py import ksft_run, ksft_pr, ksft_eq, ksft_ge, NetdevFamily + + +def empty_check(nf) -> None: + devs = nf.dev_get({}, dump=True) + ksft_ge(len(devs), 1) + + +def lo_check(nf) -> None: + lo_info = nf.dev_get({"ifindex": 1}) + ksft_eq(len(lo_info['xdp-features']), 0) + ksft_eq(len(lo_info['xdp-rx-metadata-features']), 0) + + +def main() -> None: + nf = NetdevFamily() + ksft_run([empty_check, lo_check], args=(nf, )) + + +if __name__ == "__main__": + main() From f216306bfb602b449cb2c49cf03870fb8761d146 Mon Sep 17 00:00:00 2001 From: Jakub Kicinski Date: Thu, 4 Apr 2024 19:45:24 -0700 Subject: [PATCH 13/15] netdevsim: report stats by default, like a real device Real devices should implement qstats. Devices which support pause or FEC configuration should also report the relevant stats. nsim was missing FEC stats completely, some of the qstats and pause stats required toggling a debugfs knob. Note that the tests which used pause always initialize the setting so they shouldn't be affected by the different starting value. Signed-off-by: Jakub Kicinski Signed-off-by: David S. Miller --- drivers/net/netdevsim/ethtool.c | 11 ++++++++ drivers/net/netdevsim/netdev.c | 49 +++++++++++++++++++++++++++++++++ 2 files changed, 60 insertions(+) diff --git a/drivers/net/netdevsim/ethtool.c b/drivers/net/netdevsim/ethtool.c index bd546d4d26c6..3f9c9327f149 100644 --- a/drivers/net/netdevsim/ethtool.c +++ b/drivers/net/netdevsim/ethtool.c @@ -140,6 +140,13 @@ nsim_set_fecparam(struct net_device *dev, struct ethtool_fecparam *fecparam) return 0; } +static void +nsim_get_fec_stats(struct net_device *dev, struct ethtool_fec_stats *fec_stats) +{ + fec_stats->corrected_blocks.total = 123; + fec_stats->uncorrectable_blocks.total = 4; +} + static int nsim_get_ts_info(struct net_device *dev, struct ethtool_ts_info *info) { @@ -163,6 +170,7 @@ static const struct ethtool_ops nsim_ethtool_ops = { .set_channels = nsim_set_channels, .get_fecparam = nsim_get_fecparam, .set_fecparam = nsim_set_fecparam, + .get_fec_stats = nsim_get_fec_stats, .get_ts_info = nsim_get_ts_info, }; @@ -182,6 +190,9 @@ void nsim_ethtool_init(struct netdevsim *ns) nsim_ethtool_ring_init(ns); + ns->ethtool.pauseparam.report_stats_rx = true; + ns->ethtool.pauseparam.report_stats_tx = true; + ns->ethtool.fec.fec = ETHTOOL_FEC_NONE; ns->ethtool.fec.active_fec = ETHTOOL_FEC_NONE; diff --git a/drivers/net/netdevsim/netdev.c b/drivers/net/netdevsim/netdev.c index 8330bc0bcb7e..d7ba447db17c 100644 --- a/drivers/net/netdevsim/netdev.c +++ b/drivers/net/netdevsim/netdev.c @@ -19,6 +19,7 @@ #include #include #include +#include #include #include #include @@ -330,6 +331,53 @@ static const struct net_device_ops nsim_vf_netdev_ops = { .ndo_set_features = nsim_set_features, }; +/* We don't have true per-queue stats, yet, so do some random fakery here. + * Only report stuff for queue 0. + */ +static void nsim_get_queue_stats_rx(struct net_device *dev, int idx, + struct netdev_queue_stats_rx *stats) +{ + struct rtnl_link_stats64 rtstats = {}; + + if (!idx) + nsim_get_stats64(dev, &rtstats); + + stats->packets = rtstats.rx_packets - !!rtstats.rx_packets; + stats->bytes = rtstats.rx_bytes; +} + +static void nsim_get_queue_stats_tx(struct net_device *dev, int idx, + struct netdev_queue_stats_tx *stats) +{ + struct rtnl_link_stats64 rtstats = {}; + + if (!idx) + nsim_get_stats64(dev, &rtstats); + + stats->packets = rtstats.tx_packets - !!rtstats.tx_packets; + stats->bytes = rtstats.tx_bytes; +} + +static void nsim_get_base_stats(struct net_device *dev, + struct netdev_queue_stats_rx *rx, + struct netdev_queue_stats_tx *tx) +{ + struct rtnl_link_stats64 rtstats = {}; + + nsim_get_stats64(dev, &rtstats); + + rx->packets = !!rtstats.rx_packets; + rx->bytes = 0; + tx->packets = !!rtstats.tx_packets; + tx->bytes = 0; +} + +static const struct netdev_stat_ops nsim_stat_ops = { + .get_queue_stats_tx = nsim_get_queue_stats_tx, + .get_queue_stats_rx = nsim_get_queue_stats_rx, + .get_base_stats = nsim_get_base_stats, +}; + static void nsim_setup(struct net_device *dev) { ether_setup(dev); @@ -360,6 +408,7 @@ static int nsim_init_netdevsim(struct netdevsim *ns) ns->phc = phc; ns->netdev->netdev_ops = &nsim_netdev_ops; + ns->netdev->stat_ops = &nsim_stat_ops; err = nsim_udp_tunnels_info_create(ns->nsim_dev, ns->netdev); if (err) From b4db9f840283caca0d904436f187ef56a9126eaa Mon Sep 17 00:00:00 2001 From: Jakub Kicinski Date: Thu, 4 Apr 2024 19:45:25 -0700 Subject: [PATCH 14/15] selftests: drivers: add scaffolding for Netlink tests in Python Add drivers/net as a target for mixed-use tests. The setup is expected to work similarly to the forwarding tests. Since we only need one interface (unlike forwarding tests) read the target device name from NETIF. If not present we'll try to run the test against netdevsim. Reviewed-by: Petr Machata Signed-off-by: Jakub Kicinski Signed-off-by: David S. Miller --- tools/testing/selftests/Makefile | 3 +- tools/testing/selftests/drivers/net/Makefile | 7 ++ .../testing/selftests/drivers/net/README.rst | 30 +++++ .../selftests/drivers/net/lib/py/__init__.py | 17 +++ .../selftests/drivers/net/lib/py/env.py | 52 ++++++++ .../testing/selftests/net/lib/py/__init__.py | 1 + tools/testing/selftests/net/lib/py/nsim.py | 115 ++++++++++++++++++ 7 files changed, 224 insertions(+), 1 deletion(-) create mode 100644 tools/testing/selftests/drivers/net/Makefile create mode 100644 tools/testing/selftests/drivers/net/README.rst create mode 100644 tools/testing/selftests/drivers/net/lib/py/__init__.py create mode 100644 tools/testing/selftests/drivers/net/lib/py/env.py create mode 100644 tools/testing/selftests/net/lib/py/nsim.py diff --git a/tools/testing/selftests/Makefile b/tools/testing/selftests/Makefile index f533eb7054fe..6dab886d6f7a 100644 --- a/tools/testing/selftests/Makefile +++ b/tools/testing/selftests/Makefile @@ -17,6 +17,7 @@ TARGETS += devices TARGETS += dmabuf-heaps TARGETS += drivers/dma-buf TARGETS += drivers/s390x/uvdevice +TARGETS += drivers/net TARGETS += drivers/net/bonding TARGETS += drivers/net/team TARGETS += dt @@ -117,7 +118,7 @@ TARGETS_HOTPLUG = cpu-hotplug TARGETS_HOTPLUG += memory-hotplug # Networking tests want the net/lib target, include it automatically -ifneq ($(filter net,$(TARGETS)),) +ifneq ($(filter net drivers/net,$(TARGETS)),) ifeq ($(filter net/lib,$(TARGETS)),) INSTALL_DEP_TARGETS := net/lib endif diff --git a/tools/testing/selftests/drivers/net/Makefile b/tools/testing/selftests/drivers/net/Makefile new file mode 100644 index 000000000000..379cdb1960a7 --- /dev/null +++ b/tools/testing/selftests/drivers/net/Makefile @@ -0,0 +1,7 @@ +# SPDX-License-Identifier: GPL-2.0 + +TEST_INCLUDES := $(wildcard lib/py/*.py) + +TEST_PROGS := stats.py + +include ../../lib.mk diff --git a/tools/testing/selftests/drivers/net/README.rst b/tools/testing/selftests/drivers/net/README.rst new file mode 100644 index 000000000000..5ef7c417d431 --- /dev/null +++ b/tools/testing/selftests/drivers/net/README.rst @@ -0,0 +1,30 @@ +Running tests +============= + +Tests are executed within kselftest framework like any other tests. +By default tests execute against software drivers such as netdevsim. +All tests must support running against a real device (SW-only tests +should instead be placed in net/ or drivers/net/netdevsim, HW-only +tests in drivers/net/hw). + +Set appropriate variables to point the tests at a real device. + +Variables +========= + +Variables can be set in the environment or by creating a net.config +file in the same directory as this README file. Example:: + + $ NETIF=eth0 ./some_test.sh + +or:: + + $ cat tools/testing/selftests/drivers/net/net.config + # Variable set in a file + NETIF=eth0 + +NETIF +~~~~~ + +Name of the netdevice against which the test should be executed. +When empty or not set software devices will be used. diff --git a/tools/testing/selftests/drivers/net/lib/py/__init__.py b/tools/testing/selftests/drivers/net/lib/py/__init__.py new file mode 100644 index 000000000000..4653dffcd962 --- /dev/null +++ b/tools/testing/selftests/drivers/net/lib/py/__init__.py @@ -0,0 +1,17 @@ +# SPDX-License-Identifier: GPL-2.0 + +import sys +from pathlib import Path + +KSFT_DIR = (Path(__file__).parent / "../../../..").resolve() + +try: + sys.path.append(KSFT_DIR.as_posix()) + from net.lib.py import * +except ModuleNotFoundError as e: + ksft_pr("Failed importing `net` library from kernel sources") + ksft_pr(str(e)) + ktap_result(True, comment="SKIP") + sys.exit(4) + +from .env import * diff --git a/tools/testing/selftests/drivers/net/lib/py/env.py b/tools/testing/selftests/drivers/net/lib/py/env.py new file mode 100644 index 000000000000..e1abe9491daf --- /dev/null +++ b/tools/testing/selftests/drivers/net/lib/py/env.py @@ -0,0 +1,52 @@ +# SPDX-License-Identifier: GPL-2.0 + +import os +import shlex +from pathlib import Path +from lib.py import ip +from lib.py import NetdevSimDev + +class NetDrvEnv: + def __init__(self, src_path): + self._ns = None + + self.env = os.environ.copy() + self._load_env_file(src_path) + + if 'NETIF' in self.env: + self.dev = ip("link show dev " + self.env['NETIF'], json=True)[0] + else: + self._ns = NetdevSimDev() + self.dev = self._ns.nsims[0].dev + self.ifindex = self.dev['ifindex'] + + def __enter__(self): + return self + + def __exit__(self, ex_type, ex_value, ex_tb): + """ + __exit__ gets called at the end of a "with" block. + """ + self.__del__() + + def __del__(self): + if self._ns: + self._ns.remove() + self._ns = None + + def _load_env_file(self, src_path): + src_dir = Path(src_path).parent.resolve() + if not (src_dir / "net.config").exists(): + return + + lexer = shlex.shlex(open((src_dir / "net.config").as_posix(), 'r').read()) + k = None + for token in lexer: + if k is None: + k = token + self.env[k] = "" + elif token == "=": + pass + else: + self.env[k] = token + k = None diff --git a/tools/testing/selftests/net/lib/py/__init__.py b/tools/testing/selftests/net/lib/py/__init__.py index 7823b5c1f8d7..ded7102df18a 100644 --- a/tools/testing/selftests/net/lib/py/__init__.py +++ b/tools/testing/selftests/net/lib/py/__init__.py @@ -2,5 +2,6 @@ from .consts import KSRC from .ksft import * +from .nsim import * from .utils import * from .ynl import NlError, YnlFamily, EthtoolFamily, NetdevFamily, RtnlFamily diff --git a/tools/testing/selftests/net/lib/py/nsim.py b/tools/testing/selftests/net/lib/py/nsim.py new file mode 100644 index 000000000000..b2d696e12805 --- /dev/null +++ b/tools/testing/selftests/net/lib/py/nsim.py @@ -0,0 +1,115 @@ +# SPDX-License-Identifier: GPL-2.0 + +import json +import os +import random +import re +import time +from .utils import cmd, ip + + +class NetdevSim: + """ + Class for netdevsim netdevice and its attributes. + """ + + def __init__(self, nsimdev, port_index, ifname, ns=None): + # In case udev renamed the netdev to according to new schema, + # check if the name matches the port_index. + nsimnamere = re.compile(r"eni\d+np(\d+)") + match = nsimnamere.match(ifname) + if match and int(match.groups()[0]) != port_index + 1: + raise Exception("netdevice name mismatches the expected one") + + self.nsimdev = nsimdev + self.port_index = port_index + ret = ip("-j link show dev %s" % ifname, ns=ns) + self.dev = json.loads(ret.stdout)[0] + + def dfs_write(self, path, val): + self.nsimdev.dfs_write(f'ports/{self.port_index}/' + path, val) + + +class NetdevSimDev: + """ + Class for netdevsim bus device and its attributes. + """ + @staticmethod + def ctrl_write(path, val): + fullpath = os.path.join("/sys/bus/netdevsim/", path) + with open(fullpath, "w") as f: + f.write(val) + + def dfs_write(self, path, val): + fullpath = os.path.join(f"/sys/kernel/debug/netdevsim/netdevsim{self.addr}/", path) + with open(fullpath, "w") as f: + f.write(val) + + def __init__(self, port_count=1, ns=None): + # nsim will spawn in init_net, we'll set to actual ns once we switch it there + self.ns = None + + if not os.path.exists("/sys/bus/netdevsim"): + cmd("modprobe netdevsim") + + addr = random.randrange(1 << 15) + while True: + try: + self.ctrl_write("new_device", "%u %u" % (addr, port_count)) + except OSError as e: + if e.errno == errno.ENOSPC: + addr = random.randrange(1 << 15) + continue + raise e + break + self.addr = addr + + # As probe of netdevsim device might happen from a workqueue, + # so wait here until all netdevs appear. + self.wait_for_netdevs(port_count) + + if ns: + cmd(f"devlink dev reload netdevsim/netdevsim{addr} netns {ns.name}") + self.ns = ns + + cmd("udevadm settle", ns=self.ns) + ifnames = self.get_ifnames() + + self.dfs_dir = "/sys/kernel/debug/netdevsim/netdevsim%u/" % addr + + self.nsims = [] + for port_index in range(port_count): + self.nsims.append(NetdevSim(self, port_index, ifnames[port_index], + ns=ns)) + + def get_ifnames(self): + ifnames = [] + listdir = cmd(f"ls /sys/bus/netdevsim/devices/netdevsim{self.addr}/net/", + ns=self.ns).stdout.split() + for ifname in listdir: + ifnames.append(ifname) + ifnames.sort() + return ifnames + + def wait_for_netdevs(self, port_count): + timeout = 5 + timeout_start = time.time() + + while True: + try: + ifnames = self.get_ifnames() + except FileNotFoundError as e: + ifnames = [] + if len(ifnames) == port_count: + break + if time.time() < timeout_start + timeout: + continue + raise Exception("netdevices did not appear within timeout") + + def remove(self): + self.ctrl_write("del_device", "%u" % (self.addr, )) + + def remove_nsim(self, nsim): + self.nsims.remove(nsim) + self.ctrl_write("devices/netdevsim%u/del_port" % (self.addr, ), + "%u" % (nsim.port_index, )) From f0e6c86e4bab228ca51b863af180ade0a970a393 Mon Sep 17 00:00:00 2001 From: Jakub Kicinski Date: Thu, 4 Apr 2024 19:45:26 -0700 Subject: [PATCH 15/15] testing: net-drv: add a driver test for stats reporting Add a very simple test to make sure drivers report expected stats. Drivers which implement FEC or pause configuration should report relevant stats. Qstats must be reported, at least packet and byte counts, and they must match total device stats. Tested with netdevsim, bnxt, in-tree and installed. Signed-off-by: Jakub Kicinski Signed-off-by: David S. Miller --- tools/testing/selftests/drivers/net/stats.py | 86 ++++++++++++++++++++ 1 file changed, 86 insertions(+) create mode 100755 tools/testing/selftests/drivers/net/stats.py diff --git a/tools/testing/selftests/drivers/net/stats.py b/tools/testing/selftests/drivers/net/stats.py new file mode 100755 index 000000000000..5a9d4e56b28b --- /dev/null +++ b/tools/testing/selftests/drivers/net/stats.py @@ -0,0 +1,86 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: GPL-2.0 + +from lib.py import ksft_run, ksft_in, ksft_true, KsftSkipEx, KsftXfailEx +from lib.py import EthtoolFamily, NetdevFamily, RtnlFamily, NlError +from lib.py import NetDrvEnv + +ethnl = EthtoolFamily() +netfam = NetdevFamily() +rtnl = RtnlFamily() + + +def check_pause(cfg) -> None: + global ethnl + + try: + ethnl.pause_get({"header": {"dev-index": cfg.ifindex}}) + except NlError as e: + if e.error == 95: + raise KsftXfailEx("pause not supported by the device") + raise + + data = ethnl.pause_get({"header": {"dev-index": cfg.ifindex, + "flags": {'stats'}}}) + ksft_true(data['stats'], "driver does not report stats") + + +def check_fec(cfg) -> None: + global ethnl + + try: + ethnl.fec_get({"header": {"dev-index": cfg.ifindex}}) + except NlError as e: + if e.error == 95: + raise KsftXfailEx("FEC not supported by the device") + raise + + data = ethnl.fec_get({"header": {"dev-index": cfg.ifindex, + "flags": {'stats'}}}) + ksft_true(data['stats'], "driver does not report stats") + + +def pkt_byte_sum(cfg) -> None: + global netfam, rtnl + + def get_qstat(test): + global netfam + stats = netfam.qstats_get({}, dump=True) + if stats: + for qs in stats: + if qs["ifindex"]== test.ifindex: + return qs + + qstat = get_qstat(cfg) + if qstat is None: + raise KsftSkipEx("qstats not supported by the device") + + for key in ['tx-packets', 'tx-bytes', 'rx-packets', 'rx-bytes']: + ksft_in(key, qstat, "Drivers should always report basic keys") + + # Compare stats, rtnl stats and qstats must match, + # but the interface may be up, so do a series of dumps + # each time the more "recent" stats must be higher or same. + def stat_cmp(rstat, qstat): + for key in ['tx-packets', 'tx-bytes', 'rx-packets', 'rx-bytes']: + if rstat[key] != qstat[key]: + return rstat[key] - qstat[key] + return 0 + + for _ in range(10): + rtstat = rtnl.getlink({"ifi-index": cfg.ifindex})['stats'] + if stat_cmp(rtstat, qstat) < 0: + raise Exception("RTNL stats are lower, fetched later") + qstat = get_qstat(cfg) + if stat_cmp(rtstat, qstat) > 0: + raise Exception("Qstats are lower, fetched later") + + +def main() -> None: + with NetDrvEnv(__file__) as cfg: + ksft_run([check_pause, check_fec, pkt_byte_sum], + args=(cfg, )) + + +if __name__ == "__main__": + main()